This commit is contained in:
MichaelDvP
2024-02-15 09:08:01 +01:00
110 changed files with 1942 additions and 1923 deletions

14
.gitignore vendored
View File

@@ -2,7 +2,7 @@
.vscode/c_cpp_properties.json .vscode/c_cpp_properties.json
.vscode/extensions.json .vscode/extensions.json
.vscode/launch.json .vscode/launch.json
# .vscode/settings.json .vscode/settings.json
# c++ compiling # c++ compiling
.clang_complete .clang_complete
@@ -12,11 +12,11 @@ cppcheck.out.xml
# platformio # platformio
.pio .pio
pio_local.ini pio_local.ini
*_old
# OS specific # OS specific
.DS_Store .DS_Store
*Thumbs.db *Thumbs.db
emsesp
# web specfic # web specfic
build/ build/
@@ -43,7 +43,7 @@ interface/analyse.html
test.sh test.sh
scripts/run.sh scripts/run.sh
scripts/__pycache__ scripts/__pycache__
/scripts/stackdmp.txt scripts/stackdmp.txt
# i18n generated files # i18n generated files
interface/src/i18n/i18n-react.tsx interface/src/i18n/i18n-react.tsx
@@ -57,9 +57,5 @@ interface/src/i18n/i18n-util.async.ts
sonar/ sonar/
bw-output/ bw-output/
# entity dump results # testing
# dump_entities.csv emsesp
# dump_entities.xls*
*_old

View File

@@ -5,6 +5,7 @@
## **IMPORTANT! BREAKING CHANGES** ## **IMPORTANT! BREAKING CHANGES**
- new device WATER shows dhw entities from MM100 and SM100 in dhw setting - new device WATER shows dhw entities from MM100 and SM100 in dhw setting
- The Wifi Tx Power setting in Network Settings will be reset to Auto
## Added ## Added
@@ -31,6 +32,9 @@
- MQTT autodiscovery in Domoticz not working [#1360](https://github.com/emsesp/EMS-ESP32/issues/1528) - MQTT autodiscovery in Domoticz not working [#1360](https://github.com/emsesp/EMS-ESP32/issues/1528)
- dhw comfort for new ems+, [#1495](https://github.com/emsesp/EMS-ESP32/issues/1495) - dhw comfort for new ems+, [#1495](https://github.com/emsesp/EMS-ESP32/issues/1495)
- added writeable icon to Web's Custom Entity page for each entity shown in the table - added writeable icon to Web's Custom Entity page for each entity shown in the table
- Wifi Tx Power not adjusted [#1614](https://github.com/emsesp/EMS-ESP32/issues/1614)
- MQTT discovery of custom entity doesn't consider type of data [#1587](https://github.com/emsesp/EMS-ESP32/issues/1587)
- WiFi TxPower wasn't correctly used. Added an 'Auto' setting, which is the default.
## Changed ## Changed
@@ -38,3 +42,5 @@
- HA don't set entity_category to Diagnostic/Configuration for EMS entities [#1459](https://github.com/emsesp/EMS-ESP32/discussions/1459) - HA don't set entity_category to Diagnostic/Configuration for EMS entities [#1459](https://github.com/emsesp/EMS-ESP32/discussions/1459)
- upgraded ArduinoJson to 7.0.0 #1538 and then 7.0.2 - upgraded ArduinoJson to 7.0.0 #1538 and then 7.0.2
- small changes to the API for analog and temperature sensors - small changes to the API for analog and temperature sensors
- Length of mqtt Broker adress [#1619](https://github.com/emsesp/EMS-ESP32/issues/1619)
- C++ optimizations - see <https://github.com/emsesp/EMS-ESP32/pull/1615>

View File

@@ -36,7 +36,6 @@ build_flags =
-D FACTORY_MQTT_PORT=1883 -D FACTORY_MQTT_PORT=1883
-D FACTORY_MQTT_USERNAME=\"\" -D FACTORY_MQTT_USERNAME=\"\"
-D FACTORY_MQTT_PASSWORD=\"\" -D FACTORY_MQTT_PASSWORD=\"\"
-D FACTORY_MQTT_CLIENT_ID=\"ems-esp\"
-D FACTORY_MQTT_KEEP_ALIVE=60 -D FACTORY_MQTT_KEEP_ALIVE=60
-D FACTORY_MQTT_CLEAN_SESSION=false -D FACTORY_MQTT_CLEAN_SESSION=false
-D FACTORY_MQTT_MAX_TOPIC_LENGTH=128 -D FACTORY_MQTT_MAX_TOPIC_LENGTH=128

View File

@@ -5,8 +5,8 @@
}, },
"extends": [ "extends": [
"eslint:recommended", "eslint:recommended",
"airbnb/hooks", // "airbnb/hooks",
"airbnb-typescript", // "airbnb-typescript",
"plugin:react/recommended", "plugin:react/recommended",
"plugin:react/jsx-runtime", "plugin:react/jsx-runtime",
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",

View File

@@ -26,8 +26,8 @@
"@babel/core": "^7.23.9", "@babel/core": "^7.23.9",
"@emotion/react": "^11.11.3", "@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.15.9", "@mui/icons-material": "^5.15.10",
"@mui/material": "^5.15.9", "@mui/material": "^5.15.10",
"@table-library/react-table-library": "4.1.7", "@table-library/react-table-library": "4.1.7",
"@types/imagemin": "^8.0.5", "@types/imagemin": "^8.0.5",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
@@ -54,12 +54,10 @@
"devDependencies": { "devDependencies": {
"@preact/compat": "^17.1.2", "@preact/compat": "^17.1.2",
"@preact/preset-vite": "^2.8.1", "@preact/preset-vite": "^2.8.1",
"@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/eslint-plugin": "^7.0.1",
"@typescript-eslint/parser": "^6.21.0", "@typescript-eslint/parser": "^7.0.1",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^17.1.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.1", "eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-autofix": "^1.1.0", "eslint-plugin-autofix": "^1.1.0",
@@ -72,7 +70,7 @@
"prettier": "^3.2.5", "prettier": "^3.2.5",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"terser": "^5.27.0", "terser": "^5.27.0",
"vite": "^5.1.1", "vite": "^5.1.2",
"vite-plugin-imagemin": "^0.6.1", "vite-plugin-imagemin": "^0.6.1",
"vite-tsconfig-paths": "^4.3.1" "vite-tsconfig-paths": "^4.3.1"
}, },

View File

@@ -2,6 +2,7 @@ import { readdirSync, existsSync, unlinkSync, readFileSync, createWriteStream }
import { resolve, relative, sep } from 'path'; import { resolve, relative, sep } from 'path';
import zlib from 'zlib'; import zlib from 'zlib';
import mime from 'mime-types'; import mime from 'mime-types';
import crypto from 'crypto';
const ARDUINO_INCLUDES = '#include <Arduino.h>\n\n'; const ARDUINO_INCLUDES = '#include <Arduino.h>\n\n';
const INDENT = ' '; const INDENT = ' ';
@@ -11,14 +12,17 @@ const bytesPerLine = 20;
var totalSize = 0; var totalSize = 0;
const generateWWWClass = () => const generateWWWClass = () =>
`typedef std::function<void(const String& uri, const String& contentType, const uint8_t * content, size_t len)> RouteRegistrationHandler; `typedef std::function<void(const String& uri, const String& contentType, const uint8_t * content, size_t len, const String& hash)> RouteRegistrationHandler;
// Total size is ${totalSize} bytes // Total size is ${totalSize} bytes
class WWWData { class WWWData {
${indent}public: ${indent}public:
${indent.repeat(2)}static void registerRoutes(RouteRegistrationHandler handler) { ${indent.repeat(2)}static void registerRoutes(RouteRegistrationHandler handler) {
${fileInfo ${fileInfo
.map((file) => `${indent.repeat(3)}handler("${file.uri}", "${file.mimeType}", ${file.variable}, ${file.size});`) .map(
(file) =>
`${indent.repeat(3)}handler("${file.uri}", "${file.mimeType}", ${file.variable}, ${file.size}, "${file.hash}");`
)
.join('\n')} .join('\n')}
${indent.repeat(2)}} ${indent.repeat(2)}}
}; };
@@ -50,6 +54,12 @@ const writeFile = (relativeFilePath, buffer) => {
writeStream.write('const uint8_t ' + variable + '[] = {'); writeStream.write('const uint8_t ' + variable + '[] = {');
// const zipBuffer = zlib.brotliCompressSync(buffer, { quality: 1 }); // const zipBuffer = zlib.brotliCompressSync(buffer, { quality: 1 });
const zipBuffer = zlib.gzipSync(buffer, { level: 9 }); const zipBuffer = zlib.gzipSync(buffer, { level: 9 });
// create sha
const hashSum = crypto.createHash('sha256');
hashSum.update(zipBuffer);
const hash = hashSum.digest('hex');
zipBuffer.forEach((b) => { zipBuffer.forEach((b) => {
if (!(size % bytesPerLine)) { if (!(size % bytesPerLine)) {
writeStream.write('\n'); writeStream.write('\n');
@@ -58,15 +68,19 @@ const writeFile = (relativeFilePath, buffer) => {
writeStream.write('0x' + ('00' + b.toString(16).toUpperCase()).slice(-2) + ','); writeStream.write('0x' + ('00' + b.toString(16).toUpperCase()).slice(-2) + ',');
size++; size++;
}); });
if (size % bytesPerLine) { if (size % bytesPerLine) {
writeStream.write('\n'); writeStream.write('\n');
} }
writeStream.write('};\n\n'); writeStream.write('};\n\n');
fileInfo.push({ fileInfo.push({
uri: '/' + relativeFilePath.replace(sep, '/'), uri: '/' + relativeFilePath.replace(sep, '/'),
mimeType, mimeType,
variable, variable,
size size,
hash
}); });
// console.log(relativeFilePath + ' (size ' + size + ' bytes)'); // console.log(relativeFilePath + ' (size ' + size + ' bytes)');

View File

@@ -72,6 +72,7 @@ const MqttSettingsForm: FC = () => {
name="host" name="host"
label={LL.ADDRESS_OF(LL.BROKER())} label={LL.ADDRESS_OF(LL.BROKER())}
fullWidth fullWidth
multiline
variant="outlined" variant="outlined"
value={data.host} value={data.host}
onChange={updateFormValue} onChange={updateFormValue}

View File

@@ -15,8 +15,8 @@ import {
ListItemSecondaryAction, ListItemSecondaryAction,
ListItemText, ListItemText,
Typography, Typography,
InputAdornment, TextField,
TextField MenuItem
} from '@mui/material'; } from '@mui/material';
// eslint-disable-next-line import/named // eslint-disable-next-line import/named
import { updateState, useRequest } from 'alova'; import { updateState, useRequest } from 'alova';
@@ -43,7 +43,7 @@ import {
} from 'components'; } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValueDirty, useRest } from 'utils'; import { updateValueDirty, useRest } from 'utils';
import { validate } from 'validators'; import { validate } from 'validators';
import { createNetworkSettingsValidator } from 'validators/network'; import { createNetworkSettingsValidator } from 'validators/network';
@@ -88,7 +88,7 @@ const WiFiSettingsForm: FC = () => {
static_ip_config: false, static_ip_config: false,
enableIPv6: false, enableIPv6: false,
bandwidth20: false, bandwidth20: false,
tx_power: 20, tx_power: 0,
nosleep: false, nosleep: false,
enableMDNS: true, enableMDNS: true,
enableCORS: false, enableCORS: false,
@@ -196,20 +196,27 @@ const WiFiSettingsForm: FC = () => {
margin="normal" margin="normal"
/> />
)} )}
<ValidatedTextField <TextField
fieldErrors={fieldErrors}
name="tx_power" name="tx_power"
label={LL.TX_POWER()} label={LL.TX_POWER()}
InputProps={{
endAdornment: <InputAdornment position="end">dBm</InputAdornment>
}}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={numberValue(data.tx_power)} value={data.tx_power}
onChange={updateFormValue} onChange={updateFormValue}
type="number"
margin="normal" margin="normal"
/> select
>
<MenuItem value={0}>Auto</MenuItem>
<MenuItem value={78}>19.5 dBm</MenuItem>
<MenuItem value={76}>19 dBm</MenuItem>
<MenuItem value={74}>18.5 dBm</MenuItem>
<MenuItem value={68}>17 dBm</MenuItem>
<MenuItem value={60}>15 dBm</MenuItem>
<MenuItem value={52}>13 dBm</MenuItem>
<MenuItem value={44}>11 dBm</MenuItem>
<MenuItem value={34}>8.5 dBm</MenuItem>
<MenuItem value={28}>7 dBm</MenuItem>
</TextField>
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="nosleep" checked={data.nosleep} onChange={updateFormValue} />} control={<Checkbox name="nosleep" checked={data.nosleep} onChange={updateFormValue} />}
label={LL.NETWORK_DISABLE_SLEEP()} label={LL.NETWORK_DISABLE_SLEEP()}

View File

@@ -14,9 +14,5 @@ export const createNetworkSettingsValidator = (networkSettings: NetworkSettings)
subnet_mask: [{ required: true, message: 'Subnet mask is required' }, IP_ADDRESS_VALIDATOR], subnet_mask: [{ required: true, message: 'Subnet mask is required' }, IP_ADDRESS_VALIDATOR],
dns_ip_1: IP_ADDRESS_VALIDATOR, dns_ip_1: IP_ADDRESS_VALIDATOR,
dns_ip_2: IP_ADDRESS_VALIDATOR dns_ip_2: IP_ADDRESS_VALIDATOR
}), })
tx_power: [
{ required: true, message: 'Tx Power is required' },
{ type: 'number', min: 0, max: 20, message: 'Tx Power must be between 0 and 20dBm' }
]
}); });

View File

