Rework backend add MQTT and WebSocket support

* Update back end to add MQTT and WebSocket support
* Update demo project to demonstrate MQTT and WebSockets
* Update documentation to describe newly added and modified functionallity
* Introduce separate MQTT pub/sub, HTTP get/post and WebSocket rx/tx classes
* Significant reanaming - more accurate class names
* Use PROGMEM_WWW as default
* Update README documenting PROGMEM_WWW as default
* Update README with API changes
This commit is contained in:
rjwats
2020-05-14 23:23:45 +01:00
committed by GitHub
parent c47ea49a5d
commit a1f4e57a21
77 changed files with 2907 additions and 1192 deletions

View File

@ -1,75 +0,0 @@
import React, { Component } from 'react';
import { ValidatorForm } from 'react-material-ui-form-validator';
import { Typography, Slider, Box } from '@material-ui/core';
import SaveIcon from '@material-ui/icons/Save';
import { ENDPOINT_ROOT } from '../api';
import { restController, RestControllerProps, RestFormLoader, RestFormProps, FormActions, FormButton, SectionContent } from '../components';
export const DEMO_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "demoSettings";
interface DemoSettings {
blink_speed: number;
}
type DemoControllerProps = RestControllerProps<DemoSettings>;
class DemoController extends Component<DemoControllerProps> {
componentDidMount() {
this.props.loadData();
}
render() {
return (
<SectionContent title='Demo Controller' titleGutter>
<RestFormLoader
{...this.props}
render={props => (
<DemoControllerForm {...props} />
)}
/>
</SectionContent>
)
}
}
export default restController(DEMO_SETTINGS_ENDPOINT, DemoController);
const valueToPercentage = (value: number) => `${Math.round(value / 255 * 100)}%`;
type DemoControllerFormProps = RestFormProps<DemoSettings>;
function DemoControllerForm(props: DemoControllerFormProps) {
const { data, saveData, loadData, handleSliderChange } = props;
return (
<ValidatorForm onSubmit={saveData}>
<Typography id="blink-speed-slider">
Blink Speed
</Typography>
<Box pt={5}>
<Slider
value={data.blink_speed}
valueLabelFormat={valueToPercentage}
aria-labelledby="blink-speed-slider"
valueLabelDisplay="on"
min={0}
max={255}
onChange={handleSliderChange('blink_speed')}
/>
</Box>
<FormActions>
<FormButton startIcon={<SaveIcon />} variant="contained" color="primary" type="submit">
Save
</FormButton>
<FormButton variant="contained" color="secondary" onClick={loadData}>
Reset
</FormButton>
</FormActions>
</ValidatorForm>
);
}

View File

