mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 15:59:52 +03:00
Compare commits
187 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a57fdaa4b3 | ||
|
|
1ae738016e | ||
|
|
ee5b1b8c34 | ||
|
|
4f98b4bb21 | ||
|
|
2b95a0d125 | ||
|
|
87b2a05d39 | ||
|
|
44d0b52424 | ||
|
|
de9ff6a3a1 | ||
|
|
fcc4831c9f | ||
|
|
6f435cbcfd | ||
|
|
b01264f701 | ||
|
|
e6e507a470 | ||
|
|
2b60eaf462 | ||
|
|
bf892aa5dc | ||
|
|
1bd834924a | ||
|
|
e854161da9 | ||
|
|
018b4af8d3 | ||
|
|
903696726c | ||
|
|
0a82c28fbf | ||
|
|
70d8b6824c | ||
|
|
c4e7747fd1 | ||
|
|
661b8791b3 | ||
|
|
c9a30a23ec | ||
|
|
28fde37f93 | ||
|
|
3797342a93 | ||
|
|
7faa0d6e65 | ||
|
|
23455750fa | ||
|
|
7cabae7ef5 | ||
|
|
7baf5c1d9a | ||
|
|
36780509a9 | ||
|
|
48c3aa7656 | ||
|
|
a951ebc3ed | ||
|
|
8ea48f7c81 | ||
|
|
a633225ad2 | ||
|
|
6b327e3ab3 | ||
|
|
cd43a9feb8 | ||
|
|
cf641476bf | ||
|
|
462a91b122 | ||
|
|
67a8b4eb80 | ||
|
|
e59f349a66 | ||
|
|
031f1abd5d | ||
|
|
73e478c50c | ||
|
|
14199ee4ea | ||
|
|
a9ec926ffb | ||
|
|
9f089bad75 | ||
|
|
8071fe04bc | ||
|
|
47a401b66e | ||
|
|
ddd2684d60 | ||
|
|
784ba7fc23 | ||
|
|
4bcc23641a | ||
|
|
dabb48fb61 | ||
|
|
9aea9aab50 | ||
|
|
b4aed240a7 | ||
|
|
015ab649af | ||
|
|
4cac16093f | ||
|
|
b77d9d4125 | ||
|
|
ac26d58b97 | ||
|
|
ed7b2ef4ef | ||
|
|
5fe5750130 | ||
|
|
314fff587c | ||
|
|
8318981f4e | ||
|
|
365e2fdb6b | ||
|
|
7e196785d8 | ||
|
|
5ef1c7e3bd | ||
|
|
11bdff9132 | ||
|
|
060802c8f1 | ||
|
|
312aeea39d | ||
|
|
9dbc6d4d8f | ||
|
|
33c3ef64e9 | ||
|
|
8c1a138621 | ||
|
|
4f239d035e | ||
|
|
7fa93a8de0 | ||
|
|
84e76e2bd7 | ||
|
|
2021a2e52b | ||
|
|
e1f777e33a | ||
|
|
166f8f6c3a | ||
|
|
3ace3e2b63 | ||
|
|
8c52145c7b | ||
|
|
6e3b496f86 | ||
|
|
88c8cb424b | ||
|
|
74179ab6e9 | ||
|
|
f6fefc9a69 | ||
|
|
601f91e5a7 | ||
|
|
d553542206 | ||
|
|
3bacfc3361 | ||
|
|
45a6cd3606 | ||
|
|
577017bd0c | ||
|
|
9787d1686f | ||
|
|
108f236874 | ||
|
|
d47fcda0fe | ||
|
|
5d21ba2648 | ||
|
|
1b730062b7 | ||
|
|
4841e42286 | ||
|
|
df1c227f2c | ||
|
|
6fb8a4bbe9 | ||
|
|
5c605e15dd | ||
|
|
9983269662 | ||
|
|
d891c7a325 | ||
|
|
06008fcf6c | ||
|
|
15c4a3e9a5 | ||
|
|
89f1fc8282 | ||
|
|
ca083166a1 | ||
|
|
ed177396b2 | ||
|
|
6dd901880e | ||
|
|
9771ea8f2d | ||
|
|
5cf41bdce0 | ||
|
|
f28fafed8d | ||
|
|
81e2c31dd3 | ||
|
|
d9b577d944 | ||
|
|
324a6da0d5 | ||
|
|
391fecadd0 | ||
|
|
4d0032441f | ||
|
|
8e59460845 | ||
|
|
4b6c676992 | ||
|
|
0237cc1ca4 | ||
|
|
fbef1ca69a | ||
|
|
3b4bfaa319 | ||
|
|
2b6a986c4a | ||
|
|
494827299c | ||
|
|
a920e89ea2 | ||
|
|
6a4b7a1ac7 | ||
|
|
621c73ab03 | ||
|
|
ac7003124e | ||
|
|
4208c3551a | ||
|
|
1938c93faf | ||
|
|
2a070ef55f | ||
|
|
0c17e8deb3 | ||
|
|
22b4b66cff | ||
|
|
942d062506 | ||
|
|
7c3b8954fe | ||
|
|
bf90056c61 | ||
|
|
bcd79bc250 | ||
|
|
07c7ef22cf | ||
|
|
6d420662e1 | ||
|
|
fca458687e | ||
|
|
e34620e1e8 | ||
|
|
bcdb49ffff | ||
|
|
ebb71c7724 | ||
|
|
b8dca3db32 | ||
|
|
94d704730f | ||
|
|
0c76ed2c4c | ||
|
|
8bac9f687e | ||
|
|
56b597d45f | ||
|
|
96b83e3eb3 | ||
|
|
e21ad6a6ba | ||
|
|
7fe4b99cef | ||
|
|
0c8dd1d8cf | ||
|
|
cafc6103ea | ||
|
|
6d3feaf81c | ||
|
|
c8d8b50d47 | ||
|
|
0c89d90d56 | ||
|
|
d0fc09fc01 | ||
|
|
c8b6d1e69c | ||
|
|
49d719770c | ||
|
|
c75a1c9e1e | ||
|
|
da7b0e9597 | ||
|
|
8f1243850f | ||
|
|
b931e282f2 | ||
|
|
66df8031ed | ||
|
|
966f82e38c | ||
|
|
118cbd9224 | ||
|
|
def585fa04 | ||
|
|
56a3dfd41a | ||
|
|
cc0f4c43ae | ||
|
|
c341148009 | ||
|
|
9089e5d334 | ||
|
|
720a82b3da | ||
|
|
a83d3a12fb | ||
|
|
1dae9f8beb | ||
|
|
e25d6e4d0b | ||
|
|
c01c098f7e | ||
|
|
fecfe9d791 | ||
|
|
b996c4dcf6 | ||
|
|
273efbcb65 | ||
|
|
7d177ca049 | ||
|
|
83f46ffd6c | ||
|
|
03e43ba839 | ||
|
|
71dfc0e1eb | ||
|
|
355b71cacf | ||
|
|
c660440996 | ||
|
|
70033017fd | ||
|
|
b9c08a58ad | ||
|
|
4db69760c6 | ||
|
|
8ec0731ca2 | ||
|
|
25b1957dbf | ||
|
|
f2dbc26491 | ||
|
|
fd11a09882 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -25,6 +25,6 @@ emsesp
|
||||
/data/www
|
||||
/lib/framework/WWWData.h
|
||||
/interface/build
|
||||
/interface/node_modules
|
||||
node_modules
|
||||
/interface/.eslintcache
|
||||
|
||||
|
||||
89
CHANGELOG.md
89
CHANGELOG.md
@@ -5,12 +5,92 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [3.1.0] May 4 2021
|
||||
|
||||
- Mock API to simulate an ESP, for testing web
|
||||
- Able to write values from the Web UI
|
||||
- check values with `"cmd":<valuename>` and data empty or `?`
|
||||
- set hc for values and commands by id or prefix `hc<x>`+separator, separator can be any char
|
||||
|
||||
## Fixed
|
||||
|
||||
- Don't create Home Assistant MQTT discovery entries for device values that don't exists (#756 on EMS-ESP repo)
|
||||
- Update shower MQTT when a shower start is detected
|
||||
- S32 board profile
|
||||
|
||||
## Changed
|
||||
|
||||
- Icon for Network
|
||||
- MQTT Formatting payload (nested vs single) is a pull-down option
|
||||
- moved mqtt-topics and texts to local_EN, all topics lower case
|
||||
- Re-enabled Shower Alert (still experimental)
|
||||
- lowercased Flow temp in commands
|
||||
- system console commands to main
|
||||
|
||||
## Removed
|
||||
|
||||
## [3.0.1] March 30 2021
|
||||
|
||||
## Added
|
||||
|
||||
- power settings, disabling BLE and turning off Wifi sleep
|
||||
- Rx and Tx counts to Heartbeat MQTT payload
|
||||
- ethernet support
|
||||
- id to info command to show only a heatingcircuit
|
||||
- add sending devices that are not listed to 0x07
|
||||
- extra MQTT boolean option for "ON" and "OFF"
|
||||
- support for chunked MQTT payloads to allow large data sets > 2kb
|
||||
- external Button support (#708) for resetting to factory defaults and other actions
|
||||
- new console set command in `system`, `set board_profile <profile>` for quickly enabling cabled ethernet connections without using the captive wifi portal
|
||||
- added in MQTT nested mode, for thermostat and mixer, like we had back in v2
|
||||
- cascade MC400 (product-id 210) (3.0.0b6), power values for heating sources (3.0.1b1)
|
||||
- values for wwMaxPower, wwFlowtempOffset
|
||||
- RC300 `thermostat temp -1` to clear temporary setpoint in auto mode
|
||||
- syslog port selectable (#744)
|
||||
- individual mqtt commands (#31)
|
||||
- board Profiles (#11)
|
||||
|
||||
## Fixed
|
||||
|
||||
- telegrams matched to masterthermostat 0x18
|
||||
- multiple roomcontrollers
|
||||
- readback after write with delay (give ems-devices time to set the value)
|
||||
- thermostat ES72/RC20 device 66 to command-set RC20_2
|
||||
- MQTT payloads not adding to queue when MQTT is re-connecting (fixes #369)
|
||||
- fix for HA topics with invalid command formats (#728)
|
||||
- wrong position of values #723, #732
|
||||
- OTA Upload via Web on OSX
|
||||
- Rx and Tx quality % would sometimes show > 100
|
||||
|
||||
## Changed
|
||||
|
||||
- changed how telegram parameters are rendered for mqtt, console and web (#632)
|
||||
- split `show values` in smaller packages (edited)
|
||||
- extended length of IP/hostname from 32 to 48 chars (#676)
|
||||
- check flowsensor for `tap_water_active`
|
||||
- mqtt prefixed with `Base`
|
||||
- count Dallas sensor fails
|
||||
- switch from SPIFFS to LITTLEFS
|
||||
- added ID to MQTT payloads which is the Device's product ID and used in HA to identify a unique HA device
|
||||
- increased MQTT buffer and reduced wait time between publishes
|
||||
- updated to the latest ArduinoJson library
|
||||
- some names of mqtt-tags like in v2.2.1
|
||||
- new ESP32 partition side to allow for smoother OTA and fallback
|
||||
- network Gateway IP is optional (#682)emsesp/EMS-ESP
|
||||
- moved to a new GitHub repo https://github.com/emsesp/EMS-ESP32
|
||||
- invert LED changed to Hide LED. Default is off.
|
||||
- renamed Scan Network to Scan WiFi Network
|
||||
- added version to cmd=settings
|
||||
- Allow both WiFi and Ethernet together, fall back to AP when Ethernet disconnects
|
||||
|
||||
## Removed
|
||||
|
||||
- Shower Alert (disabled for now)
|
||||
|
||||
## [3.0.0] March 18 2021
|
||||
|
||||
## **ESP32 version based off ESP-ESP v2.1**
|
||||
|
||||
|
||||
### Added
|
||||
|
||||
- Power settings, disabling BLE and turning off Wifi sleep
|
||||
- Rx and Tx counts to Heartbeat MQTT payload
|
||||
- Ethernet support
|
||||
@@ -27,6 +107,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Syslog port selectable (#744)
|
||||
|
||||
### Fixed
|
||||
|
||||
- telegrams matched to masterthermostat 0x18
|
||||
- multiple roomcontrollers
|
||||
- readback after write with delay (give ems-devices time to set the value)
|
||||
@@ -37,6 +118,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- OTA Upload via Web on OSX
|
||||
|
||||
### Changed
|
||||
|
||||
- changed how telegram parameters are rendered for mqtt, console and web (#632)
|
||||
- split `show values` in smaller packages (edited)
|
||||
- extended length of IP/hostname from 32 to 48 chars (#676)
|
||||
@@ -51,4 +133,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- new ESP32 partition side to allow for smoother OTA and fallback
|
||||
- Network Gateway IP is optional (#682)emsesp/EMS-ESP
|
||||
- moved to a new GitHub repo https://github.com/emsesp/EMS-ESP32
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
### Added
|
||||
## Added
|
||||
|
||||
## Fixed
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
### Removed
|
||||
## Changed
|
||||
|
||||
## Removed
|
||||
|
||||
@@ -2,7 +2,13 @@
|
||||
|
||||
**EMS-ESP** is an open-source firmware for the Espressif ESP8266 and ESP32 microcontroller that communicates with **EMS** (Energy Management System) based equipment from manufacturers like Bosch, Buderus, Nefit, Junkers, Worcester and Sieger.
|
||||
|
||||
This is the firmware for the ESP32.
|
||||
This is the firmware for the ESP32. Compared to version 2 on the ESP8266, this version has
|
||||
- Ethernet Support
|
||||
- Pre-configured board layouts
|
||||
- Writing values directly from the Web UI
|
||||
- Mock API server for faster offline development
|
||||
- Expose to more commands, via MQTT
|
||||
- Improvements to Dallas sensors, Shower service
|
||||
|
||||
[](https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md)
|
||||
[](https://github.com/emsesp/EMS-ESP32/commits/main)
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
# Change the IP address to that of your ESP device to enable local development of the UI.
|
||||
# Remember to also enable CORS in platformio.ini before uploading the code to the device.
|
||||
# Change the IP address to that of your ESP device to enable local development of the UI
|
||||
|
||||
# ESP32 dev
|
||||
REACT_APP_HTTP_ROOT=http://10.10.10.101
|
||||
REACT_APP_WEB_SOCKET_ROOT=ws://10.10.10.101
|
||||
# REACT_APP_HTTP_ROOT=http://localhost:3000
|
||||
# REACT_APP_WEB_SOCKET_ROOT=ws://localhost:3000
|
||||
|
||||
# ESP8266 dev
|
||||
#REACT_APP_HTTP_ROOT=http://10.10.10.140
|
||||
#REACT_APP_WEB_SOCKET_ROOT=ws://10.10.10.140
|
||||
|
||||
@@ -9,7 +9,7 @@ const fs = require('fs');
|
||||
|
||||
module.exports = function override(config, env) {
|
||||
if (env === "production") {
|
||||
// rename the ouput file, we need it's path to be short, for SPIFFS
|
||||
// rename the output file, we need it's path to be short for LittleFS
|
||||
config.output.filename = 'js/[id].[chunkhash:4].js';
|
||||
config.output.chunkFilename = 'js/[id].[chunkhash:4].js';
|
||||
|
||||
|
||||
26443
interface/package-lock.json
generated
26443
interface/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,37 +3,40 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.11.3",
|
||||
"@material-ui/core": "^4.11.4",
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"@types/lodash": "^4.14.168",
|
||||
"@types/node": "^12.20.4",
|
||||
"@types/react": "^17.0.3",
|
||||
"@types/react-dom": "^17.0.1",
|
||||
"@types/node": "^15.0.1",
|
||||
"@types/react": "^17.0.4",
|
||||
"@types/react-dom": "^17.0.3",
|
||||
"@types/react-material-ui-form-validator": "^2.1.0",
|
||||
"@types/react-router": "^5.1.12",
|
||||
"@types/react-router-dom": "^5.1.6",
|
||||
"compression-webpack-plugin": "^4.0.0",
|
||||
"@types/react-router": "^5.1.13",
|
||||
"@types/react-router-dom": "^5.1.7",
|
||||
"compression-webpack-plugin": "^5.0.2",
|
||||
"express": "^4.17.1",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"lodash": "^4.17.21",
|
||||
"mime-types": "^2.1.29",
|
||||
"notistack": "^1.0.5",
|
||||
"parse-ms": "^2.1.0",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-dropzone": "^11.3.1",
|
||||
"mime-types": "^2.1.30",
|
||||
"notistack": "^1.0.6",
|
||||
"parse-ms": "^3.0.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-dropzone": "^11.3.2",
|
||||
"react-form-validator-core": "^1.1.1",
|
||||
"react-material-ui-form-validator": "^2.1.4",
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "4.0.1",
|
||||
"react-scripts": "4.0.3",
|
||||
"sockette": "^2.0.6",
|
||||
"typescript": "4.0.5",
|
||||
"typescript": "4.2.4",
|
||||
"zlib": "^1.0.5"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-app-rewired start",
|
||||
"build": "react-app-rewired build",
|
||||
"eject": "react-scripts eject"
|
||||
"eject": "react-scripts eject",
|
||||
"mock-api": "nodemon --watch ../mock-api ../mock-api/server.js",
|
||||
"dev": "run-p start mock-api"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
@@ -51,6 +54,10 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^6.0.1",
|
||||
"http-proxy-middleware": "^1.1.1",
|
||||
"nodemon": "^2.0.7",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"react-app-rewired": "^2.1.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class App extends Component {
|
||||
render() {
|
||||
return (
|
||||
<CustomMuiTheme>
|
||||
<SnackbarProvider maxSnack={3} anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
|
||||
<SnackbarProvider autoHideDuration={3000} maxSnack={3} anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
|
||||
ref={this.notistackRef}
|
||||
action={(key) => (
|
||||
<IconButton onClick={this.onClickDismiss(key)} size="small">
|
||||
|
||||
@@ -30,7 +30,7 @@ class APSettingsForm extends React.Component<APSettingsFormProps> {
|
||||
onChange={handleValueChange('provision_mode')}
|
||||
margin="normal">
|
||||
<MenuItem value={APProvisionMode.AP_MODE_ALWAYS}>Always</MenuItem>
|
||||
<MenuItem value={APProvisionMode.AP_MODE_DISCONNECTED}>When WiFi Disconnected</MenuItem>
|
||||
<MenuItem value={APProvisionMode.AP_MODE_DISCONNECTED}>When Network Disconnected</MenuItem>
|
||||
<MenuItem value={APProvisionMode.AP_NEVER}>Never</MenuItem>
|
||||
</SelectValidator>
|
||||
{
|
||||
|
||||
@@ -18,5 +18,6 @@ export const SYSTEM_STATUS_ENDPOINT = ENDPOINT_ROOT + "systemStatus";
|
||||
export const SIGN_IN_ENDPOINT = ENDPOINT_ROOT + "signIn";
|
||||
export const VERIFY_AUTHORIZATION_ENDPOINT = ENDPOINT_ROOT + "verifyAuthorization";
|
||||
export const SECURITY_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "securitySettings";
|
||||
export const GENERATE_TOKEN_ENDPOINT = ENDPOINT_ROOT + "generateToken";
|
||||
export const RESTART_ENDPOINT = ENDPOINT_ROOT + "restart";
|
||||
export const FACTORY_RESET_ENDPOINT = ENDPOINT_ROOT + "factoryReset";
|
||||
|
||||
@@ -5,10 +5,8 @@ import { withSnackbar, WithSnackbarProps } from 'notistack';
|
||||
import * as Authentication from './Authentication';
|
||||
import { withAuthenticationContext, AuthenticationContextProps, AuthenticatedContext, AuthenticatedContextValue } from './AuthenticationContext';
|
||||
|
||||
type ChildComponent = React.ComponentType<RouteComponentProps<any>> | React.ComponentType<any>;
|
||||
|
||||
interface AuthenticatedRouteProps extends RouteProps, WithSnackbarProps, AuthenticationContextProps {
|
||||
component: ChildComponent;
|
||||
component: React.ComponentType<RouteComponentProps<any>> | React.ComponentType<any>;
|
||||
}
|
||||
|
||||
type RenderComponent = (props: RouteComponentProps<any>) => React.ReactNode;
|
||||
@@ -27,7 +25,7 @@ export class AuthenticatedRoute extends React.Component<AuthenticatedRouteProps>
|
||||
);
|
||||
}
|
||||
Authentication.storeLoginRedirect(location);
|
||||
enqueueSnackbar("Please sign in to continue.", { variant: 'info' });
|
||||
enqueueSnackbar("Please sign in to continue", { variant: 'info' });
|
||||
return (
|
||||
<Redirect to='/' />
|
||||
);
|
||||
|
||||
@@ -101,7 +101,7 @@ class AuthenticationWrapper extends React.Component<AuthenticationWrapperProps,
|
||||
me: undefined
|
||||
}
|
||||
});
|
||||
this.props.enqueueSnackbar("You have signed out.", { variant: 'success', });
|
||||
this.props.enqueueSnackbar("You have signed out", { variant: 'success', });
|
||||
history.push('/');
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,9 @@ class UnauthenticatedRoute extends Route<UnauthenticatedRouteProps> {
|
||||
if (authenticationContext.me) {
|
||||
return (<Redirect to={Authentication.fetchLoginRedirect(features)} />);
|
||||
}
|
||||
return (<Component {...props} />);
|
||||
if (Component) {
|
||||
return (<Component {...props} />);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Route {...rest} render={renderComponent} />
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Card, CardContent, CardActions } from '@material-ui/core';
|
||||
|
||||
import { withStyles, createStyles, Theme, WithTheme, WithStyles, withTheme } from '@material-ui/core/styles';
|
||||
|
||||
import WifiIcon from '@material-ui/icons/Wifi';
|
||||
import SettingsEthernetIcon from '@material-ui/icons/SettingsEthernet';
|
||||
import SettingsIcon from '@material-ui/icons/Settings';
|
||||
import AccessTimeIcon from '@material-ui/icons/AccessTime';
|
||||
import AccountCircleIcon from '@material-ui/icons/AccountCircle';
|
||||
@@ -146,7 +146,7 @@ class MenuAppBar extends React.Component<MenuAppBarProps, MenuAppBarState> {
|
||||
<List>
|
||||
<ListItem to='/network/' selected={path.startsWith('/network/')} button component={Link}>
|
||||
<ListItemIcon>
|
||||
<WifiIcon />
|
||||
<SettingsEthernetIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Network Connection" />
|
||||
</ListItem>
|
||||
|
||||
@@ -1,22 +1,31 @@
|
||||
import React from 'react';
|
||||
import { TextValidator, ValidatorForm, SelectValidator } from 'react-material-ui-form-validator';
|
||||
import React from "react";
|
||||
import {
|
||||
TextValidator,
|
||||
ValidatorForm,
|
||||
SelectValidator,
|
||||
} from "react-material-ui-form-validator";
|
||||
|
||||
import { Checkbox, TextField, Typography } from '@material-ui/core';
|
||||
import SaveIcon from '@material-ui/icons/Save';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import { Checkbox, TextField, Typography } from "@material-ui/core";
|
||||
import SaveIcon from "@material-ui/icons/Save";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
|
||||
import { RestFormProps, FormActions, FormButton, BlockFormControlLabel, PasswordValidator } from '../components';
|
||||
import { isIP, isHostname, or, isPath } from '../validators';
|
||||
import {
|
||||
RestFormProps,
|
||||
FormActions,
|
||||
FormButton,
|
||||
BlockFormControlLabel,
|
||||
PasswordValidator,
|
||||
} from "../components";
|
||||
import { isIP, isHostname, or, isPath } from "../validators";
|
||||
|
||||
import { MqttSettings } from './types';
|
||||
import { MqttSettings } from "./types";
|
||||
|
||||
type MqttSettingsFormProps = RestFormProps<MqttSettings>;
|
||||
|
||||
class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
|
||||
|
||||
componentDidMount() {
|
||||
ValidatorForm.addValidationRule('isIPOrHostname', or(isIP, isHostname));
|
||||
ValidatorForm.addValidationRule('isPath', isPath);
|
||||
ValidatorForm.addValidationRule("isIPOrHostname", or(isIP, isHostname));
|
||||
ValidatorForm.addValidationRule("isPath", isPath);
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -27,44 +36,57 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.enabled}
|
||||
onChange={handleValueChange('enabled')}
|
||||
onChange={handleValueChange("enabled")}
|
||||
value="enabled"
|
||||
/>
|
||||
}
|
||||
label="Enable MQTT"
|
||||
/>
|
||||
<TextValidator
|
||||
validators={['required', 'isIPOrHostname']}
|
||||
errorMessages={['Host is required', "Not a valid IP address or hostname"]}
|
||||
validators={["required", "isIPOrHostname"]}
|
||||
errorMessages={[
|
||||
"Host is required",
|
||||
"Not a valid IP address or hostname",
|
||||
]}
|
||||
name="host"
|
||||
label="Host"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.host}
|
||||
onChange={handleValueChange('host')}
|
||||
onChange={handleValueChange("host")}
|
||||
margin="normal"
|
||||
/>
|
||||
<TextValidator
|
||||
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']}
|
||||
errorMessages={['Port is required', "Must be a number", "Must be greater than 0 ", "Max value is 65535"]}
|
||||
validators={[
|
||||
"required",
|
||||
"isNumber",
|
||||
"minNumber:0",
|
||||
"maxNumber:65535",
|
||||
]}
|
||||
errorMessages={[
|
||||
"Port is required",
|
||||
"Must be a number",
|
||||
"Must be greater than 0 ",
|
||||
"Max value is 65535",
|
||||
]}
|
||||
name="port"
|
||||
label="Port"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.port}
|
||||
type="number"
|
||||
onChange={handleValueChange('port')}
|
||||
onChange={handleValueChange("port")}
|
||||
margin="normal"
|
||||
/>
|
||||
<TextValidator
|
||||
validators={['required', 'isPath']}
|
||||
errorMessages={['Base is required', "Not a valid Path"]}
|
||||
validators={["required", "isPath"]}
|
||||
errorMessages={["Base is required", "Not a valid Path"]}
|
||||
name="base"
|
||||
label="Base"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.base}
|
||||
onChange={handleValueChange('base')}
|
||||
onChange={handleValueChange("base")}
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
@@ -73,7 +95,7 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.username}
|
||||
onChange={handleValueChange('username')}
|
||||
onChange={handleValueChange("username")}
|
||||
margin="normal"
|
||||
/>
|
||||
<PasswordValidator
|
||||
@@ -82,7 +104,7 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.password}
|
||||
onChange={handleValueChange('password')}
|
||||
onChange={handleValueChange("password")}
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
@@ -91,28 +113,40 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.client_id}
|
||||
onChange={handleValueChange('client_id')}
|
||||
onChange={handleValueChange("client_id")}
|
||||
margin="normal"
|
||||
/>
|
||||
<TextValidator
|
||||
validators={['required', 'isNumber', 'minNumber:1', 'maxNumber:65535']}
|
||||
errorMessages={['Keep alive is required', "Must be a number", "Must be greater than 0", "Max value is 65535"]}
|
||||
validators={[
|
||||
"required",
|
||||
"isNumber",
|
||||
"minNumber:1",
|
||||
"maxNumber:65535",
|
||||
]}
|
||||
errorMessages={[
|
||||
"Keep alive is required",
|
||||
"Must be a number",
|
||||
"Must be greater than 0",
|
||||
"Max value is 65535",
|
||||
]}
|
||||
name="keep_alive"
|
||||
label="Keep Alive (seconds)"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.keep_alive}
|
||||
type="number"
|
||||
onChange={handleValueChange('keep_alive')}
|
||||
onChange={handleValueChange("keep_alive")}
|
||||
margin="normal"
|
||||
/>
|
||||
<SelectValidator name="mqtt_qos"
|
||||
<SelectValidator
|
||||
name="mqtt_qos"
|
||||
label="QoS"
|
||||
value={data.mqtt_qos}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onChange={handleValueChange('mqtt_qos')}
|
||||
margin="normal">
|
||||
onChange={handleValueChange("mqtt_qos")}
|
||||
margin="normal"
|
||||
>
|
||||
<MenuItem value={0}>0 (default)</MenuItem>
|
||||
<MenuItem value={1}>1</MenuItem>
|
||||
<MenuItem value={2}>2</MenuItem>
|
||||
@@ -121,7 +155,7 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.clean_session}
|
||||
onChange={handleValueChange('clean_session')}
|
||||
onChange={handleValueChange("clean_session")}
|
||||
value="clean_session"
|
||||
/>
|
||||
}
|
||||
@@ -131,149 +165,235 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.mqtt_retain}
|
||||
onChange={handleValueChange('mqtt_retain')}
|
||||
onChange={handleValueChange("mqtt_retain")}
|
||||
value="mqtt_retain"
|
||||
/>
|
||||
}
|
||||
label="Retain Flag"
|
||||
/>
|
||||
<br></br>
|
||||
<Typography variant="h6" color="primary" >
|
||||
<Typography variant="h6" color="primary">
|
||||
Formatting
|
||||
</Typography>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.nested_format}
|
||||
onChange={handleValueChange('nested_format')}
|
||||
value="nested_format"
|
||||
/>
|
||||
}
|
||||
label="Nested format (Thermostat & Mixer only)"
|
||||
/>
|
||||
<SelectValidator name="dallas_format"
|
||||
<SelectValidator
|
||||
name="nested_format"
|
||||
label="Topic/Payload Format"
|
||||
value={data.nested_format}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onChange={handleValueChange("nested_format")}
|
||||
margin="normal"
|
||||
>
|
||||
<MenuItem value={1}>nested on a single topic</MenuItem>
|
||||
<MenuItem value={2}>as individual topics</MenuItem>
|
||||
</SelectValidator>
|
||||
<SelectValidator
|
||||
name="dallas_format"
|
||||
label="Dallas Sensor Payload Grouping"
|
||||
value={data.dallas_format}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onChange={handleValueChange('dallas_format')}
|
||||
margin="normal">
|
||||
onChange={handleValueChange("dallas_format")}
|
||||
margin="normal"
|
||||
>
|
||||
<MenuItem value={1}>by Sensor ID</MenuItem>
|
||||
<MenuItem value={2}>by Number</MenuItem>
|
||||
</SelectValidator>
|
||||
<SelectValidator name="bool_format"
|
||||
<SelectValidator
|
||||
name="bool_format"
|
||||
label="Boolean Format"
|
||||
value={data.bool_format}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onChange={handleValueChange('bool_format')}
|
||||
margin="normal">
|
||||
onChange={handleValueChange("bool_format")}
|
||||
margin="normal"
|
||||
>
|
||||
<MenuItem value={1}>"on"/"off"</MenuItem>
|
||||
<MenuItem value={2}>true/false</MenuItem>
|
||||
<MenuItem value={3}>1/0</MenuItem>
|
||||
<MenuItem value={4}>"ON"/"OFF"</MenuItem>
|
||||
</SelectValidator>
|
||||
<SelectValidator
|
||||
name="subscribe_format"
|
||||
label="Subscribe Format"
|
||||
value={data.subscribe_format}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onChange={handleValueChange("subscribe_format")}
|
||||
margin="normal"
|
||||
>
|
||||
<MenuItem value={0}>general device topic</MenuItem>
|
||||
<MenuItem value={1}>individual topics, main heating circuit</MenuItem>
|
||||
<MenuItem value={2}>individual topics, all heating circuits</MenuItem>
|
||||
</SelectValidator>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.ha_enabled}
|
||||
onChange={handleValueChange('ha_enabled')}
|
||||
onChange={handleValueChange("ha_enabled")}
|
||||
value="ha_enabled"
|
||||
/>
|
||||
}
|
||||
label="Home Assistant MQTT Discovery"
|
||||
label="Use Home Assistant MQTT Discovery"
|
||||
/>
|
||||
{ data.ha_enabled &&
|
||||
<SelectValidator name="ha_climate_format"
|
||||
{data.ha_enabled && (
|
||||
<SelectValidator
|
||||
name="ha_climate_format"
|
||||
label="Thermostat Room Temperature"
|
||||
value={data.ha_climate_format}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onChange={handleValueChange('ha_climate_format')}
|
||||
margin="normal">
|
||||
onChange={handleValueChange("ha_climate_format")}
|
||||
margin="normal"
|
||||
>
|
||||
<MenuItem value={1}>use Current temperature (default)</MenuItem>
|
||||
<MenuItem value={2}>use Setpoint temperature</MenuItem>
|
||||
<MenuItem value={3}>Fix to 0</MenuItem>
|
||||
</SelectValidator>
|
||||
}
|
||||
)}
|
||||
<br></br>
|
||||
<Typography variant="h6" color="primary" >
|
||||
<Typography variant="h6" color="primary">
|
||||
Publish Intervals
|
||||
</Typography>
|
||||
<TextValidator
|
||||
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']}
|
||||
errorMessages={['Publish time is required', "Must be a number", "Must be 0 or greater", "Max value is 65535"]}
|
||||
validators={[
|
||||
"required",
|
||||
"isNumber",
|
||||
"minNumber:0",
|
||||
"maxNumber:65535",
|
||||
]}
|
||||
errorMessages={[
|
||||
"Publish time is required",
|
||||
"Must be a number",
|
||||
"Must be 0 or greater",
|
||||
"Max value is 65535",
|
||||
]}
|
||||
name="publish_time_boiler"
|
||||
label="Boiler Publish Interval (seconds, 0=on change)"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.publish_time_boiler}
|
||||
type="number"
|
||||
onChange={handleValueChange('publish_time_boiler')}
|
||||
onChange={handleValueChange("publish_time_boiler")}
|
||||
margin="normal"
|
||||
/>
|
||||
<TextValidator
|
||||
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']}
|
||||
errorMessages={['Publish time is required', "Must be a number", "Must be 0 or greater", "Max value is 65535"]}
|
||||
validators={[
|
||||
"required",
|
||||
"isNumber",
|
||||
"minNumber:0",
|
||||
"maxNumber:65535",
|
||||
]}
|
||||
errorMessages={[
|
||||
"Publish time is required",
|
||||
"Must be a number",
|
||||
"Must be 0 or greater",
|
||||
"Max value is 65535",
|
||||
]}
|
||||
name="publish_time_thermostat"
|
||||
label="Thermostat Publish Interval (seconds, 0=on change)"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.publish_time_thermostat}
|
||||
type="number"
|
||||
onChange={handleValueChange('publish_time_thermostat')}
|
||||
onChange={handleValueChange("publish_time_thermostat")}
|
||||
margin="normal"
|
||||
/>
|
||||
<TextValidator
|
||||
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']}
|
||||
errorMessages={['Publish time is required', "Must be a number", "Must be 0 or greater", "Max value is 65535"]}
|
||||
validators={[
|
||||
"required",
|
||||
"isNumber",
|
||||
"minNumber:0",
|
||||
"maxNumber:65535",
|
||||
]}
|
||||
errorMessages={[
|
||||
"Publish time is required",
|
||||
"Must be a number",
|
||||
"Must be 0 or greater",
|
||||
"Max value is 65535",
|
||||
]}
|
||||
name="publish_time_solar"
|
||||
label="Solar Publish Interval (seconds, 0=on change)"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.publish_time_solar}
|
||||
type="number"
|
||||
onChange={handleValueChange('publish_time_solar')}
|
||||
onChange={handleValueChange("publish_time_solar")}
|
||||
margin="normal"
|
||||
/>
|
||||
<TextValidator
|
||||
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']}
|
||||
errorMessages={['Publish time is required', "Must be a number", "Must be 0 or greater", "Max value is 65535"]}
|
||||
validators={[
|
||||
"required",
|
||||
"isNumber",
|
||||
"minNumber:0",
|
||||
"maxNumber:65535",
|
||||
]}
|
||||
errorMessages={[
|
||||
"Publish time is required",
|
||||
"Must be a number",
|
||||
"Must be 0 or greater",
|
||||
"Max value is 65535",
|
||||
]}
|
||||
name="publish_time_mixer"
|
||||
label="Mixer Publish Interval (seconds, 0=on change)"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.publish_time_mixer}
|
||||
type="number"
|
||||
onChange={handleValueChange('publish_time_mixer')}
|
||||
onChange={handleValueChange("publish_time_mixer")}
|
||||
margin="normal"
|
||||
/>
|
||||
<TextValidator
|
||||
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']}
|
||||
errorMessages={['Publish time is required', "Must be a number", "Must be 0 or greater", "Max value is 65535"]}
|
||||
validators={[
|
||||
"required",
|
||||
"isNumber",
|
||||
"minNumber:0",
|
||||
"maxNumber:65535",
|
||||
]}
|
||||
errorMessages={[
|
||||
"Publish time is required",
|
||||
"Must be a number",
|
||||
"Must be 0 or greater",
|
||||
"Max value is 65535",
|
||||
]}
|
||||
name="publish_time_sensor"
|
||||
label="Sensors Publish Interval (seconds, 0=on change)"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.publish_time_sensor}
|
||||
type="number"
|
||||
onChange={handleValueChange('publish_time_sensor')}
|
||||
onChange={handleValueChange("publish_time_sensor")}
|
||||
margin="normal"
|
||||
/>
|
||||
<TextValidator
|
||||
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']}
|
||||
errorMessages={['Publish time is required', "Must be a number", "Must be 0 or greater", "Max value is 65535"]}
|
||||
validators={[
|
||||
"required",
|
||||
"isNumber",
|
||||
"minNumber:0",
|
||||
"maxNumber:65535",
|
||||
]}
|
||||
errorMessages={[
|
||||
"Publish time is required",
|
||||
"Must be a number",
|
||||
"Must be 0 or greater",
|
||||
"Max value is 65535",
|
||||
]}
|
||||
name="publish_time_other"
|
||||
label="All other Modules Publish Interval (seconds, 0=on change)"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.publish_time_other}
|
||||
type="number"
|
||||
onChange={handleValueChange('publish_time_other')}
|
||||
onChange={handleValueChange("publish_time_other")}
|
||||
margin="normal"
|
||||
/>
|
||||
<FormActions>
|
||||
<FormButton startIcon={<SaveIcon />} variant="contained" color="primary" type="submit">
|
||||
<FormButton
|
||||
startIcon={<SaveIcon />}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
>
|
||||
Save
|
||||
</FormButton>
|
||||
</FormActions>
|
||||
|
||||
@@ -40,5 +40,6 @@ export interface MqttSettings {
|
||||
mqtt_retain: boolean;
|
||||
ha_enabled: boolean;
|
||||
ha_climate_format: number;
|
||||
nested_format: boolean;
|
||||
nested_format: number;
|
||||
subscribe_format: number;
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ class NetworkConnection extends Component<NetworkConnectionProps, NetworkConnect
|
||||
<MenuAppBar sectionTitle="Network Connection">
|
||||
<Tabs value={this.props.match.url} onChange={this.handleTabChange} variant="fullWidth">
|
||||
<Tab value="/network/status" label="Network Status" />
|
||||
<Tab value="/network/scan" label="Scan Networks" disabled={!authenticatedContext.me.admin} />
|
||||
<Tab value="/network/scan" label="Scan WiFi Networks" disabled={!authenticatedContext.me.admin} />
|
||||
<Tab value="/network/settings" label="Network Settings" disabled={!authenticatedContext.me.admin} />
|
||||
</Tabs>
|
||||
<Switch>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { TextValidator, SelectValidator, ValidatorForm } from 'react-material-ui-form-validator';
|
||||
import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator';
|
||||
|
||||
import { Checkbox, List, ListItem, ListItemText, ListItemAvatar, ListItemSecondaryAction } from '@material-ui/core';
|
||||
|
||||
@@ -9,7 +9,6 @@ import LockIcon from '@material-ui/icons/Lock';
|
||||
import LockOpenIcon from '@material-ui/icons/LockOpen';
|
||||
import DeleteIcon from '@material-ui/icons/Delete';
|
||||
import SaveIcon from '@material-ui/icons/Save';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
|
||||
import { RestFormProps, PasswordValidator, BlockFormControlLabel, FormActions, FormButton } from '../components';
|
||||
import { isIP, isHostname, optional } from '../validators';
|
||||
@@ -34,7 +33,6 @@ class NetworkSettingsForm extends React.Component<NetworkStatusFormProps> {
|
||||
ssid: selectedNetwork.ssid,
|
||||
password: "",
|
||||
hostname: props.data.hostname,
|
||||
ethernet_profile: 0,
|
||||
static_ip_config: false,
|
||||
}
|
||||
props.setData(networkSettings);
|
||||
@@ -86,7 +84,7 @@ class NetworkSettingsForm extends React.Component<NetworkStatusFormProps> {
|
||||
validators={['matchRegexp:^.{0,32}$']}
|
||||
errorMessages={['SSID must be 32 characters or less']}
|
||||
name="ssid"
|
||||
label="SSID"
|
||||
label="SSID (leave blank to disable WiFi)"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.ssid}
|
||||
@@ -119,17 +117,6 @@ class NetworkSettingsForm extends React.Component<NetworkStatusFormProps> {
|
||||
onChange={handleValueChange('hostname')}
|
||||
margin="normal"
|
||||
/>
|
||||
<SelectValidator name="ems_bus_id"
|
||||
label="Ethernet Profile"
|
||||
value={data.ethernet_profile}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onChange={handleValueChange('ethernet_profile')}
|
||||
margin="normal">
|
||||
<MenuItem value={0}>None (wifi only)</MenuItem>
|
||||
<MenuItem value={1}>Profile 1 (LAN8720)</MenuItem>
|
||||
<MenuItem value={2}>Profile 2 (TLK110)</MenuItem>
|
||||
</SelectValidator>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
import { Theme } from '@material-ui/core';
|
||||
import { NetworkStatus, NetworkConnectionStatus } from './types';
|
||||
import { Theme } from "@material-ui/core";
|
||||
import { NetworkStatus, NetworkConnectionStatus } from "./types";
|
||||
|
||||
export const isConnected = ({ status }: NetworkStatus) => {
|
||||
return ((status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED) || (status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED));
|
||||
}
|
||||
return (
|
||||
status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED ||
|
||||
status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED
|
||||
);
|
||||
};
|
||||
|
||||
export const isWiFi = ({ status }: NetworkStatus) => (status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED)
|
||||
export const isWiFi = ({ status }: NetworkStatus) =>
|
||||
status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED;
|
||||
export const isEthernet = ({ status }: NetworkStatus) =>
|
||||
status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED;
|
||||
|
||||
export const networkStatusHighlight = ({ status }: NetworkStatus, theme: Theme) => {
|
||||
export const networkStatusHighlight = (
|
||||
{ status }: NetworkStatus,
|
||||
theme: Theme
|
||||
) => {
|
||||
switch (status) {
|
||||
case NetworkConnectionStatus.WIFI_STATUS_IDLE:
|
||||
case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED:
|
||||
@@ -22,7 +31,7 @@ export const networkStatusHighlight = ({ status }: NetworkStatus, theme: Theme)
|
||||
default:
|
||||
return theme.palette.warning.main;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const networkStatus = ({ status }: NetworkStatus) => {
|
||||
switch (status) {
|
||||
@@ -45,4 +54,4 @@ export const networkStatus = ({ status }: NetworkStatus) => {
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,45 +1,64 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import React, { Component, Fragment } from "react";
|
||||
|
||||
import { WithTheme, withTheme } from '@material-ui/core/styles';
|
||||
import { Avatar, Divider, List, ListItem, ListItemAvatar, ListItemText } from '@material-ui/core';
|
||||
import { WithTheme, withTheme } from "@material-ui/core/styles";
|
||||
import {
|
||||
Avatar,
|
||||
Divider,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemText,
|
||||
} from "@material-ui/core";
|
||||
|
||||
import DNSIcon from '@material-ui/icons/Dns';
|
||||
import WifiIcon from '@material-ui/icons/Wifi';
|
||||
import SettingsInputComponentIcon from '@material-ui/icons/SettingsInputComponent';
|
||||
import SettingsInputAntennaIcon from '@material-ui/icons/SettingsInputAntenna';
|
||||
import DeviceHubIcon from '@material-ui/icons/DeviceHub';
|
||||
import RefreshIcon from '@material-ui/icons/Refresh';
|
||||
import DNSIcon from "@material-ui/icons/Dns";
|
||||
import WifiIcon from "@material-ui/icons/Wifi";
|
||||
import RouterIcon from "@material-ui/icons/Router";
|
||||
import SettingsInputComponentIcon from "@material-ui/icons/SettingsInputComponent";
|
||||
import SettingsInputAntennaIcon from "@material-ui/icons/SettingsInputAntenna";
|
||||
import DeviceHubIcon from "@material-ui/icons/DeviceHub";
|
||||
import RefreshIcon from "@material-ui/icons/Refresh";
|
||||
|
||||
import { RestFormProps, FormActions, FormButton, HighlightAvatar } from '../components';
|
||||
import { networkStatus, networkStatusHighlight, isConnected, isWiFi } from './NetworkStatus';
|
||||
import { NetworkStatus } from './types';
|
||||
import {
|
||||
RestFormProps,
|
||||
FormActions,
|
||||
FormButton,
|
||||
HighlightAvatar,
|
||||
} from "../components";
|
||||
import {
|
||||
networkStatus,
|
||||
networkStatusHighlight,
|
||||
isConnected,
|
||||
isWiFi,
|
||||
isEthernet,
|
||||
} from "./NetworkStatus";
|
||||
import { NetworkStatus } from "./types";
|
||||
|
||||
type NetworkStatusFormProps = RestFormProps<NetworkStatus> & WithTheme;
|
||||
|
||||
class NetworkStatusForm extends Component<NetworkStatusFormProps> {
|
||||
|
||||
dnsServers(status: NetworkStatus) {
|
||||
if (!status.dns_ip_1) {
|
||||
return "none";
|
||||
}
|
||||
return status.dns_ip_1 + (status.dns_ip_2 ? ',' + status.dns_ip_2 : '');
|
||||
return status.dns_ip_1 + (status.dns_ip_2 ? "," + status.dns_ip_2 : "");
|
||||
}
|
||||
|
||||
createListItems() {
|
||||
const { data, theme } = this.props
|
||||
const { data, theme } = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<HighlightAvatar color={networkStatusHighlight(data, theme)}>
|
||||
<WifiIcon />
|
||||
{isWiFi(data) && <WifiIcon />}
|
||||
{isEthernet(data) && <RouterIcon />}
|
||||
</HighlightAvatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Status" secondary={networkStatus(data)} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
{
|
||||
isWiFi(data) &&
|
||||
{isWiFi(data) && (
|
||||
<Fragment>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
@@ -51,8 +70,8 @@ class NetworkStatusForm extends Component<NetworkStatusFormProps> {
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
</Fragment>
|
||||
}
|
||||
{ isConnected(data) &&
|
||||
)}
|
||||
{isConnected(data) && (
|
||||
<Fragment>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
@@ -67,14 +86,20 @@ class NetworkStatusForm extends Component<NetworkStatusFormProps> {
|
||||
<DeviceHubIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="MAC Address" secondary={data.mac_address} />
|
||||
<ListItemText
|
||||
primary="MAC Address"
|
||||
secondary={data.mac_address}
|
||||
/>
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar>#</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Subnet Mask" secondary={data.subnet_mask} />
|
||||
<ListItemText
|
||||
primary="Subnet Mask"
|
||||
secondary={data.subnet_mask}
|
||||
/>
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
@@ -83,7 +108,10 @@ class NetworkStatusForm extends Component<NetworkStatusFormProps> {
|
||||
<SettingsInputComponentIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Gateway IP" secondary={data.gateway_ip || "none"} />
|
||||
<ListItemText
|
||||
primary="Gateway IP"
|
||||
secondary={data.gateway_ip || "none"}
|
||||
/>
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
@@ -92,11 +120,14 @@ class NetworkStatusForm extends Component<NetworkStatusFormProps> {
|
||||
<DNSIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="DNS Server IP" secondary={this.dnsServers(data)} />
|
||||
<ListItemText
|
||||
primary="DNS Server IP"
|
||||
secondary={this.dnsServers(data)}
|
||||
/>
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
</Fragment>
|
||||
}
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
@@ -104,18 +135,20 @@ class NetworkStatusForm extends Component<NetworkStatusFormProps> {
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<List>
|
||||
{this.createListItems()}
|
||||
</List>
|
||||
<List>{this.createListItems()}</List>
|
||||
<FormActions>
|
||||
<FormButton startIcon={<RefreshIcon />} variant="contained" color="secondary" onClick={this.props.loadData}>
|
||||
<FormButton
|
||||
startIcon={<RefreshIcon />}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={this.props.loadData}
|
||||
>
|
||||
Refresh
|
||||
</FormButton>
|
||||
</FormActions>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default withTheme(NetworkStatusForm);
|
||||
|
||||
@@ -36,7 +36,6 @@ export interface NetworkSettings {
|
||||
ssid: string;
|
||||
password: string;
|
||||
hostname: string;
|
||||
ethernet_profile: number;
|
||||
static_ip_config: boolean;
|
||||
local_ip?: string;
|
||||
gateway_ip?: string;
|
||||
|
||||
@@ -89,6 +89,8 @@ class NTPStatusForm extends Component<NTPStatusFormProps, NTPStatusFormState> {
|
||||
<Dialog
|
||||
open={this.state.settingTime}
|
||||
onClose={this.closeSetTime}
|
||||
fullWidth
|
||||
maxWidth="sm"
|
||||
>
|
||||
<DialogTitle>Set Time</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
|
||||
23
interface/src/project/EMSESPBoardProfiles.tsx
Normal file
23
interface/src/project/EMSESPBoardProfiles.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
|
||||
type BoardProfiles = {
|
||||
[name: string]: string
|
||||
};
|
||||
|
||||
export const BOARD_PROFILES: BoardProfiles = {
|
||||
"S32": "BBQKees Gateway S32",
|
||||
"E32": "BBQKees Gateway E32",
|
||||
"NODEMCU": "NodeMCU 32S",
|
||||
"MH-ET": "MH-ET Live D1 Mini",
|
||||
"LOLIN": "Lolin D32",
|
||||
"OLIMEX": "Olimex ESP32-EVB",
|
||||
"TLK110": "Generic Ethernet (TLK110)",
|
||||
"LAN8720": "Generic Ethernet (LAN8720)"
|
||||
}
|
||||
|
||||
export function boardProfileSelectItems() {
|
||||
return Object.keys(BOARD_PROFILES).map(code => (
|
||||
<MenuItem key={code} value={code}>{BOARD_PROFILES[code]}</MenuItem>
|
||||
));
|
||||
}
|
||||
@@ -2,42 +2,27 @@ import React, { Component, Fragment } from "react";
|
||||
import { withStyles, Theme, createStyles } from "@material-ui/core/styles";
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableContainer,
|
||||
withWidth,
|
||||
WithWidthProps,
|
||||
isWidthDown,
|
||||
Button,
|
||||
Tooltip,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Box,
|
||||
Dialog,
|
||||
Typography,
|
||||
Table, TableBody, TableCell, TableHead, TableRow, TableContainer, withWidth, WithWidthProps, isWidthDown,
|
||||
Button, Tooltip, DialogTitle, DialogContent, DialogActions, Box, Dialog, Typography
|
||||
} from "@material-ui/core";
|
||||
|
||||
import RefreshIcon from "@material-ui/icons/Refresh";
|
||||
import ListIcon from "@material-ui/icons/List";
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import EditIcon from '@material-ui/icons/Edit';
|
||||
|
||||
import {
|
||||
redirectingAuthorizedFetch,
|
||||
withAuthenticatedContext,
|
||||
AuthenticatedContextProps,
|
||||
} from "../authentication";
|
||||
import { redirectingAuthorizedFetch, withAuthenticatedContext, AuthenticatedContextProps } from "../authentication";
|
||||
import { RestFormProps, FormButton, extractEventValue } from "../components";
|
||||
|
||||
import { RestFormProps, FormButton } from "../components";
|
||||
import { EMSESPDevices, EMSESPDeviceData, Device, DeviceValue } from "./EMSESPtypes";
|
||||
|
||||
import { EMSESPDevices, EMSESPDeviceData, Device } from "./EMSESPtypes";
|
||||
import ValueForm from './ValueForm';
|
||||
|
||||
import { ENDPOINT_ROOT } from "../api";
|
||||
|
||||
export const SCANDEVICES_ENDPOINT = ENDPOINT_ROOT + "scanDevices";
|
||||
export const DEVICE_DATA_ENDPOINT = ENDPOINT_ROOT + "deviceData";
|
||||
export const WRITE_VALUE_ENDPOINT = ENDPOINT_ROOT + "writeValue";
|
||||
|
||||
const StyledTableCell = withStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -51,6 +36,16 @@ const StyledTableCell = withStyles((theme: Theme) =>
|
||||
})
|
||||
)(TableCell);
|
||||
|
||||
const CustomTooltip = withStyles((theme: Theme) => ({
|
||||
tooltip: {
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
color: 'white',
|
||||
boxShadow: theme.shadows[1],
|
||||
fontSize: 11,
|
||||
border: '1px solid #dadde9',
|
||||
},
|
||||
}))(Tooltip);
|
||||
|
||||
function compareDevices(a: Device, b: Device) {
|
||||
if (a.type < b.type) {
|
||||
return -1;
|
||||
@@ -65,35 +60,89 @@ interface EMSESPDevicesFormState {
|
||||
confirmScanDevices: boolean;
|
||||
processing: boolean;
|
||||
deviceData?: EMSESPDeviceData;
|
||||
selectedDevice?: number;
|
||||
devicevalue?: DeviceValue;
|
||||
}
|
||||
|
||||
type EMSESPDevicesFormProps = RestFormProps<EMSESPDevices> &
|
||||
AuthenticatedContextProps &
|
||||
WithWidthProps;
|
||||
type EMSESPDevicesFormProps = RestFormProps<EMSESPDevices> & AuthenticatedContextProps & WithWidthProps;
|
||||
|
||||
function formatTemp(t: string) {
|
||||
if (t == null) {
|
||||
return "(not available)";
|
||||
return "n/a";
|
||||
}
|
||||
return t + " °C";
|
||||
}
|
||||
|
||||
function formatUnit(u: string) {
|
||||
function formatUnit(u: string) {
|
||||
if (u == null) {
|
||||
return u;
|
||||
}
|
||||
return " " + u;
|
||||
}
|
||||
|
||||
class EMSESPDevicesForm extends Component<
|
||||
EMSESPDevicesFormProps,
|
||||
EMSESPDevicesFormState
|
||||
> {
|
||||
class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesFormState> {
|
||||
state: EMSESPDevicesFormState = {
|
||||
confirmScanDevices: false,
|
||||
processing: false,
|
||||
processing: false
|
||||
};
|
||||
|
||||
handleValueChange = (name: keyof DeviceValue) => (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ devicevalue: { ...this.state.devicevalue!, [name]: extractEventValue(event) } });
|
||||
};
|
||||
|
||||
cancelEditingValue = () => {
|
||||
this.setState({
|
||||
devicevalue: undefined
|
||||
});
|
||||
}
|
||||
|
||||
doneEditingValue = () => {
|
||||
const { devicevalue } = this.state;
|
||||
|
||||
redirectingAuthorizedFetch(WRITE_VALUE_ENDPOINT, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ devicevalue: devicevalue }),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
this.props.enqueueSnackbar("Write command sent to device", { variant: "success" });
|
||||
} else if (response.status === 204) {
|
||||
this.props.enqueueSnackbar("Write command failed", { variant: "error" });
|
||||
} else if (response.status === 403) {
|
||||
this.props.enqueueSnackbar("Write access denied", { variant: "error" });
|
||||
} else {
|
||||
throw Error("Unexpected response code: " + response.status);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.props.enqueueSnackbar(
|
||||
error.message || "Problem writing value", { variant: "error" }
|
||||
);
|
||||
});
|
||||
|
||||
if (devicevalue) {
|
||||
this.setState({
|
||||
devicevalue: undefined
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
sendCommand = (i: any) => {
|
||||
this.setState({
|
||||
devicevalue: {
|
||||
id: this.state.selectedDevice!,
|
||||
data: this.state.deviceData?.data[i]!,
|
||||
uom: this.state.deviceData?.data[i + 1]!,
|
||||
name: this.state.deviceData?.data[i + 2]!,
|
||||
cmd: this.state.deviceData?.data[i + 3]!,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
noDevices = () => {
|
||||
return this.props.data.devices.length === 0;
|
||||
};
|
||||
@@ -106,7 +155,7 @@ class EMSESPDevicesForm extends Component<
|
||||
return (this.state.deviceData?.data || []).length === 0;
|
||||
};
|
||||
|
||||
createDeviceItems() {
|
||||
renderDeviceItems() {
|
||||
const { width, data } = this.props;
|
||||
return (
|
||||
<TableContainer>
|
||||
@@ -119,50 +168,20 @@ class EMSESPDevicesForm extends Component<
|
||||
size="small"
|
||||
padding={isWidthDown("xs", width!) ? "none" : "default"}
|
||||
>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<StyledTableCell>Type</StyledTableCell>
|
||||
<StyledTableCell align="center">Brand</StyledTableCell>
|
||||
<StyledTableCell align="center">Model</StyledTableCell>
|
||||
<StyledTableCell align="center">Device ID</StyledTableCell>
|
||||
<StyledTableCell align="center">Product ID</StyledTableCell>
|
||||
<StyledTableCell align="center">Version</StyledTableCell>
|
||||
<StyledTableCell></StyledTableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{data.devices.sort(compareDevices).map((device) => (
|
||||
<TableRow
|
||||
hover
|
||||
key={device.id}
|
||||
onClick={() => this.handleRowClick(device.id)}
|
||||
>
|
||||
<TableCell component="th" scope="row">
|
||||
<Tooltip
|
||||
title="click for details..."
|
||||
arrow
|
||||
<TableRow hover key={device.id} onClick={() => this.handleRowClick(device)}>
|
||||
<TableCell>
|
||||
<CustomTooltip
|
||||
title={"DeviceID:0x" + ("00" + device.deviceid.toString(16).toUpperCase()).slice(-2) + " ProductID:" + device.productid + " Version:" + device.version}
|
||||
placement="right-end"
|
||||
>
|
||||
<Button
|
||||
startIcon={<ListIcon />}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
>
|
||||
<Button startIcon={<ListIcon />} size="small" variant="outlined">
|
||||
{device.type}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</CustomTooltip>
|
||||
</TableCell>
|
||||
<TableCell align="center">{device.brand}</TableCell>
|
||||
<TableCell align="center">{device.name}</TableCell>
|
||||
<TableCell align="center">
|
||||
0x
|
||||
{("00" + device.deviceid.toString(16).toUpperCase()).slice(
|
||||
-2
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="center">{device.productid}</TableCell>
|
||||
<TableCell align="center">{device.version}</TableCell>
|
||||
<TableCell></TableCell>
|
||||
<TableCell align="right">{device.brand + " " + device.name} </TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
@@ -172,13 +191,10 @@ class EMSESPDevicesForm extends Component<
|
||||
<Box
|
||||
bgcolor="error.main"
|
||||
color="error.contrastText"
|
||||
p={2}
|
||||
mt={2}
|
||||
mb={2}
|
||||
p={2} mt={2} mb={2}
|
||||
>
|
||||
<Typography variant="body1">
|
||||
No EMS devices found. Check the connections and for possible Tx
|
||||
errors.
|
||||
No EMS devices found. Check the connections and for possible Tx errors.
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
@@ -186,7 +202,7 @@ class EMSESPDevicesForm extends Component<
|
||||
);
|
||||
}
|
||||
|
||||
createSensorItems() {
|
||||
renderSensorItems() {
|
||||
const { data } = this.props;
|
||||
return (
|
||||
<TableContainer>
|
||||
@@ -234,28 +250,19 @@ class EMSESPDevicesForm extends Component<
|
||||
<Dialog
|
||||
open={this.state.confirmScanDevices}
|
||||
onClose={this.onScanDevicesRejected}
|
||||
fullWidth
|
||||
maxWidth="sm"
|
||||
>
|
||||
<DialogTitle>Confirm Scan Devices</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
Are you sure you want to initiate a scan on the EMS bus for all new
|
||||
devices?
|
||||
Are you sure you want to initiate a scan on the EMS bus for all new devices?
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={this.onScanDevicesRejected}
|
||||
color="secondary"
|
||||
>
|
||||
<Button variant="contained" onClick={this.onScanDevicesRejected} color="secondary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<RefreshIcon />}
|
||||
variant="contained"
|
||||
onClick={this.onScanDevicesConfirmed}
|
||||
disabled={this.state.processing}
|
||||
color="primary"
|
||||
autoFocus
|
||||
>
|
||||
startIcon={<RefreshIcon />} variant="contained" onClick={this.onScanDevicesConfirmed} disabled={this.state.processing} color="primary" autoFocus>
|
||||
Start Scan
|
||||
</Button>
|
||||
</DialogActions>
|
||||
@@ -292,11 +299,11 @@ class EMSESPDevicesForm extends Component<
|
||||
});
|
||||
};
|
||||
|
||||
handleRowClick = (id: any) => {
|
||||
this.setState({ deviceData: undefined });
|
||||
handleRowClick = (device: any) => {
|
||||
this.setState({ selectedDevice: device.id, deviceData: undefined });
|
||||
redirectingAuthorizedFetch(DEVICE_DATA_ENDPOINT, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ id: id }),
|
||||
body: JSON.stringify({ id: device.id }),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
@@ -304,7 +311,6 @@ class EMSESPDevicesForm extends Component<
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
return response.json();
|
||||
// this.setState({ errorMessage: undefined }, this.props.loadData);
|
||||
}
|
||||
throw Error("Unexpected response code: " + response.status);
|
||||
})
|
||||
@@ -323,6 +329,7 @@ class EMSESPDevicesForm extends Component<
|
||||
renderDeviceData() {
|
||||
const { deviceData } = this.state;
|
||||
const { width } = this.props;
|
||||
const me = this.props.authenticatedContext.me;
|
||||
|
||||
if (this.noDevices()) {
|
||||
return;
|
||||
@@ -346,16 +353,28 @@ class EMSESPDevicesForm extends Component<
|
||||
size="small"
|
||||
padding={isWidthDown("xs", width!) ? "none" : "default"}
|
||||
>
|
||||
<TableHead></TableHead>
|
||||
<TableHead>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{deviceData.data.map((item, i) => {
|
||||
if (i % 3) {
|
||||
if (i % 4) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<TableRow key={i}>
|
||||
<TableCell component="th" scope="row">{deviceData.data[i + 2]}</TableCell>
|
||||
<TableCell align="right">{deviceData.data[i]}{formatUnit(deviceData.data[i + 1])}</TableCell>
|
||||
<TableRow hover key={i}>
|
||||
<TableCell padding="checkbox" style={{ width: 18 }} >
|
||||
{deviceData.data[i + 3] && me.admin && (
|
||||
<CustomTooltip title="change value" placement="left-end"
|
||||
>
|
||||
<IconButton edge="start" size="small" aria-label="Edit"
|
||||
onClick={() => this.sendCommand(i)}>
|
||||
<EditIcon color="primary" fontSize="small" />
|
||||
</IconButton>
|
||||
</CustomTooltip>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell padding="none" component="th" scope="row">{deviceData.data[i + 2]}</TableCell>
|
||||
<TableCell padding="none" align="right">{deviceData.data[i]}{formatUnit(deviceData.data[i + 1])}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
@@ -365,47 +384,47 @@ class EMSESPDevicesForm extends Component<
|
||||
</TableContainer>
|
||||
)}
|
||||
{this.noDeviceData() && (
|
||||
<Box color="warning.main" p={0} mt={0} mb={0}>
|
||||
<Typography variant="body1">
|
||||
<i>No data available for this device</i>
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box color="warning.main" p={0} mt={0} mb={0}>
|
||||
<Typography variant="body1">
|
||||
<i>No data available for this device</i>
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Fragment >
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { devicevalue } = this.state;
|
||||
return (
|
||||
<Fragment>
|
||||
<br></br>
|
||||
{this.createDeviceItems()}
|
||||
{this.renderDeviceItems()}
|
||||
{this.renderDeviceData()}
|
||||
{this.createSensorItems()}
|
||||
{this.renderSensorItems()}
|
||||
<br></br>
|
||||
<Box display="flex" flexWrap="wrap">
|
||||
<Box flexGrow={1} padding={1}>
|
||||
<FormButton
|
||||
startIcon={<RefreshIcon />}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={this.props.loadData}
|
||||
>
|
||||
<FormButton startIcon={<RefreshIcon />} variant="contained" color="secondary" onClick={this.props.loadData} >
|
||||
Refresh
|
||||
</FormButton>
|
||||
</Box>
|
||||
<Box flexWrap="none" padding={1} whiteSpace="nowrap">
|
||||
<FormButton
|
||||
startIcon={<RefreshIcon />}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={this.onScanDevices}
|
||||
>
|
||||
<FormButton startIcon={<RefreshIcon />} variant="contained" onClick={this.onScanDevices} >
|
||||
Scan Devices
|
||||
</FormButton>
|
||||
</Box>
|
||||
</Box>
|
||||
{this.renderScanDevicesDialog()}
|
||||
{
|
||||
devicevalue &&
|
||||
<ValueForm
|
||||
devicevalue={devicevalue}
|
||||
onDoneEditing={this.doneEditingValue}
|
||||
onCancelEditing={this.cancelEditingValue}
|
||||
handleValueChange={this.handleValueChange}
|
||||
/>
|
||||
}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import React, { Component } from 'react';
|
||||
import { ValidatorForm, TextValidator, SelectValidator } from 'react-material-ui-form-validator';
|
||||
|
||||
import { Checkbox, Typography, Box, Link } from '@material-ui/core';
|
||||
import SaveIcon from '@material-ui/icons/Save';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
// import { Container } from '@material-ui/core';
|
||||
|
||||
import { ENDPOINT_ROOT } from '../api';
|
||||
import { restController, RestControllerProps, RestFormLoader, RestFormProps, FormActions, FormButton, BlockFormControlLabel, SectionContent } from '../components';
|
||||
import EMSESPSettingsForm from './EMSESPSettingsForm';
|
||||
|
||||
import { isIP, optional } from '../validators';
|
||||
import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components';
|
||||
|
||||
import { EMSESPSettings } from './EMSESPtypes';
|
||||
|
||||
@@ -19,303 +15,24 @@ type EMSESPSettingsControllerProps = RestControllerProps<EMSESPSettings>;
|
||||
class EMSESPSettingsController extends Component<EMSESPSettingsControllerProps> {
|
||||
|
||||
componentDidMount() {
|
||||
ValidatorForm.addValidationRule('isOptionalIP', optional(isIP));
|
||||
this.props.loadData();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SectionContent title='EMS-ESP Settings' titleGutter>
|
||||
// <Container maxWidth="md" disableGutters>
|
||||
<SectionContent title='' titleGutter>
|
||||
<RestFormLoader
|
||||
{...this.props}
|
||||
render={props => (
|
||||
<EMSESPSettingsControllerForm {...props} />
|
||||
render={formProps => (
|
||||
<EMSESPSettingsForm {...formProps} />
|
||||
)}
|
||||
/>
|
||||
</SectionContent>
|
||||
// </Container>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default restController(EMSESP_SETTINGS_ENDPOINT, EMSESPSettingsController);
|
||||
|
||||
type EMSESPSettingsControllerFormProps = RestFormProps<EMSESPSettings>;
|
||||
|
||||
function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps) {
|
||||
const { data, saveData, handleValueChange } = props;
|
||||
return (
|
||||
<ValidatorForm onSubmit={saveData}>
|
||||
<Box bgcolor="info.main" p={2} mt={2} mb={2}>
|
||||
<Typography variant="body1">
|
||||
Change the default settings on this page. For help click <Link target="_blank" href="https://emsesp.github.io/docs/#/Configure-firmware?id=settings" color="primary">{'here'}</Link>.
|
||||
</Typography>
|
||||
</Box>
|
||||
<br></br>
|
||||
<Typography variant="h6" color="primary" >
|
||||
EMS Bus
|
||||
</Typography>
|
||||
<SelectValidator name="tx_mode"
|
||||
label="Tx Mode"
|
||||
value={data.tx_mode}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onChange={handleValueChange('tx_mode')}
|
||||
margin="normal">
|
||||
<MenuItem value={0}>0 - Off</MenuItem>
|
||||
<MenuItem value={1}>1 - Default</MenuItem>
|
||||
<MenuItem value={2}>2 - EMS+</MenuItem>
|
||||
<MenuItem value={3}>3 - HT3</MenuItem>
|
||||
<MenuItem value={4}>4 - Hardware</MenuItem>
|
||||
</SelectValidator>
|
||||
<SelectValidator name="ems_bus_id"
|
||||
label="Bus ID"
|
||||
value={data.ems_bus_id}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onChange={handleValueChange('ems_bus_id')}
|
||||
margin="normal">
|
||||
<MenuItem value={0x0B}>Service Key (0x0B)</MenuItem>
|
||||
<MenuItem value={0x0D}>Modem (0x0D)</MenuItem>
|
||||
<MenuItem value={0x0A}>Terminal (0x0A)</MenuItem>
|
||||
<MenuItem value={0x0F}>Time Module (0x0F)</MenuItem>
|
||||
<MenuItem value={0x12}>Alarm Module (0x12)</MenuItem>
|
||||
</SelectValidator>
|
||||
<TextValidator
|
||||
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:40']}
|
||||
errorMessages={['Rx GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 40"]}
|
||||
name="rx_gpio"
|
||||
label="Rx GPIO pin"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.rx_gpio}
|
||||
type="number"
|
||||
onChange={handleValueChange('rx_gpio')}
|
||||
margin="normal"
|
||||
/>
|
||||
<TextValidator
|
||||
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:40']}
|
||||
errorMessages={['Tx GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 40"]}
|
||||
name="tx_gpio"
|
||||
label="Tx GPIO pin"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.tx_gpio}
|
||||
type="number"
|
||||
onChange={handleValueChange('tx_gpio')}
|
||||
margin="normal"
|
||||
/>
|
||||
<TextValidator
|
||||
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:120']}
|
||||
errorMessages={['Tx delay is required', "Must be a number", "Must be 0 or higher", "Max value is 120"]}
|
||||
name="tx_delay"
|
||||
label="Tx delayed start (seconds)"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.tx_delay}
|
||||
type="number"
|
||||
onChange={handleValueChange('tx_delay')}
|
||||
margin="normal"
|
||||
/>
|
||||
<br></br>
|
||||
<Typography variant="h6" color="primary" >
|
||||
External Button
|
||||
</Typography>
|
||||
<TextValidator
|
||||
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:40']}
|
||||
errorMessages={['Button GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 40"]}
|
||||
name="pbutton_gpio"
|
||||
label="Button GPIO pin"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.pbutton_gpio}
|
||||
type="number"
|
||||
onChange={handleValueChange('pbutton_gpio')}
|
||||
margin="normal"
|
||||
/>
|
||||
<br></br>
|
||||
<Typography variant="h6" color="primary" >
|
||||
Dallas Sensor
|
||||
</Typography>
|
||||
<TextValidator
|
||||
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:40']}
|
||||
errorMessages={['Dallas GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 40"]}
|
||||
name="dallas_gpio"
|
||||
label="Dallas GPIO pin (0=none)"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.dallas_gpio}
|
||||
type="number"
|
||||
onChange={handleValueChange('dallas_gpio')}
|
||||
margin="normal"
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.dallas_parasite}
|
||||
onChange={handleValueChange('dallas_parasite')}
|
||||
value="dallas_parasite"
|
||||
/>
|
||||
}
|
||||
label="Dallas Parasite Mode"
|
||||
/>
|
||||
<br></br>
|
||||
<Typography variant="h6" color="primary" >
|
||||
LED
|
||||
</Typography>
|
||||
<TextValidator
|
||||
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:40']}
|
||||
errorMessages={['LED GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 40"]}
|
||||
name="led_gpio"
|
||||
label="LED GPIO pin (0=none)"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.led_gpio}
|
||||
type="number"
|
||||
onChange={handleValueChange('led_gpio')}
|
||||
margin="normal"
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.hide_led}
|
||||
onChange={handleValueChange('hide_led')}
|
||||
value="hide_led"
|
||||
/>
|
||||
}
|
||||
label="Invert/Hide LED"
|
||||
/>
|
||||
<br></br>
|
||||
<Typography variant="h6" color="primary" >
|
||||
Shower
|
||||
</Typography>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.shower_timer}
|
||||
onChange={handleValueChange('shower_timer')}
|
||||
value="shower_timer"
|
||||
/>
|
||||
}
|
||||
label="Shower Timer"
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.shower_alert}
|
||||
onChange={handleValueChange('shower_alert')}
|
||||
value="shower_alert"
|
||||
/>
|
||||
}
|
||||
label="Shower Alert"
|
||||
/>
|
||||
<br></br>
|
||||
<Typography variant="h6" color="primary" >
|
||||
API
|
||||
</Typography>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.api_enabled}
|
||||
onChange={handleValueChange('api_enabled')}
|
||||
value="api_enabled"
|
||||
/>
|
||||
}
|
||||
label="Allow WEB API to write commands"
|
||||
/>
|
||||
<br></br>
|
||||
<Typography variant="h6" color="primary" >
|
||||
Syslog
|
||||
</Typography>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.syslog_enabled}
|
||||
onChange={handleValueChange('syslog_enabled')}
|
||||
value="syslog_enabled"
|
||||
/>
|
||||
}
|
||||
label="Enable Syslog"
|
||||
/>
|
||||
<TextValidator
|
||||
validators={['isOptionalIP']}
|
||||
errorMessages={["Not a valid IP address"]}
|
||||
name="syslog_host"
|
||||
label="Syslog IP"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.syslog_host}
|
||||
onChange={handleValueChange('syslog_host')}
|
||||
margin="normal"
|
||||
/>
|
||||
<TextValidator
|
||||
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']}
|
||||
errorMessages={['Port is required', "Must be a number", "Must be greater than 0 ", "Max value is 65535"]}
|
||||
name="syslog_port"
|
||||
label="Syslog Port (default 514)"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.syslog_port}
|
||||
type="number"
|
||||
onChange={handleValueChange('syslog_port')}
|
||||
margin="normal"
|
||||
/>
|
||||
<SelectValidator name="syslog_level"
|
||||
label="Syslog Log Level"
|
||||
value={data.syslog_level}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onChange={handleValueChange('syslog_level')}
|
||||
margin="normal">
|
||||
<MenuItem value={-1}>OFF</MenuItem>
|
||||
<MenuItem value={3}>ERR</MenuItem>
|
||||
<MenuItem value={5}>NOTICE</MenuItem>
|
||||
<MenuItem value={6}>INFO</MenuItem>
|
||||
<MenuItem value={7}>DEBUG</MenuItem>
|
||||
<MenuItem value={8}>ALL</MenuItem>
|
||||
</SelectValidator>
|
||||
<TextValidator
|
||||
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']}
|
||||
errorMessages={['Syslog Mark is required', "Must be a number", "Must be 0 or higher", "Max value is 10"]}
|
||||
name="syslog_mark_interval"
|
||||
label="Syslog Mark Interval (seconds, 0=off)"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.syslog_mark_interval}
|
||||
type="number"
|
||||
onChange={handleValueChange('syslog_mark_interval')}
|
||||
margin="normal"
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.trace_raw}
|
||||
onChange={handleValueChange('trace_raw')}
|
||||
value="trace_raw"
|
||||
/>
|
||||
}
|
||||
label="Trace EMS telegrams in raw format"
|
||||
/>
|
||||
<br></br>
|
||||
<Typography variant="h6" color="primary" >
|
||||
Analog Input
|
||||
</Typography>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.analog_enabled}
|
||||
onChange={handleValueChange('analog_enabled')}
|
||||
value="analog_enabled"
|
||||
/>
|
||||
}
|
||||
label="Enable ADC"
|
||||
/>
|
||||
<br></br>
|
||||
<FormActions>
|
||||
<FormButton startIcon={<SaveIcon />} variant="contained" color="primary" type="submit">
|
||||
Save
|
||||
</FormButton>
|
||||
</FormActions>
|
||||
</ValidatorForm>
|
||||
);
|
||||
}
|
||||
|
||||
577
interface/src/project/EMSESPSettingsForm.tsx
Normal file
577
interface/src/project/EMSESPSettingsForm.tsx
Normal file
@@ -0,0 +1,577 @@
|
||||
import React from "react";
|
||||
import {
|
||||
ValidatorForm,
|
||||
TextValidator,
|
||||
SelectValidator,
|
||||
} from "react-material-ui-form-validator";
|
||||
|
||||
import {
|
||||
Checkbox,
|
||||
Typography,
|
||||
Box,
|
||||
Link,
|
||||
withWidth,
|
||||
WithWidthProps,
|
||||
} from "@material-ui/core";
|
||||
import SaveIcon from "@material-ui/icons/Save";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
|
||||
import {
|
||||
redirectingAuthorizedFetch,
|
||||
withAuthenticatedContext,
|
||||
AuthenticatedContextProps,
|
||||
} from "../authentication";
|
||||
|
||||
import {
|
||||
RestFormProps,
|
||||
FormActions,
|
||||
FormButton,
|
||||
BlockFormControlLabel,
|
||||
} from "../components";
|
||||
|
||||
import { isIP, optional } from "../validators";
|
||||
|
||||
import { EMSESPSettings } from "./EMSESPtypes";
|
||||
|
||||
import { boardProfileSelectItems } from "./EMSESPBoardProfiles";
|
||||
|
||||
import { ENDPOINT_ROOT } from "../api";
|
||||
export const BOARD_PROFILE_ENDPOINT = ENDPOINT_ROOT + "boardProfile";
|
||||
|
||||
type EMSESPSettingsFormProps = RestFormProps<EMSESPSettings> &
|
||||
AuthenticatedContextProps &
|
||||
WithWidthProps;
|
||||
|
||||
interface EMSESPSettingsFormState {
|
||||
processing: boolean;
|
||||
}
|
||||
|
||||
class EMSESPSettingsForm extends React.Component<EMSESPSettingsFormProps> {
|
||||
state: EMSESPSettingsFormState = {
|
||||
processing: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
ValidatorForm.addValidationRule("isOptionalIP", optional(isIP));
|
||||
}
|
||||
|
||||
changeBoardProfile = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const { data, setData } = this.props;
|
||||
setData({
|
||||
...data,
|
||||
board_profile: event.target.value,
|
||||
});
|
||||
|
||||
if (event.target.value === "CUSTOM") return;
|
||||
|
||||
this.setState({ processing: true });
|
||||
redirectingAuthorizedFetch(BOARD_PROFILE_ENDPOINT, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ code: event.target.value }),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
return response.json();
|
||||
}
|
||||
throw Error("Unexpected response code: " + response.status);
|
||||
})
|
||||
.then((json) => {
|
||||
this.props.enqueueSnackbar("Profile loaded", { variant: "success" });
|
||||
setData({
|
||||
...data,
|
||||
led_gpio: json.led_gpio,
|
||||
dallas_gpio: json.dallas_gpio,
|
||||
rx_gpio: json.rx_gpio,
|
||||
tx_gpio: json.tx_gpio,
|
||||
pbutton_gpio: json.pbutton_gpio,
|
||||
board_profile: event.target.value,
|
||||
});
|
||||
this.setState({ processing: false });
|
||||
})
|
||||
.catch((error) => {
|
||||
this.props.enqueueSnackbar(
|
||||
error.message || "Problem fetching board profile",
|
||||
{ variant: "warning" }
|
||||
);
|
||||
this.setState({ processing: false });
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { data, saveData, handleValueChange } = this.props;
|
||||
return (
|
||||
<ValidatorForm onSubmit={saveData}>
|
||||
<Box bgcolor="info.main" p={2} mt={2} mb={2}>
|
||||
<Typography variant="body1">
|
||||
Adjust any of the EMS-ESP settings here. For help refer to the{" "}
|
||||
<Link
|
||||
target="_blank"
|
||||
href="https://emsesp.github.io/docs/#/Configure-firmware32?id=ems-esp-settings"
|
||||
color="primary"
|
||||
>
|
||||
{"online documentation"}
|
||||
</Link>
|
||||
.
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<br></br>
|
||||
<Typography variant="h6" color="primary">
|
||||
EMS Bus
|
||||
</Typography>
|
||||
|
||||
<Grid
|
||||
container
|
||||
spacing={1}
|
||||
direction="row"
|
||||
justify="flex-start"
|
||||
alignItems="flex-start"
|
||||
>
|
||||
<Grid item xs={5}>
|
||||
<SelectValidator
|
||||
name="tx_mode"
|
||||
label="Tx Mode"
|
||||
value={data.tx_mode}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onChange={handleValueChange("tx_mode")}
|
||||
margin="normal"
|
||||
>
|
||||
<MenuItem value={0}>Off</MenuItem>
|
||||
<MenuItem value={1}>EMS</MenuItem>
|
||||
<MenuItem value={2}>EMS+</MenuItem>
|
||||
<MenuItem value={3}>HT3</MenuItem>
|
||||
<MenuItem value={4}>Hardware</MenuItem>
|
||||
</SelectValidator>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<SelectValidator
|
||||
name="ems_bus_id"
|
||||
label="Bus ID"
|
||||
value={data.ems_bus_id}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onChange={handleValueChange("ems_bus_id")}
|
||||
margin="normal"
|
||||
>
|
||||
<MenuItem value={0x0b}>Service Key (0x0B)</MenuItem>
|
||||
<MenuItem value={0x0d}>Modem (0x0D)</MenuItem>
|
||||
<MenuItem value={0x0a}>Terminal (0x0A)</MenuItem>
|
||||
<MenuItem value={0x0f}>Time Module (0x0F)</MenuItem>
|
||||
<MenuItem value={0x12}>Alarm Module (0x12)</MenuItem>
|
||||
</SelectValidator>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextValidator
|
||||
validators={[
|
||||
"required",
|
||||
"isNumber",
|
||||
"minNumber:0",
|
||||
"maxNumber:120",
|
||||
]}
|
||||
errorMessages={[
|
||||
"Tx delay is required",
|
||||
"Must be a number",
|
||||
"Must be 0 or higher",
|
||||
"Max value is 120",
|
||||
]}
|
||||
name="tx_delay"
|
||||
label="Tx start delay (seconds)"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.tx_delay}
|
||||
type="number"
|
||||
onChange={handleValueChange("tx_delay")}
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<br></br>
|
||||
<Typography variant="h6" color="primary">
|
||||
Board Profile
|
||||
</Typography>
|
||||
|
||||
<Box color="warning.main" p={0} mt={0} mb={0}>
|
||||
<Typography variant="body2">
|
||||
<i>
|
||||
Select a pre-configured board layout to automatically set the GPIO
|
||||
pins, or set your own custom configuration
|
||||
</i>
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<SelectValidator
|
||||
name="board_profile"
|
||||
label="Board Profile"
|
||||
value={data.board_profile}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onChange={this.changeBoardProfile}
|
||||
margin="normal"
|
||||
>
|
||||
{boardProfileSelectItems()}
|
||||
<MenuItem key={"CUSTOM"} value={"CUSTOM"}>
|
||||
Custom...
|
||||
</MenuItem>
|
||||
</SelectValidator>
|
||||
|
||||
{data.board_profile === "CUSTOM" && (
|
||||
<Grid
|
||||
container
|
||||
spacing={1}
|
||||
direction="row"
|
||||
justify="flex-start"
|
||||
alignItems="flex-start"
|
||||
>
|
||||
<Grid item xs={4}>
|
||||
<TextValidator
|
||||
validators={[
|
||||
"required",
|
||||
"isNumber",
|
||||
"minNumber:0",
|
||||
"maxNumber:40",
|
||||
"matchRegexp:^((?!6|7|8|9|10|11|12|14|15|20|24|28|29|30|31)[0-9]*)$",
|
||||
]}
|
||||
errorMessages={[
|
||||
"GPIO is required",
|
||||
"Must be a number",
|
||||
"Must be 0 or higher",
|
||||
"Max value is 40",
|
||||
"Not a valid GPIO",
|
||||
]}
|
||||
name="rx_gpio"
|
||||
label="Rx GPIO"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.rx_gpio}
|
||||
type="number"
|
||||
onChange={handleValueChange("rx_gpio")}
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<TextValidator
|
||||
validators={[
|
||||
"required",
|
||||
"isNumber",
|
||||
"minNumber:0",
|
||||
"maxNumber:40",
|
||||
"matchRegexp:^((?!6|7|8|9|10|11|12|14|15|20|24|28|29|30|31)[0-9]*)$",
|
||||
]}
|
||||
errorMessages={[
|
||||
"GPIO is required",
|
||||
"Must be a number",
|
||||
"Must be 0 or higher",
|
||||
"Max value is 40",
|
||||
"Not a valid GPIO",
|
||||
]}
|
||||
name="tx_gpio"
|
||||
label="Tx GPIO"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.tx_gpio}
|
||||
type="number"
|
||||
onChange={handleValueChange("tx_gpio")}
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<TextValidator
|
||||
validators={[
|
||||
"required",
|
||||
"isNumber",
|
||||
"minNumber:0",
|
||||
"maxNumber:40",
|
||||
"matchRegexp:^((?!6|7|8|9|10|11|12|14|15|20|24|28|29|30|31)[0-9]*)$",
|
||||
]}
|
||||
errorMessages={[
|
||||
"GPIO is required",
|
||||
"Must be a number",
|
||||
"Must be 0 or higher",
|
||||
"Max value is 40",
|
||||
"Not a valid GPIO",
|
||||
]}
|
||||
name="pbutton_gpio"
|
||||
label="Button GPIO"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.pbutton_gpio}
|
||||
type="number"
|
||||
onChange={handleValueChange("pbutton_gpio")}
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<TextValidator
|
||||
validators={[
|
||||
"required",
|
||||
"isNumber",
|
||||
"minNumber:0",
|
||||
"maxNumber:40",
|
||||
"matchRegexp:^((?!6|7|8|9|10|11|12|14|15|20|24|28|29|30|31)[0-9]*)$",
|
||||
]}
|
||||
errorMessages={[
|
||||
"GPIO is required",
|
||||
"Must be a number",
|
||||
"Must be 0 or higher",
|
||||
"Max value is 40",
|
||||
"Not a valid GPIO",
|
||||
]}
|
||||
name="dallas_gpio"
|
||||
label="Dallas GPIO (0=none)"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.dallas_gpio}
|
||||
type="number"
|
||||
onChange={handleValueChange("dallas_gpio")}
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<TextValidator
|
||||
validators={[
|
||||
"required",
|
||||
"isNumber",
|
||||
"minNumber:0",
|
||||
"maxNumber:40",
|
||||
"matchRegexp:^((?!6|7|8|9|10|11|12|14|15|20|24|28|29|30|31)[0-9]*)$",
|
||||
]}
|
||||
errorMessages={[
|
||||
"GPIO is required",
|
||||
"Must be a number",
|
||||
"Must be 0 or higher",
|
||||
"Max value is 40",
|
||||
"Not a valid GPIO",
|
||||
]}
|
||||
name="led_gpio"
|
||||
label="LED GPIO (0=none)"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.led_gpio}
|
||||
type="number"
|
||||
onChange={handleValueChange("led_gpio")}
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
<br></br>
|
||||
<Typography variant="h6" color="primary">
|
||||
Options
|
||||
</Typography>
|
||||
|
||||
{data.led_gpio !== 0 && (
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.hide_led}
|
||||
onChange={handleValueChange("hide_led")}
|
||||
value="hide_led"
|
||||
/>
|
||||
}
|
||||
label="Hide LED"
|
||||
/>
|
||||
)}
|
||||
|
||||
{data.dallas_gpio !== 0 && (
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.dallas_parasite}
|
||||
onChange={handleValueChange("dallas_parasite")}
|
||||
value="dallas_parasite"
|
||||
/>
|
||||
}
|
||||
label="Enable Dallas parasite mode"
|
||||
/>
|
||||
)}
|
||||
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.api_enabled}
|
||||
onChange={handleValueChange("api_enabled")}
|
||||
value="api_enabled"
|
||||
/>
|
||||
}
|
||||
label="Enable API write commands"
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.analog_enabled}
|
||||
onChange={handleValueChange("analog_enabled")}
|
||||
value="analog_enabled"
|
||||
/>
|
||||
}
|
||||
label="Enable ADC"
|
||||
/>
|
||||
<Grid
|
||||
container
|
||||
spacing={0}
|
||||
direction="row"
|
||||
justify="flex-start"
|
||||
alignItems="flex-start"
|
||||
>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.shower_timer}
|
||||
onChange={handleValueChange("shower_timer")}
|
||||
value="shower_timer"
|
||||
/>
|
||||
}
|
||||
label="Enable Shower Timer"
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.shower_alert}
|
||||
onChange={handleValueChange("shower_alert")}
|
||||
value="shower_alert"
|
||||
/>
|
||||
}
|
||||
label="Enable Shower Alert"
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<br></br>
|
||||
<Typography variant="h6" color="primary">
|
||||
Syslog
|
||||
</Typography>
|
||||
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.syslog_enabled}
|
||||
onChange={handleValueChange("syslog_enabled")}
|
||||
value="syslog_enabled"
|
||||
/>
|
||||
}
|
||||
label="Enable Syslog"
|
||||
/>
|
||||
|
||||
{data.syslog_enabled && (
|
||||
<Grid
|
||||
container
|
||||
spacing={1}
|
||||
direction="row"
|
||||
justify="flex-start"
|
||||
alignItems="flex-start"
|
||||
>
|
||||
<Grid item xs={5}>
|
||||
<TextValidator
|
||||
validators={["isOptionalIP"]}
|
||||
errorMessages={["Not a valid IP address"]}
|
||||
name="syslog_host"
|
||||
label="IP"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.syslog_host}
|
||||
onChange={handleValueChange("syslog_host")}
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextValidator
|
||||
validators={[
|
||||
"required",
|
||||
"isNumber",
|
||||
"minNumber:0",
|
||||
"maxNumber:65535",
|
||||
]}
|
||||
errorMessages={[
|
||||
"Port is required",
|
||||
"Must be a number",
|
||||
"Must be greater than 0 ",
|
||||
"Max value is 65535",
|
||||
]}
|
||||
name="syslog_port"
|
||||
label="Port"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.syslog_port}
|
||||
type="number"
|
||||
onChange={handleValueChange("syslog_port")}
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={5}>
|
||||
<SelectValidator
|
||||
name="syslog_level"
|
||||
label="Log Level"
|
||||
value={data.syslog_level}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onChange={handleValueChange("syslog_level")}
|
||||
margin="normal"
|
||||
>
|
||||
<MenuItem value={-1}>OFF</MenuItem>
|
||||
<MenuItem value={3}>ERR</MenuItem>
|
||||
<MenuItem value={5}>NOTICE</MenuItem>
|
||||
<MenuItem value={6}>INFO</MenuItem>
|
||||
<MenuItem value={7}>DEBUG</MenuItem>
|
||||
<MenuItem value={8}>ALL</MenuItem>
|
||||
</SelectValidator>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextValidator
|
||||
validators={[
|
||||
"required",
|
||||
"isNumber",
|
||||
"minNumber:0",
|
||||
"maxNumber:65535",
|
||||
]}
|
||||
errorMessages={[
|
||||
"Syslog Mark is required",
|
||||
"Must be a number",
|
||||
"Must be 0 or higher",
|
||||
"Max value is 10",
|
||||
]}
|
||||
name="syslog_mark_interval"
|
||||
label="Mark Interval seconds (0=off)"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.syslog_mark_interval}
|
||||
type="number"
|
||||
onChange={handleValueChange("syslog_mark_interval")}
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.trace_raw}
|
||||
onChange={handleValueChange("trace_raw")}
|
||||
value="trace_raw"
|
||||
/>
|
||||
}
|
||||
label="Output EMS telegrams in raw format"
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
<br></br>
|
||||
<FormActions>
|
||||
<FormButton
|
||||
startIcon={<SaveIcon />}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
>
|
||||
Save
|
||||
</FormButton>
|
||||
</FormActions>
|
||||
</ValidatorForm>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withAuthenticatedContext(withWidth()(EMSESPSettingsForm));
|
||||
@@ -62,14 +62,14 @@ class EMSESPStatusForm extends Component<EMSESPStatusFormProps> {
|
||||
<TableCell>
|
||||
# Telegrams Received
|
||||
</TableCell>
|
||||
<TableCell align="right">{formatNumber(data.rx_received)} ({data.rx_quality}%)
|
||||
<TableCell align="right">{formatNumber(data.rx_received)} (quality {data.rx_quality}%)
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell >
|
||||
# Telegrams Sent
|
||||
</TableCell >
|
||||
<TableCell align="right">{formatNumber(data.tx_sent)} ({data.tx_quality}%)
|
||||
<TableCell align="right">{formatNumber(data.tx_sent)} (quality {data.tx_quality}%)
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface EMSESPSettings {
|
||||
analog_enabled: boolean;
|
||||
pbutton_gpio: number;
|
||||
trace_raw: boolean;
|
||||
board_profile: string;
|
||||
}
|
||||
|
||||
export enum busConnectionStatus {
|
||||
@@ -61,3 +62,11 @@ export interface EMSESPDeviceData {
|
||||
name: string;
|
||||
data: string[];
|
||||
}
|
||||
|
||||
export interface DeviceValue {
|
||||
id: number;
|
||||
data: string,
|
||||
uom: string,
|
||||
name: string,
|
||||
cmd: string
|
||||
}
|
||||
|
||||
64
interface/src/project/ValueForm.tsx
Normal file
64
interface/src/project/ValueForm.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import React, { RefObject } from 'react';
|
||||
import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator';
|
||||
import { Dialog, DialogTitle, DialogContent, DialogActions, Box, Typography } from '@material-ui/core';
|
||||
import { FormButton } from '../components';
|
||||
import { DeviceValue } from './EMSESPtypes';
|
||||
|
||||
interface ValueFormProps {
|
||||
devicevalue: DeviceValue;
|
||||
onDoneEditing: () => void;
|
||||
onCancelEditing: () => void;
|
||||
handleValueChange: (data: keyof DeviceValue) => (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
class ValueForm extends React.Component<ValueFormProps> {
|
||||
|
||||
formRef: RefObject<any> = React.createRef();
|
||||
|
||||
submit = () => {
|
||||
this.formRef.current.submit();
|
||||
}
|
||||
|
||||
buildLabel = (devicevalue: DeviceValue) => {
|
||||
if ((devicevalue.uom === "") || (!devicevalue.uom)) {
|
||||
return "New value";
|
||||
}
|
||||
return "New value (" + devicevalue.uom + ")";
|
||||
}
|
||||
|
||||
render() {
|
||||
const { devicevalue, handleValueChange, onDoneEditing, onCancelEditing } = this.props;
|
||||
return (
|
||||
<ValidatorForm onSubmit={onDoneEditing} ref={this.formRef}>
|
||||
<Dialog maxWidth="xs" onClose={onCancelEditing} aria-labelledby="user-form-dialog-title" open>
|
||||
<DialogTitle id="user-form-dialog-title">Change the {devicevalue.name}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<TextValidator
|
||||
validators={['required']}
|
||||
errorMessages={['is required']}
|
||||
name="data"
|
||||
label={this.buildLabel(devicevalue)}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={devicevalue.data}
|
||||
margin="normal"
|
||||
onChange={handleValueChange('data')}
|
||||
/>
|
||||
<Box color="warning.main" p={1} pl={0} pr={0} mt={0} mb={0}>
|
||||
<Typography variant="body2">
|
||||
<i>Note: it may take a few seconds before the change is visible. If nothing happens check the logs.</i>
|
||||
</Typography>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<FormButton variant="contained" color="secondary" onClick={onCancelEditing}>Cancel</FormButton>
|
||||
<FormButton variant="contained" color="primary" type="submit" onClick={this.submit}>Done</FormButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</ValidatorForm>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ValueForm;
|
||||
77
interface/src/security/GenerateToken.tsx
Normal file
77
interface/src/security/GenerateToken.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { Dialog, DialogTitle, DialogContent, DialogActions, Box, LinearProgress, Typography, TextField } from '@material-ui/core';
|
||||
|
||||
import { FormButton } from '../components';
|
||||
import { redirectingAuthorizedFetch } from '../authentication';
|
||||
import { GENERATE_TOKEN_ENDPOINT } from '../api';
|
||||
import { withSnackbar, WithSnackbarProps } from 'notistack';
|
||||
|
||||
interface GenerateTokenProps extends WithSnackbarProps {
|
||||
username: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
interface GenerateTokenState {
|
||||
token?: string;
|
||||
}
|
||||
|
||||
class GenerateToken extends React.Component<GenerateTokenProps, GenerateTokenState> {
|
||||
|
||||
state: GenerateTokenState = {};
|
||||
|
||||
componentDidMount() {
|
||||
const { username } = this.props;
|
||||
redirectingAuthorizedFetch(GENERATE_TOKEN_ENDPOINT + "?" + new URLSearchParams({ username }), { method: 'GET' })
|
||||
.then(response => {
|
||||
if (response.status === 200) {
|
||||
return response.json();
|
||||
} else {
|
||||
throw Error("Error generating token: " + response.status);
|
||||
}
|
||||
}).then(generatedToken => {
|
||||
console.log(generatedToken);
|
||||
this.setState({ token: generatedToken.token });
|
||||
})
|
||||
.catch(error => {
|
||||
this.props.enqueueSnackbar(error.message || "Problem generating token", { variant: 'error' });
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { onClose, username } = this.props;
|
||||
const { token } = this.state;
|
||||
return (
|
||||
<Dialog onClose={onClose} aria-labelledby="generate-token-dialog-title" open fullWidth maxWidth="sm">
|
||||
<DialogTitle id="generate-token-dialog-title">Token for: {username}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
{token ?
|
||||
<Fragment>
|
||||
<Box bgcolor="primary.main" color="primary.contrastText" p={2} mt={2} mb={2}>
|
||||
<Typography variant="body1">
|
||||
The token below may be used to access the secured APIs. This may be used for bearer authentication with the "Authorization" header or using the "access_token" query paramater.
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box mt={2} mb={2}>
|
||||
<TextField label="Token" multiline value={token} fullWidth contentEditable={false} />
|
||||
</Box>
|
||||
</Fragment>
|
||||
:
|
||||
<Box m={4} textAlign="center">
|
||||
<LinearProgress />
|
||||
<Typography variant="h6">
|
||||
Generating token…
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<FormButton variant="contained" color="primary" type="submit" onClick={onClose}>
|
||||
Close
|
||||
</FormButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withSnackbar(GenerateToken);
|
||||
@@ -11,12 +11,14 @@ import CheckIcon from '@material-ui/icons/Check';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import SaveIcon from '@material-ui/icons/Save';
|
||||
import PersonAddIcon from '@material-ui/icons/PersonAdd';
|
||||
import VpnKeyIcon from '@material-ui/icons/VpnKey';
|
||||
|
||||
import { withAuthenticatedContext, AuthenticatedContextProps } from '../authentication';
|
||||
import { RestFormProps, FormActions, FormButton, extractEventValue } from '../components';
|
||||
|
||||
import UserForm from './UserForm';
|
||||
import { SecuritySettings, User } from './types';
|
||||
import GenerateToken from './GenerateToken';
|
||||
|
||||
function compareUsers(a: User, b: User) {
|
||||
if (a.username < b.username) {
|
||||
@@ -33,6 +35,7 @@ type ManageUsersFormProps = RestFormProps<SecuritySettings> & AuthenticatedConte
|
||||
type ManageUsersFormState = {
|
||||
creating: boolean;
|
||||
user?: User;
|
||||
generateTokenFor?: string;
|
||||
}
|
||||
|
||||
class ManageUsersForm extends React.Component<ManageUsersFormProps, ManageUsersFormState> {
|
||||
@@ -66,6 +69,18 @@ class ManageUsersForm extends React.Component<ManageUsersFormProps, ManageUsersF
|
||||
this.props.setData({ ...data, users });
|
||||
}
|
||||
|
||||
closeGenerateToken = () => {
|
||||
this.setState({
|
||||
generateTokenFor: undefined
|
||||
});
|
||||
}
|
||||
|
||||
generateToken = (user: User) => {
|
||||
this.setState({
|
||||
generateTokenFor: user.username
|
||||
});
|
||||
}
|
||||
|
||||
startEditingUser = (user: User) => {
|
||||
this.setState({
|
||||
creating: false,
|
||||
@@ -103,7 +118,7 @@ class ManageUsersForm extends React.Component<ManageUsersFormProps, ManageUsersF
|
||||
|
||||
render() {
|
||||
const { width, data } = this.props;
|
||||
const { user, creating } = this.state;
|
||||
const { user, creating, generateTokenFor } = this.state;
|
||||
return (
|
||||
<Fragment>
|
||||
<ValidatorForm onSubmit={this.onSubmit}>
|
||||
@@ -122,11 +137,12 @@ class ManageUsersForm extends React.Component<ManageUsersFormProps, ManageUsersF
|
||||
{user.username}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{
|
||||
user.admin ? <CheckIcon /> : <CloseIcon />
|
||||
}
|
||||
{user.admin ? <CheckIcon /> : <CloseIcon />}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<IconButton size="small" aria-label="Generate Token" onClick={() => this.generateToken(user)}>
|
||||
<VpnKeyIcon />
|
||||
</IconButton>
|
||||
<IconButton size="small" aria-label="Delete" onClick={() => this.removeUser(user)}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
@@ -164,6 +180,9 @@ class ManageUsersForm extends React.Component<ManageUsersFormProps, ManageUsersF
|
||||
</FormButton>
|
||||
</FormActions>
|
||||
</ValidatorForm>
|
||||
{
|
||||
generateTokenFor && <GenerateToken username={generateTokenFor} onClose={this.closeGenerateToken} />
|
||||
}
|
||||
{
|
||||
user &&
|
||||
<UserForm
|
||||
|
||||
@@ -35,7 +35,7 @@ class SecuritySettingsForm extends React.Component<SecuritySettingsFormProps> {
|
||||
/>
|
||||
<Box bgcolor="primary.main" color="primary.contrastText" p={2} mt={2} mb={2}>
|
||||
<Typography variant="body1">
|
||||
The Super User password is used to sign authentication tokens and also the Console's `su` password. If you modify this all users will be signed out.
|
||||
The Super User password is used to sign authentication tokens and is also the Console's `su` password. If you modify this all users will be signed out.
|
||||
</Typography>
|
||||
</Box>
|
||||
<FormActions>
|
||||
|
||||
@@ -32,7 +32,7 @@ class UserForm extends React.Component<UserFormProps> {
|
||||
const { user, creating, handleValueChange, onDoneEditing, onCancelEditing } = this.props;
|
||||
return (
|
||||
<ValidatorForm onSubmit={onDoneEditing} ref={this.formRef}>
|
||||
<Dialog onClose={onCancelEditing} aria-labelledby="user-form-dialog-title" open>
|
||||
<Dialog onClose={onCancelEditing} aria-labelledby="user-form-dialog-title" open fullWidth maxWidth="sm">
|
||||
<DialogTitle id="user-form-dialog-title">{creating ? 'Add' : 'Modify'} User</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<TextValidator
|
||||
|
||||
@@ -9,3 +9,6 @@ export interface SecuritySettings {
|
||||
jwt_secret: string;
|
||||
}
|
||||
|
||||
export interface GeneratedToken {
|
||||
token: string;
|
||||
}
|
||||
|
||||
12
interface/src/setupProxy.js
Normal file
12
interface/src/setupProxy.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const { createProxyMiddleware } = require('http-proxy-middleware');
|
||||
|
||||
module.exports = function (app) {
|
||||
app.use(
|
||||
'/rest/*',
|
||||
createProxyMiddleware({
|
||||
target: 'http://localhost:3080',
|
||||
secure: false,
|
||||
changeOrigin: true
|
||||
})
|
||||
);
|
||||
};
|
||||
@@ -111,6 +111,8 @@ class SystemStatusForm extends Component<SystemStatusFormProps, SystemStatusForm
|
||||
<Dialog
|
||||
open={this.state.confirmRestart}
|
||||
onClose={this.onRestartRejected}
|
||||
fullWidth
|
||||
maxWidth="sm"
|
||||
>
|
||||
<DialogTitle>Confirm Restart</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
@@ -158,6 +160,8 @@ class SystemStatusForm extends Component<SystemStatusFormProps, SystemStatusForm
|
||||
<Dialog
|
||||
open={this.state.confirmFactoryReset}
|
||||
onClose={this.onFactoryResetRejected}
|
||||
fullWidth
|
||||
maxWidth="sm"
|
||||
>
|
||||
<DialogTitle>Confirm Factory Reset</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
|
||||
@@ -22,10 +22,8 @@ class UploadFirmwareForm extends React.Component<UploadFirmwareFormProps> {
|
||||
return (
|
||||
<Fragment>
|
||||
<Box py={2}>
|
||||
Upload a new firmware file (.bin or .bin.gz) below to replace the
|
||||
existing firmware.
|
||||
<p></p>This can take up to a minute. Wait until you see "Activating
|
||||
new Firmware" and EMS-ESP will automatically restart.
|
||||
Upload a new firmware file (.bin or .bin.gz) below to replace the existing firmware.
|
||||
<p></p>This can take up to a minute. Wait until you see "Activating new Firmware" and EMS-ESP will then automatically restart.
|
||||
</Box>
|
||||
<SingleUpload
|
||||
onDrop={this.handleDrop}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react",
|
||||
"jsx": "react-jsx",
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": [
|
||||
|
||||
@@ -39,12 +39,12 @@
|
||||
#include <Print.h>
|
||||
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||
#define ARDUINOJSON_5_COMPATIBILITY
|
||||
#define ARDUINOJSON_5_COMPATIBILITY
|
||||
#else
|
||||
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
|
||||
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
|
||||
#endif
|
||||
|
||||
constexpr const char* JSON_MIMETYPE = "application/json";
|
||||
constexpr const char * JSON_MIMETYPE = "application/json";
|
||||
|
||||
/*
|
||||
* Json Response
|
||||
@@ -52,34 +52,38 @@ constexpr const char* JSON_MIMETYPE = "application/json";
|
||||
|
||||
class ChunkPrint : public Print {
|
||||
private:
|
||||
uint8_t* _destination;
|
||||
size_t _to_skip;
|
||||
size_t _to_write;
|
||||
size_t _pos;
|
||||
uint8_t * _destination;
|
||||
size_t _to_skip;
|
||||
size_t _to_write;
|
||||
size_t _pos;
|
||||
|
||||
public:
|
||||
ChunkPrint(uint8_t* destination, size_t from, size_t len)
|
||||
: _destination(destination), _to_skip(from), _to_write(len), _pos{0} {}
|
||||
virtual ~ChunkPrint(){}
|
||||
size_t write(uint8_t c){
|
||||
if (_to_skip > 0) {
|
||||
_to_skip--;
|
||||
return 1;
|
||||
} else if (_to_write > 0) {
|
||||
_to_write--;
|
||||
_destination[_pos++] = c;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
ChunkPrint(uint8_t * destination, size_t from, size_t len)
|
||||
: _destination(destination)
|
||||
, _to_skip(from)
|
||||
, _to_write(len)
|
||||
, _pos{0} {
|
||||
}
|
||||
size_t write(const uint8_t *buffer, size_t size)
|
||||
{
|
||||
return this->Print::write(buffer, size);
|
||||
virtual ~ChunkPrint() {
|
||||
}
|
||||
size_t write(uint8_t c) {
|
||||
if (_to_skip > 0) {
|
||||
_to_skip--;
|
||||
return 1;
|
||||
} else if (_to_write > 0) {
|
||||
_to_write--;
|
||||
_destination[_pos++] = c;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
size_t write(const uint8_t * buffer, size_t size) {
|
||||
return this->Print::write(buffer, size);
|
||||
}
|
||||
};
|
||||
|
||||
class AsyncJsonResponse: public AsyncAbstractResponse {
|
||||
class AsyncJsonResponse : public AsyncAbstractResponse {
|
||||
protected:
|
||||
|
||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||
DynamicJsonBuffer _jsonBuffer;
|
||||
#else
|
||||
@@ -87,166 +91,201 @@ class AsyncJsonResponse: public AsyncAbstractResponse {
|
||||
#endif
|
||||
|
||||
JsonVariant _root;
|
||||
bool _isValid;
|
||||
|
||||
public:
|
||||
bool _isValid;
|
||||
|
||||
public:
|
||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||
AsyncJsonResponse(bool isArray=false): _isValid{false} {
|
||||
_code = 200;
|
||||
_contentType = JSON_MIMETYPE;
|
||||
if(isArray)
|
||||
_root = _jsonBuffer.createArray();
|
||||
else
|
||||
_root = _jsonBuffer.createObject();
|
||||
AsyncJsonResponse(bool isArray = false)
|
||||
: _isValid{false} {
|
||||
_code = 200;
|
||||
_contentType = JSON_MIMETYPE;
|
||||
if (isArray)
|
||||
_root = _jsonBuffer.createArray();
|
||||
else
|
||||
_root = _jsonBuffer.createObject();
|
||||
}
|
||||
#else
|
||||
AsyncJsonResponse(bool isArray=false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
|
||||
_code = 200;
|
||||
_contentType = JSON_MIMETYPE;
|
||||
if(isArray)
|
||||
_root = _jsonBuffer.createNestedArray();
|
||||
else
|
||||
_root = _jsonBuffer.createNestedObject();
|
||||
AsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE)
|
||||
: _jsonBuffer(maxJsonBufferSize)
|
||||
, _isValid{false} {
|
||||
_code = 200;
|
||||
_contentType = JSON_MIMETYPE;
|
||||
if (isArray)
|
||||
_root = _jsonBuffer.createNestedArray();
|
||||
else
|
||||
_root = _jsonBuffer.createNestedObject();
|
||||
}
|
||||
#endif
|
||||
|
||||
~AsyncJsonResponse() {}
|
||||
JsonVariant & getRoot() { return _root; }
|
||||
bool _sourceValid() const { return _isValid; }
|
||||
~AsyncJsonResponse() {
|
||||
}
|
||||
JsonVariant & getRoot() {
|
||||
return _root;
|
||||
}
|
||||
bool _sourceValid() const {
|
||||
return _isValid;
|
||||
}
|
||||
size_t setLength() {
|
||||
|
||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||
_contentLength = _root.measureLength();
|
||||
#else
|
||||
_contentLength = measureJson(_root);
|
||||
#endif
|
||||
|
||||
if (_contentLength) { _isValid = true; }
|
||||
return _contentLength;
|
||||
}
|
||||
|
||||
size_t getSize() { return _jsonBuffer.size(); }
|
||||
|
||||
size_t _fillBuffer(uint8_t *data, size_t len){
|
||||
ChunkPrint dest(data, _sentLength, len);
|
||||
|
||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||
_root.printTo( dest ) ;
|
||||
#else
|
||||
serializeJson(_root, dest);
|
||||
#endif
|
||||
return len;
|
||||
}
|
||||
};
|
||||
|
||||
class PrettyAsyncJsonResponse: public AsyncJsonResponse {
|
||||
public:
|
||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||
PrettyAsyncJsonResponse (bool isArray=false) : AsyncJsonResponse{isArray} {}
|
||||
_contentLength = _root.measureLength();
|
||||
#else
|
||||
PrettyAsyncJsonResponse (bool isArray=false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : AsyncJsonResponse{isArray, maxJsonBufferSize} {}
|
||||
#endif
|
||||
size_t setLength () {
|
||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||
_contentLength = _root.measurePrettyLength ();
|
||||
#else
|
||||
_contentLength = measureJsonPretty(_root);
|
||||
#endif
|
||||
if (_contentLength) {_isValid = true;}
|
||||
return _contentLength;
|
||||
}
|
||||
size_t _fillBuffer (uint8_t *data, size_t len) {
|
||||
ChunkPrint dest (data, _sentLength, len);
|
||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||
_root.prettyPrintTo (dest);
|
||||
#else
|
||||
serializeJsonPretty(_root, dest);
|
||||
#endif
|
||||
return len;
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::function<void(AsyncWebServerRequest *request, JsonVariant &json)> ArJsonRequestHandlerFunction;
|
||||
|
||||
class AsyncCallbackJsonWebHandler: public AsyncWebHandler {
|
||||
private:
|
||||
protected:
|
||||
const String _uri;
|
||||
WebRequestMethodComposite _method;
|
||||
ArJsonRequestHandlerFunction _onRequest;
|
||||
size_t _contentLength;
|
||||
#ifndef ARDUINOJSON_5_COMPATIBILITY
|
||||
const size_t maxJsonBufferSize;
|
||||
#endif
|
||||
size_t _maxContentLength;
|
||||
public:
|
||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||
AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest)
|
||||
: _uri(uri), _method(HTTP_POST|HTTP_PUT|HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
|
||||
#else
|
||||
AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize=DYNAMIC_JSON_DOCUMENT_SIZE)
|
||||
: _uri(uri), _method(HTTP_POST|HTTP_PUT|HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
|
||||
#endif
|
||||
|
||||
void setMethod(WebRequestMethodComposite method){ _method = method; }
|
||||
void setMaxContentLength(int maxContentLength){ _maxContentLength = maxContentLength; }
|
||||
void onRequest(ArJsonRequestHandlerFunction fn){ _onRequest = fn; }
|
||||
|
||||
virtual bool canHandle(AsyncWebServerRequest *request) override final{
|
||||
if(!_onRequest)
|
||||
return false;
|
||||
|
||||
if(!(_method & request->method()))
|
||||
return false;
|
||||
|
||||
if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/")))
|
||||
return false;
|
||||
|
||||
if ( !request->contentType().equalsIgnoreCase(JSON_MIMETYPE) )
|
||||
return false;
|
||||
|
||||
request->addInterestingHeader("ANY");
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void handleRequest(AsyncWebServerRequest *request) override final {
|
||||
if(_onRequest) {
|
||||
if (request->_tempObject != NULL) {
|
||||
|
||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonVariant json = jsonBuffer.parse((uint8_t*)(request->_tempObject));
|
||||
if (json.success()) {
|
||||
#else
|
||||
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
|
||||
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject));
|
||||
if(!error) {
|
||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||
_contentLength = measureJson(_root);
|
||||
#endif
|
||||
|
||||
_onRequest(request, json);
|
||||
return;
|
||||
if (_contentLength) {
|
||||
_isValid = true;
|
||||
}
|
||||
}
|
||||
request->send(_contentLength > _maxContentLength ? 413 : 400);
|
||||
} else {
|
||||
request->send(500);
|
||||
return _contentLength;
|
||||
}
|
||||
}
|
||||
virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final {
|
||||
}
|
||||
virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final {
|
||||
if (_onRequest) {
|
||||
_contentLength = total;
|
||||
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
|
||||
request->_tempObject = malloc(total);
|
||||
}
|
||||
if (request->_tempObject != NULL) {
|
||||
memcpy((uint8_t*)(request->_tempObject) + index, data, len);
|
||||
}
|
||||
|
||||
size_t getSize() {
|
||||
return _jsonBuffer.size();
|
||||
}
|
||||
|
||||
size_t _fillBuffer(uint8_t * data, size_t len) {
|
||||
ChunkPrint dest(data, _sentLength, len);
|
||||
|
||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||
_root.printTo(dest);
|
||||
#else
|
||||
serializeJson(_root, dest);
|
||||
#endif
|
||||
return len;
|
||||
}
|
||||
};
|
||||
|
||||
class PrettyAsyncJsonResponse : public AsyncJsonResponse {
|
||||
public:
|
||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||
PrettyAsyncJsonResponse(bool isArray = false)
|
||||
: AsyncJsonResponse{isArray} {
|
||||
}
|
||||
#else
|
||||
PrettyAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE)
|
||||
: AsyncJsonResponse{isArray, maxJsonBufferSize} {
|
||||
}
|
||||
#endif
|
||||
size_t setLength() {
|
||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||
_contentLength = _root.measurePrettyLength();
|
||||
#else
|
||||
_contentLength = measureJsonPretty(_root);
|
||||
#endif
|
||||
if (_contentLength) {
|
||||
_isValid = true;
|
||||
}
|
||||
return _contentLength;
|
||||
}
|
||||
size_t _fillBuffer(uint8_t * data, size_t len) {
|
||||
ChunkPrint dest(data, _sentLength, len);
|
||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||
_root.prettyPrintTo(dest);
|
||||
#else
|
||||
serializeJsonPretty(_root, dest);
|
||||
// serializeJson(_root, Serial); // for testing
|
||||
// Serial.println();
|
||||
#endif
|
||||
return len;
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::function<void(AsyncWebServerRequest * request, JsonVariant & json)> ArJsonRequestHandlerFunction;
|
||||
|
||||
class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
|
||||
private:
|
||||
protected:
|
||||
const String _uri;
|
||||
WebRequestMethodComposite _method;
|
||||
ArJsonRequestHandlerFunction _onRequest;
|
||||
size_t _contentLength;
|
||||
#ifndef ARDUINOJSON_5_COMPATIBILITY
|
||||
const size_t maxJsonBufferSize;
|
||||
#endif
|
||||
size_t _maxContentLength;
|
||||
|
||||
public:
|
||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||
AsyncCallbackJsonWebHandler(const String & uri, ArJsonRequestHandlerFunction onRequest)
|
||||
: _uri(uri)
|
||||
, _method(HTTP_POST | HTTP_PUT | HTTP_PATCH)
|
||||
, _onRequest(onRequest)
|
||||
, _maxContentLength(16384) {
|
||||
}
|
||||
#else
|
||||
AsyncCallbackJsonWebHandler(const String & uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE)
|
||||
: _uri(uri)
|
||||
, _method(HTTP_POST | HTTP_PUT | HTTP_PATCH)
|
||||
, _onRequest(onRequest)
|
||||
, maxJsonBufferSize(maxJsonBufferSize)
|
||||
, _maxContentLength(16384) {
|
||||
}
|
||||
#endif
|
||||
|
||||
void setMethod(WebRequestMethodComposite method) {
|
||||
_method = method;
|
||||
}
|
||||
void setMaxContentLength(int maxContentLength) {
|
||||
_maxContentLength = maxContentLength;
|
||||
}
|
||||
void onRequest(ArJsonRequestHandlerFunction fn) {
|
||||
_onRequest = fn;
|
||||
}
|
||||
|
||||
virtual bool canHandle(AsyncWebServerRequest * request) override final {
|
||||
if (!_onRequest)
|
||||
return false;
|
||||
|
||||
if (!(_method & request->method()))
|
||||
return false;
|
||||
|
||||
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/")))
|
||||
return false;
|
||||
|
||||
if (!request->contentType().equalsIgnoreCase(JSON_MIMETYPE))
|
||||
return false;
|
||||
|
||||
request->addInterestingHeader("ANY");
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void handleRequest(AsyncWebServerRequest * request) override final {
|
||||
if (_onRequest) {
|
||||
if (request->_tempObject != NULL) {
|
||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonVariant json = jsonBuffer.parse((uint8_t *)(request->_tempObject));
|
||||
if (json.success()) {
|
||||
#else
|
||||
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
|
||||
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
|
||||
if (!error) {
|
||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||
#endif
|
||||
|
||||
_onRequest(request, json);
|
||||
return;
|
||||
}
|
||||
}
|
||||
request->send(_contentLength > _maxContentLength ? 413 : 400);
|
||||
} else {
|
||||
request->send(500);
|
||||
}
|
||||
}
|
||||
virtual void handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) override final {
|
||||
}
|
||||
virtual void handleBody(AsyncWebServerRequest * request, uint8_t * data, size_t len, size_t index, size_t total) override final {
|
||||
if (_onRequest) {
|
||||
_contentLength = total;
|
||||
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
|
||||
request->_tempObject = malloc(total);
|
||||
}
|
||||
if (request->_tempObject != NULL) {
|
||||
memcpy((uint8_t *)(request->_tempObject) + index, data, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
virtual bool isRequestHandlerTrivial() override final {
|
||||
return _onRequest ? false : true;
|
||||
}
|
||||
}
|
||||
virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;}
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -28,9 +28,9 @@ PButton::PButton() {
|
||||
LongPressDelay_ = 750; // Hold period for a long press event (in ms)
|
||||
VLongPressDelay_ = 3000; // Hold period for a very long press event (in ms)
|
||||
|
||||
cb_onClick = nullptr;
|
||||
cb_onDblClick = nullptr;
|
||||
cb_onLongPress = nullptr;
|
||||
cb_onClick = nullptr;
|
||||
cb_onDblClick = nullptr;
|
||||
cb_onLongPress = nullptr;
|
||||
cb_onVLongPress = nullptr;
|
||||
|
||||
// Initialization of variables
|
||||
@@ -144,7 +144,7 @@ bool PButton::check(void) {
|
||||
|
||||
// added code: raise OnVLongPress event when the button is released, only for pin 0
|
||||
if (state_ == pullMode_ && vLongPressHappened_) {
|
||||
resultEvent = 4;
|
||||
resultEvent = 4;
|
||||
vLongPressHappened_ = false;
|
||||
longPressHappened_ = false;
|
||||
}
|
||||
|
||||
@@ -22,22 +22,20 @@ void APSettingsService::reconfigureAP() {
|
||||
}
|
||||
|
||||
void APSettingsService::loop() {
|
||||
// if we have an ETH connection, quit
|
||||
if (emsesp::EMSESP::system_.ethernet_connected()) {
|
||||
return;
|
||||
}
|
||||
unsigned long currentMillis = uuid::get_uptime();
|
||||
unsigned long manageElapsed = (uint32_t)(currentMillis - _lastManaged);
|
||||
if (manageElapsed >= MANAGE_NETWORK_DELAY) {
|
||||
_lastManaged = currentMillis;
|
||||
manageAP();
|
||||
}
|
||||
|
||||
handleDNS();
|
||||
}
|
||||
|
||||
void APSettingsService::manageAP() {
|
||||
WiFiMode_t currentWiFiMode = WiFi.getMode();
|
||||
if (_state.provisionMode == AP_MODE_ALWAYS || (_state.provisionMode == AP_MODE_DISCONNECTED && WiFi.status() != WL_CONNECTED)) {
|
||||
WiFiMode_t currentWiFiMode = WiFi.getMode();
|
||||
bool network_connected = (emsesp::EMSESP::system_.ethernet_connected() || (WiFi.status() == WL_CONNECTED));
|
||||
if (_state.provisionMode == AP_MODE_ALWAYS || (_state.provisionMode == AP_MODE_DISCONNECTED && !network_connected)) {
|
||||
if (_reconfigureAp || currentWiFiMode == WIFI_OFF || currentWiFiMode == WIFI_STA) {
|
||||
startAP();
|
||||
}
|
||||
@@ -48,13 +46,11 @@ void APSettingsService::manageAP() {
|
||||
}
|
||||
|
||||
void APSettingsService::startAP() {
|
||||
// Serial.println(F("Starting software access point"));
|
||||
WiFi.softAPConfig(_state.localIP, _state.gatewayIP, _state.subnetMask);
|
||||
WiFi.softAP(_state.ssid.c_str(), _state.password.c_str());
|
||||
if (!_dnsServer) {
|
||||
IPAddress apIp = WiFi.softAPIP();
|
||||
// Serial.print(F("Starting captive portal on "));
|
||||
// Serial.println(apIp);
|
||||
emsesp::EMSESP::logger().info(F("Starting Access Point with captive portal on %s"), apIp.toString().c_str());
|
||||
_dnsServer = new DNSServer;
|
||||
_dnsServer->start(DNS_PORT, "*", apIp);
|
||||
}
|
||||
@@ -62,12 +58,11 @@ void APSettingsService::startAP() {
|
||||
|
||||
void APSettingsService::stopAP() {
|
||||
if (_dnsServer) {
|
||||
// Serial.println(F("Stopping captive portal"));
|
||||
emsesp::EMSESP::logger().info(F("Stopping Access Point"));
|
||||
_dnsServer->stop();
|
||||
delete _dnsServer;
|
||||
_dnsServer = nullptr;
|
||||
}
|
||||
// Serial.println(F("Stopping software access point"));
|
||||
WiFi.softAPdisconnect(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#include <APStatus.h>
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
APStatus::APStatus(AsyncWebServer * server, SecurityManager * securityManager, APSettingsService * apSettingsService)
|
||||
: _apSettingsService(apSettingsService) {
|
||||
server->on(AP_STATUS_SERVICE_PATH,
|
||||
HTTP_GET,
|
||||
securityManager->wrapRequest(std::bind(&APStatus::apStatus, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
server->on(AP_STATUS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&APStatus::apStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
}
|
||||
|
||||
void APStatus::apStatus(AsyncWebServerRequest * request) {
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
#ifndef APStatus_h
|
||||
#define APStatus_h
|
||||
|
||||
#ifdef ESP32
|
||||
#include <WiFi.h>
|
||||
#include <AsyncTCP.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESPAsyncTCP.h>
|
||||
#endif
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <AsyncJson.h>
|
||||
|
||||
@@ -15,14 +15,13 @@ String ArduinoJsonJWT::getSecret() {
|
||||
/*
|
||||
* ESP32 uses mbedtls, ESP2866 uses bearssl.
|
||||
*
|
||||
* Both come with decent HMAC implmentations supporting sha256, as well as others.
|
||||
* 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];
|
||||
{
|
||||
#ifdef ESP32
|
||||
mbedtls_md_context_t ctx;
|
||||
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
|
||||
mbedtls_md_init(&ctx);
|
||||
@@ -31,14 +30,6 @@ String ArduinoJsonJWT::sign(String & payload) {
|
||||
mbedtls_md_hmac_update(&ctx, (unsigned char *)payload.c_str(), payload.length());
|
||||
mbedtls_md_hmac_finish(&ctx, hmacResult);
|
||||
mbedtls_md_free(&ctx);
|
||||
#elif defined(ESP8266)
|
||||
br_hmac_key_context keyCtx;
|
||||
br_hmac_key_init(&keyCtx, &br_sha256_vtable, _secret.c_str(), _secret.length());
|
||||
br_hmac_context hmacCtx;
|
||||
br_hmac_init(&hmacCtx, &keyCtx, 0);
|
||||
br_hmac_update(&hmacCtx, payload.c_str(), payload.length());
|
||||
br_hmac_out(&hmacCtx, hmacResult);
|
||||
#endif
|
||||
}
|
||||
return encode((char *)hmacResult, 32);
|
||||
}
|
||||
@@ -94,13 +85,8 @@ void ArduinoJsonJWT::parseJWT(String jwt, JsonDocument & jsonDocument) {
|
||||
String ArduinoJsonJWT::encode(const char * cstr, int inputLen) {
|
||||
// prepare encoder
|
||||
base64_encodestate _state;
|
||||
#ifdef ESP32
|
||||
base64_init_encodestate(&_state);
|
||||
size_t encodedLength = base64_encode_expected_len(inputLen) + 1;
|
||||
#elif defined(ESP8266)
|
||||
base64_init_encodestate_nonewlines(&_state);
|
||||
size_t encodedLength = base64_encode_expected_len_nonewlines(inputLen) + 1;
|
||||
#endif
|
||||
// prepare buffer of correct length, returning an empty string on failure
|
||||
char * buffer = (char *)malloc(encodedLength * sizeof(char));
|
||||
if (buffer == nullptr) {
|
||||
|
||||
@@ -5,12 +5,7 @@
|
||||
#include <ArduinoJson.h>
|
||||
#include <libb64/cdecode.h>
|
||||
#include <libb64/cencode.h>
|
||||
|
||||
#ifdef ESP32
|
||||
#include <mbedtls/md.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <bearssl/bearssl_hmac.h>
|
||||
#endif
|
||||
|
||||
class ArduinoJsonJWT {
|
||||
private:
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include <AuthenticationService.h>
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
#if FT_ENABLED(FT_SECURITY)
|
||||
|
||||
AuthenticationService::AuthenticationService(AsyncWebServer * server, SecurityManager * securityManager)
|
||||
: _securityManager(securityManager)
|
||||
, _signInHandler(SIGN_IN_PATH, std::bind(&AuthenticationService::signIn, this, std::placeholders::_1, std::placeholders::_2)) {
|
||||
server->on(VERIFY_AUTHORIZATION_PATH, HTTP_GET, std::bind(&AuthenticationService::verifyAuthorization, this, std::placeholders::_1));
|
||||
, _signInHandler(SIGN_IN_PATH, std::bind(&AuthenticationService::signIn, this, _1, _2)) {
|
||||
server->on(VERIFY_AUTHORIZATION_PATH, HTTP_GET, std::bind(&AuthenticationService::verifyAuthorization, this, _1));
|
||||
_signInHandler.setMethod(HTTP_POST);
|
||||
_signInHandler.setMaxContentLength(MAX_AUTHENTICATION_SIZE);
|
||||
server->addHandler(&_signInHandler);
|
||||
|
||||
@@ -40,13 +40,6 @@ ESP8266React::ESP8266React(AsyncWebServer * server, FS * fs)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Disable CORS if required
|
||||
#if defined(ENABLE_CORS)
|
||||
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", CORS_ORIGIN);
|
||||
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Accept, Content-Type, Authorization");
|
||||
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Credentials", "true");
|
||||
#endif
|
||||
}
|
||||
|
||||
void ESP8266React::begin() {
|
||||
|
||||
@@ -6,11 +6,7 @@
|
||||
class ESPUtils {
|
||||
public:
|
||||
static String defaultDeviceValue(String prefix = "") {
|
||||
#ifdef ESP32
|
||||
return prefix + String((uint32_t)ESP.getEfuseMac(), HEX);
|
||||
#elif defined(ESP8266)
|
||||
return prefix + String(ESP.getChipId(), HEX);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ class FSPersistence {
|
||||
|
||||
// debug added by Proddy
|
||||
#if defined(EMSESP_DEBUG)
|
||||
Serial.printf("Read File: %s: ", _filePath);
|
||||
Serial.printf("Reading file: %s: ", _filePath);
|
||||
serializeJson(jsonDocument, Serial);
|
||||
Serial.println();
|
||||
#endif
|
||||
@@ -63,7 +63,7 @@ class FSPersistence {
|
||||
|
||||
// debug added by Proddy
|
||||
#if defined(EMSESP_DEBUG)
|
||||
Serial.printf("Write File: %s: ", _filePath);
|
||||
Serial.printf("Writing to file: %s: ", _filePath);
|
||||
serializeJson(jsonDocument, Serial);
|
||||
Serial.println();
|
||||
#endif
|
||||
|
||||
@@ -16,7 +16,6 @@ void FactoryResetService::handleRequest(AsyncWebServerRequest * request) {
|
||||
* Delete function assumes that all files are stored flat, within the config directory.
|
||||
*/
|
||||
void FactoryResetService::factoryReset() {
|
||||
#ifdef ESP32
|
||||
/*
|
||||
* Based on LITTLEFS. Modified by proddy
|
||||
* Could be replaced with fs.rmdir(FS_CONFIG_DIRECTORY) in IDF 4.2
|
||||
@@ -28,14 +27,5 @@ void FactoryResetService::factoryReset() {
|
||||
file.close();
|
||||
fs->remove(pathStr);
|
||||
}
|
||||
#elif defined(ESP8266)
|
||||
Dir configDirectory = fs->openDir(FS_CONFIG_DIRECTORY);
|
||||
while (configDirectory.next()) {
|
||||
String path = FS_CONFIG_DIRECTORY;
|
||||
path.concat("/");
|
||||
path.concat(configDirectory.fileName());
|
||||
fs->remove(path);
|
||||
}
|
||||
#endif
|
||||
RestartService::restartNow();
|
||||
}
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
#ifndef FactoryResetService_h
|
||||
#define FactoryResetService_h
|
||||
|
||||
#ifdef ESP32
|
||||
#include <WiFi.h>
|
||||
#include <AsyncTCP.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESPAsyncTCP.h>
|
||||
#endif
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <SecurityManager.h>
|
||||
#include <RestartService.h>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#ifndef Features_h
|
||||
#define Features_h
|
||||
|
||||
// modified by Proddy
|
||||
|
||||
#define FT_ENABLED(feature) feature
|
||||
|
||||
// project feature off by default
|
||||
// project feature on by default
|
||||
#ifndef FT_PROJECT
|
||||
#define FT_PROJECT 0
|
||||
#define FT_PROJECT 1
|
||||
#endif
|
||||
|
||||
// security feature on by default
|
||||
@@ -23,14 +25,14 @@
|
||||
#define FT_NTP 1
|
||||
#endif
|
||||
|
||||
// mqtt feature on by default
|
||||
// ota feature on by default
|
||||
#ifndef FT_OTA
|
||||
#define FT_OTA 1
|
||||
#endif
|
||||
|
||||
// upload firmware feature off by default
|
||||
// upload firmware feature on by default
|
||||
#ifndef FT_UPLOAD_FIRMWARE
|
||||
#define FT_UPLOAD_FIRMWARE 0
|
||||
#define FT_UPLOAD_FIRMWARE 1
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include <FeaturesService.h>
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
FeaturesService::FeaturesService(AsyncWebServer * server) {
|
||||
server->on(FEATURES_SERVICE_PATH, HTTP_GET, std::bind(&FeaturesService::features, this, std::placeholders::_1));
|
||||
server->on(FEATURES_SERVICE_PATH, HTTP_GET, std::bind(&FeaturesService::features, this, _1));
|
||||
}
|
||||
|
||||
void FeaturesService::features(AsyncWebServerRequest * request) {
|
||||
|
||||
@@ -3,14 +3,8 @@
|
||||
|
||||
#include <Features.h>
|
||||
|
||||
#ifdef ESP32
|
||||
#include <WiFi.h>
|
||||
#include <AsyncTCP.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESPAsyncTCP.h>
|
||||
#endif
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <AsyncJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
#define HTTP_ENDPOINT_ORIGIN_ID "http"
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
template <class T>
|
||||
class HttpGetEndpoint {
|
||||
public:
|
||||
@@ -24,20 +26,14 @@ class HttpGetEndpoint {
|
||||
: _stateReader(stateReader)
|
||||
, _statefulService(statefulService)
|
||||
, _bufferSize(bufferSize) {
|
||||
server->on(servicePath.c_str(),
|
||||
HTTP_GET,
|
||||
securityManager->wrapRequest(std::bind(&HttpGetEndpoint::fetchSettings, this, std::placeholders::_1), authenticationPredicate));
|
||||
server->on(servicePath.c_str(), HTTP_GET, securityManager->wrapRequest(std::bind(&HttpGetEndpoint::fetchSettings, this, _1), authenticationPredicate));
|
||||
}
|
||||
|
||||
HttpGetEndpoint(JsonStateReader<T> stateReader,
|
||||
StatefulService<T> * statefulService,
|
||||
AsyncWebServer * server,
|
||||
const String & servicePath,
|
||||
size_t bufferSize = DEFAULT_BUFFER_SIZE)
|
||||
HttpGetEndpoint(JsonStateReader<T> stateReader, StatefulService<T> * statefulService, AsyncWebServer * server, const String & servicePath, size_t bufferSize = DEFAULT_BUFFER_SIZE)
|
||||
: _stateReader(stateReader)
|
||||
, _statefulService(statefulService)
|
||||
, _bufferSize(bufferSize) {
|
||||
server->on(servicePath.c_str(), HTTP_GET, std::bind(&HttpGetEndpoint::fetchSettings, this, std::placeholders::_1));
|
||||
server->on(servicePath.c_str(), HTTP_GET, std::bind(&HttpGetEndpoint::fetchSettings, this, _1));
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -69,25 +65,17 @@ class HttpPostEndpoint {
|
||||
: _stateReader(stateReader)
|
||||
, _stateUpdater(stateUpdater)
|
||||
, _statefulService(statefulService)
|
||||
, _updateHandler(servicePath,
|
||||
securityManager->wrapCallback(std::bind(&HttpPostEndpoint::updateSettings, this, std::placeholders::_1, std::placeholders::_2),
|
||||
authenticationPredicate),
|
||||
bufferSize)
|
||||
, _updateHandler(servicePath, securityManager->wrapCallback(std::bind(&HttpPostEndpoint::updateSettings, this, _1, _2), authenticationPredicate), bufferSize)
|
||||
, _bufferSize(bufferSize) {
|
||||
_updateHandler.setMethod(HTTP_POST);
|
||||
server->addHandler(&_updateHandler);
|
||||
}
|
||||
|
||||
HttpPostEndpoint(JsonStateReader<T> stateReader,
|
||||
JsonStateUpdater<T> stateUpdater,
|
||||
StatefulService<T> * statefulService,
|
||||
AsyncWebServer * server,
|
||||
const String & servicePath,
|
||||
size_t bufferSize = DEFAULT_BUFFER_SIZE)
|
||||
HttpPostEndpoint(JsonStateReader<T> stateReader, JsonStateUpdater<T> stateUpdater, StatefulService<T> * statefulService, AsyncWebServer * server, const String & servicePath, size_t bufferSize = DEFAULT_BUFFER_SIZE)
|
||||
: _stateReader(stateReader)
|
||||
, _stateUpdater(stateUpdater)
|
||||
, _statefulService(statefulService)
|
||||
, _updateHandler(servicePath, std::bind(&HttpPostEndpoint::updateSettings, this, std::placeholders::_1, std::placeholders::_2), bufferSize)
|
||||
, _updateHandler(servicePath, std::bind(&HttpPostEndpoint::updateSettings, this, _1, _2), bufferSize)
|
||||
, _bufferSize(bufferSize) {
|
||||
_updateHandler.setMethod(HTTP_POST);
|
||||
server->addHandler(&_updateHandler);
|
||||
@@ -137,12 +125,7 @@ class HttpEndpoint : public HttpGetEndpoint<T>, public HttpPostEndpoint<T> {
|
||||
, HttpPostEndpoint<T>(stateReader, stateUpdater, statefulService, server, servicePath, securityManager, authenticationPredicate, bufferSize) {
|
||||
}
|
||||
|
||||
HttpEndpoint(JsonStateReader<T> stateReader,
|
||||
JsonStateUpdater<T> stateUpdater,
|
||||
StatefulService<T> * statefulService,
|
||||
AsyncWebServer * server,
|
||||
const String & servicePath,
|
||||
size_t bufferSize = DEFAULT_BUFFER_SIZE)
|
||||
HttpEndpoint(JsonStateReader<T> stateReader, JsonStateUpdater<T> stateUpdater, StatefulService<T> * statefulService, AsyncWebServer * server, const String & servicePath, size_t bufferSize = DEFAULT_BUFFER_SIZE)
|
||||
: HttpGetEndpoint<T>(stateReader, statefulService, server, servicePath, bufferSize)
|
||||
, HttpPostEndpoint<T>(stateReader, stateUpdater, statefulService, server, servicePath, bufferSize) {
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <StatefulService.h>
|
||||
#include <AsyncMqttClient.h>
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
#define MQTT_ORIGIN_ID "mqtt"
|
||||
|
||||
template <class T>
|
||||
@@ -31,11 +33,7 @@ class MqttConnector {
|
||||
template <class T>
|
||||
class MqttPub : virtual public MqttConnector<T> {
|
||||
public:
|
||||
MqttPub(JsonStateReader<T> stateReader,
|
||||
StatefulService<T> * statefulService,
|
||||
AsyncMqttClient * mqttClient,
|
||||
const String & pubTopic = "",
|
||||
size_t bufferSize = DEFAULT_BUFFER_SIZE)
|
||||
MqttPub(JsonStateReader<T> stateReader, StatefulService<T> * statefulService, AsyncMqttClient * mqttClient, const String & pubTopic = "", size_t bufferSize = DEFAULT_BUFFER_SIZE)
|
||||
: MqttConnector<T>(statefulService, mqttClient, bufferSize)
|
||||
, _stateReader(stateReader)
|
||||
, _pubTopic(pubTopic) {
|
||||
@@ -76,22 +74,11 @@ class MqttPub : virtual public MqttConnector<T> {
|
||||
template <class T>
|
||||
class MqttSub : virtual public MqttConnector<T> {
|
||||
public:
|
||||
MqttSub(JsonStateUpdater<T> stateUpdater,
|
||||
StatefulService<T> * statefulService,
|
||||
AsyncMqttClient * mqttClient,
|
||||
const String & subTopic = "",
|
||||
size_t bufferSize = DEFAULT_BUFFER_SIZE)
|
||||
MqttSub(JsonStateUpdater<T> stateUpdater, StatefulService<T> * statefulService, AsyncMqttClient * mqttClient, const String & subTopic = "", size_t bufferSize = DEFAULT_BUFFER_SIZE)
|
||||
: MqttConnector<T>(statefulService, mqttClient, bufferSize)
|
||||
, _stateUpdater(stateUpdater)
|
||||
, _subTopic(subTopic) {
|
||||
MqttConnector<T>::_mqttClient->onMessage(std::bind(&MqttSub::onMqttMessage,
|
||||
this,
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2,
|
||||
std::placeholders::_3,
|
||||
std::placeholders::_4,
|
||||
std::placeholders::_5,
|
||||
std::placeholders::_6));
|
||||
MqttConnector<T>::_mqttClient->onMessage(std::bind(&MqttSub::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
}
|
||||
|
||||
void setSubTopic(const String & subTopic) {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "../../src/emsesp_stub.hpp" // proddy added
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
/**
|
||||
* Retains a copy of the cstr provided in the pointer provided using dynamic allocation.
|
||||
*
|
||||
@@ -33,10 +35,9 @@ MqttSettingsService::MqttSettingsService(AsyncWebServer * server, FS * fs, Secur
|
||||
, _disconnectedAt(0)
|
||||
, _disconnectReason(AsyncMqttClientDisconnectReason::TCP_DISCONNECTED)
|
||||
, _mqttClient() {
|
||||
WiFi.onEvent(std::bind(&MqttSettingsService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2), WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
|
||||
WiFi.onEvent(std::bind(&MqttSettingsService::onStationModeGotIP, this, std::placeholders::_1, std::placeholders::_2), WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP);
|
||||
_mqttClient.onConnect(std::bind(&MqttSettingsService::onMqttConnect, this, std::placeholders::_1));
|
||||
_mqttClient.onDisconnect(std::bind(&MqttSettingsService::onMqttDisconnect, this, std::placeholders::_1));
|
||||
WiFi.onEvent(std::bind(&MqttSettingsService::WiFiEvent, this, _1, _2));
|
||||
_mqttClient.onConnect(std::bind(&MqttSettingsService::onMqttConnect, this, _1));
|
||||
_mqttClient.onDisconnect(std::bind(&MqttSettingsService::onMqttDisconnect, this, _1));
|
||||
addUpdateHandler([&](const String & originId) { onConfigUpdated(); }, false);
|
||||
}
|
||||
|
||||
@@ -79,17 +80,11 @@ AsyncMqttClient * MqttSettingsService::getMqttClient() {
|
||||
}
|
||||
|
||||
void MqttSettingsService::onMqttConnect(bool sessionPresent) {
|
||||
// Serial.print(F("Connected to MQTT, "));
|
||||
// if (sessionPresent) {
|
||||
// Serial.println(F("with persistent session"));
|
||||
// } else {
|
||||
// Serial.println(F("without persistent session"));
|
||||
// }
|
||||
// emsesp::EMSESP::logger().info(F("Connected to MQTT, %s"), (sessionPresent) ? F("with persistent session") : F("without persistent session"));
|
||||
}
|
||||
|
||||
void MqttSettingsService::onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
|
||||
// Serial.print(F("Disconnected from MQTT reason: "));
|
||||
// Serial.println((uint8_t)reason);
|
||||
// emsesp::EMSESP::logger().info(F("Disconnected from MQTT reason: %s"), (uint8_t)reason);
|
||||
_disconnectReason = reason;
|
||||
_disconnectedAt = uuid::get_uptime();
|
||||
}
|
||||
@@ -99,47 +94,38 @@ void MqttSettingsService::onConfigUpdated() {
|
||||
_disconnectedAt = 0;
|
||||
|
||||
// added by proddy
|
||||
// reload EMS-ESP MQTT settings
|
||||
emsesp::EMSESP::mqtt_.start();
|
||||
emsesp::EMSESP::mqtt_.start(); // reload EMS-ESP MQTT settings
|
||||
}
|
||||
|
||||
#ifdef ESP32
|
||||
void MqttSettingsService::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
if (_state.enabled) {
|
||||
// Serial.println(F("WiFi connection dropped, starting MQTT client."));
|
||||
onConfigUpdated();
|
||||
}
|
||||
}
|
||||
void MqttSettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
switch (event) {
|
||||
case SYSTEM_EVENT_STA_GOT_IP:
|
||||
case SYSTEM_EVENT_ETH_GOT_IP:
|
||||
if (_state.enabled) {
|
||||
// emsesp::EMSESP::logger().info(F("Network connection found, starting MQTT client"));
|
||||
onConfigUpdated();
|
||||
}
|
||||
break;
|
||||
|
||||
void MqttSettingsService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
if (_state.enabled) {
|
||||
// Serial.println(F("WiFi connection dropped, stopping MQTT client."));
|
||||
onConfigUpdated();
|
||||
}
|
||||
}
|
||||
#elif defined(ESP8266)
|
||||
void MqttSettingsService::onStationModeGotIP(const WiFiEventStationModeGotIP & event) {
|
||||
if (_state.enabled) {
|
||||
// Serial.println(F("WiFi connection dropped, starting MQTT client."));
|
||||
onConfigUpdated();
|
||||
}
|
||||
}
|
||||
case SYSTEM_EVENT_STA_DISCONNECTED:
|
||||
case SYSTEM_EVENT_ETH_DISCONNECTED:
|
||||
if (_state.enabled) {
|
||||
// emsesp::EMSESP::logger().info(F("Network connection dropped, stopping MQTT client"));
|
||||
onConfigUpdated();
|
||||
}
|
||||
break;
|
||||
|
||||
void MqttSettingsService::onStationModeDisconnected(const WiFiEventStationModeDisconnected & event) {
|
||||
if (_state.enabled) {
|
||||
// Serial.println(F("WiFi connection dropped, stopping MQTT client."));
|
||||
onConfigUpdated();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void MqttSettingsService::configureMqtt() {
|
||||
// disconnect if currently connected
|
||||
_mqttClient.disconnect();
|
||||
|
||||
// only connect if WiFi is connected and MQTT is enabled
|
||||
if (_state.enabled && WiFi.isConnected()) {
|
||||
// Serial.println(F("Connecting to MQTT..."));
|
||||
if (_state.enabled && emsesp::EMSESP::system_.network_connected()) {
|
||||
_mqttClient.setServer(retainCstr(_state.host.c_str(), &_retainedHost), _state.port);
|
||||
if (_state.username.length() > 0) {
|
||||
_mqttClient.setCredentials(retainCstr(_state.username.c_str(), &_retainedUsername), retainCstr(_state.password.length() > 0 ? _state.password.c_str() : nullptr, &_retainedPassword));
|
||||
@@ -153,7 +139,7 @@ void MqttSettingsService::configureMqtt() {
|
||||
_mqttClient.connect();
|
||||
}
|
||||
|
||||
emsesp::EMSESP::dallassensor_.reload();
|
||||
emsesp::EMSESP::dallassensor_.reload(); // added by Proddy for EMS-ESP
|
||||
}
|
||||
|
||||
void MqttSettings::read(MqttSettings & settings, JsonObject & root) {
|
||||
@@ -182,6 +168,7 @@ void MqttSettings::read(MqttSettings & settings, JsonObject & root) {
|
||||
root["ha_climate_format"] = settings.ha_climate_format;
|
||||
root["ha_enabled"] = settings.ha_enabled;
|
||||
root["nested_format"] = settings.nested_format;
|
||||
root["subscribe_format"] = settings.subscribe_format;
|
||||
}
|
||||
|
||||
StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & settings) {
|
||||
@@ -213,6 +200,7 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting
|
||||
newSettings.ha_climate_format = root["ha_climate_format"] | EMSESP_DEFAULT_HA_CLIMATE_FORMAT;
|
||||
newSettings.ha_enabled = root["ha_enabled"] | EMSESP_DEFAULT_HA_ENABLED;
|
||||
newSettings.nested_format = root["nested_format"] | EMSESP_DEFAULT_NESTED_FORMAT;
|
||||
newSettings.subscribe_format = root["subscribe_format"] | EMSESP_DEFAULT_SUBSCRIBE_FORMAT;
|
||||
|
||||
if (newSettings.mqtt_qos != settings.mqtt_qos) {
|
||||
emsesp::EMSESP::mqtt_.set_qos(newSettings.mqtt_qos);
|
||||
@@ -228,6 +216,10 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (newSettings.subscribe_format != settings.subscribe_format) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (newSettings.ha_climate_format != settings.ha_climate_format) {
|
||||
emsesp::EMSESP::mqtt_.ha_climate_format(newSettings.ha_climate_format);
|
||||
changed = true;
|
||||
|
||||
@@ -41,11 +41,7 @@
|
||||
#ifndef FACTORY_MQTT_CLIENT_ID
|
||||
#define FACTORY_MQTT_CLIENT_ID generateClientId()
|
||||
static String generateClientId() {
|
||||
#ifdef ESP32
|
||||
return ESPUtils::defaultDeviceValue("esp32-");
|
||||
#elif defined(ESP8266)
|
||||
return ESPUtils::defaultDeviceValue("esp8266-");
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -61,22 +57,12 @@ static String generateClientId() {
|
||||
#define FACTORY_MQTT_MAX_TOPIC_LENGTH 128
|
||||
#endif
|
||||
|
||||
#define EMSESP_DEFAULT_BOOL_FORMAT 1 // on/off
|
||||
#define EMSESP_DEFAULT_DALLAS_FORMAT 1 // sensorid
|
||||
#define EMSESP_DEFAULT_HA_CLIMATE_FORMAT 1 // current temp
|
||||
#define EMSESP_DEFAULT_MQTT_QOS 0
|
||||
#define EMSESP_DEFAULT_MQTT_RETAIN false
|
||||
#define EMSESP_DEFAULT_HA_ENABLED false
|
||||
#define EMSESP_DEFAULT_PUBLISH_TIME 10
|
||||
#define EMSESP_DEFAULT_NESTED_FORMAT true
|
||||
|
||||
class MqttSettings {
|
||||
public:
|
||||
// host and port - if enabled
|
||||
bool enabled;
|
||||
String host;
|
||||
uint16_t port;
|
||||
String base;
|
||||
|
||||
// username and password
|
||||
String username;
|
||||
@@ -91,6 +77,7 @@ class MqttSettings {
|
||||
uint16_t maxTopicLength;
|
||||
|
||||
// proddy EMS-ESP specific
|
||||
String base;
|
||||
uint16_t publish_time_boiler;
|
||||
uint16_t publish_time_thermostat;
|
||||
uint16_t publish_time_solar;
|
||||
@@ -103,7 +90,8 @@ class MqttSettings {
|
||||
uint8_t bool_format;
|
||||
uint8_t ha_climate_format;
|
||||
bool ha_enabled;
|
||||
bool nested_format;
|
||||
uint8_t nested_format;
|
||||
uint8_t subscribe_format;
|
||||
|
||||
static void read(MqttSettings & settings, JsonObject & root);
|
||||
static StateUpdateResult update(JsonObject & root, MqttSettings & settings);
|
||||
@@ -146,8 +134,7 @@ class MqttSettingsService : public StatefulService<MqttSettings> {
|
||||
// the MQTT client instance
|
||||
AsyncMqttClient _mqttClient;
|
||||
|
||||
void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||
void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||
void onMqttConnect(bool sessionPresent);
|
||||
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason);
|
||||
void configureMqtt();
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
#include "../../src/emsesp_stub.hpp" // proddy added
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
MqttStatus::MqttStatus(AsyncWebServer * server, MqttSettingsService * mqttSettingsService, SecurityManager * securityManager)
|
||||
: _mqttSettingsService(mqttSettingsService) {
|
||||
server->on(MQTT_STATUS_SERVICE_PATH,
|
||||
HTTP_GET,
|
||||
securityManager->wrapRequest(std::bind(&MqttStatus::mqttStatus, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
securityManager->wrapRequest(std::bind(&MqttStatus::mqttStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
}
|
||||
|
||||
void MqttStatus::mqttStatus(AsyncWebServerRequest * request) {
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
#ifndef MqttStatus_h
|
||||
#define MqttStatus_h
|
||||
|
||||
#ifdef ESP32
|
||||
#include <WiFi.h>
|
||||
#include <AsyncTCP.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESPAsyncTCP.h>
|
||||
#endif
|
||||
|
||||
#include <MqttSettingsService.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <AsyncJson.h>
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
#include <NTPSettingsService.h>
|
||||
|
||||
#include "../../src/emsesp_stub.hpp" // proddy added
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
NTPSettingsService::NTPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
|
||||
: _httpEndpoint(NTPSettings::read, NTPSettings::update, this, server, NTP_SETTINGS_SERVICE_PATH, securityManager)
|
||||
, _fsPersistence(NTPSettings::read, NTPSettings::update, this, fs, NTP_SETTINGS_FILE)
|
||||
, _timeHandler(TIME_PATH, securityManager->wrapCallback(std::bind(&NTPSettingsService::configureTime, this, std::placeholders::_1, std::placeholders::_2), AuthenticationPredicates::IS_ADMIN)) {
|
||||
, _timeHandler(TIME_PATH, securityManager->wrapCallback(std::bind(&NTPSettingsService::configureTime, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) {
|
||||
_timeHandler.setMethod(HTTP_POST);
|
||||
_timeHandler.setMaxContentLength(MAX_TIME_SIZE);
|
||||
server->addHandler(&_timeHandler);
|
||||
|
||||
WiFi.onEvent(std::bind(&NTPSettingsService::WiFiEvent, this, std::placeholders::_1));
|
||||
WiFi.onEvent(std::bind(&NTPSettingsService::WiFiEvent, this, _1));
|
||||
|
||||
addUpdateHandler([&](const String & originId) { configureNTP(); }, false);
|
||||
}
|
||||
@@ -22,14 +26,15 @@ void NTPSettingsService::begin() {
|
||||
void NTPSettingsService::WiFiEvent(WiFiEvent_t event) {
|
||||
switch (event) {
|
||||
case SYSTEM_EVENT_STA_DISCONNECTED:
|
||||
// Serial.println(F("WiFi connection dropped, stopping NTP."));
|
||||
case SYSTEM_EVENT_ETH_DISCONNECTED:
|
||||
emsesp::EMSESP::logger().info(F("WiFi connection dropped, stopping NTP"));
|
||||
connected_ = false;
|
||||
configureNTP();
|
||||
break;
|
||||
|
||||
case SYSTEM_EVENT_STA_GOT_IP:
|
||||
case SYSTEM_EVENT_ETH_GOT_IP:
|
||||
// Serial.println(F("Got IP address, starting NTP Synchronization"));
|
||||
// emsesp::EMSESP::logger().info(F("Got IP address, starting NTP synchronization"));
|
||||
connected_ = true;
|
||||
configureNTP();
|
||||
break;
|
||||
@@ -41,7 +46,7 @@ void NTPSettingsService::WiFiEvent(WiFiEvent_t event) {
|
||||
|
||||
void NTPSettingsService::configureNTP() {
|
||||
if (connected_ && _state.enabled) {
|
||||
// Serial.println(F("Starting NTP..."));
|
||||
emsesp::EMSESP::logger().info(F("Starting NTP"));
|
||||
configTzTime(_state.tzFormat.c_str(), _state.server.c_str());
|
||||
} else {
|
||||
setenv("TZ", _state.tzFormat.c_str(), 1);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include <NTPStatus.h>
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
NTPStatus::NTPStatus(AsyncWebServer * server, SecurityManager * securityManager) {
|
||||
server->on(NTP_STATUS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&NTPStatus::ntpStatus, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
server->on(NTP_STATUS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&NTPStatus::ntpStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -2,15 +2,10 @@
|
||||
#define NTPStatus_h
|
||||
|
||||
#include <time.h>
|
||||
#ifdef ESP32
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <AsyncTCP.h>
|
||||
#include <lwip/apps/sntp.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESPAsyncTCP.h>
|
||||
#include <sntp.h>
|
||||
#endif
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <AsyncJson.h>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include <NetworkSettingsService.h>
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
NetworkSettingsService::NetworkSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
|
||||
: _httpEndpoint(NetworkSettings::read, NetworkSettings::update, this, server, NETWORK_SETTINGS_SERVICE_PATH, securityManager)
|
||||
, _fsPersistence(NetworkSettings::read, NetworkSettings::update, this, fs, NETWORK_SETTINGS_FILE)
|
||||
@@ -17,7 +19,7 @@ NetworkSettingsService::NetworkSettingsService(AsyncWebServer * server, FS * fs,
|
||||
WiFi.mode(WIFI_MODE_MAX);
|
||||
WiFi.mode(WIFI_MODE_NULL);
|
||||
|
||||
WiFi.onEvent(std::bind(&NetworkSettingsService::WiFiEvent, this, std::placeholders::_1));
|
||||
WiFi.onEvent(std::bind(&NetworkSettingsService::WiFiEvent, this, _1));
|
||||
|
||||
addUpdateHandler([&](const String & originId) { reconfigureWiFiConnection(); }, false);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ class NetworkSettings {
|
||||
String password;
|
||||
String hostname;
|
||||
bool staticIPConfig;
|
||||
uint8_t ethernet_profile;
|
||||
|
||||
// optional configuration for static IP address
|
||||
IPAddress localIP;
|
||||
@@ -46,7 +45,6 @@ class NetworkSettings {
|
||||
root["password"] = settings.password;
|
||||
root["hostname"] = settings.hostname;
|
||||
root["static_ip_config"] = settings.staticIPConfig;
|
||||
root["ethernet_profile"] = settings.ethernet_profile;
|
||||
|
||||
// extended settings
|
||||
JsonUtils::writeIP(root, "local_ip", settings.localIP);
|
||||
@@ -61,7 +59,6 @@ class NetworkSettings {
|
||||
settings.password = root["password"] | FACTORY_WIFI_PASSWORD;
|
||||
settings.hostname = root["hostname"] | FACTORY_WIFI_HOSTNAME;
|
||||
settings.staticIPConfig = root["static_ip_config"] | false;
|
||||
settings.ethernet_profile = root["ethernet_profile"] | 0; // no ethernet
|
||||
|
||||
// extended settings
|
||||
JsonUtils::readIP(root, "local_ip", settings.localIP);
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
#include "../../src/emsesp_stub.hpp" // proddy added
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
NetworkStatus::NetworkStatus(AsyncWebServer * server, SecurityManager * securityManager) {
|
||||
server->on(NETWORK_STATUS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&NetworkStatus::networkStatus, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
server->on(NETWORK_STATUS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&NetworkStatus::networkStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
}
|
||||
|
||||
void NetworkStatus::networkStatus(AsyncWebServerRequest * request) {
|
||||
|
||||
@@ -2,15 +2,13 @@
|
||||
|
||||
#include "../../src/emsesp_stub.hpp" // proddy added
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
OTASettingsService::OTASettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
|
||||
: _httpEndpoint(OTASettings::read, OTASettings::update, this, server, OTA_SETTINGS_SERVICE_PATH, securityManager)
|
||||
, _fsPersistence(OTASettings::read, OTASettings::update, this, fs, OTA_SETTINGS_FILE)
|
||||
, _arduinoOTA(nullptr) {
|
||||
#ifdef ESP32
|
||||
WiFi.onEvent(std::bind(&OTASettingsService::onStationModeGotIP, this, std::placeholders::_1, std::placeholders::_2), WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP);
|
||||
#elif defined(ESP8266)
|
||||
_onStationModeGotIPHandler = WiFi.onStationModeGotIP(std::bind(&OTASettingsService::onStationModeGotIP, this, std::placeholders::_1));
|
||||
#endif
|
||||
WiFi.onEvent(std::bind(&OTASettingsService::WiFiEvent, this, _1, _2));
|
||||
addUpdateHandler([&](const String & originId) { configureArduinoOTA(); }, false);
|
||||
}
|
||||
|
||||
@@ -27,54 +25,51 @@ void OTASettingsService::loop() {
|
||||
|
||||
void OTASettingsService::configureArduinoOTA() {
|
||||
if (_arduinoOTA) {
|
||||
#ifdef ESP32
|
||||
_arduinoOTA->end();
|
||||
#endif
|
||||
delete _arduinoOTA;
|
||||
_arduinoOTA = nullptr;
|
||||
}
|
||||
|
||||
if (_state.enabled) {
|
||||
// Serial.println(F("Starting OTA Update Service..."));
|
||||
_arduinoOTA = new ArduinoOTAClass;
|
||||
_arduinoOTA->setPort(_state.port);
|
||||
_arduinoOTA->setPassword(_state.password.c_str());
|
||||
|
||||
_arduinoOTA->onStart([]() {
|
||||
// Serial.println(F("Starting"));
|
||||
Serial.println(F("Starting"));
|
||||
emsesp::EMSESP::system_.upload_status(true);
|
||||
});
|
||||
_arduinoOTA->onEnd([]() {
|
||||
// Serial.println(F("\r\nEnd"));
|
||||
Serial.println(F("\r\nEnd"));
|
||||
emsesp::EMSESP::system_.upload_status(false);
|
||||
});
|
||||
|
||||
// _arduinoOTA->onProgress([](unsigned int progress, unsigned int total) {
|
||||
// Serial.printf_P(PSTR("Progress: %u%%\r\n"), (progress / (total / 100)));
|
||||
// });
|
||||
// _arduinoOTA->onError([](ota_error_t error) {
|
||||
// Serial.printf("Error[%u]: ", error);
|
||||
// if (error == OTA_AUTH_ERROR)
|
||||
// Serial.println(F("Auth Failed"));
|
||||
// else if (error == OTA_BEGIN_ERROR)
|
||||
// Serial.println(F("Begin Failed"));
|
||||
// else if (error == OTA_CONNECT_ERROR)
|
||||
// Serial.println(F("Connect Failed"));
|
||||
// else if (error == OTA_RECEIVE_ERROR)
|
||||
// Serial.println(F("Receive Failed"));
|
||||
// else if (error == OTA_END_ERROR)
|
||||
// Serial.println(F("End Failed"));
|
||||
// });
|
||||
_arduinoOTA->onProgress([](unsigned int progress, unsigned int total) { Serial.printf_P(PSTR("Progress: %u%%\r\n"), (progress / (total / 100))); });
|
||||
_arduinoOTA->onError([](ota_error_t error) {
|
||||
Serial.printf("Error[%u]: ", error);
|
||||
if (error == OTA_AUTH_ERROR)
|
||||
Serial.println(F("Auth Failed"));
|
||||
else if (error == OTA_BEGIN_ERROR)
|
||||
Serial.println(F("Begin Failed"));
|
||||
else if (error == OTA_CONNECT_ERROR)
|
||||
Serial.println(F("Connect Failed"));
|
||||
else if (error == OTA_RECEIVE_ERROR)
|
||||
Serial.println(F("Receive Failed"));
|
||||
else if (error == OTA_END_ERROR)
|
||||
Serial.println(F("End Failed"));
|
||||
});
|
||||
|
||||
_arduinoOTA->begin();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ESP32
|
||||
void OTASettingsService::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
configureArduinoOTA();
|
||||
void OTASettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
switch (event) {
|
||||
case SYSTEM_EVENT_STA_GOT_IP:
|
||||
case SYSTEM_EVENT_ETH_GOT_IP:
|
||||
configureArduinoOTA();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
#elif defined(ESP8266)
|
||||
void OTASettingsService::onStationModeGotIP(const WiFiEventStationModeGotIP & event) {
|
||||
configureArduinoOTA();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -4,11 +4,7 @@
|
||||
#include <HttpEndpoint.h>
|
||||
#include <FSPersistence.h>
|
||||
|
||||
#ifdef ESP32
|
||||
#include <ESPmDNS.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266mDNS.h>
|
||||
#endif
|
||||
|
||||
#include <ArduinoOTA.h>
|
||||
#include <WiFiUdp.h>
|
||||
@@ -61,12 +57,7 @@ class OTASettingsService : public StatefulService<OTASettings> {
|
||||
ArduinoOTAClass * _arduinoOTA;
|
||||
|
||||
void configureArduinoOTA();
|
||||
#ifdef ESP32
|
||||
void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||
#elif defined(ESP8266)
|
||||
WiFiEventHandler _onStationModeGotIPHandler;
|
||||
void onStationModeGotIP(const WiFiEventStationModeGotIP & event);
|
||||
#endif
|
||||
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||
};
|
||||
|
||||
#endif // end OTASettingsService_h
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#include <RestartService.h>
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
RestartService::RestartService(AsyncWebServer * server, SecurityManager * securityManager) {
|
||||
server->on(RESTART_SERVICE_PATH,
|
||||
HTTP_POST,
|
||||
securityManager->wrapRequest(std::bind(&RestartService::restart, this, std::placeholders::_1), AuthenticationPredicates::IS_ADMIN));
|
||||
server->on(RESTART_SERVICE_PATH, HTTP_POST, securityManager->wrapRequest(std::bind(&RestartService::restart, this, _1), AuthenticationPredicates::IS_ADMIN));
|
||||
}
|
||||
|
||||
void RestartService::restart(AsyncWebServerRequest * request) {
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
#ifndef RestartService_h
|
||||
#define RestartService_h
|
||||
|
||||
#ifdef ESP32
|
||||
#include <WiFi.h>
|
||||
#include <AsyncTCP.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESPAsyncTCP.h>
|
||||
#endif
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <SecurityManager.h>
|
||||
|
||||
@@ -9,6 +9,7 @@ SecuritySettingsService::SecuritySettingsService(AsyncWebServer * server, FS * f
|
||||
, _fsPersistence(SecuritySettings::read, SecuritySettings::update, this, fs, SECURITY_SETTINGS_FILE)
|
||||
, _jwtHandler(FACTORY_JWT_SECRET) {
|
||||
addUpdateHandler([&](const String & originId) { configureJWTHandler(); }, false);
|
||||
server->on(GENERATE_TOKEN_PATH, HTTP_GET, wrapRequest(std::bind(&SecuritySettingsService::generateToken, this, std::placeholders::_1), AuthenticationPredicates::IS_ADMIN));
|
||||
}
|
||||
|
||||
void SecuritySettingsService::begin() {
|
||||
@@ -109,6 +110,21 @@ ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequest
|
||||
};
|
||||
}
|
||||
|
||||
void SecuritySettingsService::generateToken(AsyncWebServerRequest* request) {
|
||||
AsyncWebParameter* usernameParam = request->getParam("username");
|
||||
for (User _user : _state.users) {
|
||||
if (_user.username == usernameParam->value()) {
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, GENERATE_TOKEN_SIZE);
|
||||
JsonObject root = response->getRoot();
|
||||
root["token"] = generateJWT(&_user);
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
request->send(401);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
User ADMIN_USER = User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true);
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
#define SECURITY_SETTINGS_FILE "/config/securitySettings.json"
|
||||
#define SECURITY_SETTINGS_PATH "/rest/securitySettings"
|
||||
|
||||
#define GENERATE_TOKEN_SIZE 512
|
||||
#define GENERATE_TOKEN_PATH "/rest/generateToken"
|
||||
|
||||
#if FT_ENABLED(FT_SECURITY)
|
||||
|
||||
class SecuritySettings {
|
||||
@@ -83,6 +86,8 @@ class SecuritySettingsService : public StatefulService<SecuritySettings>, public
|
||||
FSPersistence<SecuritySettings> _fsPersistence;
|
||||
ArduinoJsonJWT _jwtHandler;
|
||||
|
||||
void generateToken(AsyncWebServerRequest * request);
|
||||
|
||||
void configureJWTHandler();
|
||||
|
||||
/*
|
||||
|
||||
@@ -6,10 +6,8 @@
|
||||
|
||||
#include <list>
|
||||
#include <functional>
|
||||
#ifdef ESP32
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#endif
|
||||
|
||||
#ifndef DEFAULT_BUFFER_SIZE
|
||||
#define DEFAULT_BUFFER_SIZE 1024
|
||||
@@ -45,16 +43,10 @@ template <class T>
|
||||
class StatefulService {
|
||||
public:
|
||||
template <typename... Args>
|
||||
#ifdef ESP32
|
||||
StatefulService(Args &&... args)
|
||||
: _state(std::forward<Args>(args)...)
|
||||
, _accessMutex(xSemaphoreCreateRecursiveMutex()) {
|
||||
}
|
||||
#else
|
||||
StatefulService(Args &&... args)
|
||||
: _state(std::forward<Args>(args)...) {
|
||||
}
|
||||
#endif
|
||||
|
||||
update_handler_id_t addUpdateHandler(StateUpdateCallback cb, bool allowRemove = true) {
|
||||
if (!cb) {
|
||||
@@ -131,21 +123,15 @@ class StatefulService {
|
||||
T _state;
|
||||
|
||||
inline void beginTransaction() {
|
||||
#ifdef ESP32
|
||||
xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void endTransaction() {
|
||||
#ifdef ESP32
|
||||
xSemaphoreGiveRecursive(_accessMutex);
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
#ifdef ESP32
|
||||
SemaphoreHandle_t _accessMutex;
|
||||
#endif
|
||||
SemaphoreHandle_t _accessMutex;
|
||||
std::list<StateUpdateHandlerInfo_t> _updateHandlers;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
#include <SystemStatus.h>
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
SystemStatus::SystemStatus(AsyncWebServer * server, SecurityManager * securityManager) {
|
||||
server->on(SYSTEM_STATUS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&SystemStatus::systemStatus, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
server->on(SYSTEM_STATUS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&SystemStatus::systemStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
}
|
||||
|
||||
void SystemStatus::systemStatus(AsyncWebServerRequest * request) {
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_ESP_STATUS_SIZE);
|
||||
JsonObject root = response->getRoot();
|
||||
root["esp_platform"] = "esp32";
|
||||
root["esp_platform"] = "ESP32";
|
||||
root["max_alloc_heap"] = ESP.getMaxAllocHeap();
|
||||
root["psram_size"] = ESP.getPsramSize();
|
||||
root["free_psram"] = ESP.getFreePsram();
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
#include <UploadFirmwareService.h>
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
UploadFirmwareService::UploadFirmwareService(AsyncWebServer * server, SecurityManager * securityManager)
|
||||
: _securityManager(securityManager) {
|
||||
server->on(UPLOAD_FIRMWARE_PATH,
|
||||
HTTP_POST,
|
||||
std::bind(&UploadFirmwareService::uploadComplete, this, std::placeholders::_1),
|
||||
std::bind(&UploadFirmwareService::handleUpload,
|
||||
this,
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2,
|
||||
std::placeholders::_3,
|
||||
std::placeholders::_4,
|
||||
std::placeholders::_5,
|
||||
std::placeholders::_6));
|
||||
#ifdef ESP8266
|
||||
Update.runAsync(true);
|
||||
#endif
|
||||
server->on(UPLOAD_FIRMWARE_PATH, HTTP_POST, std::bind(&UploadFirmwareService::uploadComplete, this, _1), std::bind(&UploadFirmwareService::handleUpload, this, _1, _2, _3, _4, _5, _6));
|
||||
}
|
||||
|
||||
void UploadFirmwareService::handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) {
|
||||
@@ -72,9 +61,5 @@ void UploadFirmwareService::handleError(AsyncWebServerRequest * request, int cod
|
||||
}
|
||||
|
||||
void UploadFirmwareService::handleEarlyDisconnect() {
|
||||
#ifdef ESP32
|
||||
Update.abort();
|
||||
#elif defined(ESP8266)
|
||||
Update.end();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -3,14 +3,9 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#ifdef ESP32
|
||||
#include <Update.h>
|
||||
#include <WiFi.h>
|
||||
#include <AsyncTCP.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESPAsyncTCP.h>
|
||||
#endif
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <SecurityManager.h>
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#define WEB_SOCKET_ORIGIN "websocket"
|
||||
#define WEB_SOCKET_ORIGIN_CLIENT_ID_PREFIX "websocket:"
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
template <class T>
|
||||
class WebSocketConnector {
|
||||
protected:
|
||||
@@ -18,27 +20,15 @@ class WebSocketConnector {
|
||||
AsyncWebSocket _webSocket;
|
||||
size_t _bufferSize;
|
||||
|
||||
WebSocketConnector(StatefulService<T> * statefulService,
|
||||
AsyncWebServer * server,
|
||||
const char * webSocketPath,
|
||||
SecurityManager * securityManager,
|
||||
AuthenticationPredicate authenticationPredicate,
|
||||
size_t bufferSize)
|
||||
WebSocketConnector(StatefulService<T> * statefulService, AsyncWebServer * server, const char * webSocketPath, SecurityManager * securityManager, AuthenticationPredicate authenticationPredicate, size_t bufferSize)
|
||||
: _statefulService(statefulService)
|
||||
, _server(server)
|
||||
, _webSocket(webSocketPath)
|
||||
, _bufferSize(bufferSize) {
|
||||
_webSocket.setFilter(securityManager->filterRequest(authenticationPredicate));
|
||||
_webSocket.onEvent(std::bind(&WebSocketConnector::onWSEvent,
|
||||
this,
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2,
|
||||
std::placeholders::_3,
|
||||
std::placeholders::_4,
|
||||
std::placeholders::_5,
|
||||
std::placeholders::_6));
|
||||
_webSocket.onEvent(std::bind(&WebSocketConnector::onWSEvent, this, _1, _2, _3, _4, _5, _6));
|
||||
_server->addHandler(&_webSocket);
|
||||
_server->on(webSocketPath, HTTP_GET, std::bind(&WebSocketConnector::forbidden, this, std::placeholders::_1));
|
||||
_server->on(webSocketPath, HTTP_GET, std::bind(&WebSocketConnector::forbidden, this, _1));
|
||||
}
|
||||
|
||||
WebSocketConnector(StatefulService<T> * statefulService, AsyncWebServer * server, const char * webSocketPath, size_t bufferSize)
|
||||
@@ -46,14 +36,7 @@ class WebSocketConnector {
|
||||
, _server(server)
|
||||
, _webSocket(webSocketPath)
|
||||
, _bufferSize(bufferSize) {
|
||||
_webSocket.onEvent(std::bind(&WebSocketConnector::onWSEvent,
|
||||
this,
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2,
|
||||
std::placeholders::_3,
|
||||
std::placeholders::_4,
|
||||
std::placeholders::_5,
|
||||
std::placeholders::_6));
|
||||
_webSocket.onEvent(std::bind(&WebSocketConnector::onWSEvent, this, _1, _2, _3, _4, _5, _6));
|
||||
_server->addHandler(&_webSocket);
|
||||
}
|
||||
|
||||
@@ -84,11 +67,7 @@ class WebSocketTx : virtual public WebSocketConnector<T> {
|
||||
WebSocketConnector<T>::_statefulService->addUpdateHandler([&](const String & originId) { transmitData(nullptr, originId); }, false);
|
||||
}
|
||||
|
||||
WebSocketTx(JsonStateReader<T> stateReader,
|
||||
StatefulService<T> * statefulService,
|
||||
AsyncWebServer * server,
|
||||
const char * webSocketPath,
|
||||
size_t bufferSize = DEFAULT_BUFFER_SIZE)
|
||||
WebSocketTx(JsonStateReader<T> stateReader, StatefulService<T> * statefulService, AsyncWebServer * server, const char * webSocketPath, size_t bufferSize = DEFAULT_BUFFER_SIZE)
|
||||
: WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize)
|
||||
, _stateReader(stateReader) {
|
||||
WebSocketConnector<T>::_statefulService->addUpdateHandler([&](const String & originId) { transmitData(nullptr, originId); }, false);
|
||||
@@ -161,11 +140,7 @@ class WebSocketRx : virtual public WebSocketConnector<T> {
|
||||
, _stateUpdater(stateUpdater) {
|
||||
}
|
||||
|
||||
WebSocketRx(JsonStateUpdater<T> stateUpdater,
|
||||
StatefulService<T> * statefulService,
|
||||
AsyncWebServer * server,
|
||||
const char * webSocketPath,
|
||||
size_t bufferSize = DEFAULT_BUFFER_SIZE)
|
||||
WebSocketRx(JsonStateUpdater<T> stateUpdater, StatefulService<T> * statefulService, AsyncWebServer * server, const char * webSocketPath, size_t bufferSize = DEFAULT_BUFFER_SIZE)
|
||||
: WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize)
|
||||
, _stateUpdater(stateUpdater) {
|
||||
}
|
||||
@@ -207,12 +182,7 @@ class WebSocketTxRx : public WebSocketTx<T>, public WebSocketRx<T> {
|
||||
, WebSocketRx<T>(stateUpdater, statefulService, server, webSocketPath, securityManager, authenticationPredicate, bufferSize) {
|
||||
}
|
||||
|
||||
WebSocketTxRx(JsonStateReader<T> stateReader,
|
||||
JsonStateUpdater<T> stateUpdater,
|
||||
StatefulService<T> * statefulService,
|
||||
AsyncWebServer * server,
|
||||
const char * webSocketPath,
|
||||
size_t bufferSize = DEFAULT_BUFFER_SIZE)
|
||||
WebSocketTxRx(JsonStateReader<T> stateReader, JsonStateUpdater<T> stateUpdater, StatefulService<T> * statefulService, AsyncWebServer * server, const char * webSocketPath, size_t bufferSize = DEFAULT_BUFFER_SIZE)
|
||||
: WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize)
|
||||
, WebSocketTx<T>(stateReader, statefulService, server, webSocketPath, bufferSize)
|
||||
, WebSocketRx<T>(stateUpdater, statefulService, server, webSocketPath, bufferSize) {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include <WiFiScanner.h>
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
WiFiScanner::WiFiScanner(AsyncWebServer * server, SecurityManager * securityManager) {
|
||||
server->on(SCAN_NETWORKS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WiFiScanner::scanNetworks, this, std::placeholders::_1), AuthenticationPredicates::IS_ADMIN));
|
||||
server->on(LIST_NETWORKS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WiFiScanner::listNetworks, this, std::placeholders::_1), AuthenticationPredicates::IS_ADMIN));
|
||||
server->on(SCAN_NETWORKS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WiFiScanner::scanNetworks, this, _1), AuthenticationPredicates::IS_ADMIN));
|
||||
server->on(LIST_NETWORKS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WiFiScanner::listNetworks, this, _1), AuthenticationPredicates::IS_ADMIN));
|
||||
};
|
||||
|
||||
void WiFiScanner::scanNetworks(AsyncWebServerRequest * request) {
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
#ifndef WiFiScanner_h
|
||||
#define WiFiScanner_h
|
||||
|
||||
#ifdef ESP32
|
||||
#include <WiFi.h>
|
||||
#include <AsyncTCP.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESPAsyncTCP.h>
|
||||
#endif
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <AsyncJson.h>
|
||||
@@ -26,10 +21,6 @@ class WiFiScanner {
|
||||
private:
|
||||
void scanNetworks(AsyncWebServerRequest * request);
|
||||
void listNetworks(AsyncWebServerRequest * request);
|
||||
|
||||
#ifdef ESP8266
|
||||
uint8_t convertEncryptionType(uint8_t encryptionType);
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // end WiFiScanner_h
|
||||
|
||||
@@ -22,44 +22,24 @@
|
||||
|
||||
namespace uuid {
|
||||
|
||||
#define UPTIME_OVERFLOW 4294967295 // Uptime overflow value
|
||||
// added by proddy, modified
|
||||
static uint64_t now_millis = 0;
|
||||
|
||||
// returns system uptime in seconds
|
||||
uint32_t get_uptime_sec() {
|
||||
static uint32_t last_uptime = 0;
|
||||
static uint8_t uptime_overflows = 0;
|
||||
|
||||
if (millis() < last_uptime) {
|
||||
++uptime_overflows;
|
||||
}
|
||||
last_uptime = millis();
|
||||
uint32_t uptime_seconds = uptime_overflows * (UPTIME_OVERFLOW / 1000) + (last_uptime / 1000);
|
||||
|
||||
return uptime_seconds;
|
||||
return (uint32_t)(now_millis / 1000ULL);
|
||||
}
|
||||
|
||||
uint64_t get_uptime_ms() {
|
||||
static uint32_t high_millis = 0;
|
||||
static uint32_t low_millis = 0;
|
||||
|
||||
if (get_uptime() < low_millis) {
|
||||
high_millis++;
|
||||
}
|
||||
|
||||
low_millis = get_uptime();
|
||||
|
||||
return ((uint64_t)high_millis << 32) | low_millis;
|
||||
}
|
||||
|
||||
// added by proddy
|
||||
static uint32_t now_millis;
|
||||
|
||||
void set_uptime() {
|
||||
now_millis = ::millis();
|
||||
}
|
||||
|
||||
uint32_t get_uptime() {
|
||||
return now_millis;
|
||||
}
|
||||
|
||||
void set_uptime() {
|
||||
now_millis = esp_timer_get_time() / 1000ULL;
|
||||
}
|
||||
|
||||
uint32_t get_uptime() {
|
||||
return (uint32_t)now_millis;
|
||||
}
|
||||
|
||||
} // namespace uuid
|
||||
|
||||
@@ -22,7 +22,6 @@ namespace uuid {
|
||||
|
||||
void loop() {
|
||||
set_uptime(); // added by proddy
|
||||
get_uptime_ms();
|
||||
}
|
||||
|
||||
} // namespace uuid
|
||||
|
||||
@@ -50,10 +50,7 @@ void Commands::add_command(const flash_string_vector & name, const flash_string_
|
||||
add_command(0, 0, name, arguments, function, nullptr);
|
||||
}
|
||||
|
||||
void Commands::add_command(const flash_string_vector & name,
|
||||
const flash_string_vector & arguments,
|
||||
command_function function,
|
||||
argument_completion_function arg_function) {
|
||||
void Commands::add_command(const flash_string_vector & name, const flash_string_vector & arguments, command_function function, argument_completion_function arg_function) {
|
||||
add_command(0, 0, name, arguments, function, arg_function);
|
||||
}
|
||||
|
||||
@@ -61,20 +58,11 @@ void Commands::add_command(unsigned int context, unsigned int flags, const flash
|
||||
add_command(context, flags, name, flash_string_vector{}, function, nullptr);
|
||||
}
|
||||
|
||||
void Commands::add_command(unsigned int context,
|
||||
unsigned int flags,
|
||||
const flash_string_vector & name,
|
||||
const flash_string_vector & arguments,
|
||||
command_function function) {
|
||||
void Commands::add_command(unsigned int context, unsigned int flags, const flash_string_vector & name, const flash_string_vector & arguments, command_function function) {
|
||||
add_command(context, flags, name, arguments, function, nullptr);
|
||||
}
|
||||
|
||||
void Commands::add_command(unsigned int context,
|
||||
unsigned int flags,
|
||||
const flash_string_vector & name,
|
||||
const flash_string_vector & arguments,
|
||||
command_function function,
|
||||
argument_completion_function arg_function) {
|
||||
void Commands::add_command(unsigned int context, unsigned int flags, const flash_string_vector & name, const flash_string_vector & arguments, command_function function, argument_completion_function arg_function) {
|
||||
commands_.emplace(std::piecewise_construct, std::forward_as_tuple(context), std::forward_as_tuple(flags, name, arguments, function, arg_function));
|
||||
}
|
||||
|
||||
@@ -179,8 +167,7 @@ bool Commands::find_longest_common_prefix(const std::multimap<size_t, const Comm
|
||||
for (auto command_it = std::next(commands.begin()); command_it != commands.end(); command_it++) {
|
||||
// This relies on the null terminator character limiting the
|
||||
// length before it becomes longer than any of the strings
|
||||
if (pgm_read_byte(reinterpret_cast<PGM_P>(first) + length)
|
||||
!= pgm_read_byte(reinterpret_cast<PGM_P>(*std::next(command_it->second->name_.begin(), component_prefix)) + length)) {
|
||||
if (pgm_read_byte(reinterpret_cast<PGM_P>(first) + length) != pgm_read_byte(reinterpret_cast<PGM_P>(*std::next(command_it->second->name_.begin(), component_prefix)) + length)) {
|
||||
all_match = false;
|
||||
break;
|
||||
}
|
||||
@@ -275,8 +262,7 @@ Commands::Completion Commands::complete_command(Shell & shell, const CommandLine
|
||||
result.replacement->push_back(std::move(read_flash_string(name)));
|
||||
}
|
||||
|
||||
if (command_line.total_size() > result.replacement->size()
|
||||
&& command_line.total_size() <= matching_command->name_.size() + matching_command->maximum_arguments()) {
|
||||
if (command_line.total_size() > result.replacement->size() && command_line.total_size() <= matching_command->name_.size() + matching_command->maximum_arguments()) {
|
||||
// Try to auto-complete arguments
|
||||
std::vector<std::string> arguments{std::next(command_line->cbegin(), result.replacement->size()), command_line->cend()};
|
||||
|
||||
@@ -526,11 +512,7 @@ void Commands::for_each_available_command(Shell & shell, apply_function f) const
|
||||
}
|
||||
}
|
||||
|
||||
Commands::Command::Command(unsigned int flags,
|
||||
const flash_string_vector name,
|
||||
const flash_string_vector arguments,
|
||||
command_function function,
|
||||
argument_completion_function arg_function)
|
||||
Commands::Command::Command(unsigned int flags, const flash_string_vector name, const flash_string_vector arguments, command_function function, argument_completion_function arg_function)
|
||||
: flags_(flags)
|
||||
, name_(name)
|
||||
, arguments_(arguments)
|
||||
|
||||
@@ -65,8 +65,10 @@ void Shell::start() {
|
||||
#endif
|
||||
|
||||
line_buffer_.reserve(maximum_command_line_length_);
|
||||
line_old_.reserve(maximum_command_line_length_);
|
||||
line_old_.clear();
|
||||
for (uint8_t i = 0; i < MAX_LINES; i++) {
|
||||
line_old_[i].reserve(maximum_command_line_length_);
|
||||
line_old_[i].clear();
|
||||
}
|
||||
display_banner();
|
||||
display_prompt();
|
||||
shells_.insert(shared_from_this());
|
||||
@@ -156,7 +158,8 @@ void Shell::loop_normal() {
|
||||
// Interrupt (^C)
|
||||
line_buffer_.clear();
|
||||
println();
|
||||
cursor_ = 0;
|
||||
cursor_ = 0;
|
||||
line_no_ = 0;
|
||||
break;
|
||||
|
||||
case '\x04':
|
||||
@@ -172,13 +175,15 @@ void Shell::loop_normal() {
|
||||
// Del/Backspace (^?)
|
||||
if (line_buffer_.length() > cursor_) {
|
||||
line_buffer_.erase(line_buffer_.length() - cursor_ - 1, 1);
|
||||
line_no_ = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case '\x09':
|
||||
// Tab (^I)
|
||||
process_completion();
|
||||
cursor_ = 0;
|
||||
cursor_ = 0;
|
||||
line_no_ = 0;
|
||||
break;
|
||||
|
||||
case '\x0A':
|
||||
@@ -198,12 +203,14 @@ void Shell::loop_normal() {
|
||||
case '\x15':
|
||||
// Delete line (^U)
|
||||
line_buffer_.clear();
|
||||
cursor_ = 0;
|
||||
cursor_ = 0;
|
||||
line_no_ = 0;
|
||||
break;
|
||||
|
||||
case '\x17':
|
||||
// Delete word (^W)
|
||||
delete_buffer_word(true);
|
||||
line_no_ = 0;
|
||||
break;
|
||||
|
||||
case '\033':
|
||||
@@ -214,10 +221,20 @@ void Shell::loop_normal() {
|
||||
default:
|
||||
if (esc_) {
|
||||
if (c == 'A') { // cursor up
|
||||
line_buffer_ = line_old_;
|
||||
cursor_ = 0;
|
||||
line_buffer_ = line_old_[line_no_];
|
||||
if (line_no_ < MAX_LINES - 1) {
|
||||
line_no_++;
|
||||
}
|
||||
cursor_ = 0;
|
||||
} else if (c == 'B') { // cursor down
|
||||
line_buffer_.clear();
|
||||
if (line_no_) {
|
||||
line_no_--;
|
||||
}
|
||||
if (line_no_) {
|
||||
line_buffer_ = line_old_[line_no_ - 1];
|
||||
} else {
|
||||
line_buffer_.clear();
|
||||
}
|
||||
cursor_ = 0;
|
||||
} else if (c == 'C') { // cursor right
|
||||
if (cursor_) {
|
||||
@@ -239,6 +256,7 @@ void Shell::loop_normal() {
|
||||
if ((esc_ == 3) && cursor_) { // del
|
||||
cursor_--;
|
||||
line_buffer_.erase(line_buffer_.length() - cursor_ - 1, 1);
|
||||
line_no_ = 0;
|
||||
} else if (esc_ == 4) { // end
|
||||
cursor_ = 0;
|
||||
} else if (esc_ == 1) { // pos1
|
||||
@@ -279,6 +297,7 @@ void Shell::loop_normal() {
|
||||
} else if (c >= '\x20' && c <= '\x7E') {
|
||||
if (line_buffer_.length() < maximum_command_line_length_) {
|
||||
line_buffer_.insert(line_buffer_.length() - cursor_, 1, c);
|
||||
line_no_ = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -498,7 +517,12 @@ void Shell::process_command() {
|
||||
println();
|
||||
return;
|
||||
}
|
||||
line_old_ = line_buffer_;
|
||||
uint8_t no = line_no_ ? line_no_ : MAX_LINES;
|
||||
while (--no) {
|
||||
line_old_[no] = line_old_[no - 1];
|
||||
}
|
||||
line_no_ = 0;
|
||||
line_old_[0] = line_buffer_;
|
||||
while (!line_buffer_.empty()) {
|
||||
size_t pos = line_buffer_.find(';');
|
||||
std::string line1;
|
||||
|
||||
@@ -96,7 +96,6 @@ void Shell::output_logs() {
|
||||
}
|
||||
|
||||
::yield();
|
||||
|
||||
}
|
||||
display_prompt();
|
||||
}
|
||||
|
||||
@@ -61,8 +61,9 @@ class Commands;
|
||||
*/
|
||||
class Shell : public std::enable_shared_from_this<Shell>, public uuid::log::Handler, public ::Stream {
|
||||
public:
|
||||
static constexpr size_t MAX_COMMAND_LINE_LENGTH = 80; /*!< Maximum length of a command line. @since 0.1.0 */
|
||||
static constexpr size_t MAX_LOG_MESSAGES = 20; /*!< Maximum number of log messages to buffer before they are output. @since 0.1.0 */
|
||||
static constexpr size_t MAX_COMMAND_LINE_LENGTH = 80; /*!< Maximum length of a command line. @since 0.1.0 */
|
||||
static constexpr size_t MAX_LOG_MESSAGES = 20; /*!< Maximum number of log messages to buffer before they are output. @since 0.1.0 */
|
||||
static constexpr uint8_t MAX_LINES = 5; /*!< Maximum lines in buffer */
|
||||
|
||||
/**
|
||||
* Function to handle the response to a password entry prompt.
|
||||
@@ -903,8 +904,9 @@ class Shell : public std::enable_shared_from_this<Shell>, public uuid::log::Hand
|
||||
unsigned long log_message_id_ = 0; /*!< The next identifier to use for queued log messages. @since 0.1.0 */
|
||||
std::list<QueuedLogMessage> log_messages_; /*!< Queued log messages, in the order they were received. @since 0.1.0 */
|
||||
size_t maximum_log_messages_ = MAX_LOG_MESSAGES; /*!< Maximum command line length in bytes. @since 0.6.0 */
|
||||
std::string line_buffer_; /*!< Command line buffer. Limited to maximum_command_line_length() bytes. @since 0.1.0 */
|
||||
std::string line_old_; /*!< old Command line buffer.*/
|
||||
std::string line_buffer_; /*!< Command line buffer. Limited to maximum_command_line_length() bytes. @since 0.1.0 */
|
||||
std::string line_old_[MAX_LINES]; /*!< old Command line buffer.*/
|
||||
uint8_t line_no_ = 0;
|
||||
size_t maximum_command_line_length_ = MAX_COMMAND_LINE_LENGTH; /*!< Maximum command line length in bytes. @since 0.6.0 */
|
||||
unsigned char previous_ = 0; /*!< Previous character that was entered on the command line. Used to detect CRLF line endings. @since 0.1.0 */
|
||||
uint8_t cursor_ = 0; /*!< cursor position from end of line */
|
||||
|
||||
@@ -19,13 +19,11 @@
|
||||
#include "uuid/syslog.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#include <ESP8266WiFi.h>
|
||||
#else
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
#include <WiFiUdp.h>
|
||||
|
||||
#include "../../../src/emsesp.h"
|
||||
|
||||
#ifndef UUID_SYSLOG_HAVE_GETTIMEOFDAY
|
||||
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||
// time() does not return UTC on the ESP8266: https://github.com/esp8266/Arduino/issues/4637
|
||||
@@ -191,7 +189,8 @@ void SyslogService::mark_interval(unsigned long interval) {
|
||||
SyslogService::QueuedLogMessage::QueuedLogMessage(unsigned long id, std::shared_ptr<uuid::log::Message> && content)
|
||||
: id_(id)
|
||||
, content_(std::move(content)) {
|
||||
if (time_good_ || WiFi.status() == WL_CONNECTED) {
|
||||
// Added by proddy - check for Ethernet too. This assumes the network has already started.
|
||||
if (time_good_ || emsesp::EMSESP::system_.network_connected()) {
|
||||
#if UUID_SYSLOG_HAVE_GETTIMEOFDAY
|
||||
if (gettimeofday(&time_, nullptr) != 0) {
|
||||
time_.tv_sec = (time_t)-1;
|
||||
@@ -200,7 +199,6 @@ SyslogService::QueuedLogMessage::QueuedLogMessage(unsigned long id, std::shared_
|
||||
time_.tv_sec = time(nullptr);
|
||||
time_.tv_usec = 0;
|
||||
#endif
|
||||
|
||||
if (time_.tv_sec >= 0 && time_.tv_sec < 18140 * 86400) {
|
||||
time_.tv_sec = (time_t)-1;
|
||||
}
|
||||
@@ -269,8 +267,8 @@ bool SyslogService::can_transmit() {
|
||||
}
|
||||
#endif
|
||||
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
return false;
|
||||
if (!emsesp::EMSESP::system_.network_connected()) {
|
||||
return false; // added by proddy. Check Ethernet
|
||||
}
|
||||
|
||||
const uint64_t now = uuid::get_uptime_ms();
|
||||
@@ -387,15 +385,20 @@ bool SyslogService::can_transmit() {
|
||||
}
|
||||
|
||||
bool SyslogService::transmit(const QueuedLogMessage & message) {
|
||||
/*
|
||||
// modifications by Proddy. From https://github.com/emsesp/EMS-ESP/issues/395#issuecomment-640053528
|
||||
struct tm tm;
|
||||
int8_t tzh = 0;
|
||||
int8_t tzm = 0;
|
||||
|
||||
tm.tm_year = 0;
|
||||
if (message.time_.tv_sec != (time_t)-1) {
|
||||
gmtime_r(&message.time_.tv_sec, &tm);
|
||||
struct tm utc;
|
||||
gmtime_r(&message.time_.tv_sec, &utc);
|
||||
localtime_r(&message.time_.tv_sec, &tm);
|
||||
int16_t diff = 60 * (tm.tm_hour - utc.tm_hour) + tm.tm_min - utc.tm_min;
|
||||
diff = diff > 720 ? diff - 1440 : diff < -720 ? diff + 1440 : diff;
|
||||
tzh = diff / 60;
|
||||
tzm = diff < 0 ? (0 - diff) % 60 : diff % 60;
|
||||
}
|
||||
*/
|
||||
|
||||
if (udp_.beginPacket(host_, port_) != 1) {
|
||||
last_transmit_ = uuid::get_uptime_ms();
|
||||
@@ -403,30 +406,16 @@ bool SyslogService::transmit(const QueuedLogMessage & message) {
|
||||
}
|
||||
|
||||
udp_.printf_P(PSTR("<%u>1 "), ((unsigned int)message.content_->facility * 8) + std::min(7U, (unsigned int)message.content_->level));
|
||||
|
||||
/*
|
||||
if (tm.tm_year != 0) {
|
||||
udp_.printf_P(PSTR("%04u-%02u-%02uT%02u:%02u:%02u.%06luZ"),
|
||||
tm.tm_year + 1900,
|
||||
tm.tm_mon + 1,
|
||||
tm.tm_mday,
|
||||
tm.tm_hour,
|
||||
tm.tm_min,
|
||||
tm.tm_sec,
|
||||
(uint32_t)message.time_.tv_usec);
|
||||
udp_.printf_P(PSTR("%04u-%02u-%02uT%02u:%02u:%02u.%06u%+02d:%02d"), tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, (uint32_t)message.time_.tv_usec, tzh, tzm);
|
||||
} else {
|
||||
udp_.print('-');
|
||||
}
|
||||
*/
|
||||
|
||||
udp_.print('-');
|
||||
udp_.printf_P(PSTR(" %s - - - - \xEF\xBB\xBF"), hostname_.c_str());
|
||||
udp_.printf_P(PSTR(" %s %s - - - \xEF\xBB\xBF"), hostname_.c_str(), uuid::read_flash_string(message.content_->name).c_str());
|
||||
|
||||
udp_.print(uuid::log::format_timestamp_ms(message.content_->uptime_ms, 3).c_str());
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wformat"
|
||||
udp_.printf_P(PSTR(" %c %lu: [%S] "), uuid::log::format_level_char(message.content_->level), message.id_, message.content_->name);
|
||||
#pragma GCC diagnostic pop
|
||||
udp_.printf_P(PSTR(" %c %lu: "), uuid::log::format_level_char(message.content_->level), message.id_);
|
||||
udp_.print(message.content_->text.c_str());
|
||||
bool ok = (udp_.endPacket() == 1);
|
||||
|
||||
|
||||
@@ -61,6 +61,10 @@ unsigned long millis() {
|
||||
return __millis;
|
||||
}
|
||||
|
||||
int64_t esp_timer_get_time() {
|
||||
return __millis;
|
||||
}
|
||||
|
||||
void delay(unsigned long millis) {
|
||||
// __millis += millis;
|
||||
}
|
||||
|
||||
@@ -53,11 +53,7 @@ int digitalRead(uint8_t pin);
|
||||
|
||||
#define PROGMEM
|
||||
#define PGM_P const char *
|
||||
#define PSTR(s) \
|
||||
(__extension__({ \
|
||||
static const char __c[] = (s); \
|
||||
&__c[0]; \
|
||||
}))
|
||||
#define PSTR(s) s
|
||||
|
||||
class __FlashStringHelper;
|
||||
#define FPSTR(string_literal) (reinterpret_cast<const __FlashStringHelper *>(string_literal))
|
||||
@@ -196,11 +192,13 @@ class NativeConsole : public Stream {
|
||||
#include <Network.h>
|
||||
|
||||
extern NativeConsole Serial;
|
||||
extern ETHClass ETH;
|
||||
extern WiFiClass WiFi;
|
||||
extern ETHClass ETH;
|
||||
extern WiFiClass WiFi;
|
||||
|
||||
unsigned long millis();
|
||||
|
||||
int64_t esp_timer_get_time();
|
||||
|
||||
void delay(unsigned long millis);
|
||||
|
||||
void yield(void);
|
||||
|
||||
@@ -28,15 +28,16 @@ class DummySettings {
|
||||
bool api_enabled = true;
|
||||
|
||||
// MQTT
|
||||
uint16_t publish_time = 10; // seconds
|
||||
uint8_t mqtt_qos = 0;
|
||||
bool mqtt_retain = false;
|
||||
bool enabled = true;
|
||||
uint8_t dallas_format = 1;
|
||||
bool nested_format = true;
|
||||
uint8_t ha_climate_format = 1;
|
||||
bool ha_enabled = true;
|
||||
std::string base = "ems-esp";
|
||||
uint16_t publish_time = 10; // seconds
|
||||
uint8_t mqtt_qos = 0;
|
||||
bool mqtt_retain = false;
|
||||
bool enabled = true;
|
||||
uint8_t dallas_format = 1;
|
||||
uint8_t nested_format = 1;
|
||||
uint8_t ha_climate_format = 1;
|
||||
bool ha_enabled = true;
|
||||
String base = "ems-esp";
|
||||
uint8_t subscribe_format = 0;
|
||||
|
||||
String hostname = "ems-esp";
|
||||
String jwtSecret = "ems-esp";
|
||||
@@ -48,7 +49,7 @@ class DummySettings {
|
||||
String staticIPConfig = "";
|
||||
String dnsIP1 = "";
|
||||
String dnsIP2 = "";
|
||||
uint8_t ethernet_profile = 0;
|
||||
String board_profile = "CUSTOM";
|
||||
uint16_t publish_time_boiler = 10;
|
||||
uint16_t publish_time_thermostat = 10;
|
||||
uint16_t publish_time_solar = 10;
|
||||
|
||||
2
makefile
2
makefile
@@ -33,7 +33,7 @@ CXX_STANDARD := -std=c++11
|
||||
#----------------------------------------------------------------------
|
||||
# Defined Symbols
|
||||
#----------------------------------------------------------------------
|
||||
DEFINES += -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_TEST
|
||||
DEFINES += -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_DEFAULT_BOARD_PROFILE=\"LOLIN\"
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# Sources & Files
|
||||
|
||||
29
mock-api/README.md
Normal file
29
mock-api/README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
(https://github.com/emsesp/EMS-ESP32/issues/41)
|
||||
|
||||
When developing and testing the web interface, it's handy not to bother with re-flashing an ESP32 each time. The idea is to mimic the ESP using a mock/stub server that responds to the REST (HTTP POST & GET) and WebSocket calls.
|
||||
|
||||
To set it up it do
|
||||
```sh
|
||||
% cd mock-api
|
||||
% npm install
|
||||
% cd interface
|
||||
% npm install
|
||||
```
|
||||
|
||||
and to run it
|
||||
```sh
|
||||
% cd interface
|
||||
% npm run dev
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- It's for local development only
|
||||
- `src/.env.development` is no longer required
|
||||
- CORS is removed, also the build flag
|
||||
- new file `interface/src/setupProxy.js`
|
||||
- new files `mock-api/server.js` with the hardcoded data. Requires its own npm packages for express
|
||||
|
||||
## ToDo
|
||||
- add filter rule to prevent from exposing yourself to malicious attacks when running the dev server(https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a)
|
||||
2818
mock-api/package-lock.json
generated
Normal file
2818
mock-api/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
mock-api/package.json
Normal file
17
mock-api/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "api",
|
||||
"version": "1.0.0",
|
||||
"private": "true",
|
||||
"description": "mock api for EMS-ESP",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"dev": "nodemon ./server.js localhost 3080",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "proddy",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"express": "^4.17.1",
|
||||
"nodemon": "^2.0.7"
|
||||
}
|
||||
}
|
||||
344
mock-api/server.js
Normal file
344
mock-api/server.js
Normal file
@@ -0,0 +1,344 @@
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3080;
|
||||
|
||||
app.use(express.static(path.join(__dirname, '../interface/build')));
|
||||
app.use(express.json());
|
||||
|
||||
const ENDPOINT_ROOT = "/rest/";
|
||||
|
||||
// NTP
|
||||
const NTP_STATUS_ENDPOINT = ENDPOINT_ROOT + "ntpStatus";
|
||||
const NTP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "ntpSettings";
|
||||
const TIME_ENDPOINT = ENDPOINT_ROOT + "time";
|
||||
const ntp_settings = {
|
||||
"enabled": true, "server": "time.google.com", "tz_label": "Europe/Amsterdam", "tz_format": "CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
};
|
||||
const ntp_status = {
|
||||
"status": 1, "utc_time": "2021-04-01T14:25:42Z", "local_time": "2021-04-01T16:25:42", "server": "time.google.com", "uptime": 856
|
||||
}
|
||||
|
||||
// AP
|
||||
const AP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "apSettings";
|
||||
const AP_STATUS_ENDPOINT = ENDPOINT_ROOT + "apStatus";
|
||||
const ap_settings = {
|
||||
"provision_mode": 1, "ssid": "ems-esp", "password": "ems-esp-neo", "local_ip": "192.168.4.1",
|
||||
"gateway_ip": "192.168.4.1", "subnet_mask": "255.255.255.0"
|
||||
};
|
||||
const ap_status = {
|
||||
"status": 1, "ip_address": "192.168.4.1", "mac_address": "3C:61:05:03:AB:2D", "station_num": 0
|
||||
};
|
||||
|
||||
// NETWORK
|
||||
const NETWORK_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "networkSettings";
|
||||
const NETWORK_STATUS_ENDPOINT = ENDPOINT_ROOT + "networkStatus";
|
||||
const SCAN_NETWORKS_ENDPOINT = ENDPOINT_ROOT + "scanNetworks";
|
||||
const LIST_NETWORKS_ENDPOINT = ENDPOINT_ROOT + "listNetworks";
|
||||
const network_settings = {
|
||||
ssid: "myWifi", password: 'myPassword', hostname: 'ems-esp', static_ip_config: false
|
||||
};
|
||||
const network_status =
|
||||
{
|
||||
"status": 3, "local_ip": "10.10.10.101", "mac_address": "3C:61:05:03:AB:2C", "rssi": -41, "ssid": "home",
|
||||
"bssid": "06:ED:DA:FE:B4:68", "channel": 11, "subnet_mask": "255.255.255.0", "gateway_ip": "10.10.10.1",
|
||||
"dns_ip_1": "10.10.10.1", "dns_ip_2": "0.0.0.0"
|
||||
};
|
||||
const list_networks = {
|
||||
"networks": [
|
||||
{ "rssi": -40, "ssid": "", "bssid": "FC:EC:DA:FD:B4:68", "channel": 11, "encryption_type": 3 },
|
||||
{ "rssi": -41, "ssid": "home", "bssid": "02:EC:DA:FD:B4:68", "channel": 11, "encryption_type": 3 },
|
||||
{ "rssi": -42, "ssid": "", "bssid": "06:EC:DA:FD:B4:68", "channel": 11, "encryption_type": 3 },
|
||||
{ "rssi": -73, "ssid": "", "bssid": "FC:EC:DA:17:D4:7E", "channel": 1, "encryption_type": 3 },
|
||||
{ "rssi": -73, "ssid": "office", "bssid": "02:EC:DA:17:D4:7E", "channel": 1, "encryption_type": 3 },
|
||||
{ "rssi": -75, "ssid": "Erica", "bssid": "C8:D7:19:9A:88:BD", "channel": 2, "encryption_type": 3 },
|
||||
{ "rssi": -75, "ssid": "", "bssid": "C6:C9:E3:FF:A5:DE", "channel": 2, "encryption_type": 3 },
|
||||
{ "rssi": -76, "ssid": "Bruin", "bssid": "C0:C9:E3:FF:A5:DE", "channel": 2, "encryption_type": 3 },
|
||||
]
|
||||
};
|
||||
|
||||
// OTA
|
||||
const OTA_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "otaSettings";
|
||||
const ota_settings = {
|
||||
"enabled": true, "port": 8266, "password": "ems-esp-neo"
|
||||
};
|
||||
|
||||
// MQTT
|
||||
const MQTT_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "mqttSettings";
|
||||
const MQTT_STATUS_ENDPOINT = ENDPOINT_ROOT + "mqttStatus";
|
||||
const mqtt_settings = {
|
||||
"enabled": true, "host": "192.168.1.4", "port": 1883, "base": "ems-esp32", "username": "", "password": "",
|
||||
"client_id": "ems-esp32", "keep_alive": 60, "clean_session": true, "max_topic_length": 128,
|
||||
"publish_time_boiler": 10, "publish_time_thermostat": 10, "publish_time_solar": 10, "publish_time_mixer": 10,
|
||||
"publish_time_other": 10, "publish_time_sensor": 10, "mqtt_qos": 0, "mqtt_retain": false, "dallas_format": 1,
|
||||
"bool_format": 1, "ha_climate_format": 1, "ha_enabled": true, "nested_format": 1, "subscribe_format": 0
|
||||
};
|
||||
const mqtt_status = {
|
||||
"enabled": true, "connected": true, "client_id": "ems-esp32", "disconnect_reason": 0, "mqtt_fails": 0
|
||||
};
|
||||
|
||||
// SYSTEM
|
||||
const FEATURES_ENDPOINT = ENDPOINT_ROOT + "features";
|
||||
const VERIFY_AUTHORIZATION_ENDPOINT = ENDPOINT_ROOT + "verifyAuthorization";
|
||||
const SYSTEM_STATUS_ENDPOINT = ENDPOINT_ROOT + "systemStatus";
|
||||
const SECURITY_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "securitySettings";
|
||||
const RESTART_ENDPOINT = ENDPOINT_ROOT + "restart";
|
||||
const FACTORY_RESET_ENDPOINT = ENDPOINT_ROOT + "factoryReset";
|
||||
const UPLOAD_FIRMWARE_ENDPOINT = ENDPOINT_ROOT + "uploadFirmware";
|
||||
const SIGN_IN_ENDPOINT = ENDPOINT_ROOT + "signIn";
|
||||
const GENERATE_TOKEN_ENDPOINT = ENDPOINT_ROOT + "generateToken";
|
||||
const system_status = {
|
||||
"esp_platform": "ESP32", "max_alloc_heap": 113792, "psram_size": 0, "free_psram": 0, "cpu_freq_mhz": 240,
|
||||
"free_heap": 193340, "sdk_version": "v3.3.5-1-g85c43024c", "flash_chip_size": 4194304, "flash_chip_speed": 40000000,
|
||||
"fs_total": 65536, "fs_used": 16384, "uptime": "000+00:15:42.707"
|
||||
};
|
||||
const security_settings = {
|
||||
"jwt_secret": "naughty!", "users": [{ "username": "admin", "password": "admin", "admin": true }, { "username": "guest", "password": "guest", "admin": false }]
|
||||
};
|
||||
const features = {
|
||||
"project": true, "security": true, "mqtt": true, "ntp": true, "ota": true, "upload_firmware": true
|
||||
};
|
||||
const verify_authentication = { access_token: '1234' };
|
||||
const signin = {
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiYWRtaW4iOnRydWUsInZlcnNpb24iOiIzLjAuMmIwIn0.MsHSgoJKI1lyYz77EiT5ZN3ECMrb4mPv9FNy3udq0TU"
|
||||
};
|
||||
const generate_token = { token: '1234' };
|
||||
|
||||
// EMS-ESP Project specific
|
||||
const EMSESP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "emsespSettings";
|
||||
const EMSESP_ALLDEVICES_ENDPOINT = ENDPOINT_ROOT + "allDevices";
|
||||
const EMSESP_SCANDEVICES_ENDPOINT = ENDPOINT_ROOT + "scanDevices";
|
||||
const EMSESP_DEVICEDATA_ENDPOINT = ENDPOINT_ROOT + "deviceData";
|
||||
const EMSESP_STATUS_ENDPOINT = ENDPOINT_ROOT + "emsespStatus";
|
||||
const EMSESP_BOARDPROFILE_ENDPOINT = ENDPOINT_ROOT + "boardProfile";
|
||||
const WRITE_VALUE_ENDPOINT = ENDPOINT_ROOT + "writeValue";
|
||||
const emsesp_settings = {
|
||||
"tx_mode": 1, "tx_delay": 0, "ems_bus_id": 11, "syslog_enabled": false, "syslog_level": 3,
|
||||
"trace_raw": false, "syslog_mark_interval": 0, "syslog_host": "192.168.1.4", "syslog_port": 514,
|
||||
"master_thermostat": 0, "shower_timer": true, "shower_alert": false, "rx_gpio": 23, "tx_gpio": 5,
|
||||
"dallas_gpio": 3, "dallas_parasite": false, "led_gpio": 2, "hide_led": false, "api_enabled": true,
|
||||
"analog_enabled": false, "pbutton_gpio": 0, "board_profile": "S32"
|
||||
};
|
||||
const emsesp_alldevices = {
|
||||
"devices": [{
|
||||
"id": 1, "type": "Thermostat", "brand": "", "name": "RC20/Moduline 300",
|
||||
"deviceid": 23, "productid": 77, "version": "03.03"
|
||||
}, {
|
||||
"id": 2, "type": "Boiler", "brand": "Nefit", "name": "GBx72/Trendline/Cerapur/Greenstar Si/27i",
|
||||
"deviceid": 8, "productid": 123, "version": "06.01"
|
||||
}, {
|
||||
"id": 3, "type": "Controller", "brand": "", "name": "BC10",
|
||||
"deviceid": 9, "productid": 190, "version": "01.03"
|
||||
}],
|
||||
"sensors": []
|
||||
}
|
||||
const emsesp_status = {
|
||||
"status": 0, "rx_received": 344, "tx_sent": 104, "rx_quality": 100, "tx_quality": 100
|
||||
};
|
||||
const emsesp_devicedata_1 = {
|
||||
"name": "Thermostat: RC20/Moduline 300",
|
||||
"data": [
|
||||
"16:28:21 01/04/2021", "", "date/time", "datetime",
|
||||
"(0)", "", "error code", "",
|
||||
15, "°C", "(hc1) setpoint room temperature", "temp",
|
||||
20.5, "°C", "(hc1) current room temperature", "",
|
||||
"auto", "", "(hc1) mode", "mode"
|
||||
]
|
||||
};
|
||||
const emsesp_devicedata_2 = {
|
||||
"name": "Boiler: Nefit GBx72/Trendline/Cerapur/Greenstar Si/27i",
|
||||
"data": [
|
||||
"off", "", "heating active", "",
|
||||
"off", "", "warm water active", "",
|
||||
5, "°C", "selected flow temperature", "selflowtemp",
|
||||
0, "%", "burner selected max power", "",
|
||||
0, "%", "heating pump modulation", "",
|
||||
42.7, "°C", "current flow temperature", "",
|
||||
39, "°C", "return temperature", "",
|
||||
1.2, "bar", "system pressure", "",
|
||||
45.3, "°C", "max boiler temperature", "",
|
||||
"off", "", "gas", "",
|
||||
0, "uA", "flame current", "",
|
||||
"off", "", "heating pump", "",
|
||||
"off", "", "fan", "",
|
||||
"off", "", "ignition", "",
|
||||
"on", "", "heating activated", "",
|
||||
75, "°C", "heating temperature", "",
|
||||
90, "%", "burner pump max power", "",
|
||||
55, "%", "burner pump min power", "",
|
||||
1, null, "pump delay", "",
|
||||
10, null, "burner min period", "",
|
||||
0, "%", "burner min power", "",
|
||||
75, "%", "burner max power", "",
|
||||
-6, "°C", "hysteresis on temperature", "",
|
||||
6, "°C", "hysteresis off temperature", "",
|
||||
0, "%", "burner current power", "",
|
||||
295740, "", "burner # starts", "",
|
||||
"344 days 2 hours 8 minutes", null, "total burner operating time", "",
|
||||
"279 days 11 hours 55 minutes", null, "total heat operating time", "",
|
||||
"2946 days 19 hours 8 minutes", null, "total UBA operating time", "",
|
||||
"1C(210) 06.06.2020 12:07", "", "last error code", "",
|
||||
"0H", "", "service code", "",
|
||||
203, "", "service code number", "",
|
||||
"01.01.2012", "", "maintenance set date", "",
|
||||
"off", "", "maintenance scheduled", "",
|
||||
6000, "hours", "maintenance set time", "",
|
||||
60, "°C", "(warm water) selected temperature", "",
|
||||
62, "°C", "(warm water) set temperature", "",
|
||||
"flow", "", "(warm water) type", "",
|
||||
"hot", "", "(warm water) comfort", "",
|
||||
40, "", "(warm water) flow temperature offset", "",
|
||||
100, "%", "(warm water) max power", "",
|
||||
"off", "", "(warm water) circulation pump available", "",
|
||||
"3-way valve", "", "(warm water) charging type", "",
|
||||
70, "°C", "(warm water) disinfection temperature", "",
|
||||
"off", "", "(warm water) circulation pump freq", "",
|
||||
"off", "", "(warm water) circulation active", "",
|
||||
34.7, "°C", "(warm water) current intern temperature", "",
|
||||
0, "l/min", "(warm water) current tap water flow", "",
|
||||
34.6, "°C", "(warm water) storage intern temperature", "",
|
||||
"on", "", "(warm water) activated", "",
|
||||
"off", "", "(warm water) one time charging", "",
|
||||
"off", "", "(warm water) disinfecting", "",
|
||||
"off", "", "(warm water) charging", "",
|
||||
"off", "", "(warm water) recharging", "",
|
||||
"on", "", "(warm water) temperature ok", "",
|
||||
"off", "", "(warm water) active", "",
|
||||
"on", "", "(warm water) heating", "",
|
||||
262387, "", "(warm water) # starts", "",
|
||||
"64 days 14 hours 13 minutes", null, "(warm water) active time", ""
|
||||
]
|
||||
};
|
||||
|
||||
// NETWORK
|
||||
app.get(NETWORK_STATUS_ENDPOINT, (req, res) => { res.json(network_status); });
|
||||
app.get(NETWORK_SETTINGS_ENDPOINT, (req, res) => { res.json(network_settings); });
|
||||
app.post(NETWORK_SETTINGS_ENDPOINT, (req, res) => { res.json(network_settings); });
|
||||
app.get(LIST_NETWORKS_ENDPOINT, (req, res) => { res.json(list_networks); });
|
||||
app.get(SCAN_NETWORKS_ENDPOINT, (req, res) => { res.sendStatus(202); });
|
||||
|
||||
// AP
|
||||
app.get(AP_SETTINGS_ENDPOINT, (req, res) => { res.json(ap_settings); });
|
||||
app.get(AP_STATUS_ENDPOINT, (req, res) => { res.json(ap_status); });
|
||||
app.post(AP_SETTINGS_ENDPOINT, (req, res) => { res.json(ap_settings); });
|
||||
|
||||
// OTA
|
||||
app.get(OTA_SETTINGS_ENDPOINT, (req, res) => { res.json(ota_settings); });
|
||||
app.post(OTA_SETTINGS_ENDPOINT, (req, res) => { res.json(ota_settings); });
|
||||
|
||||
// MQTT
|
||||
app.get(MQTT_SETTINGS_ENDPOINT, (req, res) => { res.json(mqtt_settings); });
|
||||
app.post(MQTT_SETTINGS_ENDPOINT, (req, res) => { res.json(mqtt_settings); });
|
||||
app.get(MQTT_STATUS_ENDPOINT, (req, res) => { res.json(mqtt_status); });
|
||||
|
||||
// NTP
|
||||
app.get(NTP_SETTINGS_ENDPOINT, (req, res) => { res.json(ntp_settings); });
|
||||
app.post(NTP_SETTINGS_ENDPOINT, (req, res) => { res.json(ntp_settings); });
|
||||
app.get(NTP_STATUS_ENDPOINT, (req, res) => { res.json(ntp_status); });
|
||||
app.post(TIME_ENDPOINT, (req, res) => { res.sendStatus(200); });
|
||||
|
||||
// SYSTEM
|
||||
app.get(SYSTEM_STATUS_ENDPOINT, (req, res) => { res.json(system_status); });
|
||||
app.get(SECURITY_SETTINGS_ENDPOINT, (req, res) => { res.json(security_settings); });
|
||||
app.post(SECURITY_SETTINGS_ENDPOINT, (req, res) => { res.json(security_settings); });
|
||||
app.get(FEATURES_ENDPOINT, (req, res) => { res.json(features); });
|
||||
app.get(VERIFY_AUTHORIZATION_ENDPOINT, (req, res) => { res.json(verify_authentication); });
|
||||
app.post(RESTART_ENDPOINT, (req, res) => { res.sendStatus(200); });
|
||||
app.post(FACTORY_RESET_ENDPOINT, (req, res) => { res.sendStatus(200); });
|
||||
app.post(UPLOAD_FIRMWARE_ENDPOINT, (req, res) => { res.sendStatus(200); });
|
||||
app.post(SIGN_IN_ENDPOINT, (req, res) => { res.json(signin); });
|
||||
app.get(GENERATE_TOKEN_ENDPOINT, (req, res) => { res.json(generate_token); });
|
||||
|
||||
// EMS-ESP Project stuff
|
||||
app.get(EMSESP_SETTINGS_ENDPOINT, (req, res) => { res.json(emsesp_settings); });
|
||||
app.post(EMSESP_SETTINGS_ENDPOINT, (req, res) => { res.json(emsesp_settings); });
|
||||
app.get(EMSESP_ALLDEVICES_ENDPOINT, (req, res) => { res.json(emsesp_alldevices); });
|
||||
app.post(EMSESP_SCANDEVICES_ENDPOINT, (req, res) => { res.sendStatus(200); });
|
||||
app.get(EMSESP_STATUS_ENDPOINT, (req, res) => { res.json(emsesp_status); });
|
||||
app.post(EMSESP_DEVICEDATA_ENDPOINT, (req, res) => {
|
||||
const id = req.body.id;
|
||||
if (id == 1) {
|
||||
res.json(emsesp_devicedata_1);
|
||||
}
|
||||
if (id == 2) {
|
||||
res.json(emsesp_devicedata_2);
|
||||
}
|
||||
});
|
||||
|
||||
app.post(WRITE_VALUE_ENDPOINT, (req, res) => {
|
||||
const devicevalue = req.body.devicevalue;
|
||||
|
||||
console.log(devicevalue);
|
||||
|
||||
res.sendStatus(200);
|
||||
});
|
||||
|
||||
app.post(EMSESP_BOARDPROFILE_ENDPOINT, (req, res) => {
|
||||
const board_profile = req.body.code;
|
||||
|
||||
const data = {
|
||||
led_gpio: 1,
|
||||
dallas_gpio: 2,
|
||||
rx_gpio: 3,
|
||||
tx_gpio: 4,
|
||||
pbutton_gpio: 5
|
||||
};
|
||||
|
||||
if (board_profile == "S32") { // BBQKees Gateway S32
|
||||
data.led_gpio = 2;
|
||||
data.dallas_gpio = 18;
|
||||
data.rx_gpio = 23;
|
||||
data.tx_gpio = 5;
|
||||
data.pbutton_gpio = 0;
|
||||
} else if (board_profile == "E32") { // BBQKees Gateway E32
|
||||
data.led_gpio = 2;
|
||||
data.dallas_gpio = 4;
|
||||
data.rx_gpio = 5;
|
||||
data.tx_gpio = 17;
|
||||
data.pbutton_gpio = 33;
|
||||
} else if (board_profile == "MH-ET") { // MH-ET Live D1 Mini
|
||||
data.led_gpio = 2;
|
||||
data.dallas_gpio = 18;
|
||||
data.rx_gpio = 23;
|
||||
data.tx_gpio = 5;
|
||||
data.pbutton_gpio = 0;
|
||||
} else if (board_profile == "NODEMCU") { // NodeMCU 32S
|
||||
data.led_gpio = 2;
|
||||
data.dallas_gpio = 18;
|
||||
data.rx_gpio = 23;
|
||||
data.tx_gpio = 5;
|
||||
data.pbutton_gpio = 0;
|
||||
} else if (board_profile == "LOLIN") {// Lolin D32
|
||||
data.led_gpio = 2;
|
||||
data.dallas_gpio = 18;
|
||||
data.rx_gpio = 17;
|
||||
data.tx_gpio = 16;
|
||||
data.pbutton_gpio = 0;
|
||||
} else if (board_profile == "OLIMEX") {// Olimex ESP32-EVB (uses U1TXD/U1RXD/BUTTON, no LED or Dallas)
|
||||
data.led_gpio = 0;
|
||||
data.dallas_gpio = 0;
|
||||
data.rx_gpio = 36;
|
||||
data.tx_gpio = 4;
|
||||
data.pbutton_gpio = 34;
|
||||
// data = { 0, 0, 36, 4, 34};
|
||||
} else if (board_profile == "TLK110") {// Generic Ethernet (TLK110)
|
||||
data.led_gpio = 2;
|
||||
data.dallas_gpio = 4;
|
||||
data.rx_gpio = 5;
|
||||
data.tx_gpio = 17;
|
||||
data.pbutton_gpio = 33;
|
||||
} else if (board_profile == "LAN8720") {// Generic Ethernet (LAN8720)
|
||||
data.led_gpio = 2;
|
||||
data.dallas_gpio = 4;
|
||||
data.rx_gpio = 5;
|
||||
data.tx_gpio = 17;
|
||||
data.pbutton_gpio = 33;
|
||||
}
|
||||
|
||||
res.json(data);
|
||||
|
||||
});
|
||||
|
||||
app.listen(port);
|
||||
console.log(`Mock API Server is up and running at: http://localhost:${port}`);
|
||||
@@ -1,3 +1,5 @@
|
||||
; example custom platformio.ini file for EMS-ESP
|
||||
|
||||
[env]
|
||||
upload_protocol = espota
|
||||
upload_flags =
|
||||
@@ -6,14 +8,15 @@ upload_flags =
|
||||
upload_port = 10.10.10.101
|
||||
|
||||
[common]
|
||||
; debug_flags = -DENABLE_CORS -DEMSESP_TEST
|
||||
; debug_flags = -DEMSESP_DEBUG -DEMSESP_TEST
|
||||
debug_flags = -DEMSESP_TEST
|
||||
; options are EMSESP_DEBUG EMSESP_UART_DEBUG EMSESP_DEBUG_SENSOR
|
||||
; plus all the settings in default_settings.h, e.g. -DEMSESP_DEFAULT_BOARD_PROFILE=\"NODEMCU\"
|
||||
; debug_flags = -DEMSESP_DEBUG
|
||||
|
||||
[env:esp32]
|
||||
monitor_filters = esp32_exception_decoder
|
||||
debug_tool = esp-prog
|
||||
debug_init_break = tbreak setup
|
||||
extra_scripts =
|
||||
; to prevent the web UI from building each time, comment out this next line
|
||||
; pre:scripts/build_interface.py
|
||||
; scripts/rename_fw.py
|
||||
|
||||
|
||||
@@ -1,50 +1,37 @@
|
||||
; PlatformIO Project Configuration File for EMS-ESP
|
||||
; override any settings with your own local ones in pio_local.ini
|
||||
|
||||
[platformio]
|
||||
default_envs = esp32
|
||||
|
||||
# override any settings with your own local ones in pio_local.ini
|
||||
extra_configs =
|
||||
factory_settings.ini
|
||||
pio_local.ini
|
||||
|
||||
[common]
|
||||
; default platformio compile flags are: -fno-rtti -std=c++11 -Os -mlongcalls -mtext-section-literals -falign-functions=4 -ffunction-sections -fdata-sections -fno-exceptions -Wall
|
||||
core_build_flags = -Wno-deprecated-declarations
|
||||
-Wreturn-type
|
||||
-DCORE_DEBUG_LEVEL=0
|
||||
-DNDEBUG
|
||||
core_build_flags =
|
||||
-Wall
|
||||
-D CORE_DEBUG_LEVEL=0
|
||||
-D NDEBUG
|
||||
-D ARDUINO_ARCH_ESP32=1
|
||||
-D ESP32=1
|
||||
; -std=c++17 -std=gnu++17
|
||||
|
||||
esp32_build_flags = -DARDUINO_ARCH_ESP32=1
|
||||
-DESP32=1
|
||||
-DBOARD_HAS_PSRAM
|
||||
; -std=c17 -std=c++17 -std=gnu++17
|
||||
core_unbuild_flags =
|
||||
; -std=gnu++11
|
||||
|
||||
build_flags =
|
||||
${common.core_build_flags}
|
||||
${factory_settings.build_flags}
|
||||
-D FT_PROJECT=1
|
||||
-D FT_SECURITY=1
|
||||
-D FT_MQTT=1
|
||||
-D FT_OTA=1
|
||||
-D FT_NTP=1
|
||||
-D FT_UPLOAD_FIRMWARE=1
|
||||
-D ONEWIRE_CRC16=0
|
||||
-D NO_GLOBAL_ARDUINOOTA
|
||||
-D ARDUINOJSON_ENABLE_STD_STRING=1
|
||||
-D CORS_ORIGIN=\"http://localhost:3000\"
|
||||
|
||||
build_unflags = -Wall
|
||||
-Wdeprecated-declarations
|
||||
|
||||
esp32_build_unflags =
|
||||
; -std=gnu++11
|
||||
unbuild_flags =
|
||||
${common.core_unbuild_flags}
|
||||
|
||||
; these are set in your pio_local.ini
|
||||
debug_flags =
|
||||
; -D EMSESP_DEBUG
|
||||
; -D EMSESP_UART_DEBUG
|
||||
; -D EMSESP_TEST
|
||||
; -D ENABLE_CORS
|
||||
|
||||
[env]
|
||||
framework = arduino
|
||||
@@ -52,23 +39,21 @@ monitor_speed = 115200
|
||||
upload_speed = 921600
|
||||
build_type = release
|
||||
lib_ldf_mode = chain+
|
||||
; lib_compat_mode = strict
|
||||
|
||||
check_tool = cppcheck, clangtidy
|
||||
check_severity = high, medium
|
||||
check_flags =
|
||||
cppcheck: --std=c++11 -v
|
||||
clangtidy: --checks=-*,clang-analyzer-*,performance-*
|
||||
cppcheck: --std=c++11 -v
|
||||
clangtidy: --checks=-*,clang-analyzer-*,performance-*
|
||||
|
||||
; build for GitHub Actions CI
|
||||
[env:ci]
|
||||
extra_scripts =
|
||||
scripts/rename_fw.py
|
||||
extra_scripts = scripts/rename_fw.py
|
||||
board = esp32dev
|
||||
platform = espressif32
|
||||
board_build.partitions = esp32_partition_app1984k_spiffs64k.csv
|
||||
build_flags = ${common.build_flags} ${common.esp32_build_flags}
|
||||
build_unflags = ${common.build_unflags} ${common.esp32_build_unflags}
|
||||
build_flags = ${common.build_flags}
|
||||
build_unflags = ${common.unbuild_flags}
|
||||
|
||||
[env:esp32]
|
||||
extra_scripts =
|
||||
@@ -76,11 +61,11 @@ extra_scripts =
|
||||
scripts/rename_fw.py
|
||||
board = esp32dev
|
||||
platform = espressif32
|
||||
;platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#idf-release/v4.2
|
||||
; ; toolchain-xtensa32 @ 2.80200.200226
|
||||
; ; toolchain-xtensa32 @ 5.100200.201223
|
||||
; toolchain-xtensa32 @ 2.80400.2020 ; c70ec8a-toolchain-xtensa32-linux_x86_64-2.80400.2020.tar.gz
|
||||
; platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#idf-release/v4.2
|
||||
; toolchain-xtensa32 @ 2.80200.200226
|
||||
; toolchain-xtensa32 @ 5.100200.201223
|
||||
; toolchain-xtensa32 @ 2.80400.2020
|
||||
; platform = https://github.com/platformio/platform-espressif32.git
|
||||
board_build.partitions = esp32_partition_app1984k_spiffs64k.csv ; https://github.com/espressif/arduino-esp32/blob/master/tools/partitions/
|
||||
build_flags = ${common.build_flags} ${common.esp32_build_flags} ${common.debug_flags}
|
||||
build_unflags = ${common.build_unflags} ${common.esp32_build_unflags}
|
||||
board_build.partitions = esp32_partition_app1984k_spiffs64k.csv
|
||||
build_flags = ${common.build_flags} ${common.debug_flags}
|
||||
build_unflags = ${common.unbuild_flags}
|
||||
|
||||
@@ -10,9 +10,9 @@ def upload(source, target, env):
|
||||
platform = "esp" + env['PIOPLATFORM'].strip("espressif")
|
||||
|
||||
if platform == 'esp8266':
|
||||
call(["cmd.exe", "/c", "C:\\Users\\Paul\\OneDrive\\Desktop\\com8266.bat"])
|
||||
call(["cmd.exe", "/c", "C:\\Users\\Paul\\Desktop\\ems-esp8266.bat"])
|
||||
|
||||
if platform == 'esp32':
|
||||
call(["cmd.exe", "/c", "C:\\Users\\Paul\\OneDrive\\Desktop\\com32.bat"])
|
||||
call(["cmd.exe", "/c", "C:\\Users\\Paul\\Desktop\\ems-esp32.bat"])
|
||||
|
||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", [upload])
|
||||
@@ -18,18 +18,16 @@
|
||||
|
||||
#include "emsesp.h"
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
WebAPIService::WebAPIService(AsyncWebServer * server) {
|
||||
server->on(EMSESP_API_SERVICE_PATH, HTTP_GET, std::bind(&WebAPIService::webAPIService, this, std::placeholders::_1));
|
||||
server->on(EMSESP_API_SERVICE_PATH, HTTP_GET, std::bind(&WebAPIService::webAPIService, this, _1));
|
||||
}
|
||||
|
||||
// e.g. http://ems-esp/api?device=boiler&cmd=wwtemp&data=20&id=1
|
||||
void WebAPIService::webAPIService(AsyncWebServerRequest * request) {
|
||||
// see if the API is enabled
|
||||
bool api_enabled;
|
||||
EMSESP::webSettingsService.read([&](WebSettings & settings) { api_enabled = settings.api_enabled; });
|
||||
|
||||
// must have device and cmd parameters
|
||||
if ((!request->hasParam(F_(device))) || (!request->hasParam(F_(cmd)))) {
|
||||
request->send(400, "text/plain", F("Invalid syntax"));
|
||||
@@ -47,12 +45,6 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request) {
|
||||
// get cmd, we know we have one
|
||||
String cmd = request->getParam(F_(cmd))->value();
|
||||
|
||||
// look up command in our list
|
||||
if (Command::find_command(device_type, cmd.c_str()) == nullptr) {
|
||||
request->send(400, "text/plain", F("Invalid cmd"));
|
||||
return;
|
||||
}
|
||||
|
||||
String data;
|
||||
if (request->hasParam(F_(data))) {
|
||||
data = request->getParam(F_(data))->value();
|
||||
@@ -68,15 +60,17 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request) {
|
||||
}
|
||||
|
||||
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN);
|
||||
JsonObject json = doc.to<JsonObject>();
|
||||
bool ok = false;
|
||||
JsonObject json = doc.to<JsonObject>();
|
||||
bool ok = false;
|
||||
|
||||
// execute the command
|
||||
if (data.isEmpty()) {
|
||||
ok = Command::call(device_type, cmd.c_str(), nullptr, id.toInt(), json); // command only
|
||||
} else {
|
||||
// we only allow commands with parameters if the API is enabled
|
||||
bool api_enabled;
|
||||
EMSESP::webSettingsService.read([&](WebSettings & settings) { api_enabled = settings.api_enabled; });
|
||||
if (api_enabled) {
|
||||
// we only allow commands with parameters if the API is enabled
|
||||
ok = Command::call(device_type, cmd.c_str(), data.c_str(), id.toInt(), json); // has cmd, data and id
|
||||
} else {
|
||||
request->send(401, "text/plain", F("Unauthorized"));
|
||||
@@ -88,7 +82,7 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request) {
|
||||
doc.shrinkToFit();
|
||||
std::string buffer;
|
||||
serializeJsonPretty(doc, buffer);
|
||||
request->send(200, "text/plain", buffer.c_str());
|
||||
request->send(200, "text/plain;charset=utf-8", buffer.c_str());
|
||||
return;
|
||||
}
|
||||
request->send(200, "text/plain", ok ? F("OK") : F("Invalid"));
|
||||
|
||||
@@ -23,19 +23,18 @@ namespace emsesp {
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
WebDevicesService::WebDevicesService(AsyncWebServer * server, SecurityManager * securityManager)
|
||||
: _device_dataHandler(DEVICE_DATA_SERVICE_PATH,
|
||||
securityManager->wrapCallback(std::bind(&WebDevicesService::device_data, this, _1, _2), AuthenticationPredicates::IS_AUTHENTICATED)) {
|
||||
server->on(EMSESP_DEVICES_SERVICE_PATH,
|
||||
HTTP_GET,
|
||||
securityManager->wrapRequest(std::bind(&WebDevicesService::all_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
|
||||
server->on(SCAN_DEVICES_SERVICE_PATH,
|
||||
HTTP_GET,
|
||||
securityManager->wrapRequest(std::bind(&WebDevicesService::scan_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
: _device_dataHandler(DEVICE_DATA_SERVICE_PATH, securityManager->wrapCallback(std::bind(&WebDevicesService::device_data, this, _1, _2), AuthenticationPredicates::IS_AUTHENTICATED))
|
||||
, _writevalue_dataHandler(WRITE_VALUE_SERVICE_PATH, securityManager->wrapCallback(std::bind(&WebDevicesService::write_value, this, _1, _2), AuthenticationPredicates::IS_AUTHENTICATED)) {
|
||||
server->on(EMSESP_DEVICES_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WebDevicesService::all_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
server->on(SCAN_DEVICES_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WebDevicesService::scan_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
|
||||
_device_dataHandler.setMethod(HTTP_POST);
|
||||
_device_dataHandler.setMaxContentLength(256);
|
||||
server->addHandler(&_device_dataHandler);
|
||||
|
||||
_writevalue_dataHandler.setMethod(HTTP_POST);
|
||||
_writevalue_dataHandler.setMaxContentLength(256);
|
||||
server->addHandler(&_writevalue_dataHandler);
|
||||
}
|
||||
|
||||
void WebDevicesService::scan_devices(AsyncWebServerRequest * request) {
|
||||
@@ -101,4 +100,49 @@ void WebDevicesService::device_data(AsyncWebServerRequest * request, JsonVariant
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
// takes a command and its data value from a specific Device, from the Web
|
||||
void WebDevicesService::write_value(AsyncWebServerRequest * request, JsonVariant & json) {
|
||||
// only issue commands if the API is enabled
|
||||
EMSESP::webSettingsService.read([&](WebSettings & settings) {
|
||||
if (!settings.api_enabled) {
|
||||
request->send(403); // forbidden error
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
if (json.is<JsonObject>()) {
|
||||
JsonObject dv = json["devicevalue"];
|
||||
|
||||
// using the unique ID from the web find the real device type
|
||||
for (const auto & emsdevice : EMSESP::emsdevices) {
|
||||
if (emsdevice) {
|
||||
if (emsdevice->unique_id() == dv["id"].as<int>()) {
|
||||
const char * cmd = dv["cmd"];
|
||||
uint8_t device_type = emsdevice->device_type();
|
||||
bool ok = false;
|
||||
char s[10];
|
||||
// the data could be in any format, but we need string
|
||||
JsonVariant data = dv["data"];
|
||||
if (data.is<char *>()) {
|
||||
ok = Command::call(device_type, cmd, data.as<const char *>());
|
||||
} else if (data.is<int>()) {
|
||||
ok = Command::call(device_type, cmd, Helpers::render_value(s, data.as<int16_t>(), 0));
|
||||
} else if (data.is<float>()) {
|
||||
ok = Command::call(device_type, cmd, Helpers::render_value(s, (float)data.as<float>(), 1));
|
||||
} else if (data.is<bool>()) {
|
||||
ok = Command::call(device_type, cmd, data.as<bool>() ? "true" : "false");
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
request->send(200);
|
||||
}
|
||||
return; // found device, quit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
request->send(204); // no content error
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user