@@ -33,7 +33,7 @@ export const IP_ADDRESS_VALIDATOR = {
} }
}; };
const HOSTNAME_LENGTH_REGEXP = /^.{0,63}$/; const HOSTNAME_LENGTH_REGEXP = /^.{0,200}$/;
const HOSTNAME_PATTERN_REGEXP = const HOSTNAME_PATTERN_REGEXP =
/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/; /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/;
@@ -42,7 +42,7 @@ const isValidHostname = (value: string) => HOSTNAME_LENGTH_REGEXP.test(value) &&
export const HOSTNAME_VALIDATOR = { export const HOSTNAME_VALIDATOR = {
validator(rule: InternalRuleItem, value: string, callback: (error?: string) => void) { validator(rule: InternalRuleItem, value: string, callback: (error?: string) => void) {
if (value && !isValidHostname(value)) { if (value && !isValidHostname(value)) {
callback('Must be a valid hostname of up to 63 characters'); callback('Must be a valid hostname');
} else { } else {
callback(); callback();
} }
@@ -52,7 +52,7 @@ export const HOSTNAME_VALIDATOR = {
export const IP_OR_HOSTNAME_VALIDATOR = { export const IP_OR_HOSTNAME_VALIDATOR = {
validator(rule: InternalRuleItem, value: string, callback: (error?: string) => void) { validator(rule: InternalRuleItem, value: string, callback: (error?: string) => void) {
if (value && !(isValidIpAddress(value) || isValidHostname(value))) { if (value && !(isValidIpAddress(value) || isValidHostname(value))) {
callback('Must be a valid IP address or hostname of up to 63 characters'); callback('Must be a valid IP address or hostname');
} else { } else {
callback(); callback();
} }

View File

@@ -1014,16 +1014,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@mui/core-downloads-tracker@npm:^5.15.9": "@mui/core-downloads-tracker@npm:^5.15.10":
version: 5.15.9 version: 5.15.10
resolution: "@mui/core-downloads-tracker@npm:5.15.9" resolution: "@mui/core-downloads-tracker@npm:5.15.10"
checksum: 10/f0f7af8e8f6f50df29a4e41ecb59c75869f760f22df0dc534476094089c74edcd7eacb4d17e636e0b7dd06ea1f3bb6564b21dbe072f89d1b9d87373760d69e2b checksum: 10/aeb16b31f60c08cc03585fedadceadd54aa48dda394fb945ab885f884c1b1692efb72309465641b6ca2367bd53d5fdce15f189d4691f42b59206622ffb2d6f0f
languageName: node languageName: node
linkType: hard linkType: hard
"@mui/icons-material@npm:^5.15.9": "@mui/icons-material@npm:^5.15.10":
version: 5.15.9 version: 5.15.10
resolution: "@mui/icons-material@npm:5.15.9" resolution: "@mui/icons-material@npm:5.15.10"
dependencies: dependencies:
"@babel/runtime": "npm:^7.23.9" "@babel/runtime": "npm:^7.23.9"
peerDependencies: peerDependencies:
@@ -1033,17 +1033,17 @@ __metadata:
peerDependenciesMeta: peerDependenciesMeta:
"@types/react": "@types/react":
optional: true optional: true
checksum: 10/bcda24107125108569fe8252d05297f441362d33dbb96f8c32b35ac6d280a3c9a2f03548344c73d316e26c89d2d3e74057b292be6677ab1b582d94b6cf3ba100 checksum: 10/ce22c02dc7ed960a21f8d5ea7c4d4fc03d9f71e8a26ced02f75da1ffd6c768e6fa0682a308a03be53bffc2325a5aaf68be69f9e192b0a57c6752f7548e5b9045
languageName: node languageName: node
linkType: hard linkType: hard
"@mui/material@npm:^5.15.9": "@mui/material@npm:^5.15.10":
version: 5.15.9 version: 5.15.10
resolution: "@mui/material@npm:5.15.9" resolution: "@mui/material@npm:5.15.10"
dependencies: dependencies:
"@babel/runtime": "npm:^7.23.9" "@babel/runtime": "npm:^7.23.9"
"@mui/base": "npm:5.0.0-beta.36" "@mui/base": "npm:5.0.0-beta.36"
"@mui/core-downloads-tracker": "npm:^5.15.9" "@mui/core-downloads-tracker": "npm:^5.15.10"
"@mui/system": "npm:^5.15.9" "@mui/system": "npm:^5.15.9"
"@mui/types": "npm:^7.2.13" "@mui/types": "npm:^7.2.13"
"@mui/utils": "npm:^5.15.9" "@mui/utils": "npm:^5.15.9"
@@ -1066,7 +1066,7 @@ __metadata:
optional: true optional: true
"@types/react": "@types/react":
optional: true optional: true
checksum: 10/fbbb33f83520f2f0a31d7a75be02c3c038da0bd2d2a914eadbe890783f18e9a93f818ea93da21cc6a6c303352662b4da764c67094183cee5133f810ffabead07 checksum: 10/a88ad1287a905549ed516742544c8ba32f0cd7e1b184564efc8ceba5f43060d37b5cd113db605f1bb5be6c74cbdad7321d3fd7df410ba33d55548cf7c5bbf8d0
languageName: node languageName: node
linkType: hard linkType: hard
@@ -1713,15 +1713,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/eslint-plugin@npm:^6.21.0": "@typescript-eslint/eslint-plugin@npm:^7.0.1":
version: 6.21.0 version: 7.0.1
resolution: "@typescript-eslint/eslint-plugin@npm:6.21.0" resolution: "@typescript-eslint/eslint-plugin@npm:7.0.1"
dependencies: dependencies:
"@eslint-community/regexpp": "npm:^4.5.1" "@eslint-community/regexpp": "npm:^4.5.1"
"@typescript-eslint/scope-manager": "npm:6.21.0" "@typescript-eslint/scope-manager": "npm:7.0.1"
"@typescript-eslint/type-utils": "npm:6.21.0" "@typescript-eslint/type-utils": "npm:7.0.1"
"@typescript-eslint/utils": "npm:6.21.0" "@typescript-eslint/utils": "npm:7.0.1"
"@typescript-eslint/visitor-keys": "npm:6.21.0" "@typescript-eslint/visitor-keys": "npm:7.0.1"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
graphemer: "npm:^1.4.0" graphemer: "npm:^1.4.0"
ignore: "npm:^5.2.4" ignore: "npm:^5.2.4"
@@ -1729,73 +1729,73 @@ __metadata:
semver: "npm:^7.5.4" semver: "npm:^7.5.4"
ts-api-utils: "npm:^1.0.1" ts-api-utils: "npm:^1.0.1"
peerDependencies: peerDependencies:
"@typescript-eslint/parser": ^6.0.0 || ^6.0.0-alpha "@typescript-eslint/parser": ^7.0.0
eslint: ^7.0.0 || ^8.0.0 eslint: ^8.56.0
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 10/a57de0f630789330204cc1531f86cfc68b391cafb1ba67c8992133f1baa2a09d629df66e71260b040de4c9a3ff1252952037093c4128b0d56c4dbb37720b4c1d checksum: 10/0862e8ec8677fcea794394fc9eab8dba11043c08452722790e0d296d4ee84713180676e1e3135be4203ace7bb73933c94159255cb9190c7bc13bf7f03a361915
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/parser@npm:^6.21.0": "@typescript-eslint/parser@npm:^7.0.1":
version: 6.21.0 version: 7.0.1
resolution: "@typescript-eslint/parser@npm:6.21.0" resolution: "@typescript-eslint/parser@npm:7.0.1"
dependencies: dependencies:
"@typescript-eslint/scope-manager": "npm:6.21.0" "@typescript-eslint/scope-manager": "npm:7.0.1"
"@typescript-eslint/types": "npm:6.21.0" "@typescript-eslint/types": "npm:7.0.1"
"@typescript-eslint/typescript-estree": "npm:6.21.0" "@typescript-eslint/typescript-estree": "npm:7.0.1"
"@typescript-eslint/visitor-keys": "npm:6.21.0" "@typescript-eslint/visitor-keys": "npm:7.0.1"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
peerDependencies: peerDependencies:
eslint: ^7.0.0 || ^8.0.0 eslint: ^8.56.0
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 10/4d51cdbc170e72275efc5ef5fce48a81ec431e4edde8374f4d0213d8d370a06823e1a61ae31d502a5f1b0d1f48fc4d29a1b1b5c2dcf809d66d3872ccf6e46ac7 checksum: 10/b4ba1743ab730268a1924139f072e4a0a56959526fb6377e1b3964518b6c6851733ae446a44d29fed1cb96669e2913cca524895ce77a6205aaed8bda00e8cd5d
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/scope-manager@npm:6.21.0": "@typescript-eslint/scope-manager@npm:7.0.1":
version: 6.21.0 version: 7.0.1
resolution: "@typescript-eslint/scope-manager@npm:6.21.0" resolution: "@typescript-eslint/scope-manager@npm:7.0.1"
dependencies: dependencies:
"@typescript-eslint/types": "npm:6.21.0" "@typescript-eslint/types": "npm:7.0.1"
"@typescript-eslint/visitor-keys": "npm:6.21.0" "@typescript-eslint/visitor-keys": "npm:7.0.1"
checksum: 10/fe91ac52ca8e09356a71dc1a2f2c326480f3cccfec6b2b6d9154c1a90651ab8ea270b07c67df5678956c3bbf0bbe7113ab68f68f21b20912ea528b1214197395 checksum: 10/dade6055bb853adb54de795cc3da5ab8550236d4186f108573fdb02e636ab7fc4300a55b506698ced4087ca43b143a5593931cb3195ab4790470b456d9ff8846
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/type-utils@npm:6.21.0": "@typescript-eslint/type-utils@npm:7.0.1":
version: 6.21.0 version: 7.0.1
resolution: "@typescript-eslint/type-utils@npm:6.21.0" resolution: "@typescript-eslint/type-utils@npm:7.0.1"
dependencies: dependencies:
"@typescript-eslint/typescript-estree": "npm:6.21.0" "@typescript-eslint/typescript-estree": "npm:7.0.1"
"@typescript-eslint/utils": "npm:6.21.0" "@typescript-eslint/utils": "npm:7.0.1"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
ts-api-utils: "npm:^1.0.1" ts-api-utils: "npm:^1.0.1"
peerDependencies: peerDependencies:
eslint: ^7.0.0 || ^8.0.0 eslint: ^8.56.0
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 10/d03fb3ee1caa71f3ce053505f1866268d7ed79ffb7fed18623f4a1253f5b8f2ffc92636d6fd08fcbaf5bd265a6de77bf192c53105131e4724643dfc910d705fc checksum: 10/cf20a3c0e56121ac62467e48121e135798db6d2999bd4f96ed44edc39f2597812d12b1bd6a378adec54d6c5e7db75fa5f98a27ce399792a2c8a5bbd3649952f7
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/types@npm:6.21.0": "@typescript-eslint/types@npm:7.0.1":
version: 6.21.0 version: 7.0.1
resolution: "@typescript-eslint/types@npm:6.21.0" resolution: "@typescript-eslint/types@npm:7.0.1"
checksum: 10/e26da86d6f36ca5b6ef6322619f8ec55aabcd7d43c840c977ae13ae2c964c3091fc92eb33730d8be08927c9de38466c5323e78bfb270a9ff1d3611fe821046c5 checksum: 10/c08b2d34bab2a877a45a1e4c2923f50d03022b682b7aaba929ae2a9a5ad32db0e46265544a6616ccb98654b434250621be0e282fc5b21b8ccaf6b78741d68f67
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/typescript-estree@npm:6.21.0": "@typescript-eslint/typescript-estree@npm:7.0.1":
version: 6.21.0 version: 7.0.1
resolution: "@typescript-eslint/typescript-estree@npm:6.21.0" resolution: "@typescript-eslint/typescript-estree@npm:7.0.1"
dependencies: dependencies:
"@typescript-eslint/types": "npm:6.21.0" "@typescript-eslint/types": "npm:7.0.1"
"@typescript-eslint/visitor-keys": "npm:6.21.0" "@typescript-eslint/visitor-keys": "npm:7.0.1"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
globby: "npm:^11.1.0" globby: "npm:^11.1.0"
is-glob: "npm:^4.0.3" is-glob: "npm:^4.0.3"
@@ -1805,34 +1805,34 @@ __metadata:
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 10/b32fa35fca2a229e0f5f06793e5359ff9269f63e9705e858df95d55ca2cd7fdb5b3e75b284095a992c48c5fc46a1431a1a4b6747ede2dd08929dc1cbacc589b8 checksum: 10/b0b0adc84502d1ffcf3a0024179e0f2780be5f8b0a18328db46d430efc4e38a7965656b4392dd47d6176bbb1ee200aec6dd8581c39b606e260750574358cde9f
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/utils@npm:6.21.0": "@typescript-eslint/utils@npm:7.0.1":
version: 6.21.0 version: 7.0.1
resolution: "@typescript-eslint/utils@npm:6.21.0" resolution: "@typescript-eslint/utils@npm:7.0.1"
dependencies: dependencies:
"@eslint-community/eslint-utils": "npm:^4.4.0" "@eslint-community/eslint-utils": "npm:^4.4.0"
"@types/json-schema": "npm:^7.0.12" "@types/json-schema": "npm:^7.0.12"
"@types/semver": "npm:^7.5.0" "@types/semver": "npm:^7.5.0"
"@typescript-eslint/scope-manager": "npm:6.21.0" "@typescript-eslint/scope-manager": "npm:7.0.1"
"@typescript-eslint/types": "npm:6.21.0" "@typescript-eslint/types": "npm:7.0.1"
"@typescript-eslint/typescript-estree": "npm:6.21.0" "@typescript-eslint/typescript-estree": "npm:7.0.1"
semver: "npm:^7.5.4" semver: "npm:^7.5.4"
peerDependencies: peerDependencies:
eslint: ^7.0.0 || ^8.0.0 eslint: ^8.56.0
checksum: 10/b404a2c55a425a79d054346ae123087d30c7ecf7ed7abcf680c47bf70c1de4fabadc63434f3f460b2fa63df76bc9e4a0b9fa2383bb8a9fcd62733fb5c4e4f3e3 checksum: 10/b7e0cb2994f73b3f416684dc175d4e1da5f8306d6c81abbad2f219fa3e4f29154063a3c9568e4a1f879a38b79c62250e596e4ed7265f7bd1ed9b3db806cb92b7
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/visitor-keys@npm:6.21.0": "@typescript-eslint/visitor-keys@npm:7.0.1":
version: 6.21.0 version: 7.0.1
resolution: "@typescript-eslint/visitor-keys@npm:6.21.0" resolution: "@typescript-eslint/visitor-keys@npm:7.0.1"
dependencies: dependencies:
"@typescript-eslint/types": "npm:6.21.0" "@typescript-eslint/types": "npm:7.0.1"
eslint-visitor-keys: "npm:^3.4.1" eslint-visitor-keys: "npm:^3.4.1"
checksum: 10/30422cdc1e2ffad203df40351a031254b272f9c6f2b7e02e9bfa39e3fc2c7b1c6130333b0057412968deda17a3a68a578a78929a8139c6acef44d9d841dc72e1 checksum: 10/915c5b19302a4c76e843cd2d04a9a2b11907e658d7018c8b55c338b090d9115d3719809aa05b8af130cc1b216c77626d210c20f705b732e83d04ceae0c112f6b
languageName: node languageName: node
linkType: hard linkType: hard
@@ -1851,8 +1851,8 @@ __metadata:
"@babel/core": "npm:^7.23.9" "@babel/core": "npm:^7.23.9"
"@emotion/react": "npm:^11.11.3" "@emotion/react": "npm:^11.11.3"
"@emotion/styled": "npm:^11.11.0" "@emotion/styled": "npm:^11.11.0"
"@mui/icons-material": "npm:^5.15.9" "@mui/icons-material": "npm:^5.15.10"
"@mui/material": "npm:^5.15.9" "@mui/material": "npm:^5.15.10"
"@preact/compat": "npm:^17.1.2" "@preact/compat": "npm:^17.1.2"
"@preact/preset-vite": "npm:^2.8.1" "@preact/preset-vite": "npm:^2.8.1"
"@table-library/react-table-library": "npm:4.1.7" "@table-library/react-table-library": "npm:4.1.7"
@@ -1862,14 +1862,12 @@ __metadata:
"@types/react": "npm:^18.2.55" "@types/react": "npm:^18.2.55"
"@types/react-dom": "npm:^18.2.19" "@types/react-dom": "npm:^18.2.19"
"@types/react-router-dom": "npm:^5.3.3" "@types/react-router-dom": "npm:^5.3.3"
"@typescript-eslint/eslint-plugin": "npm:^6.21.0" "@typescript-eslint/eslint-plugin": "npm:^7.0.1"
"@typescript-eslint/parser": "npm:^6.21.0" "@typescript-eslint/parser": "npm:^7.0.1"
alova: "npm:^2.17.0" alova: "npm:^2.17.0"
async-validator: "npm:^4.2.5" async-validator: "npm:^4.2.5"
concurrently: "npm:^8.2.2" concurrently: "npm:^8.2.2"
eslint: "npm:^8.56.0" eslint: "npm:^8.56.0"
eslint-config-airbnb: "npm:^19.0.4"
eslint-config-airbnb-typescript: "npm:^17.1.0"
eslint-config-prettier: "npm:^9.1.0" eslint-config-prettier: "npm:^9.1.0"
eslint-import-resolver-typescript: "npm:^3.6.1" eslint-import-resolver-typescript: "npm:^3.6.1"
eslint-plugin-autofix: "npm:^1.1.0" eslint-plugin-autofix: "npm:^1.1.0"
@@ -1895,7 +1893,7 @@ __metadata:
terser: "npm:^5.27.0" terser: "npm:^5.27.0"
typesafe-i18n: "npm:^5.26.2" typesafe-i18n: "npm:^5.26.2"
typescript: "npm:^5.3.3" typescript: "npm:^5.3.3"
vite: "npm:^5.1.1" vite: "npm:^5.1.2"
vite-plugin-imagemin: "npm:^0.6.1" vite-plugin-imagemin: "npm:^0.6.1"
vite-tsconfig-paths: "npm:^4.3.1" vite-tsconfig-paths: "npm:^4.3.1"
languageName: unknown languageName: unknown
@@ -2738,13 +2736,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"confusing-browser-globals@npm:^1.0.10":
version: 1.0.11
resolution: "confusing-browser-globals@npm:1.0.11"
checksum: 10/3afc635abd37e566477f610e7978b15753f0e84025c25d49236f1f14d480117185516bdd40d2a2167e6bed8048641a9854964b9c067e3dcdfa6b5d0ad3c3a5ef
languageName: node
linkType: hard
"console-control-strings@npm:^1.1.0": "console-control-strings@npm:^1.1.0":
version: 1.1.0 version: 1.1.0
resolution: "console-control-strings@npm:1.1.0" resolution: "console-control-strings@npm:1.1.0"
@@ -3790,52 +3781,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"eslint-config-airbnb-base@npm:^15.0.0":
version: 15.0.0
resolution: "eslint-config-airbnb-base@npm:15.0.0"
dependencies:
confusing-browser-globals: "npm:^1.0.10"
object.assign: "npm:^4.1.2"
object.entries: "npm:^1.1.5"
semver: "npm:^6.3.0"
peerDependencies:
eslint: ^7.32.0 || ^8.2.0
eslint-plugin-import: ^2.25.2
checksum: 10/daa68a1dcb7bff338747a952723b5fa9d159980ec3554c395a4b52a7f7d4f00a45e7b465420eb6d4d87a82cef6361e4cfd6dbb38c2f3f52f2140b6cf13654803
languageName: node
linkType: hard
"eslint-config-airbnb-typescript@npm:^17.1.0":
version: 17.1.0
resolution: "eslint-config-airbnb-typescript@npm:17.1.0"
dependencies:
eslint-config-airbnb-base: "npm:^15.0.0"
peerDependencies:
"@typescript-eslint/eslint-plugin": ^5.13.0 || ^6.0.0
"@typescript-eslint/parser": ^5.0.0 || ^6.0.0
eslint: ^7.32.0 || ^8.2.0
eslint-plugin-import: ^2.25.3
checksum: 10/a2238d820909ac005704e04d29ed495cebbe024869c488330273ea108e18cbf74b6b13e09d54d22a598fe793b9ed5ae593a7e8f9bdc6ea17614d5f2add340960
languageName: node
linkType: hard
"eslint-config-airbnb@npm:^19.0.4":
version: 19.0.4
resolution: "eslint-config-airbnb@npm:19.0.4"
dependencies:
eslint-config-airbnb-base: "npm:^15.0.0"
object.assign: "npm:^4.1.2"
object.entries: "npm:^1.1.5"
peerDependencies:
eslint: ^7.32.0 || ^8.2.0
eslint-plugin-import: ^2.25.3
eslint-plugin-jsx-a11y: ^6.5.1
eslint-plugin-react: ^7.28.0
eslint-plugin-react-hooks: ^4.3.0
checksum: 10/f2086523cfd20c42fd620c757281bd028aa8ce9dadc7293c5c23ea60947a2d3ca04404ede77b40f5a65250fe3c04502acafc4f2f6946819fe6c257d76d9644e5
languageName: node
linkType: hard
"eslint-config-prettier@npm:^9.1.0": "eslint-config-prettier@npm:^9.1.0":
version: 9.1.0 version: 9.1.0
resolution: "eslint-config-prettier@npm:9.1.0" resolution: "eslint-config-prettier@npm:9.1.0"
@@ -6603,7 +6548,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"object.assign@npm:^4.1.2, object.assign@npm:^4.1.4": "object.assign@npm:^4.1.4":
version: 4.1.4 version: 4.1.4
resolution: "object.assign@npm:4.1.4" resolution: "object.assign@npm:4.1.4"
dependencies: dependencies:
@@ -6615,7 +6560,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"object.entries@npm:^1.1.5, object.entries@npm:^1.1.6, object.entries@npm:^1.1.7": "object.entries@npm:^1.1.6, object.entries@npm:^1.1.7":
version: 1.1.7 version: 1.1.7
resolution: "object.entries@npm:1.1.7" resolution: "object.entries@npm:1.1.7"
dependencies: dependencies:
@@ -7739,7 +7684,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"semver@npm:^6.0.0, semver@npm:^6.3.0, semver@npm:^6.3.1": "semver@npm:^6.0.0, semver@npm:^6.3.1":
version: 6.3.1 version: 6.3.1
resolution: "semver@npm:6.3.1" resolution: "semver@npm:6.3.1"
bin: bin:
@@ -8793,9 +8738,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"vite@npm:^5.1.1": "vite@npm:^5.1.2":
version: 5.1.1 version: 5.1.2
resolution: "vite@npm:5.1.1" resolution: "vite@npm:5.1.2"
dependencies: dependencies:
esbuild: "npm:^0.19.3" esbuild: "npm:^0.19.3"
fsevents: "npm:~2.3.3" fsevents: "npm:~2.3.3"
@@ -8829,7 +8774,7 @@ __metadata:
optional: true optional: true
bin: bin:
vite: bin/vite.js vite: bin/vite.js
checksum: 10/bdb8e683caddaa0a9adcbf40144ca8ea3660836b208862b07d43787ea867845919af16e58745365bd13ed3b7f66bbf9788a6869ee22cfaacac01645b59729c34 checksum: 10/fbfc5a84ee33c01cd2c3109ba08c2f3822df9a85bee79179ba5a812757f895e2da234208881b9943291d48b0a4ef8fb90ffaa790d89888530a2ad6c70c169e12
languageName: node languageName: node
linkType: hard linkType: hard

View File

@@ -1,10 +0,0 @@
name=ESP Async WebServer
version=2.6.1
author=Me-No-Dev
maintainer=Mathieu Carbou <mathieu.carbou@gmail.com>
sentence=Asynchronous HTTP and WebSocket Server Library for ESP32
paragraph=Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc
category=Other
url=https://github.com/mathieucarbou/ESPAsyncWebServer
architectures=esp32
license=LGPL-3.0

View File

@@ -15,15 +15,19 @@ Packet::~Packet() {
} }
size_t Packet::available(size_t index) { size_t Packet::available(size_t index) {
if (index >= _size) return 0; if (index >= _size)
if (!_getPayload) return _size - index; return 0;
if (!_getPayload)
return _size - index;
return _chunkedAvailable(index); return _chunkedAvailable(index);
} }
const uint8_t* Packet::data(size_t index) const { const uint8_t * Packet::data(size_t index) const {
if (!_getPayload) { if (!_getPayload) {
if (!_data) return nullptr; if (!_data)
if (index >= _size) return nullptr; return nullptr;
if (index >= _size)
return nullptr;
return &_data[index]; return &_data[index];
} }
return _chunkedData(index); return _chunkedData(index);
@@ -34,9 +38,12 @@ size_t Packet::size() const {
} }
void Packet::setDup() { void Packet::setDup() {
if (!_data) return; if (!_data)
if (packetType() != PacketType.PUBLISH) return; return;
if (_packetId == 0) return; if (packetType() != PacketType.PUBLISH)
return;
if (_packetId == 0)
return;
_data[0] |= 0x08; _data[0] |= 0x08;
} }
@@ -45,36 +52,39 @@ uint16_t Packet::packetId() const {
} }
MQTTPacketType Packet::packetType() const { MQTTPacketType Packet::packetType() const {
if (_data) return static_cast<MQTTPacketType>(_data[0] & 0xF0); if (_data)
return static_cast<MQTTPacketType>(_data[0] & 0xF0);
return static_cast<MQTTPacketType>(0); return static_cast<MQTTPacketType>(0);
} }
bool Packet::removable() const { bool Packet::removable() const {
if (_packetId == 0) return true; if (_packetId == 0)
if ((packetType() == PacketType.PUBACK) || (packetType() == PacketType.PUBCOMP)) return true; return true;
if ((packetType() == PacketType.PUBACK) || (packetType() == PacketType.PUBCOMP))
return true;
return false; return false;
} }
Packet::Packet(espMqttClientTypes::Error& error, Packet::Packet(espMqttClientTypes::Error & error,
bool cleanSession, bool cleanSession,
const char* username, const char * username,
const char* password, const char * password,
const char* willTopic, const char * willTopic,
bool willRetain, bool willRetain,
uint8_t willQos, uint8_t willQos,
const uint8_t* willPayload, const uint8_t * willPayload,
uint16_t willPayloadLength, uint16_t willPayloadLength,
uint16_t keepAlive, uint16_t keepAlive,
const char* clientId) const char * clientId)
: _packetId(0) : _packetId(0)
, _data(nullptr) , _data(nullptr)
, _size(0) , _size(0)
, _payloadIndex(0) , _payloadIndex(0)
, _payloadStartIndex(0) , _payloadStartIndex(0)
, _payloadEndIndex(0) , _payloadEndIndex(0)
, _getPayload(nullptr) { , _getPayload(nullptr) {
if (willPayload && willPayloadLength == 0) { if (willPayload && willPayloadLength == 0) {
size_t length = strlen(reinterpret_cast<const char*>(willPayload)); size_t length = strlen(reinterpret_cast<const char *>(willPayload));
if (length > UINT16_MAX) { if (length > UINT16_MAX) {
emc_log_w("Payload length truncated (l:%zu)", length); emc_log_w("Payload length truncated (l:%zu)", length);
willPayloadLength = UINT16_MAX; willPayloadLength = UINT16_MAX;
@@ -89,15 +99,12 @@ Packet::Packet(espMqttClientTypes::Error& error,
} }
// Calculate size // Calculate size
size_t remainingLength = size_t remainingLength = 6 + // protocol
6 + // protocol
1 + // protocol level 1 + // protocol level
1 + // connect flags 1 + // connect flags
2 + // keepalive 2 + // keepalive
2 + strlen(clientId) + 2 + strlen(clientId) + (willTopic ? 2 + strlen(willTopic) + 2 + willPayloadLength : 0) + (username ? 2 + strlen(username) : 0)
(willTopic ? 2 + strlen(willTopic) + 2 + willPayloadLength : 0) + + (password ? 2 + strlen(password) : 0);
(username ? 2 + strlen(username) : 0) +
(password ? 2 + strlen(password) : 0);
// allocate memory // allocate memory
if (!_allocate(remainingLength, false)) { if (!_allocate(remainingLength, false)) {
@@ -114,12 +121,16 @@ Packet::Packet(espMqttClientTypes::Error& error,
pos += encodeString(PROTOCOL, &_data[pos]); pos += encodeString(PROTOCOL, &_data[pos]);
_data[pos++] = PROTOCOL_LEVEL; _data[pos++] = PROTOCOL_LEVEL;
uint8_t connectFlags = 0; uint8_t connectFlags = 0;
if (cleanSession) connectFlags |= espMqttClientInternals::ConnectFlag.CLEAN_SESSION; if (cleanSession)
if (username != nullptr) connectFlags |= espMqttClientInternals::ConnectFlag.USERNAME; connectFlags |= espMqttClientInternals::ConnectFlag.CLEAN_SESSION;
if (password != nullptr) connectFlags |= espMqttClientInternals::ConnectFlag.PASSWORD; if (username != nullptr)
connectFlags |= espMqttClientInternals::ConnectFlag.USERNAME;
if (password != nullptr)
connectFlags |= espMqttClientInternals::ConnectFlag.PASSWORD;
if (willTopic != nullptr) { if (willTopic != nullptr) {
connectFlags |= espMqttClientInternals::ConnectFlag.WILL; connectFlags |= espMqttClientInternals::ConnectFlag.WILL;
if (willRetain) connectFlags |= espMqttClientInternals::ConnectFlag.WILL_RETAIN; if (willRetain)
connectFlags |= espMqttClientInternals::ConnectFlag.WILL_RETAIN;
switch (willQos) { switch (willQos) {
case 0: case 0:
connectFlags |= espMqttClientInternals::ConnectFlag.WILL_QOS0; connectFlags |= espMqttClientInternals::ConnectFlag.WILL_QOS0;
@@ -148,28 +159,23 @@ Packet::Packet(espMqttClientTypes::Error& error,
pos += willPayloadLength; pos += willPayloadLength;
} }
// credentials // credentials
if (username != nullptr) pos += encodeString(username, &_data[pos]); if (username != nullptr)
if (password != nullptr) encodeString(password, &_data[pos]); pos += encodeString(username, &_data[pos]);
if (password != nullptr)
encodeString(password, &_data[pos]);
error = espMqttClientTypes::Error::SUCCESS; error = espMqttClientTypes::Error::SUCCESS;
} }
Packet::Packet(espMqttClientTypes::Error& error, Packet::Packet(espMqttClientTypes::Error & error, uint16_t packetId, const char * topic, const uint8_t * payload, size_t payloadLength, uint8_t qos, bool retain)
uint16_t packetId, : _packetId(packetId)
const char* topic, , _data(nullptr)
const uint8_t* payload, , _size(0)
size_t payloadLength, , _payloadIndex(0)
uint8_t qos, , _payloadStartIndex(0)
bool retain) , _payloadEndIndex(0)
: _packetId(packetId) , _getPayload(nullptr) {
, _data(nullptr) size_t remainingLength = 2 + strlen(topic) + // topic length + topic
, _size(0)
, _payloadIndex(0)
, _payloadStartIndex(0)
, _payloadEndIndex(0)
, _getPayload(nullptr) {
size_t remainingLength =
2 + strlen(topic) + // topic length + topic
2 + // packet ID 2 + // packet ID
payloadLength; payloadLength;
@@ -191,22 +197,21 @@ Packet::Packet(espMqttClientTypes::Error& error,
error = espMqttClientTypes::Error::SUCCESS; error = espMqttClientTypes::Error::SUCCESS;
} }
Packet::Packet(espMqttClientTypes::Error& error, Packet::Packet(espMqttClientTypes::Error & error,
uint16_t packetId, uint16_t packetId,
const char* topic, const char * topic,
espMqttClientTypes::PayloadCallback payloadCallback, espMqttClientTypes::PayloadCallback payloadCallback,
size_t payloadLength, size_t payloadLength,
uint8_t qos, uint8_t qos,
bool retain) bool retain)
: _packetId(packetId) : _packetId(packetId)
, _data(nullptr) , _data(nullptr)
, _size(0) , _size(0)
, _payloadIndex(0) , _payloadIndex(0)
, _payloadStartIndex(0) , _payloadStartIndex(0)
, _payloadEndIndex(0) , _payloadEndIndex(0)
, _getPayload(payloadCallback) { , _getPayload(payloadCallback) {
size_t remainingLength = size_t remainingLength = 2 + strlen(topic) + // topic length + topic
2 + strlen(topic) + // topic length + topic
2 + // packet ID 2 + // packet ID
payloadLength; payloadLength;
@@ -231,26 +236,26 @@ Packet::Packet(espMqttClientTypes::Error& error,
error = espMqttClientTypes::Error::SUCCESS; error = espMqttClientTypes::Error::SUCCESS;
} }
Packet::Packet(espMqttClientTypes::Error& error, uint16_t packetId, const char* topic, uint8_t qos) Packet::Packet(espMqttClientTypes::Error & error, uint16_t packetId, const char * topic, uint8_t qos)
: _packetId(packetId) : _packetId(packetId)
, _data(nullptr) , _data(nullptr)
, _size(0) , _size(0)
, _payloadIndex(0) , _payloadIndex(0)
, _payloadStartIndex(0) , _payloadStartIndex(0)
, _payloadEndIndex(0) , _payloadEndIndex(0)
, _getPayload(nullptr) { , _getPayload(nullptr) {
SubscribeItem list[1] = {topic, qos}; SubscribeItem list[1] = {{topic, qos}};
_createSubscribe(error, list, 1); _createSubscribe(error, list, 1);
} }
Packet::Packet(espMqttClientTypes::Error& error, MQTTPacketType type, uint16_t packetId) Packet::Packet(espMqttClientTypes::Error & error, MQTTPacketType type, uint16_t packetId)
: _packetId(packetId) : _packetId(packetId)
, _data(nullptr) , _data(nullptr)
, _size(0) , _size(0)
, _payloadIndex(0) , _payloadIndex(0)
, _payloadStartIndex(0) , _payloadStartIndex(0)
, _payloadEndIndex(0) , _payloadEndIndex(0)
, _getPayload(nullptr) { , _getPayload(nullptr) {
if (!_allocate(2)) { if (!_allocate(2)) {
error = espMqttClientTypes::Error::OUT_OF_MEMORY; error = espMqttClientTypes::Error::OUT_OF_MEMORY;
return; return;
@@ -270,26 +275,26 @@ Packet::Packet(espMqttClientTypes::Error& error, MQTTPacketType type, uint16_t p
error = espMqttClientTypes::Error::SUCCESS; error = espMqttClientTypes::Error::SUCCESS;
} }
Packet::Packet(espMqttClientTypes::Error& error, uint16_t packetId, const char* topic) Packet::Packet(espMqttClientTypes::Error & error, uint16_t packetId, const char * topic)
: _packetId(packetId) : _packetId(packetId)
, _data(nullptr) , _data(nullptr)
, _size(0) , _size(0)
, _payloadIndex(0) , _payloadIndex(0)
, _payloadStartIndex(0) , _payloadStartIndex(0)
, _payloadEndIndex(0) , _payloadEndIndex(0)
, _getPayload(nullptr) { , _getPayload(nullptr) {
const char* list[1] = {topic}; const char * list[1] = {topic};
_createUnsubscribe(error, list, 1); _createUnsubscribe(error, list, 1);
} }
Packet::Packet(espMqttClientTypes::Error& error, MQTTPacketType type) Packet::Packet(espMqttClientTypes::Error & error, MQTTPacketType type)
: _packetId(0) : _packetId(0)
, _data(nullptr) , _data(nullptr)
, _size(0) , _size(0)
, _payloadIndex(0) , _payloadIndex(0)
, _payloadStartIndex(0) , _payloadStartIndex(0)
, _payloadEndIndex(0) , _payloadEndIndex(0)
, _getPayload(nullptr) { , _getPayload(nullptr) {
if (!_allocate(0)) { if (!_allocate(0)) {
error = espMqttClientTypes::Error::OUT_OF_MEMORY; error = espMqttClientTypes::Error::OUT_OF_MEMORY;
return; return;
@@ -306,7 +311,7 @@ bool Packet::_allocate(size_t remainingLength, bool check) {
return false; return false;
} }
_size = 1 + remainingLengthLength(remainingLength) + remainingLength; _size = 1 + remainingLengthLength(remainingLength) + remainingLength;
_data = reinterpret_cast<uint8_t*>(malloc(_size)); _data = reinterpret_cast<uint8_t *>(malloc(_size));
if (!_data) { if (!_data) {
_size = 0; _size = 0;
emc_log_w("Alloc failed (l:%zu)", _size); emc_log_w("Alloc failed (l:%zu)", _size);
@@ -317,16 +322,13 @@ bool Packet::_allocate(size_t remainingLength, bool check) {
return true; return true;
} }
size_t Packet::_fillPublishHeader(uint16_t packetId, size_t Packet::_fillPublishHeader(uint16_t packetId, const char * topic, size_t remainingLength, uint8_t qos, bool retain) {
const char* topic,
size_t remainingLength,
uint8_t qos,
bool retain) {
size_t index = 0; size_t index = 0;
// FIXED HEADER // FIXED HEADER
_data[index] = PacketType.PUBLISH; _data[index] = PacketType.PUBLISH;
if (retain) _data[index] |= HeaderFlag.PUBLISH_RETAIN; if (retain)
_data[index] |= HeaderFlag.PUBLISH_RETAIN;
if (qos == 0) { if (qos == 0) {
_data[index++] |= HeaderFlag.PUBLISH_QOS0; _data[index++] |= HeaderFlag.PUBLISH_QOS0;
} else if (qos == 1) { } else if (qos == 1) {
@@ -346,9 +348,7 @@ size_t Packet::_fillPublishHeader(uint16_t packetId,
return index; return index;
} }
void Packet::_createSubscribe(espMqttClientTypes::Error& error, void Packet::_createSubscribe(espMqttClientTypes::Error & error, SubscribeItem * list, size_t numberTopics) {
SubscribeItem* list,
size_t numberTopics) {
// Calculate size // Calculate size
size_t payload = 0; size_t payload = 0;
for (size_t i = 0; i < numberTopics; ++i) { for (size_t i = 0; i < numberTopics; ++i) {
@@ -376,9 +376,7 @@ void Packet::_createSubscribe(espMqttClientTypes::Error& error,
error = espMqttClientTypes::Error::SUCCESS; error = espMqttClientTypes::Error::SUCCESS;
} }
void Packet::_createUnsubscribe(espMqttClientTypes::Error& error, void Packet::_createUnsubscribe(espMqttClientTypes::Error & error, const char ** list, size_t numberTopics) {
const char** list,
size_t numberTopics) {
// Calculate size // Calculate size
size_t payload = 0; size_t payload = 0;
for (size_t i = 0; i < numberTopics; ++i) { for (size_t i = 0; i < numberTopics; ++i) {
@@ -427,7 +425,7 @@ size_t Packet::_chunkedAvailable(size_t index) {
return _payloadEndIndex - index + 1; return _payloadEndIndex - index + 1;
} }
const uint8_t* Packet::_chunkedData(size_t index) const { const uint8_t * Packet::_chunkedData(size_t index) const {
// CAUTION!! available(index) has to be called first to check available data and possibly fill payloadbuffer // CAUTION!! available(index) has to be called first to check available data and possibly fill payloadbuffer
if (index < _payloadIndex) { if (index < _payloadIndex) {
return &_data[index]; return &_data[index];

View File

@@ -1,4 +1,4 @@
#include <APSettingsService.h> #include "APSettingsService.h"
#include "../../src/emsesp_stub.hpp" #include "../../src/emsesp_stub.hpp"
@@ -9,8 +9,8 @@ APSettingsService::APSettingsService(AsyncWebServer * server, FS * fs, SecurityM
, _lastManaged(0) , _lastManaged(0)
, _reconfigureAp(false) , _reconfigureAp(false)
, _connected(0) { , _connected(0) {
addUpdateHandler([&](const String & originId) { reconfigureAP(); }, false); addUpdateHandler([this] { reconfigureAP(); }, false);
WiFi.onEvent(std::bind(&APSettingsService::WiFiEvent, this, _1)); WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event); });
} }
void APSettingsService::begin() { void APSettingsService::begin() {
@@ -53,7 +53,7 @@ void APSettingsService::reconfigureAP() {
void APSettingsService::loop() { void APSettingsService::loop() {
unsigned long currentMillis = uuid::get_uptime(); unsigned long currentMillis = uuid::get_uptime();
unsigned long manageElapsed = (uint32_t)(currentMillis - _lastManaged); unsigned long manageElapsed = static_cast<uint32_t>(currentMillis - _lastManaged);
if (manageElapsed >= MANAGE_NETWORK_DELAY) { if (manageElapsed >= MANAGE_NETWORK_DELAY) {
_lastManaged = currentMillis; _lastManaged = currentMillis;
manageAP(); manageAP();
@@ -76,7 +76,7 @@ void APSettingsService::manageAP() {
void APSettingsService::startAP() { void APSettingsService::startAP() {
WiFi.softAPConfig(_state.localIP, _state.gatewayIP, _state.subnetMask); WiFi.softAPConfig(_state.localIP, _state.gatewayIP, _state.subnetMask);
esp_wifi_set_bandwidth((wifi_interface_t)ESP_IF_WIFI_AP, WIFI_BW_HT20); esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_AP), WIFI_BW_HT20);
WiFi.softAP(_state.ssid.c_str(), _state.password.c_str(), _state.channel, _state.ssidHidden, _state.maxClients); WiFi.softAP(_state.ssid.c_str(), _state.password.c_str(), _state.channel, _state.ssidHidden, _state.maxClients);
#if CONFIG_IDF_TARGET_ESP32C3 #if CONFIG_IDF_TARGET_ESP32C3
WiFi.setTxPower(WIFI_POWER_8_5dBm); // https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi WiFi.setTxPower(WIFI_POWER_8_5dBm); // https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi
@@ -108,8 +108,54 @@ void APSettingsService::handleDNS() {
APNetworkStatus APSettingsService::getAPNetworkStatus() { APNetworkStatus APSettingsService::getAPNetworkStatus() {
WiFiMode_t currentWiFiMode = WiFi.getMode(); WiFiMode_t currentWiFiMode = WiFi.getMode();
bool apActive = currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA; bool apActive = currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA;
if (apActive && _state.provisionMode != AP_MODE_ALWAYS && WiFi.status() == WL_CONNECTED) { if (apActive && _state.provisionMode != AP_MODE_ALWAYS && WiFi.status() == WL_CONNECTED) {
return APNetworkStatus::LINGERING; return APNetworkStatus::LINGERING;
} }
return apActive ? APNetworkStatus::ACTIVE : APNetworkStatus::INACTIVE; return apActive ? APNetworkStatus::ACTIVE : APNetworkStatus::INACTIVE;
} }
void APSettings::read(const APSettings & settings, JsonObject root) {
root["provision_mode"] = settings.provisionMode;
root["ssid"] = settings.ssid;
root["password"] = settings.password;
root["channel"] = settings.channel;
root["ssid_hidden"] = settings.ssidHidden;
root["max_clients"] = settings.maxClients;
root["local_ip"] = settings.localIP.toString();
root["gateway_ip"] = settings.gatewayIP.toString();
root["subnet_mask"] = settings.subnetMask.toString();
}
StateUpdateResult APSettings::update(JsonObject root, APSettings & settings) {
APSettings newSettings = {};
newSettings.provisionMode = static_cast<uint8_t>(root["provision_mode"] | FACTORY_AP_PROVISION_MODE);
switch (settings.provisionMode) {
case AP_MODE_ALWAYS:
case AP_MODE_DISCONNECTED:
case AP_MODE_NEVER:
break;
default:
newSettings.provisionMode = AP_MODE_ALWAYS;
}
newSettings.ssid = root["ssid"] | FACTORY_AP_SSID;
newSettings.password = root["password"] | FACTORY_AP_PASSWORD;
newSettings.channel = static_cast<uint8_t>(root["channel"] | FACTORY_AP_CHANNEL);
newSettings.ssidHidden = root["ssid_hidden"] | FACTORY_AP_SSID_HIDDEN;
newSettings.maxClients = static_cast<uint8_t>(root["max_clients"] | FACTORY_AP_MAX_CLIENTS);
JsonUtils::readIP(root, "local_ip", newSettings.localIP, FACTORY_AP_LOCAL_IP);
JsonUtils::readIP(root, "gateway_ip", newSettings.gatewayIP, FACTORY_AP_GATEWAY_IP);
JsonUtils::readIP(root, "subnet_mask", newSettings.subnetMask, FACTORY_AP_SUBNET_MASK);
if (newSettings == settings) {
return StateUpdateResult::UNCHANGED;
}
settings = newSettings;
return StateUpdateResult::CHANGED;
}

View File

@@ -1,9 +1,9 @@
#ifndef APSettingsConfig_h #ifndef APSettingsConfig_h
#define APSettingsConfig_h #define APSettingsConfig_h
#include <HttpEndpoint.h> #include "HttpEndpoint.h"
#include <FSPersistence.h> #include "FSPersistence.h"
#include <JsonUtils.h> #include "JsonUtils.h"
#include <DNSServer.h> #include <DNSServer.h>
#include <IPAddress.h> #include <IPAddress.h>
@@ -75,45 +75,8 @@ class APSettings {
&& subnetMask == settings.subnetMask; && subnetMask == settings.subnetMask;
} }
static void read(APSettings & settings, JsonObject root) { static void read(const APSettings & settings, JsonObject root);
root["provision_mode"] = settings.provisionMode; static StateUpdateResult update(JsonObject root, APSettings & settings);
root["ssid"] = settings.ssid;
root["password"] = settings.password;
root["channel"] = settings.channel;
root["ssid_hidden"] = settings.ssidHidden;
root["max_clients"] = settings.maxClients;
root["local_ip"] = settings.localIP.toString();
root["gateway_ip"] = settings.gatewayIP.toString();
root["subnet_mask"] = settings.subnetMask.toString();
}
static StateUpdateResult update(JsonObject root, APSettings & settings) {
APSettings newSettings = {};
newSettings.provisionMode = root["provision_mode"] | FACTORY_AP_PROVISION_MODE;
switch (settings.provisionMode) {
case AP_MODE_ALWAYS:
case AP_MODE_DISCONNECTED:
case AP_MODE_NEVER:
break;
default:
newSettings.provisionMode = AP_MODE_ALWAYS;
}
newSettings.ssid = root["ssid"] | FACTORY_AP_SSID;
newSettings.password = root["password"] | FACTORY_AP_PASSWORD;
newSettings.channel = root["channel"] | FACTORY_AP_CHANNEL;
newSettings.ssidHidden = root["ssid_hidden"] | FACTORY_AP_SSID_HIDDEN;
newSettings.maxClients = root["max_clients"] | FACTORY_AP_MAX_CLIENTS;
JsonUtils::readIP(root, "local_ip", newSettings.localIP, FACTORY_AP_LOCAL_IP);
JsonUtils::readIP(root, "gateway_ip", newSettings.gatewayIP, FACTORY_AP_GATEWAY_IP);
JsonUtils::readIP(root, "subnet_mask", newSettings.subnetMask, FACTORY_AP_SUBNET_MASK);
if (newSettings == settings) {
return StateUpdateResult::UNCHANGED;
}
settings = newSettings;
return StateUpdateResult::CHANGED;
}
}; };
class APSettingsService : public StatefulService<APSettings> { class APSettingsService : public StatefulService<APSettings> {

View File

@@ -1,16 +1,14 @@
#include <APStatus.h> #include "APStatus.h"
using namespace std::placeholders; // for `_1` etc
APStatus::APStatus(AsyncWebServer * server, SecurityManager * securityManager, APSettingsService * apSettingsService) APStatus::APStatus(AsyncWebServer * server, SecurityManager * securityManager, APSettingsService * apSettingsService)
: _apSettingsService(apSettingsService) { : _apSettingsService(apSettingsService) {
server->on(AP_STATUS_SERVICE_PATH, server->on(AP_STATUS_SERVICE_PATH,
HTTP_GET, HTTP_GET,
securityManager->wrapRequest(std::bind(&APStatus::apStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); securityManager->wrapRequest([this](AsyncWebServerRequest * request) { apStatus(request); }, AuthenticationPredicates::IS_AUTHENTICATED));
} }
void APStatus::apStatus(AsyncWebServerRequest * request) { void APStatus::apStatus(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false); auto * response = new AsyncJsonResponse(false);
JsonObject root = response->getRoot(); JsonObject root = response->getRoot();
root["status"] = _apSettingsService->getAPNetworkStatus(); root["status"] = _apSettingsService->getAPNetworkStatus();

View File

@@ -7,8 +7,9 @@
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <IPAddress.h> #include <IPAddress.h>
#include <SecurityManager.h>
#include <APSettingsService.h> #include "SecurityManager.h"
#include "APSettingsService.h"
#define AP_STATUS_SERVICE_PATH "/rest/apStatus" #define AP_STATUS_SERVICE_PATH "/rest/apStatus"

View File

@@ -1,47 +1,27 @@
#include "ArduinoJsonJWT.h" #include "ArduinoJsonJWT.h"
#include <array>
ArduinoJsonJWT::ArduinoJsonJWT(String secret) ArduinoJsonJWT::ArduinoJsonJWT(String secret)
: _secret(secret) { : _secret(std::move(secret)) {
} }
void ArduinoJsonJWT::setSecret(String secret) { void ArduinoJsonJWT::setSecret(String secret) {
_secret = secret; _secret = std::move(secret);
} }
String ArduinoJsonJWT::getSecret() { String ArduinoJsonJWT::getSecret() {
return _secret; return _secret;
} }
/*
* ESP32 uses mbedtls, ESP2866 uses bearssl.
*
* Both come with decent HMAC implementations supporting sha256, as well as others.
*
* No need to pull in additional crypto libraries - lets use what we already have.
*/
String ArduinoJsonJWT::sign(String & payload) {
unsigned char hmacResult[32];
{
mbedtls_md_context_t ctx;
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 1);
mbedtls_md_hmac_starts(&ctx, (unsigned char *)_secret.c_str(), _secret.length());
mbedtls_md_hmac_update(&ctx, (unsigned char *)payload.c_str(), payload.length());
mbedtls_md_hmac_finish(&ctx, hmacResult);
mbedtls_md_free(&ctx);
}
return encode((char *)hmacResult, 32);
}
String ArduinoJsonJWT::buildJWT(JsonObject payload) { String ArduinoJsonJWT::buildJWT(JsonObject payload) {
// serialize, then encode payload // serialize, then encode payload
String jwt; String jwt;
serializeJson(payload, jwt); serializeJson(payload, jwt);
jwt = encode(jwt.c_str(), jwt.length()); jwt = encode(jwt.c_str(), static_cast<int>(jwt.length()));
// add the header to payload // add the header to payload
jwt = JWT_HEADER + '.' + jwt; jwt = getJWTHeader() + '.' + jwt;
// add signature // add signature
jwt += '.' + sign(jwt); jwt += '.' + sign(jwt);
@@ -53,65 +33,88 @@ void ArduinoJsonJWT::parseJWT(String jwt, JsonDocument & jsonDocument) {
// clear json document before we begin, jsonDocument wil be null on failure // clear json document before we begin, jsonDocument wil be null on failure
jsonDocument.clear(); jsonDocument.clear();
const String & jwt_header = getJWTHeader();
const unsigned int jwt_header_size = jwt_header.length();
// must have the correct header and delimiter // must have the correct header and delimiter
if (!jwt.startsWith(JWT_HEADER) || jwt.indexOf('.') != JWT_HEADER_SIZE) { if (!jwt.startsWith(jwt_header) || jwt.indexOf('.') != static_cast<int>(jwt_header_size)) {
return; return;
} }
// check there is a signature delimieter // check there is a signature delimieter
int signatureDelimiterIndex = jwt.lastIndexOf('.'); const int signatureDelimiterIndex = jwt.lastIndexOf('.');
if (signatureDelimiterIndex == JWT_HEADER_SIZE) { if (signatureDelimiterIndex == static_cast<int>(jwt_header_size)) {
return; return;
} }
// check the signature is valid // check the signature is valid
String signature = jwt.substring(signatureDelimiterIndex + 1); const String signature = jwt.substring(static_cast<unsigned int>(signatureDelimiterIndex) + 1);
jwt = jwt.substring(0, signatureDelimiterIndex); jwt = jwt.substring(0, static_cast<unsigned int>(signatureDelimiterIndex));
if (sign(jwt) != signature) { if (sign(jwt) != signature) {
return; return;
} }
// decode payload // decode payload
jwt = jwt.substring(JWT_HEADER_SIZE + 1); jwt = jwt.substring(jwt_header_size + 1);
jwt = decode(jwt); jwt = decode(jwt);
// parse payload, clearing json document after failure // parse payload, clearing json document after failure
DeserializationError error = deserializeJson(jsonDocument, jwt); const DeserializationError error = deserializeJson(jsonDocument, jwt);
if (error != DeserializationError::Ok || !jsonDocument.is<JsonObject>()) { if (error != DeserializationError::Ok || !jsonDocument.is<JsonObject>()) {
jsonDocument.clear(); jsonDocument.clear();
} }
} }
/*
* ESP32 uses mbedtls, ESP2866 uses bearssl.
*
* Both come with decent HMAC implementations supporting sha256, as well as others.
*
* No need to pull in additional crypto libraries - lets use what we already have.
*/
String ArduinoJsonJWT::sign(String & payload) {
std::array<unsigned char, 32> hmacResult{};
{
mbedtls_md_context_t ctx;
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 1);
mbedtls_md_hmac_starts(&ctx, reinterpret_cast<const unsigned char *>(_secret.c_str()), _secret.length());
mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char *>(payload.c_str()), payload.length());
mbedtls_md_hmac_finish(&ctx, hmacResult.data());
mbedtls_md_free(&ctx);
}
return encode(reinterpret_cast<const char *>(hmacResult.data()), hmacResult.size());
}
String ArduinoJsonJWT::encode(const char * cstr, int inputLen) { String ArduinoJsonJWT::encode(const char * cstr, int inputLen) {
// prepare encoder // prepare encoder
base64_encodestate _state; base64_encodestate _state;
base64_init_encodestate(&_state); base64_init_encodestate(&_state);
size_t encodedLength = base64_encode_expected_len(inputLen) + 1;
// prepare buffer of correct length, returning an empty string on failure // prepare buffer of correct length
char * buffer = (char *)malloc(encodedLength * sizeof(char)); const auto bufferLength = static_cast<std::size_t>(base64_encode_expected_len(inputLen)) + 1;
if (buffer == nullptr) { auto * buffer = new char[bufferLength];
return "";
}
// encode to buffer // encode to buffer
int len = base64_encode_block(cstr, inputLen, &buffer[0], &_state); int len = base64_encode_block(cstr, inputLen, &buffer[0], &_state);
len += base64_encode_blockend(&buffer[len], &_state); len += base64_encode_blockend(&buffer[len], &_state);
buffer[len] = 0; buffer[len] = '\0';
// convert to arduino string, freeing buffer // convert to arduino string, freeing buffer
String value = String(buffer); auto result = String(buffer);
free(buffer); delete[] buffer;
buffer = nullptr; buffer = nullptr;
// remove padding and convert to URL safe form // remove padding and convert to URL safe form
while (value.length() > 0 && value.charAt(value.length() - 1) == '=') { while (result.length() > 0 && result.charAt(result.length() - 1) == '=') {
value.remove(value.length() - 1); result.remove(result.length() - 1);
} }
value.replace('+', '-'); result.replace('+', '-');
value.replace('/', '_'); result.replace('/', '_');
// return as string // return as string
return value; return result;
} }
String ArduinoJsonJWT::decode(String value) { String ArduinoJsonJWT::decode(String value) {
@@ -120,12 +123,18 @@ String ArduinoJsonJWT::decode(String value) {
value.replace('_', '/'); value.replace('_', '/');
// prepare buffer of correct length // prepare buffer of correct length
char buffer[base64_decode_expected_len(value.length()) + 1]; const auto bufferLength = static_cast<std::size_t>(base64_decode_expected_len(value.length()) + 1);
auto * buffer = new char[bufferLength];
// decode // decode
int len = base64_decode_chars(value.c_str(), value.length(), &buffer[0]); const int len = base64_decode_chars(value.c_str(), static_cast<int>(value.length()), &buffer[0]);
buffer[len] = 0; buffer[len] = '\0';
// convert to arduino string, freeing buffer
auto result = String(buffer);
delete[] buffer;
buffer = nullptr;
// return as string // return as string
return String(buffer); return result;
} }

View File

@@ -3,30 +3,33 @@
#include <Arduino.h> #include <Arduino.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <libb64/cdecode.h> #include <libb64/cdecode.h>
#include <libb64/cencode.h> #include <libb64/cencode.h>
#include <mbedtls/md.h> #include <mbedtls/md.h>
class ArduinoJsonJWT { class ArduinoJsonJWT {
private:
String _secret;
const String JWT_HEADER = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
const int JWT_HEADER_SIZE = JWT_HEADER.length();
String sign(String & value);
static String encode(const char * cstr, int len);
static String decode(String value);
public: public:
ArduinoJsonJWT(String secret); explicit ArduinoJsonJWT(String secret);
void setSecret(String secret); void setSecret(String secret);
String getSecret(); String getSecret();
String buildJWT(JsonObject payload); String buildJWT(JsonObject payload);
void parseJWT(String jwt, JsonDocument & jsonDocument); void parseJWT(String jwt, JsonDocument & jsonDocument);
private:
String _secret;
String sign(String & value);
static String encode(const char * cstr, int len);
static String decode(String value);
static const String & getJWTHeader() {
static const String JWT_HEADER = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
return JWT_HEADER;
}
}; };
#endif #endif

View File

@@ -1,11 +1,9 @@
#include <AuthenticationService.h> #include "AuthenticationService.h"
using namespace std::placeholders; // for `_1` etc
AuthenticationService::AuthenticationService(AsyncWebServer * server, SecurityManager * securityManager) AuthenticationService::AuthenticationService(AsyncWebServer * server, SecurityManager * securityManager)
: _securityManager(securityManager) : _securityManager(securityManager)
, _signInHandler(SIGN_IN_PATH, std::bind(&AuthenticationService::signIn, this, _1, _2)) { , _signInHandler(SIGN_IN_PATH, [this](AsyncWebServerRequest * request, JsonVariant json) { signIn(request, json); }) {
server->on(VERIFY_AUTHORIZATION_PATH, HTTP_GET, std::bind(&AuthenticationService::verifyAuthorization, this, _1)); server->on(VERIFY_AUTHORIZATION_PATH, HTTP_GET, [this](AsyncWebServerRequest * request) { verifyAuthorization(request); });
_signInHandler.setMethod(HTTP_POST); _signInHandler.setMethod(HTTP_POST);
_signInHandler.setMaxContentLength(MAX_AUTHENTICATION_SIZE); _signInHandler.setMaxContentLength(MAX_AUTHENTICATION_SIZE);
server->addHandler(&_signInHandler); server->addHandler(&_signInHandler);
@@ -30,7 +28,7 @@ void AuthenticationService::signIn(AsyncWebServerRequest * request, JsonVariant
Authentication authentication = _securityManager->authenticate(username, password); Authentication authentication = _securityManager->authenticate(username, password);
if (authentication.authenticated) { if (authentication.authenticated) {
User * user = authentication.user; User * user = authentication.user;
AsyncJsonResponse * response = new AsyncJsonResponse(false); auto * response = new AsyncJsonResponse(false);
JsonObject jsonObject = response->getRoot(); JsonObject jsonObject = response->getRoot();
jsonObject["access_token"] = _securityManager->generateJWT(user); jsonObject["access_token"] = _securityManager->generateJWT(user);
response->setLength(); response->setLength();

View File

@@ -1,9 +1,10 @@
#ifndef AuthenticationService_H_ #ifndef AuthenticationService_H_
#define AuthenticationService_H_ #define AuthenticationService_H_
#include <Features.h> #include "Features.h"
#include "SecurityManager.h"
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#define VERIFY_AUTHORIZATION_PATH "/rest/verifyAuthorization" #define VERIFY_AUTHORIZATION_PATH "/rest/verifyAuthorization"
#define SIGN_IN_PATH "/rest/signIn" #define SIGN_IN_PATH "/rest/signIn"

View File

@@ -1,6 +1,6 @@
#include <ESP8266React.h> #include "ESP8266React.h"
#include <WWWData.h> #include "WWWData.h"
ESP8266React::ESP8266React(AsyncWebServer * server, FS * fs) ESP8266React::ESP8266React(AsyncWebServer * server, FS * fs)
: _securitySettingsService(server, fs) : _securitySettingsService(server, fs)
@@ -19,15 +19,33 @@ ESP8266React::ESP8266React(AsyncWebServer * server, FS * fs)
, _restartService(server, &_securitySettingsService) , _restartService(server, &_securitySettingsService)
, _factoryResetService(server, fs, &_securitySettingsService) , _factoryResetService(server, fs, &_securitySettingsService)
, _systemStatus(server, &_securitySettingsService) { , _systemStatus(server, &_securitySettingsService) {
// Serve static resources //
WWWData::registerRoutes([server, this](const String & uri, const String & contentType, const uint8_t * content, size_t len) { // Serve static web resources
ArRequestHandlerFunction requestHandler = [contentType, content, len](AsyncWebServerRequest * request) { //
// Populate the last modification date based on build datetime
static char last_modified[50];
sprintf(last_modified, "%s %s CET", __DATE__, __TIME__);
WWWData::registerRoutes([server](const String & uri, const String & contentType, const uint8_t * content, size_t len, const String & hash) {
ArRequestHandlerFunction requestHandler = [contentType, content, len, hash](AsyncWebServerRequest * request) {
// Check if the client already has the same version and respond with a 304 (Not modified)
if (request->header("If-Modified-Since").indexOf(last_modified) > 0) {
return request->send(304);
} else if (request->header("If-None-Match").equals(hash)) {
return request->send(304);
}
AsyncWebServerResponse * response = request->beginResponse_P(200, contentType, content, len); AsyncWebServerResponse * response = request->beginResponse_P(200, contentType, content, len);
response->addHeader("Content-Encoding", "gzip"); response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", "public, immutable, max-age=31536000");
// response->addHeader("Content-Encoding", "br"); // only works over HTTPS // response->addHeader("Content-Encoding", "br"); // only works over HTTPS
// response->addHeader("Cache-Control", "public, immutable, max-age=31536000");
response->addHeader("Last-Modified", last_modified);
response->addHeader("ETag", hash);
request->send(response); request->send(response);
}; };
server->on(uri.c_str(), HTTP_GET, requestHandler); server->on(uri.c_str(), HTTP_GET, requestHandler);
// Serving non matching get requests with "/index.html" // Serving non matching get requests with "/index.html"
// OPTIONS get a straight up 200 response // OPTIONS get a straight up 200 response
@@ -48,12 +66,13 @@ ESP8266React::ESP8266React(AsyncWebServer * server, FS * fs)
void ESP8266React::begin() { void ESP8266React::begin() {
_networkSettingsService.begin(); _networkSettingsService.begin();
_networkSettingsService.read([&](NetworkSettings & networkSettings) { _networkSettingsService.read([&](NetworkSettings & networkSettings) {
DefaultHeaders & defaultHeaders = DefaultHeaders::Instance();
if (networkSettings.enableCORS) { if (networkSettings.enableCORS) {
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", networkSettings.CORSOrigin); defaultHeaders.addHeader("Access-Control-Allow-Origin", networkSettings.CORSOrigin);
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Accept, Content-Type, Authorization"); defaultHeaders.addHeader("Access-Control-Allow-Headers", "Accept, Content-Type, Authorization");
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Credentials", "true"); defaultHeaders.addHeader("Access-Control-Allow-Credentials", "true");
} }
DefaultHeaders::Instance().addHeader("Server", networkSettings.hostname); defaultHeaders.addHeader("Server", networkSettings.hostname);
}); });
_apSettingsService.begin(); _apSettingsService.begin();
_ntpSettingsService.begin(); _ntpSettingsService.begin();

View File

@@ -1,28 +1,27 @@
#ifndef ESP8266React_h #ifndef ESP8266React_h
#define ESP8266React_h #define ESP8266React_h
#include <Arduino.h> #include "APSettingsService.h"
#include "APStatus.h"
#include "AuthenticationService.h"
#include "FactoryResetService.h"
#include "MqttSettingsService.h"
#include "MqttStatus.h"
#include "NTPSettingsService.h"
#include "NTPStatus.h"
#include "OTASettingsService.h"
#include "UploadFileService.h"
#include "RestartService.h"
#include "SecuritySettingsService.h"
#include "SystemStatus.h"
#include "WiFiScanner.h"
#include "NetworkSettingsService.h"
#include "NetworkStatus.h"
#include <Arduino.h>
#include <AsyncTCP.h> #include <AsyncTCP.h>
#include <WiFi.h> #include <WiFi.h>
#include <APSettingsService.h>
#include <APStatus.h>
#include <AuthenticationService.h>
#include <FactoryResetService.h>
#include <MqttSettingsService.h>
#include <MqttStatus.h>
#include <NTPSettingsService.h>
#include <NTPStatus.h>
#include <OTASettingsService.h>
#include <UploadFileService.h>
#include <RestartService.h>
#include <SecuritySettingsService.h>
#include <SystemStatus.h>
#include <WiFiScanner.h>
#include <NetworkSettingsService.h>
#include <NetworkStatus.h>
class ESP8266React { class ESP8266React {
public: public:
ESP8266React(AsyncWebServer * server, FS * fs); ESP8266React(AsyncWebServer * server, FS * fs);
@@ -66,9 +65,11 @@ class ESP8266React {
_mqttSettingsService.setWill(will_topic); _mqttSettingsService.setWill(will_topic);
} }
#ifndef EMSESP_STANDALONE
void factoryReset() { void factoryReset() {
_factoryResetService.factoryReset(); _factoryResetService.factoryReset();
} }
#endif
private: private:
SecuritySettingsService _securitySettingsService; SecuritySettingsService _securitySettingsService;

View File

@@ -1,13 +0,0 @@
#ifndef ESPUtils_h
#define ESPUtils_h
#include <Arduino.h>
class ESPUtils {
public:
static String defaultDeviceValue(String prefix = "") {
return prefix + String((uint32_t)ESP.getEfuseMac(), HEX);
}
};
#endif

View File

@@ -1,8 +1,8 @@
#ifndef FSPersistence_h #ifndef FSPersistence_h
#define FSPersistence_h #define FSPersistence_h
#include <StatefulService.h> #include "StatefulService.h"
#include <FS.h> #include "FS.h"
template <class T> template <class T>
class FSPersistence { class FSPersistence {
@@ -47,8 +47,8 @@ class FSPersistence {
// make directories if required, for new IDF4.2 & LittleFS // make directories if required, for new IDF4.2 & LittleFS
String path(_filePath); String path(_filePath);
int index = 0; int index = 0;
while ((index = path.indexOf('/', index + 1)) != -1) { while ((index = path.indexOf('/', static_cast<unsigned int>(index) + 1)) != -1) {
String segment = path.substring(0, index); String segment = path.substring(0, static_cast<unsigned int>(index));
if (!_fs->exists(segment)) { if (!_fs->exists(segment)) {
_fs->mkdir(segment); _fs->mkdir(segment);
} }
@@ -80,7 +80,7 @@ class FSPersistence {
void enableUpdateHandler() { void enableUpdateHandler() {
if (!_updateHandlerId) { if (!_updateHandlerId) {
_updateHandlerId = _statefulService->addUpdateHandler([&](const String & originId) { writeToFS(); }); _updateHandlerId = _statefulService->addUpdateHandler([&] { writeToFS(); });
} }
} }

View File

@@ -1,16 +1,14 @@
#include <FactoryResetService.h> #include "FactoryResetService.h"
using namespace std::placeholders;
FactoryResetService::FactoryResetService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) FactoryResetService::FactoryResetService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: fs(fs) { : fs(fs) {
server->on(FACTORY_RESET_SERVICE_PATH, server->on(FACTORY_RESET_SERVICE_PATH,
HTTP_POST, HTTP_POST,
securityManager->wrapRequest(std::bind(&FactoryResetService::handleRequest, this, _1), AuthenticationPredicates::IS_ADMIN)); securityManager->wrapRequest([this](AsyncWebServerRequest * request) { handleRequest(request); }, AuthenticationPredicates::IS_ADMIN));
} }
void FactoryResetService::handleRequest(AsyncWebServerRequest * request) { void FactoryResetService::handleRequest(AsyncWebServerRequest * request) {
request->onDisconnect(std::bind(&FactoryResetService::factoryReset, this)); request->onDisconnect([this] { factoryReset(); });
request->send(200); request->send(200);
} }
@@ -21,7 +19,7 @@ void FactoryResetService::factoryReset() {
// TODO To replaced with fs.rmdir(FS_CONFIG_DIRECTORY) now we're using IDF 4.2 // TODO To replaced with fs.rmdir(FS_CONFIG_DIRECTORY) now we're using IDF 4.2
File root = fs->open(FS_CONFIG_DIRECTORY); File root = fs->open(FS_CONFIG_DIRECTORY);
File file; File file;
while (file = root.openNextFile()) { while ((file = root.openNextFile())) {
String path = file.path(); String path = file.path();
file.close(); file.close();
fs->remove(path); fs->remove(path);

View File

@@ -3,22 +3,22 @@
#include <WiFi.h> #include <WiFi.h>
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#include <RestartService.h>
#include <FS.h> #include <FS.h>
#include "SecurityManager.h"
#include "RestartService.h"
#define FS_CONFIG_DIRECTORY "/config" #define FS_CONFIG_DIRECTORY "/config"
#define FACTORY_RESET_SERVICE_PATH "/rest/factoryReset" #define FACTORY_RESET_SERVICE_PATH "/rest/factoryReset"
class FactoryResetService { class FactoryResetService {
FS * fs;
public: public:
FactoryResetService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); FactoryResetService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
void factoryReset(); void factoryReset();
private: private:
FS * fs;
void handleRequest(AsyncWebServerRequest * request); void handleRequest(AsyncWebServerRequest * request);
}; };

View File

@@ -2,15 +2,13 @@
#define HttpEndpoint_h #define HttpEndpoint_h
#include <functional> #include <functional>
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <SecurityManager.h> #include "SecurityManager.h"
#include <StatefulService.h> #include "StatefulService.h"
#define HTTP_ENDPOINT_ORIGIN_ID "http" #define HTTP_ENDPOINT_ORIGIN_ID "http"
#define HTTPS_ENDPOINT_ORIGIN_ID "https"
using namespace std::placeholders; // for `_1` etc
template <class T> template <class T>
class HttpEndpoint { class HttpEndpoint {
@@ -19,8 +17,7 @@ class HttpEndpoint {
JsonStateUpdater<T> _stateUpdater; JsonStateUpdater<T> _stateUpdater;
StatefulService<T> * _statefulService; StatefulService<T> * _statefulService;
AsyncCallbackWebHandler * GEThandler; AsyncCallbackJsonWebHandler * handler;
AsyncCallbackJsonWebHandler * POSThandler;
public: public:
HttpEndpoint(JsonStateReader<T> stateReader, HttpEndpoint(JsonStateReader<T> stateReader,
@@ -33,12 +30,12 @@ class HttpEndpoint {
: _stateReader(stateReader) : _stateReader(stateReader)
, _stateUpdater(stateUpdater) , _stateUpdater(stateUpdater)
, _statefulService(statefulService) { , _statefulService(statefulService) {
// Create the GET and POST endpoints // Create hander for both GET and POST endpoints
POSThandler = new AsyncCallbackJsonWebHandler(servicePath, handler = new AsyncCallbackJsonWebHandler(servicePath,
securityManager->wrapCallback([this](AsyncWebServerRequest * request, securityManager->wrapCallback([this](AsyncWebServerRequest * request,
JsonVariant json) { handleRequest(request, json); }, JsonVariant json) { handleRequest(request, json); },
authenticationPredicate)); authenticationPredicate));
server->addHandler(POSThandler); server->addHandler(handler);
} }
protected: protected:
@@ -56,15 +53,17 @@ class HttpEndpoint {
if (outcome == StateUpdateResult::ERROR) { if (outcome == StateUpdateResult::ERROR) {
request->send(400); // error request->send(400); // error
return; return;
} else if (outcome == StateUpdateResult::CHANGED_RESTART) { } else if (outcome == StateUpdateResult::CHANGED || outcome == StateUpdateResult::CHANGED_RESTART) {
// persist changes
request->onDisconnect([this] { _statefulService->callUpdateHandlers(); });
if (outcome == StateUpdateResult::CHANGED_RESTART) {
request->send(205); // reboot required request->send(205); // reboot required
return; return;
} else if (outcome == StateUpdateResult::CHANGED) { }
request->onDisconnect([this]() { _statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID); });
} }
} }
AsyncJsonResponse * response = new AsyncJsonResponse(false); auto * response = new AsyncJsonResponse(false);
JsonObject jsonObject = response->getRoot().to<JsonObject>(); JsonObject jsonObject = response->getRoot().to<JsonObject>();
_statefulService->read(jsonObject, _stateReader); _statefulService->read(jsonObject, _stateReader);
response->setLength(); response->setLength();

View File

@@ -3,15 +3,19 @@
#include <IPAddress.h> #include <IPAddress.h>
const IPAddress IP_NOT_SET = IPAddress(INADDR_NONE);
class IPUtils { class IPUtils {
public: public:
static bool isSet(const IPAddress & ip) { static bool isSet(const IPAddress & ip) {
return ip != IP_NOT_SET; return ip != getNotSetIP();
} }
static bool isNotSet(const IPAddress & ip) { static bool isNotSet(const IPAddress & ip) {
return ip == IP_NOT_SET; return ip == getNotSetIP();
}
private:
static const IPAddress & getNotSetIP() {
static const IPAddress IP_NOT_SET = IPAddress(INADDR_NONE);
return IP_NOT_SET;
} }
}; };

View File

@@ -2,9 +2,10 @@
#define JsonUtils_h #define JsonUtils_h
#include <Arduino.h> #include <Arduino.h>
#include <IPAddress.h>
#include <IPUtils.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <IPAddress.h>
#include "IPUtils.h"
class JsonUtils { class JsonUtils {
public: public:

View File

@@ -1,45 +1,27 @@
#include <MqttSettingsService.h> #include "MqttSettingsService.h"
#include "../../src/emsesp_stub.hpp" #include "../../src/emsesp_stub.hpp"
using namespace std::placeholders; // for `_1` etc
/**
* Retains a copy of the cstr provided in the pointer provided using dynamic allocation.
*
* Frees the pointer before allocation and leaves it as nullptr if cstr == nullptr.
*/
static char * retainCstr(const char * cstr, char ** ptr) {
// free up previously retained value if exists
free(*ptr);
*ptr = nullptr;
// dynamically allocate and copy cstr (if non null)
if (cstr != nullptr) {
*ptr = (char *)malloc(strlen(cstr) + 1);
strcpy(*ptr, cstr);
}
// return reference to pointer for convenience
return *ptr;
}
MqttSettingsService::MqttSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) MqttSettingsService::MqttSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(MqttSettings::read, MqttSettings::update, this, server, MQTT_SETTINGS_SERVICE_PATH, securityManager) : _httpEndpoint(MqttSettings::read, MqttSettings::update, this, server, MQTT_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(MqttSettings::read, MqttSettings::update, this, fs, MQTT_SETTINGS_FILE) , _fsPersistence(MqttSettings::read, MqttSettings::update, this, fs, MQTT_SETTINGS_FILE)
, _retainedHost(nullptr)
, _retainedClientId(nullptr)
, _retainedUsername(nullptr)
, _retainedPassword(nullptr)
, _reconfigureMqtt(false) , _reconfigureMqtt(false)
, _disconnectedAt(0) , _disconnectedAt(0)
, _disconnectReason(espMqttClientTypes::DisconnectReason::TCP_DISCONNECTED) , _disconnectReason(espMqttClientTypes::DisconnectReason::TCP_DISCONNECTED)
, _mqttClient(nullptr) { , _mqttClient(nullptr) {
WiFi.onEvent(std::bind(&MqttSettingsService::WiFiEvent, this, _1, _2)); WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event); });
addUpdateHandler([&](const String & originId) { onConfigUpdated(); }, false); addUpdateHandler([this] { onConfigUpdated(); }, false);
}
static String generateClientId() {
#ifdef EMSESP_STANDALONE
return "ems-esp";
#else
return "esp32-" + String(static_cast<uint32_t>(ESP.getEfuseMac()), HEX);
#endif
} }
MqttSettingsService::~MqttSettingsService() { MqttSettingsService::~MqttSettingsService() {
delete _mqttClient;
} }
void MqttSettingsService::begin() { void MqttSettingsService::begin() {
@@ -55,32 +37,41 @@ void MqttSettingsService::startClient() {
return; return;
} }
delete _mqttClient; delete _mqttClient;
_mqttClient = nullptr;
} }
#if CONFIG_IDF_TARGET_ESP32S3 #if CONFIG_IDF_TARGET_ESP32S3
if (_state.enableTLS) { if (_state.enableTLS) {
isSecure = true; isSecure = true;
_mqttClient = static_cast<MqttClient *>(new espMqttClientSecure(espMqttClientTypes::UseInternalTask::NO)); _mqttClient = new espMqttClientSecure(espMqttClientTypes::UseInternalTask::NO);
if (_state.rootCA == "insecure") { if (_state.rootCA == "insecure") {
static_cast<espMqttClientSecure *>(_mqttClient)->setInsecure(); static_cast<espMqttClientSecure *>(_mqttClient)->setInsecure();
} else { } else {
String certificate = "-----BEGIN CERTIFICATE-----\n" + _state.rootCA + "\n-----END CERTIFICATE-----\n"; String certificate = "-----BEGIN CERTIFICATE-----\n" + _state.rootCA + "\n-----END CERTIFICATE-----\n";
static_cast<espMqttClientSecure *>(_mqttClient)->setCACert(retainCstr(certificate.c_str(), &_retainedRootCA)); static_cast<espMqttClientSecure *>(_mqttClient)->setCACert(certificate.c_str());
} }
static_cast<espMqttClientSecure *>(_mqttClient)->onConnect(std::bind(&MqttSettingsService::onMqttConnect, this, _1)); static_cast<espMqttClientSecure *>(_mqttClient)->onConnect([this](bool sessionPresent) { onMqttConnect(sessionPresent); });
static_cast<espMqttClientSecure *>(_mqttClient)->onDisconnect(std::bind(&MqttSettingsService::onMqttDisconnect, this, _1)); static_cast<espMqttClientSecure *>(_mqttClient)->onDisconnect([this](espMqttClientTypes::DisconnectReason reason) { onMqttDisconnect(reason); });
static_cast<espMqttClientSecure *>(_mqttClient)->onMessage(std::bind(&MqttSettingsService::onMqttMessage, this, _1, _2, _3, _4, _5, _6)); static_cast<espMqttClientSecure *>(_mqttClient)
->onMessage(
[this](const espMqttClientTypes::MessageProperties & properties, const char * topic, const uint8_t * payload, size_t len, size_t index, size_t total) {
onMqttMessage(properties, topic, payload, len, index, total);
});
return; return;
} }
#endif #endif
isSecure = false; isSecure = false;
_mqttClient = static_cast<MqttClient *>(new espMqttClient(espMqttClientTypes::UseInternalTask::NO)); _mqttClient = new espMqttClient(espMqttClientTypes::UseInternalTask::NO);
static_cast<espMqttClient *>(_mqttClient)->onConnect(std::bind(&MqttSettingsService::onMqttConnect, this, _1)); static_cast<espMqttClient *>(_mqttClient)->onConnect([this](bool sessionPresent) { onMqttConnect(sessionPresent); });
static_cast<espMqttClient *>(_mqttClient)->onDisconnect(std::bind(&MqttSettingsService::onMqttDisconnect, this, _1)); static_cast<espMqttClient *>(_mqttClient)->onDisconnect([this](espMqttClientTypes::DisconnectReason reason) { onMqttDisconnect(reason); });
static_cast<espMqttClient *>(_mqttClient)->onMessage(std::bind(&MqttSettingsService::onMqttMessage, this, _1, _2, _3, _4, _5, _6)); static_cast<espMqttClient *>(_mqttClient)
->onMessage(
[this](const espMqttClientTypes::MessageProperties & properties, const char * topic, const uint8_t * payload, size_t len, size_t index, size_t total) {
onMqttMessage(properties, topic, payload, len, index, total);
});
} }
void MqttSettingsService::loop() { void MqttSettingsService::loop() {
if (_reconfigureMqtt || (_disconnectedAt && (uint32_t)(uuid::get_uptime() - _disconnectedAt) >= MQTT_RECONNECTION_DELAY)) { if (_reconfigureMqtt || (_disconnectedAt && static_cast<uint32_t>(uuid::get_uptime() - _disconnectedAt) >= MQTT_RECONNECTION_DELAY)) {
// reconfigure MQTT client // reconfigure MQTT client
_disconnectedAt = configureMqtt() ? 0 : uuid::get_uptime(); _disconnectedAt = configureMqtt() ? 0 : uuid::get_uptime();
_reconfigureMqtt = false; _reconfigureMqtt = false;
@@ -116,6 +107,9 @@ void MqttSettingsService::onMqttMessage(const espMqttClientTypes::MessagePropert
size_t len, size_t len,
size_t index, size_t index,
size_t total) { size_t total) {
(void)properties;
(void)index;
(void)total;
emsesp::EMSESP::mqtt_.on_message(topic, payload, len); emsesp::EMSESP::mqtt_.on_message(topic, payload, len);
} }
@@ -128,9 +122,12 @@ MqttClient * MqttSettingsService::getMqttClient() {
} }
void MqttSettingsService::onMqttConnect(bool sessionPresent) { void MqttSettingsService::onMqttConnect(bool sessionPresent) {
(void)sessionPresent;
// _disconnectedAt = 0; // _disconnectedAt = 0;
emsesp::EMSESP::mqtt_.on_connect(); emsesp::EMSESP::mqtt_.on_connect();
// emsesp::EMSESP::logger().info("Connected to MQTT, %s", (sessionPresent) ? ("with persistent session") : ("without persistent session")); #ifdef EMSESP_DEBUG
emsesp::EMSESP::logger().debug("Connected to MQTT, %s", (sessionPresent) ? ("with persistent session") : ("without persistent session"));
#endif
} }
void MqttSettingsService::onMqttDisconnect(espMqttClientTypes::DisconnectReason reason) { void MqttSettingsService::onMqttDisconnect(espMqttClientTypes::DisconnectReason reason) {
@@ -149,7 +146,7 @@ void MqttSettingsService::onConfigUpdated() {
emsesp::EMSESP::mqtt_.start(); // reload EMS-ESP MQTT settings emsesp::EMSESP::mqtt_.start(); // reload EMS-ESP MQTT settings
} }
void MqttSettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) { void MqttSettingsService::WiFiEvent(WiFiEvent_t event) {
switch (event) { switch (event) {
case ARDUINO_EVENT_WIFI_STA_GOT_IP: case ARDUINO_EVENT_WIFI_STA_GOT_IP:
case ARDUINO_EVENT_ETH_GOT_IP: case ARDUINO_EVENT_ETH_GOT_IP:
@@ -183,31 +180,25 @@ bool MqttSettingsService::configureMqtt() {
_reconfigureMqtt = false; _reconfigureMqtt = false;
#if CONFIG_IDF_TARGET_ESP32S3 #if CONFIG_IDF_TARGET_ESP32S3
if (_state.enableTLS) { if (_state.enableTLS) {
// emsesp::EMSESP::logger().info("Start secure MQTT with rootCA"); #if EMSESP_DEBUG
static_cast<espMqttClientSecure *>(_mqttClient)->setServer(retainCstr(_state.host.c_str(), &_retainedHost), _state.port); emsesp::EMSESP::logger().debug("Start secure MQTT with rootCA");
#endif
static_cast<espMqttClientSecure *>(_mqttClient)->setServer(_state.host.c_str(), _state.port);
if (_state.username.length() > 0) { if (_state.username.length() > 0) {
static_cast<espMqttClientSecure *>(_mqttClient) static_cast<espMqttClientSecure *>(_mqttClient)
->setCredentials(retainCstr(_state.username.c_str(), &_retainedUsername), ->setCredentials(_state.username.c_str(), _state.password.length() > 0 ? _state.password.c_str() : nullptr);
retainCstr(_state.password.length() > 0 ? _state.password.c_str() : nullptr, &_retainedPassword));
} else {
static_cast<espMqttClientSecure *>(_mqttClient)->setCredentials(retainCstr(nullptr, &_retainedUsername), retainCstr(nullptr, &_retainedPassword));
} }
static_cast<espMqttClientSecure *>(_mqttClient)->setClientId(retainCstr(_state.clientId.c_str(), &_retainedClientId)); static_cast<espMqttClientSecure *>(_mqttClient)->setClientId(_state.clientId.c_str());
static_cast<espMqttClientSecure *>(_mqttClient)->setKeepAlive(_state.keepAlive); static_cast<espMqttClientSecure *>(_mqttClient)->setKeepAlive(_state.keepAlive);
static_cast<espMqttClientSecure *>(_mqttClient)->setCleanSession(_state.cleanSession); static_cast<espMqttClientSecure *>(_mqttClient)->setCleanSession(_state.cleanSession);
return _mqttClient->connect(); return _mqttClient->connect();
} }
#endif #endif
// emsesp::EMSESP::logger().info("Configuring MQTT client"); static_cast<espMqttClient *>(_mqttClient)->setServer(_state.host.c_str(), _state.port);
static_cast<espMqttClient *>(_mqttClient)->setServer(retainCstr(_state.host.c_str(), &_retainedHost), _state.port);
if (_state.username.length() > 0) { if (_state.username.length() > 0) {
static_cast<espMqttClient *>(_mqttClient) static_cast<espMqttClient *>(_mqttClient)->setCredentials(_state.username.c_str(), _state.password.length() > 0 ? _state.password.c_str() : nullptr);
->setCredentials(retainCstr(_state.username.c_str(), &_retainedUsername),
retainCstr(_state.password.length() > 0 ? _state.password.c_str() : nullptr, &_retainedPassword));
} else {
static_cast<espMqttClient *>(_mqttClient)->setCredentials(retainCstr(nullptr, &_retainedUsername), retainCstr(nullptr, &_retainedPassword));
} }
static_cast<espMqttClient *>(_mqttClient)->setClientId(retainCstr(_state.clientId.c_str(), &_retainedClientId)); static_cast<espMqttClient *>(_mqttClient)->setClientId(_state.clientId.c_str());
static_cast<espMqttClient *>(_mqttClient)->setKeepAlive(_state.keepAlive); static_cast<espMqttClient *>(_mqttClient)->setKeepAlive(_state.keepAlive);
static_cast<espMqttClient *>(_mqttClient)->setCleanSession(_state.cleanSession); static_cast<espMqttClient *>(_mqttClient)->setCleanSession(_state.cleanSession);
return _mqttClient->connect(); return _mqttClient->connect();
@@ -265,33 +256,33 @@ StateUpdateResult MqttSettings::update(JsonObject root, MqttSettings & settings)
#endif #endif
newSettings.enabled = root["enabled"] | FACTORY_MQTT_ENABLED; newSettings.enabled = root["enabled"] | FACTORY_MQTT_ENABLED;
newSettings.host = root["host"] | FACTORY_MQTT_HOST; newSettings.host = root["host"] | FACTORY_MQTT_HOST;
newSettings.port = root["port"] | FACTORY_MQTT_PORT; newSettings.port = static_cast<uint16_t>(root["port"] | FACTORY_MQTT_PORT);
newSettings.base = root["base"] | FACTORY_MQTT_BASE; newSettings.base = root["base"] | FACTORY_MQTT_BASE;
newSettings.username = root["username"] | FACTORY_MQTT_USERNAME; newSettings.username = root["username"] | FACTORY_MQTT_USERNAME;
newSettings.password = root["password"] | FACTORY_MQTT_PASSWORD; newSettings.password = root["password"] | FACTORY_MQTT_PASSWORD;
newSettings.clientId = root["client_id"] | FACTORY_MQTT_CLIENT_ID; newSettings.clientId = root["client_id"] | generateClientId();
newSettings.keepAlive = root["keep_alive"] | FACTORY_MQTT_KEEP_ALIVE; newSettings.keepAlive = static_cast<uint16_t>(root["keep_alive"] | FACTORY_MQTT_KEEP_ALIVE);
newSettings.cleanSession = root["clean_session"] | FACTORY_MQTT_CLEAN_SESSION; newSettings.cleanSession = root["clean_session"] | FACTORY_MQTT_CLEAN_SESSION;
newSettings.mqtt_qos = root["mqtt_qos"] | EMSESP_DEFAULT_MQTT_QOS; newSettings.mqtt_qos = static_cast<uint8_t>(root["mqtt_qos"] | EMSESP_DEFAULT_MQTT_QOS);
newSettings.mqtt_retain = root["mqtt_retain"] | EMSESP_DEFAULT_MQTT_RETAIN; newSettings.mqtt_retain = root["mqtt_retain"] | EMSESP_DEFAULT_MQTT_RETAIN;
newSettings.publish_time_boiler = root["publish_time_boiler"] | EMSESP_DEFAULT_PUBLISH_TIME; newSettings.publish_time_boiler = static_cast<uint16_t>(root["publish_time_boiler"] | EMSESP_DEFAULT_PUBLISH_TIME);
newSettings.publish_time_thermostat = root["publish_time_thermostat"] | EMSESP_DEFAULT_PUBLISH_TIME; newSettings.publish_time_thermostat = static_cast<uint16_t>(root["publish_time_thermostat"] | EMSESP_DEFAULT_PUBLISH_TIME);
newSettings.publish_time_solar = root["publish_time_solar"] | EMSESP_DEFAULT_PUBLISH_TIME; newSettings.publish_time_solar = static_cast<uint16_t>(root["publish_time_solar"] | EMSESP_DEFAULT_PUBLISH_TIME);
newSettings.publish_time_mixer = root["publish_time_mixer"] | EMSESP_DEFAULT_PUBLISH_TIME; newSettings.publish_time_mixer = static_cast<uint16_t>(root["publish_time_mixer"] | EMSESP_DEFAULT_PUBLISH_TIME);
newSettings.publish_time_water = root["publish_time_water"] | EMSESP_DEFAULT_PUBLISH_TIME; newSettings.publish_time_water = static_cast<uint16_t>(root["publish_time_water"] | EMSESP_DEFAULT_PUBLISH_TIME);
newSettings.publish_time_other = root["publish_time_other"] | EMSESP_DEFAULT_PUBLISH_TIME; newSettings.publish_time_other = static_cast<uint16_t>(root["publish_time_other"] | EMSESP_DEFAULT_PUBLISH_TIME);
newSettings.publish_time_sensor = root["publish_time_sensor"] | EMSESP_DEFAULT_PUBLISH_TIME; newSettings.publish_time_sensor = static_cast<uint16_t>(root["publish_time_sensor"] | EMSESP_DEFAULT_PUBLISH_TIME);
newSettings.publish_time_heartbeat = root["publish_time_heartbeat"] | EMSESP_DEFAULT_PUBLISH_HEARTBEAT; newSettings.publish_time_heartbeat = static_cast<uint16_t>(root["publish_time_heartbeat"] | EMSESP_DEFAULT_PUBLISH_HEARTBEAT);
newSettings.ha_enabled = root["ha_enabled"] | EMSESP_DEFAULT_HA_ENABLED; newSettings.ha_enabled = root["ha_enabled"] | EMSESP_DEFAULT_HA_ENABLED;
newSettings.nested_format = root["nested_format"] | EMSESP_DEFAULT_NESTED_FORMAT; newSettings.nested_format = static_cast<uint8_t>(root["nested_format"] | EMSESP_DEFAULT_NESTED_FORMAT);
newSettings.discovery_prefix = root["discovery_prefix"] | EMSESP_DEFAULT_DISCOVERY_PREFIX; newSettings.discovery_prefix = root["discovery_prefix"] | EMSESP_DEFAULT_DISCOVERY_PREFIX;
newSettings.discovery_type = root["discovery_type"] | EMSESP_DEFAULT_DISCOVERY_TYPE; newSettings.discovery_type = static_cast<uint8_t>(root["discovery_type"] | EMSESP_DEFAULT_DISCOVERY_TYPE);
newSettings.publish_single = root["publish_single"] | EMSESP_DEFAULT_PUBLISH_SINGLE; newSettings.publish_single = root["publish_single"] | EMSESP_DEFAULT_PUBLISH_SINGLE;
newSettings.publish_single2cmd = root["publish_single2cmd"] | EMSESP_DEFAULT_PUBLISH_SINGLE2CMD; newSettings.publish_single2cmd = root["publish_single2cmd"] | EMSESP_DEFAULT_PUBLISH_SINGLE2CMD;
newSettings.send_response = root["send_response"] | EMSESP_DEFAULT_SEND_RESPONSE; newSettings.send_response = root["send_response"] | EMSESP_DEFAULT_SEND_RESPONSE;
newSettings.entity_format = root["entity_format"] | EMSESP_DEFAULT_ENTITY_FORMAT; newSettings.entity_format = static_cast<uint8_t>(root["entity_format"] | EMSESP_DEFAULT_ENTITY_FORMAT);
if (newSettings.enabled != settings.enabled) { if (newSettings.enabled != settings.enabled) {
changed = true; changed = true;

View File

@@ -1,11 +1,11 @@
#ifndef MqttSettingsService_h #ifndef MqttSettingsService_h
#define MqttSettingsService_h #define MqttSettingsService_h
#include <StatefulService.h> #include "StatefulService.h"
#include <HttpEndpoint.h> #include "HttpEndpoint.h"
#include <FSPersistence.h> #include "FSPersistence.h"
#include <espMqttClient.h> #include <espMqttClient.h>
#include <ESPUtils.h>
#include <uuid/common.h> #include <uuid/common.h>
@@ -38,13 +38,6 @@
#define FACTORY_MQTT_PASSWORD "" #define FACTORY_MQTT_PASSWORD ""
#endif #endif
#ifndef FACTORY_MQTT_CLIENT_ID
#define FACTORY_MQTT_CLIENT_ID generateClientId()
static String generateClientId() {
return ESPUtils::defaultDeviceValue("esp32-");
}
#endif
#ifndef FACTORY_MQTT_KEEP_ALIVE #ifndef FACTORY_MQTT_KEEP_ALIVE
#define FACTORY_MQTT_KEEP_ALIVE 16 #define FACTORY_MQTT_KEEP_ALIVE 16
#endif #endif
@@ -59,21 +52,14 @@ static String generateClientId() {
class MqttSettings { class MqttSettings {
public: public:
// host and port - if enabled
bool enabled; bool enabled;
String host; String host;
uint16_t port; uint16_t port;
String rootCA; String rootCA;
bool enableTLS; bool enableTLS;
// username and password
String username; String username;
String password; String password;
// client id settings
String clientId; String clientId;
// connection settings
uint16_t keepAlive; uint16_t keepAlive;
bool cleanSession; bool cleanSession;
@@ -124,14 +110,6 @@ class MqttSettingsService : public StatefulService<MqttSettings> {
HttpEndpoint<MqttSettings> _httpEndpoint; HttpEndpoint<MqttSettings> _httpEndpoint;
FSPersistence<MqttSettings> _fsPersistence; FSPersistence<MqttSettings> _fsPersistence;
// Pointers to hold retained copies of the mqtt client connection strings.
// This is required as espMqttClient holds references to the supplied connection strings.
char * _retainedHost;
char * _retainedClientId;
char * _retainedUsername;
char * _retainedPassword;
char * _retainedRootCA;
// variable to help manage connection // variable to help manage connection
bool _reconfigureMqtt; bool _reconfigureMqtt;
unsigned long _disconnectedAt; unsigned long _disconnectedAt;
@@ -142,11 +120,12 @@ class MqttSettingsService : public StatefulService<MqttSettings> {
// the MQTT client instance // the MQTT client instance
MqttClient * _mqttClient; MqttClient * _mqttClient;
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info); void WiFiEvent(WiFiEvent_t event);
void onMqttConnect(bool sessionPresent); void onMqttConnect(bool sessionPresent);
void onMqttDisconnect(espMqttClientTypes::DisconnectReason reason); void onMqttDisconnect(espMqttClientTypes::DisconnectReason reason);
void void
onMqttMessage(const espMqttClientTypes::MessageProperties & properties, const char * topic, const uint8_t * payload, size_t len, size_t index, size_t total); onMqttMessage(const espMqttClientTypes::MessageProperties & properties, const char * topic, const uint8_t * payload, size_t len, size_t index, size_t total);
bool configureMqtt(); bool configureMqtt();
}; };

View File

@@ -1,18 +1,16 @@
#include <MqttStatus.h> #include "MqttStatus.h"
#include "../../src/emsesp_stub.hpp" #include "../../src/emsesp_stub.hpp"
using namespace std::placeholders; // for `_1` etc
MqttStatus::MqttStatus(AsyncWebServer * server, MqttSettingsService * mqttSettingsService, SecurityManager * securityManager) MqttStatus::MqttStatus(AsyncWebServer * server, MqttSettingsService * mqttSettingsService, SecurityManager * securityManager)
: _mqttSettingsService(mqttSettingsService) { : _mqttSettingsService(mqttSettingsService) {
server->on(MQTT_STATUS_SERVICE_PATH, server->on(MQTT_STATUS_SERVICE_PATH,
HTTP_GET, HTTP_GET,
securityManager->wrapRequest(std::bind(&MqttStatus::mqttStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); securityManager->wrapRequest([this](AsyncWebServerRequest * request) { mqttStatus(request); }, AuthenticationPredicates::IS_AUTHENTICATED));
} }
void MqttStatus::mqttStatus(AsyncWebServerRequest * request) { void MqttStatus::mqttStatus(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false); auto * response = new AsyncJsonResponse(false);
JsonObject root = response->getRoot(); JsonObject root = response->getRoot();
root["enabled"] = _mqttSettingsService->isEnabled(); root["enabled"] = _mqttSettingsService->isEnabled();

View File

@@ -2,10 +2,11 @@
#define MqttStatus_h #define MqttStatus_h
#include <WiFi.h> #include <WiFi.h>
#include <MqttSettingsService.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#include "MqttSettingsService.h"
#include "SecurityManager.h"
#define MQTT_STATUS_SERVICE_PATH "/rest/mqttStatus" #define MQTT_STATUS_SERVICE_PATH "/rest/mqttStatus"

View File

@@ -1,20 +1,20 @@
#include <NTPSettingsService.h> #include "NTPSettingsService.h"
#include "../../src/emsesp_stub.hpp" #include "../../src/emsesp_stub.hpp"
using namespace std::placeholders; // for `_1` etc
NTPSettingsService::NTPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) NTPSettingsService::NTPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(NTPSettings::read, NTPSettings::update, this, server, NTP_SETTINGS_SERVICE_PATH, securityManager) : _httpEndpoint(NTPSettings::read, NTPSettings::update, this, server, NTP_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(NTPSettings::read, NTPSettings::update, this, fs, NTP_SETTINGS_FILE) , _fsPersistence(NTPSettings::read, NTPSettings::update, this, fs, NTP_SETTINGS_FILE)
, _timeHandler(TIME_PATH, securityManager->wrapCallback(std::bind(&NTPSettingsService::configureTime, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) { , _timeHandler(TIME_PATH,
securityManager->wrapCallback([this](AsyncWebServerRequest * request, JsonVariant json) { configureTime(request, json); },
AuthenticationPredicates::IS_ADMIN))
, _connected(false) {
_timeHandler.setMethod(HTTP_POST); _timeHandler.setMethod(HTTP_POST);
_timeHandler.setMaxContentLength(MAX_TIME_SIZE); _timeHandler.setMaxContentLength(MAX_TIME_SIZE);
server->addHandler(&_timeHandler); server->addHandler(&_timeHandler);
WiFi.onEvent(std::bind(&NTPSettingsService::WiFiEvent, this, _1)); WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event); });
addUpdateHandler([this] { configureNTP(); }, false);
addUpdateHandler([&](const String & originId) { configureNTP(); }, false);
} }
void NTPSettingsService::begin() { void NTPSettingsService::begin() {
@@ -27,9 +27,9 @@ void NTPSettingsService::WiFiEvent(WiFiEvent_t event) {
switch (event) { switch (event) {
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
case ARDUINO_EVENT_ETH_DISCONNECTED: case ARDUINO_EVENT_ETH_DISCONNECTED:
if (connected_) { if (_connected) {
emsesp::EMSESP::logger().info("WiFi connection dropped, stopping NTP"); emsesp::EMSESP::logger().info("WiFi connection dropped, stopping NTP");
connected_ = false; _connected = false;
configureNTP(); configureNTP();
} }
break; break;
@@ -37,7 +37,7 @@ void NTPSettingsService::WiFiEvent(WiFiEvent_t event) {
case ARDUINO_EVENT_WIFI_STA_GOT_IP: case ARDUINO_EVENT_WIFI_STA_GOT_IP:
case ARDUINO_EVENT_ETH_GOT_IP: case ARDUINO_EVENT_ETH_GOT_IP:
// emsesp::EMSESP::logger().info("Got IP address, starting NTP synchronization"); // emsesp::EMSESP::logger().info("Got IP address, starting NTP synchronization");
connected_ = true; _connected = true;
configureNTP(); configureNTP();
break; break;
@@ -49,7 +49,7 @@ void NTPSettingsService::WiFiEvent(WiFiEvent_t event) {
// https://werner.rothschopf.net/microcontroller/202103_arduino_esp32_ntp_en.htm // https://werner.rothschopf.net/microcontroller/202103_arduino_esp32_ntp_en.htm
void NTPSettingsService::configureNTP() { void NTPSettingsService::configureNTP() {
emsesp::EMSESP::system_.ntp_connected(false); emsesp::EMSESP::system_.ntp_connected(false);
if (connected_ && _state.enabled) { if (_connected && _state.enabled) {
emsesp::EMSESP::logger().info("Starting NTP service"); emsesp::EMSESP::logger().info("Starting NTP service");
esp_sntp_set_sync_interval(3600000); // one hour esp_sntp_set_sync_interval(3600000); // one hour
esp_sntp_set_time_sync_notification_cb(ntp_received); esp_sntp_set_time_sync_notification_cb(ntp_received);
@@ -63,13 +63,13 @@ void NTPSettingsService::configureNTP() {
void NTPSettingsService::configureTime(AsyncWebServerRequest * request, JsonVariant json) { void NTPSettingsService::configureTime(AsyncWebServerRequest * request, JsonVariant json) {
if (json.is<JsonObject>()) { if (json.is<JsonObject>()) {
struct tm tm = {0}; struct tm tm = {};
String timeLocal = json["local_time"]; String timeLocal = json["local_time"];
char * s = strptime(timeLocal.c_str(), "%Y-%m-%dT%H:%M:%S", &tm); char * s = strptime(timeLocal.c_str(), "%Y-%m-%dT%H:%M:%S", &tm);
if (s != nullptr) { if (s != nullptr) {
tm.tm_isdst = -1; // not set by strptime, tells mktime to determine daylightsaving tm.tm_isdst = -1; // not set by strptime, tells mktime to determine daylightsaving
time_t time = mktime(&tm); time_t time = mktime(&tm);
struct timeval now = {.tv_sec = time}; struct timeval now = {.tv_sec = time, .tv_usec = {}};
settimeofday(&now, nullptr); settimeofday(&now, nullptr);
AsyncWebServerResponse * response = request->beginResponse(200); AsyncWebServerResponse * response = request->beginResponse(200);
request->send(response); request->send(response);
@@ -82,6 +82,22 @@ void NTPSettingsService::configureTime(AsyncWebServerRequest * request, JsonVari
} }
void NTPSettingsService::ntp_received(struct timeval * tv) { void NTPSettingsService::ntp_received(struct timeval * tv) {
(void)tv;
// emsesp::EMSESP::logger().info("NTP sync to %d sec", tv->tv_sec); // emsesp::EMSESP::logger().info("NTP sync to %d sec", tv->tv_sec);
emsesp::EMSESP::system_.ntp_connected(true); emsesp::EMSESP::system_.ntp_connected(true);
} }
void NTPSettings::read(NTPSettings & settings, JsonObject root) {
root["enabled"] = settings.enabled;
root["server"] = settings.server;
root["tz_label"] = settings.tzLabel;
root["tz_format"] = settings.tzFormat;
}
StateUpdateResult NTPSettings::update(JsonObject root, NTPSettings & settings) {
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;
return StateUpdateResult::CHANGED;
}

View File

@@ -1,10 +1,10 @@
#ifndef NTPSettingsService_h #ifndef NTPSettingsService_h
#define NTPSettingsService_h #define NTPSettingsService_h
#include <HttpEndpoint.h> #include "HttpEndpoint.h"
#include <FSPersistence.h> #include "FSPersistence.h"
#include <time.h> #include <ctime>
#include <esp_sntp.h> #include <esp_sntp.h>
#ifndef FACTORY_NTP_ENABLED #ifndef FACTORY_NTP_ENABLED
@@ -36,20 +36,8 @@ class NTPSettings {
String tzFormat; String tzFormat;
String server; String server;
static void read(NTPSettings & settings, JsonObject root) { static void read(NTPSettings & settings, JsonObject root);
root["enabled"] = settings.enabled; static StateUpdateResult update(JsonObject root, NTPSettings & settings);
root["server"] = settings.server;
root["tz_label"] = settings.tzLabel;
root["tz_format"] = settings.tzFormat;
}
static StateUpdateResult update(JsonObject root, NTPSettings & settings) {
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;
return StateUpdateResult::CHANGED;
}
}; };
class NTPSettingsService : public StatefulService<NTPSettings> { class NTPSettingsService : public StatefulService<NTPSettings> {
@@ -63,8 +51,8 @@ class NTPSettingsService : public StatefulService<NTPSettings> {
HttpEndpoint<NTPSettings> _httpEndpoint; HttpEndpoint<NTPSettings> _httpEndpoint;
FSPersistence<NTPSettings> _fsPersistence; FSPersistence<NTPSettings> _fsPersistence;
AsyncCallbackJsonWebHandler _timeHandler; AsyncCallbackJsonWebHandler _timeHandler;
bool _connected;
bool connected_ = false;
void WiFiEvent(WiFiEvent_t event); void WiFiEvent(WiFiEvent_t event);
void configureNTP(); void configureNTP();
void configureTime(AsyncWebServerRequest * request, JsonVariant json); void configureTime(AsyncWebServerRequest * request, JsonVariant json);

View File

@@ -1,12 +1,13 @@
#include <NTPStatus.h> #include "NTPStatus.h"
#include "../../src/emsesp_stub.hpp" #include "../../src/emsesp_stub.hpp"
using namespace std::placeholders; // for `_1` etc #include <array>
NTPStatus::NTPStatus(AsyncWebServer * server, SecurityManager * securityManager) { NTPStatus::NTPStatus(AsyncWebServer * server, SecurityManager * securityManager) {
server->on(NTP_STATUS_SERVICE_PATH, server->on(NTP_STATUS_SERVICE_PATH,
HTTP_GET, HTTP_GET,
securityManager->wrapRequest(std::bind(&NTPStatus::ntpStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); securityManager->wrapRequest([this](AsyncWebServerRequest * request) { ntpStatus(request); }, AuthenticationPredicates::IS_AUTHENTICATED));
} }
/* /*
@@ -15,9 +16,9 @@ NTPStatus::NTPStatus(AsyncWebServer * server, SecurityManager * securityManager)
* Uses a 25 byte buffer, large enough to fit an ISO time string with offset. * Uses a 25 byte buffer, large enough to fit an ISO time string with offset.
*/ */
String formatTime(tm * time, const char * format) { String formatTime(tm * time, const char * format) {
char time_string[25]; std::array<char, 25> time_string{};
strftime(time_string, 25, format, time); strftime(time_string.data(), time_string.size(), format, time);
return String(time_string); return {time_string.data()};
} }
String toUTCTimeString(tm * time) { String toUTCTimeString(tm * time) {
@@ -29,14 +30,23 @@ String toLocalTimeString(tm * time) {
} }
void NTPStatus::ntpStatus(AsyncWebServerRequest * request) { void NTPStatus::ntpStatus(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false); auto * response = new AsyncJsonResponse(false);
JsonObject root = response->getRoot(); JsonObject root = response->getRoot();
// grab the current instant in unix seconds // grab the current instant in unix seconds
time_t now = time(nullptr); time_t now = time(nullptr);
// only provide enabled/disabled status for now // only provide enabled/disabled status for now
root["status"] = esp_sntp_enabled() ? emsesp::EMSESP::system_.ntp_connected() ? 2 : 1 : 0; root["status"] = [] {
if (esp_sntp_enabled()) {
if (emsesp::EMSESP::system_.ntp_connected()) {
return 2;
} else {
return 1;
}
}
return 0;
}();
// the current time in UTC // the current time in UTC
root["utc_time"] = toUTCTimeString(gmtime(&now)); root["utc_time"] = toUTCTimeString(gmtime(&now));

View File

@@ -1,14 +1,14 @@
#ifndef NTPStatus_h #ifndef NTPStatus_h
#define NTPStatus_h #define NTPStatus_h
#include <time.h> #include <ctime>
#include <WiFi.h> #include <WiFi.h>
#include <esp_sntp.h> #include <esp_sntp.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#include "SecurityManager.h"
#include <uuid/common.h> #include <uuid/common.h>
#define NTP_STATUS_SERVICE_PATH "/rest/ntpStatus" #define NTP_STATUS_SERVICE_PATH "/rest/ntpStatus"

View File

@@ -1,13 +1,25 @@
#include <NetworkSettingsService.h> #include "NetworkSettingsService.h"
using namespace std::placeholders; // for `_1` etc #include "../../src/emsesp_stub.hpp"
NetworkSettingsService::NetworkSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) NetworkSettingsService::NetworkSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(NetworkSettings::read, NetworkSettings::update, this, server, NETWORK_SETTINGS_SERVICE_PATH, securityManager) : _httpEndpoint(NetworkSettings::read, NetworkSettings::update, this, server, NETWORK_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(NetworkSettings::read, NetworkSettings::update, this, fs, NETWORK_SETTINGS_FILE) , _fsPersistence(NetworkSettings::read, NetworkSettings::update, this, fs, NETWORK_SETTINGS_FILE)
, _lastConnectionAttempt(0) { , _lastConnectionAttempt(0)
addUpdateHandler([&](const String & originId) { reconfigureWiFiConnection(); }, false); , _stopping(false) {
WiFi.onEvent(std::bind(&NetworkSettingsService::WiFiEvent, this, _1)); addUpdateHandler([this] { reconfigureWiFiConnection(); }, false);
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event, info); });
}
static bool formatBssid(const String & bssid, uint8_t (&mac)[6]) {
uint tmp[6];
if (bssid.isEmpty() || sscanf(bssid.c_str(), "%X:%X:%X:%X:%X:%X", &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5]) != 6) {
return false;
}
for (uint8_t i = 0; i < 6; i++) {
mac[i] = static_cast<uint8_t>(tmp[i]);
}
return true;
} }
void NetworkSettingsService::begin() { void NetworkSettingsService::begin() {
@@ -44,7 +56,7 @@ void NetworkSettingsService::reconfigureWiFiConnection() {
void NetworkSettingsService::loop() { void NetworkSettingsService::loop() {
unsigned long currentMillis = millis(); unsigned long currentMillis = millis();
if (!_lastConnectionAttempt || (uint32_t)(currentMillis - _lastConnectionAttempt) >= WIFI_RECONNECTION_DELAY) { if (!_lastConnectionAttempt || static_cast<uint32_t>(currentMillis - _lastConnectionAttempt) >= WIFI_RECONNECTION_DELAY) {
_lastConnectionAttempt = currentMillis; _lastConnectionAttempt = currentMillis;
manageSTA(); manageSTA();
} }
@@ -65,49 +77,379 @@ void NetworkSettingsService::manageSTA() {
// www.esp32.com/viewtopic.php?t=12055 // www.esp32.com/viewtopic.php?t=12055
if (_state.bandwidth20) { if (_state.bandwidth20) {
esp_wifi_set_bandwidth((wifi_interface_t)ESP_IF_WIFI_STA, WIFI_BW_HT20); esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_STA), WIFI_BW_HT20);
} else { } else {
esp_wifi_set_bandwidth((wifi_interface_t)ESP_IF_WIFI_STA, WIFI_BW_HT40); esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_STA), WIFI_BW_HT40);
} }
if (_state.nosleep) { if (_state.nosleep) {
WiFi.setSleep(false); // turn off sleep - WIFI_PS_NONE WiFi.setSleep(false); // turn off sleep - WIFI_PS_NONE
} }
// attempt to connect to the network // attempt to connect to the network
uint mac[6]; uint8_t bssid[6];
if (!_state.bssid.isEmpty() && sscanf(_state.bssid.c_str(), "%X:%X:%X:%X:%X:%X", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) == 6) { if (formatBssid(_state.bssid, bssid)) {
uint8_t mac1[6]; WiFi.begin(_state.ssid.c_str(), _state.password.c_str(), 0, bssid);
for (uint8_t i = 0; i < 6; i++) {
mac1[i] = (uint8_t)mac[i];
}
WiFi.begin(_state.ssid.c_str(), _state.password.c_str(), 0, mac1);
} else { } else {
WiFi.begin(_state.ssid.c_str(), _state.password.c_str()); WiFi.begin(_state.ssid.c_str(), _state.password.c_str());
} }
// set power after wifi is startet, fixed value for C3_V1
// if (WiFi.isConnected()) {
#ifdef BOARD_C3_MINI_V1 #ifdef BOARD_C3_MINI_V1
// always hardcode Tx power for Wemos CS Mini v1
// v1 needs this value, see https://github.com/emsesp/EMS-ESP32/pull/620#discussion_r993173979 // v1 needs this value, see https://github.com/emsesp/EMS-ESP32/pull/620#discussion_r993173979
WiFi.setTxPower(WIFI_POWER_8_5dBm); // https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi // https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi
WiFi.setTxPower(WIFI_POWER_8_5dBm);
#else #else
// esp_wifi_set_max_tx_power(_state.tx_power * 4); if (_state.tx_power != 0) {
// TODO make it dynamic // if not set to Auto (0) set the Tx power now
WiFi.setTxPower((wifi_power_t)(_state.tx_power * 4)); if (!WiFi.setTxPower(static_cast<wifi_power_t>(_state.tx_power))) {
emsesp::EMSESP::logger().warning("Failed to set WiFi Tx Power");
}
}
#endif #endif
// }
} else { // not connected but STA-mode active => disconnect } else { // not connected but STA-mode active => disconnect
reconfigureWiFiConnection(); reconfigureWiFiConnection();
} }
} }
// handles if wifi stopped // set the TxPower based on the RSSI (signal strength), picking the lowest value
void NetworkSettingsService::WiFiEvent(WiFiEvent_t event) { // code is based of RSSI (signal strength) and copied from Tasmota's WiFiSetTXpowerBasedOnRssi() which is copied ESPEasy's ESPEasyWifi.SetWiFiTXpower() function
if (event == ARDUINO_EVENT_WIFI_STA_STOP) { void NetworkSettingsService::setWiFiPowerOnRSSI() {
// Range ESP32 : 2dBm - 20dBm
// 802.11b - wifi1
// 802.11a - wifi2
// 802.11g - wifi3
// 802.11n - wifi4
// 802.11ac - wifi5
// 802.11ax - wifi6
int max_tx_pwr = MAX_TX_PWR_DBM_n; // assume wifi4
int threshold = WIFI_SENSITIVITY_n + 70; // Margin in dBm * 10 on top of threshold
// Assume AP sends with max set by ETSI standard.
// 2.4 GHz: 100 mWatt (20 dBm)
// US and some other countries allow 1000 mW (30 dBm)
int rssi = WiFi.RSSI() * 10;
int newrssi = rssi - 200; // We cannot send with over 20 dBm, thus it makes no sense to force higher TX power all the time.
int min_tx_pwr = 0;
if (newrssi < threshold) {
min_tx_pwr = threshold - newrssi;
}
if (min_tx_pwr > max_tx_pwr) {
min_tx_pwr = max_tx_pwr;
}
uint8_t set_power = min_tx_pwr / 10; // this is the recommended power setting to use
// from WiFIGeneric.h use:
// WIFI_POWER_19_5dBm = 78,// 19.5dBm
// WIFI_POWER_19dBm = 76,// 19dBm
// WIFI_POWER_18_5dBm = 74,// 18.5dBm
// WIFI_POWER_17dBm = 68,// 17dBm
// WIFI_POWER_15dBm = 60,// 15dBm
// WIFI_POWER_13dBm = 52,// 13dBm
// WIFI_POWER_11dBm = 44,// 11dBm
// WIFI_POWER_8_5dBm = 34,// 8.5dBm
// WIFI_POWER_7dBm = 28,// 7dBm
// WIFI_POWER_5dBm = 20,// 5dBm
// WIFI_POWER_2dBm = 8,// 2dBm
// WIFI_POWER_MINUS_1dBm = -4// -1dBm
wifi_power_t p = WIFI_POWER_2dBm;
if (min_tx_pwr > 185)
p = WIFI_POWER_19_5dBm;
else if (min_tx_pwr > 170)
p = WIFI_POWER_18_5dBm;
else if (min_tx_pwr > 150)
p = WIFI_POWER_17dBm;
else if (min_tx_pwr > 130)
p = WIFI_POWER_15dBm;
else if (min_tx_pwr > 110)
p = WIFI_POWER_13dBm;
else if (min_tx_pwr > 85)
p = WIFI_POWER_11dBm;
else if (min_tx_pwr > 70)
p = WIFI_POWER_8_5dBm;
else if (min_tx_pwr > 50)
p = WIFI_POWER_7dBm;
else if (min_tx_pwr > 20)
p = WIFI_POWER_5dBm;
#ifdef EMSESP_DEBUG
emsesp::EMSESP::logger().debug("Recommended set WiFi Tx Power (set_power %d, new power %d, rssi %d, threshold %d)", set_power, p, rssi, threshold);
#else
char result[10];
emsesp::EMSESP::logger().info("Setting WiFi Tx Power to %s dBm", emsesp::Helpers::render_value(result, ((double)(p) / 4), 1));
#endif
if (!WiFi.setTxPower(p)) {
emsesp::EMSESP::logger().warning("Failed to set WiFi Tx Power");
}
}
// start the multicast UDP service so EMS-ESP is discoverable via .local
void NetworkSettingsService::mDNS_start() const {
#ifndef EMSESP_STANDALONE
MDNS.end();
if (_state.enableMDNS) {
if (!MDNS.begin(emsesp::EMSESP::system_.hostname().c_str())) {
emsesp::EMSESP::logger().warning("Failed to start mDNS Responder service");
return;
}
std::string address_s = emsesp::EMSESP::system_.hostname() + ".local";
MDNS.addService("http", "tcp", 80); // add our web server and rest API
MDNS.addService("telnet", "tcp", 23); // add our telnet console
MDNS.addServiceTxt("http", "tcp", "version", EMSESP_APP_VERSION);
MDNS.addServiceTxt("http", "tcp", "address", address_s.c_str());
emsesp::EMSESP::logger().info("Starting mDNS Responder service");
}
#endif
}
const char * NetworkSettingsService::disconnectReason(uint8_t code) {
#ifndef EMSESP_STANDALONE
switch (code) {
case WIFI_REASON_UNSPECIFIED: // = 1,
return "unspecified";
case WIFI_REASON_AUTH_EXPIRE: // = 2,
return "auth expire";
case WIFI_REASON_AUTH_LEAVE: // = 3,
return "auth leave";
case WIFI_REASON_ASSOC_EXPIRE: // = 4,
return "assoc expired";
case WIFI_REASON_ASSOC_TOOMANY: // = 5,
return "assoc too many";
case WIFI_REASON_NOT_AUTHED: // = 6,
return "not authenticated";
case WIFI_REASON_NOT_ASSOCED: // = 7,
return "not assoc";
case WIFI_REASON_ASSOC_LEAVE: // = 8,
return "assoc leave";
case WIFI_REASON_ASSOC_NOT_AUTHED: // = 9,
return "assoc not authed";
case WIFI_REASON_DISASSOC_PWRCAP_BAD: // = 10,
return "disassoc powerCAP bad";
case WIFI_REASON_DISASSOC_SUPCHAN_BAD: // = 11,
return "disassoc supchan bad";
case WIFI_REASON_IE_INVALID: // = 13,
return "IE invalid";
case WIFI_REASON_MIC_FAILURE: // = 14,
return "MIC failure";
case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: // = 15,
return "4way handshake timeout";
case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT: // = 16,
return "group key-update timeout";
case WIFI_REASON_IE_IN_4WAY_DIFFERS: // = 17,
return "IE in 4way differs";
case WIFI_REASON_GROUP_CIPHER_INVALID: // = 18,
return "group cipher invalid";
case WIFI_REASON_PAIRWISE_CIPHER_INVALID: // = 19,
return "pairwise cipher invalid";
case WIFI_REASON_AKMP_INVALID: // = 20,
return "AKMP invalid";
case WIFI_REASON_UNSUPP_RSN_IE_VERSION: // = 21,
return "unsupported RSN_IE version";
case WIFI_REASON_INVALID_RSN_IE_CAP: // = 22,
return "invalid RSN_IE_CAP";
case WIFI_REASON_802_1X_AUTH_FAILED: // = 23,
return "802 X1 auth failed";
case WIFI_REASON_CIPHER_SUITE_REJECTED: // = 24,
return "cipher suite rejected";
case WIFI_REASON_BEACON_TIMEOUT: // = 200,
return "beacon timeout";
case WIFI_REASON_NO_AP_FOUND: // = 201,
return "no AP found";
case WIFI_REASON_AUTH_FAIL: // = 202,
return "auth fail";
case WIFI_REASON_ASSOC_FAIL: // = 203,
return "assoc fail";
case WIFI_REASON_HANDSHAKE_TIMEOUT: // = 204,
return "handshake timeout";
case WIFI_REASON_CONNECTION_FAIL: // 205,
return "connection fail";
case WIFI_REASON_AP_TSF_RESET: // 206,
return "AP tsf reset";
case WIFI_REASON_ROAMING: // 207,
return "roaming";
case WIFI_REASON_ASSOC_COMEBACK_TIME_TOO_LONG: // 208,
return "assoc comeback time too long";
case WIFI_REASON_SA_QUERY_TIMEOUT: // 209,
return "sa query timeout";
default:
return "unknown";
}
#endif
return "";
}
// handles both WiFI and Ethernet
void NetworkSettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
#ifndef EMSESP_STANDALONE
switch (event) {
case ARDUINO_EVENT_WIFI_STA_STOP:
if (_stopping) { if (_stopping) {
_lastConnectionAttempt = 0; _lastConnectionAttempt = 0;
_stopping = false; _stopping = false;
} }
break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
emsesp::EMSESP::logger().warning("WiFi disconnected. Reason: %s (%d)",
disconnectReason(info.wifi_sta_disconnected.reason),
info.wifi_sta_disconnected.reason); // IDF 4.0
emsesp::EMSESP::system_.has_ipv6(false);
break;
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
char result[10];
emsesp::EMSESP::logger().info("WiFi connected (IP=%s, hostname=%s, TxPower=%s dBm)",
WiFi.localIP().toString().c_str(),
WiFi.getHostname(),
emsesp::Helpers::render_value(result, ((double)(WiFi.getTxPower()) / 4), 1));
mDNS_start();
break;
case ARDUINO_EVENT_ETH_START:
ETH.setHostname(emsesp::EMSESP::system_.hostname().c_str());
// configure for static IP
if (_state.staticIPConfig) {
ETH.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2);
} }
// if (!_stopping && (event == ARDUINO_EVENT_WIFI_STA_LOST_IP || event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED)) { break;
// reconfigureWiFiConnection();
// } case ARDUINO_EVENT_ETH_GOT_IP:
// prevent double calls
if (!emsesp::EMSESP::system_.ethernet_connected()) {
emsesp::EMSESP::logger().info("Ethernet connected (IP=%s, speed %d Mbps)", ETH.localIP().toString().c_str(), ETH.linkSpeed());
emsesp::EMSESP::system_.ethernet_connected(true);
mDNS_start();
}
break;
case ARDUINO_EVENT_ETH_DISCONNECTED:
emsesp::EMSESP::logger().warning("Ethernet disconnected");
emsesp::EMSESP::system_.ethernet_connected(false);
emsesp::EMSESP::system_.has_ipv6(false);
break;
case ARDUINO_EVENT_ETH_STOP:
emsesp::EMSESP::logger().info("Ethernet stopped");
emsesp::EMSESP::system_.ethernet_connected(false);
emsesp::EMSESP::system_.has_ipv6(false);
break;
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
// Set the TxPower after the connection is established, if we're using TxPower = 0 (Auto)
if (_state.tx_power == 0) {
setWiFiPowerOnRSSI();
}
if (_state.enableIPv6) {
WiFi.enableIpV6();
}
break;
case ARDUINO_EVENT_ETH_CONNECTED:
if (_state.enableIPv6) {
ETH.enableIpV6();
}
break;
// IPv6 specific
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
case ARDUINO_EVENT_ETH_GOT_IP6:
if (emsesp::EMSESP::system_.ethernet_connected()) {
emsesp::EMSESP::logger().info("Ethernet connected (IPv6=%s, speed %d Mbps)", ETH.localIPv6().toString().c_str(), ETH.linkSpeed());
} else {
emsesp::EMSESP::logger().info("WiFi connected (IPv6=%s, hostname=%s, TxPower=%s dBm)",
WiFi.localIPv6().toString().c_str(),
WiFi.getHostname(),
emsesp::Helpers::render_value(result, ((double)(WiFi.getTxPower()) / 4), 1));
}
mDNS_start();
emsesp::EMSESP::system_.has_ipv6(true);
break;
default:
break;
}
#endif
}
void NetworkSettings::read(NetworkSettings & settings, JsonObject root) {
// connection settings
root["ssid"] = settings.ssid;
root["bssid"] = settings.bssid;
root["password"] = settings.password;
root["hostname"] = settings.hostname;
root["static_ip_config"] = settings.staticIPConfig;
root["enableIPv6"] = settings.enableIPv6;
root["bandwidth20"] = settings.bandwidth20;
root["nosleep"] = settings.nosleep;
root["enableMDNS"] = settings.enableMDNS;
root["enableCORS"] = settings.enableCORS;
root["CORSOrigin"] = settings.CORSOrigin;
root["tx_power"] = settings.tx_power;
// extended settings
JsonUtils::writeIP(root, "local_ip", settings.localIP);
JsonUtils::writeIP(root, "gateway_ip", settings.gatewayIP);
JsonUtils::writeIP(root, "subnet_mask", settings.subnetMask);
JsonUtils::writeIP(root, "dns_ip_1", settings.dnsIP1);
JsonUtils::writeIP(root, "dns_ip_2", settings.dnsIP2);
}
StateUpdateResult NetworkSettings::update(JsonObject root, NetworkSettings & settings) {
// keep copy of original settings
auto enableCORS = settings.enableCORS;
auto CORSOrigin = settings.CORSOrigin;
auto ssid = settings.ssid;
auto tx_power = settings.tx_power;
settings.ssid = root["ssid"] | FACTORY_WIFI_SSID;
settings.bssid = root["bssid"] | "";
settings.password = root["password"] | FACTORY_WIFI_PASSWORD;
settings.hostname = root["hostname"] | FACTORY_WIFI_HOSTNAME;
settings.staticIPConfig = root["static_ip_config"] | false;
settings.enableIPv6 = root["enableIPv6"] | false;
settings.bandwidth20 = root["bandwidth20"] | false;
settings.tx_power = static_cast<uint8_t>(root["tx_power"] | 0);
settings.nosleep = root["nosleep"] | false;
settings.enableMDNS = root["enableMDNS"] | true;
settings.enableCORS = root["enableCORS"] | false;
settings.CORSOrigin = root["CORSOrigin"] | "*";
// extended settings
JsonUtils::readIP(root, "local_ip", settings.localIP);
JsonUtils::readIP(root, "gateway_ip", settings.gatewayIP);
JsonUtils::readIP(root, "subnet_mask", settings.subnetMask);
JsonUtils::readIP(root, "dns_ip_1", settings.dnsIP1);
JsonUtils::readIP(root, "dns_ip_2", settings.dnsIP2);
// Swap around the dns servers if 2 is populated but 1 is not
if (IPUtils::isNotSet(settings.dnsIP1) && IPUtils::isSet(settings.dnsIP2)) {
settings.dnsIP1 = settings.dnsIP2;
settings.dnsIP2 = INADDR_NONE;
}
// Turning off static ip config if we don't meet the minimum requirements
// of ipAddress, gateway and subnet. This may change to static ip only
// as sensible defaults can be assumed for gateway and subnet
if (settings.staticIPConfig && (IPUtils::isNotSet(settings.localIP) || IPUtils::isNotSet(settings.gatewayIP) || IPUtils::isNotSet(settings.subnetMask))) {
settings.staticIPConfig = false;
}
// see if we need to inform the user of a restart
if (tx_power != settings.tx_power || enableCORS != settings.enableCORS || CORSOrigin != settings.CORSOrigin
|| (ssid != settings.ssid && settings.ssid.isEmpty())) {
return StateUpdateResult::CHANGED_RESTART; // tell WebUI that a restart is needed
}
return StateUpdateResult::CHANGED;
} }

View File

@@ -1,19 +1,22 @@
#ifndef NetworkSettingsService_h #ifndef NetworkSettingsService_h
#define NetworkSettingsService_h #define NetworkSettingsService_h
#include <StatefulService.h> #include "StatefulService.h"
#include <FSPersistence.h> #include "FSPersistence.h"
#include <HttpEndpoint.h> #include "HttpEndpoint.h"
#include <JsonUtils.h> #include "JsonUtils.h"
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
#include <esp_wifi.h> #include <esp_wifi.h>
#include <ETH.h> #include <ETH.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPmDNS.h>
#endif #endif
#define NETWORK_SETTINGS_FILE "/config/networkSettings.json" #define NETWORK_SETTINGS_FILE "/config/networkSettings.json"
#define NETWORK_SETTINGS_SERVICE_PATH "/rest/networkSettings" #define NETWORK_SETTINGS_SERVICE_PATH "/rest/networkSettings"
#define WIFI_RECONNECTION_DELAY 1000 * 3 #define WIFI_RECONNECTION_DELAY (1000 * 3)
#ifndef FACTORY_WIFI_SSID #ifndef FACTORY_WIFI_SSID
#define FACTORY_WIFI_SSID "" #define FACTORY_WIFI_SSID ""
@@ -27,6 +30,37 @@
#define FACTORY_WIFI_HOSTNAME "" #define FACTORY_WIFI_HOSTNAME ""
#endif #endif
// copied from Tasmota
#if CONFIG_IDF_TARGET_ESP32S2
#define MAX_TX_PWR_DBM_11b 195
#define MAX_TX_PWR_DBM_54g 150
#define MAX_TX_PWR_DBM_n 130
#define WIFI_SENSITIVITY_11b -880
#define WIFI_SENSITIVITY_54g -750
#define WIFI_SENSITIVITY_n -720
#elif CONFIG_IDF_TARGET_ESP32S3
#define MAX_TX_PWR_DBM_11b 210
#define MAX_TX_PWR_DBM_54g 190
#define MAX_TX_PWR_DBM_n 185
#define WIFI_SENSITIVITY_11b -880
#define WIFI_SENSITIVITY_54g -760
#define WIFI_SENSITIVITY_n -720
#elif CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3
#define MAX_TX_PWR_DBM_11b 210
#define MAX_TX_PWR_DBM_54g 190
#define MAX_TX_PWR_DBM_n 185
#define WIFI_SENSITIVITY_11b -880
#define WIFI_SENSITIVITY_54g -760
#define WIFI_SENSITIVITY_n -730
#else
#define MAX_TX_PWR_DBM_11b 195
#define MAX_TX_PWR_DBM_54g 160
#define MAX_TX_PWR_DBM_n 140
#define WIFI_SENSITIVITY_11b -880
#define WIFI_SENSITIVITY_54g -750
#define WIFI_SENSITIVITY_n -700
#endif
class NetworkSettings { class NetworkSettings {
public: public:
// core wifi configuration // core wifi configuration
@@ -37,7 +71,7 @@ class NetworkSettings {
bool staticIPConfig; bool staticIPConfig;
bool enableIPv6; bool enableIPv6;
bool bandwidth20; bool bandwidth20;
int8_t tx_power; uint8_t tx_power;
bool nosleep; bool nosleep;
bool enableMDNS; bool enableMDNS;
bool enableCORS; bool enableCORS;
@@ -50,73 +84,11 @@ class NetworkSettings {
IPAddress dnsIP1; IPAddress dnsIP1;
IPAddress dnsIP2; IPAddress dnsIP2;
static void read(NetworkSettings & settings, JsonObject root) { static void read(NetworkSettings & settings, JsonObject root);
// connection settings static StateUpdateResult update(JsonObject root, NetworkSettings & settings);
root["ssid"] = settings.ssid;
root["bssid"] = settings.bssid;
root["password"] = settings.password;
root["hostname"] = settings.hostname;
root["static_ip_config"] = settings.staticIPConfig;
root["enableIPv6"] = settings.enableIPv6;
root["bandwidth20"] = settings.bandwidth20;
root["tx_power"] = settings.tx_power;
root["nosleep"] = settings.nosleep;
root["enableMDNS"] = settings.enableMDNS;
root["enableCORS"] = settings.enableCORS;
root["CORSOrigin"] = settings.CORSOrigin;
// extended settings
JsonUtils::writeIP(root, "local_ip", settings.localIP);
JsonUtils::writeIP(root, "gateway_ip", settings.gatewayIP);
JsonUtils::writeIP(root, "subnet_mask", settings.subnetMask);
JsonUtils::writeIP(root, "dns_ip_1", settings.dnsIP1);
JsonUtils::writeIP(root, "dns_ip_2", settings.dnsIP2);
}
static StateUpdateResult update(JsonObject root, NetworkSettings & settings) {
auto enableCORS = settings.enableCORS;
auto CORSOrigin = settings.CORSOrigin;
auto ssid = settings.ssid;
settings.ssid = root["ssid"] | FACTORY_WIFI_SSID;
settings.bssid = root["bssid"] | "";
settings.password = root["password"] | FACTORY_WIFI_PASSWORD;
settings.hostname = root["hostname"] | FACTORY_WIFI_HOSTNAME;
settings.staticIPConfig = root["static_ip_config"] | false;
settings.enableIPv6 = root["enableIPv6"] | false;
settings.bandwidth20 = root["bandwidth20"] | false;
settings.tx_power = root["tx_power"] | 20;
settings.nosleep = root["nosleep"] | false;
settings.enableMDNS = root["enableMDNS"] | true;
settings.enableCORS = root["enableCORS"] | false;
settings.CORSOrigin = root["CORSOrigin"] | "*";
// extended settings
JsonUtils::readIP(root, "local_ip", settings.localIP);
JsonUtils::readIP(root, "gateway_ip", settings.gatewayIP);
JsonUtils::readIP(root, "subnet_mask", settings.subnetMask);
JsonUtils::readIP(root, "dns_ip_1", settings.dnsIP1);
JsonUtils::readIP(root, "dns_ip_2", settings.dnsIP2);
// Swap around the dns servers if 2 is populated but 1 is not
if (IPUtils::isNotSet(settings.dnsIP1) && IPUtils::isSet(settings.dnsIP2)) {
settings.dnsIP1 = settings.dnsIP2;
settings.dnsIP2 = INADDR_NONE;
}
// Turning off static ip config if we don't meet the minimum requirements
// of ipAddress, gateway and subnet. This may change to static ip only
// as sensible defaults can be assumed for gateway and subnet
if (settings.staticIPConfig && (IPUtils::isNotSet(settings.localIP) || IPUtils::isNotSet(settings.gatewayIP) || IPUtils::isNotSet(settings.subnetMask))) {
settings.staticIPConfig = false;
}
if (enableCORS != settings.enableCORS || CORSOrigin != settings.CORSOrigin || (ssid != settings.ssid && settings.ssid == "")) {
return StateUpdateResult::CHANGED_RESTART; // tell WebUI that a restart is needed
}
return StateUpdateResult::CHANGED;
}
}; };
class NetworkSettingsService : public StatefulService<NetworkSettings> { class NetworkSettingsService : public StatefulService<NetworkSettings> {
public: public:
NetworkSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); NetworkSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
@@ -127,13 +99,16 @@ class NetworkSettingsService : public StatefulService<NetworkSettings> {
private: private:
HttpEndpoint<NetworkSettings> _httpEndpoint; HttpEndpoint<NetworkSettings> _httpEndpoint;
FSPersistence<NetworkSettings> _fsPersistence; FSPersistence<NetworkSettings> _fsPersistence;
unsigned long _lastConnectionAttempt; unsigned long _lastConnectionAttempt;
bool _stopping; bool _stopping;
void WiFiEvent(WiFiEvent_t event);
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
void mDNS_start() const;
const char * disconnectReason(uint8_t code);
void reconfigureWiFiConnection(); void reconfigureWiFiConnection();
void manageSTA(); void manageSTA();
void setWiFiPowerOnRSSI();
}; };
#endif #endif

View File

@@ -1,17 +1,15 @@
#include <NetworkStatus.h> #include "NetworkStatus.h"
#include "../../src/emsesp_stub.hpp" #include "../../src/emsesp_stub.hpp"
using namespace std::placeholders; // for `_1` etc
NetworkStatus::NetworkStatus(AsyncWebServer * server, SecurityManager * securityManager) { NetworkStatus::NetworkStatus(AsyncWebServer * server, SecurityManager * securityManager) {
server->on(NETWORK_STATUS_SERVICE_PATH, server->on(NETWORK_STATUS_SERVICE_PATH,
HTTP_GET, HTTP_GET,
securityManager->wrapRequest(std::bind(&NetworkStatus::networkStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); securityManager->wrapRequest([this](AsyncWebServerRequest * request) { networkStatus(request); }, AuthenticationPredicates::IS_AUTHENTICATED));
} }
void NetworkStatus::networkStatus(AsyncWebServerRequest * request) { void NetworkStatus::networkStatus(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false); auto * response = new AsyncJsonResponse(false);
JsonObject root = response->getRoot(); JsonObject root = response->getRoot();
bool ethernet_connected = emsesp::EMSESP::system_.ethernet_connected(); bool ethernet_connected = emsesp::EMSESP::system_.ethernet_connected();
@@ -22,7 +20,7 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) {
root["status"] = 10; // custom code #10 - ETHERNET_STATUS_CONNECTED root["status"] = 10; // custom code #10 - ETHERNET_STATUS_CONNECTED
root["hostname"] = ETH.getHostname(); root["hostname"] = ETH.getHostname();
} else { } else {
root["status"] = (uint8_t)wifi_status; root["status"] = static_cast<uint8_t>(wifi_status);
root["hostname"] = WiFi.getHostname(); root["hostname"] = WiFi.getHostname();
} }

View File

@@ -1,16 +1,12 @@
#ifndef NetworkStatus_h #ifndef NetworkStatus_h
#define NetworkStatus_h #define NetworkStatus_h
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ETH.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <IPAddress.h> #include <IPAddress.h>
#include <IPUtils.h>
#include <SecurityManager.h> #include "IPUtils.h"
#include "SecurityManager.h"
#define MAX_NETWORK_STATUS_SIZE 1024 #define MAX_NETWORK_STATUS_SIZE 1024
#define NETWORK_STATUS_SERVICE_PATH "/rest/networkStatus" #define NETWORK_STATUS_SERVICE_PATH "/rest/networkStatus"

View File

@@ -1,15 +1,13 @@
#include <OTASettingsService.h> #include "OTASettingsService.h"
#include "../../src/emsesp_stub.hpp" #include "../../src/emsesp_stub.hpp"
using namespace std::placeholders; // for `_1` etc
OTASettingsService::OTASettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) OTASettingsService::OTASettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(OTASettings::read, OTASettings::update, this, server, OTA_SETTINGS_SERVICE_PATH, securityManager) : _httpEndpoint(OTASettings::read, OTASettings::update, this, server, OTA_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(OTASettings::read, OTASettings::update, this, fs, OTA_SETTINGS_FILE) , _fsPersistence(OTASettings::read, OTASettings::update, this, fs, OTA_SETTINGS_FILE)
, _arduinoOTA(nullptr) { , _arduinoOTA(nullptr) {
WiFi.onEvent(std::bind(&OTASettingsService::WiFiEvent, this, _1, _2)); WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event); });
addUpdateHandler([&](const String & originId) { configureArduinoOTA(); }, false); addUpdateHandler([this] { configureArduinoOTA(); }, false);
} }
void OTASettingsService::begin() { void OTASettingsService::begin() {
@@ -32,11 +30,11 @@ void OTASettingsService::configureArduinoOTA() {
if (_state.enabled) { if (_state.enabled) {
_arduinoOTA = new ArduinoOTAClass; _arduinoOTA = new ArduinoOTAClass;
_arduinoOTA->setPort(_state.port); _arduinoOTA->setPort(static_cast<uint16_t>(_state.port));
_arduinoOTA->setPassword(_state.password.c_str()); _arduinoOTA->setPassword(_state.password.c_str());
_arduinoOTA->onStart([]() { emsesp::EMSESP::system_.upload_status(true); }); _arduinoOTA->onStart([] { emsesp::EMSESP::system_.upload_status(true); });
_arduinoOTA->onEnd([]() { emsesp::EMSESP::system_.upload_status(false); }); _arduinoOTA->onEnd([] { emsesp::EMSESP::system_.upload_status(false); });
_arduinoOTA->onProgress([](unsigned int progress, unsigned int total) { _arduinoOTA->onProgress([](unsigned int progress, unsigned int total) {
// Serial.printf("Progress: %u%%\r\n", (progress / (total / 100))); // Serial.printf("Progress: %u%%\r\n", (progress / (total / 100)));
@@ -64,7 +62,7 @@ void OTASettingsService::configureArduinoOTA() {
} }
} }
void OTASettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) { void OTASettingsService::WiFiEvent(WiFiEvent_t event) {
switch (event) { switch (event) {
case ARDUINO_EVENT_WIFI_STA_GOT_IP: case ARDUINO_EVENT_WIFI_STA_GOT_IP:
case ARDUINO_EVENT_ETH_GOT_IP: case ARDUINO_EVENT_ETH_GOT_IP:
@@ -74,3 +72,16 @@ void OTASettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
break; break;
} }
} }
void OTASettings::read(OTASettings & settings, JsonObject root) {
root["enabled"] = settings.enabled;
root["port"] = settings.port;
root["password"] = settings.password;
}
StateUpdateResult OTASettings::update(JsonObject root, OTASettings & settings) {
settings.enabled = root["enabled"] | FACTORY_OTA_ENABLED;
settings.port = root["port"] | FACTORY_OTA_PORT;
settings.password = root["password"] | FACTORY_OTA_PASSWORD;
return StateUpdateResult::CHANGED;
}

View File

@@ -1,8 +1,8 @@
#ifndef OTASettingsService_h #ifndef OTASettingsService_h
#define OTASettingsService_h #define OTASettingsService_h
#include <HttpEndpoint.h> #include "HttpEndpoint.h"
#include <FSPersistence.h> #include "FSPersistence.h"
#include <ArduinoOTA.h> #include <ArduinoOTA.h>
#include <WiFiUdp.h> #include <WiFiUdp.h>
@@ -28,18 +28,8 @@ class OTASettings {
int port; int port;
String password; String password;
static void read(OTASettings & settings, JsonObject root) { static void read(OTASettings & settings, JsonObject root);
root["enabled"] = settings.enabled; static StateUpdateResult update(JsonObject root, OTASettings & settings);
root["port"] = settings.port;
root["password"] = settings.password;
}
static StateUpdateResult update(JsonObject root, OTASettings & settings) {
settings.enabled = root["enabled"] | FACTORY_OTA_ENABLED;
settings.port = root["port"] | FACTORY_OTA_PORT;
settings.password = root["password"] | FACTORY_OTA_PASSWORD;
return StateUpdateResult::CHANGED;
}
}; };
class OTASettingsService : public StatefulService<OTASettings> { class OTASettingsService : public StatefulService<OTASettings> {
@@ -55,7 +45,7 @@ class OTASettingsService : public StatefulService<OTASettings> {
ArduinoOTAClass * _arduinoOTA; ArduinoOTAClass * _arduinoOTA;
void configureArduinoOTA(); void configureArduinoOTA();
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info); void WiFiEvent(WiFiEvent_t event);
}; };
#endif #endif

View File

@@ -1,15 +1,22 @@
#include <RestartService.h> #include "RestartService.h"
#include <esp_ota_ops.h> #include <esp_ota_ops.h>
#include "../../src/emsesp_stub.hpp" #include "../../src/emsesp_stub.hpp"
using namespace std::placeholders; // for `_1` etc
RestartService::RestartService(AsyncWebServer * server, SecurityManager * securityManager) { RestartService::RestartService(AsyncWebServer * server, SecurityManager * securityManager) {
server->on(RESTART_SERVICE_PATH, HTTP_POST, securityManager->wrapRequest(std::bind(&RestartService::restart, this, _1), AuthenticationPredicates::IS_ADMIN)); server->on(RESTART_SERVICE_PATH,
HTTP_POST,
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { restart(request); }, AuthenticationPredicates::IS_ADMIN));
server->on(PARTITION_SERVICE_PATH, server->on(PARTITION_SERVICE_PATH,
HTTP_POST, HTTP_POST,
securityManager->wrapRequest(std::bind(&RestartService::partition, this, _1), AuthenticationPredicates::IS_ADMIN)); securityManager->wrapRequest([this](AsyncWebServerRequest * request) { partition(request); }, AuthenticationPredicates::IS_ADMIN));
}
void RestartService::restartNow() {
WiFi.disconnect(true);
delay(500);
ESP.restart();
} }
void RestartService::restart(AsyncWebServerRequest * request) { void RestartService::restart(AsyncWebServerRequest * request) {
@@ -19,7 +26,7 @@ void RestartService::restart(AsyncWebServerRequest * request) {
} }
void RestartService::partition(AsyncWebServerRequest * request) { void RestartService::partition(AsyncWebServerRequest * request) {
const esp_partition_t * factory_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL); const esp_partition_t * factory_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, nullptr);
if (factory_partition) { if (factory_partition) {
esp_ota_set_boot_partition(factory_partition); esp_ota_set_boot_partition(factory_partition);
emsesp::EMSESP::system_.store_nvs_values(); emsesp::EMSESP::system_.store_nvs_values();
@@ -27,7 +34,7 @@ void RestartService::partition(AsyncWebServerRequest * request) {
request->send(200); request->send(200);
return; return;
} }
const esp_partition_t * ota_partition = esp_ota_get_next_update_partition(NULL); const esp_partition_t * ota_partition = esp_ota_get_next_update_partition(nullptr);
if (!ota_partition) { if (!ota_partition) {
request->send(400); // bad request request->send(400); // bad request
return; return;

View File

@@ -3,9 +3,9 @@
#include <WiFi.h> #include <WiFi.h>
#include <AsyncTCP.h> #include <AsyncTCP.h>
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#include "SecurityManager.h"
#define RESTART_SERVICE_PATH "/rest/restart" #define RESTART_SERVICE_PATH "/rest/restart"
#define PARTITION_SERVICE_PATH "/rest/partition" #define PARTITION_SERVICE_PATH "/rest/partition"
@@ -14,11 +14,7 @@ class RestartService {
public: public:
RestartService(AsyncWebServer * server, SecurityManager * securityManager); RestartService(AsyncWebServer * server, SecurityManager * securityManager);
static void restartNow() { static void restartNow();
WiFi.disconnect(true);
delay(500);
ESP.restart();
}
private: private:
void restart(AsyncWebServerRequest * request); void restart(AsyncWebServerRequest * request);

View File

@@ -1,17 +1,13 @@
#ifndef SecurityManager_h #ifndef SecurityManager_h
#define SecurityManager_h #define SecurityManager_h
#include <Features.h> #include "Features.h"
#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>
#ifndef FACTORY_JWT_SECRET
#define FACTORY_JWT_SECRET ESPUtils::defaultDeviceValue()
#endif
#define ACCESS_TOKEN_PARAMATER "access_token" #define ACCESS_TOKEN_PARAMATER "access_token"
#define AUTHORIZATION_HEADER "Authorization" #define AUTHORIZATION_HEADER "Authorization"
@@ -26,28 +22,27 @@ class User {
public: public:
User(String username, String password, bool admin) User(String username, String password, bool admin)
: username(username) : username(std::move(username))
, password(password) , password(std::move(password))
, admin(admin) { , admin(admin) {
} }
}; };
class Authentication { class Authentication {
public: public:
User * user; User * user = nullptr;
boolean authenticated; boolean authenticated = false;
public: public:
Authentication(User & user) explicit Authentication(const User & user)
: user(new User(user)) : user(new User(user))
, authenticated(true) { , authenticated(true) {
} }
Authentication()
: user(nullptr) Authentication() = default;
, authenticated(false) {
}
~Authentication() { ~Authentication() {
delete (user); delete user;
} }
}; };
@@ -55,13 +50,14 @@ typedef std::function<boolean(Authentication & authentication)> AuthenticationPr
class AuthenticationPredicates { class AuthenticationPredicates {
public: public:
static bool NONE_REQUIRED(Authentication & authentication) { static bool NONE_REQUIRED(const Authentication & authentication) {
(void)authentication;
return true; return true;
}; };
static bool IS_AUTHENTICATED(Authentication & authentication) { static bool IS_AUTHENTICATED(const Authentication & authentication) {
return authentication.authenticated; return authentication.authenticated;
}; };
static bool IS_ADMIN(Authentication & authentication) { static bool IS_ADMIN(const Authentication & authentication) {
return authentication.authenticated && authentication.user->admin; return authentication.authenticated && authentication.user->admin;
}; };
}; };
@@ -76,7 +72,7 @@ class SecurityManager {
/* /*
* Generate a JWT for the user provided * Generate a JWT for the user provided
*/ */
virtual String generateJWT(User * user) = 0; virtual String generateJWT(const User * user) = 0;
/* /*
* Check the request header for the Authorization token * Check the request header for the Authorization token

View File

@@ -1,15 +1,13 @@
#include <SecuritySettingsService.h> #include "SecuritySettingsService.h"
#include "../../src/emsesp_stub.hpp"
SecuritySettingsService::SecuritySettingsService(AsyncWebServer * server, FS * fs) SecuritySettingsService::SecuritySettingsService(AsyncWebServer * server, FS * fs)
: _httpEndpoint(SecuritySettings::read, SecuritySettings::update, this, server, SECURITY_SETTINGS_PATH, this) : _httpEndpoint(SecuritySettings::read, SecuritySettings::update, this, server, SECURITY_SETTINGS_PATH, this)
, _fsPersistence(SecuritySettings::read, SecuritySettings::update, this, fs, SECURITY_SETTINGS_FILE) , _fsPersistence(SecuritySettings::read, SecuritySettings::update, this, fs, SECURITY_SETTINGS_FILE)
, _jwtHandler(FACTORY_JWT_SECRET) { , _jwtHandler(FACTORY_JWT_SECRET) {
addUpdateHandler([&](const String & originId) { configureJWTHandler(); }, false); addUpdateHandler([this] { configureJWTHandler(); }, false);
server->on(GENERATE_TOKEN_PATH, server->on(GENERATE_TOKEN_PATH,
HTTP_GET, HTTP_GET,
wrapRequest(std::bind(&SecuritySettingsService::generateToken, this, std::placeholders::_1), AuthenticationPredicates::IS_ADMIN)); SecuritySettingsService::wrapRequest([this](AsyncWebServerRequest * request) { generateToken(request); }, AuthenticationPredicates::IS_ADMIN));
} }
void SecuritySettingsService::begin() { void SecuritySettingsService::begin() {
@@ -30,7 +28,7 @@ Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerReques
String value = tokenParamater->value(); String value = tokenParamater->value();
return authenticateJWT(value); return authenticateJWT(value);
} }
return Authentication(); return {};
} }
void SecuritySettingsService::configureJWTHandler() { void SecuritySettingsService::configureJWTHandler() {
@@ -43,37 +41,37 @@ Authentication SecuritySettingsService::authenticateJWT(String & jwt) {
if (payloadDocument.is<JsonObject>()) { if (payloadDocument.is<JsonObject>()) {
JsonObject parsedPayload = payloadDocument.as<JsonObject>(); JsonObject parsedPayload = payloadDocument.as<JsonObject>();
String username = parsedPayload["username"]; String username = parsedPayload["username"];
for (User _user : _state.users) { for (const User & _user : _state.users) {
if (_user.username == username && validatePayload(parsedPayload, &_user)) { if (_user.username == username && validatePayload(parsedPayload, &_user)) {
return Authentication(_user); return Authentication(_user);
} }
} }
} }
return Authentication(); return {};
} }
Authentication SecuritySettingsService::authenticate(const String & username, const String & password) { Authentication SecuritySettingsService::authenticate(const String & username, const String & password) {
for (User _user : _state.users) { for (const User & _user : _state.users) {
if (_user.username == username && _user.password == password) { if (_user.username == username && _user.password == password) {
return Authentication(_user); return Authentication(_user);
} }
} }
return Authentication(); return {};
} }
inline void populateJWTPayload(JsonObject payload, User * user) { inline void populateJWTPayload(JsonObject payload, const User * user) {
payload["username"] = user->username; payload["username"] = user->username;
payload["admin"] = user->admin; payload["admin"] = user->admin;
} }
boolean SecuritySettingsService::validatePayload(JsonObject parsedPayload, User * user) { boolean SecuritySettingsService::validatePayload(JsonObject parsedPayload, const User * user) {
JsonDocument jsonDocument; JsonDocument jsonDocument;
JsonObject payload = jsonDocument.to<JsonObject>(); JsonObject payload = jsonDocument.to<JsonObject>();
populateJWTPayload(payload, user); populateJWTPayload(payload, user);
return payload == parsedPayload; return payload == parsedPayload;
} }
String SecuritySettingsService::generateJWT(User * user) { String SecuritySettingsService::generateJWT(const User * user) {
JsonDocument jsonDocument; JsonDocument jsonDocument;
JsonObject payload = jsonDocument.to<JsonObject>(); JsonObject payload = jsonDocument.to<JsonObject>();
populateJWTPayload(payload, user); populateJWTPayload(payload, user);
@@ -111,9 +109,9 @@ ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequest
void SecuritySettingsService::generateToken(AsyncWebServerRequest * request) { void SecuritySettingsService::generateToken(AsyncWebServerRequest * request) {
AsyncWebParameter * usernameParam = request->getParam("username"); AsyncWebParameter * usernameParam = request->getParam("username");
for (User _user : _state.users) { for (const User & _user : _state.users) {
if (_user.username == usernameParam->value()) { if (_user.username == usernameParam->value()) {
AsyncJsonResponse * response = new AsyncJsonResponse(false); auto * response = new AsyncJsonResponse(false);
JsonObject root = response->getRoot(); JsonObject root = response->getRoot();
root["token"] = generateJWT(&_user); root["token"] = generateJWT(&_user);
response->setLength(); response->setLength();

View File

@@ -1,10 +1,10 @@
#ifndef SecuritySettingsService_h #ifndef SecuritySettingsService_h
#define SecuritySettingsService_h #define SecuritySettingsService_h
#include <Features.h> #include "Features.h"
#include <SecurityManager.h> #include "SecurityManager.h"
#include <HttpEndpoint.h> #include "HttpEndpoint.h"
#include <FSPersistence.h> #include "FSPersistence.h"
#ifndef FACTORY_ADMIN_USERNAME #ifndef FACTORY_ADMIN_USERNAME
#define FACTORY_ADMIN_USERNAME "admin" #define FACTORY_ADMIN_USERNAME "admin"
@@ -32,7 +32,6 @@ class SecuritySettings {
public: public:
String jwtSecret; String jwtSecret;
std::vector<User> users; std::vector<User> users;
// std::list<User> users;
static void read(SecuritySettings & settings, JsonObject root) { static void read(SecuritySettings & settings, JsonObject root) {
// secret // secret
@@ -40,7 +39,7 @@ class SecuritySettings {
// users // users
JsonArray users = root["users"].to<JsonArray>(); JsonArray users = root["users"].to<JsonArray>();
for (User user : settings.users) { for (const User & user : settings.users) {
JsonObject userRoot = users.add<JsonObject>(); JsonObject userRoot = users.add<JsonObject>();
userRoot["username"] = user.username; userRoot["username"] = user.username;
userRoot["password"] = user.password; userRoot["password"] = user.password;
@@ -56,29 +55,29 @@ class SecuritySettings {
settings.users.clear(); settings.users.clear();
if (root["users"].is<JsonArray>()) { if (root["users"].is<JsonArray>()) {
for (JsonVariant user : root["users"].as<JsonArray>()) { for (JsonVariant user : root["users"].as<JsonArray>()) {
settings.users.push_back(User(user["username"], user["password"], user["admin"])); settings.users.emplace_back(user["username"], user["password"], user["admin"]);
} }
} else { } else {
settings.users.push_back(User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true)); settings.users.emplace_back(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true);
settings.users.push_back(User(FACTORY_GUEST_USERNAME, FACTORY_GUEST_PASSWORD, false)); settings.users.emplace_back(FACTORY_GUEST_USERNAME, FACTORY_GUEST_PASSWORD, false);
} }
return StateUpdateResult::CHANGED; return StateUpdateResult::CHANGED;
} }
}; };
class SecuritySettingsService : public StatefulService<SecuritySettings>, public SecurityManager { class SecuritySettingsService final : public StatefulService<SecuritySettings>, public SecurityManager {
public: public:
SecuritySettingsService(AsyncWebServer * server, FS * fs); SecuritySettingsService(AsyncWebServer * server, FS * fs);
void begin(); void begin();
// Functions to implement SecurityManager // Functions to implement SecurityManager
Authentication authenticate(const String & username, const String & password); Authentication authenticate(const String & username, const String & password) override;
Authentication authenticateRequest(AsyncWebServerRequest * request); Authentication authenticateRequest(AsyncWebServerRequest * request) override;
String generateJWT(User * user); String generateJWT(const User * user) override;
ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate); ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate) override;
ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate); ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) override;
ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction callback, AuthenticationPredicate predicate); ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction callback, AuthenticationPredicate predicate) override;
private: private:
HttpEndpoint<SecuritySettings> _httpEndpoint; HttpEndpoint<SecuritySettings> _httpEndpoint;
@@ -97,7 +96,7 @@ class SecuritySettingsService : public StatefulService<SecuritySettings>, public
/* /*
* Verify the payload is correct * Verify the payload is correct
*/ */
boolean validatePayload(JsonObject parsedPayload, User * user); boolean validatePayload(JsonObject parsedPayload, const User * user);
}; };
#endif #endif

View File

@@ -1,3 +1,3 @@
#include <StatefulService.h> #include "StatefulService.h"
update_handler_id_t StateUpdateHandlerInfo::currentUpdatedHandlerId = 0; update_handler_id_t StateUpdateHandlerInfo::currentUpdatedHandlerId = 0;

View File

@@ -24,7 +24,7 @@ template <typename T>
using JsonStateReader = std::function<void(T & settings, JsonObject root)>; using JsonStateReader = std::function<void(T & settings, JsonObject root)>;
typedef size_t update_handler_id_t; typedef size_t update_handler_id_t;
typedef std::function<void(const String & originId)> StateUpdateCallback; typedef std::function<void()> StateUpdateCallback;
typedef struct StateUpdateHandlerInfo { typedef struct StateUpdateHandlerInfo {
static update_handler_id_t currentUpdatedHandlerId; static update_handler_id_t currentUpdatedHandlerId;
@@ -33,7 +33,7 @@ typedef struct StateUpdateHandlerInfo {
bool _allowRemove; bool _allowRemove;
StateUpdateHandlerInfo(StateUpdateCallback cb, bool allowRemove) StateUpdateHandlerInfo(StateUpdateCallback cb, bool allowRemove)
: _id(++currentUpdatedHandlerId) : _id(++currentUpdatedHandlerId)
, _cb(cb) , _cb(std::move(cb))
, _allowRemove(allowRemove){}; , _allowRemove(allowRemove){};
} StateUpdateHandlerInfo_t; } StateUpdateHandlerInfo_t;
@@ -41,7 +41,7 @@ template <class T>
class StatefulService { class StatefulService {
public: public:
template <typename... Args> template <typename... Args>
StatefulService(Args &&... args) explicit StatefulService(Args &&... args)
: _state(std::forward<Args>(args)...) : _state(std::forward<Args>(args)...)
, _accessMutex(xSemaphoreCreateRecursiveMutex()) { , _accessMutex(xSemaphoreCreateRecursiveMutex()) {
} }
@@ -50,27 +50,28 @@ class StatefulService {
if (!cb) { if (!cb) {
return 0; return 0;
} }
StateUpdateHandlerInfo_t updateHandler(cb, allowRemove); StateUpdateHandlerInfo_t updateHandler(std::move(cb), allowRemove);
_updateHandlers.push_back(updateHandler); _updateHandlers.push_back(std::move(updateHandler));
return updateHandler._id; return updateHandler._id;
} }
void removeUpdateHandler(update_handler_id_t id) { void removeUpdateHandler(update_handler_id_t id) {
for (auto i = _updateHandlers.begin(); i != _updateHandlers.end();) { for (auto it = _updateHandlers.begin(); it != _updateHandlers.end();) {
if ((*i)._allowRemove && (*i)._id == id) { auto & elem = *it;
i = _updateHandlers.erase(i); if (elem._allowRemove && elem._id == id) {
it = _updateHandlers.erase(it);
} else { } else {
++i; ++it;
} }
} }
} }
StateUpdateResult update(std::function<StateUpdateResult(T &)> stateUpdater, const String & originId) { StateUpdateResult update(std::function<StateUpdateResult(T &)> stateUpdater) {
beginTransaction(); beginTransaction();
StateUpdateResult result = stateUpdater(_state); StateUpdateResult result = stateUpdater(_state);
endTransaction(); endTransaction();
if (result == StateUpdateResult::CHANGED) { if (result == StateUpdateResult::CHANGED) {
callUpdateHandlers(originId); callUpdateHandlers();
} }
return result; return result;
} }
@@ -82,12 +83,12 @@ class StatefulService {
return result; return result;
} }
StateUpdateResult update(JsonObject jsonObject, JsonStateUpdater<T> stateUpdater, const String & originId) { StateUpdateResult update(JsonObject jsonObject, JsonStateUpdater<T> stateUpdater) {
beginTransaction(); beginTransaction();
StateUpdateResult result = stateUpdater(jsonObject, _state); StateUpdateResult result = stateUpdater(jsonObject, _state);
endTransaction(); endTransaction();
if (result == StateUpdateResult::CHANGED) { if (result == StateUpdateResult::CHANGED) {
callUpdateHandlers(originId); callUpdateHandlers();
} }
return result; return result;
} }
@@ -111,9 +112,9 @@ class StatefulService {
endTransaction(); endTransaction();
} }
void callUpdateHandlers(const String & originId) { void callUpdateHandlers() {
for (const StateUpdateHandlerInfo_t & updateHandler : _updateHandlers) { for (const StateUpdateHandlerInfo_t & updateHandler : _updateHandlers) {
updateHandler._cb(originId); updateHandler._cb();
} }
} }
@@ -131,7 +132,6 @@ class StatefulService {
private: private:
SemaphoreHandle_t _accessMutex; SemaphoreHandle_t _accessMutex;
std::vector<StateUpdateHandlerInfo_t> _updateHandlers; std::vector<StateUpdateHandlerInfo_t> _updateHandlers;
// std::list<StateUpdateHandlerInfo_t> _updateHandlers;
}; };
#endif #endif

View File

@@ -1,23 +1,30 @@
#include <SystemStatus.h> #include "SystemStatus.h"
#include <esp_ota_ops.h> #include <esp_ota_ops.h>
#include "../../src/emsesp_stub.hpp" #include "../../src/emsesp_stub.hpp"
using namespace std::placeholders; // for `_1` etc
SystemStatus::SystemStatus(AsyncWebServer * server, SecurityManager * securityManager) { SystemStatus::SystemStatus(AsyncWebServer * server, SecurityManager * securityManager) {
server->on(SYSTEM_STATUS_SERVICE_PATH, server->on(SYSTEM_STATUS_SERVICE_PATH,
HTTP_GET, HTTP_GET,
securityManager->wrapRequest(std::bind(&SystemStatus::systemStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); securityManager->wrapRequest([this](AsyncWebServerRequest * request) { systemStatus(request); }, AuthenticationPredicates::IS_AUTHENTICATED));
} }
void SystemStatus::systemStatus(AsyncWebServerRequest * request) { void SystemStatus::systemStatus(AsyncWebServerRequest * request) {
emsesp::EMSESP::system_.refreshHeapMem(); // refresh free heap and max alloc heap emsesp::EMSESP::system_.refreshHeapMem(); // refresh free heap and max alloc heap
AsyncJsonResponse * response = new AsyncJsonResponse(false); auto * response = new AsyncJsonResponse(false);
JsonObject root = response->getRoot(); JsonObject root = response->getRoot();
#ifdef EMSESP_DEBUG
root["emsesp_version"] = std::string(EMSESP_APP_VERSION) + " (DEBUG)";
#else
#ifdef EMSESP_TEST
root["emsesp_version"] = std::string(EMSESP_APP_VERSION) + " (TEST)";
#else
root["emsesp_version"] = EMSESP_APP_VERSION; root["emsesp_version"] = EMSESP_APP_VERSION;
#endif
#endif
root["esp_platform"] = EMSESP_PLATFORM; root["esp_platform"] = EMSESP_PLATFORM;
root["cpu_type"] = ESP.getChipModel(); root["cpu_type"] = ESP.getChipModel();
root["cpu_rev"] = ESP.getChipRevision(); root["cpu_rev"] = ESP.getChipRevision();
@@ -41,11 +48,11 @@ void SystemStatus::systemStatus(AsyncWebServerRequest * request) {
root["psram_size"] = emsesp::EMSESP::system_.PSram(); root["psram_size"] = emsesp::EMSESP::system_.PSram();
root["free_psram"] = ESP.getFreePsram() / 1024; root["free_psram"] = ESP.getFreePsram() / 1024;
} }
const esp_partition_t * partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL); const esp_partition_t * partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, nullptr);
if (partition != NULL) { // factory partition found if (partition != NULL) { // factory partition found
root["has_loader"] = true; root["has_loader"] = true;
} else { // check for not empty, smaller OTA partition } else { // check for not empty, smaller OTA partition
partition = esp_ota_get_next_update_partition(NULL); partition = esp_ota_get_next_update_partition(nullptr);
if (partition) { if (partition) {
uint64_t buffer; uint64_t buffer;
esp_partition_read(partition, 0, &buffer, 8); esp_partition_read(partition, 0, &buffer, 8);

View File

@@ -5,10 +5,10 @@
#include <AsyncTCP.h> #include <AsyncTCP.h>
#include <FS.h> #include <FS.h>
#include <LittleFS.h> #include <LittleFS.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#include "SecurityManager.h"
#define SYSTEM_STATUS_SERVICE_PATH "/rest/systemStatus" #define SYSTEM_STATUS_SERVICE_PATH "/rest/systemStatus"

View File

@@ -1,20 +1,27 @@
#include <UploadFileService.h> #include "UploadFileService.h"
#include <esp_ota_ops.h>
#include <esp_app_format.h>
#include "../../src/emsesp_stub.hpp" #include "../../src/emsesp_stub.hpp"
using namespace std::placeholders; // for `_1` etc #include <esp_app_format.h>
static bool is_firmware = false; static String getFilenameExtension(const String & filename) {
static char md5[33] = "\0"; const auto pos = filename.lastIndexOf('.');
if (pos != -1) {
return filename.substring(static_cast<unsigned int>(pos) + 1);
}
return {};
}
UploadFileService::UploadFileService(AsyncWebServer * server, SecurityManager * securityManager) UploadFileService::UploadFileService(AsyncWebServer * server, SecurityManager * securityManager)
: _securityManager(securityManager) { : _securityManager(securityManager)
server->on(UPLOAD_FILE_PATH, , _is_firmware(false)
, _md5() {
server->on(
UPLOAD_FILE_PATH,
HTTP_POST, HTTP_POST,
std::bind(&UploadFileService::uploadComplete, this, _1), [this](AsyncWebServerRequest * request) { uploadComplete(request); },
std::bind(&UploadFileService::handleUpload, this, _1, _2, _3, _4, _5, _6)); [this](AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) {
handleUpload(request, filename, index, data, len, final);
});
} }
void UploadFileService::handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) { void UploadFileService::handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) {
@@ -28,29 +35,27 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
// at init // at init
if (!index) { if (!index) {
// check details of the file, to see if its a valid bin or json file // check details of the file, to see if its a valid bin or json file
std::string fname(filename.c_str()); const String extension = getFilenameExtension(filename);
auto position = fname.find_last_of("."); const std::size_t filesize = request->contentLength();
std::string extension = fname.substr(position + 1);
size_t fsize = request->contentLength();
is_firmware = false; _is_firmware = false;
if ((extension == "bin") && (fsize > 1000000)) { if ((extension == "bin") && (filesize > 1000000)) {
is_firmware = true; _is_firmware = true;
} else if (extension == "json") { } else if (extension == "json") {
md5[0] = '\0'; // clear md5 _md5[0] = '\0'; // clear md5
} else if (extension == "md5") { } else if (extension == "md5") {
if (len == 32) { if (len == _md5.size() - 1) {
memcpy(md5, data, 32); std::memcpy(_md5.data(), data, _md5.size() - 1);
md5[32] = '\0'; _md5.back() = '\0';
} }
return; return;
} else { } else {
md5[0] = '\0'; _md5.front() = '\0';
handleError(request, 406); // Not Acceptable - unsupported file type handleError(request, 406); // Not Acceptable - unsupported file type
return; return;
} }
if (is_firmware) { if (_is_firmware) {
// Check firmware header, 0xE9 magic offset 0 indicates esp bin, chip offset 12: esp32:0, S2:2, C3:5 // Check firmware header, 0xE9 magic offset 0 indicates esp bin, chip offset 12: esp32:0, S2:2, C3:5
#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 #if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
if (len > 12 && (data[0] != 0xE9 || data[12] != 0)) { if (len > 12 && (data[0] != 0xE9 || data[12] != 0)) {
@@ -74,12 +79,12 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
} }
#endif #endif
// it's firmware - initialize the ArduinoOTA updater // it's firmware - initialize the ArduinoOTA updater
if (Update.begin(fsize - sizeof(esp_image_header_t))) { if (Update.begin(filesize - sizeof(esp_image_header_t))) {
if (strlen(md5) == 32) { if (strlen(_md5.data()) == _md5.size() - 1) {
Update.setMD5(md5); Update.setMD5(_md5.data());
md5[0] = '\0'; _md5.front() = '\0';
} }
request->onDisconnect(UploadFileService::handleEarlyDisconnect); // success, let's make sure we end the update if the client hangs up request->onDisconnect([this] { handleEarlyDisconnect(); }); // success, let's make sure we end the update if the client hangs up
} else { } else {
handleError(request, 507); // failed to begin, send an error response Insufficient Storage handleError(request, 507); // failed to begin, send an error response Insufficient Storage
return; return;
@@ -90,25 +95,19 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
} }
} }
if (!is_firmware) { if (!_is_firmware) {
if (len) { if (len && len != request->_tempFile.write(data, len)) { // stream the incoming chunk to the opened file
if (len != request->_tempFile.write(data, len)) { // stream the incoming chunk to the opened file
handleError(request, 507); // 507-Insufficient Storage handleError(request, 507); // 507-Insufficient Storage
} }
} } else if (!request->_tempObject) { // if we haven't delt with an error, continue with the firmware update
} else {
// if we haven't delt with an error, continue with the firmware update
if (!request->_tempObject) {
if (Update.write(data, len) != len) { if (Update.write(data, len) != len) {
handleError(request, 500); handleError(request, 500);
return;
} }
if (final) { if (final && !Update.end(true)) {
if (!Update.end(true)) {
handleError(request, 500); handleError(request, 500);
} }
} }
}
}
} }
void UploadFileService::uploadComplete(AsyncWebServerRequest * request) { void UploadFileService::uploadComplete(AsyncWebServerRequest * request) {
@@ -124,7 +123,7 @@ void UploadFileService::uploadComplete(AsyncWebServerRequest * request) {
// check if it was a firmware upgrade // check if it was a firmware upgrade
// if no error, send the success response as a JSON // if no error, send the success response as a JSON
if (is_firmware && !request->_tempObject) { if (_is_firmware && !request->_tempObject) {
emsesp::EMSESP::system_.store_nvs_values(); emsesp::EMSESP::system_.store_nvs_values();
request->onDisconnect(RestartService::restartNow); request->onDisconnect(RestartService::restartNow);
AsyncWebServerResponse * response = request->beginResponse(200); AsyncWebServerResponse * response = request->beginResponse(200);
@@ -132,10 +131,10 @@ void UploadFileService::uploadComplete(AsyncWebServerRequest * request) {
return; return;
} }
if (strlen(md5) == 32) { if (strlen(_md5.data()) == _md5.size() - 1) {
auto * response = new AsyncJsonResponse(false); auto * response = new AsyncJsonResponse(false);
JsonObject root = response->getRoot(); JsonObject root = response->getRoot();
root["md5"] = md5; root["md5"] = _md5.data();
response->setLength(); response->setLength();
request->send(response); request->send(response);
return; return;
@@ -163,6 +162,6 @@ void UploadFileService::handleError(AsyncWebServerRequest * request, int code) {
} }
void UploadFileService::handleEarlyDisconnect() { void UploadFileService::handleEarlyDisconnect() {
is_firmware = false; _is_firmware = false;
Update.abort(); Update.abort();
} }

View File

@@ -1,16 +1,16 @@
#ifndef UploadFileService_h #ifndef UploadFileService_h
#define UploadFileService_h #define UploadFileService_h
#include <Arduino.h> #include "RestartService.h"
#include "SecurityManager.h"
#include <Arduino.h>
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
#include <Update.h> #include <Update.h>
#include <WiFi.h> #include <WiFi.h>
#include <LittleFS.h> #include <array>
#include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#include <RestartService.h>
#define UPLOAD_FILE_PATH "/rest/uploadFile" #define UPLOAD_FILE_PATH "/rest/uploadFile"
#define TEMP_FILENAME_PATH "/tmp_upload" #define TEMP_FILENAME_PATH "/tmp_upload"
@@ -21,10 +21,13 @@ class UploadFileService {
private: private:
SecurityManager * _securityManager; SecurityManager * _securityManager;
bool _is_firmware;
std::array<char, 33> _md5;
void handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final); void handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final);
void uploadComplete(AsyncWebServerRequest * request); void uploadComplete(AsyncWebServerRequest * request);
void handleError(AsyncWebServerRequest * request, int code); void handleError(AsyncWebServerRequest * request, int code);
static void handleEarlyDisconnect(); void handleEarlyDisconnect();
}; };
#endif #endif

View File

@@ -1,14 +1,12 @@
#include <WiFiScanner.h> #include "WiFiScanner.h"
using namespace std::placeholders; // for `_1` etc
WiFiScanner::WiFiScanner(AsyncWebServer * server, SecurityManager * securityManager) { WiFiScanner::WiFiScanner(AsyncWebServer * server, SecurityManager * securityManager) {
server->on(SCAN_NETWORKS_SERVICE_PATH, server->on(SCAN_NETWORKS_SERVICE_PATH,
HTTP_GET, HTTP_GET,
securityManager->wrapRequest(std::bind(&WiFiScanner::scanNetworks, this, _1), AuthenticationPredicates::IS_ADMIN)); securityManager->wrapRequest([this](AsyncWebServerRequest * request) { scanNetworks(request); }, AuthenticationPredicates::IS_ADMIN));
server->on(LIST_NETWORKS_SERVICE_PATH, server->on(LIST_NETWORKS_SERVICE_PATH,
HTTP_GET, HTTP_GET,
securityManager->wrapRequest(std::bind(&WiFiScanner::listNetworks, this, _1), AuthenticationPredicates::IS_ADMIN)); securityManager->wrapRequest([this](AsyncWebServerRequest * request) { listNetworks(request); }, AuthenticationPredicates::IS_ADMIN));
}; };
void WiFiScanner::scanNetworks(AsyncWebServerRequest * request) { void WiFiScanner::scanNetworks(AsyncWebServerRequest * request) {
@@ -21,18 +19,18 @@ void WiFiScanner::scanNetworks(AsyncWebServerRequest * request) {
} }
void WiFiScanner::listNetworks(AsyncWebServerRequest * request) { void WiFiScanner::listNetworks(AsyncWebServerRequest * request) {
int numNetworks = WiFi.scanComplete(); const int numNetworks = WiFi.scanComplete();
if (numNetworks > -1) { if (numNetworks > -1) {
AsyncJsonResponse * response = new AsyncJsonResponse(false); auto * response = new AsyncJsonResponse(false);
JsonObject root = response->getRoot(); JsonObject root = response->getRoot();
JsonArray networks = root["networks"].to<JsonArray>(); JsonArray networks = root["networks"].to<JsonArray>();
for (int i = 0; i < numNetworks; i++) { for (uint8_t i = 0; i < numNetworks; i++) {
JsonObject network = networks.add<JsonObject>(); JsonObject network = networks.add<JsonObject>();
network["rssi"] = WiFi.RSSI(i); network["rssi"] = WiFi.RSSI(i);
network["ssid"] = WiFi.SSID(i); network["ssid"] = WiFi.SSID(i);
network["bssid"] = WiFi.BSSIDstr(i); network["bssid"] = WiFi.BSSIDstr(i);
network["channel"] = WiFi.channel(i); network["channel"] = WiFi.channel(i);
network["encryption_type"] = (uint8_t)WiFi.encryptionType(i); network["encryption_type"] = static_cast<uint8_t>(WiFi.encryptionType(i));
} }
response->setLength(); response->setLength();
request->send(response); request->send(response);

View File

@@ -3,10 +3,10 @@
#include <WiFi.h> #include <WiFi.h>
#include <AsyncTCP.h> #include <AsyncTCP.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#include "SecurityManager.h"
#define SCAN_NETWORKS_SERVICE_PATH "/rest/scanNetworks" #define SCAN_NETWORKS_SERVICE_PATH "/rest/scanNetworks"
#define LIST_NETWORKS_SERVICE_PATH "/rest/listNetworks" #define LIST_NETWORKS_SERVICE_PATH "/rest/listNetworks"

View File

@@ -936,7 +936,7 @@ class Shell : public std::enable_shared_from_this<Shell>, public uuid::log::Hand
* @param[in] message New log message, shared by all handlers. * @param[in] message New log message, shared by all handlers.
* @since 0.1.0 * @since 0.1.0
*/ */
virtual void operator<<(std::shared_ptr<uuid::log::Message> message); virtual void operator<<(std::shared_ptr<uuid::log::Message> message) override;
/** /**
* Get the current log level. * Get the current log level.
* *

View File

@@ -17,17 +17,16 @@
#ifdef EMSESP_STANDALONE #ifdef EMSESP_STANDALONE
#include <Arduino.h>
#include <stdio.h> #include <stdio.h>
#include <stdarg.h> #include <stdarg.h>
#include <iostream> #include <iostream>
#include <thread> #include <thread>
#include <atomic> #include <atomic>
#include <string> #include <string>
#include <Network.h> #include "Arduino.h"
#include "Network.h"
NativeConsole Serial; NativeConsole Serial;

View File

@@ -25,16 +25,14 @@
#include <cstring> #include <cstring>
#include <string> #include <string>
#include <math.h> #include <math.h>
#include <algorithm>
#include <algorithm> // for count_if
#include <Print.h> #include <Print.h>
#include <Printable.h> #include <Printable.h>
#include <Stream.h> #include <Stream.h>
#include <WString.h>
#include <iostream> #include <iostream>
#include "WString.h"
typedef double double_t; typedef double double_t;
#define ICACHE_FLASH_ATTR #define ICACHE_FLASH_ATTR
@@ -188,6 +186,4 @@ void yield(void);
void setup(void); void setup(void);
void loop(void); void loop(void);
#endif #endif

View File

@@ -1,8 +1,10 @@
#ifndef ASYNC_JSON_H_ #ifndef ASYNC_JSON_H_
#define ASYNC_JSON_H_ #define ASYNC_JSON_H_
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
#include "ESPAsyncWebServer.h"
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024 #define DYNAMIC_JSON_DOCUMENT_SIZE 1024

View File

@@ -2,6 +2,7 @@
#define ASYNCTCP_H_ #define ASYNCTCP_H_
#include "Arduino.h" #include "Arduino.h"
#include <functional> #include <functional>
class AsyncClient; class AsyncClient;

View File

@@ -1,21 +1,19 @@
#ifndef ESP8266React_h #ifndef ESP8266React_h
#define ESP8266React_h #define ESP8266React_h
#include <Arduino.h>
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <espMqttClient.h>
#include <ESPAsyncWebServer.h>
#include <list> #include <list>
#include <FS.h> #include "Arduino.h"
#include <SecurityManager.h> #include "ArduinoJson.h"
#include <SecuritySettingsService.h> #include "AsyncJson.h"
#include <StatefulService.h> #include "ESPAsyncWebServer.h"
#include <Network.h> #include "FS.h"
#include "SecurityManager.h"
#include "SecuritySettingsService.h"
#include "StatefulService.h"
#include "Network.h"
#include <espMqttClient.h>
#define AP_SETTINGS_FILE "/config/apSettings.json" #define AP_SETTINGS_FILE "/config/apSettings.json"
#define MQTT_SETTINGS_FILE "/config/mqttSettings.json" #define MQTT_SETTINGS_FILE "/config/mqttSettings.json"
@@ -74,6 +72,7 @@ class DummySettings {
bool enableMDNS = true; bool enableMDNS = true;
bool enableCORS = false; bool enableCORS = false;
String CORSOrigin = "*"; String CORSOrigin = "*";
uint8_t tx_power = 0;
static void read(DummySettings & settings, JsonObject root){}; static void read(DummySettings & settings, JsonObject root){};
static void read(DummySettings & settings){}; static void read(DummySettings & settings){};
@@ -103,7 +102,6 @@ class ESP8266React {
, _securitySettingsService(server, fs){}; , _securitySettingsService(server, fs){};
void begin() { void begin() {
// initialize mqtt
_mqttClient = new espMqttClient(); _mqttClient = new espMqttClient();
}; };
void loop(){}; void loop(){};

View File

@@ -2,9 +2,9 @@
#define _ESPAsyncWebServer_H_ #define _ESPAsyncWebServer_H_
#include "Arduino.h" #include "Arduino.h"
#include "AsyncTCP.h"
#include <functional> #include <functional>
#include <AsyncTCP.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
class AsyncWebServer; class AsyncWebServer;

View File

@@ -21,9 +21,10 @@
#ifndef FS_H #ifndef FS_H
#define FS_H #define FS_H
#include "Arduino.h"
#include <memory> #include <memory>
#include <string> #include <string>
#include <Arduino.h>
#include <stdio.h> #include <stdio.h>
#include <sys/types.h> #include <sys/types.h>
#include <dirent.h> #include <dirent.h>
@@ -53,7 +54,7 @@ class File : public Stream {
int peek() override; int peek() override;
void flush() override; void flush() override;
size_t read(uint8_t * buf, size_t size); size_t read(uint8_t * buf, size_t size);
size_t readBytes(char * buffer, size_t length) { size_t readBytes(char * buffer, size_t length) override {
return read((uint8_t *)buffer, length); return read((uint8_t *)buffer, length);
} }

View File

@@ -1,8 +1,8 @@
#ifndef FSPersistence_h #ifndef FSPersistence_h
#define FSPersistence_h #define FSPersistence_h
#include <StatefulService.h> #include "StatefulService.h"
#include <FS.h> #include "FS.h"
template <class T> template <class T>
class FSPersistence { class FSPersistence {
@@ -40,7 +40,7 @@ class FSPersistence {
void enableUpdateHandler() { void enableUpdateHandler() {
if (!_updateHandlerId) { if (!_updateHandlerId) {
_updateHandlerId = _statefulService->addUpdateHandler([&](const String & originId) { writeToFS(); }); _updateHandlerId = _statefulService->addUpdateHandler([this] { writeToFS(); });
} }
} }

View File

@@ -3,11 +3,11 @@
#include <functional> #include <functional>
#include <AsyncJson.h> #include "AsyncJson.h"
#include <ESPAsyncWebServer.h> #include "ESPAsyncWebServer.h"
#include <SecurityManager.h> #include "SecurityManager.h"
#include <StatefulService.h> #include "StatefulService.h"
#define HTTP_ENDPOINT_ORIGIN_ID "http" #define HTTP_ENDPOINT_ORIGIN_ID "http"

View File

@@ -17,9 +17,9 @@
*/ */
#ifdef ENV_NATIVE #ifdef ENV_NATIVE
#include <Arduino.h> #include "Arduino.h"
#include <FS.h> #include "FS.h"
#include <LittleFS.h> #include "LittleFS.h"
fs::LittleFSFS LittleFS; fs::LittleFSFS LittleFS;

View File

@@ -1,7 +1,8 @@
#ifndef Network_h #ifndef Network_h
#define Network_h #define Network_h
#include <Arduino.h> #include "Arduino.h"
#include <functional> #include <functional>
#include <IPAddress.h> #include <IPAddress.h>
@@ -11,6 +12,21 @@
#define WIFI_AP WIFI_MODE_AP #define WIFI_AP WIFI_MODE_AP
#define WIFI_AP_STA WIFI_MODE_APSTA #define WIFI_AP_STA WIFI_MODE_APSTA
typedef enum {
WIFI_POWER_19_5dBm = 78, // 19.5dBm
WIFI_POWER_19dBm = 76, // 19dBm
WIFI_POWER_18_5dBm = 74, // 18.5dBm
WIFI_POWER_17dBm = 68, // 17dBm
WIFI_POWER_15dBm = 60, // 15dBm
WIFI_POWER_13dBm = 52, // 13dBm
WIFI_POWER_11dBm = 44, // 11dBm
WIFI_POWER_8_5dBm = 34, // 8.5dBm
WIFI_POWER_7dBm = 28, // 7dBm
WIFI_POWER_5dBm = 20, // 5dBm
WIFI_POWER_2dBm = 8, // 2dBm
WIFI_POWER_MINUS_1dBm = -4 // -1dBm
} wifi_power_t;
typedef enum { typedef enum {
ETH_CLOCK_GPIO0_IN = 0, /*!< RMII clock input to GPIO0 */ ETH_CLOCK_GPIO0_IN = 0, /*!< RMII clock input to GPIO0 */
ETH_CLOCK_GPIO0_OUT = 1, /*!< RMII clock output from GPIO0 */ ETH_CLOCK_GPIO0_OUT = 1, /*!< RMII clock output from GPIO0 */

View File

@@ -28,7 +28,8 @@
#include <string> #include <string>
#include <Printable.h> #include <Printable.h>
#include <WString.h>
#include "WString.h"
int vsnprintf_P(char * str, size_t size, const char * format, va_list ap); int vsnprintf_P(char * str, size_t size, const char * format, va_list ap);

View File

@@ -1,18 +1,15 @@
#ifndef SecurityManager_h #ifndef SecurityManager_h
#define SecurityManager_h #define SecurityManager_h
#include <Arduino.h> #include "Arduino.h"
#include <Features.h> #include "Features.h"
#include <ESPAsyncWebServer.h> #include "ESPAsyncWebServer.h"
#include <AsyncJson.h> #include "AsyncJson.h"
#include <list> #include <list>
#ifndef FACTORY_JWT_SECRET #define FACTORY_JWT_SECRET "ems-esp"
#define FACTORY_JWT_SECRET ESPUtils::defaultDeviceValue()
#endif
#define ACCESS_TOKEN_PARAMATER "access_token" #define ACCESS_TOKEN_PARAMATER "access_token"
#define AUTHORIZATION_HEADER "Authorization" #define AUTHORIZATION_HEADER "Authorization"
#define AUTHORIZATION_HEADER_PREFIX "Bearer " #define AUTHORIZATION_HEADER_PREFIX "Bearer "
#define AUTHORIZATION_HEADER_PREFIX_LEN 7 #define AUTHORIZATION_HEADER_PREFIX_LEN 7

View File

@@ -1,6 +1,6 @@
#ifdef EMSESP_STANDALONE #ifdef EMSESP_STANDALONE
#include <SecuritySettingsService.h> #include "SecuritySettingsService.h"
User ADMIN_USER = User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true); User ADMIN_USER = User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true);
@@ -11,7 +11,7 @@ SecuritySettingsService::~SecuritySettingsService() {
} }
ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) { ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) {
return [this, predicate](AsyncWebServerRequest * request) { return true; }; return [predicate](AsyncWebServerRequest * request) { return true; };
} }
// Return the admin user on all request - disabling security features // Return the admin user on all request - disabling security features

View File

@@ -1,10 +1,10 @@
#ifndef SecuritySettingsService_h #ifndef SecuritySettingsService_h
#define SecuritySettingsService_h #define SecuritySettingsService_h
#include <Features.h> #include "Features.h"
#include <SecurityManager.h> #include "SecurityManager.h"
#include <HttpEndpoint.h> #include "HttpEndpoint.h"
#include <FSPersistence.h> #include "FSPersistence.h"
#ifndef FACTORY_ADMIN_USERNAME #ifndef FACTORY_ADMIN_USERNAME
#define FACTORY_ADMIN_USERNAME "admin" #define FACTORY_ADMIN_USERNAME "admin"

View File

@@ -1,3 +1,3 @@
#include <StatefulService.h> #include "StatefulService.h"
update_handler_id_t StateUpdateHandlerInfo::currentUpdatedHandlerId = 0; update_handler_id_t StateUpdateHandlerInfo::currentUpdatedHandlerId = 0;

View File

@@ -1,7 +1,8 @@
#ifndef StatefulService_h #ifndef StatefulService_h
#define StatefulService_h #define StatefulService_h
#include <Arduino.h> #include "Arduino.h"
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <list> #include <list>
@@ -21,7 +22,7 @@ template <typename T>
using JsonStateReader = std::function<void(T & settings, JsonObject root)>; using JsonStateReader = std::function<void(T & settings, JsonObject root)>;
typedef size_t update_handler_id_t; typedef size_t update_handler_id_t;
typedef std::function<void(const String & originId)> StateUpdateCallback; typedef std::function<void()> StateUpdateCallback;
typedef struct StateUpdateHandlerInfo { typedef struct StateUpdateHandlerInfo {
static update_handler_id_t currentUpdatedHandlerId; static update_handler_id_t currentUpdatedHandlerId;
@@ -68,12 +69,12 @@ class StatefulService {
} }
} }
StateUpdateResult update(std::function<StateUpdateResult(T &)> stateUpdater, const String & originId) { StateUpdateResult update(std::function<StateUpdateResult(T &)> stateUpdater) {
beginTransaction(); beginTransaction();
StateUpdateResult result = stateUpdater(_state); StateUpdateResult result = stateUpdater(_state);
endTransaction(); endTransaction();
if (result == StateUpdateResult::CHANGED) { if (result == StateUpdateResult::CHANGED) {
callUpdateHandlers(originId); callUpdateHandlers();
} }
return result; return result;
} }
@@ -85,12 +86,12 @@ class StatefulService {
return result; return result;
} }
StateUpdateResult update(JsonObject jsonObject, JsonStateUpdater<T> stateUpdater, const String & originId) { StateUpdateResult update(JsonObject jsonObject, JsonStateUpdater<T> stateUpdater) {
beginTransaction(); beginTransaction();
StateUpdateResult result = stateUpdater(jsonObject, _state); StateUpdateResult result = stateUpdater(jsonObject, _state);
endTransaction(); endTransaction();
if (result == StateUpdateResult::CHANGED) { if (result == StateUpdateResult::CHANGED) {
callUpdateHandlers(originId); callUpdateHandlers();
} }
return result; return result;
} }
@@ -114,9 +115,9 @@ class StatefulService {
endTransaction(); endTransaction();
} }
void callUpdateHandlers(const String & originId) { void callUpdateHandlers() {
for (const StateUpdateHandlerInfo_t & updateHandler : _updateHandlers) { for (const StateUpdateHandlerInfo_t & updateHandler : _updateHandlers) {
updateHandler._cb(originId); updateHandler._cb();
} }
} }

View File

@@ -1,7 +1,7 @@
#ifdef EMSESP_STANDALONE #ifdef EMSESP_STANDALONE
#include <Arduino.h> #include "Arduino.h"
#include "WString.h" #include "WString.h"
/* /*

View File

@@ -19,7 +19,7 @@
#ifndef EMSESP_EMSUART_H #ifndef EMSESP_EMSUART_H
#define EMSESP_EMSUART_H #define EMSESP_EMSUART_H
#include <Arduino.h> #include "Arduino.h"
namespace emsesp { namespace emsesp {

View File

@@ -225,7 +225,7 @@ let network_settings = {
password: 'myPassword', password: 'myPassword',
hostname: 'ems-esp', hostname: 'ems-esp',
nosleep: true, nosleep: true,
tx_power: 20, tx_power: 0,
bandwidth20: false, bandwidth20: false,
static_ip_config: false, static_ip_config: false,
enableMDNS: true, enableMDNS: true,

View File

@@ -155,7 +155,7 @@ network_settings = {
password: 'myPassword', password: 'myPassword',
hostname: 'ems-esp', hostname: 'ems-esp',
nosleep: true, nosleep: true,
tx_power: 20, tx_power: 0,
bandwidth20: false, bandwidth20: false,
static_ip_config: false, static_ip_config: false,
enableMDNS: true, enableMDNS: true,

View File

@@ -2,7 +2,6 @@
[common] [common]
; custom build flags to use in my_build_flags ; custom build flags to use in my_build_flags
; -DEMSESP_WIFI_TWEAK ; experimental WiFi tweaks for stability
; -DEMSESP_UART_DEBUG ; debugging UART ; -DEMSESP_UART_DEBUG ; debugging UART
; -DEMSESP_DEBUG ; enables DEBUG to the log. Will generate a lot of extra traffic on Console and Syslog ; -DEMSESP_DEBUG ; enables DEBUG to the log. Will generate a lot of extra traffic on Console and Syslog
; -DEMSESP_DEFAULT_BOARD_PROFILE=\"NODEMCU\" ; hard code the default board name ; -DEMSESP_DEFAULT_BOARD_PROFILE=\"NODEMCU\" ; hard code the default board name

View File

@@ -13,8 +13,6 @@ extra_configs =
[common] [common]
core_build_flags = core_build_flags =
-D ARDUINO_ARCH_ESP32=1
-D ESP32=1
-O2 -O2
-std=gnu++17 -std=gnu++17
@@ -50,7 +48,7 @@ extra_scripts =
scripts/rename_fw.py scripts/rename_fw.py
[espressi32_base_tasmota] [espressi32_base_tasmota]
; use Tasmota's libary which removes some libs (like mbedtsl) and increases available heap ; use Tasmota's library which removes some libs (like mbedtsl) and increases available heap
; platform = https://github.com/tasmota/platform-espressif32.git ; latest development ; platform = https://github.com/tasmota/platform-espressif32.git ; latest development
; latest release with WiFi_secure.h, Arduino 2.0.14 ; latest release with WiFi_secure.h, Arduino 2.0.14
platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.01.01/platform-espressif32.zip platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.01.01/platform-espressif32.zip
@@ -68,11 +66,9 @@ extra_scripts =
[env] [env]
monitor_speed = 115200 monitor_speed = 115200
monitor_filters = esp32_exception_decoder
upload_speed = 921600 upload_speed = 921600
build_type = release build_type = release
lib_ldf_mode = chain+ lib_ldf_mode = chain+
; board_build.flash_mode = qio
check_tool = cppcheck, clangtidy check_tool = cppcheck, clangtidy
check_severity = high, medium check_severity = high, medium
@@ -169,14 +165,14 @@ build_flags =
platform = native platform = native
build_flags = build_flags =
-DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_PROGMEM=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DARDUINOJSON_USE_DOUBLE=0 -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_PROGMEM=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DARDUINOJSON_USE_DOUBLE=0
-DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_TEST -D__linux__ -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_TEST
-DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.6.5-dev\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\" -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.6.5-dev\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\"
-lpthread -lpthread
-D__linux__
-std=gnu++11 -Og -ggdb -std=gnu++11 -Og -ggdb
build_src_flags = build_src_flags =
-Wall -Wextra -Werror -Wall -Wextra -Werror
-Wno-unused-parameter -Wno-sign-compare -Wno-unused-parameter -Wno-sign-compare
; -Wswitch-enum -Wno-unused-parameter -Wno-inconsistent-missing-override -Wno-unused-lambda-capture -Wno-missing-braces
-I./lib_standalone -I./lib_standalone
-I./lib/ArduinoJson/src -I./lib/ArduinoJson/src
-I./lib/uuid-common/src -I./lib/uuid-common/src

View File

@@ -1,10 +0,0 @@
# for calling dos upload from Window WSL2 Linux, because serial ports are not mapped yet
# example file
Import('env')
from subprocess import call
def upload(source, target, env):
print("bin file: " + str(target[0]))
call(["cmd.exe", "/c", "c:\\Users\\paul\\OneDrive\\Desktop\\ems-esp32.bat"])
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", [upload])

View File

@@ -324,8 +324,7 @@ void AnalogSensor::loop() {
bool AnalogSensor::update(uint8_t gpio, const std::string & name, double offset, double factor, uint8_t uom, int8_t type, bool deleted) { bool AnalogSensor::update(uint8_t gpio, const std::string & name, double offset, double factor, uint8_t uom, int8_t type, bool deleted) {
// first see if we can find the sensor in our customization list // first see if we can find the sensor in our customization list
bool found_sensor = false; bool found_sensor = false;
EMSESP::webCustomizationService.update( EMSESP::webCustomizationService.update([&](WebCustomization & settings) {
[&](WebCustomization & settings) {
for (auto & AnalogCustomization : settings.analogCustomizations) { for (auto & AnalogCustomization : settings.analogCustomizations) {
if (AnalogCustomization.type == AnalogType::COUNTER || AnalogCustomization.type >= AnalogType::DIGITAL_OUT) { if (AnalogCustomization.type == AnalogType::COUNTER || AnalogCustomization.type >= AnalogType::DIGITAL_OUT) {
Command::erase_command(EMSdevice::DeviceType::ANALOGSENSOR, AnalogCustomization.name.c_str()); Command::erase_command(EMSdevice::DeviceType::ANALOGSENSOR, AnalogCustomization.name.c_str());
@@ -353,8 +352,7 @@ bool AnalogSensor::update(uint8_t gpio, const std::string & name, double offset,
} }
} }
return StateUpdateResult::UNCHANGED; return StateUpdateResult::UNCHANGED;
}, });
"local");
// if the sensor exists and we're using HA, delete the old HA record // if the sensor exists and we're using HA, delete the old HA record
if (found_sensor && Mqtt::ha_enabled()) { if (found_sensor && Mqtt::ha_enabled()) {
@@ -363,8 +361,7 @@ bool AnalogSensor::update(uint8_t gpio, const std::string & name, double offset,
// we didn't find it, it's new, so create and store it in the customization list // we didn't find it, it's new, so create and store it in the customization list
if (!found_sensor) { if (!found_sensor) {
EMSESP::webCustomizationService.update( EMSESP::webCustomizationService.update([&](WebCustomization & settings) {
[&](WebCustomization & settings) {
auto newSensor = AnalogCustomization(); auto newSensor = AnalogCustomization();
newSensor.gpio = gpio; newSensor.gpio = gpio;
newSensor.name = name; newSensor.name = name;
@@ -375,8 +372,7 @@ bool AnalogSensor::update(uint8_t gpio, const std::string & name, double offset,
settings.analogCustomizations.push_back(newSensor); settings.analogCustomizations.push_back(newSensor);
LOG_DEBUG("Adding new customization for analog sensor GPIO %02d", gpio); LOG_DEBUG("Adding new customization for analog sensor GPIO %02d", gpio);
return StateUpdateResult::CHANGED; // persist the change return StateUpdateResult::CHANGED; // persist the change
}, });
"local");
} }
// reloads the sensors in the customizations file into the sensors list // reloads the sensors in the customizations file into the sensors list
@@ -819,8 +815,7 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) {
return false; return false;
} }
if (oldoffset != sensor.offset()) { if (oldoffset != sensor.offset()) {
EMSESP::webCustomizationService.update( EMSESP::webCustomizationService.update([&](WebCustomization & settings) {
[&](WebCustomization & settings) {
for (auto & AnalogCustomization : settings.analogCustomizations) { for (auto & AnalogCustomization : settings.analogCustomizations) {
if (AnalogCustomization.gpio == sensor.gpio() && AnalogCustomization.type == sensor.type()) { if (AnalogCustomization.gpio == sensor.gpio() && AnalogCustomization.type == sensor.type()) {
AnalogCustomization.offset = sensor.offset(); AnalogCustomization.offset = sensor.offset();
@@ -831,8 +826,7 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) {
} else { } else {
return StateUpdateResult::CHANGED; // persist the change return StateUpdateResult::CHANGED; // persist the change
} }
}, });
"local");
publish_sensor(sensor); publish_sensor(sensor);
changed_ = true; changed_ = true;
} }

View File

@@ -184,12 +184,10 @@ static void setup_commands(std::shared_ptr<Commands> & commands) {
shell.enter_password(F_(new_password_prompt2), [password1](Shell & shell, bool completed, const std::string & password2) { shell.enter_password(F_(new_password_prompt2), [password1](Shell & shell, bool completed, const std::string & password2) {
if (completed) { if (completed) {
if (password1 == password2) { if (password1 == password2) {
to_app(shell).esp8266React.getSecuritySettingsService()->update( to_app(shell).esp8266React.getSecuritySettingsService()->update([&](SecuritySettings & securitySettings) {
[&](SecuritySettings & securitySettings) {
securitySettings.jwtSecret = password2.c_str(); securitySettings.jwtSecret = password2.c_str();
return StateUpdateResult::CHANGED; return StateUpdateResult::CHANGED;
}, });
"local");
shell.println("Admin password updated"); shell.println("Admin password updated");
} else { } else {
shell.println("Passwords do not match"); shell.println("Passwords do not match");
@@ -258,12 +256,10 @@ static void setup_commands(std::shared_ptr<Commands> & commands) {
shell.println("The network connection will be reset..."); shell.println("The network connection will be reset...");
Shell::loop_all(); Shell::loop_all();
delay(1000); // wait a second delay(1000); // wait a second
to_app(shell).esp8266React.getNetworkSettingsService()->update( to_app(shell).esp8266React.getNetworkSettingsService()->update([&](NetworkSettings & networkSettings) {
[&](NetworkSettings & networkSettings) {
networkSettings.hostname = arguments.front().c_str(); networkSettings.hostname = arguments.front().c_str();
return StateUpdateResult::CHANGED; return StateUpdateResult::CHANGED;
}, });
"local");
}); });
commands->add_command(ShellContext::MAIN, commands->add_command(ShellContext::MAIN,
@@ -293,8 +289,7 @@ static void setup_commands(std::shared_ptr<Commands> & commands) {
if (arguments.size() == 2 && Helpers::toLower(arguments.back()) == "nvs") { if (arguments.size() == 2 && Helpers::toLower(arguments.back()) == "nvs") {
to_app(shell).nvs_.putString("boot", board_profile.c_str()); to_app(shell).nvs_.putString("boot", board_profile.c_str());
} }
to_app(shell).webSettingsService.update( to_app(shell).webSettingsService.update([&](WebSettings & settings) {
[&](WebSettings & settings) {
settings.board_profile = board_profile.c_str(); settings.board_profile = board_profile.c_str();
settings.led_gpio = data[0]; settings.led_gpio = data[0];
settings.dallas_gpio = data[1]; settings.dallas_gpio = data[1];
@@ -306,8 +301,7 @@ static void setup_commands(std::shared_ptr<Commands> & commands) {
settings.eth_phy_addr = data[7]; settings.eth_phy_addr = data[7];
settings.eth_clock_mode = data[8]; settings.eth_clock_mode = data[8];
return StateUpdateResult::CHANGED; return StateUpdateResult::CHANGED;
}, });
"local");
shell.printfln("Loaded board profile %s", board_profile.c_str()); shell.printfln("Loaded board profile %s", board_profile.c_str());
to_app(shell).system_.network_init(true); to_app(shell).system_.network_init(true);
}); });
@@ -320,13 +314,11 @@ static void setup_commands(std::shared_ptr<Commands> & commands) {
[](Shell & shell, const std::vector<std::string> & arguments) { [](Shell & shell, const std::vector<std::string> & arguments) {
uint8_t device_id = Helpers::hextoint(arguments.front().c_str()); uint8_t device_id = Helpers::hextoint(arguments.front().c_str());
if ((device_id == 0x0B) || (device_id == 0x0D) || (device_id == 0x0A) || (device_id == 0x0F) || (device_id == 0x12)) { if ((device_id == 0x0B) || (device_id == 0x0D) || (device_id == 0x0A) || (device_id == 0x0F) || (device_id == 0x12)) {
to_app(shell).webSettingsService.update( to_app(shell).webSettingsService.update([&](WebSettings & settings) {
[&](WebSettings & settings) {
settings.ems_bus_id = device_id; settings.ems_bus_id = device_id;
shell.printfln(F_(bus_id_fmt), settings.ems_bus_id); shell.printfln(F_(bus_id_fmt), settings.ems_bus_id);
return StateUpdateResult::CHANGED; return StateUpdateResult::CHANGED;
}, });
"local");
} else { } else {
shell.println("Must be 0B, 0D, 0A, 0E, 0F, or 48 - 4D"); shell.println("Must be 0B, 0D, 0A, 0E, 0F, or 48 - 4D");
} }
@@ -342,13 +334,11 @@ static void setup_commands(std::shared_ptr<Commands> & commands) {
[](Shell & shell, const std::vector<std::string> & arguments) { [](Shell & shell, const std::vector<std::string> & arguments) {
uint8_t tx_mode = std::strtol(arguments[0].c_str(), nullptr, 10); uint8_t tx_mode = std::strtol(arguments[0].c_str(), nullptr, 10);
// save the tx_mode // save the tx_mode
to_app(shell).webSettingsService.update( to_app(shell).webSettingsService.update([&](WebSettings & settings) {
[&](WebSettings & settings) {
settings.tx_mode = tx_mode; settings.tx_mode = tx_mode;
shell.printfln(F_(tx_mode_fmt), settings.tx_mode); shell.printfln(F_(tx_mode_fmt), settings.tx_mode);
return StateUpdateResult::CHANGED; return StateUpdateResult::CHANGED;
}, });
"local");
to_app(shell).uart_init(); to_app(shell).uart_init();
}); });

View File

@@ -32,7 +32,7 @@ class EMSESPConsole : public EMSESPShell {
#endif #endif
~EMSESPConsole() override; ~EMSESPConsole() override;
std::string console_name(); std::string console_name() override;
private: private:
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE

View File

@@ -884,12 +884,10 @@ bool Solar::set_SM10MaxFlow(const char * value, const int8_t id) {
return false; return false;
} }
maxFlow_ = (flow * 10); maxFlow_ = (flow * 10);
EMSESP::webSettingsService.update( EMSESP::webSettingsService.update([&](WebSettings & settings) {
[&](WebSettings & settings) {
settings.solar_maxflow = maxFlow_; settings.solar_maxflow = maxFlow_;
return StateUpdateResult::CHANGED; return StateUpdateResult::CHANGED;
}, });
"local");
return true; return true;
} }

View File

@@ -1492,8 +1492,10 @@ void EMSESP::start() {
bool factory_settings = false; bool factory_settings = false;
#endif #endif
esp8266React.begin(); // loads core system services settings (network, mqtt, ap, ntp etc)
webLogService.begin(); // start web log service. now we can start capturing logs to the web log webLogService.begin(); // start web log service. now we can start capturing logs to the web log
esp8266React.begin(); // loads core system services settings (network, mqtt, ap, ntp etc)
nvs_.begin("ems-esp", false, "nvs"); nvs_.begin("ems-esp", false, "nvs");
LOG_INFO("Starting EMS-ESP version %s", EMSESP_APP_VERSION); // welcome message LOG_INFO("Starting EMS-ESP version %s", EMSESP_APP_VERSION); // welcome message

View File

@@ -38,8 +38,7 @@
#endif #endif
#include <Preferences.h> #include <Preferences.h>
#include <ESP8266React.h> #include "ESP8266React.h"
#include "web/WebStatusService.h" #include "web/WebStatusService.h"
#include "web/WebDataService.h" #include "web/WebDataService.h"
#include "web/WebSettingsService.h" #include "web/WebSettingsService.h"

View File

@@ -23,8 +23,8 @@
#include "temperaturesensor.h" #include "temperaturesensor.h"
#include "version.h" #include "version.h"
#include "default_settings.h" #include "default_settings.h"
#include "helpers.h"
#include <ESP8266React.h> #include "ESP8266React.h"
#include <uuid/log.h> #include <uuid/log.h>

View File

@@ -69,7 +69,7 @@ MAKE_WORD_TRANSLATION(fetch_cmd, "refresh all EMS values", "Lese alle EMS-Werte
MAKE_WORD_TRANSLATION(restart_cmd, "restart EMS-ESP", "Neustart", "opnieuw opstarten", "", "uruchom ponownie EMS-ESP", "restart EMS-ESP", "redémarrer EMS-ESP", "EMS-ESPyi yeniden başlat", "riavvia EMS-ESP", "reštart EMS-ESP") // TODO translate MAKE_WORD_TRANSLATION(restart_cmd, "restart EMS-ESP", "Neustart", "opnieuw opstarten", "", "uruchom ponownie EMS-ESP", "restart EMS-ESP", "redémarrer EMS-ESP", "EMS-ESPyi yeniden başlat", "riavvia EMS-ESP", "reštart EMS-ESP") // TODO translate
MAKE_WORD_TRANSLATION(watch_cmd, "watch incoming telegrams", "Watch auf eingehende Telegramme", "inkomende telegrammen bekijken", "", "obserwuj przyczodzące telegramy", "se innkommende telegrammer", "", "Gelen telegramları", "guardare i telegrammi in arrivo", "sledovať prichádzajúce telegramy") // TODO translate MAKE_WORD_TRANSLATION(watch_cmd, "watch incoming telegrams", "Watch auf eingehende Telegramme", "inkomende telegrammen bekijken", "", "obserwuj przyczodzące telegramy", "se innkommende telegrammer", "", "Gelen telegramları", "guardare i telegrammi in arrivo", "sledovať prichádzajúce telegramy") // TODO translate
MAKE_WORD_TRANSLATION(publish_cmd, "publish all to MQTT", "Publiziere MQTT", "publiceer alles naar MQTT", "", "opublikuj wszystko na MQTT", "Publiser alt til MQTT", "", "Hepsini MQTTye gönder", "pubblica tutto su MQTT", "zverejniť všetko na MQTT") // TODO translate MAKE_WORD_TRANSLATION(publish_cmd, "publish all to MQTT", "Publiziere MQTT", "publiceer alles naar MQTT", "", "opublikuj wszystko na MQTT", "Publiser alt til MQTT", "", "Hepsini MQTTye gönder", "pubblica tutto su MQTT", "zverejniť všetko na MQTT") // TODO translate
MAKE_WORD_TRANSLATION(system_info_cmd, "show system status", "Zeige System-Status", "toon systeemstatus", "", "pokaż status systemu", "vis system status", "", "Sistem Durumunu Göster", "visualizza stati di sistema", "zobraziť stav systému") // TODO translate MAKE_WORD_TRANSLATION(system_info_cmd, "show system info", "Zeige System-Status", "toon systeemstatus", "", "pokaż status systemu", "vis system status", "", "Sistem Durumunu Göster", "visualizza stati di sistema", "zobraziť stav systému") // TODO translate
MAKE_WORD_TRANSLATION(schedule_cmd, "enable schedule item", "Aktiviere Zeitplan", "activeer tijdschema item", "", "aktywuj wybrany harmonogram", "", "", "program öğesini etkinleştir", "abilitare l'elemento programmato", "povoliť položku plánu") // TODO translate MAKE_WORD_TRANSLATION(schedule_cmd, "enable schedule item", "Aktiviere Zeitplan", "activeer tijdschema item", "", "aktywuj wybrany harmonogram", "", "", "program öğesini etkinleştir", "abilitare l'elemento programmato", "povoliť položku plánu") // TODO translate
MAKE_WORD_TRANSLATION(entity_cmd, "set custom value on ems", "Sende eigene Entitäten zu EMS", "verstuur custom waarde naar EMS", "", "wyślij własną wartość na EMS", "", "", "emp üzerinde özel değer ayarla", "imposta valori personalizzati su EMS", "nastaviť vlastnú hodnotu na ems") // TODO translate MAKE_WORD_TRANSLATION(entity_cmd, "set custom value on ems", "Sende eigene Entitäten zu EMS", "verstuur custom waarde naar EMS", "", "wyślij własną wartość na EMS", "", "", "emp üzerinde özel değer ayarla", "imposta valori personalizzati su EMS", "nastaviť vlastnú hodnotu na ems") // TODO translate
MAKE_WORD_TRANSLATION(commands_response, "get response", "Hole Antwort", "Verzoek om antwoord", "", "uzyskaj odpowiedź", "", "", "gelen cevap", "", "získať odpoveď") // TODO translate MAKE_WORD_TRANSLATION(commands_response, "get response", "Hole Antwort", "Verzoek om antwoord", "", "uzyskaj odpowiedź", "", "", "gelen cevap", "", "získať odpoveď") // TODO translate

View File

@@ -201,9 +201,9 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) {
for (const auto & mqtt_subfunction : mqtt_subfunctions_) { for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
shell.printfln(" %s/%s", Mqtt::base().c_str(), mqtt_subfunction.topic_.c_str()); shell.printfln(" %s/%s", Mqtt::base().c_str(), mqtt_subfunction.topic_.c_str());
} }
shell.println();
shell.println(); shell.println();
shell.println();
} }
#if defined(EMSESP_TEST) #if defined(EMSESP_TEST)
@@ -388,7 +388,7 @@ void Mqtt::start() {
// add the 'publish' command ('call system publish' in console or via API) // add the 'publish' command ('call system publish' in console or via API)
Command::add(EMSdevice::DeviceType::SYSTEM, F_(publish), System::command_publish, FL_(publish_cmd)); Command::add(EMSdevice::DeviceType::SYSTEM, F_(publish), System::command_publish, FL_(publish_cmd));
// create last will topic with the base prefixed. It has to be static because asyncmqttclient destroys the reference // create last will topic with the base prefixed. It has to be static because the client destroys the reference
static char will_topic[MQTT_TOPIC_MAX_SIZE]; static char will_topic[MQTT_TOPIC_MAX_SIZE];
if (!Mqtt::base().empty()) { if (!Mqtt::base().empty()) {
snprintf(will_topic, MQTT_TOPIC_MAX_SIZE, "%s/status", Mqtt::base().c_str()); snprintf(will_topic, MQTT_TOPIC_MAX_SIZE, "%s/status", Mqtt::base().c_str());
@@ -466,6 +466,7 @@ void Mqtt::on_disconnect(espMqttClientTypes::DisconnectReason reason) {
return; return;
} }
connecting_ = false; connecting_ = false;
if (reason == espMqttClientTypes::DisconnectReason::TCP_DISCONNECTED) { if (reason == espMqttClientTypes::DisconnectReason::TCP_DISCONNECTED) {
LOG_WARNING("MQTT disconnected: TCP"); LOG_WARNING("MQTT disconnected: TCP");
} else if (reason == espMqttClientTypes::DisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION) { } else if (reason == espMqttClientTypes::DisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION) {
@@ -483,6 +484,7 @@ void Mqtt::on_disconnect(espMqttClientTypes::DisconnectReason reason) {
} else { } else {
LOG_WARNING("MQTT disconnected: code %d", reason); LOG_WARNING("MQTT disconnected: code %d", reason);
} }
mqttClient_->clearQueue(true); mqttClient_->clearQueue(true);
} }

View File

@@ -211,8 +211,7 @@ bool System::command_syslog_level(const char * value, const int8_t id) {
changed = true; changed = true;
} }
return StateUpdateResult::CHANGED; return StateUpdateResult::CHANGED;
}, });
"local");
if (changed) { if (changed) {
EMSESP::system_.syslog_init(); EMSESP::system_.syslog_init();
} }
@@ -277,11 +276,12 @@ void System::system_restart() {
// saves all settings // saves all settings
void System::wifi_reconnect() { void System::wifi_reconnect() {
LOG_INFO("WiFi reconnecting..."); EMSESP::esp8266React.getNetworkSettingsService()->read(
[](NetworkSettings & networkSettings) { LOG_INFO("WiFi reconnecting to SSID '%s'...", networkSettings.ssid.c_str()); });
Shell::loop_all(); Shell::loop_all();
delay(1000); // wait a second delay(1000); // wait a second
EMSESP::webSettingsService.save(); // local settings EMSESP::webSettingsService.save(); // save local settings
EMSESP::esp8266React.getNetworkSettingsService()->callUpdateHandlers("local"); // in case we've changed ssid or password EMSESP::esp8266React.getNetworkSettingsService()->callUpdateHandlers(); // in case we've changed ssid or password
} }
// format the FS. Wipes everything. // format the FS. Wipes everything.
@@ -388,33 +388,6 @@ void System::reload_settings() {
}); });
} }
// adjust WiFi settings
// this for problem solving mesh and connection issues, and also get EMS bus-powered more stable by lowering power
void System::wifi_tweak() {
#if defined(EMSESP_WIFI_TWEAK)
// Default Tx Power is 80 = 20dBm <-- default
// WIFI_POWER_19_5dBm = 78,// 19.5dBm
// WIFI_POWER_19dBm = 76,// 19dBm
// WIFI_POWER_18_5dBm = 74,// 18.5dBm
// WIFI_POWER_17dBm = 68,// 17dBm
// WIFI_POWER_15dBm = 60,// 15dBm
// WIFI_POWER_13dBm = 52,// 13dBm
// WIFI_POWER_11dBm = 44,// 11dBm
// WIFI_POWER_8_5dBm = 34,// 8.5dBm
// WIFI_POWER_7dBm = 28,// 7dBm
// WIFI_POWER_5dBm = 20,// 5dBm
// WIFI_POWER_2dBm = 8,// 2dBm
// WIFI_POWER_MINUS_1dBm = -4// -1dBm
wifi_power_t p1 = WiFi.getTxPower();
(void)WiFi.setTxPower(WIFI_POWER_17dBm);
wifi_power_t p2 = WiFi.getTxPower();
bool s1 = WiFi.getSleep();
WiFi.setSleep(false); // turn off sleep - WIFI_PS_NONE
bool s2 = WiFi.getSleep();
LOG_DEBUG("Adjusting WiFi - Tx power %d->%d, Sleep %d->%d", p1, p2, s1, s2);
#endif
}
// check for valid ESP32 pins. This is very dependent on which ESP32 board is being used. // check for valid ESP32 pins. This is very dependent on which ESP32 board is being used.
// Typically you can't use 1, 6-11, 20, 24, 28-31 and 40+ // Typically you can't use 1, 6-11, 20, 24, 28-31 and 40+
// we allow 0 as it has a special function on the NodeMCU apparently // we allow 0 as it has a special function on the NodeMCU apparently
@@ -472,7 +445,7 @@ void System::start() {
// button single click // button single click
void System::button_OnClick(PButton & b) { void System::button_OnClick(PButton & b) {
LOG_DEBUG("Button pressed - single click"); LOG_NOTICE("Button pressed - single click - show settings folders");
#if defined(EMSESP_TEST) #if defined(EMSESP_TEST)
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
@@ -483,20 +456,19 @@ void System::button_OnClick(PButton & b) {
// button double click // button double click
void System::button_OnDblClick(PButton & b) { void System::button_OnDblClick(PButton & b) {
LOG_DEBUG("Button pressed - double click - reconnect"); LOG_NOTICE("Button pressed - double click - wifi reconnect");
EMSESP::system_.wifi_reconnect(); EMSESP::system_.wifi_reconnect();
} }
// button long press // button long press
void System::button_OnLongPress(PButton & b) { void System::button_OnLongPress(PButton & b) {
LOG_DEBUG("Button pressed - long press"); LOG_NOTICE("Button pressed - long press");
} }
// button indefinite press // button indefinite press
void System::button_OnVLongPress(PButton & b) { void System::button_OnVLongPress(PButton & b) {
LOG_DEBUG("Button pressed - very long press"); LOG_NOTICE("Button pressed - very long press - factory reset");
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
LOG_WARNING("Performing factory reset...");
EMSESP::esp8266React.factoryReset(); EMSESP::esp8266React.factoryReset();
#endif #endif
} }
@@ -977,6 +949,8 @@ void System::show_system(uuid::console::Shell & shell) {
shell.printfln(" SSID: %s", WiFi.SSID().c_str()); shell.printfln(" SSID: %s", WiFi.SSID().c_str());
shell.printfln(" BSSID: %s", WiFi.BSSIDstr().c_str()); shell.printfln(" BSSID: %s", WiFi.BSSIDstr().c_str());
shell.printfln(" RSSI: %d dBm (%d %%)", WiFi.RSSI(), wifi_quality(WiFi.RSSI())); shell.printfln(" RSSI: %d dBm (%d %%)", WiFi.RSSI(), wifi_quality(WiFi.RSSI()));
char result[10];
shell.printfln(" TxPower: %s dBm", emsesp::Helpers::render_value(result, (double)(WiFi.getTxPower() / 4), 1));
shell.printfln(" MAC address: %s", WiFi.macAddress().c_str()); shell.printfln(" MAC address: %s", WiFi.macAddress().c_str());
shell.printfln(" Hostname: %s", WiFi.getHostname()); shell.printfln(" Hostname: %s", WiFi.getHostname());
shell.printfln(" IPv4 address: %s/%s", uuid::printable_to_string(WiFi.localIP()).c_str(), uuid::printable_to_string(WiFi.subnetMask()).c_str()); shell.printfln(" IPv4 address: %s/%s", uuid::printable_to_string(WiFi.localIP()).c_str(), uuid::printable_to_string(WiFi.subnetMask()).c_str());
@@ -1110,15 +1084,13 @@ bool System::check_upgrade(bool factory_settings) {
version::Semver200_version settings_version(settingsVersion); version::Semver200_version settings_version(settingsVersion);
#if defined(EMSESP_DEBUG)
if (!missing_version) { if (!missing_version) {
LOG_INFO("Checking version (settings has %d.%d.%d-%s)...", LOG_DEBUG("Checking version upgrade (settings file is v%d.%d.%d-%s)",
settings_version.major(), settings_version.major(),
settings_version.minor(), settings_version.minor(),
settings_version.patch(), settings_version.patch(),
settings_version.prerelease().c_str()); settings_version.prerelease().c_str());
} }
#endif
if (factory_settings) { if (factory_settings) {
return false; // fresh install, do nothing return false; // fresh install, do nothing
@@ -1135,14 +1107,23 @@ bool System::check_upgrade(bool factory_settings) {
// if we're coming from 3.4.4 or 3.5.0b14 which had no version stored then we need to apply new settings // if we're coming from 3.4.4 or 3.5.0b14 which had no version stored then we need to apply new settings
if (missing_version) { if (missing_version) {
LOG_DEBUG("Setting MQTT Entity ID format to v3.4 format"); LOG_INFO("Setting MQTT Entity ID format to v3.4 format");
EMSESP::esp8266React.getMqttSettingsService()->update( EMSESP::esp8266React.getMqttSettingsService()->update([&](MqttSettings & mqttSettings) {
[&](MqttSettings & mqttSettings) {
mqttSettings.entity_format = 0; // use old Entity ID format from v3.4 mqttSettings.entity_format = 0; // use old Entity ID format from v3.4
return StateUpdateResult::CHANGED; return StateUpdateResult::CHANGED;
}, });
"local");
} }
// Network Settings Wifi tx_power is now using the value * 4.
EMSESP::esp8266React.getNetworkSettingsService()->update([&](NetworkSettings & networkSettings) {
if (networkSettings.tx_power == 20) {
networkSettings.tx_power = WIFI_POWER_19_5dBm; // use 19.5 as we don't have 20 anymore
LOG_INFO("Setting WiFi TX Power to Auto");
return StateUpdateResult::CHANGED;
}
return StateUpdateResult::UNCHANGED;
});
} else if (this_version < settings_version) { } else if (this_version < settings_version) {
// need downgrade // need downgrade
LOG_NOTICE("Downgrading to version %d.%d.%d-%s", this_version.major(), this_version.minor(), this_version.patch(), this_version.prerelease().c_str()); LOG_NOTICE("Downgrading to version %d.%d.%d-%s", this_version.major(), this_version.minor(), this_version.patch(), this_version.prerelease().c_str());
@@ -1153,12 +1134,10 @@ bool System::check_upgrade(bool factory_settings) {
// if we did a change, set the new version and reboot // if we did a change, set the new version and reboot
if (save_version) { if (save_version) {
EMSESP::webSettingsService.update( EMSESP::webSettingsService.update([&](WebSettings & settings) {
[&](WebSettings & settings) {
settings.version = EMSESP_APP_VERSION; settings.version = EMSESP_APP_VERSION;
return StateUpdateResult::CHANGED; return StateUpdateResult::CHANGED;
}, });
"local");
return true; // need reboot return true; // need reboot
} }
@@ -1257,6 +1236,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
if (WiFi.status() == WL_CONNECTED && !settings.bssid.isEmpty()) { if (WiFi.status() == WL_CONNECTED && !settings.bssid.isEmpty()) {
node["BSSID"] = "set"; node["BSSID"] = "set";
} }
node["TxPower setting"] = settings.tx_power;
node["static ip config"] = settings.staticIPConfig; node["static ip config"] = settings.staticIPConfig;
node["enable IPv6"] = settings.enableIPv6; node["enable IPv6"] = settings.enableIPv6;
node["low bandwidth"] = settings.bandwidth20; node["low bandwidth"] = settings.bandwidth20;
@@ -1580,8 +1560,8 @@ std::string System::reset_reason(uint8_t cpu) const {
// set NTP status // set NTP status
void System::ntp_connected(bool b) { void System::ntp_connected(bool b) {
if (b != ntp_connected_) { if (b != ntp_connected_ && !b) {
LOG_INFO(b ? "NTP connected" : "NTP disconnected"); // if changed report it LOG_WARNING("NTP disconnected"); // if turned off report it
} }
ntp_connected_ = b; ntp_connected_ = b;

View File

@@ -75,7 +75,6 @@ class System {
bool upload_status(); bool upload_status();
void show_mem(const char * note); void show_mem(const char * note);
void reload_settings(); void reload_settings();
void wifi_tweak();
void syslog_init(); void syslog_init();
bool check_upgrade(bool factory_settings); bool check_upgrade(bool factory_settings);
bool check_restore(); bool check_restore();

View File

@@ -21,16 +21,15 @@
#include <string> #include <string>
#include <deque> #include <deque>
#include <uuid/log.h>
// UART drivers // UART drivers
#if defined(ESP32) #if defined(ESP32)
#include "uart/emsuart_esp32.h" #include "uart/emsuart_esp32.h"
#elif defined(EMSESP_STANDALONE) #elif defined(EMSESP_STANDALONE)
#include <emsuart_standalone.h> #include "emsuart_standalone.h"
#endif #endif
#include <uuid/log.h>
#include "helpers.h" #include "helpers.h"
#define MAX_RX_TELEGRAMS 10 // size of Rx queue #define MAX_RX_TELEGRAMS 10 // size of Rx queue

View File

@@ -302,8 +302,7 @@ bool TemperatureSensor::update(const std::string & id, const std::string & name,
sensor.set_offset(offset); sensor.set_offset(offset);
// store the new name and offset in our configuration // store the new name and offset in our configuration
EMSESP::webCustomizationService.update( EMSESP::webCustomizationService.update([&](WebCustomization & settings) {
[&](WebCustomization & settings) {
// look it up to see if it exists // look it up to see if it exists
bool found = false; bool found = false;
for (auto & SensorCustomization : settings.sensorCustomizations) { for (auto & SensorCustomization : settings.sensorCustomizations) {
@@ -325,8 +324,7 @@ bool TemperatureSensor::update(const std::string & id, const std::string & name,
} }
sensor.ha_registered = false; // it's changed so we may need to recreate the HA config sensor.ha_registered = false; // it's changed so we may need to recreate the HA config
return StateUpdateResult::CHANGED; return StateUpdateResult::CHANGED;
}, });
"local");
return true; return true;
} }
} }

View File

@@ -1978,20 +1978,22 @@ void Test::listDir(fs::FS & fs, const char * dirname, uint8_t levels) {
File file = root.openNextFile(); File file = root.openNextFile();
while (file) { while (file) {
if (file.isDirectory()) { if (file.isDirectory()) {
Serial.print(" DIR : "); Serial.print(" DIR: ");
Serial.println(file.name()); Serial.println(file.name());
if (levels) { if (levels) {
listDir(fs, file.name(), levels - 1); listDir(fs, file.name(), levels - 1);
} }
Serial.println(); Serial.println();
} else { } else {
Serial.print(" FILE: "); Serial.print(" ");
Serial.print(file.name()); Serial.print(file.name());
Serial.print("\tSIZE: "); Serial.print(" (");
Serial.println(file.size()); Serial.print(file.size());
Serial.println(" bytes)");
} }
file = root.openNextFile(); file = root.openNextFile();
} }
Serial.println();
} }
#endif #endif
#endif #endif

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.6.5-test.13" #define EMSESP_APP_VERSION "3.6.5-test.14"

View File

@@ -18,8 +18,6 @@
#include "emsesp.h" #include "emsesp.h"
using namespace std::placeholders; // for `_1` etc
namespace emsesp { namespace emsesp {
uint32_t WebAPIService::api_count_ = 0; uint32_t WebAPIService::api_count_ = 0;
@@ -27,17 +25,26 @@ uint16_t WebAPIService::api_fails_ = 0;
WebAPIService::WebAPIService(AsyncWebServer * server, SecurityManager * securityManager) WebAPIService::WebAPIService(AsyncWebServer * server, SecurityManager * securityManager)
: _securityManager(securityManager) : _securityManager(securityManager)
, _apiHandler("/api", std::bind(&WebAPIService::webAPIService_post, this, _1, _2)) { // for POSTS, must use 'Content-Type: application/json' in header , _apiHandler(EMSESP_API_SERVICE_PATH, [this](AsyncWebServerRequest * request, JsonVariant json) { webAPIService_post(request, json); }) { // for POSTs
server->on("/api", HTTP_GET, std::bind(&WebAPIService::webAPIService_get, this, _1)); // for GETS server->on(EMSESP_API_SERVICE_PATH, HTTP_GET, [this](AsyncWebServerRequest * request) { webAPIService_get(request); }); // for GETs
server->addHandler(&_apiHandler); server->addHandler(&_apiHandler);
// for settings // for settings
server->on(GET_SETTINGS_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WebAPIService::getSettings, this, _1), AuthenticationPredicates::IS_ADMIN)); server->on(GET_SETTINGS_PATH,
HTTP_GET,
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { getSettings(request); }, AuthenticationPredicates::IS_ADMIN));
server->on(GET_CUSTOMIZATIONS_PATH, server->on(GET_CUSTOMIZATIONS_PATH,
HTTP_GET, HTTP_GET,
securityManager->wrapRequest(std::bind(&WebAPIService::getCustomizations, this, _1), AuthenticationPredicates::IS_ADMIN)); securityManager->wrapRequest([this](AsyncWebServerRequest * request) { getCustomizations(request); }, AuthenticationPredicates::IS_ADMIN));
server->on(GET_SCHEDULE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WebAPIService::getSchedule, this, _1), AuthenticationPredicates::IS_ADMIN));
server->on(GET_ENTITIES_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WebAPIService::getEntities, this, _1), AuthenticationPredicates::IS_ADMIN)); server->on(GET_SCHEDULE_PATH,
HTTP_GET,
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { getSchedule(request); }, AuthenticationPredicates::IS_ADMIN));
server->on(GET_ENTITIES_PATH,
HTTP_GET,
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { getEntities(request); }, AuthenticationPredicates::IS_ADMIN));
} }
// HTTP GET // HTTP GET

Some files were not shown because too many files have changed in this diff Show More