@ -65,10 +65,26 @@ class DemoInformation extends Component {
</TableRow>
<TableRow>
<TableCell>
DemoController.tsx
LightStateRestController.tsx
</TableCell>
<TableCell>
The demo controller tab, to control the built-in LED.
A form which lets the user control the LED over a REST service.
</TableCell>
</TableRow>
<TableRow>
<TableCell>
LightStateWebSocketController.tsx
</TableCell>
<TableCell>
A form which lets the user control and monitor the status of the LED over WebSockets.
</TableCell>
</TableRow>
<TableRow>
<TableCell>
LightMqttSettingsController.tsx
</TableCell>
<TableCell>
A form which lets the user change the MQTT settings for MQTT based control of the LED.
</TableCell>
</TableRow>
</TableBody>

View File

@ -8,7 +8,9 @@ import { MenuAppBar } from '../components';
import { AuthenticatedRoute } from '../authentication';
import DemoInformation from './DemoInformation';
import DemoController from './DemoController';
import LightStateRestController from './LightStateRestController';
import LightStateWebSocketController from './LightStateWebSocketController';
import LightMqttSettingsController from './LightMqttSettingsController';
class DemoProject extends Component<RouteComponentProps> {
@ -20,12 +22,16 @@ class DemoProject extends Component<RouteComponentProps> {
return (
<MenuAppBar sectionTitle="Demo Project">
<Tabs value={this.props.match.url} onChange={this.handleTabChange} variant="fullWidth">
<Tab value={`/${PROJECT_PATH}/demo/information`} label="Demo Information" />
<Tab value={`/${PROJECT_PATH}/demo/controller`} label="Demo Controller" />
<Tab value={`/${PROJECT_PATH}/demo/information`} label="Information" />
<Tab value={`/${PROJECT_PATH}/demo/rest`} label="REST Controller" />
<Tab value={`/${PROJECT_PATH}/demo/socket`} label="WebSocket Controller" />
<Tab value={`/${PROJECT_PATH}/demo/mqtt`} label="MQTT Controller" />
</Tabs>
<Switch>
<AuthenticatedRoute exact path={`/${PROJECT_PATH}/demo/information`} component={DemoInformation} />
<AuthenticatedRoute exact path={`/${PROJECT_PATH}/demo/controller`} component={DemoController} />
<AuthenticatedRoute exact path={`/${PROJECT_PATH}/demo/rest`} component={LightStateRestController} />
<AuthenticatedRoute exact path={`/${PROJECT_PATH}/demo/socket`} component={LightStateWebSocketController} />
<AuthenticatedRoute exact path={`/${PROJECT_PATH}/demo/mqtt`} component={LightMqttSettingsController} />
<Redirect to={`/${PROJECT_PATH}/demo/information`} />
</Switch>
</MenuAppBar>

View File

@ -0,0 +1,93 @@
import React, { Component } from 'react';
import { ValidatorForm, TextValidator } from 'react-material-ui-form-validator';
import { Typography, Box } from '@material-ui/core';
import SaveIcon from '@material-ui/icons/Save';
import { ENDPOINT_ROOT } from '../api';
import { restController, RestControllerProps, RestFormLoader, RestFormProps, FormActions, FormButton, SectionContent } from '../components';
import { LightMqttSettings } from './types';
export const LIGHT_BROKER_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "brokerSettings";
type LightMqttSettingsControllerProps = RestControllerProps<LightMqttSettings>;
class LightMqttSettingsController extends Component<LightMqttSettingsControllerProps> {
componentDidMount() {
this.props.loadData();
}
render() {
return (
<SectionContent title='MQTT Controller' titleGutter>
<RestFormLoader
{...this.props}
render={props => (
<LightMqttSettingsControllerForm {...props} />
)}
/>
</SectionContent>
)
}
}
export default restController(LIGHT_BROKER_SETTINGS_ENDPOINT, LightMqttSettingsController);
type LightMqttSettingsControllerFormProps = RestFormProps<LightMqttSettings>;
function LightMqttSettingsControllerForm(props: LightMqttSettingsControllerFormProps) {
const { data, saveData, loadData, handleValueChange } = props;
return (
<ValidatorForm onSubmit={saveData}>
<Box bgcolor="primary.main" color="primary.contrastText" p={2} mt={2} mb={2}>
<Typography variant="body1">
The LED is controllable via MQTT with the demo project designed to work with Home Assistant's auto discovery feature.
</Typography>
</Box>
<TextValidator
validators={['required']}
errorMessages={['Unique ID is required']}
name="unique_id"
label="Unique ID"
fullWidth
variant="outlined"
value={data.unique_id}
onChange={handleValueChange('unique_id')}
margin="normal"
/>
<TextValidator
validators={['required']}
errorMessages={['Name is required']}
name="name"
label="Name"
fullWidth
variant="outlined"
value={data.name}
onChange={handleValueChange('name')}
margin="normal"
/>
<TextValidator
validators={['required']}
errorMessages={['MQTT Path is required']}
name="mqtt_path"
label="MQTT Path"
fullWidth
variant="outlined"
value={data.mqtt_path}
onChange={handleValueChange('mqtt_path')}
margin="normal"
/>
<FormActions>
<FormButton startIcon={<SaveIcon />} variant="contained" color="primary" type="submit">
Save
</FormButton>
<FormButton variant="contained" color="secondary" onClick={loadData}>
Reset
</FormButton>
</FormActions>
</ValidatorForm>
);
}

View File

@ -0,0 +1,70 @@
import React, { Component } from 'react';
import { ValidatorForm } from 'react-material-ui-form-validator';
import { Typography, Box, Checkbox } from '@material-ui/core';
import SaveIcon from '@material-ui/icons/Save';
import { ENDPOINT_ROOT } from '../api';
import { restController, RestControllerProps, RestFormLoader, RestFormProps, FormActions, FormButton, SectionContent, BlockFormControlLabel } from '../components';
import { LightState } from './types';
export const LIGHT_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "lightState";
type LightStateRestControllerProps = RestControllerProps<LightState>;
class LightStateRestController extends Component<LightStateRestControllerProps> {
componentDidMount() {
this.props.loadData();
}
render() {
return (
<SectionContent title='REST Controller' titleGutter>
<RestFormLoader
{...this.props}
render={props => (
<LightStateRestControllerForm {...props} />
)}
/>
</SectionContent>
)
}
}
export default restController(LIGHT_SETTINGS_ENDPOINT, LightStateRestController);
type LightStateRestControllerFormProps = RestFormProps<LightState>;
function LightStateRestControllerForm(props: LightStateRestControllerFormProps) {
const { data, saveData, loadData, handleValueChange } = props;
return (
<ValidatorForm onSubmit={saveData}>
<Box bgcolor="primary.main" color="primary.contrastText" p={2} mt={2} mb={2}>
<Typography variant="body1">
The form below controls the LED via the RESTful service exposed by the ESP device.
</Typography>
</Box>
<BlockFormControlLabel
control={
<Checkbox
checked={data.led_on}
onChange={handleValueChange('led_on')}
color="primary"
/>
}
label="LED State?"
/>
<FormActions>
<FormButton startIcon={<SaveIcon />} variant="contained" color="primary" type="submit">
Save
</FormButton>
<FormButton variant="contained" color="secondary" onClick={loadData}>
Reset
</FormButton>
</FormActions>
</ValidatorForm>
);
}

View File

@ -0,0 +1,62 @@
import React, { Component } from 'react';
import { ValidatorForm } from 'react-material-ui-form-validator';
import { Typography, Box, Switch } from '@material-ui/core';
import { WEB_SOCKET_ROOT } from '../api';
import { WebSocketControllerProps, WebSocketFormLoader, WebSocketFormProps, webSocketController } from '../components';
import { SectionContent, BlockFormControlLabel } from '../components';
import { LightState } from './types';
export const LIGHT_SETTINGS_WEBSOCKET_URL = WEB_SOCKET_ROOT + "lightState";
type LightStateWebSocketControllerProps = WebSocketControllerProps<LightState>;
class LightStateWebSocketController extends Component<LightStateWebSocketControllerProps> {
render() {
return (
<SectionContent title='WebSocket Controller' titleGutter>
<WebSocketFormLoader
{...this.props}
render={props => (
<LightStateWebSocketControllerForm {...props} />
)}
/>
</SectionContent>
)
}
}
export default webSocketController(LIGHT_SETTINGS_WEBSOCKET_URL, 100, LightStateWebSocketController);
type LightStateWebSocketControllerFormProps = WebSocketFormProps<LightState>;
function LightStateWebSocketControllerForm(props: LightStateWebSocketControllerFormProps) {
const { data, saveData, setData } = props;
const changeLedOn = (event: React.ChangeEvent<HTMLInputElement>) => {
setData({ led_on: event.target.checked }, saveData);
}
return (
<ValidatorForm onSubmit={saveData}>
<Box bgcolor="primary.main" color="primary.contrastText" p={2} mt={2} mb={2}>
<Typography variant="body1">
The switch below controls the LED via the WebSocket. It will automatically update whenever the LED state changes.
</Typography>
</Box>
<BlockFormControlLabel
control={
<Switch
checked={data.led_on}
onChange={changeLedOn}
color="primary"
/>
}
label="LED State?"
/>
</ValidatorForm>
);
}

View File

@ -0,0 +1,9 @@
export interface LightState {
led_on: boolean;
}
export interface LightMqttSettings {
unique_id : string;
name: string;
mqtt_path : string;
}