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:
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
93
interface/src/project/LightMqttSettingsController.tsx
Normal file
93
interface/src/project/LightMqttSettingsController.tsx
Normal 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>
|
||||
);
|
||||
}
|
70
interface/src/project/LightStateRestController.tsx
Normal file
70
interface/src/project/LightStateRestController.tsx
Normal 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>
|
||||
);
|
||||
}
|
62
interface/src/project/LightStateWebSocketController.tsx
Normal file
62
interface/src/project/LightStateWebSocketController.tsx
Normal 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>
|
||||
);
|
||||
}
|
9
interface/src/project/types.ts
Normal file
9
interface/src/project/types.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export interface LightState {
|
||||
led_on: boolean;
|
||||
}
|
||||
|
||||
export interface LightMqttSettings {
|
||||
unique_id : string;
|
||||
name: string;
|
||||
mqtt_path : string;
|
||||
}
|
Reference in New Issue
Block a user