Merge remote-tracking branch 'origin/dev' into main

This commit is contained in:
proddy
2021-05-04 12:21:51 +02:00
90 changed files with 13231 additions and 6082 deletions

2
.gitignore vendored
View File

@@ -25,6 +25,6 @@ emsesp
/data/www /data/www
/lib/framework/WWWData.h /lib/framework/WWWData.h
/interface/build /interface/build
/interface/node_modules node_modules
/interface/.eslintcache /interface/.eslintcache

View File

@@ -5,6 +5,30 @@ 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/), 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). 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 ## [3.0.1] March 30 2021
## Added ## Added

View File

@@ -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. **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
[![version](https://img.shields.io/github/release/emsesp/EMS-ESP32.svg?label=Latest%20Release)](https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md) [![version](https://img.shields.io/github/release/emsesp/EMS-ESP32.svg?label=Latest%20Release)](https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md)
[![release-date](https://img.shields.io/github/release-date/emsesp/EMS-ESP32.svg?label=Released)](https://github.com/emsesp/EMS-ESP32/commits/main) [![release-date](https://img.shields.io/github/release-date/emsesp/EMS-ESP32.svg?label=Released)](https://github.com/emsesp/EMS-ESP32/commits/main)

View File

@@ -1,11 +1,5 @@
# Change the IP address to that of your ESP device to enable local development of the UI. # 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
# with -DENABLE_CORS
# my Wifi # REACT_APP_HTTP_ROOT=http://localhost:3000
#REACT_APP_HTTP_ROOT=http://10.10.10.101 # REACT_APP_WEB_SOCKET_ROOT=ws://localhost:3000
#REACT_APP_WEB_SOCKET_ROOT=ws://10.10.10.101
# my Ethernet
REACT_APP_HTTP_ROOT=http://192.168.1.134
REACT_APP_WEB_SOCKET_ROOT=ws://http://192.168.1.134

View File

@@ -9,7 +9,7 @@ const fs = require('fs');
module.exports = function override(config, env) { module.exports = function override(config, env) {
if (env === "production") { 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.filename = 'js/[id].[chunkhash:4].js';
config.output.chunkFilename = 'js/[id].[chunkhash:4].js'; config.output.chunkFilename = 'js/[id].[chunkhash:4].js';

10569
interface/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,37 +3,40 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@material-ui/core": "^4.11.3", "@material-ui/core": "^4.11.4",
"@material-ui/icons": "^4.11.2", "@material-ui/icons": "^4.11.2",
"@types/lodash": "^4.14.168", "@types/lodash": "^4.14.168",
"@types/node": "^12.20.4", "@types/node": "^15.0.1",
"@types/react": "^17.0.3", "@types/react": "^17.0.4",
"@types/react-dom": "^17.0.1", "@types/react-dom": "^17.0.3",
"@types/react-material-ui-form-validator": "^2.1.0", "@types/react-material-ui-form-validator": "^2.1.0",
"@types/react-router": "^5.1.12", "@types/react-router": "^5.1.13",
"@types/react-router-dom": "^5.1.6", "@types/react-router-dom": "^5.1.7",
"compression-webpack-plugin": "^4.0.0", "compression-webpack-plugin": "^5.0.2",
"express": "^4.17.1",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mime-types": "^2.1.29", "mime-types": "^2.1.30",
"notistack": "^1.0.5", "notistack": "^1.0.6",
"parse-ms": "^2.1.0", "parse-ms": "^3.0.0",
"react": "^17.0.1", "react": "^17.0.2",
"react-dom": "^17.0.1", "react-dom": "^17.0.2",
"react-dropzone": "^11.3.1", "react-dropzone": "^11.3.2",
"react-form-validator-core": "^1.1.1", "react-form-validator-core": "^1.1.1",
"react-material-ui-form-validator": "^2.1.4", "react-material-ui-form-validator": "^2.1.4",
"react-router": "^5.2.0", "react-router": "^5.2.0",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "4.0.1", "react-scripts": "4.0.3",
"sockette": "^2.0.6", "sockette": "^2.0.6",
"typescript": "4.0.5", "typescript": "4.2.4",
"zlib": "^1.0.5" "zlib": "^1.0.5"
}, },
"scripts": { "scripts": {
"start": "react-app-rewired start", "start": "react-app-rewired start",
"build": "react-app-rewired build", "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": { "eslintConfig": {
"extends": "react-app" "extends": "react-app"
@@ -51,6 +54,10 @@
] ]
}, },
"devDependencies": { "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" "react-app-rewired": "^2.1.8"
} }
} }

View File

@@ -18,5 +18,6 @@ export const SYSTEM_STATUS_ENDPOINT = ENDPOINT_ROOT + "systemStatus";
export const SIGN_IN_ENDPOINT = ENDPOINT_ROOT + "signIn"; export const SIGN_IN_ENDPOINT = ENDPOINT_ROOT + "signIn";
export const VERIFY_AUTHORIZATION_ENDPOINT = ENDPOINT_ROOT + "verifyAuthorization"; export const VERIFY_AUTHORIZATION_ENDPOINT = ENDPOINT_ROOT + "verifyAuthorization";
export const SECURITY_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "securitySettings"; export const SECURITY_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "securitySettings";
export const GENERATE_TOKEN_ENDPOINT = ENDPOINT_ROOT + "generateToken";
export const RESTART_ENDPOINT = ENDPOINT_ROOT + "restart"; export const RESTART_ENDPOINT = ENDPOINT_ROOT + "restart";
export const FACTORY_RESET_ENDPOINT = ENDPOINT_ROOT + "factoryReset"; export const FACTORY_RESET_ENDPOINT = ENDPOINT_ROOT + "factoryReset";

View File

@@ -5,10 +5,8 @@ import { withSnackbar, WithSnackbarProps } from 'notistack';
import * as Authentication from './Authentication'; import * as Authentication from './Authentication';
import { withAuthenticationContext, AuthenticationContextProps, AuthenticatedContext, AuthenticatedContextValue } from './AuthenticationContext'; import { withAuthenticationContext, AuthenticationContextProps, AuthenticatedContext, AuthenticatedContextValue } from './AuthenticationContext';
type ChildComponent = React.ComponentType<RouteComponentProps<any>> | React.ComponentType<any>;
interface AuthenticatedRouteProps extends RouteProps, WithSnackbarProps, AuthenticationContextProps { interface AuthenticatedRouteProps extends RouteProps, WithSnackbarProps, AuthenticationContextProps {
component: ChildComponent; component: React.ComponentType<RouteComponentProps<any>> | React.ComponentType<any>;
} }
type RenderComponent = (props: RouteComponentProps<any>) => React.ReactNode; type RenderComponent = (props: RouteComponentProps<any>) => React.ReactNode;
@@ -27,7 +25,7 @@ export class AuthenticatedRoute extends React.Component<AuthenticatedRouteProps>
); );
} }
Authentication.storeLoginRedirect(location); Authentication.storeLoginRedirect(location);
enqueueSnackbar("Please sign in to continue.", { variant: 'info' }); enqueueSnackbar("Please sign in to continue", { variant: 'info' });
return ( return (
<Redirect to='/' /> <Redirect to='/' />
); );

View File

@@ -101,7 +101,7 @@ class AuthenticationWrapper extends React.Component<AuthenticationWrapperProps,
me: undefined me: undefined
} }
}); });
this.props.enqueueSnackbar("You have signed out.", { variant: 'success', }); this.props.enqueueSnackbar("You have signed out", { variant: 'success', });
history.push('/'); history.push('/');
} }

View File

@@ -19,8 +19,10 @@ class UnauthenticatedRoute extends Route<UnauthenticatedRouteProps> {
if (authenticationContext.me) { if (authenticationContext.me) {
return (<Redirect to={Authentication.fetchLoginRedirect(features)} />); return (<Redirect to={Authentication.fetchLoginRedirect(features)} />);
} }
if (Component) {
return (<Component {...props} />); return (<Component {...props} />);
} }
}
return ( return (
<Route {...rest} render={renderComponent} /> <Route {...rest} render={renderComponent} />
); );

View File

@@ -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 { 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 SettingsIcon from '@material-ui/icons/Settings';
import AccessTimeIcon from '@material-ui/icons/AccessTime'; import AccessTimeIcon from '@material-ui/icons/AccessTime';
import AccountCircleIcon from '@material-ui/icons/AccountCircle'; import AccountCircleIcon from '@material-ui/icons/AccountCircle';
@@ -146,7 +146,7 @@ class MenuAppBar extends React.Component<MenuAppBarProps, MenuAppBarState> {
<List> <List>
<ListItem to='/network/' selected={path.startsWith('/network/')} button component={Link}> <ListItem to='/network/' selected={path.startsWith('/network/')} button component={Link}>
<ListItemIcon> <ListItemIcon>
<WifiIcon /> <SettingsEthernetIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary="Network Connection" /> <ListItemText primary="Network Connection" />
</ListItem> </ListItem>

View File

@@ -1,22 +1,31 @@
import React from 'react'; import React from "react";
import { TextValidator, ValidatorForm, SelectValidator } from 'react-material-ui-form-validator'; import {
TextValidator,
ValidatorForm,
SelectValidator,
} from "react-material-ui-form-validator";
import { Checkbox, TextField, Typography } from '@material-ui/core'; import { Checkbox, TextField, Typography } from "@material-ui/core";
import SaveIcon from '@material-ui/icons/Save'; import SaveIcon from "@material-ui/icons/Save";
import MenuItem from '@material-ui/core/MenuItem'; import MenuItem from "@material-ui/core/MenuItem";
import { RestFormProps, FormActions, FormButton, BlockFormControlLabel, PasswordValidator } from '../components'; import {
import { isIP, isHostname, or, isPath } from '../validators'; 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>; type MqttSettingsFormProps = RestFormProps<MqttSettings>;
class MqttSettingsForm extends React.Component<MqttSettingsFormProps> { class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
componentDidMount() { componentDidMount() {
ValidatorForm.addValidationRule('isIPOrHostname', or(isIP, isHostname)); ValidatorForm.addValidationRule("isIPOrHostname", or(isIP, isHostname));
ValidatorForm.addValidationRule('isPath', isPath); ValidatorForm.addValidationRule("isPath", isPath);
} }
render() { render() {
@@ -27,44 +36,57 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
control={ control={
<Checkbox <Checkbox
checked={data.enabled} checked={data.enabled}
onChange={handleValueChange('enabled')} onChange={handleValueChange("enabled")}
value="enabled" value="enabled"
/> />
} }
label="Enable MQTT" label="Enable MQTT"
/> />
<TextValidator <TextValidator
validators={['required', 'isIPOrHostname']} validators={["required", "isIPOrHostname"]}
errorMessages={['Host is required', "Not a valid IP address or hostname"]} errorMessages={[
"Host is required",
"Not a valid IP address or hostname",
]}
name="host" name="host"
label="Host" label="Host"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.host} value={data.host}
onChange={handleValueChange('host')} onChange={handleValueChange("host")}
margin="normal" margin="normal"
/> />
<TextValidator <TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']} validators={[
errorMessages={['Port is required', "Must be a number", "Must be greater than 0 ", "Max value is 65535"]} "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" name="port"
label="Port" label="Port"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.port} value={data.port}
type="number" type="number"
onChange={handleValueChange('port')} onChange={handleValueChange("port")}
margin="normal" margin="normal"
/> />
<TextValidator <TextValidator
validators={['required', 'isPath']} validators={["required", "isPath"]}
errorMessages={['Base is required', "Not a valid Path"]} errorMessages={["Base is required", "Not a valid Path"]}
name="base" name="base"
label="Base" label="Base"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.base} value={data.base}
onChange={handleValueChange('base')} onChange={handleValueChange("base")}
margin="normal" margin="normal"
/> />
<TextField <TextField
@@ -73,7 +95,7 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.username} value={data.username}
onChange={handleValueChange('username')} onChange={handleValueChange("username")}
margin="normal" margin="normal"
/> />
<PasswordValidator <PasswordValidator
@@ -82,7 +104,7 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.password} value={data.password}
onChange={handleValueChange('password')} onChange={handleValueChange("password")}
margin="normal" margin="normal"
/> />
<TextField <TextField
@@ -91,28 +113,40 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.client_id} value={data.client_id}
onChange={handleValueChange('client_id')} onChange={handleValueChange("client_id")}
margin="normal" margin="normal"
/> />
<TextValidator <TextValidator
validators={['required', 'isNumber', 'minNumber:1', 'maxNumber:65535']} validators={[
errorMessages={['Keep alive is required', "Must be a number", "Must be greater than 0", "Max value is 65535"]} "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" name="keep_alive"
label="Keep Alive (seconds)" label="Keep Alive (seconds)"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.keep_alive} value={data.keep_alive}
type="number" type="number"
onChange={handleValueChange('keep_alive')} onChange={handleValueChange("keep_alive")}
margin="normal" margin="normal"
/> />
<SelectValidator name="mqtt_qos" <SelectValidator
name="mqtt_qos"
label="QoS" label="QoS"
value={data.mqtt_qos} value={data.mqtt_qos}
fullWidth fullWidth
variant="outlined" variant="outlined"
onChange={handleValueChange('mqtt_qos')} onChange={handleValueChange("mqtt_qos")}
margin="normal"> margin="normal"
>
<MenuItem value={0}>0 (default)</MenuItem> <MenuItem value={0}>0 (default)</MenuItem>
<MenuItem value={1}>1</MenuItem> <MenuItem value={1}>1</MenuItem>
<MenuItem value={2}>2</MenuItem> <MenuItem value={2}>2</MenuItem>
@@ -121,7 +155,7 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
control={ control={
<Checkbox <Checkbox
checked={data.clean_session} checked={data.clean_session}
onChange={handleValueChange('clean_session')} onChange={handleValueChange("clean_session")}
value="clean_session" value="clean_session"
/> />
} }
@@ -131,7 +165,7 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
control={ control={
<Checkbox <Checkbox
checked={data.mqtt_retain} checked={data.mqtt_retain}
onChange={handleValueChange('mqtt_retain')} onChange={handleValueChange("mqtt_retain")}
value="mqtt_retain" value="mqtt_retain"
/> />
} }
@@ -141,45 +175,53 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
<Typography variant="h6" color="primary"> <Typography variant="h6" color="primary">
Formatting Formatting
</Typography> </Typography>
<BlockFormControlLabel <SelectValidator
control={ name="nested_format"
<Checkbox label="Topic/Payload Format"
checked={data.nested_format} value={data.nested_format}
onChange={handleValueChange('nested_format')} fullWidth
value="nested_format" variant="outlined"
/> onChange={handleValueChange("nested_format")}
} margin="normal"
label="Nested format (Thermostat & Mixer only)" >
/> <MenuItem value={1}>nested on a single topic</MenuItem>
<SelectValidator name="dallas_format" <MenuItem value={2}>as individual topics</MenuItem>
</SelectValidator>
<SelectValidator
name="dallas_format"
label="Dallas Sensor Payload Grouping" label="Dallas Sensor Payload Grouping"
value={data.dallas_format} value={data.dallas_format}
fullWidth fullWidth
variant="outlined" variant="outlined"
onChange={handleValueChange('dallas_format')} onChange={handleValueChange("dallas_format")}
margin="normal"> margin="normal"
>
<MenuItem value={1}>by Sensor ID</MenuItem> <MenuItem value={1}>by Sensor ID</MenuItem>
<MenuItem value={2}>by Number</MenuItem> <MenuItem value={2}>by Number</MenuItem>
</SelectValidator> </SelectValidator>
<SelectValidator name="bool_format" <SelectValidator
name="bool_format"
label="Boolean Format" label="Boolean Format"
value={data.bool_format} value={data.bool_format}
fullWidth fullWidth
variant="outlined" variant="outlined"
onChange={handleValueChange('bool_format')} onChange={handleValueChange("bool_format")}
margin="normal"> margin="normal"
>
<MenuItem value={1}>"on"/"off"</MenuItem> <MenuItem value={1}>"on"/"off"</MenuItem>
<MenuItem value={2}>true/false</MenuItem> <MenuItem value={2}>true/false</MenuItem>
<MenuItem value={3}>1/0</MenuItem> <MenuItem value={3}>1/0</MenuItem>
<MenuItem value={4}>"ON"/"OFF"</MenuItem> <MenuItem value={4}>"ON"/"OFF"</MenuItem>
</SelectValidator> </SelectValidator>
<SelectValidator name="subscribe_format" <SelectValidator
name="subscribe_format"
label="Subscribe Format" label="Subscribe Format"
value={data.subscribe_format} value={data.subscribe_format}
fullWidth fullWidth
variant="outlined" variant="outlined"
onChange={handleValueChange('subscribe_format')} onChange={handleValueChange("subscribe_format")}
margin="normal"> margin="normal"
>
<MenuItem value={0}>general device topic</MenuItem> <MenuItem value={0}>general device topic</MenuItem>
<MenuItem value={1}>individual topics, main heating circuit</MenuItem> <MenuItem value={1}>individual topics, main heating circuit</MenuItem>
<MenuItem value={2}>individual topics, all heating circuits</MenuItem> <MenuItem value={2}>individual topics, all heating circuits</MenuItem>
@@ -188,103 +230,170 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
control={ control={
<Checkbox <Checkbox
checked={data.ha_enabled} checked={data.ha_enabled}
onChange={handleValueChange('ha_enabled')} onChange={handleValueChange("ha_enabled")}
value="ha_enabled" value="ha_enabled"
/> />
} }
label="Home Assistant MQTT Discovery" label="Use Home Assistant MQTT Discovery"
/> />
{ data.ha_enabled && {data.ha_enabled && (
<SelectValidator name="ha_climate_format" <SelectValidator
name="ha_climate_format"
label="Thermostat Room Temperature" label="Thermostat Room Temperature"
value={data.ha_climate_format} value={data.ha_climate_format}
fullWidth fullWidth
variant="outlined" variant="outlined"
onChange={handleValueChange('ha_climate_format')} onChange={handleValueChange("ha_climate_format")}
margin="normal"> margin="normal"
>
<MenuItem value={1}>use Current temperature (default)</MenuItem> <MenuItem value={1}>use Current temperature (default)</MenuItem>
<MenuItem value={2}>use Setpoint temperature</MenuItem> <MenuItem value={2}>use Setpoint temperature</MenuItem>
<MenuItem value={3}>Fix to 0</MenuItem> <MenuItem value={3}>Fix to 0</MenuItem>
</SelectValidator> </SelectValidator>
} )}
<br></br> <br></br>
<Typography variant="h6" color="primary"> <Typography variant="h6" color="primary">
Publish Intervals Publish Intervals
</Typography> </Typography>
<TextValidator <TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']} validators={[
errorMessages={['Publish time is required', "Must be a number", "Must be 0 or greater", "Max value is 65535"]} "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" name="publish_time_boiler"
label="Boiler Publish Interval (seconds, 0=on change)" label="Boiler Publish Interval (seconds, 0=on change)"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.publish_time_boiler} value={data.publish_time_boiler}
type="number" type="number"
onChange={handleValueChange('publish_time_boiler')} onChange={handleValueChange("publish_time_boiler")}
margin="normal" margin="normal"
/> />
<TextValidator <TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']} validators={[
errorMessages={['Publish time is required', "Must be a number", "Must be 0 or greater", "Max value is 65535"]} "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" name="publish_time_thermostat"
label="Thermostat Publish Interval (seconds, 0=on change)" label="Thermostat Publish Interval (seconds, 0=on change)"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.publish_time_thermostat} value={data.publish_time_thermostat}
type="number" type="number"
onChange={handleValueChange('publish_time_thermostat')} onChange={handleValueChange("publish_time_thermostat")}
margin="normal" margin="normal"
/> />
<TextValidator <TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']} validators={[
errorMessages={['Publish time is required', "Must be a number", "Must be 0 or greater", "Max value is 65535"]} "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" name="publish_time_solar"
label="Solar Publish Interval (seconds, 0=on change)" label="Solar Publish Interval (seconds, 0=on change)"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.publish_time_solar} value={data.publish_time_solar}
type="number" type="number"
onChange={handleValueChange('publish_time_solar')} onChange={handleValueChange("publish_time_solar")}
margin="normal" margin="normal"
/> />
<TextValidator <TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']} validators={[
errorMessages={['Publish time is required', "Must be a number", "Must be 0 or greater", "Max value is 65535"]} "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" name="publish_time_mixer"
label="Mixer Publish Interval (seconds, 0=on change)" label="Mixer Publish Interval (seconds, 0=on change)"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.publish_time_mixer} value={data.publish_time_mixer}
type="number" type="number"
onChange={handleValueChange('publish_time_mixer')} onChange={handleValueChange("publish_time_mixer")}
margin="normal" margin="normal"
/> />
<TextValidator <TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']} validators={[
errorMessages={['Publish time is required', "Must be a number", "Must be 0 or greater", "Max value is 65535"]} "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" name="publish_time_sensor"
label="Sensors Publish Interval (seconds, 0=on change)" label="Sensors Publish Interval (seconds, 0=on change)"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.publish_time_sensor} value={data.publish_time_sensor}
type="number" type="number"
onChange={handleValueChange('publish_time_sensor')} onChange={handleValueChange("publish_time_sensor")}
margin="normal" margin="normal"
/> />
<TextValidator <TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']} validators={[
errorMessages={['Publish time is required', "Must be a number", "Must be 0 or greater", "Max value is 65535"]} "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" name="publish_time_other"
label="All other Modules Publish Interval (seconds, 0=on change)" label="All other Modules Publish Interval (seconds, 0=on change)"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.publish_time_other} value={data.publish_time_other}
type="number" type="number"
onChange={handleValueChange('publish_time_other')} onChange={handleValueChange("publish_time_other")}
margin="normal" margin="normal"
/> />
<FormActions> <FormActions>
<FormButton startIcon={<SaveIcon />} variant="contained" color="primary" type="submit"> <FormButton
startIcon={<SaveIcon />}
variant="contained"
color="primary"
type="submit"
>
Save Save
</FormButton> </FormButton>
</FormActions> </FormActions>

View File

@@ -40,6 +40,6 @@ export interface MqttSettings {
mqtt_retain: boolean; mqtt_retain: boolean;
ha_enabled: boolean; ha_enabled: boolean;
ha_climate_format: number; ha_climate_format: number;
nested_format: boolean; nested_format: number;
subscribe_format: number; subscribe_format: number;
} }

View File

@@ -1,13 +1,22 @@
import { Theme } from '@material-ui/core'; import { Theme } from "@material-ui/core";
import { NetworkStatus, NetworkConnectionStatus } from './types'; import { NetworkStatus, NetworkConnectionStatus } from "./types";
export const isConnected = ({ status }: NetworkStatus) => { 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) { switch (status) {
case NetworkConnectionStatus.WIFI_STATUS_IDLE: case NetworkConnectionStatus.WIFI_STATUS_IDLE:
case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED: case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED:
@@ -22,7 +31,7 @@ export const networkStatusHighlight = ({ status }: NetworkStatus, theme: Theme)
default: default:
return theme.palette.warning.main; return theme.palette.warning.main;
} }
} };
export const networkStatus = ({ status }: NetworkStatus) => { export const networkStatus = ({ status }: NetworkStatus) => {
switch (status) { switch (status) {
@@ -45,4 +54,4 @@ export const networkStatus = ({ status }: NetworkStatus) => {
default: default:
return "Unknown"; return "Unknown";
} }
} };

View File

@@ -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 { WithTheme, withTheme } from "@material-ui/core/styles";
import { Avatar, Divider, List, ListItem, ListItemAvatar, ListItemText } from '@material-ui/core'; import {
Avatar,
Divider,
List,
ListItem,
ListItemAvatar,
ListItemText,
} from "@material-ui/core";
import DNSIcon from '@material-ui/icons/Dns'; import DNSIcon from "@material-ui/icons/Dns";
import WifiIcon from '@material-ui/icons/Wifi'; import WifiIcon from "@material-ui/icons/Wifi";
import SettingsInputComponentIcon from '@material-ui/icons/SettingsInputComponent'; import RouterIcon from "@material-ui/icons/Router";
import SettingsInputAntennaIcon from '@material-ui/icons/SettingsInputAntenna'; import SettingsInputComponentIcon from "@material-ui/icons/SettingsInputComponent";
import DeviceHubIcon from '@material-ui/icons/DeviceHub'; import SettingsInputAntennaIcon from "@material-ui/icons/SettingsInputAntenna";
import RefreshIcon from '@material-ui/icons/Refresh'; import DeviceHubIcon from "@material-ui/icons/DeviceHub";
import RefreshIcon from "@material-ui/icons/Refresh";
import { RestFormProps, FormActions, FormButton, HighlightAvatar } from '../components'; import {
import { networkStatus, networkStatusHighlight, isConnected, isWiFi } from './NetworkStatus'; RestFormProps,
import { NetworkStatus } from './types'; FormActions,
FormButton,
HighlightAvatar,
} from "../components";
import {
networkStatus,
networkStatusHighlight,
isConnected,
isWiFi,
isEthernet,
} from "./NetworkStatus";
import { NetworkStatus } from "./types";
type NetworkStatusFormProps = RestFormProps<NetworkStatus> & WithTheme; type NetworkStatusFormProps = RestFormProps<NetworkStatus> & WithTheme;
class NetworkStatusForm extends Component<NetworkStatusFormProps> { class NetworkStatusForm extends Component<NetworkStatusFormProps> {
dnsServers(status: NetworkStatus) { dnsServers(status: NetworkStatus) {
if (!status.dns_ip_1) { if (!status.dns_ip_1) {
return "none"; 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() { createListItems() {
const { data, theme } = this.props const { data, theme } = this.props;
return ( return (
<Fragment> <Fragment>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
<HighlightAvatar color={networkStatusHighlight(data, theme)}> <HighlightAvatar color={networkStatusHighlight(data, theme)}>
<WifiIcon /> {isWiFi(data) && <WifiIcon />}
{isEthernet(data) && <RouterIcon />}
</HighlightAvatar> </HighlightAvatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="Status" secondary={networkStatus(data)} /> <ListItemText primary="Status" secondary={networkStatus(data)} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
{ {isWiFi(data) && (
isWiFi(data) &&
<Fragment> <Fragment>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
@@ -51,8 +70,8 @@ class NetworkStatusForm extends Component<NetworkStatusFormProps> {
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
</Fragment> </Fragment>
} )}
{ isConnected(data) && {isConnected(data) && (
<Fragment> <Fragment>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
@@ -67,14 +86,20 @@ class NetworkStatusForm extends Component<NetworkStatusFormProps> {
<DeviceHubIcon /> <DeviceHubIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="MAC Address" secondary={data.mac_address} /> <ListItemText
primary="MAC Address"
secondary={data.mac_address}
/>
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
<Avatar>#</Avatar> <Avatar>#</Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="Subnet Mask" secondary={data.subnet_mask} /> <ListItemText
primary="Subnet Mask"
secondary={data.subnet_mask}
/>
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem> <ListItem>
@@ -83,7 +108,10 @@ class NetworkStatusForm extends Component<NetworkStatusFormProps> {
<SettingsInputComponentIcon /> <SettingsInputComponentIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="Gateway IP" secondary={data.gateway_ip || "none"} /> <ListItemText
primary="Gateway IP"
secondary={data.gateway_ip || "none"}
/>
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem> <ListItem>
@@ -92,11 +120,14 @@ class NetworkStatusForm extends Component<NetworkStatusFormProps> {
<DNSIcon /> <DNSIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="DNS Server IP" secondary={this.dnsServers(data)} /> <ListItemText
primary="DNS Server IP"
secondary={this.dnsServers(data)}
/>
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
</Fragment> </Fragment>
} )}
</Fragment> </Fragment>
); );
} }
@@ -104,18 +135,20 @@ class NetworkStatusForm extends Component<NetworkStatusFormProps> {
render() { render() {
return ( return (
<Fragment> <Fragment>
<List> <List>{this.createListItems()}</List>
{this.createListItems()}
</List>
<FormActions> <FormActions>
<FormButton startIcon={<RefreshIcon />} variant="contained" color="secondary" onClick={this.props.loadData}> <FormButton
startIcon={<RefreshIcon />}
variant="contained"
color="secondary"
onClick={this.props.loadData}
>
Refresh Refresh
</FormButton> </FormButton>
</FormActions> </FormActions>
</Fragment> </Fragment>
); );
} }
} }
export default withTheme(NetworkStatusForm); export default withTheme(NetworkStatusForm);

View File

@@ -89,6 +89,8 @@ class NTPStatusForm extends Component<NTPStatusFormProps, NTPStatusFormState> {
<Dialog <Dialog
open={this.state.settingTime} open={this.state.settingTime}
onClose={this.closeSetTime} onClose={this.closeSetTime}
fullWidth
maxWidth="sm"
> >
<DialogTitle>Set Time</DialogTitle> <DialogTitle>Set Time</DialogTitle>
<DialogContent dividers> <DialogContent dividers>

View File

@@ -9,7 +9,7 @@ export const BOARD_PROFILES: BoardProfiles = {
"S32": "BBQKees Gateway S32", "S32": "BBQKees Gateway S32",
"E32": "BBQKees Gateway E32", "E32": "BBQKees Gateway E32",
"NODEMCU": "NodeMCU 32S", "NODEMCU": "NodeMCU 32S",
"MT-ET": "MT-ET Live D1 Mini", "MH-ET": "MH-ET Live D1 Mini",
"LOLIN": "Lolin D32", "LOLIN": "Lolin D32",
"OLIMEX": "Olimex ESP32-EVB", "OLIMEX": "Olimex ESP32-EVB",
"TLK110": "Generic Ethernet (TLK110)", "TLK110": "Generic Ethernet (TLK110)",

View File

@@ -8,15 +8,21 @@ import {
import RefreshIcon from "@material-ui/icons/Refresh"; import RefreshIcon from "@material-ui/icons/Refresh";
import ListIcon from "@material-ui/icons/List"; 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 } from "../components"; import { RestFormProps, FormButton, extractEventValue } from "../components";
import { EMSESPDevices, EMSESPDeviceData, Device } from "./EMSESPtypes"; import { EMSESPDevices, EMSESPDeviceData, Device, DeviceValue } from "./EMSESPtypes";
import ValueForm from './ValueForm';
import { ENDPOINT_ROOT } from "../api"; import { ENDPOINT_ROOT } from "../api";
export const SCANDEVICES_ENDPOINT = ENDPOINT_ROOT + "scanDevices"; export const SCANDEVICES_ENDPOINT = ENDPOINT_ROOT + "scanDevices";
export const DEVICE_DATA_ENDPOINT = ENDPOINT_ROOT + "deviceData"; export const DEVICE_DATA_ENDPOINT = ENDPOINT_ROOT + "deviceData";
export const WRITE_VALUE_ENDPOINT = ENDPOINT_ROOT + "writeValue";
const StyledTableCell = withStyles((theme: Theme) => const StyledTableCell = withStyles((theme: Theme) =>
createStyles({ createStyles({
@@ -30,6 +36,16 @@ const StyledTableCell = withStyles((theme: Theme) =>
}) })
)(TableCell); )(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) { function compareDevices(a: Device, b: Device) {
if (a.type < b.type) { if (a.type < b.type) {
return -1; return -1;
@@ -44,11 +60,11 @@ interface EMSESPDevicesFormState {
confirmScanDevices: boolean; confirmScanDevices: boolean;
processing: boolean; processing: boolean;
deviceData?: EMSESPDeviceData; deviceData?: EMSESPDeviceData;
selectedDevice?: number;
devicevalue?: DeviceValue;
} }
type EMSESPDevicesFormProps = RestFormProps<EMSESPDevices> & type EMSESPDevicesFormProps = RestFormProps<EMSESPDevices> & AuthenticatedContextProps & WithWidthProps;
AuthenticatedContextProps &
WithWidthProps;
function formatTemp(t: string) { function formatTemp(t: string) {
if (t == null) { if (t == null) {
@@ -67,9 +83,66 @@ function formatUnit(u: string) {
class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesFormState> { class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesFormState> {
state: EMSESPDevicesFormState = { state: EMSESPDevicesFormState = {
confirmScanDevices: false, 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 = () => { noDevices = () => {
return this.props.data.devices.length === 0; return this.props.data.devices.length === 0;
}; };
@@ -95,50 +168,20 @@ class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesF
size="small" size="small"
padding={isWidthDown("xs", width!) ? "none" : "default"} 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> <TableBody>
{data.devices.sort(compareDevices).map((device) => ( {data.devices.sort(compareDevices).map((device) => (
<TableRow <TableRow hover key={device.id} onClick={() => this.handleRowClick(device)}>
hover <TableCell>
key={device.id} <CustomTooltip
onClick={() => this.handleRowClick(device.id)} title={"DeviceID:0x" + ("00" + device.deviceid.toString(16).toUpperCase()).slice(-2) + " ProductID:" + device.productid + " Version:" + device.version}
>
<TableCell component="th" scope="row">
<Tooltip
title="click for details..."
arrow
placement="right-end" placement="right-end"
> >
<Button <Button startIcon={<ListIcon />} size="small" variant="outlined">
startIcon={<ListIcon />}
size="small"
variant="outlined"
>
{device.type} {device.type}
</Button> </Button>
</Tooltip> </CustomTooltip>
</TableCell> </TableCell>
<TableCell align="center">{device.brand}</TableCell> <TableCell align="right">{device.brand + " " + device.name} </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>
</TableRow> </TableRow>
))} ))}
</TableBody> </TableBody>
@@ -148,13 +191,10 @@ class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesF
<Box <Box
bgcolor="error.main" bgcolor="error.main"
color="error.contrastText" color="error.contrastText"
p={2} p={2} mt={2} mb={2}
mt={2}
mb={2}
> >
<Typography variant="body1"> <Typography variant="body1">
No EMS devices found. Check the connections and for possible Tx No EMS devices found. Check the connections and for possible Tx errors.
errors.
</Typography> </Typography>
</Box> </Box>
)} )}
@@ -210,28 +250,19 @@ class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesF
<Dialog <Dialog
open={this.state.confirmScanDevices} open={this.state.confirmScanDevices}
onClose={this.onScanDevicesRejected} onClose={this.onScanDevicesRejected}
fullWidth
maxWidth="sm"
> >
<DialogTitle>Confirm Scan Devices</DialogTitle> <DialogTitle>Confirm Scan Devices</DialogTitle>
<DialogContent dividers> <DialogContent dividers>
Are you sure you want to initiate a scan on the EMS bus for all new Are you sure you want to initiate a scan on the EMS bus for all new devices?
devices?
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button <Button variant="contained" onClick={this.onScanDevicesRejected} color="secondary">
variant="contained"
onClick={this.onScanDevicesRejected}
color="secondary"
>
Cancel Cancel
</Button> </Button>
<Button <Button
startIcon={<RefreshIcon />} startIcon={<RefreshIcon />} variant="contained" onClick={this.onScanDevicesConfirmed} disabled={this.state.processing} color="primary" autoFocus>
variant="contained"
onClick={this.onScanDevicesConfirmed}
disabled={this.state.processing}
color="primary"
autoFocus
>
Start Scan Start Scan
</Button> </Button>
</DialogActions> </DialogActions>
@@ -268,11 +299,11 @@ class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesF
}); });
}; };
handleRowClick = (id: any) => { handleRowClick = (device: any) => {
this.setState({ deviceData: undefined }); this.setState({ selectedDevice: device.id, deviceData: undefined });
redirectingAuthorizedFetch(DEVICE_DATA_ENDPOINT, { redirectingAuthorizedFetch(DEVICE_DATA_ENDPOINT, {
method: "POST", method: "POST",
body: JSON.stringify({ id: id }), body: JSON.stringify({ id: device.id }),
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
@@ -298,6 +329,7 @@ class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesF
renderDeviceData() { renderDeviceData() {
const { deviceData } = this.state; const { deviceData } = this.state;
const { width } = this.props; const { width } = this.props;
const me = this.props.authenticatedContext.me;
if (this.noDevices()) { if (this.noDevices()) {
return; return;
@@ -321,16 +353,28 @@ class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesF
size="small" size="small"
padding={isWidthDown("xs", width!) ? "none" : "default"} padding={isWidthDown("xs", width!) ? "none" : "default"}
> >
<TableHead></TableHead> <TableHead>
</TableHead>
<TableBody> <TableBody>
{deviceData.data.map((item, i) => { {deviceData.data.map((item, i) => {
if (i % 3) { if (i % 4) {
return null; return null;
} else { } else {
return ( return (
<TableRow key={i}> <TableRow hover key={i}>
<TableCell component="th" scope="row">{deviceData.data[i + 2]}</TableCell> <TableCell padding="checkbox" style={{ width: 18 }} >
<TableCell align="right">{deviceData.data[i]}{formatUnit(deviceData.data[i + 1])}</TableCell> {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> </TableRow>
); );
} }
@@ -351,6 +395,7 @@ class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesF
} }
render() { render() {
const { devicevalue } = this.state;
return ( return (
<Fragment> <Fragment>
<br></br> <br></br>
@@ -360,27 +405,26 @@ class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesF
<br></br> <br></br>
<Box display="flex" flexWrap="wrap"> <Box display="flex" flexWrap="wrap">
<Box flexGrow={1} padding={1}> <Box flexGrow={1} padding={1}>
<FormButton <FormButton startIcon={<RefreshIcon />} variant="contained" color="secondary" onClick={this.props.loadData} >
startIcon={<RefreshIcon />}
variant="contained"
color="secondary"
onClick={this.props.loadData}
>
Refresh Refresh
</FormButton> </FormButton>
</Box> </Box>
<Box flexWrap="none" padding={1} whiteSpace="nowrap"> <Box flexWrap="none" padding={1} whiteSpace="nowrap">
<FormButton <FormButton startIcon={<RefreshIcon />} variant="contained" onClick={this.onScanDevices} >
startIcon={<RefreshIcon />}
variant="contained"
color="primary"
onClick={this.onScanDevices}
>
Scan Devices Scan Devices
</FormButton> </FormButton>
</Box> </Box>
</Box> </Box>
{this.renderScanDevicesDialog()} {this.renderScanDevicesDialog()}
{
devicevalue &&
<ValueForm
devicevalue={devicevalue}
onDoneEditing={this.doneEditingValue}
onCancelEditing={this.cancelEditingValue}
handleValueChange={this.handleValueChange}
/>
}
</Fragment> </Fragment>
); );
} }

View File

@@ -1,50 +1,70 @@
import React from 'react'; import React from "react";
import { ValidatorForm, TextValidator, SelectValidator } from 'react-material-ui-form-validator'; import {
ValidatorForm,
TextValidator,
SelectValidator,
} from "react-material-ui-form-validator";
import { Checkbox, Typography, Box, Link, withWidth, WithWidthProps } from '@material-ui/core'; import {
import SaveIcon from '@material-ui/icons/Save'; Checkbox,
import MenuItem from '@material-ui/core/MenuItem'; 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 Grid from "@material-ui/core/Grid";
import { redirectingAuthorizedFetch, withAuthenticatedContext, AuthenticatedContextProps } from "../authentication"; import {
redirectingAuthorizedFetch,
withAuthenticatedContext,
AuthenticatedContextProps,
} from "../authentication";
import { RestFormProps, FormActions, FormButton, BlockFormControlLabel } from '../components'; import {
RestFormProps,
FormActions,
FormButton,
BlockFormControlLabel,
} from "../components";
import { isIP, optional } from '../validators'; import { isIP, optional } from "../validators";
import { EMSESPSettings } from './EMSESPtypes'; import { EMSESPSettings } from "./EMSESPtypes";
import { boardProfileSelectItems } from './EMSESPBoardProfiles'; import { boardProfileSelectItems } from "./EMSESPBoardProfiles";
import { ENDPOINT_ROOT } from "../api"; import { ENDPOINT_ROOT } from "../api";
export const BOARD_PROFILE_ENDPOINT = ENDPOINT_ROOT + "boardProfile"; export const BOARD_PROFILE_ENDPOINT = ENDPOINT_ROOT + "boardProfile";
type EMSESPSettingsFormProps = RestFormProps<EMSESPSettings> & AuthenticatedContextProps & WithWidthProps; type EMSESPSettingsFormProps = RestFormProps<EMSESPSettings> &
AuthenticatedContextProps &
WithWidthProps;
interface EMSESPSettingsFormState { interface EMSESPSettingsFormState {
processing: boolean; processing: boolean;
} }
class EMSESPSettingsForm extends React.Component<EMSESPSettingsFormProps> { class EMSESPSettingsForm extends React.Component<EMSESPSettingsFormProps> {
state: EMSESPSettingsFormState = { state: EMSESPSettingsFormState = {
processing: false processing: false,
} };
componentDidMount() { componentDidMount() {
ValidatorForm.addValidationRule('isOptionalIP', optional(isIP)); ValidatorForm.addValidationRule("isOptionalIP", optional(isIP));
} }
changeBoardProfile = (event: React.ChangeEvent<HTMLSelectElement>) => { changeBoardProfile = (event: React.ChangeEvent<HTMLSelectElement>) => {
const { data, setData } = this.props; const { data, setData } = this.props;
setData({ setData({
...data, ...data,
board_profile: event.target.value board_profile: event.target.value,
}); });
if (event.target.value === "CUSTOM") if (event.target.value === "CUSTOM") return;
return;
this.setState({ processing: true }); this.setState({ processing: true });
redirectingAuthorizedFetch(BOARD_PROFILE_ENDPOINT, { redirectingAuthorizedFetch(BOARD_PROFILE_ENDPOINT, {
@@ -61,7 +81,7 @@ class EMSESPSettingsForm extends React.Component<EMSESPSettingsFormProps> {
throw Error("Unexpected response code: " + response.status); throw Error("Unexpected response code: " + response.status);
}) })
.then((json) => { .then((json) => {
this.props.enqueueSnackbar("Profile loaded", { variant: 'success' }); this.props.enqueueSnackbar("Profile loaded", { variant: "success" });
setData({ setData({
...data, ...data,
led_gpio: json.led_gpio, led_gpio: json.led_gpio,
@@ -69,7 +89,7 @@ class EMSESPSettingsForm extends React.Component<EMSESPSettingsFormProps> {
rx_gpio: json.rx_gpio, rx_gpio: json.rx_gpio,
tx_gpio: json.tx_gpio, tx_gpio: json.tx_gpio,
pbutton_gpio: json.pbutton_gpio, pbutton_gpio: json.pbutton_gpio,
board_profile: event.target.value board_profile: event.target.value,
}); });
this.setState({ processing: false }); this.setState({ processing: false });
}) })
@@ -88,7 +108,15 @@ class EMSESPSettingsForm extends React.Component<EMSESPSettingsFormProps> {
<ValidatorForm onSubmit={saveData}> <ValidatorForm onSubmit={saveData}>
<Box bgcolor="info.main" p={2} mt={2} mb={2}> <Box bgcolor="info.main" p={2} mt={2} mb={2}>
<Typography variant="body1"> <Typography variant="body1">
Modify 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>. 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> </Typography>
</Box> </Box>
@@ -97,15 +125,23 @@ class EMSESPSettingsForm extends React.Component<EMSESPSettingsFormProps> {
EMS Bus EMS Bus
</Typography> </Typography>
<Grid container spacing={1} direction="row" justify="flex-start" alignItems="flex-start"> <Grid
container
spacing={1}
direction="row"
justify="flex-start"
alignItems="flex-start"
>
<Grid item xs={5}> <Grid item xs={5}>
<SelectValidator name="tx_mode" <SelectValidator
name="tx_mode"
label="Tx Mode" label="Tx Mode"
value={data.tx_mode} value={data.tx_mode}
fullWidth fullWidth
variant="outlined" variant="outlined"
onChange={handleValueChange('tx_mode')} onChange={handleValueChange("tx_mode")}
margin="normal"> margin="normal"
>
<MenuItem value={0}>Off</MenuItem> <MenuItem value={0}>Off</MenuItem>
<MenuItem value={1}>EMS</MenuItem> <MenuItem value={1}>EMS</MenuItem>
<MenuItem value={2}>EMS+</MenuItem> <MenuItem value={2}>EMS+</MenuItem>
@@ -114,31 +150,43 @@ class EMSESPSettingsForm extends React.Component<EMSESPSettingsFormProps> {
</SelectValidator> </SelectValidator>
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>
<SelectValidator name="ems_bus_id" <SelectValidator
name="ems_bus_id"
label="Bus ID" label="Bus ID"
value={data.ems_bus_id} value={data.ems_bus_id}
fullWidth fullWidth
variant="outlined" variant="outlined"
onChange={handleValueChange('ems_bus_id')} onChange={handleValueChange("ems_bus_id")}
margin="normal"> margin="normal"
<MenuItem value={0x0B}>Service Key (0x0B)</MenuItem> >
<MenuItem value={0x0D}>Modem (0x0D)</MenuItem> <MenuItem value={0x0b}>Service Key (0x0B)</MenuItem>
<MenuItem value={0x0A}>Terminal (0x0A)</MenuItem> <MenuItem value={0x0d}>Modem (0x0D)</MenuItem>
<MenuItem value={0x0F}>Time Module (0x0F)</MenuItem> <MenuItem value={0x0a}>Terminal (0x0A)</MenuItem>
<MenuItem value={0x0f}>Time Module (0x0F)</MenuItem>
<MenuItem value={0x12}>Alarm Module (0x12)</MenuItem> <MenuItem value={0x12}>Alarm Module (0x12)</MenuItem>
</SelectValidator> </SelectValidator>
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>
<TextValidator <TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:120']} validators={[
errorMessages={['Tx delay is required', "Must be a number", "Must be 0 or higher", "Max value is 120"]} "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" name="tx_delay"
label="Tx start delay (seconds)" label="Tx start delay (seconds)"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.tx_delay} value={data.tx_delay}
type="number" type="number"
onChange={handleValueChange('tx_delay')} onChange={handleValueChange("tx_delay")}
margin="normal" margin="normal"
/> />
</Grid> </Grid>
@@ -151,154 +199,205 @@ class EMSESPSettingsForm extends React.Component<EMSESPSettingsFormProps> {
<Box color="warning.main" p={0} mt={0} mb={0}> <Box color="warning.main" p={0} mt={0} mb={0}>
<Typography variant="body2"> <Typography variant="body2">
<i>Select a pre-configured board layout to automatically set the GPIO pins, or set your own custom configuration</i> <i>
Select a pre-configured board layout to automatically set the GPIO
pins, or set your own custom configuration
</i>
</Typography> </Typography>
</Box> </Box>
<SelectValidator name="board_profile" <SelectValidator
name="board_profile"
label="Board Profile" label="Board Profile"
value={data.board_profile} value={data.board_profile}
fullWidth fullWidth
variant="outlined" variant="outlined"
onChange={this.changeBoardProfile} onChange={this.changeBoardProfile}
margin="normal"> margin="normal"
>
{boardProfileSelectItems()} {boardProfileSelectItems()}
<MenuItem key={"CUSTOM"} value={"CUSTOM"}>Custom...</MenuItem> <MenuItem key={"CUSTOM"} value={"CUSTOM"}>
Custom...
</MenuItem>
</SelectValidator> </SelectValidator>
{ (data.board_profile === "CUSTOM") && {data.board_profile === "CUSTOM" && (
<Grid container spacing={1} direction="row" justify="flex-start" alignItems="flex-start"> <Grid
container
spacing={1}
direction="row"
justify="flex-start"
alignItems="flex-start"
>
<Grid item xs={4}> <Grid item xs={4}>
<TextValidator <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]*)$']} validators={[
errorMessages={['GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 40", "Not a valid GPIO"]} "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" name="rx_gpio"
label="Rx GPIO" label="Rx GPIO"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.rx_gpio} value={data.rx_gpio}
type="number" type="number"
onChange={handleValueChange('rx_gpio')} onChange={handleValueChange("rx_gpio")}
margin="normal" margin="normal"
/> />
</Grid> </Grid>
<Grid item xs={4}> <Grid item xs={4}>
<TextValidator <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]*)$']} validators={[
errorMessages={['GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 40", "Not a valid GPIO"]} "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" name="tx_gpio"
label="Tx GPIO" label="Tx GPIO"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.tx_gpio} value={data.tx_gpio}
type="number" type="number"
onChange={handleValueChange('tx_gpio')} onChange={handleValueChange("tx_gpio")}
margin="normal" margin="normal"
/> />
</Grid> </Grid>
<Grid item xs={4}> <Grid item xs={4}>
<TextValidator <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]*)$']} validators={[
errorMessages={['GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 40", "Not a valid GPIO"]} "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" name="pbutton_gpio"
label="Button GPIO" label="Button GPIO"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.pbutton_gpio} value={data.pbutton_gpio}
type="number" type="number"
onChange={handleValueChange('pbutton_gpio')} onChange={handleValueChange("pbutton_gpio")}
margin="normal" margin="normal"
/> />
</Grid> </Grid>
<Grid item xs={4}> <Grid item xs={4}>
<TextValidator <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]*)$']} validators={[
errorMessages={['GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 40", "Not a valid GPIO"]} "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" name="dallas_gpio"
label="Dallas GPIO (0=none)" label="Dallas GPIO (0=none)"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.dallas_gpio} value={data.dallas_gpio}
type="number" type="number"
onChange={handleValueChange('dallas_gpio')} onChange={handleValueChange("dallas_gpio")}
margin="normal" margin="normal"
/> />
</Grid> </Grid>
<Grid item xs={4}> <Grid item xs={4}>
<TextValidator <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]*)$']} validators={[
errorMessages={['GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 40", "Not a valid GPIO"]} "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" name="led_gpio"
label="LED GPIO (0=none)" label="LED GPIO (0=none)"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.led_gpio} value={data.led_gpio}
type="number" type="number"
onChange={handleValueChange('led_gpio')} onChange={handleValueChange("led_gpio")}
margin="normal" margin="normal"
/> />
</Grid> </Grid>
</Grid> </Grid>
} )}
<br></br> <br></br>
<Typography variant="h6" color="primary"> <Typography variant="h6" color="primary">
Options Options
</Typography> </Typography>
{ data.dallas_gpio !== 0 && {data.led_gpio !== 0 && (
<BlockFormControlLabel
control={
<Checkbox
checked={data.dallas_parasite}
onChange={handleValueChange('dallas_parasite')}
value="dallas_parasite"
/>
}
label="Enable Dallas parasite mode"
/>
}
{ data.led_gpio !== 0 &&
<BlockFormControlLabel <BlockFormControlLabel
control={ control={
<Checkbox <Checkbox
checked={data.hide_led} checked={data.hide_led}
onChange={handleValueChange('hide_led')} onChange={handleValueChange("hide_led")}
value="hide_led" value="hide_led"
/> />
} }
label="Hide LED" label="Hide LED"
/> />
} )}
<Grid container spacing={0} direction="row" justify="flex-start" alignItems="flex-start">
{data.dallas_gpio !== 0 && (
<BlockFormControlLabel <BlockFormControlLabel
control={ control={
<Checkbox <Checkbox
checked={data.shower_timer} checked={data.dallas_parasite}
onChange={handleValueChange('shower_timer')} onChange={handleValueChange("dallas_parasite")}
value="shower_timer" value="dallas_parasite"
/> />
} }
label="Shower Timer" label="Enable Dallas parasite mode"
/> />
{/* <BlockFormControlLabel )}
control={
<Checkbox
checked={data.shower_alert}
onChange={handleValueChange('shower_alert')}
value="shower_alert"
/>
}
label="Shower Alert"
/> */}
</Grid>
<BlockFormControlLabel <BlockFormControlLabel
control={ control={
<Checkbox <Checkbox
checked={data.api_enabled} checked={data.api_enabled}
onChange={handleValueChange('api_enabled')} onChange={handleValueChange("api_enabled")}
value="api_enabled" value="api_enabled"
/> />
} }
@@ -308,12 +407,41 @@ class EMSESPSettingsForm extends React.Component<EMSESPSettingsFormProps> {
control={ control={
<Checkbox <Checkbox
checked={data.analog_enabled} checked={data.analog_enabled}
onChange={handleValueChange('analog_enabled')} onChange={handleValueChange("analog_enabled")}
value="analog_enabled" value="analog_enabled"
/> />
} }
label="Enable ADC" 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> <br></br>
<Typography variant="h6" color="primary"> <Typography variant="h6" color="primary">
Syslog Syslog
@@ -323,50 +451,68 @@ class EMSESPSettingsForm extends React.Component<EMSESPSettingsFormProps> {
control={ control={
<Checkbox <Checkbox
checked={data.syslog_enabled} checked={data.syslog_enabled}
onChange={handleValueChange('syslog_enabled')} onChange={handleValueChange("syslog_enabled")}
value="syslog_enabled" value="syslog_enabled"
/> />
} }
label="Enable Syslog" label="Enable Syslog"
/> />
{ data.syslog_enabled && {data.syslog_enabled && (
<Grid container spacing={1} direction="row" justify="flex-start" alignItems="flex-start"> <Grid
container
spacing={1}
direction="row"
justify="flex-start"
alignItems="flex-start"
>
<Grid item xs={5}> <Grid item xs={5}>
<TextValidator <TextValidator
validators={['isOptionalIP']} validators={["isOptionalIP"]}
errorMessages={["Not a valid IP address"]} errorMessages={["Not a valid IP address"]}
name="syslog_host" name="syslog_host"
label="IP" label="IP"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.syslog_host} value={data.syslog_host}
onChange={handleValueChange('syslog_host')} onChange={handleValueChange("syslog_host")}
margin="normal" margin="normal"
/> />
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>
<TextValidator <TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']} validators={[
errorMessages={['Port is required', "Must be a number", "Must be greater than 0 ", "Max value is 65535"]} "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" name="syslog_port"
label="Port" label="Port"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.syslog_port} value={data.syslog_port}
type="number" type="number"
onChange={handleValueChange('syslog_port')} onChange={handleValueChange("syslog_port")}
margin="normal" margin="normal"
/> />
</Grid> </Grid>
<Grid item xs={5}> <Grid item xs={5}>
<SelectValidator name="syslog_level" <SelectValidator
name="syslog_level"
label="Log Level" label="Log Level"
value={data.syslog_level} value={data.syslog_level}
fullWidth fullWidth
variant="outlined" variant="outlined"
onChange={handleValueChange('syslog_level')} onChange={handleValueChange("syslog_level")}
margin="normal"> margin="normal"
>
<MenuItem value={-1}>OFF</MenuItem> <MenuItem value={-1}>OFF</MenuItem>
<MenuItem value={3}>ERR</MenuItem> <MenuItem value={3}>ERR</MenuItem>
<MenuItem value={5}>NOTICE</MenuItem> <MenuItem value={5}>NOTICE</MenuItem>
@@ -377,15 +523,25 @@ class EMSESPSettingsForm extends React.Component<EMSESPSettingsFormProps> {
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>
<TextValidator <TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']} validators={[
errorMessages={['Syslog Mark is required', "Must be a number", "Must be 0 or higher", "Max value is 10"]} "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" name="syslog_mark_interval"
label="Mark Interval seconds (0=off)" label="Mark Interval seconds (0=off)"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.syslog_mark_interval} value={data.syslog_mark_interval}
type="number" type="number"
onChange={handleValueChange('syslog_mark_interval')} onChange={handleValueChange("syslog_mark_interval")}
margin="normal" margin="normal"
/> />
</Grid> </Grid>
@@ -393,25 +549,29 @@ class EMSESPSettingsForm extends React.Component<EMSESPSettingsFormProps> {
control={ control={
<Checkbox <Checkbox
checked={data.trace_raw} checked={data.trace_raw}
onChange={handleValueChange('trace_raw')} onChange={handleValueChange("trace_raw")}
value="trace_raw" value="trace_raw"
/> />
} }
label="Output EMS telegrams in raw format" label="Output EMS telegrams in raw format"
/> />
</Grid> </Grid>
} )}
<br></br> <br></br>
<FormActions> <FormActions>
<FormButton startIcon={<SaveIcon />} variant="contained" color="primary" type="submit"> <FormButton
startIcon={<SaveIcon />}
variant="contained"
color="primary"
type="submit"
>
Save Save
</FormButton> </FormButton>
</FormActions> </FormActions>
</ValidatorForm> </ValidatorForm>
); );
} }
} }
export default withAuthenticatedContext(withWidth()(EMSESPSettingsForm)); export default withAuthenticatedContext(withWidth()(EMSESPSettingsForm));

View File

@@ -62,3 +62,11 @@ export interface EMSESPDeviceData {
name: string; name: string;
data: string[]; data: string[];
} }
export interface DeviceValue {
id: number;
data: string,
uom: string,
name: string,
cmd: string
}

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

View 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&hellip;
</Typography>
</Box>
}
</DialogContent>
<DialogActions>
<FormButton variant="contained" color="primary" type="submit" onClick={onClose}>
Close
</FormButton>
</DialogActions>
</Dialog>
);
}
}
export default withSnackbar(GenerateToken);

View File

@@ -11,12 +11,14 @@ import CheckIcon from '@material-ui/icons/Check';
import IconButton from '@material-ui/core/IconButton'; import IconButton from '@material-ui/core/IconButton';
import SaveIcon from '@material-ui/icons/Save'; import SaveIcon from '@material-ui/icons/Save';
import PersonAddIcon from '@material-ui/icons/PersonAdd'; import PersonAddIcon from '@material-ui/icons/PersonAdd';
import VpnKeyIcon from '@material-ui/icons/VpnKey';
import { withAuthenticatedContext, AuthenticatedContextProps } from '../authentication'; import { withAuthenticatedContext, AuthenticatedContextProps } from '../authentication';
import { RestFormProps, FormActions, FormButton, extractEventValue } from '../components'; import { RestFormProps, FormActions, FormButton, extractEventValue } from '../components';
import UserForm from './UserForm'; import UserForm from './UserForm';
import { SecuritySettings, User } from './types'; import { SecuritySettings, User } from './types';
import GenerateToken from './GenerateToken';
function compareUsers(a: User, b: User) { function compareUsers(a: User, b: User) {
if (a.username < b.username) { if (a.username < b.username) {
@@ -33,6 +35,7 @@ type ManageUsersFormProps = RestFormProps<SecuritySettings> & AuthenticatedConte
type ManageUsersFormState = { type ManageUsersFormState = {
creating: boolean; creating: boolean;
user?: User; user?: User;
generateTokenFor?: string;
} }
class ManageUsersForm extends React.Component<ManageUsersFormProps, ManageUsersFormState> { class ManageUsersForm extends React.Component<ManageUsersFormProps, ManageUsersFormState> {
@@ -66,6 +69,18 @@ class ManageUsersForm extends React.Component<ManageUsersFormProps, ManageUsersF
this.props.setData({ ...data, users }); this.props.setData({ ...data, users });
} }
closeGenerateToken = () => {
this.setState({
generateTokenFor: undefined
});
}
generateToken = (user: User) => {
this.setState({
generateTokenFor: user.username
});
}
startEditingUser = (user: User) => { startEditingUser = (user: User) => {
this.setState({ this.setState({
creating: false, creating: false,
@@ -103,7 +118,7 @@ class ManageUsersForm extends React.Component<ManageUsersFormProps, ManageUsersF
render() { render() {
const { width, data } = this.props; const { width, data } = this.props;
const { user, creating } = this.state; const { user, creating, generateTokenFor } = this.state;
return ( return (
<Fragment> <Fragment>
<ValidatorForm onSubmit={this.onSubmit}> <ValidatorForm onSubmit={this.onSubmit}>
@@ -122,11 +137,12 @@ class ManageUsersForm extends React.Component<ManageUsersFormProps, ManageUsersF
{user.username} {user.username}
</TableCell> </TableCell>
<TableCell align="center"> <TableCell align="center">
{ {user.admin ? <CheckIcon /> : <CloseIcon />}
user.admin ? <CheckIcon /> : <CloseIcon />
}
</TableCell> </TableCell>
<TableCell align="center"> <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)}> <IconButton size="small" aria-label="Delete" onClick={() => this.removeUser(user)}>
<DeleteIcon /> <DeleteIcon />
</IconButton> </IconButton>
@@ -164,6 +180,9 @@ class ManageUsersForm extends React.Component<ManageUsersFormProps, ManageUsersF
</FormButton> </FormButton>
</FormActions> </FormActions>
</ValidatorForm> </ValidatorForm>
{
generateTokenFor && <GenerateToken username={generateTokenFor} onClose={this.closeGenerateToken} />
}
{ {
user && user &&
<UserForm <UserForm

View File

@@ -32,7 +32,7 @@ class UserForm extends React.Component<UserFormProps> {
const { user, creating, handleValueChange, onDoneEditing, onCancelEditing } = this.props; const { user, creating, handleValueChange, onDoneEditing, onCancelEditing } = this.props;
return ( return (
<ValidatorForm onSubmit={onDoneEditing} ref={this.formRef}> <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> <DialogTitle id="user-form-dialog-title">{creating ? 'Add' : 'Modify'} User</DialogTitle>
<DialogContent dividers> <DialogContent dividers>
<TextValidator <TextValidator

View File

@@ -9,3 +9,6 @@ export interface SecuritySettings {
jwt_secret: string; jwt_secret: string;
} }
export interface GeneratedToken {
token: string;
}

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

View File

@@ -111,6 +111,8 @@ class SystemStatusForm extends Component<SystemStatusFormProps, SystemStatusForm
<Dialog <Dialog
open={this.state.confirmRestart} open={this.state.confirmRestart}
onClose={this.onRestartRejected} onClose={this.onRestartRejected}
fullWidth
maxWidth="sm"
> >
<DialogTitle>Confirm Restart</DialogTitle> <DialogTitle>Confirm Restart</DialogTitle>
<DialogContent dividers> <DialogContent dividers>
@@ -158,6 +160,8 @@ class SystemStatusForm extends Component<SystemStatusFormProps, SystemStatusForm
<Dialog <Dialog
open={this.state.confirmFactoryReset} open={this.state.confirmFactoryReset}
onClose={this.onFactoryResetRejected} onClose={this.onFactoryResetRejected}
fullWidth
maxWidth="sm"
> >
<DialogTitle>Confirm Factory Reset</DialogTitle> <DialogTitle>Confirm Factory Reset</DialogTitle>
<DialogContent dividers> <DialogContent dividers>

View File

@@ -17,7 +17,7 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"jsx": "react", "jsx": "react-jsx",
"noFallthroughCasesInSwitch": true "noFallthroughCasesInSwitch": true
}, },
"include": [ "include": [

View File

@@ -56,10 +56,16 @@ class ChunkPrint : public Print {
size_t _to_skip; size_t _to_skip;
size_t _to_write; size_t _to_write;
size_t _pos; size_t _pos;
public: public:
ChunkPrint(uint8_t * destination, size_t from, size_t len) ChunkPrint(uint8_t * destination, size_t from, size_t len)
: _destination(destination), _to_skip(from), _to_write(len), _pos{0} {} : _destination(destination)
virtual ~ChunkPrint(){} , _to_skip(from)
, _to_write(len)
, _pos{0} {
}
virtual ~ChunkPrint() {
}
size_t write(uint8_t c) { size_t write(uint8_t c) {
if (_to_skip > 0) { if (_to_skip > 0) {
_to_skip--; _to_skip--;
@@ -71,15 +77,13 @@ class ChunkPrint : public Print {
} }
return 0; return 0;
} }
size_t write(const uint8_t *buffer, size_t size) size_t write(const uint8_t * buffer, size_t size) {
{
return this->Print::write(buffer, size); return this->Print::write(buffer, size);
} }
}; };
class AsyncJsonResponse : public AsyncAbstractResponse { class AsyncJsonResponse : public AsyncAbstractResponse {
protected: protected:
#ifdef ARDUINOJSON_5_COMPATIBILITY #ifdef ARDUINOJSON_5_COMPATIBILITY
DynamicJsonBuffer _jsonBuffer; DynamicJsonBuffer _jsonBuffer;
#else #else
@@ -90,9 +94,9 @@ class AsyncJsonResponse: public AsyncAbstractResponse {
bool _isValid; bool _isValid;
public: public:
#ifdef ARDUINOJSON_5_COMPATIBILITY #ifdef ARDUINOJSON_5_COMPATIBILITY
AsyncJsonResponse(bool isArray=false): _isValid{false} { AsyncJsonResponse(bool isArray = false)
: _isValid{false} {
_code = 200; _code = 200;
_contentType = JSON_MIMETYPE; _contentType = JSON_MIMETYPE;
if (isArray) if (isArray)
@@ -101,7 +105,9 @@ class AsyncJsonResponse: public AsyncAbstractResponse {
_root = _jsonBuffer.createObject(); _root = _jsonBuffer.createObject();
} }
#else #else
AsyncJsonResponse(bool isArray=false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : _jsonBuffer(maxJsonBufferSize), _isValid{false} { AsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE)
: _jsonBuffer(maxJsonBufferSize)
, _isValid{false} {
_code = 200; _code = 200;
_contentType = JSON_MIMETYPE; _contentType = JSON_MIMETYPE;
if (isArray) if (isArray)
@@ -111,22 +117,30 @@ class AsyncJsonResponse: public AsyncAbstractResponse {
} }
#endif #endif
~AsyncJsonResponse() {} ~AsyncJsonResponse() {
JsonVariant & getRoot() { return _root; } }
bool _sourceValid() const { return _isValid; } JsonVariant & getRoot() {
return _root;
}
bool _sourceValid() const {
return _isValid;
}
size_t setLength() { size_t setLength() {
#ifdef ARDUINOJSON_5_COMPATIBILITY #ifdef ARDUINOJSON_5_COMPATIBILITY
_contentLength = _root.measureLength(); _contentLength = _root.measureLength();
#else #else
_contentLength = measureJson(_root); _contentLength = measureJson(_root);
#endif #endif
if (_contentLength) { _isValid = true; } if (_contentLength) {
_isValid = true;
}
return _contentLength; return _contentLength;
} }
size_t getSize() { return _jsonBuffer.size(); } size_t getSize() {
return _jsonBuffer.size();
}
size_t _fillBuffer(uint8_t * data, size_t len) { size_t _fillBuffer(uint8_t * data, size_t len) {
ChunkPrint dest(data, _sentLength, len); ChunkPrint dest(data, _sentLength, len);
@@ -143,9 +157,13 @@ class AsyncJsonResponse: public AsyncAbstractResponse {
class PrettyAsyncJsonResponse : public AsyncJsonResponse { class PrettyAsyncJsonResponse : public AsyncJsonResponse {
public: public:
#ifdef ARDUINOJSON_5_COMPATIBILITY #ifdef ARDUINOJSON_5_COMPATIBILITY
PrettyAsyncJsonResponse (bool isArray=false) : AsyncJsonResponse{isArray} {} PrettyAsyncJsonResponse(bool isArray = false)
: AsyncJsonResponse{isArray} {
}
#else #else
PrettyAsyncJsonResponse (bool isArray=false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : AsyncJsonResponse{isArray, maxJsonBufferSize} {} PrettyAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE)
: AsyncJsonResponse{isArray, maxJsonBufferSize} {
}
#endif #endif
size_t setLength() { size_t setLength() {
#ifdef ARDUINOJSON_5_COMPATIBILITY #ifdef ARDUINOJSON_5_COMPATIBILITY
@@ -153,7 +171,9 @@ public:
#else #else
_contentLength = measureJsonPretty(_root); _contentLength = measureJsonPretty(_root);
#endif #endif
if (_contentLength) {_isValid = true;} if (_contentLength) {
_isValid = true;
}
return _contentLength; return _contentLength;
} }
size_t _fillBuffer(uint8_t * data, size_t len) { size_t _fillBuffer(uint8_t * data, size_t len) {
@@ -162,6 +182,8 @@ public:
_root.prettyPrintTo(dest); _root.prettyPrintTo(dest);
#else #else
serializeJsonPretty(_root, dest); serializeJsonPretty(_root, dest);
// serializeJson(_root, Serial); // for testing
// Serial.println();
#endif #endif
return len; return len;
} }
@@ -180,18 +202,34 @@ protected:
const size_t maxJsonBufferSize; const size_t maxJsonBufferSize;
#endif #endif
size_t _maxContentLength; size_t _maxContentLength;
public: public:
#ifdef ARDUINOJSON_5_COMPATIBILITY #ifdef ARDUINOJSON_5_COMPATIBILITY
AsyncCallbackJsonWebHandler(const String & uri, ArJsonRequestHandlerFunction onRequest) AsyncCallbackJsonWebHandler(const String & uri, ArJsonRequestHandlerFunction onRequest)
: _uri(uri), _method(HTTP_POST|HTTP_PUT|HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {} : _uri(uri)
, _method(HTTP_POST | HTTP_PUT | HTTP_PATCH)
, _onRequest(onRequest)
, _maxContentLength(16384) {
}
#else #else
AsyncCallbackJsonWebHandler(const String & uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) 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) {} : _uri(uri)
, _method(HTTP_POST | HTTP_PUT | HTTP_PATCH)
, _onRequest(onRequest)
, maxJsonBufferSize(maxJsonBufferSize)
, _maxContentLength(16384) {
}
#endif #endif
void setMethod(WebRequestMethodComposite method){ _method = method; } void setMethod(WebRequestMethodComposite method) {
void setMaxContentLength(int maxContentLength){ _maxContentLength = maxContentLength; } _method = method;
void onRequest(ArJsonRequestHandlerFunction fn){ _onRequest = fn; } }
void setMaxContentLength(int maxContentLength) {
_maxContentLength = maxContentLength;
}
void onRequest(ArJsonRequestHandlerFunction fn) {
_onRequest = fn;
}
virtual bool canHandle(AsyncWebServerRequest * request) override final { virtual bool canHandle(AsyncWebServerRequest * request) override final {
if (!_onRequest) if (!_onRequest)
@@ -213,7 +251,6 @@ public:
virtual void handleRequest(AsyncWebServerRequest * request) override final { virtual void handleRequest(AsyncWebServerRequest * request) override final {
if (_onRequest) { if (_onRequest) {
if (request->_tempObject != NULL) { if (request->_tempObject != NULL) {
#ifdef ARDUINOJSON_5_COMPATIBILITY #ifdef ARDUINOJSON_5_COMPATIBILITY
DynamicJsonBuffer jsonBuffer; DynamicJsonBuffer jsonBuffer;
JsonVariant json = jsonBuffer.parse((uint8_t *)(request->_tempObject)); JsonVariant json = jsonBuffer.parse((uint8_t *)(request->_tempObject));
@@ -247,6 +284,8 @@ public:
} }
} }
} }
virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;} virtual bool isRequestHandlerTrivial() override final {
return _onRequest ? false : true;
}
}; };
#endif #endif

View File

@@ -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() { void ESP8266React::begin() {

View File

@@ -57,18 +57,6 @@ static String generateClientId() {
#define FACTORY_MQTT_MAX_TOPIC_LENGTH 128 #define FACTORY_MQTT_MAX_TOPIC_LENGTH 128
#endif #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
#define EMSESP_DEFAULT_SUBSCRIBE_FORMAT 0
#define EMSESP_DEFAULT_BOARD_PROFILE "S32"
class MqttSettings { class MqttSettings {
public: public:
// host and port - if enabled // host and port - if enabled
@@ -102,7 +90,7 @@ class MqttSettings {
uint8_t bool_format; uint8_t bool_format;
uint8_t ha_climate_format; uint8_t ha_climate_format;
bool ha_enabled; bool ha_enabled;
bool nested_format; uint8_t nested_format;
uint8_t subscribe_format; uint8_t subscribe_format;
static void read(MqttSettings & settings, JsonObject & root); static void read(MqttSettings & settings, JsonObject & root);

View File

@@ -9,6 +9,7 @@ SecuritySettingsService::SecuritySettingsService(AsyncWebServer * server, FS * f
, _fsPersistence(SecuritySettings::read, SecuritySettings::update, this, fs, SECURITY_SETTINGS_FILE) , _fsPersistence(SecuritySettings::read, SecuritySettings::update, this, fs, SECURITY_SETTINGS_FILE)
, _jwtHandler(FACTORY_JWT_SECRET) { , _jwtHandler(FACTORY_JWT_SECRET) {
addUpdateHandler([&](const String & originId) { configureJWTHandler(); }, false); addUpdateHandler([&](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() { 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 #else
User ADMIN_USER = User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true); User ADMIN_USER = User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true);

View File

@@ -25,6 +25,9 @@
#define SECURITY_SETTINGS_FILE "/config/securitySettings.json" #define SECURITY_SETTINGS_FILE "/config/securitySettings.json"
#define SECURITY_SETTINGS_PATH "/rest/securitySettings" #define SECURITY_SETTINGS_PATH "/rest/securitySettings"
#define GENERATE_TOKEN_SIZE 512
#define GENERATE_TOKEN_PATH "/rest/generateToken"
#if FT_ENABLED(FT_SECURITY) #if FT_ENABLED(FT_SECURITY)
class SecuritySettings { class SecuritySettings {
@@ -83,6 +86,8 @@ class SecuritySettingsService : public StatefulService<SecuritySettings>, public
FSPersistence<SecuritySettings> _fsPersistence; FSPersistence<SecuritySettings> _fsPersistence;
ArduinoJsonJWT _jwtHandler; ArduinoJsonJWT _jwtHandler;
void generateToken(AsyncWebServerRequest * request);
void configureJWTHandler(); void configureJWTHandler();
/* /*

View File

@@ -22,44 +22,24 @@
namespace uuid { namespace uuid {
#define UPTIME_OVERFLOW 4294967295 // Uptime overflow value // added by proddy, modified
static uint64_t now_millis = 0;
// returns system uptime in seconds // returns system uptime in seconds
uint32_t get_uptime_sec() { uint32_t get_uptime_sec() {
static uint32_t last_uptime = 0; return (uint32_t)(now_millis / 1000ULL);
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;
} }
uint64_t get_uptime_ms() { 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; return now_millis;
} }
void set_uptime() {
now_millis = esp_timer_get_time() / 1000ULL;
}
uint32_t get_uptime() {
return (uint32_t)now_millis;
}
} // namespace uuid } // namespace uuid

View File

@@ -22,7 +22,6 @@ namespace uuid {
void loop() { void loop() {
set_uptime(); // added by proddy set_uptime(); // added by proddy
get_uptime_ms();
} }
} // namespace uuid } // namespace uuid

View File

@@ -50,10 +50,7 @@ void Commands::add_command(const flash_string_vector & name, const flash_string_
add_command(0, 0, name, arguments, function, nullptr); add_command(0, 0, name, arguments, function, nullptr);
} }
void Commands::add_command(const flash_string_vector & name, void Commands::add_command(const flash_string_vector & name, const flash_string_vector & arguments, command_function function, argument_completion_function arg_function) {
const flash_string_vector & arguments,
command_function function,
argument_completion_function arg_function) {
add_command(0, 0, name, arguments, 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); add_command(context, flags, name, flash_string_vector{}, function, nullptr);
} }
void Commands::add_command(unsigned int context, void Commands::add_command(unsigned int context, unsigned int flags, const flash_string_vector & name, const flash_string_vector & arguments, command_function function) {
unsigned int flags,
const flash_string_vector & name,
const flash_string_vector & arguments,
command_function function) {
add_command(context, flags, name, arguments, function, nullptr); add_command(context, flags, name, arguments, function, nullptr);
} }
void Commands::add_command(unsigned int context, 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) {
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)); 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++) { for (auto command_it = std::next(commands.begin()); command_it != commands.end(); command_it++) {
// This relies on the null terminator character limiting the // This relies on the null terminator character limiting the
// length before it becomes longer than any of the strings // length before it becomes longer than any of the strings
if (pgm_read_byte(reinterpret_cast<PGM_P>(first) + 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)) {
!= pgm_read_byte(reinterpret_cast<PGM_P>(*std::next(command_it->second->name_.begin(), component_prefix)) + length)) {
all_match = false; all_match = false;
break; break;
} }
@@ -275,8 +262,7 @@ Commands::Completion Commands::complete_command(Shell & shell, const CommandLine
result.replacement->push_back(std::move(read_flash_string(name))); result.replacement->push_back(std::move(read_flash_string(name)));
} }
if (command_line.total_size() > result.replacement->size() if (command_line.total_size() > result.replacement->size() && command_line.total_size() <= matching_command->name_.size() + matching_command->maximum_arguments()) {
&& command_line.total_size() <= matching_command->name_.size() + matching_command->maximum_arguments()) {
// Try to auto-complete arguments // Try to auto-complete arguments
std::vector<std::string> arguments{std::next(command_line->cbegin(), result.replacement->size()), command_line->cend()}; 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, Commands::Command::Command(unsigned int flags, const flash_string_vector name, const flash_string_vector arguments, command_function function, argument_completion_function arg_function)
const flash_string_vector name,
const flash_string_vector arguments,
command_function function,
argument_completion_function arg_function)
: flags_(flags) : flags_(flags)
, name_(name) , name_(name)
, arguments_(arguments) , arguments_(arguments)

View File

@@ -65,8 +65,10 @@ void Shell::start() {
#endif #endif
line_buffer_.reserve(maximum_command_line_length_); line_buffer_.reserve(maximum_command_line_length_);
line_old_.reserve(maximum_command_line_length_); for (uint8_t i = 0; i < MAX_LINES; i++) {
line_old_.clear(); line_old_[i].reserve(maximum_command_line_length_);
line_old_[i].clear();
}
display_banner(); display_banner();
display_prompt(); display_prompt();
shells_.insert(shared_from_this()); shells_.insert(shared_from_this());
@@ -157,6 +159,7 @@ void Shell::loop_normal() {
line_buffer_.clear(); line_buffer_.clear();
println(); println();
cursor_ = 0; cursor_ = 0;
line_no_ = 0;
break; break;
case '\x04': case '\x04':
@@ -172,6 +175,7 @@ void Shell::loop_normal() {
// Del/Backspace (^?) // Del/Backspace (^?)
if (line_buffer_.length() > cursor_) { if (line_buffer_.length() > cursor_) {
line_buffer_.erase(line_buffer_.length() - cursor_ - 1, 1); line_buffer_.erase(line_buffer_.length() - cursor_ - 1, 1);
line_no_ = 0;
} }
break; break;
@@ -179,6 +183,7 @@ void Shell::loop_normal() {
// Tab (^I) // Tab (^I)
process_completion(); process_completion();
cursor_ = 0; cursor_ = 0;
line_no_ = 0;
break; break;
case '\x0A': case '\x0A':
@@ -199,11 +204,13 @@ void Shell::loop_normal() {
// Delete line (^U) // Delete line (^U)
line_buffer_.clear(); line_buffer_.clear();
cursor_ = 0; cursor_ = 0;
line_no_ = 0;
break; break;
case '\x17': case '\x17':
// Delete word (^W) // Delete word (^W)
delete_buffer_word(true); delete_buffer_word(true);
line_no_ = 0;
break; break;
case '\033': case '\033':
@@ -214,10 +221,20 @@ void Shell::loop_normal() {
default: default:
if (esc_) { if (esc_) {
if (c == 'A') { // cursor up if (c == 'A') { // cursor up
line_buffer_ = line_old_; line_buffer_ = line_old_[line_no_];
if (line_no_ < MAX_LINES - 1) {
line_no_++;
}
cursor_ = 0; cursor_ = 0;
} else if (c == 'B') { // cursor down } else if (c == 'B') { // cursor down
if (line_no_) {
line_no_--;
}
if (line_no_) {
line_buffer_ = line_old_[line_no_ - 1];
} else {
line_buffer_.clear(); line_buffer_.clear();
}
cursor_ = 0; cursor_ = 0;
} else if (c == 'C') { // cursor right } else if (c == 'C') { // cursor right
if (cursor_) { if (cursor_) {
@@ -239,6 +256,7 @@ void Shell::loop_normal() {
if ((esc_ == 3) && cursor_) { // del if ((esc_ == 3) && cursor_) { // del
cursor_--; cursor_--;
line_buffer_.erase(line_buffer_.length() - cursor_ - 1, 1); line_buffer_.erase(line_buffer_.length() - cursor_ - 1, 1);
line_no_ = 0;
} else if (esc_ == 4) { // end } else if (esc_ == 4) { // end
cursor_ = 0; cursor_ = 0;
} else if (esc_ == 1) { // pos1 } else if (esc_ == 1) { // pos1
@@ -279,6 +297,7 @@ void Shell::loop_normal() {
} else if (c >= '\x20' && c <= '\x7E') { } else if (c >= '\x20' && c <= '\x7E') {
if (line_buffer_.length() < maximum_command_line_length_) { if (line_buffer_.length() < maximum_command_line_length_) {
line_buffer_.insert(line_buffer_.length() - cursor_, 1, c); line_buffer_.insert(line_buffer_.length() - cursor_, 1, c);
line_no_ = 0;
} }
} }
break; break;
@@ -498,7 +517,12 @@ void Shell::process_command() {
println(); println();
return; 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()) { while (!line_buffer_.empty()) {
size_t pos = line_buffer_.find(';'); size_t pos = line_buffer_.find(';');
std::string line1; std::string line1;

View File

@@ -96,7 +96,6 @@ void Shell::output_logs() {
} }
::yield(); ::yield();
} }
display_prompt(); display_prompt();
} }

View File

@@ -63,6 +63,7 @@ class Shell : public std::enable_shared_from_this<Shell>, public uuid::log::Hand
public: 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_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_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. * Function to handle the response to a password entry prompt.
@@ -904,7 +905,8 @@ class Shell : public std::enable_shared_from_this<Shell>, public uuid::log::Hand
std::list<QueuedLogMessage> log_messages_; /*!< Queued log messages, in the order they were received. @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 */ 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_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_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 */ 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 */ 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 */ uint8_t cursor_ = 0; /*!< cursor position from end of line */

View File

@@ -199,7 +199,6 @@ SyslogService::QueuedLogMessage::QueuedLogMessage(unsigned long id, std::shared_
time_.tv_sec = time(nullptr); time_.tv_sec = time(nullptr);
time_.tv_usec = 0; time_.tv_usec = 0;
#endif #endif
if (time_.tv_sec >= 0 && time_.tv_sec < 18140 * 86400) { if (time_.tv_sec >= 0 && time_.tv_sec < 18140 * 86400) {
time_.tv_sec = (time_t)-1; time_.tv_sec = (time_t)-1;
} }
@@ -386,15 +385,20 @@ bool SyslogService::can_transmit() {
} }
bool SyslogService::transmit(const QueuedLogMessage & message) { bool SyslogService::transmit(const QueuedLogMessage & message) {
/*
// modifications by Proddy. From https://github.com/emsesp/EMS-ESP/issues/395#issuecomment-640053528
struct tm tm; struct tm tm;
int8_t tzh = 0;
int8_t tzm = 0;
tm.tm_year = 0; tm.tm_year = 0;
if (message.time_.tv_sec != (time_t)-1) { 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) { if (udp_.beginPacket(host_, port_) != 1) {
last_transmit_ = uuid::get_uptime_ms(); last_transmit_ = uuid::get_uptime_ms();
@@ -402,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)); 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) { if (tm.tm_year != 0) {
udp_.printf_P(PSTR("%04u-%02u-%02uT%02u:%02u:%02u.%06luZ"), 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);
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);
} else { } else {
udp_.print('-'); udp_.print('-');
} }
*/
udp_.print('-'); udp_.printf_P(PSTR(" %s %s - - - \xEF\xBB\xBF"), hostname_.c_str(), uuid::read_flash_string(message.content_->name).c_str());
udp_.printf_P(PSTR(" %s - - - - \xEF\xBB\xBF"), hostname_.c_str());
udp_.print(uuid::log::format_timestamp_ms(message.content_->uptime_ms, 3).c_str()); udp_.print(uuid::log::format_timestamp_ms(message.content_->uptime_ms, 3).c_str());
udp_.printf_P(PSTR(" %c %lu: "), uuid::log::format_level_char(message.content_->level), message.id_);
#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_.print(message.content_->text.c_str()); udp_.print(message.content_->text.c_str());
bool ok = (udp_.endPacket() == 1); bool ok = (udp_.endPacket() == 1);

View File

@@ -61,6 +61,10 @@ unsigned long millis() {
return __millis; return __millis;
} }
int64_t esp_timer_get_time() {
return __millis;
}
void delay(unsigned long millis) { void delay(unsigned long millis) {
// __millis += millis; // __millis += millis;
} }

View File

@@ -53,11 +53,7 @@ int digitalRead(uint8_t pin);
#define PROGMEM #define PROGMEM
#define PGM_P const char * #define PGM_P const char *
#define PSTR(s) \ #define PSTR(s) s
(__extension__({ \
static const char __c[] = (s); \
&__c[0]; \
}))
class __FlashStringHelper; class __FlashStringHelper;
#define FPSTR(string_literal) (reinterpret_cast<const __FlashStringHelper *>(string_literal)) #define FPSTR(string_literal) (reinterpret_cast<const __FlashStringHelper *>(string_literal))
@@ -201,6 +197,8 @@ extern WiFiClass WiFi;
unsigned long millis(); unsigned long millis();
int64_t esp_timer_get_time();
void delay(unsigned long millis); void delay(unsigned long millis);
void yield(void); void yield(void);

View File

@@ -33,7 +33,7 @@ class DummySettings {
bool mqtt_retain = false; bool mqtt_retain = false;
bool enabled = true; bool enabled = true;
uint8_t dallas_format = 1; uint8_t dallas_format = 1;
bool nested_format = true; uint8_t nested_format = 1;
uint8_t ha_climate_format = 1; uint8_t ha_climate_format = 1;
bool ha_enabled = true; bool ha_enabled = true;
String base = "ems-esp"; String base = "ems-esp";

View File

@@ -33,7 +33,7 @@ CXX_STANDARD := -std=c++11
#---------------------------------------------------------------------- #----------------------------------------------------------------------
# Defined Symbols # Defined Symbols
#---------------------------------------------------------------------- #----------------------------------------------------------------------
DEFINES += -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_TEST -DEMSESP_DEFAULT_BOARD_PROFILE=\"LOLIN\" DEFINES += -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_DEFAULT_BOARD_PROFILE=\"LOLIN\"
#---------------------------------------------------------------------- #----------------------------------------------------------------------
# Sources & Files # Sources & Files

29
mock-api/README.md Normal file
View 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

File diff suppressed because it is too large Load Diff

17
mock-api/package.json Normal file
View 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
View 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}`);

View File

@@ -8,15 +8,15 @@ upload_flags =
upload_port = 10.10.10.101 upload_port = 10.10.10.101
[common] [common]
; options are EMSESP_DEBUG EMSESP_UART_DEBUG EMSESP_TEST ENABLE_CORS DEMSESP_DEFAULT_BOARD_PROFILE ; options are EMSESP_DEBUG EMSESP_UART_DEBUG EMSESP_DEBUG_SENSOR
; debug_flags = -DENABLE_CORS -DEMSESP_DEBUG -DEMSESP_TEST -DCORS_ORIGIN=\"http://localhost:3000\" ; plus all the settings in default_settings.h, e.g. -DEMSESP_DEFAULT_BOARD_PROFILE=\"NODEMCU\"
; debug_flags = -DEMSESP_DEFAULT_BOARD_PROFILE=\"NODEMCU\" ; debug_flags = -DEMSESP_DEBUG
[env:esp32] [env:esp32]
monitor_filters = esp32_exception_decoder monitor_filters = esp32_exception_decoder
debug_tool = esp-prog debug_tool = esp-prog
debug_init_break = tbreak setup debug_init_break = tbreak setup
extra_scripts = extra_scripts =
; to prevent the web UI from building each time, comment out this next line
; pre:scripts/build_interface.py ; pre:scripts/build_interface.py
; scripts/upload_fw.py

View File

@@ -28,10 +28,6 @@ WebAPIService::WebAPIService(AsyncWebServer * server) {
// e.g. http://ems-esp/api?device=boiler&cmd=wwtemp&data=20&id=1 // e.g. http://ems-esp/api?device=boiler&cmd=wwtemp&data=20&id=1
void WebAPIService::webAPIService(AsyncWebServerRequest * request) { 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 // must have device and cmd parameters
if ((!request->hasParam(F_(device))) || (!request->hasParam(F_(cmd)))) { if ((!request->hasParam(F_(device))) || (!request->hasParam(F_(cmd)))) {
request->send(400, "text/plain", F("Invalid syntax")); request->send(400, "text/plain", F("Invalid syntax"));
@@ -49,12 +45,6 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request) {
// get cmd, we know we have one // get cmd, we know we have one
String cmd = request->getParam(F_(cmd))->value(); 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; String data;
if (request->hasParam(F_(data))) { if (request->hasParam(F_(data))) {
data = request->getParam(F_(data))->value(); data = request->getParam(F_(data))->value();
@@ -77,8 +67,10 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request) {
if (data.isEmpty()) { if (data.isEmpty()) {
ok = Command::call(device_type, cmd.c_str(), nullptr, id.toInt(), json); // command only ok = Command::call(device_type, cmd.c_str(), nullptr, id.toInt(), json); // command only
} else { } else {
if (api_enabled) {
// we only allow commands with parameters if the API is enabled // 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) {
ok = Command::call(device_type, cmd.c_str(), data.c_str(), id.toInt(), json); // has cmd, data and id ok = Command::call(device_type, cmd.c_str(), data.c_str(), id.toInt(), json); // has cmd, data and id
} else { } else {
request->send(401, "text/plain", F("Unauthorized")); request->send(401, "text/plain", F("Unauthorized"));
@@ -90,7 +82,7 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request) {
doc.shrinkToFit(); doc.shrinkToFit();
std::string buffer; std::string buffer;
serializeJsonPretty(doc, buffer); serializeJsonPretty(doc, buffer);
request->send(200, "text/plain", buffer.c_str()); request->send(200, "text/plain;charset=utf-8", buffer.c_str());
return; return;
} }
request->send(200, "text/plain", ok ? F("OK") : F("Invalid")); request->send(200, "text/plain", ok ? F("OK") : F("Invalid"));

View File

@@ -23,14 +23,18 @@ namespace emsesp {
using namespace std::placeholders; // for `_1` etc using namespace std::placeholders; // for `_1` etc
WebDevicesService::WebDevicesService(AsyncWebServer * server, SecurityManager * securityManager) 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)) { : _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(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)); 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.setMethod(HTTP_POST);
_device_dataHandler.setMaxContentLength(256); _device_dataHandler.setMaxContentLength(256);
server->addHandler(&_device_dataHandler); server->addHandler(&_device_dataHandler);
_writevalue_dataHandler.setMethod(HTTP_POST);
_writevalue_dataHandler.setMaxContentLength(256);
server->addHandler(&_writevalue_dataHandler);
} }
void WebDevicesService::scan_devices(AsyncWebServerRequest * request) { void WebDevicesService::scan_devices(AsyncWebServerRequest * request) {
@@ -96,4 +100,49 @@ void WebDevicesService::device_data(AsyncWebServerRequest * request, JsonVariant
request->send(response); 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 } // namespace emsesp

View File

@@ -27,6 +27,8 @@
#define EMSESP_DEVICES_SERVICE_PATH "/rest/allDevices" #define EMSESP_DEVICES_SERVICE_PATH "/rest/allDevices"
#define SCAN_DEVICES_SERVICE_PATH "/rest/scanDevices" #define SCAN_DEVICES_SERVICE_PATH "/rest/scanDevices"
#define DEVICE_DATA_SERVICE_PATH "/rest/deviceData" #define DEVICE_DATA_SERVICE_PATH "/rest/deviceData"
#define WRITE_VALUE_SERVICE_PATH "/rest/writeValue"
namespace emsesp { namespace emsesp {
@@ -35,11 +37,15 @@ class WebDevicesService {
WebDevicesService(AsyncWebServer * server, SecurityManager * securityManager); WebDevicesService(AsyncWebServer * server, SecurityManager * securityManager);
private: private:
// GET
void all_devices(AsyncWebServerRequest * request); void all_devices(AsyncWebServerRequest * request);
void scan_devices(AsyncWebServerRequest * request); void scan_devices(AsyncWebServerRequest * request);
void device_data(AsyncWebServerRequest * request, JsonVariant & json);
AsyncCallbackJsonWebHandler _device_dataHandler; // POST
void device_data(AsyncWebServerRequest * request, JsonVariant & json);
void write_value(AsyncWebServerRequest * request, JsonVariant & json);
AsyncCallbackJsonWebHandler _device_dataHandler, _writevalue_dataHandler;
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -22,39 +22,12 @@
#include <HttpEndpoint.h> #include <HttpEndpoint.h>
#include <FSPersistence.h> #include <FSPersistence.h>
#include "default_settings.h"
#define EMSESP_SETTINGS_FILE "/config/emsespSettings.json" #define EMSESP_SETTINGS_FILE "/config/emsespSettings.json"
#define EMSESP_SETTINGS_SERVICE_PATH "/rest/emsespSettings" #define EMSESP_SETTINGS_SERVICE_PATH "/rest/emsespSettings"
#define EMSESP_BOARD_PROFILE_SERVICE_PATH "/rest/boardProfile" #define EMSESP_BOARD_PROFILE_SERVICE_PATH "/rest/boardProfile"
#define EMSESP_DEFAULT_TX_MODE 1 // EMS1.0
#define EMSESP_DEFAULT_TX_DELAY 0 // no delay
#define EMSESP_DEFAULT_EMS_BUS_ID 0x0B // service key
#define EMSESP_DEFAULT_SYSLOG_ENABLED false
#define EMSESP_DEFAULT_SYSLOG_LEVEL 3 // ERR
#define EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL 0
#define EMSESP_DEFAULT_SYSLOG_HOST ""
#define EMSESP_DEFAULT_SYSLOG_PORT 514
#define EMSESP_DEFAULT_TRACELOG_RAW false
#define EMSESP_DEFAULT_MASTER_THERMOSTAT 0 // not set
#define EMSESP_DEFAULT_SHOWER_TIMER false
#define EMSESP_DEFAULT_SHOWER_ALERT false
#define EMSESP_DEFAULT_HIDE_LED false
#define EMSESP_DEFAULT_DALLAS_PARASITE false
#define EMSESP_DEFAULT_API_ENABLED false // turn off, because its insecure
#define EMSESP_DEFAULT_BOOL_FORMAT 1 // on/off
#define EMSESP_DEFAULT_ANALOG_ENABLED false
#ifndef EMSESP_DEFAULT_BOARD_PROFILE
#define EMSESP_DEFAULT_BOARD_PROFILE "S32" // Gateway S32
#endif
// Default GPIO PIN definitions - based on Wemos/Nodemcu
#define EMSESP_DEFAULT_RX_GPIO 23 // D7
#define EMSESP_DEFAULT_TX_GPIO 5 // D8
#define EMSESP_DEFAULT_DALLAS_GPIO 18
#define EMSESP_DEFAULT_LED_GPIO 2
#define EMSESP_DEFAULT_PBUTTON_GPIO 0
namespace emsesp { namespace emsesp {
class WebSettings { class WebSettings {

View File

@@ -30,14 +30,18 @@ std::vector<Command::CmdFunction> Command::cmdfunctions_;
// id may be used to represent a heating circuit for example, it's optional // id may be used to represent a heating circuit for example, it's optional
// returns false if error or not found // returns false if error or not found
bool Command::call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id) { bool Command::call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id) {
auto cf = find_command(device_type, cmd); std::string dname = EMSdevice::device_type_2_device_name(device_type);
int8_t id_new = id;
char cmd_new[20];
check_command(cmd_new, cmd, id_new);
auto cf = find_command(device_type, cmd_new);
if ((cf == nullptr) || (cf->cmdfunction_json_)) { if ((cf == nullptr) || (cf->cmdfunction_json_)) {
LOG_WARNING(F("Command %s not found"), cmd); LOG_WARNING(F("Command %s on %s not found"), cmd, dname.c_str());
return false; // command not found, or requires a json return false; // command not found, or requires a json
} }
#ifdef EMSESP_DEBUG #ifdef EMSESP_DEBUG
std::string dname = EMSdevice::device_type_2_device_name(device_type);
if (value == nullptr) { if (value == nullptr) {
LOG_DEBUG(F("[DEBUG] Calling %s command %s"), dname.c_str(), cmd); LOG_DEBUG(F("[DEBUG] Calling %s command %s"), dname.c_str(), cmd);
} else if (id == -1) { } else if (id == -1) {
@@ -47,18 +51,18 @@ bool Command::call(const uint8_t device_type, const char * cmd, const char * val
} }
#endif #endif
return ((cf->cmdfunction_)(value, id)); return ((cf->cmdfunction_)(value, id_new));
} }
// calls a command. Takes a json object for output. // calls a command. Takes a json object for output.
// id may be used to represent a heating circuit for example // id may be used to represent a heating circuit for example
// returns false if error or not found // returns false if error or not found
bool Command::call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id, JsonObject & json) { bool Command::call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id, JsonObject & json) {
auto cf = find_command(device_type, cmd); int8_t id_new = id;
if (cf == nullptr) { char cmd_new[20];
LOG_WARNING(F("Command %s not found"), cmd);
return false; // command not found or not json check_command(cmd_new, cmd, id_new);
} auto cf = find_command(device_type, cmd_new);
#ifdef EMSESP_DEBUG #ifdef EMSESP_DEBUG
std::string dname = EMSdevice::device_type_2_device_name(device_type); std::string dname = EMSdevice::device_type_2_device_name(device_type);
@@ -77,13 +81,49 @@ bool Command::call(const uint8_t device_type, const char * cmd, const char * val
return false; return false;
} }
if (!cf->cmdfunction_json_) { if (cf == nullptr) {
return ((cf->cmdfunction_)(value, id)); return EMSESP::get_device_value_info(json, cmd_new, id_new, device_type);
}
if (cf->cmdfunction_json_) {
return ((cf->cmdfunction_json_)(value, id_new, json));
} else { } else {
return ((cf->cmdfunction_json_)(value, id, json)); if (value == nullptr || strlen(value) == 0 || strcmp(value, "?") == 0 || strcmp(value, "*") == 0) {
return EMSESP::get_device_value_info(json, cmd_new, id_new, device_type);
}
return ((cf->cmdfunction_)(value, id_new));
} }
} }
char * Command::check_command(char * out, const char * cmd, int8_t & id) {
// convert cmd to lowercase
strlcpy(out, cmd, 20);
for (char * p = out; *p; p++) {
*p = tolower(*p);
}
//scan for prefix hc.
for (uint8_t i = DeviceValueTAG::TAG_HC1; i <= DeviceValueTAG::TAG_HC4; i++) {
if ((strncmp(out, EMSdevice::tag_to_string(i).c_str(), 3) == 0) && (strlen(out) > 4)) {
strcpy(out, &out[4]);
id = 1 + i - DeviceValueTAG::TAG_HC1;
break;
}
}
//scan for prefix wwc.
for (uint8_t i = DeviceValueTAG::TAG_WWC1; i <= DeviceValueTAG::TAG_WWC4; i++) {
if ((strncmp(out, EMSdevice::tag_to_string(i).c_str(), 4) == 0) && (strlen(out) > 5)) {
strcpy(out, &out[5]);
id = 8 + i - DeviceValueTAG::TAG_WWC1;
break;
}
}
return out;
}
// add a command to the list, which does not return json // add a command to the list, which does not return json
void Command::add(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb, uint8_t flag) { void Command::add(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb, uint8_t flag) {
// if the command already exists for that device type don't add it // if the command already exists for that device type don't add it
@@ -110,6 +150,7 @@ void Command::add_with_json(const uint8_t device_type, const __FlashStringHelper
} }
// see if a command exists for that device type // see if a command exists for that device type
// is not case sensitive
Command::CmdFunction * Command::find_command(const uint8_t device_type, const char * cmd) { Command::CmdFunction * Command::find_command(const uint8_t device_type, const char * cmd) {
if ((cmd == nullptr) || (strlen(cmd) == 0) || (cmdfunctions_.empty())) { if ((cmd == nullptr) || (strlen(cmd) == 0) || (cmdfunctions_.empty())) {
return nullptr; return nullptr;
@@ -123,7 +164,7 @@ Command::CmdFunction * Command::find_command(const uint8_t device_type, const ch
} }
for (auto & cf : cmdfunctions_) { for (auto & cf : cmdfunctions_) {
if (!strcmp_P(lowerCmd, reinterpret_cast<PGM_P>(cf.cmd_)) && (cf.device_type_ == device_type)) { if (!strcmp(lowerCmd, Helpers::toLower(uuid::read_flash_string(cf.cmd_)).c_str()) && (cf.device_type_ == device_type)) {
return &cf; return &cf;
} }
} }

View File

@@ -41,7 +41,7 @@ class Command {
public: public:
struct CmdFunction { struct CmdFunction {
uint8_t device_type_; // DeviceType:: uint8_t device_type_; // DeviceType::
uint8_t flag_; uint8_t flag_; // mqtt flags for command subscriptions
const __FlashStringHelper * cmd_; const __FlashStringHelper * cmd_;
cmdfunction_p cmdfunction_; cmdfunction_p cmdfunction_;
cmdfunction_json_p cmdfunction_json_; cmdfunction_json_p cmdfunction_json_;
@@ -65,6 +65,7 @@ class Command {
static void add_with_json(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_json_p cb); static void add_with_json(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_json_p cb);
static void show_all(uuid::console::Shell & shell); static void show_all(uuid::console::Shell & shell);
static Command::CmdFunction * find_command(const uint8_t device_type, const char * cmd); static Command::CmdFunction * find_command(const uint8_t device_type, const char * cmd);
static char * check_command(char * out, const char * cmd, int8_t & id);
static void show(uuid::console::Shell & shell, uint8_t device_type); static void show(uuid::console::Shell & shell, uint8_t device_type);
static void show_devices(uuid::console::Shell & shell); static void show_devices(uuid::console::Shell & shell);

View File

@@ -20,7 +20,7 @@
#include "emsesp.h" #include "emsesp.h"
#include "version.h" #include "version.h"
#if defined(EMSESP_TEST) #if defined(EMSESP_DEBUG)
#include "test/test.h" #include "test/test.h"
#endif #endif
@@ -54,9 +54,8 @@ void EMSESPShell::stopped() {
logger().log(LogLevel::DEBUG, LogFacility::CONSOLE, F("User session closed on console %s"), console_name().c_str()); logger().log(LogLevel::DEBUG, LogFacility::CONSOLE, F("User session closed on console %s"), console_name().c_str());
// remove all custom contexts // remove all custom contexts
commands->remove_all_commands(); // commands->remove_all_commands();
// console_commands_loaded_ = false; // make sure they get reloaded next time a console is opened
console_commands_loaded_ = false; // make sure they get reloaded next time a console is opened
} }
// show welcome banner // show welcome banner
@@ -99,9 +98,8 @@ void EMSESPShell::add_console_commands() {
if (console_commands_loaded_) { if (console_commands_loaded_) {
return; return;
} }
console_commands_loaded_ = true;
// just in case, remove everything // just in case, remove everything
// commands->remove_context_commands(ShellContext::MAIN);
commands->remove_all_commands(); commands->remove_all_commands();
commands->add_command(ShellContext::MAIN, CommandFlags::USER, flash_string_vector{F_(show)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { commands->add_command(ShellContext::MAIN, CommandFlags::USER, flash_string_vector{F_(show)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
@@ -212,6 +210,7 @@ void EMSESPShell::add_console_commands() {
shell.printfln(F_(bus_id_fmt), settings.ems_bus_id); shell.printfln(F_(bus_id_fmt), settings.ems_bus_id);
char buffer[4]; char buffer[4];
shell.printfln(F_(master_thermostat_fmt), settings.master_thermostat == 0 ? uuid::read_flash_string(F_(auto)).c_str() : Helpers::hextoa(buffer, settings.master_thermostat)); shell.printfln(F_(master_thermostat_fmt), settings.master_thermostat == 0 ? uuid::read_flash_string(F_(auto)).c_str() : Helpers::hextoa(buffer, settings.master_thermostat));
shell.printfln(F_(board_profile_fmt), settings.board_profile.c_str());
}); });
}); });
@@ -222,12 +221,16 @@ void EMSESPShell::add_console_commands() {
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) { [=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
uint8_t device_id = Helpers::hextoint(arguments.front().c_str()); uint8_t device_id = Helpers::hextoint(arguments.front().c_str());
uint16_t type_id = Helpers::hextoint(arguments[1].c_str()); uint16_t type_id = Helpers::hextoint(arguments[1].c_str());
EMSESP::set_read_id(type_id); if (arguments.size() == 4) {
if (arguments.size() == 3) { uint16_t offset = Helpers::hextoint(arguments[2].c_str());
uint8_t length = Helpers::hextoint(arguments.back().c_str());
EMSESP::send_read_request(type_id, device_id, offset, length);
} else if (arguments.size() == 3) {
uint16_t offset = Helpers::hextoint(arguments.back().c_str()); uint16_t offset = Helpers::hextoint(arguments.back().c_str());
EMSESP::send_read_request(type_id, device_id, offset); EMSESP::send_read_request(type_id, device_id, offset, EMS_MAX_TELEGRAM_LENGTH);
} else { } else {
EMSESP::send_read_request(type_id, device_id); // send with length to send immediatly and trigger publish read_id
EMSESP::send_read_request(type_id, device_id, 0, EMS_MAX_TELEGRAM_LENGTH);
} }
}); });
@@ -346,11 +349,6 @@ void EMSESPShell::add_console_commands() {
} }
const char * cmd = arguments[1].c_str(); const char * cmd = arguments[1].c_str();
if (Command::find_command(device_type, cmd) == nullptr) {
shell.print(F("Unknown command. Available commands are: "));
Command::show(shell, device_type);
return;
}
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN); DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN);
JsonObject json = doc.to<JsonObject>(); JsonObject json = doc.to<JsonObject>();
@@ -376,6 +374,11 @@ void EMSESPShell::add_console_commands() {
doc.shrinkToFit(); doc.shrinkToFit();
serializeJsonPretty(doc, shell); serializeJsonPretty(doc, shell);
shell.println(); shell.println();
return;
} else if (!ok) {
shell.println(F("Unknown command, value, or id."));
shell.print(F("Available commands are: "));
Command::show(shell, device_type);
} }
}, },
[&](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) -> std::vector<std::string> { [&](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) -> std::vector<std::string> {
@@ -405,45 +408,42 @@ void EMSESPShell::add_console_commands() {
return {}; return {};
}); });
// System context menu
commands->add_command(ShellContext::MAIN, CommandFlags::USER, flash_string_vector{F_(system)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
System::console_commands(shell, ShellContext::SYSTEM);
});
Console::load_standard_commands(ShellContext::MAIN); Console::load_standard_commands(ShellContext::MAIN);
Console::load_system_commands(ShellContext::MAIN);
console_commands_loaded_ = true;
} }
std::string EMSESPShell::hostname_text() { std::string EMSESPShell::hostname_text() {
return console_hostname_; return console_hostname_;
} }
/*
// remove commands from the current context to save memory before exiting // remove commands from the current context to save memory before exiting
bool EMSESPShell::exit_context() { bool EMSESPShell::exit_context() {
unsigned int current_context = context(); unsigned int current_context = context();
commands->remove_context_commands(current_context);
if (current_context == ShellContext::MAIN) { if (current_context == ShellContext::MAIN) {
Shell::stop(); Shell::stop();
return true; return true;
} }
return Shell::exit_context(); // commands->remove_context_commands(current_context);
// return Shell::exit_context();
return false;
} }
// enter a custom context (sub-menu) // enter a custom context (sub-menu)
void Console::enter_custom_context(Shell & shell, unsigned int context) { void Console::enter_custom_context(Shell & shell, unsigned int context) {
load_standard_commands(context); // load_standard_commands(context);
// don't go into the new context if it's already the root (to prevent double loading) // don't go into the new context if it's already the root (to prevent double loading)
if (context != ShellContext::MAIN) { if (context != ShellContext::MAIN) {
shell.enter_context(context); shell.enter_context(context);
} }
} }
*/
// each custom context has the common commands like log, help, exit, su etc // each custom context has the common commands like log, help, exit, su etc
void Console::load_standard_commands(unsigned int context) { void Console::load_standard_commands(unsigned int context) {
#if defined(EMSESP_TEST) #if defined(EMSESP_DEBUG)
EMSESPShell::commands->add_command(context, CommandFlags::USER, flash_string_vector{F("test")}, flash_string_vector{F_(name_optional)}, [](Shell & shell, const std::vector<std::string> & arguments) { EMSESPShell::commands->add_command(context, CommandFlags::USER, flash_string_vector{F("test")}, flash_string_vector{F_(name_optional)}, [](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.size() == 0) { if (arguments.size() == 0) {
Test::run_test(shell, "default"); Test::run_test(shell, "default");
@@ -500,9 +500,8 @@ void Console::load_standard_commands(unsigned int context) {
}); });
EMSESPShell::commands->add_command(context, CommandFlags::USER, flash_string_vector{F_(exit)}, [=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { EMSESPShell::commands->add_command(context, CommandFlags::USER, flash_string_vector{F_(exit)}, [=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
// delete MAIN console stuff first to save memory shell.stop();
EMSESPShell::commands->remove_context_commands(context); // shell.exit_context();
shell.exit_context();
}); });
EMSESPShell::commands->add_command(context, CommandFlags::USER, flash_string_vector{F_(su)}, [=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { EMSESPShell::commands->add_command(context, CommandFlags::USER, flash_string_vector{F_(su)}, [=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
@@ -534,6 +533,139 @@ void Console::load_standard_commands(unsigned int context) {
}); });
} }
// console commands to add
void Console::load_system_commands(unsigned int context) {
EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN,
flash_string_vector{F_(restart)},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::system_.restart(); });
EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN,
flash_string_vector{F_(wifi), F_(reconnect)},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::system_.wifi_reconnect(); });
EMSESPShell::commands->add_command(ShellContext::MAIN, CommandFlags::ADMIN, flash_string_vector{F_(format)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.enter_password(F_(password_prompt), [=](Shell & shell, bool completed, const std::string & password) {
if (completed) {
EMSESP::esp8266React.getSecuritySettingsService()->read([&](SecuritySettings & securitySettings) {
if (securitySettings.jwtSecret.equals(password.c_str())) {
EMSESP::system_.format(shell);
} else {
shell.println(F("incorrect password"));
}
});
}
});
});
EMSESPShell::commands->add_command(context, CommandFlags::ADMIN, flash_string_vector{F_(passwd)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.enter_password(F_(new_password_prompt1), [](Shell & shell, bool completed, const std::string & password1) {
if (completed) {
shell.enter_password(F_(new_password_prompt2), [password1](Shell & shell, bool completed, const std::string & password2) {
if (completed) {
if (password1 == password2) {
EMSESP::esp8266React.getSecuritySettingsService()->update(
[&](SecuritySettings & securitySettings) {
securitySettings.jwtSecret = password2.c_str();
return StateUpdateResult::CHANGED;
},
"local");
shell.println(F("su password updated"));
} else {
shell.println(F("Passwords do not match"));
}
}
});
}
});
});
EMSESPShell::commands->add_command(context, CommandFlags::USER, flash_string_vector{F_(show), F_(system)}, [=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::system_.show_system(shell);
shell.println();
});
EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(hostname)},
flash_string_vector{F_(name_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
shell.println("The network connection will be reset...");
Shell::loop_all();
delay(1000); // wait a second
EMSESP::esp8266React.getNetworkSettingsService()->update(
[&](NetworkSettings & networkSettings) {
networkSettings.hostname = arguments.front().c_str();
return StateUpdateResult::CHANGED;
},
"local");
});
EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(wifi), F_(ssid)},
flash_string_vector{F_(name_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
EMSESP::esp8266React.getNetworkSettingsService()->updateWithoutPropagation([&](NetworkSettings & networkSettings) {
networkSettings.ssid = arguments.front().c_str();
return StateUpdateResult::CHANGED;
});
shell.println("Use `wifi reconnect` to save and apply the new settings");
});
EMSESPShell::commands->add_command(context, CommandFlags::ADMIN, flash_string_vector{F_(set), F_(wifi), F_(password)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.enter_password(F_(new_password_prompt1), [](Shell & shell, bool completed, const std::string & password1) {
if (completed) {
shell.enter_password(F_(new_password_prompt2), [password1](Shell & shell, bool completed, const std::string & password2) {
if (completed) {
if (password1 == password2) {
EMSESP::esp8266React.getNetworkSettingsService()->updateWithoutPropagation([&](NetworkSettings & networkSettings) {
networkSettings.password = password2.c_str();
return StateUpdateResult::CHANGED;
});
shell.println("Use `wifi reconnect` to save and apply the new settings");
} else {
shell.println(F("Passwords do not match"));
}
}
});
}
});
});
EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(board_profile)},
flash_string_vector{F_(name_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
std::vector<uint8_t> data; // led, dallas, rx, tx, button
std::string board_profile = Helpers::toUpper(arguments.front());
if (!EMSESP::system_.load_board_profile(data, board_profile)) {
shell.println(F("Invalid board profile (S32, E32, MH-ET, NODEMCU, OLIMEX, TLK110, LAN8720, CUSTOM)"));
return;
}
EMSESP::webSettingsService.update(
[&](WebSettings & settings) {
settings.board_profile = board_profile.c_str();
settings.led_gpio = data[0];
settings.dallas_gpio = data[1];
settings.rx_gpio = data[2];
settings.tx_gpio = data[3];
settings.pbutton_gpio = data[4];
return StateUpdateResult::CHANGED;
},
"local");
shell.printfln("Loaded board profile %s (%d,%d,%d,%d,%d)", board_profile.c_str(), data[0], data[1], data[2], data[3], data[4]);
EMSESP::system_.network_init(true);
});
EMSESPShell::commands->add_command(context, CommandFlags::ADMIN, flash_string_vector{F_(show), F_(users)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::system_.show_users(shell);
});
}
/*
// prompt, change per context // prompt, change per context
std::string EMSESPShell::context_text() { std::string EMSESPShell::context_text() {
switch (static_cast<ShellContext>(context())) { switch (static_cast<ShellContext>(context())) {
@@ -547,6 +679,7 @@ std::string EMSESPShell::context_text() {
return std::string{}; return std::string{};
} }
} }
*/
// when in su (admin) show # as the prompt suffix // when in su (admin) show # as the prompt suffix
std::string EMSESPShell::prompt_suffix() { std::string EMSESPShell::prompt_suffix() {
@@ -566,7 +699,7 @@ EMSESPStreamConsole::EMSESPStreamConsole(Stream & stream, bool local)
, uuid::console::StreamConsole(stream) , uuid::console::StreamConsole(stream)
, EMSESPShell() , EMSESPShell()
, name_(uuid::read_flash_string(F("Serial"))) , name_(uuid::read_flash_string(F("Serial")))
, pty_(std::numeric_limits<size_t>::max()) , pty_(SIZE_MAX)
, addr_() , addr_()
, port_(0) { , port_(0) {
} }
@@ -602,8 +735,6 @@ EMSESPStreamConsole::~EMSESPStreamConsole() {
#endif #endif
ptys_[pty_] = false; ptys_[pty_] = false;
ptys_.shrink_to_fit(); ptys_.shrink_to_fit();
EMSESPShell::commands->remove_all_commands();
} }
} }

View File

@@ -44,6 +44,8 @@ using uuid::log::Level;
#define LOG_WARNING(...) logger_.warning(__VA_ARGS__) #define LOG_WARNING(...) logger_.warning(__VA_ARGS__)
#define LOG_ERROR(...) logger_.err(__VA_ARGS__) #define LOG_ERROR(...) logger_.err(__VA_ARGS__)
#define MQTT_TOPIC(list_name) (__pstr__##list_name[0])
// clang-format off // clang-format off
// strings stored 32 bit aligned on ESP8266/ESP32 // strings stored 32 bit aligned on ESP8266/ESP32
#define MAKE_PSTR(string_name, string_literal) static const char __pstr__##string_name[] __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = string_literal; #define MAKE_PSTR(string_name, string_literal) static const char __pstr__##string_name[] __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = string_literal;
@@ -99,10 +101,10 @@ class EMSESPShell : virtual public uuid::console::Shell {
void stopped() override; void stopped() override;
void display_banner() override; void display_banner() override;
std::string hostname_text() override; std::string hostname_text() override;
std::string context_text() override; // std::string context_text() override;
std::string prompt_suffix() override; std::string prompt_suffix() override;
void end_of_transmission() override; void end_of_transmission() override;
bool exit_context() override; // bool exit_context() override;
private: private:
void add_console_commands(); void add_console_commands();
@@ -134,8 +136,9 @@ class Console {
uuid::log::Level log_level(); uuid::log::Level log_level();
static void enter_custom_context(Shell & shell, unsigned int context); // static void enter_custom_context(Shell & shell, unsigned int context);
static void load_standard_commands(unsigned int context); static void load_standard_commands(unsigned int context);
static void load_system_commands(unsigned int context);
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -308,12 +308,20 @@ bool DallasSensor::command_info(const char * value, const int8_t id, JsonObject
for (const auto & sensor : sensors_) { for (const auto & sensor : sensors_) {
char sensorID[10]; // sensor{1-n} char sensorID[10]; // sensor{1-n}
snprintf_P(sensorID, 10, PSTR("sensor%d"), i++); snprintf_P(sensorID, 10, PSTR("sensor%d"), i++);
if (id == 0) {
if (Mqtt::dallas_format() == Mqtt::Dallas_Format::SENSORID && Helpers::hasValue(sensor.temperature_c)) {
json[sensor.to_string()] = (float)(sensor.temperature_c) / 10;
} else if (Helpers::hasValue(sensor.temperature_c)) {
json[sensorID] = (float)(sensor.temperature_c) / 10;
}
} else {
JsonObject dataSensor = json.createNestedObject(sensorID); JsonObject dataSensor = json.createNestedObject(sensorID);
dataSensor["id"] = sensor.to_string(); dataSensor["id"] = sensor.to_string();
if (Helpers::hasValue(sensor.temperature_c)) { if (Helpers::hasValue(sensor.temperature_c)) {
dataSensor["temp"] = (float)(sensor.temperature_c) / 10; dataSensor["temp"] = (float)(sensor.temperature_c) / 10;
} }
} }
}
return true; return true;
} }
@@ -394,9 +402,9 @@ void DallasSensor::publish_values(const bool force) {
// use '_' as HA doesn't like '-' in the topic name // use '_' as HA doesn't like '-' in the topic name
std::string topicname = sensor.to_string(); std::string topicname = sensor.to_string();
std::replace(topicname.begin(), topicname.end(), '-', '_'); std::replace(topicname.begin(), topicname.end(), '-', '_');
snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/dallas_sensor%s/config"), Mqtt::base().c_str(), topicname.c_str()); snprintf_P(topic, sizeof(topic), PSTR("sensor/%s/dallas_sensor%s/config"), Mqtt::base().c_str(), topicname.c_str());
} else { } else {
snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/dallas_sensor%d/config"), Mqtt::base().c_str(), sensor_no); snprintf_P(topic, sizeof(topic), PSTR("sensor/%s/dallas_sensor%d/config"), Mqtt::base().c_str(), sensor_no);
} }
Mqtt::publish_ha(topic, config.as<JsonObject>()); Mqtt::publish_ha(topic, config.as<JsonObject>());

View File

@@ -68,6 +68,10 @@ class DallasSensor {
return sensorfails_; return sensorfails_;
} }
bool dallas_enabled() {
return (dallas_gpio_ != 0);
}
private: private:
static constexpr uint8_t MAX_SENSORS = 20; static constexpr uint8_t MAX_SENSORS = 20;

155
src/default_settings.h Normal file
View File

@@ -0,0 +1,155 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef EMSESP_DEFAULT_SETTINGS_H
#define EMSESP_DEFAULT_SETTINGS_H
// GENERAL SETTINGS
#ifndef EMSESP_DEFAULT_TX_MODE
#define EMSESP_DEFAULT_TX_MODE 1 // EMS1.0
#endif
#ifndef EMSESP_DEFAULT_TX_DELAY
#define EMSESP_DEFAULT_TX_DELAY 0 // no delay
#endif
#ifndef EMSESP_DEFAULT_EMS_BUS_ID
#define EMSESP_DEFAULT_EMS_BUS_ID 0x0B // service key
#endif
#ifndef EMSESP_DEFAULT_SYSLOG_ENABLED
#define EMSESP_DEFAULT_SYSLOG_ENABLED false
#endif
#ifndef EMSESP_DEFAULT_SYSLOG_LEVEL
#define EMSESP_DEFAULT_SYSLOG_LEVEL 3 // ERR
#endif
#ifndef EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL
#define EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL 0
#endif
#ifndef EMSESP_DEFAULT_SYSLOG_HOST
#define EMSESP_DEFAULT_SYSLOG_HOST ""
#endif
#ifndef EMSESP_DEFAULT_SYSLOG_PORT
#define EMSESP_DEFAULT_SYSLOG_PORT 514
#endif
#ifndef EMSESP_DEFAULT_TRACELOG_RAW
#define EMSESP_DEFAULT_TRACELOG_RAW false
#endif
#ifndef EMSESP_DEFAULT_MASTER_THERMOSTAT
#define EMSESP_DEFAULT_MASTER_THERMOSTAT 0 // not set
#endif
#ifndef EMSESP_DEFAULT_SHOWER_TIMER
#define EMSESP_DEFAULT_SHOWER_TIMER false
#endif
#ifndef EMSESP_DEFAULT_SHOWER_ALERT
#define EMSESP_DEFAULT_SHOWER_ALERT false
#endif
#ifndef EMSESP_DEFAULT_HIDE_LED
#define EMSESP_DEFAULT_HIDE_LED false
#endif
#ifndef EMSESP_DEFAULT_DALLAS_PARASITE
#define EMSESP_DEFAULT_DALLAS_PARASITE false
#endif
#ifndef EMSESP_DEFAULT_API_ENABLED
#define EMSESP_DEFAULT_API_ENABLED false // turn off, because its insecure
#endif
#ifndef EMSESP_DEFAULT_BOOL_FORMAT
#define EMSESP_DEFAULT_BOOL_FORMAT 1 // on/off
#endif
#ifndef EMSESP_DEFAULT_ANALOG_ENABLED
#define EMSESP_DEFAULT_ANALOG_ENABLED false
#endif
#ifndef EMSESP_DEFAULT_BOARD_PROFILE
#define EMSESP_DEFAULT_BOARD_PROFILE "S32" // Gateway S32
#endif
// Default GPIO PIN definitions - based on Wemos/Nodemcu
#ifndef EMSESP_DEFAULT_RX_GPIO
#define EMSESP_DEFAULT_RX_GPIO 23 // D7
#endif
#ifndef EMSESP_DEFAULT_TX_GPIO
#define EMSESP_DEFAULT_TX_GPIO 5 // D8
#endif
#ifndef EMSESP_DEFAULT_DALLAS_GPIO
#define EMSESP_DEFAULT_DALLAS_GPIO 18
#endif
#ifndef EMSESP_DEFAULT_LED_GPIO
#define EMSESP_DEFAULT_LED_GPIO 2
#endif
#ifndef EMSESP_DEFAULT_PBUTTON_GPIO
#define EMSESP_DEFAULT_PBUTTON_GPIO 0
#endif
// MQTT
#ifndef EMSESP_DEFAULT_BOOL_FORMAT
#define EMSESP_DEFAULT_BOOL_FORMAT 1 // on/off
#endif
#ifndef EMSESP_DEFAULT_DALLAS_FORMAT
#define EMSESP_DEFAULT_DALLAS_FORMAT 1 // sensorid
#endif
#ifndef EMSESP_DEFAULT_HA_CLIMATE_FORMAT
#define EMSESP_DEFAULT_HA_CLIMATE_FORMAT 1 // current temp
#endif
#ifndef EMSESP_DEFAULT_MQTT_QOS
#define EMSESP_DEFAULT_MQTT_QOS 0
#endif
#ifndef EMSESP_DEFAULT_MQTT_RETAIN
#define EMSESP_DEFAULT_MQTT_RETAIN false
#endif
#ifndef EMSESP_DEFAULT_HA_ENABLED
#define EMSESP_DEFAULT_HA_ENABLED false
#endif
#ifndef EMSESP_DEFAULT_PUBLISH_TIME
#define EMSESP_DEFAULT_PUBLISH_TIME 10
#endif
#ifndef EMSESP_DEFAULT_NESTED_FORMAT
#define EMSESP_DEFAULT_NESTED_FORMAT 1
#endif
#ifndef EMSESP_DEFAULT_SUBSCRIBE_FORMAT
#define EMSESP_DEFAULT_SUBSCRIBE_FORMAT 0
#endif
#endif

View File

@@ -25,9 +25,9 @@
// Boilers - 0x08 // Boilers - 0x08
{ 64, DeviceType::BOILER, F("BK13/BK15/Smartline/GB1x2"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, { 64, DeviceType::BOILER, F("BK13/BK15/Smartline/GB1x2"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{ 72, DeviceType::BOILER, F("GB125/MC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, { 72, DeviceType::BOILER, F("GB125/MC10"), DeviceFlags::EMS_DEVICE_FLAG_EMS},
{ 84, DeviceType::BOILER, F("Logamax Plus GB022"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, { 84, DeviceType::BOILER, F("Logamax Plus GB022"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{ 95, DeviceType::BOILER, F("Condens 2500/Logamax/Logomatic/Cerapur Top/Greenstar/Generic HT3"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, { 95, DeviceType::BOILER, F("Condens 2500/Logamax/Logomatic/Cerapur Top/Greenstar/Generic HT3"), DeviceFlags::EMS_DEVICE_FLAG_HT3},
{115, DeviceType::BOILER, F("Topline/GB162"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {115, DeviceType::BOILER, F("Topline/GB162"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{122, DeviceType::BOILER, F("Proline"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {122, DeviceType::BOILER, F("Proline"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{123, DeviceType::BOILER, F("GBx72/Trendline/Cerapur/Greenstar Si/27i"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {123, DeviceType::BOILER, F("GBx72/Trendline/Cerapur/Greenstar Si/27i"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
@@ -35,7 +35,7 @@
{133, DeviceType::BOILER, F("GB125/Logamatic MC110"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {133, DeviceType::BOILER, F("GB125/Logamatic MC110"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{167, DeviceType::BOILER, F("Cerapur Aero"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {167, DeviceType::BOILER, F("Cerapur Aero"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{170, DeviceType::BOILER, F("Logano GB212"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {170, DeviceType::BOILER, F("Logano GB212"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{172, DeviceType::BOILER, F("Enviline/Compress 6000AW/Hybrid 7000iAW/SupraEco"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {172, DeviceType::BOILER, F("Enviline/Compress 6000AW/Hybrid 7000iAW/SupraEco"), DeviceFlags::EMS_DEVICE_FLAG_HEATPUMP},
{195, DeviceType::BOILER, F("Condens 5000i/Greenstar 8000"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {195, DeviceType::BOILER, F("Condens 5000i/Greenstar 8000"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{203, DeviceType::BOILER, F("Logamax U122/Cerapur"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {203, DeviceType::BOILER, F("Logamax U122/Cerapur"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{208, DeviceType::BOILER, F("Logamax Plus/GB192/Condens GC9000"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {208, DeviceType::BOILER, F("Logamax Plus/GB192/Condens GC9000"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
@@ -68,23 +68,24 @@
{203, DeviceType::THERMOSTAT, F("EasyControl CT200"), DeviceFlags::EMS_DEVICE_FLAG_EASY | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write {203, DeviceType::THERMOSTAT, F("EasyControl CT200"), DeviceFlags::EMS_DEVICE_FLAG_EASY | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write
// Thermostat - Buderus/Nefit/Bosch specific - 0x17 / 0x10 / 0x18 / 0x19 / 0x38 // Thermostat - Buderus/Nefit/Bosch specific - 0x17 / 0x10 / 0x18 / 0x19 / 0x38
{ 67, DeviceType::THERMOSTAT, F("RC30"), DeviceFlags::EMS_DEVICE_FLAG_RC30_1},// 0x10 - based on RC35 { 67, DeviceType::THERMOSTAT, F("RC30"), DeviceFlags::EMS_DEVICE_FLAG_RC30_N},// 0x10 - based on RC35
{ 77, DeviceType::THERMOSTAT, F("RC20/Moduline 300"), DeviceFlags::EMS_DEVICE_FLAG_RC20},// 0x17 { 77, DeviceType::THERMOSTAT, F("RC20/Moduline 300"), DeviceFlags::EMS_DEVICE_FLAG_RC20},// 0x17
{ 78, DeviceType::THERMOSTAT, F("Moduline 400"), DeviceFlags::EMS_DEVICE_FLAG_RC30}, // 0x10 { 78, DeviceType::THERMOSTAT, F("Moduline 400"), DeviceFlags::EMS_DEVICE_FLAG_RC30}, // 0x10
{ 79, DeviceType::THERMOSTAT, F("RC10/Moduline 100"), DeviceFlags::EMS_DEVICE_FLAG_RC10},// 0x17 { 79, DeviceType::THERMOSTAT, F("RC10/Moduline 100"), DeviceFlags::EMS_DEVICE_FLAG_RC10},// 0x17
{ 80, DeviceType::THERMOSTAT, F("Moduline 200"), DeviceFlags::EMS_DEVICE_FLAG_RC10}, // 0x17 { 80, DeviceType::THERMOSTAT, F("Moduline 200"), DeviceFlags::EMS_DEVICE_FLAG_RC10}, // 0x17
{ 86, DeviceType::THERMOSTAT, F("RC35"), DeviceFlags::EMS_DEVICE_FLAG_RC35}, // 0x10 { 86, DeviceType::THERMOSTAT, F("RC35"), DeviceFlags::EMS_DEVICE_FLAG_RC35}, // 0x10
{ 90, DeviceType::THERMOSTAT, F("RC10/Moduline 100"), DeviceFlags::EMS_DEVICE_FLAG_RC20_2}, // 0x17 { 90, DeviceType::THERMOSTAT, F("RC10/Moduline 100"), DeviceFlags::EMS_DEVICE_FLAG_RC20_N}, // 0x17
{ 93, DeviceType::THERMOSTAT, F("RC20RF"), DeviceFlags::EMS_DEVICE_FLAG_RC20}, // 0x19 { 93, DeviceType::THERMOSTAT, F("RC20RF"), DeviceFlags::EMS_DEVICE_FLAG_RC20}, // 0x19
{ 94, DeviceType::THERMOSTAT, F("RFM20 Remote"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x18 { 94, DeviceType::THERMOSTAT, F("RFM20 Remote"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x18
{157, DeviceType::THERMOSTAT, F("RC200/CW100"), DeviceFlags::EMS_DEVICE_FLAG_RC100}, // 0x18 {157, DeviceType::THERMOSTAT, F("RC200/CW100"), DeviceFlags::EMS_DEVICE_FLAG_RC100}, // 0x18
{158, DeviceType::THERMOSTAT, F("RC300/RC310/Moduline 3000/1010H/CW400/Sense II"), DeviceFlags::EMS_DEVICE_FLAG_RC300}, // 0x10 {158, DeviceType::THERMOSTAT, F("RC300/RC310/Moduline 3000/1010H/CW400/Sense II"), DeviceFlags::EMS_DEVICE_FLAG_RC300}, // 0x10
{165, DeviceType::THERMOSTAT, F("RC100/Moduline 1000/1010"), DeviceFlags::EMS_DEVICE_FLAG_RC100}, // 0x18, 0x38 {165, DeviceType::THERMOSTAT, F("RC100/Moduline 1000/1010"), DeviceFlags::EMS_DEVICE_FLAG_RC100}, // 0x18, 0x38
{216, DeviceType::THERMOSTAT, F("CRF200S"), DeviceFlags::EMS_DEVICE_FLAG_CRF | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18
// Thermostat - Sieger - 0x10 / 0x17 // Thermostat - Sieger - 0x10 / 0x17
{ 66, DeviceType::THERMOSTAT, F("ES72/RC20"), DeviceFlags::EMS_DEVICE_FLAG_RC20_2}, // 0x17 or remote { 66, DeviceType::THERMOSTAT, F("ES72/RC20"), DeviceFlags::EMS_DEVICE_FLAG_RC20_N}, // 0x17 or remote
{ 76, DeviceType::THERMOSTAT, F("ES73"), DeviceFlags::EMS_DEVICE_FLAG_RC35}, // 0x10 { 76, DeviceType::THERMOSTAT, F("ES73"), DeviceFlags::EMS_DEVICE_FLAG_RC35}, // 0x10
{113, DeviceType::THERMOSTAT, F("ES72/RC20"), DeviceFlags::EMS_DEVICE_FLAG_RC20_2}, // 0x17 {113, DeviceType::THERMOSTAT, F("ES72/RC20"), DeviceFlags::EMS_DEVICE_FLAG_RC20_N}, // 0x17
// Thermostat - Junkers - 0x10 // Thermostat - Junkers - 0x10
{105, DeviceType::THERMOSTAT, F("FW100"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS}, {105, DeviceType::THERMOSTAT, F("FW100"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},

View File

@@ -33,172 +33,175 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
uint8_t hs = device_id - EMSdevice::EMS_DEVICE_ID_BOILER_1; // heating source id, count from 0 uint8_t hs = device_id - EMSdevice::EMS_DEVICE_ID_BOILER_1; // heating source id, count from 0
// Runtime of each heatingsource in 0x06DC, ff // Runtime of each heatingsource in 0x06DC, ff
register_telegram_type(0x6DC + hs, F("CascadeMessage"), false, MAKE_PF_CB(process_CascadeMessage)); register_telegram_type(0x6DC + hs, F("CascadeMessage"), false, MAKE_PF_CB(process_CascadeMessage));
register_device_value(TAG_HS1 + hs, &burnWorkMin_, DeviceValueType::TIME, nullptr, F("burnWorkMin"), F("total burner operating time"), DeviceValueUOM::MINUTES); register_device_value(TAG_HS1 + hs, &burnWorkMin_, DeviceValueType::TIME, nullptr, FL_(burnWorkMin), DeviceValueUOM::MINUTES);
// selBurnpower in D2 and E4 // selBurnpower in D2 and E4
// register_telegram_type(0xD2, F("CascadePowerMessage"), false, MAKE_PF_CB(process_CascadePowerMessage)); // register_telegram_type(0xD2, F("CascadePowerMessage"), false, MAKE_PF_CB(process_CascadePowerMessage));
// individual Flowtemps and powervalues for each heatingsource in E4 // individual Flowtemps and powervalues for each heatingsource in E4
register_telegram_type(0xE4, F("UBAMonitorFastPlus"), false, MAKE_PF_CB(process_UBAMonitorFastPlus)); register_telegram_type(0xE4, F("UBAMonitorFastPlus"), false, MAKE_PF_CB(process_UBAMonitorFastPlus));
register_device_value(TAG_HS1 + hs, &selFlowTemp_, DeviceValueType::UINT, nullptr, F("selFlowTemp"), F("selected flow temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_HS1 + hs, &selFlowTemp_, DeviceValueType::UINT, nullptr, FL_(selFlowTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_HS1 + hs, &selBurnPow_, DeviceValueType::UINT, nullptr, F("selBurnPow"), F("burner selected max power"), DeviceValueUOM::PERCENT); register_device_value(TAG_HS1 + hs, &selBurnPow_, DeviceValueType::UINT, nullptr, FL_(selBurnPow), DeviceValueUOM::PERCENT);
register_device_value(TAG_HS1 + hs, &curFlowTemp_, DeviceValueType::USHORT, FL_(div10), F("curFlowTemp"), F("current flow temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_HS1 + hs, &curFlowTemp_, DeviceValueType::USHORT, FL_(div10), FL_(curFlowTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_HS1 + hs, &curBurnPow_, DeviceValueType::UINT, nullptr, F("curBurnPow"), F("burner current power"), DeviceValueUOM::PERCENT); register_device_value(TAG_HS1 + hs, &curBurnPow_, DeviceValueType::UINT, nullptr, FL_(curBurnPow), DeviceValueUOM::PERCENT);
return; return;
} }
// register values for master boiler/cascade module // register values for master boiler/cascade module
reserve_telgram_functions(25); // reserve some space for the telegram registries, to avoid memory fragmentation reserve_telgram_functions(25); // reserve some space for the telegram registries, to avoid memory fragmentation
// the telegram handlers... // the telegram handlers...
// common for all boilers
register_telegram_type(0x10, F("UBAErrorMessage1"), false, MAKE_PF_CB(process_UBAErrorMessage)); register_telegram_type(0x10, F("UBAErrorMessage1"), false, MAKE_PF_CB(process_UBAErrorMessage));
register_telegram_type(0x11, F("UBAErrorMessage2"), false, MAKE_PF_CB(process_UBAErrorMessage)); register_telegram_type(0x11, F("UBAErrorMessage2"), false, MAKE_PF_CB(process_UBAErrorMessage));
register_telegram_type(0x14, F("UBATotalUptime"), true, MAKE_PF_CB(process_UBATotalUptime)); register_telegram_type(0x14, F("UBATotalUptime"), true, MAKE_PF_CB(process_UBATotalUptime));
register_telegram_type(0x15, F("UBAMaintenanceData"), false, MAKE_PF_CB(process_UBAMaintenanceData)); register_telegram_type(0x15, F("UBAMaintenanceData"), false, MAKE_PF_CB(process_UBAMaintenanceData));
register_telegram_type(0x16, F("UBAParameters"), true, MAKE_PF_CB(process_UBAParameters)); register_telegram_type(0x1C, F("UBAMaintenanceStatus"), false, MAKE_PF_CB(process_UBAMaintenanceStatus));
// EMS1.0 and maybe EMS+?
register_telegram_type(0x18, F("UBAMonitorFast"), false, MAKE_PF_CB(process_UBAMonitorFast)); register_telegram_type(0x18, F("UBAMonitorFast"), false, MAKE_PF_CB(process_UBAMonitorFast));
register_telegram_type(0x19, F("UBAMonitorSlow"), true, MAKE_PF_CB(process_UBAMonitorSlow)); register_telegram_type(0x19, F("UBAMonitorSlow"), true, MAKE_PF_CB(process_UBAMonitorSlow));
register_telegram_type(0x1A, F("UBASetPoints"), false, MAKE_PF_CB(process_UBASetPoints)); register_telegram_type(0x1A, F("UBASetPoints"), false, MAKE_PF_CB(process_UBASetPoints));
register_telegram_type(0x1C, F("UBAMaintenanceStatus"), false, MAKE_PF_CB(process_UBAMaintenanceStatus)); register_telegram_type(0x35, F("UBAFlags"), false, MAKE_PF_CB(process_UBAFlags));
register_telegram_type(0x26, F("UBASettingsWW"), true, MAKE_PF_CB(process_UBASettingsWW)); // only EMS 1.0
register_telegram_type(0x2A, F("MC10Status"), false, MAKE_PF_CB(process_MC10Status)); register_telegram_type(0x16, F("UBAParameters"), true, MAKE_PF_CB(process_UBAParameters));
register_telegram_type(0x33, F("UBAParameterWW"), true, MAKE_PF_CB(process_UBAParameterWW)); register_telegram_type(0x33, F("UBAParameterWW"), true, MAKE_PF_CB(process_UBAParameterWW));
register_telegram_type(0x34, F("UBAMonitorWW"), false, MAKE_PF_CB(process_UBAMonitorWW)); register_telegram_type(0x34, F("UBAMonitorWW"), false, MAKE_PF_CB(process_UBAMonitorWW));
register_telegram_type(0x35, F("UBAFlags"), false, MAKE_PF_CB(process_UBAFlags)); // not ems1.0, but HT3
if (model() != EMSdevice::EMS_DEVICE_FLAG_EMS) {
register_telegram_type(0x26, F("UBASettingsWW"), true, MAKE_PF_CB(process_UBASettingsWW));
register_telegram_type(0x2A, F("MC110Status"), false, MAKE_PF_CB(process_MC110Status));
}
// only EMS+
if (model() != EMSdevice::EMS_DEVICE_FLAG_EMS && model() != EMSdevice::EMS_DEVICE_FLAG_HT3) {
register_telegram_type(0xD1, F("UBAOutdoorTemp"), false, MAKE_PF_CB(process_UBAOutdoorTemp)); register_telegram_type(0xD1, F("UBAOutdoorTemp"), false, MAKE_PF_CB(process_UBAOutdoorTemp));
register_telegram_type(0xE3, F("UBAMonitorSlowPlus"), false, MAKE_PF_CB(process_UBAMonitorSlowPlus2)); register_telegram_type(0xE3, F("UBAMonitorSlowPlus"), false, MAKE_PF_CB(process_UBAMonitorSlowPlus2));
register_telegram_type(0xE4, F("UBAMonitorFastPlus"), false, MAKE_PF_CB(process_UBAMonitorFastPlus)); register_telegram_type(0xE4, F("UBAMonitorFastPlus"), false, MAKE_PF_CB(process_UBAMonitorFastPlus));
register_telegram_type(0xE5, F("UBAMonitorSlowPlus"), false, MAKE_PF_CB(process_UBAMonitorSlowPlus)); register_telegram_type(0xE5, F("UBAMonitorSlowPlus"), false, MAKE_PF_CB(process_UBAMonitorSlowPlus));
register_telegram_type(0xE6, F("UBAParametersPlus"), true, MAKE_PF_CB(process_UBAParametersPlus)); register_telegram_type(0xE6, F("UBAParametersPlus"), true, MAKE_PF_CB(process_UBAParametersPlus));
register_telegram_type(0xE9, F("UBADHWStatus"), false, MAKE_PF_CB(process_UBADHWStatus)); register_telegram_type(0xE9, F("UBAMonitorWWPlus"), false, MAKE_PF_CB(process_UBAMonitorWWPlus));
register_telegram_type(0xEA, F("UBAParameterWWPlus"), true, MAKE_PF_CB(process_UBAParameterWWPlus)); register_telegram_type(0xEA, F("UBAParameterWWPlus"), true, MAKE_PF_CB(process_UBAParameterWWPlus));
}
if (model() == EMSdevice::EMS_DEVICE_FLAG_HEATPUMP) {
register_telegram_type(0x494, F("UBAEnergySupplied"), false, MAKE_PF_CB(process_UBAEnergySupplied)); register_telegram_type(0x494, F("UBAEnergySupplied"), false, MAKE_PF_CB(process_UBAEnergySupplied));
register_telegram_type(0x495, F("UBAInformation"), false, MAKE_PF_CB(process_UBAInformation)); register_telegram_type(0x495, F("UBAInformation"), false, MAKE_PF_CB(process_UBAInformation));
register_telegram_type(0x48D, F("HpPower"), false, MAKE_PF_CB(process_HpPower));
register_telegram_type(0x48F, F("HpOutdoor"), false, MAKE_PF_CB(process_HpOutdoor));
}
// MQTT commands for boiler topic // MQTT commands for boiler topic
register_mqtt_cmd(F("comfort"), MAKE_CF_CB(set_warmwater_mode)); register_device_value(TAG_BOILER_DATA, &dummybool_, DeviceValueType::BOOL, nullptr, FL_(wwtapactivated), DeviceValueUOM::NONE, MAKE_CF_CB(set_tapwarmwater_activated));
register_mqtt_cmd(F("wwactivated"), MAKE_CF_CB(set_warmwater_activated)); register_device_value(TAG_BOILER_DATA, &dummy8u_, DeviceValueType::ENUM, FL_(enum_reset), FL_(reset), DeviceValueUOM::NONE, MAKE_CF_CB(set_reset));
register_mqtt_cmd(F("wwtapactivated"), MAKE_CF_CB(set_tapwarmwater_activated));
register_mqtt_cmd(F("wwflowtempoffset"), MAKE_CF_CB(set_wWFlowTempOffset));
register_mqtt_cmd(F("wwmaxpower"), MAKE_CF_CB(set_warmwater_maxpower));
register_mqtt_cmd(F("wwonetime"), MAKE_CF_CB(set_warmwater_onetime));
register_mqtt_cmd(F("wwcircpump"), MAKE_CF_CB(set_warmwater_circulation_pump));
register_mqtt_cmd(F("wwcirculation"), MAKE_CF_CB(set_warmwater_circulation));
register_mqtt_cmd(F("wwcircmode"), MAKE_CF_CB(set_warmwater_circulation_mode));
register_mqtt_cmd(F("flowtemp"), MAKE_CF_CB(set_flow_temp));
register_mqtt_cmd(F("wwsettemp"), MAKE_CF_CB(set_warmwater_temp));
register_mqtt_cmd(F("heatingactivated"), MAKE_CF_CB(set_heating_activated));
register_mqtt_cmd(F("heatingtemp"), MAKE_CF_CB(set_heating_temp));
register_mqtt_cmd(F("burnmaxpower"), MAKE_CF_CB(set_max_power));
register_mqtt_cmd(F("burnminpower"), MAKE_CF_CB(set_min_power));
register_mqtt_cmd(F("boilhyston"), MAKE_CF_CB(set_hyst_on));
register_mqtt_cmd(F("boilhystoff"), MAKE_CF_CB(set_hyst_off));
register_mqtt_cmd(F("burnperiod"), MAKE_CF_CB(set_burn_period));
register_mqtt_cmd(F("pumpdelay"), MAKE_CF_CB(set_pump_delay));
register_mqtt_cmd(F("maintenance"), MAKE_CF_CB(set_maintenance));
register_mqtt_cmd(F("pumpmodmax"), MAKE_CF_CB(set_max_pump));
register_mqtt_cmd(F("pumpmodmin"), MAKE_CF_CB(set_min_pump));
register_mqtt_cmd(F("reset"), MAKE_CF_CB(set_reset));
// add values // add values
reserve_device_values(50); // reserve_device_values(90);
// main - boiler_data topic // main - boiler_data topic
register_device_value(TAG_BOILER_DATA, &id_, DeviceValueType::UINT, nullptr, F("id"), nullptr); // empty full name to prevent being shown in web or console register_device_value(TAG_BOILER_DATA, &id_, DeviceValueType::UINT, nullptr, FL_(ID), DeviceValueUOM::NONE);
id_ = product_id; id_ = product_id;
register_device_value(TAG_BOILER_DATA, &heatingActive_, DeviceValueType::BOOL, nullptr, F("heatingActive"), F("heating active")); register_device_value(TAG_BOILER_DATA, &heatingActive_, DeviceValueType::BOOL, nullptr, FL_(heatingActive), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA, &tapwaterActive_, DeviceValueType::BOOL, nullptr, F("tapwaterActive"), F("warm water active")); register_device_value(TAG_BOILER_DATA, &tapwaterActive_, DeviceValueType::BOOL, nullptr, FL_(tapwaterActive), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA, &selFlowTemp_, DeviceValueType::UINT, nullptr, F("selFlowTemp"), F("selected flow temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_BOILER_DATA, &selFlowTemp_, DeviceValueType::UINT, nullptr, FL_(selFlowTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_flow_temp));
register_device_value(TAG_BOILER_DATA, &selBurnPow_, DeviceValueType::UINT, nullptr, F("selBurnPow"), F("burner selected max power"), DeviceValueUOM::PERCENT); register_device_value(TAG_BOILER_DATA, &selBurnPow_, DeviceValueType::UINT, nullptr, FL_(selBurnPow), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_burn_power));
register_device_value(TAG_BOILER_DATA, &heatingPumpMod_, DeviceValueType::UINT, nullptr, F("heatingPumpMod"), F("heating pump modulation"), DeviceValueUOM::PERCENT); register_device_value(TAG_BOILER_DATA, &heatingPumpMod_, DeviceValueType::UINT, nullptr, FL_(heatingPumpMod), DeviceValueUOM::PERCENT);
register_device_value(TAG_BOILER_DATA, &heatingPump2Mod_, DeviceValueType::UINT, nullptr, F("heatingPump2Mod"), F("heating pump 2 modulation"), DeviceValueUOM::PERCENT); register_device_value(TAG_BOILER_DATA, &heatingPump2Mod_, DeviceValueType::UINT, nullptr, FL_(heatingPump2Mod), DeviceValueUOM::PERCENT);
register_device_value(TAG_BOILER_DATA, &outdoorTemp_, DeviceValueType::SHORT, FL_(div10), F("outdoorTemp"), F("outside temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_BOILER_DATA, &outdoorTemp_, DeviceValueType::SHORT, FL_(div10), FL_(outdoorTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA, &curFlowTemp_, DeviceValueType::USHORT, FL_(div10), F("curFlowTemp"), F("current flow temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_BOILER_DATA, &curFlowTemp_, DeviceValueType::USHORT, FL_(div10), FL_(curFlowTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA, &retTemp_, DeviceValueType::USHORT, FL_(div10), F("retTemp"), F("return temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_BOILER_DATA, &retTemp_, DeviceValueType::USHORT, FL_(div10), FL_(retTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA, &switchTemp_, DeviceValueType::USHORT, FL_(div10), F("switchTemp"), F("mixing switch temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_BOILER_DATA, &switchTemp_, DeviceValueType::USHORT, FL_(div10), FL_(switchTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA, &sysPress_, DeviceValueType::UINT, FL_(div10), F("sysPress"), F("system pressure"), DeviceValueUOM::BAR); register_device_value(TAG_BOILER_DATA, &sysPress_, DeviceValueType::UINT, FL_(div10), FL_(sysPress), DeviceValueUOM::BAR);
register_device_value(TAG_BOILER_DATA, &boilTemp_, DeviceValueType::USHORT, FL_(div10), F("boilTemp"), F("max boiler temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_BOILER_DATA, &boilTemp_, DeviceValueType::USHORT, FL_(div10), FL_(boilTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA, &exhaustTemp_, DeviceValueType::USHORT, FL_(div10), F("exhaustTemp"), F("exhaust temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_BOILER_DATA, &exhaustTemp_, DeviceValueType::USHORT, FL_(div10), FL_(exhaustTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA, &burnGas_, DeviceValueType::BOOL, nullptr, F("burnGas"), F("gas")); register_device_value(TAG_BOILER_DATA, &burnGas_, DeviceValueType::BOOL, nullptr, FL_(burnGas), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA, &flameCurr_, DeviceValueType::USHORT, FL_(div10), F("flameCurr"), F("flame current"), DeviceValueUOM::UA); register_device_value(TAG_BOILER_DATA, &flameCurr_, DeviceValueType::USHORT, FL_(div10), FL_(flameCurr), DeviceValueUOM::UA);
register_device_value(TAG_BOILER_DATA, &heatingPump_, DeviceValueType::BOOL, nullptr, F("heatingPump"), F("heating pump"), DeviceValueUOM::PUMP); register_device_value(TAG_BOILER_DATA, &heatingPump_, DeviceValueType::BOOL, nullptr, FL_(heatingPump), DeviceValueUOM::PUMP);
register_device_value(TAG_BOILER_DATA, &fanWork_, DeviceValueType::BOOL, nullptr, F("fanWork"), F("fan")); register_device_value(TAG_BOILER_DATA, &fanWork_, DeviceValueType::BOOL, nullptr, FL_(fanWork), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA, &ignWork_, DeviceValueType::BOOL, nullptr, F("ignWork"), F("ignition")); register_device_value(TAG_BOILER_DATA, &ignWork_, DeviceValueType::BOOL, nullptr, FL_(ignWork), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA, &heatingActivated_, DeviceValueType::BOOL, nullptr, F("heatingActivated"), F("heating activated")); register_device_value(TAG_BOILER_DATA, &heatingActivated_, DeviceValueType::BOOL, nullptr, FL_(heatingActivated), DeviceValueUOM::NONE, MAKE_CF_CB(set_heating_activated));
register_device_value(TAG_BOILER_DATA, &heatingTemp_, DeviceValueType::UINT, nullptr, F("heatingTemp"), F("heating temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_BOILER_DATA, &heatingTemp_, DeviceValueType::UINT, nullptr, FL_(heatingTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_heating_temp));
register_device_value(TAG_BOILER_DATA, &pumpModMax_, DeviceValueType::UINT, nullptr, F("pumpModMax"), F("burner pump max power"), DeviceValueUOM::PERCENT); register_device_value(TAG_BOILER_DATA, &pumpModMax_, DeviceValueType::UINT, nullptr, FL_(pumpModMax), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_max_pump));
register_device_value(TAG_BOILER_DATA, &pumpModMin_, DeviceValueType::UINT, nullptr, F("pumpModMin"), F("burner pump min power"), DeviceValueUOM::PERCENT); register_device_value(TAG_BOILER_DATA, &pumpModMin_, DeviceValueType::UINT, nullptr, FL_(pumpModMin), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_min_pump));
register_device_value(TAG_BOILER_DATA, &pumpDelay_, DeviceValueType::UINT, nullptr, F("pumpDelay"), F("pump delay"), DeviceValueUOM::MINUTES); register_device_value(TAG_BOILER_DATA, &pumpDelay_, DeviceValueType::UINT, nullptr, FL_(pumpDelay), DeviceValueUOM::MINUTES, MAKE_CF_CB(set_pump_delay));
register_device_value(TAG_BOILER_DATA, &burnMinPeriod_, DeviceValueType::UINT, nullptr, F("burnMinPeriod"), F("burner min period"), DeviceValueUOM::MINUTES); register_device_value(TAG_BOILER_DATA, &burnMinPeriod_, DeviceValueType::UINT, nullptr, FL_(burnMinPeriod), DeviceValueUOM::MINUTES, MAKE_CF_CB(set_burn_period));
register_device_value(TAG_BOILER_DATA, &burnMinPower_, DeviceValueType::UINT, nullptr, F("burnMinPower"), F("burner min power"), DeviceValueUOM::PERCENT); register_device_value(TAG_BOILER_DATA, &burnMinPower_, DeviceValueType::UINT, nullptr, FL_(burnMinPower), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_min_power));
register_device_value(TAG_BOILER_DATA, &burnMaxPower_, DeviceValueType::UINT, nullptr, F("burnMaxPower"), F("burner max power"), DeviceValueUOM::PERCENT); register_device_value(TAG_BOILER_DATA, &burnMaxPower_, DeviceValueType::UINT, nullptr, FL_(burnMaxPower), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_max_power));
register_device_value(TAG_BOILER_DATA, &boilHystOn_, DeviceValueType::INT, nullptr, F("boilHystOn"), F("hysteresis on temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_BOILER_DATA, &boilHystOn_, DeviceValueType::INT, nullptr, FL_(boilHystOn), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_hyst_on));
register_device_value(TAG_BOILER_DATA, &boilHystOff_, DeviceValueType::INT, nullptr, F("boilHystOff"), F("hysteresis off temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_BOILER_DATA, &boilHystOff_, DeviceValueType::INT, nullptr, FL_(boilHystOff), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_hyst_off));
register_device_value(TAG_BOILER_DATA, &setFlowTemp_, DeviceValueType::UINT, nullptr, F("setFlowTemp"), F("set flow temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_BOILER_DATA, &setFlowTemp_, DeviceValueType::UINT, nullptr, FL_(setFlowTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA, &setBurnPow_, DeviceValueType::UINT, nullptr, F("setBurnPow"), F("burner set power"), DeviceValueUOM::PERCENT); register_device_value(TAG_BOILER_DATA, &setBurnPow_, DeviceValueType::UINT, nullptr, FL_(setBurnPow), DeviceValueUOM::PERCENT);
register_device_value(TAG_BOILER_DATA, &curBurnPow_, DeviceValueType::UINT, nullptr, F("curBurnPow"), F("burner current power"), DeviceValueUOM::PERCENT); register_device_value(TAG_BOILER_DATA, &curBurnPow_, DeviceValueType::UINT, nullptr, FL_(curBurnPow), DeviceValueUOM::PERCENT);
register_device_value(TAG_BOILER_DATA, &burnStarts_, DeviceValueType::ULONG, nullptr, F("burnStarts"), F("burner # starts")); register_device_value(TAG_BOILER_DATA, &burnStarts_, DeviceValueType::ULONG, nullptr, FL_(burnStarts), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA, &burnWorkMin_, DeviceValueType::TIME, nullptr, F("burnWorkMin"), F("total burner operating time"), DeviceValueUOM::MINUTES); register_device_value(TAG_BOILER_DATA, &burnWorkMin_, DeviceValueType::TIME, nullptr, FL_(burnWorkMin), DeviceValueUOM::MINUTES);
register_device_value(TAG_BOILER_DATA, &heatWorkMin_, DeviceValueType::TIME, nullptr, F("heatWorkMin"), F("total heat operating time"), DeviceValueUOM::MINUTES); register_device_value(TAG_BOILER_DATA, &heatWorkMin_, DeviceValueType::TIME, nullptr, FL_(heatWorkMin), DeviceValueUOM::MINUTES);
register_device_value(TAG_BOILER_DATA, &UBAuptime_, DeviceValueType::TIME, nullptr, F("UBAuptime"), F("total UBA operating time"), DeviceValueUOM::MINUTES); register_device_value(TAG_BOILER_DATA, &UBAuptime_, DeviceValueType::TIME, nullptr, FL_(UBAuptime), DeviceValueUOM::MINUTES);
register_device_value(TAG_BOILER_DATA, &lastCode_, DeviceValueType::TEXT, nullptr, F("lastCode"), F("last error code")); register_device_value(TAG_BOILER_DATA, &lastCode_, DeviceValueType::TEXT, nullptr, FL_(lastCode), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA, &serviceCode_, DeviceValueType::TEXT, nullptr, F("serviceCode"), F("service code")); register_device_value(TAG_BOILER_DATA, &serviceCode_, DeviceValueType::TEXT, nullptr, FL_(serviceCode), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA, &serviceCodeNumber_, DeviceValueType::USHORT, nullptr, F("serviceCodeNumber"), F("service code number")); register_device_value(TAG_BOILER_DATA, &serviceCodeNumber_, DeviceValueType::USHORT, nullptr, FL_(serviceCodeNumber), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA, &maintenanceMessage_, DeviceValueType::TEXT, nullptr, FL_(maintenanceMessage), DeviceValueUOM::NONE);
// info register_device_value(TAG_BOILER_DATA, &maintenanceDate_, DeviceValueType::TEXT, nullptr, FL_(maintenanceDate), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA, &upTimeControl_, DeviceValueType::TIME, FL_(div60), F("upTimeControl"), F("operating time total heat"), DeviceValueUOM::MINUTES); register_device_value(TAG_BOILER_DATA, &maintenanceType_, DeviceValueType::ENUM, FL_(enum_off_time_date), FL_(maintenanceType), DeviceValueUOM::NONE, MAKE_CF_CB(set_maintenance));
register_device_value(TAG_BOILER_DATA, &upTimeCompHeating_, DeviceValueType::TIME, FL_(div60), F("upTimeCompHeating"), F("operating time compressor heating"), DeviceValueUOM::MINUTES); register_device_value(TAG_BOILER_DATA, &maintenanceTime_, DeviceValueType::USHORT, nullptr, FL_(maintenanceTime), DeviceValueUOM::HOURS);
register_device_value(TAG_BOILER_DATA, &upTimeCompCooling_, DeviceValueType::TIME, FL_(div60), F("upTimeCompCooling"), F("operating time compressor cooling"), DeviceValueUOM::MINUTES); // heatpump info
register_device_value(TAG_BOILER_DATA, &upTimeCompWw_, DeviceValueType::TIME, FL_(div60), F("upTimeCompWw"), F("operating time compressor warm water"), DeviceValueUOM::MINUTES); if (model() == EMS_DEVICE_FLAG_HEATPUMP) {
register_device_value(TAG_BOILER_DATA, &heatingStarts_, DeviceValueType::ULONG, nullptr, F("heatingStarts"), F("# heating control starts")); register_device_value(TAG_BOILER_DATA, &upTimeControl_, DeviceValueType::TIME, FL_(div60), FL_(upTimeControl), DeviceValueUOM::MINUTES);
register_device_value(TAG_BOILER_DATA, &coolingStarts_, DeviceValueType::ULONG, nullptr, F("coolingStarts"), F("# cooling control starts")); register_device_value(TAG_BOILER_DATA, &upTimeCompHeating_, DeviceValueType::TIME, FL_(div60), FL_(upTimeCompHeating), DeviceValueUOM::MINUTES);
register_device_value(TAG_BOILER_DATA, &nrgConsTotal_, DeviceValueType::ULONG, nullptr, F("nrgConsTotal"), F("total energy consumption")); register_device_value(TAG_BOILER_DATA, &upTimeCompCooling_, DeviceValueType::TIME, FL_(div60), FL_(upTimeCompCooling), DeviceValueUOM::MINUTES);
register_device_value(TAG_BOILER_DATA, &nrgConsCompTotal_, DeviceValueType::ULONG, nullptr, F("nrgConsCompTotal"), F("energy consumption compressor total")); register_device_value(TAG_BOILER_DATA, &upTimeCompWw_, DeviceValueType::TIME, FL_(div60), FL_(upTimeCompWw), DeviceValueUOM::MINUTES);
register_device_value(TAG_BOILER_DATA, &nrgConsCompHeating_, DeviceValueType::ULONG, nullptr, F("nrgConsCompHeating"), F("energy consumption compressor heating")); register_device_value(TAG_BOILER_DATA, &heatingStarts_, DeviceValueType::ULONG, nullptr, FL_(heatingStarts), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA, &nrgConsCompWw_, DeviceValueType::ULONG, nullptr, F("nrgConsCompWw"), F("energy consumption compressor warm water")); register_device_value(TAG_BOILER_DATA, &coolingStarts_, DeviceValueType::ULONG, nullptr, FL_(coolingStarts), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA, &nrgConsCompCooling_, DeviceValueType::ULONG, nullptr, F("nrgConsCompCooling"), F("energy consumption compressor cooling")); register_device_value(TAG_BOILER_DATA, &nrgConsTotal_, DeviceValueType::ULONG, nullptr, FL_(nrgConsTotal), DeviceValueUOM::KWH);
register_device_value(TAG_BOILER_DATA, &nrgSuppTotal_, DeviceValueType::ULONG, nullptr, F("nrgSuppTotal"), F("total energy supplied")); register_device_value(TAG_BOILER_DATA, &nrgConsCompTotal_, DeviceValueType::ULONG, nullptr, FL_(nrgConsCompTotal), DeviceValueUOM::KWH);
register_device_value(TAG_BOILER_DATA, &nrgSuppHeating_, DeviceValueType::ULONG, nullptr, F("nrgSuppHeating"), F("total energy supplied heating")); register_device_value(TAG_BOILER_DATA, &nrgConsCompHeating_, DeviceValueType::ULONG, nullptr, FL_(nrgConsCompHeating), DeviceValueUOM::KWH);
register_device_value(TAG_BOILER_DATA, &nrgSuppWw_, DeviceValueType::ULONG, nullptr, F("nrgSuppWw"), F("total energy warm supplied warm water")); register_device_value(TAG_BOILER_DATA, &nrgConsCompWw_, DeviceValueType::ULONG, nullptr, FL_(nrgConsCompWw), DeviceValueUOM::KWH);
register_device_value(TAG_BOILER_DATA, &nrgSuppCooling_, DeviceValueType::ULONG, nullptr, F("nrgSuppCooling"), F("total energy supplied cooling")); register_device_value(TAG_BOILER_DATA, &nrgConsCompCooling_, DeviceValueType::ULONG, nullptr, FL_(nrgConsCompCooling), DeviceValueUOM::KWH);
register_device_value(TAG_BOILER_DATA, &auxElecHeatNrgConsTotal_, DeviceValueType::ULONG, nullptr, F("auxElecHeatNrgConsTotal"), F("auxiliary electrical heater energy consumption total")); register_device_value(TAG_BOILER_DATA, &auxElecHeatNrgConsTotal_, DeviceValueType::ULONG, nullptr, FL_(auxElecHeatNrgConsTotal), DeviceValueUOM::KWH);
register_device_value(TAG_BOILER_DATA, &auxElecHeatNrgConsHeating_, DeviceValueType::ULONG, nullptr, F("auxElecHeatNrgConsHeating"), F("auxiliary electrical heater energy consumption heating")); register_device_value(TAG_BOILER_DATA, &auxElecHeatNrgConsHeating_, DeviceValueType::ULONG, nullptr, FL_(auxElecHeatNrgConsHeating), DeviceValueUOM::KWH);
register_device_value(TAG_BOILER_DATA, &auxElecHeatNrgConsDHW_, DeviceValueType::ULONG, nullptr, F("auxElecHeatNrgConsDHW"), F("auxiliary electrical heater energy consumption DHW")); register_device_value(TAG_BOILER_DATA, &auxElecHeatNrgConsWW_, DeviceValueType::ULONG, nullptr, FL_(auxElecHeatNrgConsWW), DeviceValueUOM::KWH);
register_device_value(TAG_BOILER_DATA, &maintenanceMessage_, DeviceValueType::TEXT, nullptr, F("maintenanceMessage"), F("maintenance message")); register_device_value(TAG_BOILER_DATA, &nrgSuppTotal_, DeviceValueType::ULONG, nullptr, FL_(nrgSuppTotal), DeviceValueUOM::KWH);
register_device_value(TAG_BOILER_DATA, &maintenanceDate_, DeviceValueType::TEXT, nullptr, F("maintenanceDate"), F("maintenance set date")); register_device_value(TAG_BOILER_DATA, &nrgSuppHeating_, DeviceValueType::ULONG, nullptr, FL_(nrgSuppHeating), DeviceValueUOM::KWH);
register_device_value(TAG_BOILER_DATA, &maintenanceType_, DeviceValueType::ENUM, FL_(enum_off_time_date), F("maintenanceType"), F("maintenance scheduled")); register_device_value(TAG_BOILER_DATA, &nrgSuppWw_, DeviceValueType::ULONG, nullptr, FL_(nrgSuppWw), DeviceValueUOM::KWH);
register_device_value(TAG_BOILER_DATA, &maintenanceTime_, DeviceValueType::USHORT, nullptr, F("maintenanceTime"), F("maintenance set time"), DeviceValueUOM::HOURS); register_device_value(TAG_BOILER_DATA, &nrgSuppCooling_, DeviceValueType::ULONG, nullptr, FL_(nrgSuppCooling), DeviceValueUOM::KWH);
register_device_value(TAG_BOILER_DATA, &hpPower_, DeviceValueType::UINT, FL_(div10), FL_(hpPower), DeviceValueUOM::KW);
register_device_value(TAG_BOILER_DATA, &hpTc0_, DeviceValueType::SHORT, FL_(div10), FL_(hpTc0), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA, &hpTc1_, DeviceValueType::SHORT, FL_(div10), FL_(hpTc1), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA, &hpTc3_, DeviceValueType::SHORT, FL_(div10), FL_(hpTc3), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA, &hpTr3_, DeviceValueType::SHORT, FL_(div10), FL_(hpTr3), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA, &hpTr4_, DeviceValueType::SHORT, FL_(div10), FL_(hpTr4), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA, &hpTr5_, DeviceValueType::SHORT, FL_(div10), FL_(hpTr5), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA, &hpTr6_, DeviceValueType::SHORT, FL_(div10), FL_(hpTr6), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA, &hpTr7_, DeviceValueType::SHORT, FL_(div10), FL_(hpTr7), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA, &hpTl2_, DeviceValueType::SHORT, FL_(div10), FL_(hpTl2), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA, &hpPl1_, DeviceValueType::SHORT, FL_(div10), FL_(hpPl1), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA, &hpPh1_, DeviceValueType::SHORT, FL_(div10), FL_(hpPh1), DeviceValueUOM::DEGREES);
}
// warm water - boiler_data_ww topic // warm water - boiler_data_ww topic
register_device_value(TAG_BOILER_DATA_WW, &wWSelTemp_, DeviceValueType::UINT, nullptr, F("wWSelTemp"), F("selected temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_DEVICE_DATA_WW, &wWSelTemp_, DeviceValueType::UINT, nullptr, FL_(wWSelTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA_WW, &wWSetTemp_, DeviceValueType::UINT, nullptr, F("wWSetTemp"), F("set temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_DEVICE_DATA_WW, &wWSetTemp_, DeviceValueType::UINT, nullptr, FL_(wWSetTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_warmwater_temp));
register_device_value(TAG_BOILER_DATA_WW, &wWType_, DeviceValueType::ENUM, FL_(enum_flow), F("wWType"), F("type")); register_device_value(TAG_DEVICE_DATA_WW, &wWType_, DeviceValueType::ENUM, FL_(enum_flow), FL_(wWType), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA_WW, &wWComfort_, DeviceValueType::ENUM, FL_(enum_comfort), F("wWComfort"), F("comfort")); register_device_value(TAG_DEVICE_DATA_WW, &wWComfort_, DeviceValueType::ENUM, FL_(enum_comfort), FL_(wWComfort), DeviceValueUOM::NONE, MAKE_CF_CB(set_warmwater_mode));
register_device_value(TAG_BOILER_DATA_WW, &wWFlowTempOffset_, DeviceValueType::UINT, nullptr, F("wWFlowTempOffset"), F("flow temperature offset")); register_device_value(TAG_DEVICE_DATA_WW, &wWFlowTempOffset_, DeviceValueType::UINT, nullptr, FL_(wWFlowTempOffset), DeviceValueUOM::NONE, MAKE_CF_CB(set_wWFlowTempOffset));
register_device_value(TAG_BOILER_DATA_WW, &wWMaxPower_, DeviceValueType::UINT, nullptr, F("wWMaxPower"), F("max power"), DeviceValueUOM::PERCENT); register_device_value(TAG_DEVICE_DATA_WW, &wWMaxPower_, DeviceValueType::UINT, nullptr, FL_(wWMaxPower), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_warmwater_maxpower));
register_device_value(TAG_BOILER_DATA_WW, &wWCircPump_, DeviceValueType::BOOL, nullptr, F("wWCircPump"), F("circulation pump available")); register_device_value(TAG_DEVICE_DATA_WW, &wWCircPump_, DeviceValueType::BOOL, nullptr, FL_(wWCircPump), DeviceValueUOM::NONE, MAKE_CF_CB(set_warmwater_circulation_pump));
register_device_value(TAG_BOILER_DATA_WW, &wWChargeType_, DeviceValueType::BOOL, FL_(enum_charge), F("wWChargeType"), F("charging type")); register_device_value(TAG_DEVICE_DATA_WW, &wWChargeType_, DeviceValueType::BOOL, FL_(enum_charge), FL_(wWChargeType), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA_WW, &wWDisinfectionTemp_, DeviceValueType::UINT, nullptr, F("wWDisinfectionTemp"), F("disinfection temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_DEVICE_DATA_WW, &wWDisinfectionTemp_, DeviceValueType::UINT, nullptr, FL_(wWDisinfectionTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_disinfect_temp));
register_device_value(TAG_BOILER_DATA_WW, &wWCircMode_, DeviceValueType::ENUM, FL_(enum_freq), F("wWCircMode"), F("circulation pump freq")); register_device_value(TAG_DEVICE_DATA_WW, &wWCircMode_, DeviceValueType::ENUM, FL_(enum_freq), FL_(wWCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_warmwater_circulation_mode));
register_device_value(TAG_BOILER_DATA_WW, &wWCirc_, DeviceValueType::BOOL, nullptr, F("wWCirc"), F("circulation active")); register_device_value(TAG_DEVICE_DATA_WW, &wWCirc_, DeviceValueType::BOOL, nullptr, FL_(wWCirc), DeviceValueUOM::NONE, MAKE_CF_CB(set_warmwater_circulation));
register_device_value(TAG_BOILER_DATA_WW, &wWCurTemp_, DeviceValueType::USHORT, FL_(div10), F("wWCurTemp"), F("current intern temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_DEVICE_DATA_WW, &wWCurTemp_, DeviceValueType::USHORT, FL_(div10), FL_(wWCurTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA_WW, &wWCurTemp2_, DeviceValueType::USHORT, FL_(div10), F("wWCurTemp2"), F("current extern temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_DEVICE_DATA_WW, &wWCurTemp2_, DeviceValueType::USHORT, FL_(div10), FL_(wWCurTemp2), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA_WW, &wWCurFlow_, DeviceValueType::UINT, FL_(div10), F("wWCurFlow"), F("current tap water flow"), DeviceValueUOM::LMIN); register_device_value(TAG_DEVICE_DATA_WW, &wWCurFlow_, DeviceValueType::UINT, FL_(div10), FL_(wWCurFlow), DeviceValueUOM::LMIN);
register_device_value(TAG_BOILER_DATA_WW, &wWStorageTemp1_, DeviceValueType::USHORT, FL_(div10), F("wWStorageTemp1"), F("storage intern temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_DEVICE_DATA_WW, &wWStorageTemp1_, DeviceValueType::USHORT, FL_(div10), FL_(wWStorageTemp1), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA_WW, &wWStorageTemp2_, DeviceValueType::USHORT, FL_(div10), F("wWStorageTemp2"), F("storage extern temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_DEVICE_DATA_WW, &wWStorageTemp2_, DeviceValueType::USHORT, FL_(div10), FL_(wWStorageTemp2), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA_WW, &wWActivated_, DeviceValueType::BOOL, nullptr, F("wWActivated"), F("activated")); register_device_value(TAG_DEVICE_DATA_WW, &wWActivated_, DeviceValueType::BOOL, nullptr, FL_(wWActivated), DeviceValueUOM::NONE, MAKE_CF_CB(set_warmwater_activated));
register_device_value(TAG_BOILER_DATA_WW, &wWOneTime_, DeviceValueType::BOOL, nullptr, F("wWOneTime"), F("one time charging")); register_device_value(TAG_DEVICE_DATA_WW, &wWOneTime_, DeviceValueType::BOOL, nullptr, FL_(wWOneTime), DeviceValueUOM::NONE, MAKE_CF_CB(set_warmwater_onetime));
register_device_value(TAG_BOILER_DATA_WW, &wWDisinfecting_, DeviceValueType::BOOL, nullptr, F("wWDisinfecting"), F("disinfecting")); register_device_value(TAG_DEVICE_DATA_WW, &wWDisinfecting_, DeviceValueType::BOOL, nullptr, FL_(wWDisinfecting), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA_WW, &wWCharging_, DeviceValueType::BOOL, nullptr, F("wWCharging"), F("charging")); register_device_value(TAG_DEVICE_DATA_WW, &wWCharging_, DeviceValueType::BOOL, nullptr, FL_(wWCharging), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA_WW, &wWRecharging_, DeviceValueType::BOOL, nullptr, F("wWRecharging"), F("recharging")); register_device_value(TAG_DEVICE_DATA_WW, &wWRecharging_, DeviceValueType::BOOL, nullptr, FL_(wWRecharging), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA_WW, &wWTempOK_, DeviceValueType::BOOL, nullptr, F("wWTempOK"), F("temperature ok")); register_device_value(TAG_DEVICE_DATA_WW, &wWTempOK_, DeviceValueType::BOOL, nullptr, FL_(wWTempOK), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA_WW, &wWActive_, DeviceValueType::BOOL, nullptr, F("wWActive"), F("active")); register_device_value(TAG_DEVICE_DATA_WW, &wWActive_, DeviceValueType::BOOL, nullptr, FL_(wWActive), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA_WW, &wWHeat_, DeviceValueType::BOOL, nullptr, F("wWHeat"), F("heating")); register_device_value(TAG_DEVICE_DATA_WW, &wWHeat_, DeviceValueType::BOOL, nullptr, FL_(wWHeat), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA_WW, &wWSetPumpPower_, DeviceValueType::UINT, nullptr, F("wWSetPumpPower"), F("pump set power"), DeviceValueUOM::PERCENT); register_device_value(TAG_DEVICE_DATA_WW, &wWSetPumpPower_, DeviceValueType::UINT, nullptr, FL_(wWSetPumpPower), DeviceValueUOM::PERCENT);
register_device_value(TAG_BOILER_DATA_WW, &mixerTemp_, DeviceValueType::USHORT, FL_(div10), F("mixerTemp"), F("mixer temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_DEVICE_DATA_WW, &mixerTemp_, DeviceValueType::USHORT, FL_(div10), FL_(mixerTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA_WW, &tankMiddleTemp_, DeviceValueType::USHORT, FL_(div10), F("tankMiddleTemp"), F("tank middle temperature (TS3)"), DeviceValueUOM::DEGREES); register_device_value(TAG_DEVICE_DATA_WW, &tankMiddleTemp_, DeviceValueType::USHORT, FL_(div10), FL_(tankMiddleTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA_WW, &wWStarts_, DeviceValueType::ULONG, nullptr, F("wWStarts"), F("# starts")); register_device_value(TAG_DEVICE_DATA_WW, &wWStarts_, DeviceValueType::ULONG, nullptr, FL_(wWStarts), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA_WW, &wWStarts2_, DeviceValueType::ULONG, nullptr, F("wWStarts2"), F("# control starts")); register_device_value(TAG_DEVICE_DATA_WW, &wWStarts2_, DeviceValueType::ULONG, nullptr, FL_(wWStarts2), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA_WW, &wWWorkM_, DeviceValueType::TIME, nullptr, F("wWWorkM"), F("active time"), DeviceValueUOM::MINUTES); register_device_value(TAG_DEVICE_DATA_WW, &wWWorkM_, DeviceValueType::TIME, nullptr, FL_(wWWorkM), DeviceValueUOM::MINUTES);
// fetch some initial data // fetch some initial data
EMSESP::send_read_request(0x10, device_id); // read last errorcode on start (only published on errors) EMSESP::send_read_request(0x10, device_id); // read last errorcode on start (only published on errors)
@@ -227,7 +230,7 @@ bool Boiler::publish_ha_config() {
ids.add("ems-esp-boiler"); ids.add("ems-esp-boiler");
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/boiler/config"), Mqtt::base().c_str()); snprintf_P(topic, sizeof(topic), PSTR("sensor/%s/boiler/config"), Mqtt::base().c_str());
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
return true; return true;
@@ -249,7 +252,7 @@ void Boiler::check_active(const bool force) {
if (heatingActive_ != val || force) { if (heatingActive_ != val || force) {
heatingActive_ = val; heatingActive_ = val;
char s[7]; char s[7];
Mqtt::publish(F("heating_active"), Helpers::render_boolean(s, b)); Mqtt::publish(F_(heating_active), Helpers::render_boolean(s, b));
} }
// check if tap water is active, bits 1 and 4 must be set // check if tap water is active, bits 1 and 4 must be set
@@ -268,20 +271,24 @@ void Boiler::check_active(const bool force) {
if (tapwaterActive_ != val || force) { if (tapwaterActive_ != val || force) {
tapwaterActive_ = val; tapwaterActive_ = val;
char s[7]; char s[7];
Mqtt::publish(F("tapwater_active"), Helpers::render_boolean(s, b)); Mqtt::publish(F_(tapwater_active), Helpers::render_boolean(s, b));
EMSESP::tap_water_active(b); // let EMS-ESP know, used in the Shower class EMSESP::tap_water_active(b); // let EMS-ESP know, used in the Shower class
} }
} }
// 0x33 // 0x33
// Boiler(0x08) -> Me(0x0B), UBAParameterWW(0x33), data: 08 FF 30 FB FF 28 FF 07 46 00 00
void Boiler::process_UBAParameterWW(std::shared_ptr<const Telegram> telegram) { void Boiler::process_UBAParameterWW(std::shared_ptr<const Telegram> telegram) {
// has_update(telegram->read_bitvalue(wwEquipt_,0,3)); // 8=boiler has ww
has_update(telegram->read_value(wWActivated_, 1)); // 0xFF means on has_update(telegram->read_value(wWActivated_, 1)); // 0xFF means on
has_update(telegram->read_value(wWSelTemp_, 2));
// has_update(telegram->read_value(wW?_, 3)); // Hyst on (default -5)
// has_update(telegram->read_value(wW?_, 4)); // (0xFF) Maybe: Hyst off -1?
has_update(telegram->read_value(wWFlowTempOffset_, 5)); // default 40
has_update(telegram->read_value(wWCircPump_, 6)); // 0xFF means on has_update(telegram->read_value(wWCircPump_, 6)); // 0xFF means on
has_update(telegram->read_value(wWCircMode_, 7)); // 1=1x3min 6=6x3min 7=continuous has_update(telegram->read_value(wWCircMode_, 7)); // 1=1x3min 6=6x3min 7=continuous
has_update(telegram->read_value(wWChargeType_, 10)); // 0 = charge pump, 0xff = 3-way valve
has_update(telegram->read_value(wWSelTemp_, 2));
has_update(telegram->read_value(wWDisinfectionTemp_, 8)); has_update(telegram->read_value(wWDisinfectionTemp_, 8));
has_update(telegram->read_value(wWFlowTempOffset_, 5)); has_update(telegram->read_value(wWChargeType_, 10)); // 0 = charge pump, 0xff = 3-way valve
telegram->read_value(wWComfort_, 9); telegram->read_value(wWComfort_, 9);
if (wWComfort_ == 0x00) { if (wWComfort_ == 0x00) {
@@ -355,6 +362,7 @@ void Boiler::process_UBAParameters(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(boilHystOff_, 4)); has_update(telegram->read_value(boilHystOff_, 4));
has_update(telegram->read_value(boilHystOn_, 5)); has_update(telegram->read_value(boilHystOn_, 5));
has_update(telegram->read_value(burnMinPeriod_, 6)); has_update(telegram->read_value(burnMinPeriod_, 6));
// has_update(telegram->read_value(pumpType_, 7)); // 0=off, 02=?
has_update(telegram->read_value(pumpDelay_, 8)); has_update(telegram->read_value(pumpDelay_, 8));
has_update(telegram->read_value(pumpModMax_, 9)); has_update(telegram->read_value(pumpModMax_, 9));
has_update(telegram->read_value(pumpModMin_, 10)); has_update(telegram->read_value(pumpModMin_, 10));
@@ -371,14 +379,15 @@ void Boiler::process_UBASettingsWW(std::shared_ptr<const Telegram> telegram) {
/* /*
* UBAMonitorWW - type 0x34 - warm water monitor. 19 bytes long * UBAMonitorWW - type 0x34 - warm water monitor. 19 bytes long
* received every 10 seconds * received every 10 seconds
* Boiler(0x08) -> Me(0x0B), UBAMonitorWW(0x34), data: 30 01 BA 7D 00 21 00 00 03 00 01 22 2B 00 19 5B
*/ */
void Boiler::process_UBAMonitorWW(std::shared_ptr<const Telegram> telegram) { void Boiler::process_UBAMonitorWW(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(wWSetTemp_, 0)); has_update(telegram->read_value(wWSetTemp_, 0));
has_update(telegram->read_value(wWCurTemp_, 1)); has_update(telegram->read_value(wWCurTemp_, 1));
has_update(telegram->read_value(wWCurTemp2_, 3)); has_update(telegram->read_value(wWCurTemp2_, 3));
has_update(telegram->read_value(wWCurFlow_, 9));
has_update(telegram->read_value(wWType_, 8));
has_update(telegram->read_value(wWType_, 8));
has_update(telegram->read_value(wWCurFlow_, 9));
has_update(telegram->read_value(wWWorkM_, 10, 3)); // force to 3 bytes has_update(telegram->read_value(wWWorkM_, 10, 3)); // force to 3 bytes
has_update(telegram->read_value(wWStarts_, 13, 3)); // force to 3 bytes has_update(telegram->read_value(wWStarts_, 13, 3)); // force to 3 bytes
@@ -511,9 +520,9 @@ void Boiler::process_UBAParameterWWPlus(std::shared_ptr<const Telegram> telegram
// has_update(telegram->read_value(wWSelTemp_, 6)); // settings, status in E9 // has_update(telegram->read_value(wWSelTemp_, 6)); // settings, status in E9
} }
// 0xE9 - DHW Status // 0xE9 - WW monitor ems+
// e.g. 08 00 E9 00 37 01 F6 01 ED 00 00 00 00 41 3C 00 00 00 00 00 00 00 00 00 00 00 00 37 00 00 00 (CRC=77) #data=27 // e.g. 08 00 E9 00 37 01 F6 01 ED 00 00 00 00 41 3C 00 00 00 00 00 00 00 00 00 00 00 00 37 00 00 00 (CRC=77) #data=27
void Boiler::process_UBADHWStatus(std::shared_ptr<const Telegram> telegram) { void Boiler::process_UBAMonitorWWPlus(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(wWSetTemp_, 0)); has_update(telegram->read_value(wWSetTemp_, 0));
has_update(telegram->read_value(wWCurTemp_, 1)); has_update(telegram->read_value(wWCurTemp_, 1));
has_update(telegram->read_value(wWCurTemp2_, 3)); has_update(telegram->read_value(wWCurTemp2_, 3));
@@ -556,7 +565,7 @@ void Boiler::process_UBAInformation(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(auxElecHeatNrgConsTotal_, 40)); has_update(telegram->read_value(auxElecHeatNrgConsTotal_, 40));
has_update(telegram->read_value(auxElecHeatNrgConsHeating_, 48)); has_update(telegram->read_value(auxElecHeatNrgConsHeating_, 48));
has_update(telegram->read_value(auxElecHeatNrgConsDHW_, 44)); has_update(telegram->read_value(auxElecHeatNrgConsWW_, 44));
has_update(telegram->read_value(nrgConsCompTotal_, 56)); has_update(telegram->read_value(nrgConsCompTotal_, 56));
has_update(telegram->read_value(nrgConsCompHeating_, 68)); has_update(telegram->read_value(nrgConsCompHeating_, 68));
@@ -578,10 +587,31 @@ void Boiler::process_UBAEnergySupplied(std::shared_ptr<const Telegram> telegram)
has_update(telegram->read_value(nrgSuppCooling_, 16)); has_update(telegram->read_value(nrgSuppCooling_, 16));
} }
// 0x2A - MC10Status // Heatpump power - type 0x48D
void Boiler::process_HpPower(std::shared_ptr<const Telegram> telegram){
has_update(telegram->read_value(hpPower_, 11));
}
// Heatpump outdoor unit - type 0x48F
void Boiler::process_HpOutdoor(std::shared_ptr<const Telegram> telegram){
has_update(telegram->read_value(hpTc0_, 6));
has_update(telegram->read_value(hpTc1_, 4));
has_update(telegram->read_value(hpTc3_, 2));
has_update(telegram->read_value(hpTr3_, 16));
has_update(telegram->read_value(hpTr4_, 18));
has_update(telegram->read_value(hpTr5_, 20));
has_update(telegram->read_value(hpTr6_, 0));
has_update(telegram->read_value(hpTr7_, 30));
has_update(telegram->read_value(hpTl2_, 12));
has_update(telegram->read_value(hpPl1_, 26));
has_update(telegram->read_value(hpPh1_, 28));
}
// 0x2A - MC110Status
// e.g. 88 00 2A 00 00 00 00 00 00 00 00 00 D2 00 00 80 00 00 01 08 80 00 02 47 00 // e.g. 88 00 2A 00 00 00 00 00 00 00 00 00 D2 00 00 80 00 00 01 08 80 00 02 47 00
// see https://github.com/emsesp/EMS-ESP/issues/397 // see https://github.com/emsesp/EMS-ESP/issues/397
void Boiler::process_MC10Status(std::shared_ptr<const Telegram> telegram) { void Boiler::process_MC110Status(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(mixerTemp_, 14)); has_update(telegram->read_value(mixerTemp_, 14));
has_update(telegram->read_value(tankMiddleTemp_, 18)); has_update(telegram->read_value(tankMiddleTemp_, 18));
} }
@@ -595,8 +625,7 @@ void Boiler::process_UBAOutdoorTemp(std::shared_ptr<const Telegram> telegram) {
// UBASetPoint 0x1A // UBASetPoint 0x1A
void Boiler::process_UBASetPoints(std::shared_ptr<const Telegram> telegram) { void Boiler::process_UBASetPoints(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(setFlowTemp_, has_update(telegram->read_value(setFlowTemp_, 0)); // boiler set temp from thermostat
0)); // boiler set temp from thermostat
has_update(telegram->read_value(setBurnPow_, 1)); // max json power in % has_update(telegram->read_value(setBurnPow_, 1)); // max json power in %
has_update(telegram->read_value(wWSetPumpPower_, 2)); // ww pump speed/power? has_update(telegram->read_value(wWSetPumpPower_, 2)); // ww pump speed/power?
} }
@@ -615,6 +644,7 @@ void Boiler::process_CascadeMessage(std::shared_ptr<const Telegram> telegram) {
// 0x35 - not yet implemented // 0x35 - not yet implemented
void Boiler::process_UBAFlags(std::shared_ptr<const Telegram> telegram) { void Boiler::process_UBAFlags(std::shared_ptr<const Telegram> telegram) {
} }
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
// 0x1C // 0x1C
@@ -696,6 +726,7 @@ bool Boiler::set_warmwater_temp(const char * value, const int8_t id) {
if (get_toggle_fetch(EMS_TYPE_UBAParametersPlus)) { if (get_toggle_fetch(EMS_TYPE_UBAParametersPlus)) {
write_command(EMS_TYPE_UBAParameterWWPlus, 6, v, EMS_TYPE_UBAParameterWWPlus); write_command(EMS_TYPE_UBAParameterWWPlus, 6, v, EMS_TYPE_UBAParameterWWPlus);
} else { } else {
// some boiler have it in 0x33, some in 0x35
write_command(EMS_TYPE_UBAFlags, 3, v, 0x34); // for i9000, see #397 write_command(EMS_TYPE_UBAFlags, 3, v, 0x34); // for i9000, see #397
write_command(EMS_TYPE_UBAParameterWW, 2, v, EMS_TYPE_UBAParameterWW); // read seltemp back write_command(EMS_TYPE_UBAParameterWW, 2, v, EMS_TYPE_UBAParameterWW); // read seltemp back
} }
@@ -703,6 +734,23 @@ bool Boiler::set_warmwater_temp(const char * value, const int8_t id) {
return true; return true;
} }
// Set the warm water disinfection temperature
bool Boiler::set_disinfect_temp(const char * value, const int8_t id) {
int v = 0;
if (!Helpers::value2number(value, v)) {
LOG_WARNING(F("Set boiler warm water disinfect temperature: Invalid value"));
return false;
}
LOG_INFO(F("Setting boiler warm water disinfect temperature to %d C"), v);
if (get_toggle_fetch(EMS_TYPE_UBAParametersPlus)) {
write_command(EMS_TYPE_UBAParameterWWPlus, 12, v, EMS_TYPE_UBAParameterWWPlus);
} else {
write_command(EMS_TYPE_UBAParameterWW, 8, v, EMS_TYPE_UBAParameterWW);
}
return true;
}
// flow temp // flow temp
bool Boiler::set_flow_temp(const char * value, const int8_t id) { bool Boiler::set_flow_temp(const char * value, const int8_t id) {
int v = 0; int v = 0;
@@ -712,9 +760,21 @@ bool Boiler::set_flow_temp(const char * value, const int8_t id) {
} }
LOG_INFO(F("Setting boiler flow temperature to %d C"), v); LOG_INFO(F("Setting boiler flow temperature to %d C"), v);
// some boiler have it in 0x1A, some in 0x35, but both telegrams are sometimes writeonly
write_command(EMS_TYPE_UBASetPoints, 0, v, EMS_TYPE_UBASetPoints); write_command(EMS_TYPE_UBASetPoints, 0, v, EMS_TYPE_UBASetPoints);
// write_command(0x35, 3, v, 0x35);
return true;
}
// set selected burner power
bool Boiler::set_burn_power(const char * value, const int8_t id) {
int v = 0;
if (!Helpers::value2number(value, v)) {
LOG_WARNING(F("Set burner max. power: Invalid value"));
return false;
}
LOG_INFO(F("Setting burner max. power to %d %"), v);
write_command(EMS_TYPE_UBASetPoints, 1, v, EMS_TYPE_UBASetPoints);
return true; return true;
} }
@@ -930,7 +990,7 @@ bool Boiler::set_pump_delay(const char * value, const int8_t id) {
// on a RC35 it's by EMSESP::send_write_request(0x37, 0x10, 2, &set, 1, 0); (set is 1,2,3) 1=hot, 2=eco, 3=intelligent // on a RC35 it's by EMSESP::send_write_request(0x37, 0x10, 2, &set, 1, 0); (set is 1,2,3) 1=hot, 2=eco, 3=intelligent
bool Boiler::set_warmwater_mode(const char * value, const int8_t id) { bool Boiler::set_warmwater_mode(const char * value, const int8_t id) {
uint8_t set; uint8_t set;
if (!Helpers::value2enum(value, set, {F("hot"), F("eco"), F("intelligent")})) { if (!Helpers::value2enum(value, set, FL_(enum_comfort))) {
LOG_WARNING(F("Set boiler warm water mode: Invalid value")); LOG_WARNING(F("Set boiler warm water mode: Invalid value"));
return false; return false;
} }
@@ -1102,20 +1162,20 @@ bool Boiler::set_warmwater_circulation_mode(const char * value, const int8_t id)
} }
// Reset command // Reset command
// 0 & 1 Reset-Mode (Manuel, others) // 0 & 1 Reset-Mode (Manual, others)
// 8 reset maintenance message Hxx // 8 reset maintenance message Hxx
// 12 & 13 Reset that Error-memory // 12 & 13 Reset that Error-memory
bool Boiler::set_reset(const char * value, const int8_t id) { bool Boiler::set_reset(const char * value, const int8_t id) {
std::string s(12, '\0'); uint8_t num;
if (!Helpers::value2string(value, s)) { if (!Helpers::value2enum(value, num, FL_(enum_reset))) {
return false; return false;
} }
if (s == "maintenance") { if (num == 0) {
LOG_INFO(F("Reset boiler maintenance message")); LOG_INFO(F("Reset boiler maintenance message"));
write_command(0x05, 0x08, 0xFF, 0x1C); write_command(0x05, 0x08, 0xFF, 0x1C);
return true; return true;
} else if (s == "error") { } else if (num == 1) {
LOG_INFO(F("Reset boiler error message")); LOG_INFO(F("Reset boiler error message"));
write_command(0x05, 0x00, 0x5A); // error reset write_command(0x05, 0x00, 0x5A); // error reset
return true; return true;
@@ -1127,7 +1187,7 @@ bool Boiler::set_reset(const char * value, const int8_t id) {
bool Boiler::set_maintenance(const char * value, const int8_t id) { bool Boiler::set_maintenance(const char * value, const int8_t id) {
std::string s(12, '\0'); std::string s(12, '\0');
if (Helpers::value2string(value, s)) { if (Helpers::value2string(value, s)) {
if (s == "reset") { if (s == Helpers::toLower(uuid::read_flash_string(F_(reset)))) {
LOG_INFO(F("Reset boiler maintenance message")); LOG_INFO(F("Reset boiler maintenance message"));
write_command(0x05, 0x08, 0xFF, 0x1C); write_command(0x05, 0x08, 0xFF, 0x1C);
return true; return true;
@@ -1160,7 +1220,7 @@ bool Boiler::set_maintenance(const char * value, const int8_t id) {
} }
uint8_t num; uint8_t num;
if (Helpers::value2enum(value, num, {F("off"), F("time"), F("date")})) { if (Helpers::value2enum(value, num, FL_(enum_off_time_date))) {
LOG_INFO(F("Setting maintenance type to %s"), value); LOG_INFO(F("Setting maintenance type to %s"), value);
write_command(0x15, 0, num, 0x15); write_command(0x15, 0, num, 0x15);
return true; return true;

View File

@@ -32,6 +32,11 @@ class Boiler : public EMSdevice {
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;
// specific boiler characteristics, stripping the top 4 bits
inline uint8_t model() const {
return (flags() & 0x0F);
}
void check_active(const bool force = false); void check_active(const bool force = false);
uint8_t boilerState_ = EMS_VALUE_UINT_NOTSET; // Boiler state flag - FOR INTERNAL USE uint8_t boilerState_ = EMS_VALUE_UINT_NOTSET; // Boiler state flag - FOR INTERNAL USE
@@ -84,6 +89,8 @@ class Boiler : public EMSdevice {
// main // main
uint8_t id_; // product id uint8_t id_; // product id
uint8_t dummy8u_; // for commands with no output
uint8_t dummybool_; // for commands with no output
uint8_t heatingActive_; // Central heating is on/off uint8_t heatingActive_; // Central heating is on/off
uint8_t tapwaterActive_; // Hot tap water is on/off uint8_t tapwaterActive_; // Hot tap water is on/off
uint8_t selFlowTemp_; // Selected flow temperature uint8_t selFlowTemp_; // Selected flow temperature
@@ -141,12 +148,26 @@ class Boiler : public EMSdevice {
uint32_t nrgSuppCooling_; // Energy supplied cooling uint32_t nrgSuppCooling_; // Energy supplied cooling
uint32_t auxElecHeatNrgConsTotal_; // Auxiliary electrical heater energy consumption total uint32_t auxElecHeatNrgConsTotal_; // Auxiliary electrical heater energy consumption total
uint32_t auxElecHeatNrgConsHeating_; // Auxiliary electrical heater energy consumption heating uint32_t auxElecHeatNrgConsHeating_; // Auxiliary electrical heater energy consumption heating
uint32_t auxElecHeatNrgConsDHW_; // Auxiliary electrical heater energy consumption DHW uint32_t auxElecHeatNrgConsWW_; // Auxiliary electrical heater energy consumption DHW
char maintenanceMessage_[4]; char maintenanceMessage_[4];
char maintenanceDate_[12]; char maintenanceDate_[12];
uint8_t maintenanceType_; uint8_t maintenanceType_;
uint16_t maintenanceTime_; uint16_t maintenanceTime_;
// heatpump
uint8_t hpPower_;
int16_t hpTc0_;
int16_t hpTc1_;
int16_t hpTc3_;
int16_t hpTr3_;
int16_t hpTr4_;
int16_t hpTr5_;
int16_t hpTr6_;
int16_t hpTr7_;
int16_t hpTl2_;
int16_t hpPl1_;
int16_t hpPh1_;
void process_UBAParameterWW(std::shared_ptr<const Telegram> telegram); void process_UBAParameterWW(std::shared_ptr<const Telegram> telegram);
void process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram); void process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram);
void process_UBATotalUptime(std::shared_ptr<const Telegram> telegram); void process_UBATotalUptime(std::shared_ptr<const Telegram> telegram);
@@ -161,15 +182,17 @@ class Boiler : public EMSdevice {
void process_UBAOutdoorTemp(std::shared_ptr<const Telegram> telegram); void process_UBAOutdoorTemp(std::shared_ptr<const Telegram> telegram);
void process_UBASetPoints(std::shared_ptr<const Telegram> telegram); void process_UBASetPoints(std::shared_ptr<const Telegram> telegram);
void process_UBAFlags(std::shared_ptr<const Telegram> telegram); void process_UBAFlags(std::shared_ptr<const Telegram> telegram);
void process_MC10Status(std::shared_ptr<const Telegram> telegram); void process_MC110Status(std::shared_ptr<const Telegram> telegram);
void process_UBAMaintenanceStatus(std::shared_ptr<const Telegram> telegram); void process_UBAMaintenanceStatus(std::shared_ptr<const Telegram> telegram);
void process_UBAMaintenanceData(std::shared_ptr<const Telegram> telegram); void process_UBAMaintenanceData(std::shared_ptr<const Telegram> telegram);
void process_UBAErrorMessage(std::shared_ptr<const Telegram> telegram); void process_UBAErrorMessage(std::shared_ptr<const Telegram> telegram);
void process_UBADHWStatus(std::shared_ptr<const Telegram> telegram); void process_UBAMonitorWWPlus(std::shared_ptr<const Telegram> telegram);
void process_UBAInformation(std::shared_ptr<const Telegram> telegram); void process_UBAInformation(std::shared_ptr<const Telegram> telegram);
void process_UBAEnergySupplied(std::shared_ptr<const Telegram> telegram); void process_UBAEnergySupplied(std::shared_ptr<const Telegram> telegram);
void process_CascadeMessage(std::shared_ptr<const Telegram> telegram); void process_CascadeMessage(std::shared_ptr<const Telegram> telegram);
void process_UBASettingsWW(std::shared_ptr<const Telegram> telegram); void process_UBASettingsWW(std::shared_ptr<const Telegram> telegram);
void process_HpPower(std::shared_ptr<const Telegram> telegram);
void process_HpOutdoor(std::shared_ptr<const Telegram> telegram);
// commands - none of these use the additional id parameter // commands - none of these use the additional id parameter
bool set_warmwater_mode(const char * value, const int8_t id); bool set_warmwater_mode(const char * value, const int8_t id);
@@ -180,9 +203,11 @@ class Boiler : public EMSdevice {
bool set_warmwater_circulation_pump(const char * value, const int8_t id); bool set_warmwater_circulation_pump(const char * value, const int8_t id);
bool set_warmwater_circulation_mode(const char * value, const int8_t id); bool set_warmwater_circulation_mode(const char * value, const int8_t id);
bool set_warmwater_temp(const char * value, const int8_t id); bool set_warmwater_temp(const char * value, const int8_t id);
bool set_disinfect_temp(const char * value, const int8_t id);
bool set_warmwater_maxpower(const char * value, const int8_t id); bool set_warmwater_maxpower(const char * value, const int8_t id);
bool set_wWFlowTempOffset(const char * value, const int8_t id); bool set_wWFlowTempOffset(const char * value, const int8_t id);
bool set_flow_temp(const char * value, const int8_t id); bool set_flow_temp(const char * value, const int8_t id);
bool set_burn_power(const char * value, const int8_t id);
bool set_heating_activated(const char * value, const int8_t id); bool set_heating_activated(const char * value, const int8_t id);
bool set_heating_temp(const char * value, const int8_t id); bool set_heating_temp(const char * value, const int8_t id);
bool set_min_power(const char * value, const int8_t id); bool set_min_power(const char * value, const int8_t id);

View File

@@ -33,9 +33,9 @@ Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, c
register_telegram_type(0x047B, F("HP2"), true, MAKE_PF_CB(process_HPMonitor2)); register_telegram_type(0x047B, F("HP2"), true, MAKE_PF_CB(process_HPMonitor2));
// device values // device values
register_device_value(TAG_NONE, &id_, DeviceValueType::UINT, nullptr, F("id"), nullptr); // empty full name to prevent being shown in web or console register_device_value(TAG_NONE, &id_, DeviceValueType::UINT, nullptr, FL_(ID), DeviceValueUOM::NONE);
register_device_value(TAG_NONE, &airHumidity_, DeviceValueType::UINT, FL_(div2), F("airHumidity"), F("relative air humidity")); register_device_value(TAG_NONE, &airHumidity_, DeviceValueType::UINT, FL_(div2), FL_(airHumidity), DeviceValueUOM::PERCENT);
register_device_value(TAG_NONE, &dewTemperature_, DeviceValueType::UINT, nullptr, F("dewTemperature"), F("dew point temperature")); register_device_value(TAG_NONE, &dewTemperature_, DeviceValueType::UINT, nullptr, FL_(dewTemperature), DeviceValueUOM::DEGREES);
id_ = product_id; id_ = product_id;
} }
@@ -61,7 +61,7 @@ bool Heatpump::publish_ha_config() {
ids.add("ems-esp-heatpump"); ids.add("ems-esp-heatpump");
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/heatpump/config"), Mqtt::base().c_str()); snprintf_P(topic, sizeof(topic), PSTR("sensor/%s/heatpump/config"), Mqtt::base().c_str());
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
return true; return true;

View File

@@ -57,20 +57,20 @@ Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
type_ = Type::HC; type_ = Type::HC;
hc_ = device_id - 0x20 + 1; hc_ = device_id - 0x20 + 1;
uint8_t tag = TAG_HC1 + hc_ - 1; uint8_t tag = TAG_HC1 + hc_ - 1;
register_device_value(tag, &id_, DeviceValueType::UINT, nullptr, F("id"), nullptr); // empty full name to prevent being shown in web or console register_device_value(tag, &id_, DeviceValueType::UINT, nullptr, FL_(ID), DeviceValueUOM::NONE);
register_device_value(tag, &flowSetTemp_, DeviceValueType::UINT, nullptr, F("flowSetTemp"), F("setpoint flow temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &flowSetTemp_, DeviceValueType::UINT, nullptr, FL_(flowSetTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &flowTempHc_, DeviceValueType::USHORT, FL_(div10), F("flowTempHc"), F("flow temperature in assigned hc (TC1)"), DeviceValueUOM::DEGREES); register_device_value(tag, &flowTempHc_, DeviceValueType::USHORT, FL_(div10), FL_(flowTempHc), DeviceValueUOM::DEGREES);
register_device_value(tag, &pumpStatus_, DeviceValueType::BOOL, nullptr, F("pumpStatus"), F("pump status in assigned hc (PC1)"), DeviceValueUOM::PUMP); register_device_value(tag, &pumpStatus_, DeviceValueType::BOOL, nullptr, FL_(pumpStatus), DeviceValueUOM::PUMP);
register_device_value(tag, &status_, DeviceValueType::INT, nullptr, F("valveStatus"), F("mixing valve actuator in assigned hc (VC1)"), DeviceValueUOM::PERCENT); register_device_value(tag, &status_, DeviceValueType::INT, nullptr, FL_(mixerStatus), DeviceValueUOM::PERCENT);
register_device_value(tag, &flowTempVf_, DeviceValueType::USHORT, FL_(div10), F("flowTempVf"), F("flow temperature in header (T0/Vf)"), DeviceValueUOM::DEGREES); register_device_value(tag, &flowTempVf_, DeviceValueType::USHORT, FL_(div10), FL_(flowTempVf), DeviceValueUOM::DEGREES);
} else { } else {
type_ = Type::WWC; type_ = Type::WWC;
hc_ = device_id - 0x28 + 1; hc_ = device_id - 0x28 + 1;
uint8_t tag = TAG_WWC1 + hc_ - 1; uint8_t tag = TAG_WWC1 + hc_ - 1;
register_device_value(tag, &id_, DeviceValueType::UINT, nullptr, F("id"), nullptr); // empty full name to prevent being shown in web or console register_device_value(tag, &id_, DeviceValueType::UINT, nullptr, FL_(ID), DeviceValueUOM::NONE);
register_device_value(tag, &flowTempHc_, DeviceValueType::USHORT, FL_(div10), F("wwTemp"), F("current warm water temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &flowTempHc_, DeviceValueType::USHORT, FL_(div10), FL_(wwTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &pumpStatus_, DeviceValueType::BOOL, nullptr, F("pumpStatus"), F("pump status in assigned hc (PC1)"), DeviceValueUOM::PUMP); register_device_value(tag, &pumpStatus_, DeviceValueType::BOOL, nullptr, FL_(wwPumpStatus), DeviceValueUOM::PUMP);
register_device_value(tag, &status_, DeviceValueType::INT, nullptr, F("tempStatus"), F("temperature switch in assigned hc (MC1)")); register_device_value(tag, &status_, DeviceValueType::INT, nullptr, FL_(wwTempStatus), DeviceValueUOM::NONE);
} }
id_ = product_id; id_ = product_id;
@@ -109,9 +109,9 @@ bool Mixer::publish_ha_config() {
// determine the topic, if its HC and WWC. This is determined by the incoming telegram types. // determine the topic, if its HC and WWC. This is determined by the incoming telegram types.
std::string topic(Mqtt::MQTT_TOPIC_MAX_SIZE, '\0'); std::string topic(Mqtt::MQTT_TOPIC_MAX_SIZE, '\0');
if (type_ == Type::HC) { if (type_ == Type::HC) {
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/sensor/%s/mixer_hc%d/config"), Mqtt::base().c_str(), hc_); snprintf_P(&topic[0], topic.capacity() + 1, PSTR("sensor/%s/mixer_hc%d/config"), Mqtt::base().c_str(), hc_);
} else { } else {
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/sensor/%s/mixer_wwc%d/config"), Mqtt::base().c_str(), hc_); // WWC snprintf_P(&topic[0], topic.capacity() + 1, PSTR("sensor/%s/mixer_wwc%d/config"), Mqtt::base().c_str(), hc_); // WWC
} }
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
@@ -162,7 +162,6 @@ void Mixer::process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram) {
// Mixer IPM - 0x001E Temperature Message in unmixed circuits // Mixer IPM - 0x001E Temperature Message in unmixed circuits
// in unmixed circuits FlowTemp in 10C is zero, this is the measured flowtemp in header // in unmixed circuits FlowTemp in 10C is zero, this is the measured flowtemp in header
void Mixer::process_IPMTempMessage(std::shared_ptr<const Telegram> telegram) { void Mixer::process_IPMTempMessage(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(flowTempVf_, 0)); // TC1, is * 10 has_update(telegram->read_value(flowTempVf_, 0)); // TC1, is * 10
} }

View File

@@ -50,8 +50,6 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
register_telegram_type(0x0380, F("SM100CollectorConfig"), true, MAKE_PF_CB(process_SM100CollectorConfig)); register_telegram_type(0x0380, F("SM100CollectorConfig"), true, MAKE_PF_CB(process_SM100CollectorConfig));
register_telegram_type(0x038E, F("SM100Energy"), true, MAKE_PF_CB(process_SM100Energy)); register_telegram_type(0x038E, F("SM100Energy"), true, MAKE_PF_CB(process_SM100Energy));
register_telegram_type(0x0391, F("SM100Time"), true, MAKE_PF_CB(process_SM100Time)); register_telegram_type(0x0391, F("SM100Time"), true, MAKE_PF_CB(process_SM100Time));
register_mqtt_cmd(F("SM100TankBottomMaxTemp"), MAKE_CF_CB(set_SM100TankBottomMaxTemp));
} }
} }
@@ -64,32 +62,39 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
// special case for a device_id with 0x2A where it's not actual a solar module // special case for a device_id with 0x2A where it's not actual a solar module
if (device_id == 0x2A) { if (device_id == 0x2A) {
register_device_value(TAG_NONE, &type_, DeviceValueType::TEXT, nullptr, F("type"), F("type")); register_device_value(TAG_NONE, &type_, DeviceValueType::TEXT, nullptr, FL_(type), DeviceValueUOM::NONE);
strncpy(type_, "warm water circuit", sizeof(type_)); strlcpy(type_, "warm water circuit", sizeof(type_));
register_device_value(TAG_DEVICE_DATA_WW, &wwTemp_1_, DeviceValueType::UINT, nullptr, FL_(wwTemp1), DeviceValueUOM::DEGREES);
register_device_value(TAG_DEVICE_DATA_WW, &wwTemp_3_, DeviceValueType::UINT, nullptr, FL_(wwTemp3), DeviceValueUOM::DEGREES);
register_device_value(TAG_DEVICE_DATA_WW, &wwTemp_4_, DeviceValueType::UINT, nullptr, FL_(wwTemp4), DeviceValueUOM::DEGREES);
register_device_value(TAG_DEVICE_DATA_WW, &wwTemp_5_, DeviceValueType::UINT, nullptr, FL_(wwTemp5), DeviceValueUOM::DEGREES);
register_device_value(TAG_DEVICE_DATA_WW, &wwTemp_7_, DeviceValueType::UINT, nullptr, FL_(wwTemp7), DeviceValueUOM::DEGREES);
register_device_value(TAG_DEVICE_DATA_WW, &wwPump_, DeviceValueType::UINT, nullptr, FL_(wwPump), DeviceValueUOM::DEGREES);
return;
} }
register_device_value(TAG_NONE, &id_, DeviceValueType::UINT, nullptr, F("id"), nullptr); // empty full name to prevent being shown in web or console register_device_value(TAG_NONE, &id_, DeviceValueType::UINT, nullptr, FL_(ID), DeviceValueUOM::NONE);
id_ = product_id; id_ = product_id;
register_device_value(TAG_NONE, &collectorTemp_, DeviceValueType::SHORT, FL_(div10), F("collectorTemp"), F("collector temperature (TS1)"), DeviceValueUOM::DEGREES); register_device_value(TAG_NONE, &collectorTemp_, DeviceValueType::SHORT, FL_(div10), FL_(collectorTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_NONE, &tankBottomTemp_, DeviceValueType::SHORT, FL_(div10), F("tankBottomTemp"), F("tank bottom temperature (TS2)"), DeviceValueUOM::DEGREES); register_device_value(TAG_NONE, &tankBottomTemp_, DeviceValueType::SHORT, FL_(div10), FL_(tankBottomTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_NONE, &tankBottomTemp2_, DeviceValueType::SHORT, FL_(div10), F("tank2BottomTemp"), F("second tank bottom temperature (TS5)"), DeviceValueUOM::DEGREES); register_device_value(TAG_NONE, &tankBottomTemp2_, DeviceValueType::SHORT, FL_(div10), FL_(tank2BottomTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_NONE, &heatExchangerTemp_, DeviceValueType::SHORT, FL_(div10), F("heatExchangerTemp"), F("heat exchanger temperature (TS6)"), DeviceValueUOM::DEGREES); register_device_value(TAG_NONE, &heatExchangerTemp_, DeviceValueType::SHORT, FL_(div10), FL_(heatExchangerTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_NONE, &tankBottomMaxTemp_, DeviceValueType::UINT, nullptr, F("tank1MaxTempCurrent"), F("maximum tank temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_NONE, &tankBottomMaxTemp_, DeviceValueType::UINT, nullptr, FL_(tankMaxTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_SM100TankBottomMaxTemp));
register_device_value(TAG_NONE, &solarPumpModulation_, DeviceValueType::UINT, nullptr, F("solarPumpModulation"), F("pump modulation (PS1)"), DeviceValueUOM::PERCENT); register_device_value(TAG_NONE, &solarPumpModulation_, DeviceValueType::UINT, nullptr, FL_(solarPumpModulation), DeviceValueUOM::PERCENT);
register_device_value(TAG_NONE, &cylinderPumpModulation_, DeviceValueType::UINT, nullptr, F("cylinderPumpModulation"), F("cylinder pump modulation (PS5)"), DeviceValueUOM::PERCENT); register_device_value(TAG_NONE, &cylinderPumpModulation_, DeviceValueType::UINT, nullptr, FL_(cylinderPumpModulation), DeviceValueUOM::PERCENT);
register_device_value(TAG_NONE, &solarPump_, DeviceValueType::BOOL, nullptr, F("solarPump"), F("pump (PS1)"), DeviceValueUOM::PUMP); register_device_value(TAG_NONE, &solarPump_, DeviceValueType::BOOL, nullptr, FL_(solarPump), DeviceValueUOM::PUMP);
register_device_value(TAG_NONE, &valveStatus_, DeviceValueType::BOOL, nullptr, F("valveStatus"), F("valve status")); register_device_value(TAG_NONE, &valveStatus_, DeviceValueType::BOOL, nullptr, FL_(valveStatus), DeviceValueUOM::NONE);
register_device_value(TAG_NONE, &tankHeated_, DeviceValueType::BOOL, nullptr, F("tankHeated"), F("tank heated")); register_device_value(TAG_NONE, &tankHeated_, DeviceValueType::BOOL, nullptr, FL_(tankHeated), DeviceValueUOM::NONE);
register_device_value(TAG_NONE, &collectorShutdown_, DeviceValueType::BOOL, nullptr, F("collectorShutdown"), F("collector shutdown")); register_device_value(TAG_NONE, &collectorShutdown_, DeviceValueType::BOOL, nullptr, FL_(collectorShutdown), DeviceValueUOM::NONE);
register_device_value(TAG_NONE, &pumpWorkTime_, DeviceValueType::TIME, nullptr, F("pumpWorkTime"), F("pump working time"), DeviceValueUOM::MINUTES); register_device_value(TAG_NONE, &pumpWorkTime_, DeviceValueType::TIME, nullptr, FL_(pumpWorkTime), DeviceValueUOM::MINUTES);
register_device_value(TAG_NONE, &energyLastHour_, DeviceValueType::ULONG, FL_(div10), F("energyLastHour"), F("energy last hour"), DeviceValueUOM::WH); register_device_value(TAG_NONE, &energyLastHour_, DeviceValueType::ULONG, FL_(div10), FL_(energyLastHour), DeviceValueUOM::WH);
register_device_value(TAG_NONE, &energyTotal_, DeviceValueType::ULONG, FL_(div10), F("energyTotal"), F("energy total"), DeviceValueUOM::KWH); register_device_value(TAG_NONE, &energyTotal_, DeviceValueType::ULONG, FL_(div10), FL_(energyTotal), DeviceValueUOM::KWH);
register_device_value(TAG_NONE, &energyToday_, DeviceValueType::ULONG, nullptr, F("energyToday"), F("energy today"), DeviceValueUOM::WH); register_device_value(TAG_NONE, &energyToday_, DeviceValueType::ULONG, nullptr, FL_(energyToday), DeviceValueUOM::WH);
} }
// publish HA config // publish HA config
@@ -112,7 +117,7 @@ bool Solar::publish_ha_config() {
ids.add("ems-esp-solar"); ids.add("ems-esp-solar");
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/solar/config"), Mqtt::base().c_str()); snprintf_P(topic, sizeof(topic), PSTR("sensor/%s/solar/config"), Mqtt::base().c_str());
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
return true; return true;
@@ -203,6 +208,22 @@ void Solar::process_SM100Monitor(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(heatExchangerTemp_, 20)); // is *10 - TS6: Heat exchanger temperature sensor has_update(telegram->read_value(heatExchangerTemp_, 20)); // is *10 - TS6: Heat exchanger temperature sensor
} }
// SM100wwTemperatur - 0x07D6
// Solar Module(0x2A) -> (0x00), (0x7D6), data: 01 C1 00 00 02 5B 01 AF 01 AD 80 00 01 90
void Solar::process_SM100wwTemperature(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(wwTemp_1_, 0));
has_update(telegram->read_value(wwTemp_3_, 4));
has_update(telegram->read_value(wwTemp_4_, 6));
has_update(telegram->read_value(wwTemp_5_, 8));
has_update(telegram->read_value(wwTemp_7_, 12));
}
// SM100wwStatus - 0x07AA
// Solar Module(0x2A) -> (0x00), (0x7AA), data: 64 00 04 00 03 00 28 01 0F
void Solar::process_SM100wwStatus(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(wwPump_, 0));
}
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wunused-parameter"
@@ -212,22 +233,6 @@ void Solar::process_SM100Monitor2(std::shared_ptr<const Telegram> telegram) {
// not implemented yet // not implemented yet
} }
// SM100wwTemperatur - 0x07D6
// Solar Module(0x2A) -> (0x00), (0x7D6), data: 01 C1 00 00 02 5B 01 AF 01 AD 80 00 01 90
void Solar::process_SM100wwTemperature(std::shared_ptr<const Telegram> telegram) {
// has_update(telegram->read_value(wwTemp_1_, 0));
// has_update(telegram->read_value(wwTemp_3_, 4));
// has_update(telegram->read_value(wwTemp_4_, 6));
// has_update(telegram->read_value(wwTemp_5_, 8));
// has_update(telegram->read_value(wwTemp_7_, 12));
}
// SM100wwStatus - 0x07AA
// Solar Module(0x2A) -> (0x00), (0x7AA), data: 64 00 04 00 03 00 28 01 0F
void Solar::process_SM100wwStatus(std::shared_ptr<const Telegram> telegram) {
// has_update(telegram->read_value(wwPump_, 0));
}
// SM100wwCommand - 0x07AB // SM100wwCommand - 0x07AB
// Thermostat(0x10) -> Solar Module(0x2A), (0x7AB), data: 01 00 01 // Thermostat(0x10) -> Solar Module(0x2A), (0x7AB), data: 01 00 01
void Solar::process_SM100wwCommand(std::shared_ptr<const Telegram> telegram) { void Solar::process_SM100wwCommand(std::shared_ptr<const Telegram> telegram) {

View File

@@ -76,6 +76,16 @@ class Solar : public EMSdevice {
uint16_t collector1Area_; // Area of collector field 1 uint16_t collector1Area_; // Area of collector field 1
uint8_t collector1Type_; // Type of collector field 1, 01=flat, 02=vacuum uint8_t collector1Type_; // Type of collector field 1, 01=flat, 02=vacuum
// SM100wwTemperature - 0x07D6
uint8_t wwTemp_1_;
uint8_t wwTemp_3_;
uint8_t wwTemp_4_;
uint8_t wwTemp_5_;
uint8_t wwTemp_7_;
// SM100wwStatus - 0x07AA
uint8_t wwPump_;
char type_[20]; // Solar of WWC char type_[20]; // Solar of WWC
uint8_t id_; uint8_t id_;

View File

@@ -34,11 +34,10 @@ Switch::Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const
register_telegram_type(0x9D, F("WM10SetMessage"), false, MAKE_PF_CB(process_WM10SetMessage)); register_telegram_type(0x9D, F("WM10SetMessage"), false, MAKE_PF_CB(process_WM10SetMessage));
register_telegram_type(0x1E, F("WM10TempMessage"), false, MAKE_PF_CB(process_WM10TempMessage)); register_telegram_type(0x1E, F("WM10TempMessage"), false, MAKE_PF_CB(process_WM10TempMessage));
register_device_value(TAG_NONE, &activated_, DeviceValueType::BOOL, nullptr, F("activated"), F("activated")); register_device_value(TAG_NONE, &id_, DeviceValueType::UINT, nullptr, FL_(ID), DeviceValueUOM::NONE);
register_device_value(TAG_NONE, &flowTempHc_, DeviceValueType::USHORT, FL_(div10), F("flowTempHc"), F("flow temperature in assigned hc (TC1)"), DeviceValueUOM::DEGREES); register_device_value(TAG_NONE, &activated_, DeviceValueType::BOOL, nullptr, FL_(activated), DeviceValueUOM::NONE);
register_device_value(TAG_NONE, &status_, DeviceValueType::INT, nullptr, F("status"), F("status")); register_device_value(TAG_NONE, &flowTempHc_, DeviceValueType::USHORT, FL_(div10), FL_(flowTempHc), DeviceValueUOM::DEGREES);
register_device_value(TAG_NONE, &status_, DeviceValueType::INT, nullptr, FL_(status), DeviceValueUOM::NONE);
register_device_value(TAG_NONE, &id_, DeviceValueType::UINT, nullptr, F("id"), nullptr); // empty full name to prevent being shown in web or console
id_ = product_id; id_ = product_id;
} }
@@ -67,7 +66,7 @@ bool Switch::publish_ha_config() {
ids.add("ems-esp-switch"); ids.add("ems-esp-switch");
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/switch/config"), Mqtt::base().c_str()); snprintf_P(topic, sizeof(topic), PSTR("sensor/%s/switch/config"), Mqtt::base().c_str());
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
return true; return true;

View File

@@ -59,7 +59,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
} }
// RC35 // RC35
} else if ((model == EMSdevice::EMS_DEVICE_FLAG_RC35) || (model == EMSdevice::EMS_DEVICE_FLAG_RC30_1)) { } else if ((model == EMSdevice::EMS_DEVICE_FLAG_RC35) || (model == EMSdevice::EMS_DEVICE_FLAG_RC30_N)) {
monitor_typeids = {0x3E, 0x48, 0x52, 0x5C}; monitor_typeids = {0x3E, 0x48, 0x52, 0x5C};
set_typeids = {0x3D, 0x47, 0x51, 0x5B}; set_typeids = {0x3D, 0x47, 0x51, 0x5B};
timer_typeids = {0x3F, 0x49, 0x53, 0x5D}; timer_typeids = {0x3F, 0x49, 0x53, 0x5D};
@@ -84,7 +84,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
register_telegram_type(0xAF, F("RC20Remote"), false, MAKE_PF_CB(process_RC20Remote)); register_telegram_type(0xAF, F("RC20Remote"), false, MAKE_PF_CB(process_RC20Remote));
} }
// RC20 newer // RC20 newer
} else if (model == EMSdevice::EMS_DEVICE_FLAG_RC20_2) { } else if (model == EMSdevice::EMS_DEVICE_FLAG_RC20_N) {
monitor_typeids = {0xAE}; monitor_typeids = {0xAE};
set_typeids = {0xAD}; set_typeids = {0xAD};
if (actual_master_thermostat == device_id) { if (actual_master_thermostat == device_id) {
@@ -110,6 +110,13 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
set_typeids = {}; set_typeids = {};
register_telegram_type(monitor_typeids[0], F("EasyMonitor"), true, MAKE_PF_CB(process_EasyMonitor)); register_telegram_type(monitor_typeids[0], F("EasyMonitor"), true, MAKE_PF_CB(process_EasyMonitor));
} else if (model == EMSdevice::EMS_DEVICE_FLAG_CRF) {
monitor_typeids = {0x02A5, 0x02A6, 0x02A7, 0x02A8};
set_typeids = {};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(monitor_typeids[i], F("CRFMonitor"), true, MAKE_PF_CB(process_CRFMonitor));
}
// RC300/RC100 // RC300/RC100
} else if ((model == EMSdevice::EMS_DEVICE_FLAG_RC300) || (model == EMSdevice::EMS_DEVICE_FLAG_RC100)) { } else if ((model == EMSdevice::EMS_DEVICE_FLAG_RC300) || (model == EMSdevice::EMS_DEVICE_FLAG_RC100)) {
monitor_typeids = {0x02A5, 0x02A6, 0x02A7, 0x02A8}; monitor_typeids = {0x02A5, 0x02A6, 0x02A7, 0x02A8};
@@ -167,8 +174,6 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
// register device values for common values (not heating circuit) // register device values for common values (not heating circuit)
register_device_values(); register_device_values();
add_commands();
// only for for the master-thermostat, go a query all the heating circuits. This is only done once. // only for for the master-thermostat, go a query all the heating circuits. This is only done once.
// The automatic fetch will from now on only update the active heating circuits // The automatic fetch will from now on only update the active heating circuits
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
@@ -182,7 +187,9 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
EMSESP::send_read_request(set_typeids[i], device_id); EMSESP::send_read_request(set_typeids[i], device_id);
} }
EMSESP::send_read_request(EMS_TYPE_RCTime, device_id);
EMSESP::send_read_request(0x12, device_id); // read last error (only published on errors) EMSESP::send_read_request(0x12, device_id); // read last error (only published on errors)
EMSESP::send_read_request(0xA2, device_id); // read errorCode (only published on errors)
} }
// publish HA config // publish HA config
@@ -205,7 +212,7 @@ bool Thermostat::publish_ha_config() {
ids.add("ems-esp-thermostat"); ids.add("ems-esp-thermostat");
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/thermostat/config"), Mqtt::base().c_str()); snprintf_P(topic, sizeof(topic), PSTR("sensor/%s/thermostat/config"), Mqtt::base().c_str());
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
return true; return true;
@@ -292,6 +299,7 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
// not found, search device-id types for remote thermostats // not found, search device-id types for remote thermostats
if (telegram->src >= 0x18 && telegram->src <= 0x1B) { if (telegram->src >= 0x18 && telegram->src <= 0x1B) {
hc_num = telegram->src - 0x17; hc_num = telegram->src - 0x17;
toggle_ = true;
} }
// still didn't recognize it, ignore it // still didn't recognize it, ignore it
@@ -306,12 +314,10 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
return heating_circuit; return heating_circuit;
} }
} }
// register new heatingcircuits only on active monitor telegrams // register new heatingcircuits only on active monitor telegrams
if (!toggle_) { if (!toggle_) {
return nullptr; return nullptr;
} }
/* /*
* at this point we have discovered a new heating circuit * at this point we have discovered a new heating circuit
*/ */
@@ -378,7 +384,7 @@ void Thermostat::register_mqtt_ha_config_hc(uint8_t hc_num) {
doc["~"] = Mqtt::base(); // ems-esp doc["~"] = Mqtt::base(); // ems-esp
char topic_t[Mqtt::MQTT_TOPIC_MAX_SIZE]; char topic_t[Mqtt::MQTT_TOPIC_MAX_SIZE];
if (Mqtt::nested_format()) { if (Mqtt::nested_format() == 1) {
snprintf_P(topic_t, sizeof(topic_t), PSTR("~/%s"), Mqtt::tag_to_topic(EMSdevice::DeviceType::THERMOSTAT, DeviceValueTAG::TAG_NONE).c_str()); snprintf_P(topic_t, sizeof(topic_t), PSTR("~/%s"), Mqtt::tag_to_topic(EMSdevice::DeviceType::THERMOSTAT, DeviceValueTAG::TAG_NONE).c_str());
char mode_str_tpl[40]; char mode_str_tpl[40];
@@ -424,18 +430,13 @@ void Thermostat::register_mqtt_ha_config_hc(uint8_t hc_num) {
ids.add("ems-esp-thermostat"); ids.add("ems-esp-thermostat");
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf_P(topic, sizeof(topic), PSTR("homeassistant/climate/%s/thermostat_hc%d/config"), Mqtt::base().c_str(), hc_num); snprintf_P(topic, sizeof(topic), PSTR("climate/%s/thermostat_hc%d/config"), Mqtt::base().c_str(), hc_num);
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
// enable the a special "thermostat_hc<n>" topic to take both mode strings and floats for each of the heating circuits // enable the a special "thermostat_hc<n>" topic to take both mode strings and floats for each of the heating circuits
std::string topic2(Mqtt::MQTT_TOPIC_MAX_SIZE, '\0'); std::string topic2(Mqtt::MQTT_TOPIC_MAX_SIZE, '\0');
snprintf_P(&topic2[0], topic2.capacity() + 1, PSTR("thermostat_hc%d"), hc_num); snprintf_P(&topic2[0], topic2.capacity() + 1, PSTR("thermostat_hc%d"), hc_num);
register_mqtt_topic(topic2, [=](const char * m) { return thermostat_ha_cmd(m, hc_num); }); register_mqtt_topic(topic2, [=](const char * m) { return thermostat_ha_cmd(m, hc_num); });
char hc_name[10]; // hc{1-4}
strlcpy(hc_name, "hc", 10);
char s[3];
strlcat(hc_name, Helpers::itoa(s, hc_num), 10);
} }
// for HA specifically when receiving over MQTT in the thermostat topic // for HA specifically when receiving over MQTT in the thermostat topic
@@ -475,6 +476,12 @@ uint8_t Thermostat::HeatingCircuit::get_mode() const {
} else if (mode == 2) { } else if (mode == 2) {
return HeatingCircuit::Mode::AUTO; return HeatingCircuit::Mode::AUTO;
} }
} else if (model == EMSdevice::EMS_DEVICE_FLAG_CRF) {
if (mode == 0) {
return HeatingCircuit::Mode::AUTO;
} else if (mode == 1) {
return HeatingCircuit::Mode::OFF;
}
} else if ((model == EMSdevice::EMS_DEVICE_FLAG_RC300) || (model == EMSdevice::EMS_DEVICE_FLAG_RC100)) { } else if ((model == EMSdevice::EMS_DEVICE_FLAG_RC300) || (model == EMSdevice::EMS_DEVICE_FLAG_RC100)) {
if (mode == 0) { if (mode == 0) {
return HeatingCircuit::Mode::MANUAL; return HeatingCircuit::Mode::MANUAL;
@@ -515,12 +522,18 @@ uint8_t Thermostat::HeatingCircuit::get_mode_type() const {
} else if (modetype == 1) { } else if (modetype == 1) {
return HeatingCircuit::Mode::NOFROST; return HeatingCircuit::Mode::NOFROST;
} }
} else if ((model == EMS_DEVICE_FLAG_RC35) || (model == EMS_DEVICE_FLAG_RC30_1)) { } else if ((model == EMS_DEVICE_FLAG_RC35) || (model == EMS_DEVICE_FLAG_RC30_N)) {
if (modetype == 0) { if (modetype == 0) {
return HeatingCircuit::Mode::NIGHT; return HeatingCircuit::Mode::NIGHT;
} else if (modetype == 1) { } else if (modetype == 1) {
return HeatingCircuit::Mode::DAY; return HeatingCircuit::Mode::DAY;
} }
} else if (model == EMS_DEVICE_FLAG_CRF) {
if (modetype == 0) {
return HeatingCircuit::Mode::OFF;
} else if (modetype == 1) {
return HeatingCircuit::Mode::ON;
}
} else if (model == EMS_DEVICE_FLAG_RC300) { } else if (model == EMS_DEVICE_FLAG_RC300) {
if (modetype == 0) { if (modetype == 0) {
return HeatingCircuit::Mode::ECO; return HeatingCircuit::Mode::ECO;
@@ -539,59 +552,65 @@ uint8_t Thermostat::HeatingCircuit::get_mode_type() const {
std::string Thermostat::mode_tostring(uint8_t mode) { std::string Thermostat::mode_tostring(uint8_t mode) {
switch (mode) { switch (mode) {
case HeatingCircuit::Mode::OFF: case HeatingCircuit::Mode::OFF:
return read_flash_string(F("off")); return read_flash_string(F_(off));
break; break;
case HeatingCircuit::Mode::MANUAL: case HeatingCircuit::Mode::MANUAL:
return read_flash_string(F("manual")); return read_flash_string(F_(manual));
break; break;
case HeatingCircuit::Mode::DAY: case HeatingCircuit::Mode::DAY:
return read_flash_string(F("day")); return read_flash_string(F_(day));
break; break;
case HeatingCircuit::Mode::NIGHT: case HeatingCircuit::Mode::NIGHT:
return read_flash_string(F("night")); return read_flash_string(F_(night));
break; break;
case HeatingCircuit::Mode::ECO: case HeatingCircuit::Mode::ECO:
return read_flash_string(F("eco")); return read_flash_string(F_(eco));
break; break;
case HeatingCircuit::Mode::COMFORT: case HeatingCircuit::Mode::COMFORT:
return read_flash_string(F("comfort")); return read_flash_string(F_(comfort));
break; break;
case HeatingCircuit::Mode::HEAT: case HeatingCircuit::Mode::HEAT:
return read_flash_string(F("heat")); return read_flash_string(F_(heat));
break; break;
case HeatingCircuit::Mode::HOLIDAY: case HeatingCircuit::Mode::HOLIDAY:
return read_flash_string(F("holiday")); return read_flash_string(F_(holiday));
break; break;
case HeatingCircuit::Mode::NOFROST: case HeatingCircuit::Mode::NOFROST:
return read_flash_string(F("nofrost")); return read_flash_string(F_(nofrost));
break; break;
case HeatingCircuit::Mode::AUTO: case HeatingCircuit::Mode::AUTO:
return read_flash_string(F("auto")); return read_flash_string(F_(auto));
break; break;
case HeatingCircuit::Mode::SUMMER: case HeatingCircuit::Mode::SUMMER:
return read_flash_string(F("summer")); return read_flash_string(F_(summer));
break; break;
case HeatingCircuit::Mode::OFFSET: case HeatingCircuit::Mode::OFFSET:
return read_flash_string(F("offset")); return read_flash_string(F_(offset));
break; break;
case HeatingCircuit::Mode::DESIGN: case HeatingCircuit::Mode::DESIGN:
return read_flash_string(F("design")); return read_flash_string(F_(design));
break; break;
case HeatingCircuit::Mode::MINFLOW: case HeatingCircuit::Mode::MINFLOW:
return read_flash_string(F("minflow")); return read_flash_string(F_(minflow));
break; break;
case HeatingCircuit::Mode::MAXFLOW: case HeatingCircuit::Mode::MAXFLOW:
return read_flash_string(F("maxflow")); return read_flash_string(F_(maxflow));
break; break;
case HeatingCircuit::Mode::ROOMINFLUENCE: case HeatingCircuit::Mode::ROOMINFLUENCE:
return read_flash_string(F("roominfluence")); return read_flash_string(F_(roominfluence[0]));
break; break;
case HeatingCircuit::Mode::FLOWOFFSET: case HeatingCircuit::Mode::FLOWOFFSET:
return read_flash_string(F("flowtempoffset")); return read_flash_string(F_(flowtempoffset[0]));
break;
case HeatingCircuit::Mode::TEMPAUTO:
return read_flash_string(F_(tempauto));
break;
case HeatingCircuit::Mode::NOREDUCE:
return read_flash_string(F_(noreduce));
break; break;
default: default:
case HeatingCircuit::Mode::UNKNOWN: case HeatingCircuit::Mode::UNKNOWN:
return read_flash_string(F("unknown")); return read_flash_string(F_(unknown));
break; break;
} }
} }
@@ -751,6 +770,19 @@ void Thermostat::process_JunkersMonitor(std::shared_ptr<const Telegram> telegram
has_update(telegram->read_value(hc->mode, 1)); // 1 = manual, 2 = auto has_update(telegram->read_value(hc->mode, 1)); // 1 = manual, 2 = auto
} }
// type 0x02A5 - data from Worchester CRF200
void Thermostat::process_CRFMonitor(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
has_update(telegram->read_value(hc->curr_roomTemp, 0)); // is * 10
has_update(telegram->read_bitvalue(hc->modetype, 2, 0));
has_update(telegram->read_bitvalue(hc->mode, 2, 4)); // bit 4, mode (auto=0 or off=1)
has_update(telegram->read_value(hc->setpoint_roomTemp, 6, 1)); // is * 2, force as single byte
has_update(telegram->read_value(hc->targetflowtemp, 4));
}
// type 0x02A5 - data from the Nefit RC1010/3000 thermostat (0x18) and RC300/310s on 0x10 // type 0x02A5 - data from the Nefit RC1010/3000 thermostat (0x18) and RC300/310s on 0x10
void Thermostat::process_RC300Monitor(std::shared_ptr<const Telegram> telegram) { void Thermostat::process_RC300Monitor(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
@@ -794,6 +826,7 @@ void Thermostat::process_RC300Set(std::shared_ptr<const Telegram> telegram) {
// has_update(telegram->read_value(hc->mode, 0); // Auto = xFF, Manual = x00 eg. 10 00 FF 08 01 B9 FF // has_update(telegram->read_value(hc->mode, 0); // Auto = xFF, Manual = x00 eg. 10 00 FF 08 01 B9 FF
has_update(telegram->read_value(hc->daytemp, 2)); // is * 2 has_update(telegram->read_value(hc->daytemp, 2)); // is * 2
has_update(telegram->read_value(hc->nighttemp, 4)); // is * 2 has_update(telegram->read_value(hc->nighttemp, 4)); // is * 2
has_update(telegram->read_value(hc->tempautotemp, 8));
has_update(telegram->read_value(hc->manualtemp, 10)); // is * 2 has_update(telegram->read_value(hc->manualtemp, 10)); // is * 2
has_update(telegram->read_value(hc->program, 11)); // timer program 1 or 2 has_update(telegram->read_value(hc->program, 11)); // timer program 1 or 2
} }
@@ -957,8 +990,10 @@ void Thermostat::process_RC35Set(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(hc->nofrosttemp, 23)); // is * 1 has_update(telegram->read_value(hc->nofrosttemp, 23)); // is * 1
has_update(telegram->read_value(hc->flowtempoffset, 24)); // is * 1, only in mixed circuits has_update(telegram->read_value(hc->flowtempoffset, 24)); // is * 1, only in mixed circuits
has_update(telegram->read_value(hc->reducemode, 25)); // 0-nofrost, 1-reduce, 2-roomhold, 3-outdoorhold has_update(telegram->read_value(hc->reducemode, 25)); // 0-nofrost, 1-reduce, 2-roomhold, 3-outdoorhold
has_update(telegram->read_value(hc->control, 26)); // 0-off, 1-RC20 (remote), 2-RC35
has_update(telegram->read_value(hc->controlmode, 33)); // 0-outdoortemp, 1-roomtemp has_update(telegram->read_value(hc->controlmode, 33)); // 0-outdoortemp, 1-roomtemp
// has_update(telegram->read_value(hc->noreducetemp, 38)); // outdoor temperature for no reduce has_update(telegram->read_value(hc->tempautotemp, 37));
has_update(telegram->read_value(hc->noreducetemp, 38)); // outdoor temperature for no reduce
has_update(telegram->read_value(hc->minflowtemp, 16)); has_update(telegram->read_value(hc->minflowtemp, 16));
if (hc->heatingtype == 3) { if (hc->heatingtype == 3) {
has_update(telegram->read_value(hc->designtemp, 36)); // is * 1 has_update(telegram->read_value(hc->designtemp, 36)); // is * 1
@@ -977,6 +1012,8 @@ void Thermostat::process_RC35Timer(std::shared_ptr<const Telegram> telegram) {
} }
has_update(telegram->read_value(hc->program, 84)); // 0 .. 10, 0-userprogram 1, 10-userprogram 2 has_update(telegram->read_value(hc->program, 84)); // 0 .. 10, 0-userprogram 1, 10-userprogram 2
has_update(telegram->read_value(hc->pause, 85)); // time in hours
has_update(telegram->read_value(hc->party, 86)); // time in hours
} }
// process_RCTime - type 0x06 - date and time from a thermostat - 14 bytes long // process_RCTime - type 0x06 - date and time from a thermostat - 14 bytes long
@@ -1135,10 +1172,11 @@ bool Thermostat::set_remotetemp(const char * value, const int8_t id) {
} }
if (f > 100 || f < 0) { if (f > 100 || f < 0) {
Roomctrl::set_remotetemp(hc->hc_num() - 1, EMS_VALUE_SHORT_NOTSET); hc->remotetemp = EMS_VALUE_SHORT_NOTSET;
} else { } else {
Roomctrl::set_remotetemp(hc->hc_num() - 1, (int16_t)(f * 10)); hc->remotetemp = (int16_t)(f * 10);
} }
Roomctrl::set_remotetemp(hc->hc_num() - 1, hc->remotetemp);
return true; return true;
} }
@@ -1146,26 +1184,27 @@ bool Thermostat::set_remotetemp(const char * value, const int8_t id) {
// 0xA5 - Set the building settings // 0xA5 - Set the building settings
bool Thermostat::set_building(const char * value, const int8_t id) { bool Thermostat::set_building(const char * value, const int8_t id) {
uint8_t bd = 0; uint8_t bd = 0;
if (!Helpers::value2enum(value, bd, {F("light"), F("medium"), F("heavy")})) { if ((model() == EMS_DEVICE_FLAG_RC300) || (model() == EMS_DEVICE_FLAG_RC100)) {
if (Helpers::value2enum(value, bd, FL_(enum_ibaBuildingType))) {
LOG_INFO(F("Setting building to %s"), value);
write_command(0x240, 9, bd, 0x240);
return true;
}
} else {
if (Helpers::value2enum(value, bd, FL_(enum_ibaBuildingType2))) {
LOG_INFO(F("Setting building to %s"), value);
write_command(EMS_TYPE_IBASettings, 6, bd, EMS_TYPE_IBASettings);
return true;
}
}
LOG_WARNING(F("Set building: Invalid value")); LOG_WARNING(F("Set building: Invalid value"));
return false; return false;
} }
LOG_INFO(F("Setting building to %s"), value);
if ((model() == EMS_DEVICE_FLAG_RC300) || (model() == EMS_DEVICE_FLAG_RC100)) {
write_command(0x240, 9, bd + 1, 0x240);
} else {
write_command(EMS_TYPE_IBASettings, 6, bd, EMS_TYPE_IBASettings);
}
return true;
}
// 0xA5 Set the language settings // 0xA5 Set the language settings
bool Thermostat::set_language(const char * value, const int8_t id) { bool Thermostat::set_language(const char * value, const int8_t id) {
uint8_t lg = 0; uint8_t lg = 0;
if (!Helpers::value2enum(value, lg, {F("german"), F("dutch"), F("french"), F("italian")})) { if (!Helpers::value2enum(value, lg, FL_(enum_ibaLanguage))) {
LOG_WARNING(F("Set language: Invalid value")); LOG_WARNING(F("Set language: Invalid value"));
return false; return false;
} }
@@ -1179,7 +1218,7 @@ bool Thermostat::set_language(const char * value, const int8_t id) {
// Set the control-mode for hc 0-off, 1-RC20, 2-RC3x // Set the control-mode for hc 0-off, 1-RC20, 2-RC3x
bool Thermostat::set_control(const char * value, const int8_t id) { bool Thermostat::set_control(const char * value, const int8_t id) {
uint8_t ctrl = 0; uint8_t ctrl = 0;
if (!Helpers::value2enum(value, ctrl, {F("off"), F("rc20"), F("rc3x")})) { if (!Helpers::value2enum(value, ctrl, FL_(enum_control))) {
LOG_WARNING(F("Set control: Invalid value")); LOG_WARNING(F("Set control: Invalid value"));
return false; return false;
} }
@@ -1201,14 +1240,14 @@ bool Thermostat::set_wwmode(const char * value, const int8_t id) {
uint8_t set = 0xFF; uint8_t set = 0xFF;
if ((model() == EMS_DEVICE_FLAG_RC300) || (model() == EMS_DEVICE_FLAG_RC100)) { if ((model() == EMS_DEVICE_FLAG_RC300) || (model() == EMS_DEVICE_FLAG_RC100)) {
if (!Helpers::value2enum(value, set, {F("off"), F("low"), F("high"), F("auto"), F("own")})) { if (!Helpers::value2enum(value, set, FL_(enum_wwMode))) {
LOG_WARNING(F("Set warm water mode: Invalid mode")); LOG_WARNING(F("Set warm water mode: Invalid mode"));
return false; return false;
} }
LOG_INFO(F("Setting warm water mode to %s"), value); LOG_INFO(F("Setting warm water mode to %s"), value);
write_command(0x02F5, 2, set, 0x02F5); write_command(0x02F5, 2, set, 0x02F5);
} else { } else {
if (!Helpers::value2enum(value, set, {F("off"), F("on"), F("auto")})) { if (!Helpers::value2enum(value, set, FL_(enum_wwMode2))) {
LOG_WARNING(F("Set warm water mode: Invalid mode")); LOG_WARNING(F("Set warm water mode: Invalid mode"));
return false; return false;
} }
@@ -1260,7 +1299,7 @@ bool Thermostat::set_wwcircmode(const char * value, const int8_t id) {
uint8_t set = 0xFF; uint8_t set = 0xFF;
if ((model() == EMS_DEVICE_FLAG_RC300) || (model() == EMS_DEVICE_FLAG_RC100)) { if ((model() == EMS_DEVICE_FLAG_RC300) || (model() == EMS_DEVICE_FLAG_RC100)) {
if (!Helpers::value2enum(value, set, {F("off"), F("on"), F("auto"), F("own")})) { if (!Helpers::value2enum(value, set, FL_(enum_wwCircMode))) {
LOG_WARNING(F("Set warm water circulation mode: Invalid mode")); LOG_WARNING(F("Set warm water circulation mode: Invalid mode"));
return false; return false;
} }
@@ -1268,7 +1307,7 @@ bool Thermostat::set_wwcircmode(const char * value, const int8_t id) {
write_command(0x02F5, 3, set, 0x02F5); write_command(0x02F5, 3, set, 0x02F5);
return true; return true;
} }
if (!Helpers::value2enum(value, set, {F("off"), F("on"), F("auto")})) { if (!Helpers::value2enum(value, set, FL_(enum_wwCircMode2))) {
LOG_WARNING(F("Set warm water circulation mode: Invalid mode")); LOG_WARNING(F("Set warm water circulation mode: Invalid mode"));
return false; return false;
} }
@@ -1279,34 +1318,39 @@ bool Thermostat::set_wwcircmode(const char * value, const int8_t id) {
// set the holiday as string dd.mm.yyyy-dd.mm.yyyy // set the holiday as string dd.mm.yyyy-dd.mm.yyyy
bool Thermostat::set_holiday(const char * value, const int8_t id) { bool Thermostat::set_holiday(const char * value, const int8_t id) {
std::string hd(30, '\0');
if (!Helpers::value2string(value, hd)) {
LOG_WARNING(F("Set holiday: Invalid value"));
return false;
}
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id;
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num);
if (hc == nullptr) { if (hc == nullptr) {
LOG_WARNING(F("Set holiday: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, device_id()); LOG_WARNING(F("Set holiday: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, device_id());
return false; return false;
} }
if (value == nullptr || value[0] == '-') {
read_command(timer_typeids[hc->hc_num() - 1], 87, 6);
return true;
} else if (strlen(value) == 1 && value[0] == '+') {
read_command(timer_typeids[hc->hc_num() - 1], 93, 6);
return true;
} else if (strlen(value) != 21) {
LOG_WARNING(F("Set holiday: Invalid value"));
return false;
}
uint8_t data[6]; uint8_t data[6];
data[0] = (hd[0] - '0') * 10 + (hd[1] - '0'); data[0] = (value[0] - '0') * 10 + (value[1] - '0');
data[1] = (hd[3] - '0') * 10 + (hd[4] - '0'); data[1] = (value[3] - '0') * 10 + (value[4] - '0');
data[2] = (hd[7] - '0') * 100 + (hd[8] - '0') * 10 + (hd[9] - '0'); data[2] = (value[7] - '0') * 100 + (value[8] - '0') * 10 + (value[9] - '0');
data[3] = (hd[11] - '0') * 10 + (hd[12] - '0'); data[3] = (value[11] - '0') * 10 + (value[12] - '0');
data[4] = (hd[14] - '0') * 10 + (hd[15] - '0'); data[4] = (value[14] - '0') * 10 + (value[15] - '0');
data[5] = (hd[18] - '0') * 100 + (hd[19] - '0') * 10 + (hd[20] - '0'); data[5] = (value[18] - '0') * 100 + (value[19] - '0') * 10 + (value[20] - '0');
if (hd[10] == '-') { if (value[10] == '-') {
LOG_INFO(F("Setting holiday away from home for hc %d"), hc->hc_num()); LOG_INFO(F("Setting holiday away from home for hc %d"), hc->hc_num());
write_command(timer_typeids[hc->hc_num() - 1], 87, data, 6, 0); write_command(timer_typeids[hc->hc_num() - 1], 87, data, 6, 0);
} else if (hd[10] == '+') { } else if (value[10] == '+') {
LOG_INFO(F("Setting holiday at home for hc %d"), hc->hc_num()); LOG_INFO(F("Setting holiday at home for hc %d"), hc->hc_num());
write_command(timer_typeids[hc->hc_num() - 1], 93, data, 6, 0); write_command(timer_typeids[hc->hc_num() - 1], 93, data, 6, 0);
} else { } else {
LOG_WARNING(F("Set holiday: Invalid")); LOG_WARNING(F("Set holiday: Invalid value"));
return false; return false;
} }
@@ -1315,18 +1359,21 @@ bool Thermostat::set_holiday(const char * value, const int8_t id) {
// set pause in hours // set pause in hours
bool Thermostat::set_pause(const char * value, const int8_t id) { bool Thermostat::set_pause(const char * value, const int8_t id) {
int hrs = 0;
if (!Helpers::value2number(value, hrs)) {
LOG_WARNING(F("Set pause: Invalid value"));
return false;
}
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id;
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num);
if (hc == nullptr) { if (hc == nullptr) {
LOG_WARNING(F("Set pause: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, device_id()); LOG_WARNING(F("Set pause: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, device_id());
return false; return false;
} }
if (value == nullptr) {
read_command(timer_typeids[hc->hc_num() - 1], 85, 1);
return true;
}
int hrs = 0;
if (!Helpers::value2number(value, hrs)) {
LOG_WARNING(F("Set pause: Invalid value"));
return false;
}
LOG_INFO(F("Setting pause: %d hours, hc: %d"), hrs, hc->hc_num()); LOG_INFO(F("Setting pause: %d hours, hc: %d"), hrs, hc->hc_num());
write_command(timer_typeids[hc->hc_num() - 1], 85, hrs); write_command(timer_typeids[hc->hc_num() - 1], 85, hrs);
@@ -1336,18 +1383,21 @@ bool Thermostat::set_pause(const char * value, const int8_t id) {
// set partymode in hours // set partymode in hours
bool Thermostat::set_party(const char * value, const int8_t id) { bool Thermostat::set_party(const char * value, const int8_t id) {
int hrs = 0;
if (!Helpers::value2number(value, hrs)) {
LOG_WARNING(F("Set party: Invalid value"));
return false;
}
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id;
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num);
if (hc == nullptr) { if (hc == nullptr) {
LOG_WARNING(F("Set party: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, device_id()); LOG_WARNING(F("Set party: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, device_id());
return false; return false;
} }
if (value == nullptr) {
read_command(timer_typeids[hc->hc_num() - 1], 85, 1);
return true;
}
int hrs = 0;
if (!Helpers::value2number(value, hrs)) {
LOG_WARNING(F("Set party: Invalid value"));
return false;
}
LOG_INFO(F("Setting party: %d hours, hc: %d"), hrs, hc->hc_num()); LOG_INFO(F("Setting party: %d hours, hc: %d"), hrs, hc->hc_num());
write_command(timer_typeids[hc->hc_num() - 1], 86, hrs); write_command(timer_typeids[hc->hc_num() - 1], 86, hrs);
@@ -1492,7 +1542,7 @@ bool Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) {
offset = EMS_OFFSET_RC20Set_mode; offset = EMS_OFFSET_RC20Set_mode;
validate_typeid = set_typeids[hc_p]; validate_typeid = set_typeids[hc_p];
break; break;
case EMSdevice::EMS_DEVICE_FLAG_RC20_2: // ES72 case EMSdevice::EMS_DEVICE_FLAG_RC20_N: // ES72
offset = EMS_OFFSET_RC20_2_Set_mode; offset = EMS_OFFSET_RC20_2_Set_mode;
validate_typeid = set_typeids[hc_p]; validate_typeid = set_typeids[hc_p];
break; break;
@@ -1501,7 +1551,7 @@ bool Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) {
validate_typeid = set_typeids[hc_p]; validate_typeid = set_typeids[hc_p];
break; break;
case EMSdevice::EMS_DEVICE_FLAG_RC35: case EMSdevice::EMS_DEVICE_FLAG_RC35:
case EMSdevice::EMS_DEVICE_FLAG_RC30_1: case EMSdevice::EMS_DEVICE_FLAG_RC30_N:
offset = EMS_OFFSET_RC35Set_mode; offset = EMS_OFFSET_RC35Set_mode;
validate_typeid = set_typeids[hc_p]; validate_typeid = set_typeids[hc_p];
break; break;
@@ -1555,7 +1605,7 @@ bool Thermostat::set_summermode(const char * value, const int8_t id) {
return false; return false;
} }
uint8_t set = 0xFF; uint8_t set = 0xFF;
if (!Helpers::value2enum(value, set, {F("summer"), F("auto"), F("winter")})) { if (!Helpers::value2enum(value, set, FL_(enum_summermode))) {
LOG_WARNING(F("Setting summer mode: Invalid mode")); LOG_WARNING(F("Setting summer mode: Invalid mode"));
return false; return false;
} }
@@ -1573,7 +1623,7 @@ bool Thermostat::set_reducemode(const char * value, const int8_t id) {
return false; return false;
} }
uint8_t set = 0xFF; uint8_t set = 0xFF;
if (!Helpers::value2enum(value, set, {F("nofrost"), F("reduce"), F("room"), F("outdoor")})) { if (!Helpers::value2enum(value, set, FL_(enum_reducemode))) {
LOG_WARNING(F("Setting reduce mode: Invalid mode")); LOG_WARNING(F("Setting reduce mode: Invalid mode"));
return false; return false;
} }
@@ -1592,13 +1642,13 @@ bool Thermostat::set_controlmode(const char * value, const int8_t id) {
} }
uint8_t set = 0xFF; uint8_t set = 0xFF;
if (model() == EMS_DEVICE_FLAG_RC300 || model() == EMS_DEVICE_FLAG_RC100) { if (model() == EMS_DEVICE_FLAG_RC300 || model() == EMS_DEVICE_FLAG_RC100) {
if (Helpers::value2enum(value, set, {F("off"), F("outdoor"), F("simple"), F("MPC"), F("room"), F("power"), F("const")})) { if (Helpers::value2enum(value, set, FL_(enum_controlmode))) {
LOG_INFO(F("Setting control mode to %d for heating circuit %d"), set, hc->hc_num()); LOG_INFO(F("Setting control mode to %d for heating circuit %d"), set, hc->hc_num());
write_command(curve_typeids[hc->hc_num() - 1], 0, set, curve_typeids[hc->hc_num() - 1]); write_command(curve_typeids[hc->hc_num() - 1], 0, set, curve_typeids[hc->hc_num() - 1]);
return true; return true;
} }
} else if (model() == EMS_DEVICE_FLAG_RC35 || model() == EMS_DEVICE_FLAG_RC30_1) { } else if (model() == EMS_DEVICE_FLAG_RC35 || model() == EMS_DEVICE_FLAG_RC30_N) {
if (Helpers::value2enum(value, set, {F("outdoor"), F("room")})) { if (Helpers::value2enum(value, set, FL_(enum_controlmode2))) {
LOG_INFO(F("Setting control mode to %d for heating circuit %d"), set, hc->hc_num()); LOG_INFO(F("Setting control mode to %d for heating circuit %d"), set, hc->hc_num());
write_command(set_typeids[hc->hc_num() - 1], 33, set, set_typeids[hc->hc_num() - 1]); write_command(set_typeids[hc->hc_num() - 1], 33, set, set_typeids[hc->hc_num() - 1]);
return true; return true;
@@ -1617,7 +1667,17 @@ bool Thermostat::set_switchtime(const char * value, const int8_t id) {
LOG_WARNING(F("Setting switchtime: Heating Circuit %d not found or activated"), hc_num); LOG_WARNING(F("Setting switchtime: Heating Circuit %d not found or activated"), hc_num);
return false; return false;
} }
if (strlen(value) != 12) { if (value == nullptr) {
return false;
} else if (strlen(value) == 2) { // query point 01?
uint8_t no = (value[0] - '0') * 10 + (value[1] - '0');
if (no < 42) {
read_command(timer_typeids[hc->hc_num() - 1], 2 * no, 2);
return true;
}
return false;
} else if (strlen(value) != 12) {
LOG_WARNING(F("Setting switchtime: Invalid data")); LOG_WARNING(F("Setting switchtime: Invalid data"));
return false; return false;
} }
@@ -1637,7 +1697,7 @@ bool Thermostat::set_switchtime(const char * value, const int8_t id) {
return false; return false;
} }
if ((model() == EMS_DEVICE_FLAG_RC35 || model() == EMS_DEVICE_FLAG_RC30_1)) { if ((model() == EMS_DEVICE_FLAG_RC35 || model() == EMS_DEVICE_FLAG_RC30_N)) {
write_command(timer_typeids[hc->hc_num() - 1], no * 2, (uint8_t *)&data, 2, timer_typeids[hc->hc_num() - 1]); write_command(timer_typeids[hc->hc_num() - 1], no * 2, (uint8_t *)&data, 2, timer_typeids[hc->hc_num() - 1]);
} else { } else {
LOG_WARNING(F("Setting switchtime: thermostat not supported")); LOG_WARNING(F("Setting switchtime: thermostat not supported"));
@@ -1666,9 +1726,9 @@ bool Thermostat::set_program(const char * value, const int8_t id) {
return false; return false;
} }
if (model() == EMS_DEVICE_FLAG_RC20_2 && set > 0 && set < 10) { if (model() == EMS_DEVICE_FLAG_RC20_N && set > 0 && set < 10) {
write_command(set_typeids[hc->hc_num() - 1], 11, set, set_typeids[hc->hc_num() - 1]); write_command(set_typeids[hc->hc_num() - 1], 11, set, set_typeids[hc->hc_num() - 1]);
} else if ((model() == EMS_DEVICE_FLAG_RC35 || model() == EMS_DEVICE_FLAG_RC30_1) && set < 11) { } else if ((model() == EMS_DEVICE_FLAG_RC35 || model() == EMS_DEVICE_FLAG_RC30_N) && set < 11) {
write_command(timer_typeids[hc->hc_num() - 1], 84, set, timer_typeids[hc->hc_num() - 1]); write_command(timer_typeids[hc->hc_num() - 1], 84, set, timer_typeids[hc->hc_num() - 1]);
} else if ((model() == EMS_DEVICE_FLAG_RC300 || model() == EMS_DEVICE_FLAG_RC100) && (set == 1 || set == 2)) { } else if ((model() == EMS_DEVICE_FLAG_RC300 || model() == EMS_DEVICE_FLAG_RC100) && (set == 1 || set == 2)) {
write_command(set_typeids[hc->hc_num() - 1], 11, set, set_typeids[hc->hc_num() - 1]); write_command(set_typeids[hc->hc_num() - 1], 11, set, set_typeids[hc->hc_num() - 1]);
@@ -1717,6 +1777,12 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
case HeatingCircuit::Mode::MANUAL: case HeatingCircuit::Mode::MANUAL:
offset = 0x0A; // manual offset offset = 0x0A; // manual offset
break; break;
case HeatingCircuit::Mode::TEMPAUTO:
offset = 0x08; // manual offset
if (temperature == -1) {
factor = 0xFF; // use factor as value
}
break;
case HeatingCircuit::Mode::COMFORT: case HeatingCircuit::Mode::COMFORT:
offset = 0x02; // comfort offset level 2 offset = 0x02; // comfort offset level 2
break; break;
@@ -1783,7 +1849,7 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
break; break;
} }
} else if (model == EMS_DEVICE_FLAG_RC20_2) { } else if (model == EMS_DEVICE_FLAG_RC20_N) {
switch (mode) { switch (mode) {
case HeatingCircuit::Mode::NIGHT: // change the night temp case HeatingCircuit::Mode::NIGHT: // change the night temp
offset = EMS_OFFSET_RC20_2_Set_temp_night; offset = EMS_OFFSET_RC20_2_Set_temp_night;
@@ -1798,7 +1864,7 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
break; break;
} }
} else if ((model == EMS_DEVICE_FLAG_RC35) || (model == EMS_DEVICE_FLAG_RC30_1)) { } else if ((model == EMS_DEVICE_FLAG_RC35) || (model == EMS_DEVICE_FLAG_RC30_N)) {
validate_typeid = set_typeids[hc->hc_num() - 1]; validate_typeid = set_typeids[hc->hc_num() - 1];
switch (mode) { switch (mode) {
case HeatingCircuit::Mode::NIGHT: // change the night temp case HeatingCircuit::Mode::NIGHT: // change the night temp
@@ -1837,6 +1903,13 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
offset = 4; offset = 4;
factor = 1; factor = 1;
break; break;
case HeatingCircuit::Mode::NOREDUCE:
offset = EMS_OFFSET_RC35Set_noreducetemp;
factor = 1;
break;
case HeatingCircuit::Mode::TEMPAUTO:
offset = EMS_OFFSET_RC35Set_seltemp;
break;
case HeatingCircuit::Mode::MINFLOW: case HeatingCircuit::Mode::MINFLOW:
offset = 16; offset = 16;
factor = 1; factor = 1;
@@ -2005,6 +2078,14 @@ bool Thermostat::set_manualtemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::MANUAL); return set_temperature_value(value, id, HeatingCircuit::Mode::MANUAL);
} }
bool Thermostat::set_tempautotemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::TEMPAUTO);
}
bool Thermostat::set_noreducetemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::NOREDUCE);
}
bool Thermostat::set_flowtempoffset(const char * value, const int8_t id) { bool Thermostat::set_flowtempoffset(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::FLOWOFFSET); return set_temperature_value(value, id, HeatingCircuit::Mode::FLOWOFFSET);
} }
@@ -2021,142 +2102,64 @@ bool Thermostat::set_roominfluence(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::ROOMINFLUENCE); return set_temperature_value(value, id, HeatingCircuit::Mode::ROOMINFLUENCE);
} }
// API commands for MQTT and Console // register main device values, top level for all thermostats (excluding heating circuits)
void Thermostat::add_commands() { // as these are done in void Thermostat::register_device_values_hc()
// if this thermostat doesn't support write, don't add the commands void Thermostat::register_device_values() {
if (has_flags(EMS_DEVICE_FLAG_NO_WRITE)) { // Common for all thermostats
return; register_device_value(TAG_THERMOSTAT_DATA, &id_, DeviceValueType::UINT, nullptr, FL_(ID), DeviceValueUOM::NONE);
} register_device_value(TAG_THERMOSTAT_DATA, &errorCode_, DeviceValueType::TEXT, nullptr, FL_(errorCode), DeviceValueUOM::NONE);
register_device_value(TAG_THERMOSTAT_DATA, &lastCode_, DeviceValueType::TEXT, nullptr, FL_(lastCode), DeviceValueUOM::NONE);
// common to all thermostats switch (this->model()) {
register_mqtt_cmd(F("temp"), MAKE_CF_CB(set_temp), FLAG_HC);
register_mqtt_cmd(F("mode"), MAKE_CF_CB(set_mode), FLAG_HC);
if (model() == EMS_DEVICE_FLAG_RC35) { // section is together with RC30
register_mqtt_cmd(F("datetime"), MAKE_CF_CB(set_datetime));
}
switch (model()) {
case EMS_DEVICE_FLAG_RC100: case EMS_DEVICE_FLAG_RC100:
case EMS_DEVICE_FLAG_RC300: case EMS_DEVICE_FLAG_RC300:
register_mqtt_cmd(F("datetime"), MAKE_CF_CB(set_datetime)); register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::TEXT, nullptr, FL_(dateTime), DeviceValueUOM::NONE, MAKE_CF_CB(set_datetime));
register_mqtt_cmd(F("manualtemp"), MAKE_CF_CB(set_manualtemp), FLAG_HC); register_device_value(TAG_THERMOSTAT_DATA, &floordrystatus_, DeviceValueType::ENUM, FL_(enum_floordrystatus), FL_(floordrystatus), DeviceValueUOM::NONE);
register_mqtt_cmd(F("ecotemp"), MAKE_CF_CB(set_ecotemp), FLAG_HC); register_device_value(TAG_THERMOSTAT_DATA, &dampedoutdoortemp2_, DeviceValueType::SHORT, FL_(div10), FL_(dampedoutdoortemp), DeviceValueUOM::DEGREES);
register_mqtt_cmd(F("comforttemp"), MAKE_CF_CB(set_comforttemp), FLAG_HC); register_device_value(TAG_THERMOSTAT_DATA, &floordrytemp_, DeviceValueType::UINT, nullptr, FL_(floordrytemp), DeviceValueUOM::DEGREES);
register_mqtt_cmd(F("summermode"), MAKE_CF_CB(set_summermode), FLAG_HC); register_device_value(TAG_THERMOSTAT_DATA, &ibaBuildingType_, DeviceValueType::ENUM, FL_(enum_ibaBuildingType), FL_(ibaBuildingType), DeviceValueUOM::NONE, MAKE_CF_CB(set_building));
register_mqtt_cmd(F("summertemp"), MAKE_CF_CB(set_summertemp), FLAG_HC); register_device_value(TAG_DEVICE_DATA_WW, &wwSetTemp_, DeviceValueType::UINT, nullptr, FL_(wwSetTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwtemp));
register_mqtt_cmd(F("wwmode"), MAKE_CF_CB(set_wwmode)); register_device_value(TAG_DEVICE_DATA_WW, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode));
register_mqtt_cmd(F("wwsettemp"), MAKE_CF_CB(set_wwtemp)); register_device_value(TAG_DEVICE_DATA_WW, &wwSetTempLow_, DeviceValueType::UINT, nullptr, FL_(wwSetTempLow), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwtemplow));
register_mqtt_cmd(F("wwsettemplow"), MAKE_CF_CB(set_wwtemplow)); register_device_value(TAG_DEVICE_DATA_WW, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode), FL_(wWCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwcircmode));
register_mqtt_cmd(F("wwonetime"), MAKE_CF_CB(set_wwonetime)); register_device_value(TAG_DEVICE_DATA_WW, &wwExtra1_, DeviceValueType::UINT, nullptr, FL_(wwExtra1), DeviceValueUOM::DEGREES);
register_mqtt_cmd(F("wwcircmode"), MAKE_CF_CB(set_wwcircmode)); register_device_value(TAG_DEVICE_DATA_WW, &wwExtra2_, DeviceValueType::UINT, nullptr, FL_(wwExtra2), DeviceValueUOM::DEGREES);
register_mqtt_cmd(F("building"), MAKE_CF_CB(set_building));
register_mqtt_cmd(F("nofrosttemp"), MAKE_CF_CB(set_nofrosttemp), FLAG_HC);
register_mqtt_cmd(F("designtemp"), MAKE_CF_CB(set_designtemp), FLAG_HC);
register_mqtt_cmd(F("offsettemp"), MAKE_CF_CB(set_offsettemp), FLAG_HC);
register_mqtt_cmd(F("minflowtemp"), MAKE_CF_CB(set_minflowtemp), FLAG_HC);
register_mqtt_cmd(F("maxflowtemp"), MAKE_CF_CB(set_maxflowtemp), FLAG_HC);
register_mqtt_cmd(F("minexttemp"), MAKE_CF_CB(set_minexttemp));
register_mqtt_cmd(F("roominfluence"), MAKE_CF_CB(set_roominfluence), FLAG_HC);
register_mqtt_cmd(F("program"), MAKE_CF_CB(set_program), FLAG_HC);
register_mqtt_cmd(F("controlmode"), MAKE_CF_CB(set_controlmode), FLAG_HC);
break; break;
case EMS_DEVICE_FLAG_RC20_2: case EMS_DEVICE_FLAG_RC20_N:
register_mqtt_cmd(F("nighttemp"), MAKE_CF_CB(set_nighttemp)); case EMS_DEVICE_FLAG_RC20:
register_mqtt_cmd(F("daytemp"), MAKE_CF_CB(set_daytemp)); register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::TEXT, nullptr, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime
register_mqtt_cmd(F("program"), MAKE_CF_CB(set_program));
break; break;
case EMS_DEVICE_FLAG_RC30_1: // only RC30_1 case EMS_DEVICE_FLAG_RC30_N:
register_mqtt_cmd(F("clockoffset"), MAKE_CF_CB(set_clockoffset)); register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::TEXT, nullptr, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime
register_mqtt_cmd(F("language"), MAKE_CF_CB(set_language)); register_device_value(TAG_THERMOSTAT_DATA, &ibaMainDisplay_, DeviceValueType::ENUM, FL_(enum_ibaMainDisplay), FL_(ibaMainDisplay), DeviceValueUOM::NONE);
register_mqtt_cmd(F("display"), MAKE_CF_CB(set_display)); register_device_value(TAG_THERMOSTAT_DATA, &ibaLanguage_, DeviceValueType::ENUM, FL_(enum_ibaLanguage), FL_(ibaLanguage), DeviceValueUOM::NONE);
case EMS_DEVICE_FLAG_RC35: // RC30 and RC35 register_device_value(TAG_THERMOSTAT_DATA, &ibaClockOffset_, DeviceValueType::UINT, nullptr, FL_(ibaClockOffset), DeviceValueUOM::NONE); // offset (in sec) to clock, 0xff=-1s, 0x02=2s
register_mqtt_cmd(F("nighttemp"), MAKE_CF_CB(set_nighttemp), FLAG_HC); register_device_value(TAG_THERMOSTAT_DATA, &ibaCalIntTemperature_, DeviceValueType::INT, FL_(div2), FL_(ibaCalIntTemperature), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_calinttemp));
register_mqtt_cmd(F("daytemp"), MAKE_CF_CB(set_daytemp), FLAG_HC); register_device_value(TAG_THERMOSTAT_DATA, &ibaMinExtTemperature_, DeviceValueType::INT, nullptr, FL_(ibaMinExtTemperature), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_minexttemp));
register_mqtt_cmd(F("nofrosttemp"), MAKE_CF_CB(set_nofrosttemp), FLAG_HC); register_device_value(TAG_THERMOSTAT_DATA, &dampedoutdoortemp_, DeviceValueType::INT, nullptr, FL_(dampedoutdoortemp), DeviceValueUOM::DEGREES);
register_mqtt_cmd(F("remotetemp"), MAKE_CF_CB(set_remotetemp), FLAG_HC); register_device_value(TAG_THERMOSTAT_DATA, &ibaBuildingType_, DeviceValueType::ENUM, FL_(enum_ibaBuildingType2), FL_(ibaBuildingType), DeviceValueUOM::NONE, MAKE_CF_CB(set_building));
register_mqtt_cmd(F("minexttemp"), MAKE_CF_CB(set_minexttemp)); register_device_value(TAG_DEVICE_DATA_WW, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode2), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode));
register_mqtt_cmd(F("calinttemp"), MAKE_CF_CB(set_calinttemp)); register_device_value(TAG_DEVICE_DATA_WW, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode2), FL_(wWCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwcircmode));
register_mqtt_cmd(F("building"), MAKE_CF_CB(set_building)); break;
register_mqtt_cmd(F("control"), MAKE_CF_CB(set_control), FLAG_HC); case EMS_DEVICE_FLAG_RC35:
register_mqtt_cmd(F("pause"), MAKE_CF_CB(set_pause), FLAG_HC); register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::TEXT, nullptr, FL_(dateTime), DeviceValueUOM::NONE, MAKE_CF_CB(set_datetime));
register_mqtt_cmd(F("party"), MAKE_CF_CB(set_party), FLAG_HC); register_device_value(TAG_THERMOSTAT_DATA, &ibaCalIntTemperature_, DeviceValueType::INT, FL_(div2), FL_(ibaCalIntTemperature), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_calinttemp));
register_mqtt_cmd(F("holiday"), MAKE_CF_CB(set_holiday), FLAG_HC); register_device_value(TAG_THERMOSTAT_DATA, &ibaMinExtTemperature_, DeviceValueType::INT, nullptr, FL_(ibaMinExtTemperature), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_minexttemp));
register_mqtt_cmd(F("summertemp"), MAKE_CF_CB(set_summertemp), FLAG_HC); register_device_value(TAG_THERMOSTAT_DATA, &tempsensor1_, DeviceValueType::USHORT, FL_(div10), FL_(tempsensor1), DeviceValueUOM::DEGREES);
register_mqtt_cmd(F("designtemp"), MAKE_CF_CB(set_designtemp), FLAG_HC); register_device_value(TAG_THERMOSTAT_DATA, &tempsensor2_, DeviceValueType::USHORT, FL_(div10), FL_(tempsensor2), DeviceValueUOM::DEGREES);
register_mqtt_cmd(F("offsettemp"), MAKE_CF_CB(set_offsettemp), FLAG_HC); register_device_value(TAG_THERMOSTAT_DATA, &dampedoutdoortemp_, DeviceValueType::INT, nullptr, FL_(dampedoutdoortemp), DeviceValueUOM::DEGREES);
register_mqtt_cmd(F("holidaytemp"), MAKE_CF_CB(set_holidaytemp), FLAG_HC); register_device_value(TAG_THERMOSTAT_DATA, &ibaBuildingType_, DeviceValueType::ENUM, FL_(enum_ibaBuildingType2), FL_(ibaBuildingType), DeviceValueUOM::NONE, MAKE_CF_CB(set_building));
register_mqtt_cmd(F("wwmode"), MAKE_CF_CB(set_wwmode)); register_device_value(TAG_DEVICE_DATA_WW, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode2), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode));
register_mqtt_cmd(F("wwcircmode"), MAKE_CF_CB(set_wwcircmode)); register_device_value(TAG_DEVICE_DATA_WW, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode2), FL_(wWCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwcircmode));
register_mqtt_cmd(F("roominfluence"), MAKE_CF_CB(set_roominfluence), FLAG_HC);
register_mqtt_cmd(F("flowtempoffset"), MAKE_CF_CB(set_flowtempoffset), FLAG_HC);
register_mqtt_cmd(F("minflowtemp"), MAKE_CF_CB(set_minflowtemp), FLAG_HC);
register_mqtt_cmd(F("maxflowtemp"), MAKE_CF_CB(set_maxflowtemp), FLAG_HC);
register_mqtt_cmd(F("reducemode"), MAKE_CF_CB(set_reducemode), FLAG_HC);
register_mqtt_cmd(F("program"), MAKE_CF_CB(set_program), FLAG_HC);
register_mqtt_cmd(F("switchtime"), MAKE_CF_CB(set_switchtime), FLAG_HC);
register_mqtt_cmd(F("controlmode"), MAKE_CF_CB(set_controlmode), FLAG_HC);
break; break;
case EMS_DEVICE_FLAG_JUNKERS: case EMS_DEVICE_FLAG_JUNKERS:
register_mqtt_cmd(F("datetime"), MAKE_CF_CB(set_datetime)); register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::TEXT, nullptr, FL_(dateTime), DeviceValueUOM::NONE, MAKE_CF_CB(set_datetime));
register_mqtt_cmd(F("nofrosttemp"), MAKE_CF_CB(set_nofrosttemp), FLAG_HC);
register_mqtt_cmd(F("ecotemp"), MAKE_CF_CB(set_ecotemp), FLAG_HC);
register_mqtt_cmd(F("heattemp"), MAKE_CF_CB(set_heattemp), FLAG_HC);
break; break;
default: default:
break; break;
} }
} }
// register main device values, top level for all thermostats (excluding heating circuits)
void Thermostat::register_device_values() {
uint8_t model = this->model();
// Common for all thermostats
register_device_value(TAG_THERMOSTAT_DATA, &id_, DeviceValueType::UINT, nullptr, F("id"), nullptr); // empty full name to prevent being shown in web or console
register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::TEXT, nullptr, F("dateTime"), F("date/time"));
register_device_value(TAG_THERMOSTAT_DATA, &errorCode_, DeviceValueType::TEXT, nullptr, F("errorCode"), F("error code"));
register_device_value(TAG_THERMOSTAT_DATA, &lastCode_, DeviceValueType::TEXT, nullptr, F("lastCode"), F("last error"));
// RC30 only
if (model == EMSdevice::EMS_DEVICE_FLAG_RC30_1) {
register_device_value(TAG_THERMOSTAT_DATA, &ibaMainDisplay_, DeviceValueType::ENUM, FL_(enum_ibaMainDisplay), F("display"), F("display"));
register_device_value(TAG_THERMOSTAT_DATA, &ibaLanguage_, DeviceValueType::ENUM, FL_(enum_ibaLanguage), F("language"), F("language"));
register_device_value(TAG_THERMOSTAT_DATA, &ibaClockOffset_, DeviceValueType::UINT, nullptr, F("offsetclock"), F("clock offset")); // offset (in sec) to clock, 0xff=-1s, 0x02=2s
}
// RC300 and RC100
if (model == EMS_DEVICE_FLAG_RC300 || model == EMS_DEVICE_FLAG_RC100) {
register_device_value(TAG_THERMOSTAT_DATA, &floordrystatus_, DeviceValueType::ENUM, FL_(enum_floordrystatus), F("floordry"), F("floor drying"));
register_device_value(TAG_THERMOSTAT_DATA, &dampedoutdoortemp2_, DeviceValueType::SHORT, FL_(div10), F("dampedoutdoortemp"), F("damped outdoor temperature"), DeviceValueUOM::DEGREES);
register_device_value(TAG_THERMOSTAT_DATA, &floordrytemp_, DeviceValueType::UINT, nullptr, F("floordrytemp"), F("floor drying temperature"), DeviceValueUOM::DEGREES);
register_device_value(TAG_THERMOSTAT_DATA, &ibaBuildingType_, DeviceValueType::ENUM, FL_(enum_ibaBuildingType), F("building"), F("building"));
register_device_value(TAG_THERMOSTAT_DATA, &wwSetTemp_, DeviceValueType::UINT, nullptr, F("wwsettemp"), F("warm water set temperature"), DeviceValueUOM::DEGREES);
register_device_value(TAG_THERMOSTAT_DATA, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode), F("wwmode"), F("warm water mode"));
register_device_value(TAG_THERMOSTAT_DATA, &wwSetTempLow_, DeviceValueType::UINT, nullptr, F("wwsettemplow"), F("warm water set temperature low"), DeviceValueUOM::DEGREES);
register_device_value(TAG_THERMOSTAT_DATA, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode), F("wwcircmode"), F("warm water circulation mode"));
register_device_value(TAG_THERMOSTAT_DATA, &wwExtra1_, DeviceValueType::UINT, nullptr, F("wwextra1"), F("warm water circuit 1 extra"), DeviceValueUOM::DEGREES);
register_device_value(TAG_THERMOSTAT_DATA, &wwExtra2_, DeviceValueType::UINT, nullptr, F("wwextra2"), F("warm water circuit 2 extra"), DeviceValueUOM::DEGREES);
}
// RC30 and RC35
if (model == EMS_DEVICE_FLAG_RC35 || model == EMS_DEVICE_FLAG_RC30_1) {
register_device_value(TAG_THERMOSTAT_DATA, &ibaCalIntTemperature_, DeviceValueType::INT, FL_(div2), F("intoffset"), F("offset internal temperature"), DeviceValueUOM::DEGREES);
register_device_value(TAG_THERMOSTAT_DATA,
&ibaMinExtTemperature_,
DeviceValueType::INT,
nullptr,
F("minexttemp"),
F("min external temperature"),
DeviceValueUOM::DEGREES); // min ext temp for heating curve, in deg.
register_device_value(TAG_THERMOSTAT_DATA, &tempsensor1_, DeviceValueType::USHORT, FL_(div10), F("inttemp1"), F("temperature sensor 1"), DeviceValueUOM::DEGREES);
register_device_value(TAG_THERMOSTAT_DATA, &tempsensor2_, DeviceValueType::USHORT, FL_(div10), F("inttemp2"), F("temperature sensor 2"), DeviceValueUOM::DEGREES);
register_device_value(TAG_THERMOSTAT_DATA, &dampedoutdoortemp_, DeviceValueType::INT, nullptr, F("dampedoutdoortemp"), F("damped outdoor temperature"), DeviceValueUOM::DEGREES);
register_device_value(TAG_THERMOSTAT_DATA, &ibaBuildingType_, DeviceValueType::ENUM, FL_(enum_ibaBuildingType2), F("building"), F("building"));
register_device_value(TAG_THERMOSTAT_DATA, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode2), F("wwmode"), F("warm water mode"));
register_device_value(TAG_THERMOSTAT_DATA, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode2), F("wwcircmode"), F("warm water circulation mode"));
}
}
// registers the values for a heating circuit // registers the values for a heating circuit
void Thermostat::register_device_values_hc(std::shared_ptr<emsesp::Thermostat::HeatingCircuit> hc) { void Thermostat::register_device_values_hc(std::shared_ptr<emsesp::Thermostat::HeatingCircuit> hc) {
uint8_t model = hc->get_model(); uint8_t model = hc->get_model();
@@ -2178,8 +2181,13 @@ void Thermostat::register_device_values_hc(std::shared_ptr<emsesp::Thermostat::H
setpoint_temp_divider = FL_(div2); setpoint_temp_divider = FL_(div2);
curr_temp_divider = FL_(div10); curr_temp_divider = FL_(div10);
} }
register_device_value(tag, &hc->setpoint_roomTemp, DeviceValueType::SHORT, setpoint_temp_divider, F("seltemp"), F("setpoint room temperature"), DeviceValueUOM::DEGREES); if (has_flags(EMS_DEVICE_FLAG_NO_WRITE) || device_id() != EMSESP::actual_master_thermostat()) {
register_device_value(tag, &hc->curr_roomTemp, DeviceValueType::SHORT, curr_temp_divider, F("currtemp"), F("current room temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->setpoint_roomTemp, DeviceValueType::SHORT, setpoint_temp_divider, FL_(setpoint_roomTemp), DeviceValueUOM::DEGREES);
} else {
register_device_value(tag, &hc->setpoint_roomTemp, DeviceValueType::SHORT, setpoint_temp_divider, FL_(setpoint_roomTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_temp), 5, 29);
register_device_value(tag, &hc->setpoint_roomTemp, DeviceValueType::SHORT, setpoint_temp_divider, FL_(temp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_temp), 5, 29);
}
register_device_value(tag, &hc->curr_roomTemp, DeviceValueType::SHORT, curr_temp_divider, FL_(curr_roomTemp), DeviceValueUOM::DEGREES);
if (device_id() != EMSESP::actual_master_thermostat()) { if (device_id() != EMSESP::actual_master_thermostat()) {
return; return;
@@ -2190,81 +2198,94 @@ void Thermostat::register_device_values_hc(std::shared_ptr<emsesp::Thermostat::H
if (Mqtt::ha_enabled()) { if (Mqtt::ha_enabled()) {
uint8_t option = Mqtt::ha_climate_format(); uint8_t option = Mqtt::ha_climate_format();
if (option == Mqtt::HA_Climate_Format::CURRENT) { if (option == Mqtt::HA_Climate_Format::CURRENT) {
register_device_value(tag, &hc->curr_roomTemp, DeviceValueType::SHORT, curr_temp_divider, F("hatemp"), nullptr); register_device_value(tag, &hc->curr_roomTemp, DeviceValueType::SHORT, curr_temp_divider, FL_(hatemp), DeviceValueUOM::NONE);
} else if (option == Mqtt::HA_Climate_Format::SETPOINT) { } else if (option == Mqtt::HA_Climate_Format::SETPOINT) {
register_device_value(tag, &hc->setpoint_roomTemp, DeviceValueType::SHORT, setpoint_temp_divider, F("hatemp"), nullptr); register_device_value(tag, &hc->setpoint_roomTemp, DeviceValueType::SHORT, setpoint_temp_divider, FL_(hatemp), DeviceValueUOM::NONE);
} else if (option == Mqtt::HA_Climate_Format::ZERO) { } else if (option == Mqtt::HA_Climate_Format::ZERO) {
register_device_value(tag, &zero_value_, DeviceValueType::UINT, nullptr, F("hatemp"), nullptr); register_device_value(tag, &zero_value_, DeviceValueType::UINT, nullptr, FL_(hatemp), DeviceValueUOM::NONE);
} }
// if we're sending to HA the only valid mode types are heat, auto and off // if we're sending to HA the only valid mode types are heat, auto and off
// manual & day = heat // manual & day = heat
// night & off = off // night & off = off
// everything else auto // everything else auto
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_hamode), F("hamode"), nullptr); register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_hamode), FL_(hamode), DeviceValueUOM::NONE);
} }
switch (model) {
if (model == EMSdevice::EMS_DEVICE_FLAG_RC300 || model == EMSdevice::EMS_DEVICE_FLAG_RC100) { case EMS_DEVICE_FLAG_RC100:
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode), F("mode"), F("mode")); case EMS_DEVICE_FLAG_RC300:
register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype), F("modetype"), F("mode type")); register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode));
register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), F("ecotemp"), F("eco temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype), FL_(modetype), DeviceValueUOM::NONE);
register_device_value(tag, &hc->manualtemp, DeviceValueType::UINT, FL_(div2), F("manualtemp"), F("manual temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), FL_(ecotemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_ecotemp));
register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), F("comforttemp"), F("comfort temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->manualtemp, DeviceValueType::UINT, FL_(div2), FL_(manualtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_manualtemp));
register_device_value(tag, &hc->summertemp, DeviceValueType::UINT, nullptr, F("summertemp"), F("summer temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), FL_(comforttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_comforttemp));
register_device_value(tag, &hc->designtemp, DeviceValueType::UINT, nullptr, F("designtemp"), F("design temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->summertemp, DeviceValueType::UINT, nullptr, FL_(summertemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_summertemp));
register_device_value(tag, &hc->offsettemp, DeviceValueType::INT, nullptr, F("offsettemp"), F("offset temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->designtemp, DeviceValueType::UINT, nullptr, FL_(designtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_designtemp));
register_device_value(tag, &hc->minflowtemp, DeviceValueType::UINT, nullptr, F("minflowtemp"), F("min flow temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->offsettemp, DeviceValueType::INT, nullptr, FL_(offsettemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_offsettemp));
register_device_value(tag, &hc->maxflowtemp, DeviceValueType::UINT, nullptr, F("maxflowtemp"), F("max flow temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->minflowtemp, DeviceValueType::UINT, nullptr, FL_(minflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_minflowtemp));
register_device_value(tag, &hc->roominfluence, DeviceValueType::UINT, nullptr, F("roominfluence"), F("room influence")); register_device_value(tag, &hc->maxflowtemp, DeviceValueType::UINT, nullptr, FL_(maxflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_maxflowtemp));
register_device_value(tag, &hc->nofrosttemp, DeviceValueType::INT, nullptr, F("nofrosttemp"), F("nofrost temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->roominfluence, DeviceValueType::UINT, nullptr, FL_(roominfluence), DeviceValueUOM::NONE, MAKE_CF_CB(set_roominfluence));
register_device_value(tag, &hc->targetflowtemp, DeviceValueType::UINT, nullptr, F("targetflowtemp"), F("target flow temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->nofrosttemp, DeviceValueType::INT, nullptr, FL_(nofrosttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nofrosttemp));
register_device_value(tag, &hc->heatingtype, DeviceValueType::ENUM, FL_(enum_heatingtype), F("heatingtype"), F("heating type")); register_device_value(tag, &hc->targetflowtemp, DeviceValueType::UINT, nullptr, FL_(targetflowtemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &hc->summer_setmode, DeviceValueType::ENUM, FL_(enum_summermode), F("summermode"), F("summer mode")); register_device_value(tag, &hc->heatingtype, DeviceValueType::ENUM, FL_(enum_heatingtype), FL_(heatingtype), DeviceValueUOM::NONE);
register_device_value(tag, &hc->controlmode, DeviceValueType::ENUM, FL_(enum_controlmode), F("controlmode"), F("control mode")); register_device_value(tag, &hc->summer_setmode, DeviceValueType::ENUM, FL_(enum_summermode), FL_(summermode), DeviceValueUOM::NONE, MAKE_CF_CB(set_summermode));
register_device_value(tag, &hc->program, DeviceValueType::UINT, nullptr, F("program"), F("program")); register_device_value(tag, &hc->summermode, DeviceValueType::BOOL, nullptr, FL_(summermode), DeviceValueUOM::NONE);
} register_device_value(tag, &hc->controlmode, DeviceValueType::ENUM, FL_(enum_controlmode), FL_(controlmode), DeviceValueUOM::NONE, MAKE_CF_CB(set_controlmode));
register_device_value(tag, &hc->program, DeviceValueType::UINT, nullptr, FL_(program), DeviceValueUOM::NONE, MAKE_CF_CB(set_program));
if (model == EMS_DEVICE_FLAG_RC20) { register_device_value(tag, &hc->tempautotemp, DeviceValueType::UINT, FL_(div2), FL_(tempautotemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_tempautotemp));
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode2), F("mode"), F("mode")); break;
} case EMS_DEVICE_FLAG_CRF:
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode5), FL_(mode), DeviceValueUOM::NONE);
if (model == EMS_DEVICE_FLAG_RC20_2) { register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype5), FL_(modetype), DeviceValueUOM::NONE);
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode2), F("mode"), F("mode")); register_device_value(tag, &hc->targetflowtemp, DeviceValueType::UINT, nullptr, FL_(targetflowtemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype2), F("modetype"), F("mode type")); break;
register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), F("daytemp"), F("day temperature"), DeviceValueUOM::DEGREES); case EMS_DEVICE_FLAG_RC20:
register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), F("nighttemp"), F("night temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode2), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode));
register_device_value(tag, &hc->program, DeviceValueType::UINT, nullptr, F("program"), F("program")); break;
} case EMS_DEVICE_FLAG_RC20_N:
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode2), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode));
if (model == EMS_DEVICE_FLAG_RC35 || model == EMS_DEVICE_FLAG_RC30_1) { register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype2), FL_(modetype), DeviceValueUOM::NONE);
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode3), F("mode"), F("mode")); register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), FL_(daytemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daytemp));
register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype3), F("modetype"), F("mode type")); register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), FL_(nighttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nighttemp));
register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), F("daytemp"), F("day temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->program, DeviceValueType::UINT, nullptr, FL_(program), DeviceValueUOM::NONE, MAKE_CF_CB(set_program));
register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), F("nighttemp"), F("night temperature"), DeviceValueUOM::DEGREES); break;
register_device_value(tag, &hc->designtemp, DeviceValueType::UINT, nullptr, F("designtemp"), F("design temperature"), DeviceValueUOM::DEGREES); case EMS_DEVICE_FLAG_RC30_N:
register_device_value(tag, &hc->offsettemp, DeviceValueType::INT, FL_(div2), F("offsettemp"), F("offset temperature"), DeviceValueUOM::DEGREES); case EMS_DEVICE_FLAG_RC35:
register_device_value(tag, &hc->holidaytemp, DeviceValueType::UINT, FL_(div2), F("holidaytemp"), F("holiday temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode3), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode));
register_device_value(tag, &hc->targetflowtemp, DeviceValueType::UINT, nullptr, F("targetflowtemp"), F("target flow temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype3), FL_(modetype), DeviceValueUOM::NONE);
register_device_value(tag, &hc->summertemp, DeviceValueType::UINT, nullptr, F("summertemp"), F("summer temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), FL_(daytemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daytemp));
register_device_value(tag, &hc->summermode, DeviceValueType::BOOL, nullptr, F("summermode"), F("summer mode")); register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), FL_(nighttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nighttemp));
register_device_value(tag, &hc->holidaymode, DeviceValueType::BOOL, nullptr, F("holidaymode"), F("holiday mode")); register_device_value(tag, &hc->designtemp, DeviceValueType::UINT, nullptr, FL_(designtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_designtemp));
register_device_value(tag, &hc->nofrosttemp, DeviceValueType::INT, nullptr, F("nofrosttemp"), F("nofrost temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->offsettemp, DeviceValueType::INT, FL_(div2), FL_(offsettemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_offsettemp));
register_device_value(tag, &hc->roominfluence, DeviceValueType::UINT, nullptr, F("roominfluence"), F("room influence")); register_device_value(tag, &hc->holidaytemp, DeviceValueType::UINT, FL_(div2), FL_(holidaytemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_holidaytemp));
register_device_value(tag, &hc->minflowtemp, DeviceValueType::UINT, nullptr, F("minflowtemp"), F("min flow temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->targetflowtemp, DeviceValueType::UINT, nullptr, FL_(targetflowtemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &hc->maxflowtemp, DeviceValueType::UINT, nullptr, F("maxflowtemp"), F("max flow temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->summertemp, DeviceValueType::UINT, nullptr, FL_(summertemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_summertemp));
register_device_value(tag, &hc->flowtempoffset, DeviceValueType::UINT, nullptr, F("flowtempoffset"), F("flow temperature offset"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->summermode, DeviceValueType::BOOL, nullptr, FL_(summermode), DeviceValueUOM::NONE);
register_device_value(tag, &hc->heatingtype, DeviceValueType::ENUM, FL_(enum_heatingtype), F("heatingtype"), F("heating type")); register_device_value(tag, &hc->holidaymode, DeviceValueType::BOOL, nullptr, FL_(holidaymode), DeviceValueUOM::NONE, MAKE_CF_CB(set_holiday));
register_device_value(tag, &hc->reducemode, DeviceValueType::ENUM, FL_(enum_reducemode), F("reducemode"), F("reduce mode")); register_device_value(tag, &hc->nofrosttemp, DeviceValueType::INT, nullptr, FL_(nofrosttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nofrosttemp));
register_device_value(tag, &hc->controlmode, DeviceValueType::ENUM, FL_(enum_controlmode2), F("controlmode"), F("control mode")); register_device_value(tag, &hc->roominfluence, DeviceValueType::UINT, nullptr, FL_(roominfluence), DeviceValueUOM::NONE, MAKE_CF_CB(set_roominfluence));
register_device_value(tag, &hc->program, DeviceValueType::UINT, nullptr, F("program"), F("program")); register_device_value(tag, &hc->minflowtemp, DeviceValueType::UINT, nullptr, FL_(minflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_minflowtemp));
} register_device_value(tag, &hc->maxflowtemp, DeviceValueType::UINT, nullptr, FL_(maxflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_maxflowtemp));
register_device_value(tag, &hc->flowtempoffset, DeviceValueType::UINT, nullptr, FL_(flowtempoffset), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_flowtempoffset));
if (model == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) { register_device_value(tag, &hc->heatingtype, DeviceValueType::ENUM, FL_(enum_heatingtype), FL_(heatingtype), DeviceValueUOM::NONE);
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode4), F("mode"), F("mode")); register_device_value(tag, &hc->reducemode, DeviceValueType::ENUM, FL_(enum_reducemode), FL_(reducemode), DeviceValueUOM::NONE, MAKE_CF_CB(set_reducemode));
register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype4), F("modetype"), F("mode type")); register_device_value(tag, &hc->controlmode, DeviceValueType::ENUM, FL_(enum_controlmode2), FL_(controlmode), DeviceValueUOM::NONE, MAKE_CF_CB(set_controlmode));
register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), F("heattemp"), F("heat temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_control), FL_(control), DeviceValueUOM::NONE, MAKE_CF_CB(set_control));
register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), F("ecotemp"), F("eco temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->program, DeviceValueType::UINT, nullptr, FL_(program), DeviceValueUOM::NONE, MAKE_CF_CB(set_program));
register_device_value(tag, &hc->nofrosttemp, DeviceValueType::INT, FL_(div2), F("nofrosttemp"), F("nofrost temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->pause, DeviceValueType::UINT, nullptr, FL_(pause), DeviceValueUOM::HOURS, MAKE_CF_CB(set_pause));
register_device_value(tag, &hc->party, DeviceValueType::UINT, nullptr, FL_(party), DeviceValueUOM::HOURS, MAKE_CF_CB(set_party));
register_device_value(tag, &hc->tempautotemp, DeviceValueType::UINT, FL_(div2), FL_(tempautotemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_tempautotemp));
register_device_value(tag, &hc->noreducetemp, DeviceValueType::INT, nullptr, FL_(noreducetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_noreducetemp));
register_device_value(tag, &hc->remotetemp, DeviceValueType::SHORT, FL_(div10), FL_(remotetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_remotetemp));
register_device_value(tag, &dummychar_, DeviceValueType::TEXT, nullptr, FL_(switchtime), DeviceValueUOM::NONE, MAKE_CF_CB(set_switchtime));
break;
case EMS_DEVICE_FLAG_JUNKERS:
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode4), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode));
register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype4), FL_(modetype), DeviceValueUOM::NONE);
register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), FL_(heattemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_heattemp));
register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), FL_(ecotemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_ecotemp));
register_device_value(tag, &hc->nofrosttemp, DeviceValueType::INT, FL_(div2), FL_(nofrosttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nofrosttemp));
break;
} }
} }

View File

@@ -36,6 +36,8 @@ class Thermostat : public EMSdevice {
int16_t setpoint_roomTemp; int16_t setpoint_roomTemp;
int16_t curr_roomTemp; int16_t curr_roomTemp;
int16_t remotetemp; // for readback
uint8_t tempautotemp;
uint8_t mode; uint8_t mode;
uint8_t modetype; uint8_t modetype;
uint8_t summermode; uint8_t summermode;
@@ -58,6 +60,10 @@ class Thermostat : public EMSdevice {
uint8_t reducemode; uint8_t reducemode;
uint8_t program; uint8_t program;
uint8_t controlmode; uint8_t controlmode;
uint8_t control;
uint8_t pause;
uint8_t party;
int8_t noreducetemp; // signed -20°C to +10°C
uint8_t hc_num() const { uint8_t hc_num() const {
return hc_num_; return hc_num_;
@@ -93,6 +99,9 @@ class Thermostat : public EMSdevice {
MINFLOW, MINFLOW,
MAXFLOW, MAXFLOW,
ROOMINFLUENCE, ROOMINFLUENCE,
TEMPAUTO,
NOREDUCE,
ON,
UNKNOWN UNKNOWN
}; };
@@ -114,8 +123,6 @@ class Thermostat : public EMSdevice {
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;
void add_commands();
void register_device_values(); void register_device_values();
void register_device_values(uint8_t hc_num); void register_device_values(uint8_t hc_num);
@@ -137,7 +144,8 @@ class Thermostat : public EMSdevice {
char dateTime_[25]; // date and time stamp char dateTime_[25]; // date and time stamp
char errorCode_[15]; // code from 0xA2 as string i.e. "A22(816)" char errorCode_[15]; // code from 0xA2 as string i.e. "A22(816)"
uint16_t errorNumber_; // used internally to build error code uint16_t errorNumber_; // used internally to build error code
char lastCode_[30]; char lastCode_[30]; // error log
char dummychar_[5]; // for commands with no output
// Installation parameters // Installation parameters
uint8_t ibaMainDisplay_; // display on Thermostat: 0 int temp, 1 int setpoint, 2 ext temp, 3 burner temp, 4 ww temp, 5 functioning mode, 6 time, 7 data, 9 smoke temp uint8_t ibaMainDisplay_; // display on Thermostat: 0 int temp, 1 int setpoint, 2 ext temp, 3 burner temp, 4 ww temp, 5 functioning mode, 6 time, 7 data, 9 smoke temp
@@ -200,6 +208,7 @@ class Thermostat : public EMSdevice {
static constexpr uint8_t EMS_OFFSET_RC35Set_heatingtype = 0; // e.g. floor heating = 3 static constexpr uint8_t EMS_OFFSET_RC35Set_heatingtype = 0; // e.g. floor heating = 3
static constexpr uint8_t EMS_OFFSET_RC35Set_targetflowtemp = 14; // target flow temperature static constexpr uint8_t EMS_OFFSET_RC35Set_targetflowtemp = 14; // target flow temperature
static constexpr uint8_t EMS_OFFSET_RC35Set_seltemp = 37; // selected temp static constexpr uint8_t EMS_OFFSET_RC35Set_seltemp = 37; // selected temp
static constexpr uint8_t EMS_OFFSET_RC35Set_noreducetemp = 38; // temp to stop reducing
static constexpr uint8_t EMS_OFFSET_RC35Set_temp_offset = 6; static constexpr uint8_t EMS_OFFSET_RC35Set_temp_offset = 6;
static constexpr uint8_t EMS_OFFSET_RC35Set_temp_flowoffset = 24; static constexpr uint8_t EMS_OFFSET_RC35Set_temp_flowoffset = 24;
static constexpr uint8_t EMS_OFFSET_RC35Set_temp_design = 17; static constexpr uint8_t EMS_OFFSET_RC35Set_temp_design = 17;
@@ -268,6 +277,7 @@ class Thermostat : public EMSdevice {
void process_RC20Set_2(std::shared_ptr<const Telegram> telegram); void process_RC20Set_2(std::shared_ptr<const Telegram> telegram);
void process_RC10Monitor(std::shared_ptr<const Telegram> telegram); void process_RC10Monitor(std::shared_ptr<const Telegram> telegram);
void process_RC10Set(std::shared_ptr<const Telegram> telegram); void process_RC10Set(std::shared_ptr<const Telegram> telegram);
void process_CRFMonitor(std::shared_ptr<const Telegram> telegram);
void process_RC300Monitor(std::shared_ptr<const Telegram> telegram); void process_RC300Monitor(std::shared_ptr<const Telegram> telegram);
void process_RC300Set(std::shared_ptr<const Telegram> telegram); void process_RC300Set(std::shared_ptr<const Telegram> telegram);
void process_RC300Summer(std::shared_ptr<const Telegram> telegram); void process_RC300Summer(std::shared_ptr<const Telegram> telegram);
@@ -309,6 +319,8 @@ class Thermostat : public EMSdevice {
bool set_offsettemp(const char * value, const int8_t id); bool set_offsettemp(const char * value, const int8_t id);
bool set_holidaytemp(const char * value, const int8_t id); bool set_holidaytemp(const char * value, const int8_t id);
bool set_manualtemp(const char * value, const int8_t id); bool set_manualtemp(const char * value, const int8_t id);
bool set_tempautotemp(const char * value, const int8_t id);
bool set_noreducetemp(const char * value, const int8_t id);
bool set_remotetemp(const char * value, const int8_t id); bool set_remotetemp(const char * value, const int8_t id);
bool set_roominfluence(const char * value, const int8_t id); bool set_roominfluence(const char * value, const int8_t id);
bool set_flowtempoffset(const char * value, const int8_t id); bool set_flowtempoffset(const char * value, const int8_t id);

View File

@@ -33,7 +33,8 @@ static const __FlashStringHelper * DeviceValueUOM_s[] __attribute__((__aligned__
F_(hours), F_(hours),
F_(minutes), F_(minutes),
F_(ua), F_(ua),
F_(bar) F_(bar),
F_(kw)
}; };
@@ -44,7 +45,7 @@ static const __FlashStringHelper * const DeviceValueTAG_s[] PROGMEM = {
F_(tag_none), // "" F_(tag_none), // ""
F_(tag_heartbeat), // "" F_(tag_heartbeat), // ""
F_(tag_boiler_data), // "" F_(tag_boiler_data), // ""
F_(tag_boiler_data_ww), // "warm water" F_(tag_device_data_ww), // "warm water"
F_(tag_thermostat_data), // "" F_(tag_thermostat_data), // ""
F_(tag_hc1), // "hc1" F_(tag_hc1), // "hc1"
F_(tag_hc2), // "hc2" F_(tag_hc2), // "hc2"
@@ -77,9 +78,9 @@ static const __FlashStringHelper * const DeviceValueTAG_s[] PROGMEM = {
static const __FlashStringHelper * const DeviceValueTAG_mqtt[] PROGMEM = { static const __FlashStringHelper * const DeviceValueTAG_mqtt[] PROGMEM = {
F_(tag_none), // "" F_(tag_none), // ""
F_(tag_heartbeat_mqtt), // "heartbeat" F_(heartbeat), // "heartbeat"
F_(tag_boiler_data_mqtt), // "" F_(tag_boiler_data_mqtt), // ""
F_(tag_boiler_data_ww_mqtt), // "ww" F_(tag_device_data_ww_mqtt), // "ww"
F_(tag_thermostat_data), // "" F_(tag_thermostat_data), // ""
F_(tag_hc1), // "hc1" F_(tag_hc1), // "hc1"
F_(tag_hc2), // "hc2" F_(tag_hc2), // "hc2"
@@ -149,7 +150,7 @@ std::string EMSdevice::brand_to_string() const {
break; break;
case EMSdevice::Brand::NO_BRAND: case EMSdevice::Brand::NO_BRAND:
default: default:
return read_flash_string(F("---")); return read_flash_string(F(""));
break; break;
} }
@@ -211,31 +212,38 @@ std::string EMSdevice::device_type_2_device_name(const uint8_t device_type) {
// returns device_type from a string // returns device_type from a string
uint8_t EMSdevice::device_name_2_device_type(const char * topic) { uint8_t EMSdevice::device_name_2_device_type(const char * topic) {
if (!strcmp_P(topic, reinterpret_cast<PGM_P>(F_(boiler)))) { // convert topic to lowercase and compare
char lowtopic[20];
strlcpy(lowtopic, topic, sizeof(lowtopic));
for (char * p = lowtopic; *p; p++) {
*p = tolower(*p);
}
if (!strcmp_P(lowtopic, reinterpret_cast<PGM_P>(F_(boiler)))) {
return DeviceType::BOILER; return DeviceType::BOILER;
} }
if (!strcmp_P(topic, reinterpret_cast<PGM_P>(F_(thermostat)))) { if (!strcmp_P(lowtopic, reinterpret_cast<PGM_P>(F_(thermostat)))) {
return DeviceType::THERMOSTAT; return DeviceType::THERMOSTAT;
} }
if (!strcmp_P(topic, reinterpret_cast<PGM_P>(F_(system)))) { if (!strcmp_P(lowtopic, reinterpret_cast<PGM_P>(F_(system)))) {
return DeviceType::SYSTEM; return DeviceType::SYSTEM;
} }
if (!strcmp_P(topic, reinterpret_cast<PGM_P>(F_(heatpump)))) { if (!strcmp_P(lowtopic, reinterpret_cast<PGM_P>(F_(heatpump)))) {
return DeviceType::HEATPUMP; return DeviceType::HEATPUMP;
} }
if (!strcmp_P(topic, reinterpret_cast<PGM_P>(F_(solar)))) { if (!strcmp_P(lowtopic, reinterpret_cast<PGM_P>(F_(solar)))) {
return DeviceType::SOLAR; return DeviceType::SOLAR;
} }
if (!strcmp_P(topic, reinterpret_cast<PGM_P>(F_(mixer)))) { if (!strcmp_P(lowtopic, reinterpret_cast<PGM_P>(F_(mixer)))) {
return DeviceType::MIXER; return DeviceType::MIXER;
} }
if (!strcmp_P(topic, reinterpret_cast<PGM_P>(F_(dallassensor)))) { if (!strcmp_P(lowtopic, reinterpret_cast<PGM_P>(F_(dallassensor)))) {
return DeviceType::DALLASSENSOR; return DeviceType::DALLASSENSOR;
} }
@@ -405,9 +413,9 @@ void EMSdevice::register_mqtt_topic(const std::string & topic, mqtt_subfunction_
} }
// add command to library // add command to library
void EMSdevice::register_mqtt_cmd(const __FlashStringHelper * cmd, cmdfunction_p f, uint8_t flag) { // void EMSdevice::register_cmd(const __FlashStringHelper * cmd, cmdfunction_p f, uint8_t flag) {
Command::add(device_type_, cmd, f, flag); // Command::add(device_type_, cmd, f, flag);
} // }
// register a callback function for a specific telegram type // register a callback function for a specific telegram type
void EMSdevice::register_telegram_type(const uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, process_function_p f) { void EMSdevice::register_telegram_type(const uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, process_function_p f) {
@@ -423,7 +431,16 @@ void EMSdevice::register_telegram_type(const uint16_t telegram_type_id, const __
// short_name: used in Mqtt as keys // short_name: used in Mqtt as keys
// full name: used in Web and Console unless empty (nullptr) // full name: used in Web and Console unless empty (nullptr)
// uom: unit of measure from DeviceValueUOM // uom: unit of measure from DeviceValueUOM
void EMSdevice::register_device_value(uint8_t tag, void * value_p, uint8_t type, const __FlashStringHelper * const * options, const __FlashStringHelper * short_name, const __FlashStringHelper * full_name, uint8_t uom) { void EMSdevice::register_device_value(uint8_t tag,
void * value_p,
uint8_t type,
const __FlashStringHelper * const * options,
const __FlashStringHelper * short_name,
const __FlashStringHelper * full_name,
uint8_t uom,
bool has_cmd,
int32_t min,
uint32_t max) {
// init the value depending on it's type // init the value depending on it's type
if (type == DeviceValueType::TEXT) { if (type == DeviceValueType::TEXT) {
*(char *)(value_p) = {'\0'}; *(char *)(value_p) = {'\0'};
@@ -435,8 +452,10 @@ void EMSdevice::register_device_value(uint8_t tag, void * value_p, uint8_t type,
*(uint16_t *)(value_p) = EMS_VALUE_USHORT_NOTSET; *(uint16_t *)(value_p) = EMS_VALUE_USHORT_NOTSET;
} else if ((type == DeviceValueType::ULONG) || (type == DeviceValueType::TIME)) { } else if ((type == DeviceValueType::ULONG) || (type == DeviceValueType::TIME)) {
*(uint32_t *)(value_p) = EMS_VALUE_ULONG_NOTSET; *(uint32_t *)(value_p) = EMS_VALUE_ULONG_NOTSET;
} else if (type == DeviceValueType::BOOL) {
*(int8_t *)(value_p) = EMS_VALUE_BOOL_NOTSET; // bool is uint8_t, but other initial value
} else { } else {
*(uint8_t *)(value_p) = EMS_VALUE_UINT_NOTSET; // enums, uint8_t, bool behave as uint8_t *(uint8_t *)(value_p) = EMS_VALUE_UINT_NOTSET; // enums behave as uint8_t
} }
// count #options // count #options
@@ -448,14 +467,37 @@ void EMSdevice::register_device_value(uint8_t tag, void * value_p, uint8_t type,
}; };
} }
devicevalues_.emplace_back(device_type_, tag, value_p, type, options, options_size, short_name, full_name, uom); devicevalues_.emplace_back(device_type_, tag, value_p, type, options, options_size, short_name, full_name, uom, 0, has_cmd, min, max);
}
void EMSdevice::register_device_value(uint8_t tag, void * value_p, uint8_t type, const __FlashStringHelper * const * options, const __FlashStringHelper * const * name, uint8_t uom, cmdfunction_p f, int32_t min, uint32_t max) {
register_device_value(tag, value_p, type, options, name[0], name[1], uom, (f != nullptr), min, max);
if (f != nullptr) {
if (tag >= TAG_HC1 && tag <= TAG_HC4) {
Command::add(device_type_, name[0], f, FLAG_HC);
} else {
Command::add(device_type_, name[0], f, 0);
}
}
}
void EMSdevice::register_device_value(uint8_t tag, void * value_p, uint8_t type, const __FlashStringHelper * const * options, const __FlashStringHelper * const * name, uint8_t uom, cmdfunction_p f) {
register_device_value(tag, value_p, type, options, name, uom, f, 0, 0);
}
// void EMSdevice::register_device_value(uint8_t tag, void * value_p, uint8_t type, const __FlashStringHelper * const * options, const __FlashStringHelper * const * name, uint8_t uom, int32_t min, uint32_t max) {
// register_device_value(tag, value_p, type, options, name, uom, nullptr, min, max);
// }
void EMSdevice::register_device_value(uint8_t tag, void * value_p, uint8_t type, const __FlashStringHelper * const * options, const __FlashStringHelper * const * name, uint8_t uom) {
register_device_value(tag, value_p, type, options, name, uom, nullptr, 0, 0);
} }
// looks up the uom (suffix) for a given key from the device value table // looks up the uom (suffix) for a given key from the device value table
std::string EMSdevice::get_value_uom(const char * key) { std::string EMSdevice::get_value_uom(const char * key) {
// the key may have a suffix at the start which is between brackets. remove it. // the key may have a suffix at the start which is between brackets. remove it.
char new_key[80]; char new_key[80];
strncpy(new_key, key, sizeof(new_key)); strlcpy(new_key, key, sizeof(new_key));
char * p = new_key; char * p = new_key;
if (key[0] == '(') { if (key[0] == '(') {
while ((*p++ != ')') && (*p != '\0')) while ((*p++ != ')') && (*p != '\0'))
@@ -566,7 +608,7 @@ bool EMSdevice::generate_values_json_web(JsonObject & json) {
if (sz > num_elements) { if (sz > num_elements) {
// add the unit of measure (uom) // add the unit of measure (uom)
if (dv.uom == DeviceValueUOM::MINUTES) { if (dv.uom == DeviceValueUOM::MINUTES) {
data.add(nullptr); data.add(nullptr); // use null for time/date
} else { } else {
data.add(uom_to_string(dv.uom)); data.add(uom_to_string(dv.uom));
} }
@@ -579,7 +621,15 @@ bool EMSdevice::generate_values_json_web(JsonObject & json) {
snprintf_P(name, sizeof(name), "(%s) %s", tag_to_string(dv.tag).c_str(), uuid::read_flash_string(dv.full_name).c_str()); snprintf_P(name, sizeof(name), "(%s) %s", tag_to_string(dv.tag).c_str(), uuid::read_flash_string(dv.full_name).c_str());
data.add(name); data.add(name);
} }
num_elements = sz + 2;
// add the name of the Command function if it exists
if (dv.has_cmd) {
data.add(dv.short_name);
} else {
data.add("");
}
num_elements = sz + 3; // increase count by 3
} }
} }
} }
@@ -587,19 +637,181 @@ bool EMSdevice::generate_values_json_web(JsonObject & json) {
return (num_elements != 0); return (num_elements != 0);
} }
bool EMSdevice::get_value_info(JsonObject & root, const char * cmd, const int8_t id) {
JsonObject json = root;
int8_t tag = id;
// check if we have hc or wwc
if (id >= 1 && id <= 4) {
tag = DeviceValueTAG::TAG_HC1 + id - 1;
} else if (id >= 8 && id <= 11) {
tag = DeviceValueTAG::TAG_WWC1 + id - 8;
}
// search device value with this tag
for (auto & dv : devicevalues_) {
if (strcmp(cmd, Helpers::toLower(uuid::read_flash_string(dv.short_name)).c_str()) == 0 && (tag <= 0 || tag == dv.tag)) {
uint8_t divider = (dv.options_size == 1) ? Helpers::atoint(uuid::read_flash_string(dv.options[0]).c_str()) : 0;
const char * type = "type";
const char * min = "min";
const char * max = "max";
const char * value = "value";
json["name"] = dv.short_name;
if (!tag_to_mqtt(dv.tag).empty()) {
json["circuit"] = tag_to_mqtt(dv.tag);
}
switch (dv.type) {
case DeviceValueType::ENUM: {
if (Helpers::hasValue((uint8_t)(*(uint8_t *)(dv.value_p)))) {
if (Mqtt::bool_format() == BOOL_FORMAT_10) {
json[value] = (uint8_t)(*(uint8_t *)(dv.value_p));
} else {
json[value] = dv.options[*(uint8_t *)(dv.value_p)]; // text
}
}
json[type] = F_(enum);
uint8_t min_ = (uuid::read_flash_string(dv.options[0]) == "") ? 1 : 0;
json[min] = min_;
json[max] = dv.options_size - 1;
JsonArray enum_ = json.createNestedArray(F_(enum));
for (uint8_t i = min_; i < dv.options_size; i++) {
enum_.add(dv.options[i]);
}
break;
}
case DeviceValueType::USHORT:
if (Helpers::hasValue(*(uint16_t *)(dv.value_p))) {
json[value] = Helpers::round2(*(uint16_t *)(dv.value_p), divider);
}
json[type] = F_(number);
json[min] = 0;
json[max] = divider ? EMS_VALUE_USHORT_NOTSET / divider : EMS_VALUE_USHORT_NOTSET;
break;
case DeviceValueType::UINT:
if (Helpers::hasValue(*(uint8_t *)(dv.value_p))) {
json[value] = Helpers::round2(*(uint8_t *)(dv.value_p), divider);
}
json[type] = F_(number);
json[min] = 0;
if (dv.uom == DeviceValueUOM::PERCENT) {
json[max] = 100;
} else {
json[max] = divider ? EMS_VALUE_UINT_NOTSET / divider : EMS_VALUE_UINT_NOTSET;
}
break;
case DeviceValueType::SHORT:
if (Helpers::hasValue(*(int16_t *)(dv.value_p))) {
json[value] = Helpers::round2(*(int16_t *)(dv.value_p), divider);
}
json[type] = F_(number);
json[min] = divider ? -EMS_VALUE_SHORT_NOTSET / divider : -EMS_VALUE_SHORT_NOTSET;
json[max] = divider ? EMS_VALUE_SHORT_NOTSET / divider : EMS_VALUE_SHORT_NOTSET;
break;
case DeviceValueType::INT:
if (Helpers::hasValue(*(int8_t *)(dv.value_p))) {
json[value] = Helpers::round2(*(int8_t *)(dv.value_p), divider);
}
json[type] = F_(number);
if (dv.uom == DeviceValueUOM::PERCENT) {
json[min] = -100;
json[max] = 100;
} else {
json[min] = divider ? -EMS_VALUE_INT_NOTSET / divider : -EMS_VALUE_INT_NOTSET;
json[max] = divider ? EMS_VALUE_INT_NOTSET / divider : EMS_VALUE_INT_NOTSET;
}
break;
case DeviceValueType::ULONG:
if (Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
json[value] = Helpers::round2(*(uint32_t *)(dv.value_p), divider);
}
json[type] = F_(number);
json[min] = 0;
json[max] = divider ? EMS_VALUE_ULONG_NOTSET / divider : EMS_VALUE_ULONG_NOTSET;
break;
case DeviceValueType::BOOL: {
if (Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) {
if (dv.options_size == 2) {
json[value] = (bool)(*(uint8_t *)(dv.value_p)) ? dv.options[0] : dv.options[1];
} else if (Mqtt::bool_format() == BOOL_FORMAT_ONOFF) {
json[value] = (bool)(*(uint8_t *)(dv.value_p)) ? F_(on) : F_(off);
} else if (Mqtt::bool_format() == BOOL_FORMAT_ONOFF_CAP) {
json[value] = (bool)(*(uint8_t *)(dv.value_p)) ? F_(ON) : F_(OFF);
} else if (Mqtt::bool_format() == BOOL_FORMAT_TRUEFALSE) {
json[value] = (bool)(*(uint8_t *)(dv.value_p)) ? true : false;
} else {
json[value] = (bool)(*(uint8_t *)(dv.value_p)) ? 1 : 0;
}
}
json[type] = F("boolean");
json[min] = 0;
json[max] = 1;
JsonArray enum_ = json.createNestedArray(F_(enum));
if (dv.options_size == 2) {
enum_.add(dv.options[1]);
enum_.add(dv.options[0]);
} else if (Mqtt::bool_format() == BOOL_FORMAT_ONOFF) {
enum_.add(F_(off));
enum_.add(F_(on));
} else if (Mqtt::bool_format() == BOOL_FORMAT_ONOFF_CAP) {
enum_.add(F_(OFF));
enum_.add(F_(ON));
} else if (Mqtt::bool_format() == BOOL_FORMAT_TRUEFALSE) {
enum_.add(false);
enum_.add(true);
} else {
enum_.add(0);
enum_.add(1);
}
break;
}
case DeviceValueType::TIME:
if (Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
json[value] = (divider) ? *(uint32_t *)(dv.value_p) / divider : *(uint32_t *)(dv.value_p);
}
json[type] = F_(number);
json[min] = 0;
json[max] = divider ? EMS_VALUE_ULONG_NOTSET / divider : EMS_VALUE_ULONG_NOTSET;
break;
case DeviceValueType::TEXT:
if (Helpers::hasValue((char *)(dv.value_p))) {
json[value] = (char *)(dv.value_p);
}
json[type] = F_(text);
break;
default:
json[type] = F_(unknown);
break;
}
if (dv.uom != DeviceValueUOM::NONE) {
json["unit"] = EMSdevice::uom_to_string(dv.uom);
}
json["writeable"] = dv.has_cmd;
// if we have individual limits, overwrite the common limits
if (dv.min != 0 || dv.max != 0) {
json[min] = dv.min;
json[max] = dv.max;
}
return true;
}
}
return false;
}
// For each value in the device create the json object pair and add it to given json // For each value in the device create the json object pair and add it to given json
// return false if empty // return false if empty
// this is used to create both the MQTT payloads and Console messages (console = true) // this is used to create both the MQTT payloads and Console messages (console = true)
bool EMSdevice::generate_values_json(JsonObject & root, const uint8_t tag_filter, const bool nested, const bool console) { bool EMSdevice::generate_values_json(JsonObject & root, const uint8_t tag_filter, const bool nested, const bool console) {
bool has_value = false; // to see if we've added a value. it's faster than doing a json.size() at the end bool has_values = false; // to see if we've added a value. it's faster than doing a json.size() at the end
uint8_t old_tag = 255; // NAN uint8_t old_tag = 255; // NAN
JsonObject json = root; JsonObject json = root;
for (const auto & dv : devicevalues_) { for (auto & dv : devicevalues_) {
bool has_value = false;
// only show if tag is either empty (TAG_NONE) or matches a value // only show if tag is either empty (TAG_NONE) or matches a value
// and don't show if full_name is empty unless we're outputing for mqtt payloads // and don't show if full_name is empty unless we're outputing for mqtt payloads
// for nested we use all values // for nested we use all values, dont show command only (have_cmd and no fulname)
if (((nested) || tag_filter == DeviceValueTAG::TAG_NONE || (tag_filter == dv.tag)) && (dv.full_name != nullptr || !console)) { if (((nested) || tag_filter == DeviceValueTAG::TAG_NONE || (tag_filter == dv.tag)) && (dv.full_name != nullptr || !console) && !(dv.full_name == nullptr && dv.has_cmd)) {
// we have a tag if it matches the filter given, and that the tag name is not empty/"" // we have a tag if it matches the filter given, and that the tag name is not empty/""
bool have_tag = ((dv.tag != tag_filter) && !tag_to_string(dv.tag).empty()); bool have_tag = ((dv.tag != tag_filter) && !tag_to_string(dv.tag).empty());
@@ -626,7 +838,7 @@ bool EMSdevice::generate_values_json(JsonObject & root, const uint8_t tag_filter
// handle Booleans (true, false) // handle Booleans (true, false)
if ((dv.type == DeviceValueType::BOOL) && Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) { if ((dv.type == DeviceValueType::BOOL) && Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) {
// see if we have options for the bool's // see if we have options for the bool's
if (dv.options_size == 2) { if (dv.options_size == 2 && Mqtt::bool_format() != BOOL_FORMAT_10) {
json[name] = *(uint8_t *)(dv.value_p) ? dv.options[0] : dv.options[1]; json[name] = *(uint8_t *)(dv.value_p) ? dv.options[0] : dv.options[1];
has_value = true; has_value = true;
} else { } else {
@@ -727,21 +939,35 @@ bool EMSdevice::generate_values_json(JsonObject & root, const uint8_t tag_filter
} }
} }
} }
dv.ha |= has_value ? DeviceValueHA::HA_VALUE : DeviceValueHA::HA_NONE;
has_values |= has_value;
} }
return has_value; return has_values;
} }
// create the Home Assistant configs for each value // create the Home Assistant configs for each value
// this is called when an MQTT publish is done via an EMS Device, and only done once // this is called when an MQTT publish is done via an EMS Device
void EMSdevice::publish_mqtt_ha_sensor() { void EMSdevice::publish_mqtt_ha_sensor() {
for (const auto & dv : devicevalues_) { for (auto & dv : devicevalues_) {
if (dv.ha == DeviceValueHA::HA_VALUE) {
Mqtt::publish_mqtt_ha_sensor(dv.type, dv.tag, dv.full_name, device_type_, dv.short_name, dv.uom); Mqtt::publish_mqtt_ha_sensor(dv.type, dv.tag, dv.full_name, device_type_, dv.short_name, dv.uom);
dv.ha |= DeviceValueHA::HA_DONE;
} }
}
if (!ha_config_done()) {
bool ok = publish_ha_config(); bool ok = publish_ha_config();
ha_config_done(ok); // see if it worked ha_config_done(ok); // see if it worked
} }
}
void EMSdevice::ha_config_clear() {
for (auto & dv : devicevalues_) {
// dv.ha &= ~DeviceValueHA::HA_DONE; // repubish all with values
dv.ha = DeviceValueHA::HA_NONE; // also wait for new value
}
ha_config_done(false);
}
// return the name of the telegram type // return the name of the telegram type
std::string EMSdevice::telegram_type_name(std::shared_ptr<const Telegram> telegram) { std::string EMSdevice::telegram_type_name(std::shared_ptr<const Telegram> telegram) {
@@ -800,8 +1026,8 @@ void EMSdevice::write_command(const uint16_t type_id, const uint8_t offset, cons
} }
// send Tx read command to the device // send Tx read command to the device
void EMSdevice::read_command(const uint16_t type_id) { void EMSdevice::read_command(const uint16_t type_id, const uint8_t offset, const uint8_t length) {
EMSESP::send_read_request(type_id, device_id()); EMSESP::send_read_request(type_id, device_id(), offset, length);
} }
} // namespace emsesp } // namespace emsesp

View File

@@ -55,61 +55,16 @@ enum DeviceValueType : uint8_t {
}; };
// Unit Of Measurement mapping - maps to DeviceValueUOM_s in emsdevice.cpp // Unit Of Measurement mapping - maps to DeviceValueUOM_s in emsdevice.cpp
// sequence is important!
// uom - also used with HA // uom - also used with HA
MAKE_PSTR(percent, "%") // sequence is important!
MAKE_PSTR(degrees, "°C") enum DeviceValueUOM : uint8_t { NONE = 0, DEGREES, PERCENT, LMIN, KWH, WH, HOURS, MINUTES, UA, BAR, KW, PUMP };
MAKE_PSTR(kwh, "kWh")
MAKE_PSTR(wh, "Wh")
MAKE_PSTR(bar, "bar")
MAKE_PSTR(minutes, "minutes")
MAKE_PSTR(hours, "hours")
MAKE_PSTR(ua, "uA")
MAKE_PSTR(lmin, "l/min")
enum DeviceValueUOM : uint8_t { NONE = 0, DEGREES, PERCENT, LMIN, KWH, WH, HOURS, MINUTES, UA, BAR, PUMP };
// TAG mapping - maps to DeviceValueTAG_s in emsdevice.cpp // TAG mapping - maps to DeviceValueTAG_s in emsdevice.cpp
// use empty string if want to suppress showing tags
MAKE_PSTR(tag_none, "")
MAKE_PSTR(tag_heartbeat, "")
MAKE_PSTR(tag_boiler_data, "")
MAKE_PSTR(tag_boiler_data_ww, "warm water")
MAKE_PSTR(tag_thermostat_data, "")
MAKE_PSTR(tag_hc1, "hc1")
MAKE_PSTR(tag_hc2, "hc2")
MAKE_PSTR(tag_hc3, "hc3")
MAKE_PSTR(tag_hc4, "hc4")
MAKE_PSTR(tag_wwc1, "wwc1")
MAKE_PSTR(tag_wwc2, "wwc2")
MAKE_PSTR(tag_wwc3, "wwc3")
MAKE_PSTR(tag_wwc4, "wwc4")
MAKE_PSTR(tag_hs1, "hs1")
MAKE_PSTR(tag_hs2, "hs2")
MAKE_PSTR(tag_hs3, "hs3")
MAKE_PSTR(tag_hs4, "hs4")
MAKE_PSTR(tag_hs5, "hs5")
MAKE_PSTR(tag_hs6, "hs6")
MAKE_PSTR(tag_hs7, "hs7")
MAKE_PSTR(tag_hs8, "hs8")
MAKE_PSTR(tag_hs9, "hs9")
MAKE_PSTR(tag_hs10, "hs10")
MAKE_PSTR(tag_hs11, "hs11")
MAKE_PSTR(tag_hs12, "hs12")
MAKE_PSTR(tag_hs13, "hs13")
MAKE_PSTR(tag_hs14, "hs14")
MAKE_PSTR(tag_hs15, "hs15")
MAKE_PSTR(tag_hs16, "hs16")
// MQTT topic names
MAKE_PSTR(tag_heartbeat_mqtt, "heartbeat")
MAKE_PSTR(tag_boiler_data_mqtt, "")
MAKE_PSTR(tag_boiler_data_ww_mqtt, "ww")
enum DeviceValueTAG : uint8_t { enum DeviceValueTAG : uint8_t {
TAG_NONE = 0, // wild card TAG_NONE = 0, // wild card
TAG_HEARTBEAT, TAG_HEARTBEAT,
TAG_BOILER_DATA, TAG_BOILER_DATA,
TAG_BOILER_DATA_WW, TAG_DEVICE_DATA_WW,
TAG_THERMOSTAT_DATA, TAG_THERMOSTAT_DATA,
TAG_HC1, TAG_HC1,
TAG_HC2, TAG_HC2,
@@ -139,12 +94,10 @@ enum DeviceValueTAG : uint8_t {
}; };
// mqtt flags for command subscriptions // mqtt flags for command subscriptions
enum MqttSubFlag : uint8_t { enum MqttSubFlag : uint8_t { FLAG_NORMAL = 0, FLAG_HC, FLAG_WWC, FLAG_NOSUB };
FLAG_NORMAL = 0,
FLAG_HC, // mqtt-HA flags
FLAG_WWC, enum DeviceValueHA : uint8_t { HA_NONE = 0, HA_VALUE, HA_DONE };
FLAG_NOSUB
};
class EMSdevice { class EMSdevice {
public: public:
@@ -265,6 +218,7 @@ class EMSdevice {
bool handle_telegram(std::shared_ptr<const Telegram> telegram); bool handle_telegram(std::shared_ptr<const Telegram> telegram);
std::string get_value_uom(const char * key); std::string get_value_uom(const char * key);
bool get_value_info(JsonObject & root, const char * cmd, const int8_t id);
bool generate_values_json(JsonObject & json, const uint8_t tag_filter, const bool nested, const bool console = false); bool generate_values_json(JsonObject & json, const uint8_t tag_filter, const bool nested, const bool console = false);
bool generate_values_json_web(JsonObject & json); bool generate_values_json_web(JsonObject & json);
@@ -274,15 +228,22 @@ class EMSdevice {
const __FlashStringHelper * const * options, const __FlashStringHelper * const * options,
const __FlashStringHelper * short_name, const __FlashStringHelper * short_name,
const __FlashStringHelper * full_name, const __FlashStringHelper * full_name,
uint8_t uom = DeviceValueUOM::NONE); uint8_t uom,
bool has_cmd,
int32_t min,
uint32_t max);
void register_device_value(uint8_t tag, void * value_p, uint8_t type, const __FlashStringHelper * const * options, const __FlashStringHelper * const * name, uint8_t uom, cmdfunction_p f, int32_t min, uint32_t max);
void register_device_value(uint8_t tag, void * value_p, uint8_t type, const __FlashStringHelper * const * options, const __FlashStringHelper * const * name, uint8_t uom, cmdfunction_p f);
void register_device_value(uint8_t tag, void * value_p, uint8_t type, const __FlashStringHelper * const * options, const __FlashStringHelper * const * name, uint8_t uom);
// void register_device_value(uint8_t tag, void * value_p, uint8_t type, const __FlashStringHelper * const * options, const __FlashStringHelper * const * name, uint8_t uom, int32_t min, uint32_t max);
void write_command(const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid); void write_command(const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid);
void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid); void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid);
void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value); void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value);
void read_command(const uint16_t type_id); void read_command(const uint16_t type_id, uint8_t offset = 0, uint8_t length = 0);
void register_mqtt_topic(const std::string & topic, mqtt_subfunction_p f); void register_mqtt_topic(const std::string & topic, mqtt_subfunction_p f);
void register_mqtt_cmd(const __FlashStringHelper * cmd, cmdfunction_p f, uint8_t flag = 0); // void register_cmd(const __FlashStringHelper * cmd, cmdfunction_p f, uint8_t flag = 0);
void publish_mqtt_ha_sensor(); void publish_mqtt_ha_sensor();
@@ -300,6 +261,8 @@ class EMSdevice {
ha_config_done_ = v; ha_config_done_ = v;
} }
void ha_config_clear();
enum Brand : uint8_t { enum Brand : uint8_t {
NO_BRAND = 0, // 0 NO_BRAND = 0, // 0
BOSCH, // 1 BOSCH, // 1
@@ -338,6 +301,12 @@ class EMSdevice {
// device flags: The lower 4 bits hold the unique identifier, the upper 4 bits are used for specific flags // device flags: The lower 4 bits hold the unique identifier, the upper 4 bits are used for specific flags
static constexpr uint8_t EMS_DEVICE_FLAG_NONE = 0; static constexpr uint8_t EMS_DEVICE_FLAG_NONE = 0;
// Boiler
static constexpr uint8_t EMS_DEVICE_FLAG_EMS = 1;
static constexpr uint8_t EMS_DEVICE_FLAG_EMSPLUS = 2;
static constexpr uint8_t EMS_DEVICE_FLAG_HT3 = 3;
static constexpr uint8_t EMS_DEVICE_FLAG_HEATPUMP = 4;
// Solar Module // Solar Module
static constexpr uint8_t EMS_DEVICE_FLAG_SM10 = 1; static constexpr uint8_t EMS_DEVICE_FLAG_SM10 = 1;
static constexpr uint8_t EMS_DEVICE_FLAG_SM100 = 2; static constexpr uint8_t EMS_DEVICE_FLAG_SM100 = 2;
@@ -354,13 +323,14 @@ class EMSdevice {
static constexpr uint8_t EMS_DEVICE_FLAG_EASY = 1; static constexpr uint8_t EMS_DEVICE_FLAG_EASY = 1;
static constexpr uint8_t EMS_DEVICE_FLAG_RC10 = 2; static constexpr uint8_t EMS_DEVICE_FLAG_RC10 = 2;
static constexpr uint8_t EMS_DEVICE_FLAG_RC20 = 3; static constexpr uint8_t EMS_DEVICE_FLAG_RC20 = 3;
static constexpr uint8_t EMS_DEVICE_FLAG_RC20_2 = 4; // Variation on RC20, Older, like ES72 static constexpr uint8_t EMS_DEVICE_FLAG_RC20_N = 4; // Variation on RC20, Older, like ES72
static constexpr uint8_t EMS_DEVICE_FLAG_RC30_1 = 5; // variation on RC30, Newer models static constexpr uint8_t EMS_DEVICE_FLAG_RC30_N = 5; // variation on RC30, Newer models
static constexpr uint8_t EMS_DEVICE_FLAG_RC30 = 6; static constexpr uint8_t EMS_DEVICE_FLAG_RC30 = 6;
static constexpr uint8_t EMS_DEVICE_FLAG_RC35 = 7; static constexpr uint8_t EMS_DEVICE_FLAG_RC35 = 7;
static constexpr uint8_t EMS_DEVICE_FLAG_RC300 = 8; static constexpr uint8_t EMS_DEVICE_FLAG_RC300 = 8;
static constexpr uint8_t EMS_DEVICE_FLAG_RC100 = 9; static constexpr uint8_t EMS_DEVICE_FLAG_RC100 = 9;
static constexpr uint8_t EMS_DEVICE_FLAG_JUNKERS = 10; static constexpr uint8_t EMS_DEVICE_FLAG_JUNKERS = 10;
static constexpr uint8_t EMS_DEVICE_FLAG_CRF = 11; // CRF200 only monitor
void reserve_device_values(uint8_t elements) { void reserve_device_values(uint8_t elements) {
devicevalues_.reserve(elements); devicevalues_.reserve(elements);
@@ -407,6 +377,10 @@ class EMSdevice {
const __FlashStringHelper * short_name; // used in MQTT const __FlashStringHelper * short_name; // used in MQTT
const __FlashStringHelper * full_name; // used in Web and Console const __FlashStringHelper * full_name; // used in Web and Console
uint8_t uom; // DeviceValueUOM::* uint8_t uom; // DeviceValueUOM::*
uint8_t ha; // DevcieValueHA::
bool has_cmd; // true if there is a Console/MQTT command which matches the short_name
int32_t min;
uint32_t max;
DeviceValue(uint8_t device_type, DeviceValue(uint8_t device_type,
uint8_t tag, uint8_t tag,
@@ -416,7 +390,11 @@ class EMSdevice {
uint8_t options_size, uint8_t options_size,
const __FlashStringHelper * short_name, const __FlashStringHelper * short_name,
const __FlashStringHelper * full_name, const __FlashStringHelper * full_name,
uint8_t uom) uint8_t uom,
uint8_t ha,
bool has_cmd,
int32_t min,
uint32_t max)
: device_type(device_type) : device_type(device_type)
, tag(tag) , tag(tag)
, value_p(value_p) , value_p(value_p)
@@ -425,7 +403,11 @@ class EMSdevice {
, options_size(options_size) , options_size(options_size)
, short_name(short_name) , short_name(short_name)
, full_name(full_name) , full_name(full_name)
, uom(uom) { , uom(uom)
, ha(ha)
, has_cmd(has_cmd)
, min(min)
, max(max) {
} }
}; };
const std::vector<DeviceValue> devicevalues() const; const std::vector<DeviceValue> devicevalues() const;

View File

@@ -423,7 +423,7 @@ void EMSESP::reset_mqtt_ha() {
} }
for (const auto & emsdevice : emsdevices) { for (const auto & emsdevice : emsdevices) {
emsdevice->ha_config_done(false); emsdevice->ha_config_clear();
} }
dallassensor_.reload(); dallassensor_.reload();
} }
@@ -435,13 +435,13 @@ void EMSESP::publish_device_values(uint8_t device_type) {
JsonObject json = doc.to<JsonObject>(); JsonObject json = doc.to<JsonObject>();
bool need_publish = false; bool need_publish = false;
bool nested = Mqtt::nested_format(); uint8_t nested = Mqtt::nested_format();
// group by device type // group by device type
for (const auto & emsdevice : emsdevices) { for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_type)) { if (emsdevice && (emsdevice->device_type() == device_type)) {
// if we're using HA and it's not already done, send the config topics first. only do this once // if we're using HA, done is checked for each sensor in devices
if (Mqtt::ha_enabled() && (!emsdevice->ha_config_done())) { if (Mqtt::ha_enabled()) {
emsdevice->publish_mqtt_ha_sensor(); // create the configs for each value as a sensor emsdevice->publish_mqtt_ha_sensor(); // create the configs for each value as a sensor
} }
@@ -450,8 +450,8 @@ void EMSESP::publish_device_values(uint8_t device_type) {
emsdevice->generate_values_json(json, DeviceValueTAG::TAG_BOILER_DATA, false); emsdevice->generate_values_json(json, DeviceValueTAG::TAG_BOILER_DATA, false);
Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_BOILER_DATA), json); Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_BOILER_DATA), json);
json.clear(); json.clear();
emsdevice->generate_values_json(json, DeviceValueTAG::TAG_BOILER_DATA_WW, false); emsdevice->generate_values_json(json, DeviceValueTAG::TAG_DEVICE_DATA_WW, false);
Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_BOILER_DATA_WW), json); Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_DEVICE_DATA_WW), json);
need_publish = false; need_publish = false;
} }
@@ -511,6 +511,10 @@ void EMSESP::publish_other_values() {
} }
void EMSESP::publish_sensor_values(const bool time, const bool force) { void EMSESP::publish_sensor_values(const bool time, const bool force) {
if (!dallas_enabled()) {
return;
}
if (dallassensor_.updated_values() || time || force) { if (dallassensor_.updated_values() || time || force) {
dallassensor_.publish_values(force); dallassensor_.publish_values(force);
} }
@@ -540,7 +544,37 @@ void EMSESP::publish_response(std::shared_ptr<const Telegram> telegram) {
doc["value"] = value; doc["value"] = value;
} }
Mqtt::publish(F("response"), doc.as<JsonObject>()); Mqtt::publish(F_(response), doc.as<JsonObject>());
}
bool EMSESP::get_device_value_info(JsonObject & root, const char * cmd, const int8_t id, const uint8_t devicetype) {
for (const auto & emsdevice : emsdevices) {
if (emsdevice->device_type() == devicetype) {
return emsdevice->get_value_info(root, cmd, id);
}
}
if (devicetype == DeviceType::DALLASSENSOR) {
uint8_t i = 1;
for (const auto & sensor : EMSESP::sensor_devices()) {
char sensorID[10];
snprintf_P(sensorID, 10, PSTR("sensor%d"), i++);
if ((strcmp(cmd, sensorID) == 0) || (strcmp(cmd, Helpers::toLower(sensor.to_string()).c_str()) == 0)) {
root["name"] = sensor.to_string();
if (Helpers::hasValue(sensor.temperature_c)) {
root["value"] = (float)(sensor.temperature_c) / 10;
}
root["type"] = F_(number);
root["min"] = -55;
root["max"] = 125;
root["unit"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES);
root["writeable"] = false;
return true;
}
}
}
return false;
} }
// search for recognized device_ids : Me, All, otherwise print hex value // search for recognized device_ids : Me, All, otherwise print hex value
@@ -947,14 +981,9 @@ bool EMSESP::command_info(uint8_t device_type, JsonObject & json, const int8_t i
return has_value; return has_value;
} }
// send a read request, passing it into to the Tx Service, with offset // send a read request, passing it into to the Tx Service, with optional offset and length
void EMSESP::send_read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset) { void EMSESP::send_read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t length) {
txservice_.read_request(type_id, dest, offset); txservice_.read_request(type_id, dest, offset, length);
}
// send a read request, passing it into to the Tx Service, with no offset
void EMSESP::send_read_request(const uint16_t type_id, const uint8_t dest) {
txservice_.read_request(type_id, dest, 0); // 0 = no offset
} }
// sends write request // sends write request

View File

@@ -61,7 +61,7 @@
#define EMSESP_JSON_SIZE_MEDIUM_DYN 1024 // for large json docs, using DynamicJsonDocument #define EMSESP_JSON_SIZE_MEDIUM_DYN 1024 // for large json docs, using DynamicJsonDocument
#define EMSESP_JSON_SIZE_LARGE_DYN 2048 // for very large json docs, using DynamicJsonDocument #define EMSESP_JSON_SIZE_LARGE_DYN 2048 // for very large json docs, using DynamicJsonDocument
#define EMSESP_JSON_SIZE_XLARGE_DYN 4096 // for very very large json docs, using DynamicJsonDocument #define EMSESP_JSON_SIZE_XLARGE_DYN 4096 // for very very large json docs, using DynamicJsonDocument
#define EMSESP_JSON_SIZE_XXLARGE_DYN 5120 // for extra very very large json docs, using DynamicJsonDocument #define EMSESP_JSON_SIZE_XXLARGE_DYN 8192 // for extra very very large json docs, using DynamicJsonDocument
// helpers for callback functions // helpers for callback functions
#define MAKE_PF_CB(__f) [&](std::shared_ptr<const Telegram> t) { __f(t); } // for process function callbacks to register_telegram_type() #define MAKE_PF_CB(__f) [&](std::shared_ptr<const Telegram> t) { __f(t); } // for process function callbacks to register_telegram_type()
@@ -92,8 +92,7 @@ class EMSESP {
static bool process_telegram(std::shared_ptr<const Telegram> telegram); static bool process_telegram(std::shared_ptr<const Telegram> telegram);
static std::string pretty_telegram(std::shared_ptr<const Telegram> telegram); static std::string pretty_telegram(std::shared_ptr<const Telegram> telegram);
static void send_read_request(const uint16_t type_id, const uint8_t dest); static void send_read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset = 0, const uint8_t length = 0);
static void send_read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset);
static void send_write_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid); static void send_write_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid);
static void send_write_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t value); static void send_write_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t value);
static void send_write_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid); static void send_write_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid);
@@ -107,6 +106,8 @@ class EMSESP {
static void actual_master_thermostat(const uint8_t device_id); static void actual_master_thermostat(const uint8_t device_id);
static uint8_t check_master_device(const uint8_t device_id, const uint16_t type_id, const bool read); static uint8_t check_master_device(const uint8_t device_id, const uint16_t type_id, const bool read);
static bool get_device_value_info(JsonObject & root, const char * cmd, const int8_t id, const uint8_t devicetype);
static void show_device_values(uuid::console::Shell & shell); static void show_device_values(uuid::console::Shell & shell);
static void show_sensor_values(uuid::console::Shell & shell); static void show_sensor_values(uuid::console::Shell & shell);
@@ -129,6 +130,10 @@ class EMSESP {
return dallassensor_.fails(); return dallassensor_.fails();
} }
static bool dallas_enabled() {
return (dallassensor_.dallas_enabled());
}
enum Watch : uint8_t { WATCH_OFF, WATCH_ON, WATCH_RAW, WATCH_UNKNOWN }; enum Watch : uint8_t { WATCH_OFF, WATCH_ON, WATCH_RAW, WATCH_UNKNOWN };
static void watch_id(uint16_t id); static void watch_id(uint16_t id);
static uint16_t watch_id() { static uint16_t watch_id() {

View File

@@ -22,6 +22,7 @@
#include "mqtt.h" #include "mqtt.h"
#include "dallassensor.h" #include "dallassensor.h"
#include "version.h" #include "version.h"
#include "default_settings.h"
// forward declarators // forward declarators
// used to bind EMS-ESP functions to external frameworks // used to bind EMS-ESP functions to external frameworks

View File

@@ -126,9 +126,9 @@ char * Helpers::smallitoa(char * result, const uint16_t value) {
char * Helpers::render_boolean(char * result, bool value) { char * Helpers::render_boolean(char * result, bool value) {
uint8_t bool_format_ = Mqtt::bool_format(); uint8_t bool_format_ = Mqtt::bool_format();
if (bool_format_ == BOOL_FORMAT_ONOFF) { if (bool_format_ == BOOL_FORMAT_ONOFF) {
strlcpy(result, value ? "on" : "off", 5); strlcpy(result, value ? uuid::read_flash_string(F_(on)).c_str() : uuid::read_flash_string(F_(off)).c_str(), 5);
} else if (bool_format_ == BOOL_FORMAT_ONOFF_CAP) { } else if (bool_format_ == BOOL_FORMAT_ONOFF_CAP) {
strlcpy(result, value ? "ON" : "OFF", 5); strlcpy(result, value ? uuid::read_flash_string(F_(ON)).c_str() : uuid::read_flash_string(F_(OFF)).c_str(), 5);
} else if (bool_format_ == BOOL_FORMAT_TRUEFALSE) { } else if (bool_format_ == BOOL_FORMAT_TRUEFALSE) {
strlcpy(result, value ? "true" : "false", 7); strlcpy(result, value ? "true" : "false", 7);
} else { } else {
@@ -457,12 +457,12 @@ bool Helpers::value2bool(const char * v, bool & value) {
std::string bool_str = toLower(v); // convert to lower case std::string bool_str = toLower(v); // convert to lower case
if ((bool_str == "on") || (bool_str == "1") or (bool_str == "true")) { if ((bool_str == uuid::read_flash_string(F_(on))) || (bool_str == "1") or (bool_str == "true")) {
value = true; value = true;
return true; // is a bool return true; // is a bool
} }
if ((bool_str == "off") || (bool_str == "0") or (bool_str == "false")) { if ((bool_str == uuid::read_flash_string(F_(off))) || (bool_str == "0") or (bool_str == "false")) {
value = false; value = false;
return true; // is a bool return true; // is a bool
} }
@@ -471,14 +471,14 @@ bool Helpers::value2bool(const char * v, bool & value) {
} }
// checks to see if a string is member of a vector and return the index, also allow true/false for on/off // checks to see if a string is member of a vector and return the index, also allow true/false for on/off
bool Helpers::value2enum(const char * v, uint8_t & value, const flash_string_vector & strs) { bool Helpers::value2enum(const char * v, uint8_t & value, const __FlashStringHelper * const * strs) {
if ((v == nullptr) || (strlen(v) == 0)) { if ((v == nullptr) || (strlen(v) == 0)) {
return false; return false;
} }
std::string str = toLower(v); std::string str = toLower(v);
for (value = 0; value < strs.size(); value++) { for (value = 0; strs[value]; value++) {
std::string str1 = uuid::read_flash_string(strs[value]); std::string str1 = toLower(uuid::read_flash_string(strs[value]));
if ((str1 == "off" && str == "false") || (str1 == "on" && str == "true") || (str == str1) || (v[0] == ('0' + value) && v[1] == '\0')) { if ((str1 == uuid::read_flash_string(F_(off)) && str == "false") || (str1 == uuid::read_flash_string(F_(on)) && str == "true") || (str == str1) || (v[0] == ('0' + value) && v[1] == '\0')) {
return true; return true;
} }
} }

View File

@@ -65,7 +65,7 @@ class Helpers {
static bool value2float(const char * v, float & value); static bool value2float(const char * v, float & value);
static bool value2bool(const char * v, bool & value); static bool value2bool(const char * v, bool & value);
static bool value2string(const char * v, std::string & value); static bool value2string(const char * v, std::string & value);
static bool value2enum(const char * v, uint8_t & value, const flash_string_vector & strs); static bool value2enum(const char * v, uint8_t & value, const __FlashStringHelper * const * strs);
#ifdef EMSESP_STANDALONE #ifdef EMSESP_STANDALONE
static char * ultostr(char * ptr, uint32_t value, const uint8_t base); static char * ultostr(char * ptr, uint32_t value, const uint8_t base);

View File

@@ -95,22 +95,25 @@ MAKE_PSTR_WORD(generic)
MAKE_PSTR_WORD(dallassensor) MAKE_PSTR_WORD(dallassensor)
MAKE_PSTR_WORD(unknown) MAKE_PSTR_WORD(unknown)
// strings // format strings
MAKE_PSTR(EMSESP, "EMS-ESP") MAKE_PSTR(EMSESP, "EMS-ESP")
MAKE_PSTR(master_thermostat_fmt, "Master Thermostat Device ID = %s") MAKE_PSTR(master_thermostat_fmt, "Master Thermostat Device ID: %s")
MAKE_PSTR(host_fmt, "Host = %s") MAKE_PSTR(host_fmt, "Host: %s")
MAKE_PSTR(port_fmt, "Port = %d") MAKE_PSTR(port_fmt, "Port: %d")
MAKE_PSTR(hostname_fmt, "Hostname = %s") MAKE_PSTR(hostname_fmt, "Hostname: %s")
MAKE_PSTR(board_profile_fmt, "Board Profile = %s") MAKE_PSTR(board_profile_fmt, "Board Profile: %s")
MAKE_PSTR(mark_interval_fmt, "Mark interval = %lus") MAKE_PSTR(mark_interval_fmt, "Mark interval: %lus")
MAKE_PSTR(wifi_ssid_fmt, "WiFi SSID = %s") MAKE_PSTR(wifi_ssid_fmt, "WiFi SSID: %s")
MAKE_PSTR(wifi_password_fmt, "WiFi Password = %S") MAKE_PSTR(wifi_password_fmt, "WiFi Password: %S")
MAKE_PSTR(ethernet_option_fmt, "Ethernet option = %d") MAKE_PSTR(ethernet_option_fmt, "Ethernet option: %d")
MAKE_PSTR(tx_mode_fmt, "Tx mode: %d")
MAKE_PSTR(bus_id_fmt, "Bus ID: %02X")
MAKE_PSTR(log_level_fmt, "Log level: %s")
//strings
MAKE_PSTR(cmd_optional, "[cmd]") MAKE_PSTR(cmd_optional, "[cmd]")
MAKE_PSTR(ha_optional, "[ha]") MAKE_PSTR(ha_optional, "[ha]")
MAKE_PSTR(deep_optional, "[deep]") MAKE_PSTR(deep_optional, "[deep]")
MAKE_PSTR(tx_mode_fmt, "Tx mode = %d")
MAKE_PSTR(bus_id_fmt, "Bus ID = %02X")
MAKE_PSTR(watchid_optional, "[ID]") MAKE_PSTR(watchid_optional, "[ID]")
MAKE_PSTR(watch_format_optional, "[off | on | raw | unknown]") MAKE_PSTR(watch_format_optional, "[off | on | raw | unknown]")
MAKE_PSTR(invalid_watch, "Invalid watch type") MAKE_PSTR(invalid_watch, "Invalid watch type")
@@ -124,7 +127,6 @@ MAKE_PSTR(typeid_mandatory, "<type ID>")
MAKE_PSTR(deviceid_mandatory, "<device ID>") MAKE_PSTR(deviceid_mandatory, "<device ID>")
MAKE_PSTR(device_type_optional, "[device]") MAKE_PSTR(device_type_optional, "[device]")
MAKE_PSTR(invalid_log_level, "Invalid log level") MAKE_PSTR(invalid_log_level, "Invalid log level")
MAKE_PSTR(log_level_fmt, "Log level = %s")
MAKE_PSTR(log_level_optional, "[level]") MAKE_PSTR(log_level_optional, "[level]")
MAKE_PSTR(name_mandatory, "<name>") MAKE_PSTR(name_mandatory, "<name>")
MAKE_PSTR(name_optional, "[name]") MAKE_PSTR(name_optional, "[name]")
@@ -133,16 +135,69 @@ MAKE_PSTR(new_password_prompt2, "Retype new password: ")
MAKE_PSTR(password_prompt, "Password: ") MAKE_PSTR(password_prompt, "Password: ")
MAKE_PSTR(unset, "<unset>") MAKE_PSTR(unset, "<unset>")
MAKE_PSTR_WORD(2); MAKE_PSTR_WORD(number)
MAKE_PSTR_WORD(10); MAKE_PSTR_WORD(enum)
MAKE_PSTR_WORD(100); MAKE_PSTR_WORD(text)
MAKE_PSTR_WORD(60);
MAKE_PSTR_WORD(2)
MAKE_PSTR_WORD(10)
MAKE_PSTR_WORD(100)
MAKE_PSTR_WORD(60)
MAKE_PSTR_LIST(div2, F_(2)) MAKE_PSTR_LIST(div2, F_(2))
MAKE_PSTR_LIST(div10, F_(10)) MAKE_PSTR_LIST(div10, F_(10))
MAKE_PSTR_LIST(div100, F_(100)) MAKE_PSTR_LIST(div100, F_(100))
MAKE_PSTR_LIST(div60, F_(60)) MAKE_PSTR_LIST(div60, F_(60))
// Unit Of Measurement mapping - maps to DeviceValueUOM_s in emsdevice.cpp
// uom - also used with HA
MAKE_PSTR(percent, "%")
MAKE_PSTR(degrees, "°C")
MAKE_PSTR(kwh, "kWh")
MAKE_PSTR(wh, "Wh")
MAKE_PSTR(bar, "bar")
MAKE_PSTR(minutes, "minutes")
MAKE_PSTR(hours, "hours")
MAKE_PSTR(ua, "uA")
MAKE_PSTR(lmin, "l/min")
MAKE_PSTR(kw, "kW")
// TAG mapping - maps to DeviceValueTAG_s in emsdevice.cpp
// use empty string if want to suppress showing tags
MAKE_PSTR(tag_none, "")
MAKE_PSTR(tag_heartbeat, "")
MAKE_PSTR(tag_boiler_data, "")
MAKE_PSTR(tag_device_data_ww, "warm water")
MAKE_PSTR(tag_thermostat_data, "")
MAKE_PSTR(tag_hc1, "hc1")
MAKE_PSTR(tag_hc2, "hc2")
MAKE_PSTR(tag_hc3, "hc3")
MAKE_PSTR(tag_hc4, "hc4")
MAKE_PSTR(tag_wwc1, "wwc1")
MAKE_PSTR(tag_wwc2, "wwc2")
MAKE_PSTR(tag_wwc3, "wwc3")
MAKE_PSTR(tag_wwc4, "wwc4")
MAKE_PSTR(tag_hs1, "hs1")
MAKE_PSTR(tag_hs2, "hs2")
MAKE_PSTR(tag_hs3, "hs3")
MAKE_PSTR(tag_hs4, "hs4")
MAKE_PSTR(tag_hs5, "hs5")
MAKE_PSTR(tag_hs6, "hs6")
MAKE_PSTR(tag_hs7, "hs7")
MAKE_PSTR(tag_hs8, "hs8")
MAKE_PSTR(tag_hs9, "hs9")
MAKE_PSTR(tag_hs10, "hs10")
MAKE_PSTR(tag_hs11, "hs11")
MAKE_PSTR(tag_hs12, "hs12")
MAKE_PSTR(tag_hs13, "hs13")
MAKE_PSTR(tag_hs14, "hs14")
MAKE_PSTR(tag_hs15, "hs15")
MAKE_PSTR(tag_hs16, "hs16")
// MQTT topic names
// MAKE_PSTR(tag_heartbeat_mqtt, "heartbeat")
MAKE_PSTR(tag_boiler_data_mqtt, "")
MAKE_PSTR(tag_device_data_ww_mqtt, "ww")
// boiler // boiler
MAKE_PSTR_WORD(time) MAKE_PSTR_WORD(time)
@@ -153,7 +208,7 @@ MAKE_PSTR_WORD(3x3min)
MAKE_PSTR_WORD(4x3min) MAKE_PSTR_WORD(4x3min)
MAKE_PSTR_WORD(5x3min) MAKE_PSTR_WORD(5x3min)
MAKE_PSTR_WORD(6x3min) MAKE_PSTR_WORD(6x3min)
MAKE_PSTR_WORD(continuos); MAKE_PSTR_WORD(continuos)
MAKE_PSTR(3wayvalve, "3-way valve") MAKE_PSTR(3wayvalve, "3-way valve")
MAKE_PSTR(chargepump, "charge pump") MAKE_PSTR(chargepump, "charge pump")
MAKE_PSTR_WORD(hot) MAKE_PSTR_WORD(hot)
@@ -163,6 +218,8 @@ MAKE_PSTR_WORD(flow)
MAKE_PSTR_WORD(buffer) MAKE_PSTR_WORD(buffer)
MAKE_PSTR(bufferedflow, "buffered flow") MAKE_PSTR(bufferedflow, "buffered flow")
MAKE_PSTR(layeredbuffer, "layered buffer") MAKE_PSTR(layeredbuffer, "layered buffer")
MAKE_PSTR_WORD(maintenance)
MAKE_PSTR_WORD(error)
// boiler lists // boiler lists
MAKE_PSTR_LIST(enum_off_time_date, F_(off), F_(time), F_(date)) MAKE_PSTR_LIST(enum_off_time_date, F_(off), F_(time), F_(date))
@@ -170,6 +227,7 @@ MAKE_PSTR_LIST(enum_freq, F_(off), F_(1x3min), F_(2x3min), F_(3x3min), F_(4x3min
MAKE_PSTR_LIST(enum_charge, F_(3wayvalve), F_(chargepump)) MAKE_PSTR_LIST(enum_charge, F_(3wayvalve), F_(chargepump))
MAKE_PSTR_LIST(enum_comfort, F_(hot), F_(eco), F_(intelligent)) MAKE_PSTR_LIST(enum_comfort, F_(hot), F_(eco), F_(intelligent))
MAKE_PSTR_LIST(enum_flow, F_(off), F_(flow), F_(bufferedflow), F_(buffer), F_(layeredbuffer)) MAKE_PSTR_LIST(enum_flow, F_(off), F_(flow), F_(bufferedflow), F_(buffer), F_(layeredbuffer))
MAKE_PSTR_LIST(enum_reset, F_(maintenance), F_(error))
// thermostat // thermostat
MAKE_PSTR_WORD(light) MAKE_PSTR_WORD(light)
@@ -182,10 +240,10 @@ MAKE_PSTR_WORD(heat)
MAKE_PSTR_WORD(hold) MAKE_PSTR_WORD(hold)
MAKE_PSTR_WORD(cool) MAKE_PSTR_WORD(cool)
MAKE_PSTR_WORD(end) MAKE_PSTR_WORD(end)
MAKE_PSTR_WORD(German) MAKE_PSTR_WORD(german)
MAKE_PSTR_WORD(Dutch) MAKE_PSTR_WORD(dutch)
MAKE_PSTR_WORD(French) MAKE_PSTR_WORD(french)
MAKE_PSTR_WORD(Italian) MAKE_PSTR_WORD(italian)
MAKE_PSTR_WORD(high) MAKE_PSTR_WORD(high)
MAKE_PSTR_WORD(low) MAKE_PSTR_WORD(low)
MAKE_PSTR_WORD(radiator) MAKE_PSTR_WORD(radiator)
@@ -194,11 +252,12 @@ MAKE_PSTR_WORD(floor)
MAKE_PSTR_WORD(summer) MAKE_PSTR_WORD(summer)
MAKE_PSTR_WORD(winter) MAKE_PSTR_WORD(winter)
MAKE_PSTR_WORD(outdoor) MAKE_PSTR_WORD(outdoor)
MAKE_PSTR_WORD(MPC) MAKE_PSTR_WORD(mpc)
MAKE_PSTR_WORD(room) MAKE_PSTR_WORD(room)
MAKE_PSTR_WORD(power) MAKE_PSTR_WORD(power)
MAKE_PSTR_WORD(constant) MAKE_PSTR_WORD(constant)
MAKE_PSTR_WORD(simple) MAKE_PSTR_WORD(simple)
MAKE_PSTR_WORD(optimized)
MAKE_PSTR_WORD(nofrost) MAKE_PSTR_WORD(nofrost)
MAKE_PSTR_WORD(comfort) MAKE_PSTR_WORD(comfort)
MAKE_PSTR_WORD(manual) MAKE_PSTR_WORD(manual)
@@ -206,22 +265,31 @@ MAKE_PSTR_WORD(night)
MAKE_PSTR_WORD(day) MAKE_PSTR_WORD(day)
MAKE_PSTR_WORD(holiday) MAKE_PSTR_WORD(holiday)
MAKE_PSTR_WORD(reduce) MAKE_PSTR_WORD(reduce)
MAKE_PSTR_WORD(noreduce)
MAKE_PSTR_WORD(offset)
MAKE_PSTR_WORD(design)
MAKE_PSTR_WORD(tempauto)
MAKE_PSTR_WORD(minflow)
MAKE_PSTR_WORD(maxflow)
MAKE_PSTR_WORD(rc3x)
MAKE_PSTR_WORD(rc20)
MAKE_PSTR(internal_temperature, "internal temperature") MAKE_PSTR(internal_temperature, "internal temperature")
MAKE_PSTR(internal_setpoint, "internal setpoint") MAKE_PSTR(internal_setpoint, "internal setpoint")
MAKE_PSTR(external_temperature, "external temperature") MAKE_PSTR(external_temperature, "external temperature")
MAKE_PSTR(burner_temperature, "burner temperature") MAKE_PSTR(burner_temperature, "burner temperature")
MAKE_PSTR(WW_temperature, "WW temperature") MAKE_PSTR(ww_temperature, "ww temperature")
MAKE_PSTR(functioning_mode, "functioning mode") MAKE_PSTR(functioning_mode, "functioning mode")
MAKE_PSTR(smoke_temperature, "smoke temperature") MAKE_PSTR(smoke_temperature, "smoke temperature")
// thermostat lists // thermostat lists
MAKE_PSTR_LIST(enum_ibaMainDisplay, F_(internal_temperature), F_(internal_setpoint), F_(external_temperature), F_(burner_temperature), F_(WW_temperature), F_(functioning_mode), F_(time), F_(date), F_(smoke_temperature)) MAKE_PSTR_LIST(enum_ibaMainDisplay, F_(internal_temperature), F_(internal_setpoint), F_(external_temperature), F_(burner_temperature), F_(ww_temperature), F_(functioning_mode), F_(time), F_(date), F_(smoke_temperature))
MAKE_PSTR_LIST(enum_ibaLanguage, F_(German), F_(Dutch), F_(French), F_(Italian)) MAKE_PSTR_LIST(enum_ibaLanguage, F_(german), F_(dutch), F_(french), F_(italian))
MAKE_PSTR_LIST(enum_floordrystatus, F_(off), F_(start), F_(heat), F_(hold), F_(cool), F_(end)) MAKE_PSTR_LIST(enum_floordrystatus, F_(off), F_(start), F_(heat), F_(hold), F_(cool), F_(end))
MAKE_PSTR_LIST(enum_ibaBuildingType, F_(blank), F_(light), F_(medium), F_(heavy)) MAKE_PSTR_LIST(enum_ibaBuildingType, F_(blank), F_(light), F_(medium), F_(heavy)) // RC300
MAKE_PSTR_LIST(enum_wwMode, F_(off), F_(low), F_(high), F_(auto), F_(own_prog)) MAKE_PSTR_LIST(enum_wwMode, F_(off), F_(low), F_(high), F_(auto), F_(own_prog))
MAKE_PSTR_LIST(enum_wwCircMode, F_(off), F_(on), F_(auto), F_(own_prog)) MAKE_PSTR_LIST(enum_wwCircMode, F_(off), F_(on), F_(auto), F_(own_prog))
MAKE_PSTR_LIST(enum_ibaBuildingType2, F_(light), F_(medium), F_(heavy)) MAKE_PSTR_LIST(enum_ibaBuildingType2, F_(light), F_(medium), F_(heavy)) // RC30, RC35
MAKE_PSTR_LIST(enum_wwMode2, F_(off), F_(on), F_(auto)) MAKE_PSTR_LIST(enum_wwMode2, F_(off), F_(on), F_(auto))
MAKE_PSTR_LIST(enum_wwCircMode2, F_(off), F_(on), F_(auto)) MAKE_PSTR_LIST(enum_wwCircMode2, F_(off), F_(on), F_(auto))
MAKE_PSTR_LIST(enum_heatingtype, F_(off), F_(radiator), F_(convector), F_(floor)) MAKE_PSTR_LIST(enum_heatingtype, F_(off), F_(radiator), F_(convector), F_(floor))
@@ -231,15 +299,262 @@ MAKE_PSTR_LIST(enum_mode, F_(manual), F_(auto))
MAKE_PSTR_LIST(enum_mode2, F_(off), F_(manual), F_(auto)) MAKE_PSTR_LIST(enum_mode2, F_(off), F_(manual), F_(auto))
MAKE_PSTR_LIST(enum_mode3, F_(night), F_(day), F_(auto)) MAKE_PSTR_LIST(enum_mode3, F_(night), F_(day), F_(auto))
MAKE_PSTR_LIST(enum_mode4, F_(blank), F_(manual), F_(auto), F_(holiday)) MAKE_PSTR_LIST(enum_mode4, F_(blank), F_(manual), F_(auto), F_(holiday))
MAKE_PSTR_LIST(enum_mode5, F_(auto), F_(off))
MAKE_PSTR_LIST(enum_modetype, F_(eco), F_(comfort)) MAKE_PSTR_LIST(enum_modetype, F_(eco), F_(comfort))
MAKE_PSTR_LIST(enum_modetype2, F_(day)) MAKE_PSTR_LIST(enum_modetype2, F_(day))
MAKE_PSTR_LIST(enum_modetype3, F_(night), F_(day)) MAKE_PSTR_LIST(enum_modetype3, F_(night), F_(day))
MAKE_PSTR_LIST(enum_modetype4, F_(blank), F_(nofrost), F_(eco), F_(heat)) MAKE_PSTR_LIST(enum_modetype4, F_(blank), F_(nofrost), F_(eco), F_(heat))
MAKE_PSTR_LIST(enum_modetype5, F_(off), F_(on))
MAKE_PSTR_LIST(enum_reducemode, F_(nofrost), F_(reduce), F_(room), F_(outdoor)) MAKE_PSTR_LIST(enum_reducemode, F_(nofrost), F_(reduce), F_(room), F_(outdoor))
MAKE_PSTR_LIST(enum_controlmode, F_(off), F_(outdoor), F_(simple), F_(MPC), F_(room), F_(power), F_(constant)) MAKE_PSTR_LIST(enum_controlmode, F_(off), F_(optimized), F_(simple), F_(mpc), F_(room), F_(power), F_(constant))
MAKE_PSTR_LIST(enum_controlmode2, F_(outdoor), F_(room)) MAKE_PSTR_LIST(enum_controlmode2, F_(outdoor), F_(room))
MAKE_PSTR_LIST(enum_controlmode3, F_(off), F_(room), F_(outdoor), F("room+outdoor"))
MAKE_PSTR_LIST(enum_control, F_(off), F_(rc20), F_(rc3x))
MAKE_PSTR_LIST(enum_hamode, F_(off), F_(heat), F_(auto), F_(heat), F_(off), F_(heat), F_(auto), F_(auto), F_(auto), F_(auto)) MAKE_PSTR_LIST(enum_hamode, F_(off), F_(heat), F_(auto), F_(heat), F_(off), F_(heat), F_(auto), F_(auto), F_(auto), F_(auto))
/*
* MQTT topics and full text for values and commands
*/
MAKE_PSTR(homeassistant, "homeassistant/")
// id for all devices
// empty full name to prevent being shown in web or console
MAKE_PSTR_LIST(ID, F_(id))
// Boiler
// extra commands, no output
MAKE_PSTR_LIST(wwtapactivated, F("wwtapactivated"))
MAKE_PSTR_LIST(reset, F("reset"))
// single mqtt topics
MAKE_PSTR_WORD(heating_active)
MAKE_PSTR_WORD(tapwater_active)
MAKE_PSTR_WORD(response)
// mqtt, commands and text
MAKE_PSTR_LIST(heatingActive, F("heatingactive"), F("heating active"))
MAKE_PSTR_LIST(tapwaterActive, F("tapwateractive"), F("warm water active"))
MAKE_PSTR_LIST(selFlowTemp, F("selflowtemp"), F("selected flow temperature"))
MAKE_PSTR_LIST(selBurnPow, F("selburnpow"), F("burner selected max power"))
MAKE_PSTR_LIST(heatingPumpMod, F("heatingpumpmod"), F("heating pump modulation"))
MAKE_PSTR_LIST(heatingPump2Mod, F("heatingpump2mod"), F("heating pump 2 modulation"))
MAKE_PSTR_LIST(outdoorTemp, F("outdoortemp"), F("outside temperature"))
MAKE_PSTR_LIST(curFlowTemp, F("curflowtemp"), F("current flow temperature"))
MAKE_PSTR_LIST(retTemp, F("rettemp"), F("return temperature"))
MAKE_PSTR_LIST(switchTemp, F("switchtemp"), F("mixing switch temperature"))
MAKE_PSTR_LIST(sysPress, F("syspress"), F("system pressure"))
MAKE_PSTR_LIST(boilTemp, F("boiltemp"), F("max boiler temperature"))
MAKE_PSTR_LIST(exhaustTemp, F("exhausttemp"), F("exhaust temperature"))
MAKE_PSTR_LIST(burnGas, F("burngas"), F("gas"))
MAKE_PSTR_LIST(flameCurr, F("flamecurr"), F("flame current"))
MAKE_PSTR_LIST(heatingPump, F("heatingpump"), F("heating pump"))
MAKE_PSTR_LIST(fanWork, F("fanwork"), F("fan"))
MAKE_PSTR_LIST(ignWork, F("ignwork"), F("ignition"))
MAKE_PSTR_LIST(heatingActivated, F("heatingactivated"), F("heating activated"))
MAKE_PSTR_LIST(heatingTemp, F("heatingtemp"), F("heating temperature"))
MAKE_PSTR_LIST(pumpModMax, F("pumpmodmax"), F("burner pump max power"))
MAKE_PSTR_LIST(pumpModMin, F("pumpmodmin"), F("burner pump min power"))
MAKE_PSTR_LIST(pumpDelay, F("pumpdelay"), F("pump delay"))
MAKE_PSTR_LIST(burnMinPeriod, F("burnminperiod"), F("burner min period"))
MAKE_PSTR_LIST(burnMinPower, F("burnminpower"), F("burner min power"))
MAKE_PSTR_LIST(burnMaxPower, F("burnmaxpower"), F("burner max power"))
MAKE_PSTR_LIST(boilHystOn, F("boilhyston"), F("hysteresis on temperature"))
MAKE_PSTR_LIST(boilHystOff, F("boilhystoff"), F("hysteresis off temperature"))
MAKE_PSTR_LIST(setFlowTemp, F("setflowtemp"), F("set flow temperature"))
MAKE_PSTR_LIST(setBurnPow, F("setburnpow"), F("burner set power"))
MAKE_PSTR_LIST(curBurnPow, F("curburnpow"), F("burner current power"))
MAKE_PSTR_LIST(burnStarts, F("burnstarts"), F("burner # starts"))
MAKE_PSTR_LIST(burnWorkMin, F("burnworkmin"), F("total burner operating time"))
MAKE_PSTR_LIST(heatWorkMin, F("heatworkmin"), F("total heat operating time"))
MAKE_PSTR_LIST(UBAuptime, F("ubauptime"), F("total UBA operating time"))
MAKE_PSTR_LIST(lastCode, F("lastcode"), F("last error code"))
MAKE_PSTR_LIST(serviceCode, F("servicecode"), F("service code"))
MAKE_PSTR_LIST(serviceCodeNumber, F("servicecodenumber"), F("service code number"))
MAKE_PSTR_LIST(maintenanceMessage, F("maintenancemessage"), F("maintenance message"))
MAKE_PSTR_LIST(maintenanceDate, F("maintenancedate"), F("maintenance set date"))
MAKE_PSTR_LIST(maintenanceType, F_(maintenance), F("maintenance scheduled"))
MAKE_PSTR_LIST(maintenanceTime, F("maintenancetime"), F("maintenance set time"))
MAKE_PSTR_LIST(upTimeControl, F("uptimecontrol"), F("operating time total heat"))
MAKE_PSTR_LIST(upTimeCompHeating, F("uptimecompheating"), F("operating time compressor heating"))
MAKE_PSTR_LIST(upTimeCompCooling, F("uptimecompcooling"), F("operating time compressor cooling"))
MAKE_PSTR_LIST(upTimeCompWw, F("uptimecompww"), F("operating time compressor warm water"))
MAKE_PSTR_LIST(heatingStarts, F("heatingstarts"), F("# heating control starts"))
MAKE_PSTR_LIST(coolingStarts, F("coolingstarts"), F("# cooling control starts"))
MAKE_PSTR_LIST(nrgConsTotal, F("nrgconstotal"), F("total energy consumption"))
MAKE_PSTR_LIST(nrgConsCompTotal, F("nrgconscomptotal"), F("energy consumption compressor total"))
MAKE_PSTR_LIST(nrgConsCompHeating, F("nrgconscompheating"), F("energy consumption compressor heating"))
MAKE_PSTR_LIST(nrgConsCompWw, F("nrgconscompww"), F("energy consumption compressor warm water"))
MAKE_PSTR_LIST(nrgConsCompCooling, F("nrgconscompcooling"), F("energy consumption compressor cooling"))
MAKE_PSTR_LIST(nrgSuppTotal, F("nrgsupptotal"), F("total energy supplied"))
MAKE_PSTR_LIST(nrgSuppHeating, F("nrgsuppheating"), F("total energy supplied heating"))
MAKE_PSTR_LIST(nrgSuppWw, F("nrgsuppww"), F("total energy warm supplied warm water"))
MAKE_PSTR_LIST(nrgSuppCooling, F("nrgsuppcooling"), F("total energy supplied cooling"))
MAKE_PSTR_LIST(auxElecHeatNrgConsTotal, F("auxelecheatnrgconstotal"), F("auxiliary electrical heater energy consumption total"))
MAKE_PSTR_LIST(auxElecHeatNrgConsHeating, F("auxelecheatnrgconsheating"), F("auxiliary electrical heater energy consumption heating"))
MAKE_PSTR_LIST(auxElecHeatNrgConsWW, F("auxelecheatnrgconsww"), F("auxiliary electrical heater energy consumption"))
MAKE_PSTR_LIST(hpPower, F("hppower"), F("heatpump power"))
MAKE_PSTR_LIST(hpTc0, F("hptc0"), F("water temperature condenser inlet (TC0)"))
MAKE_PSTR_LIST(hpTc1, F("hptc1"), F("water temperture condenser output (TC1)"))
MAKE_PSTR_LIST(hpTc3, F("hptc3"), F("condenser temperature (TC3)"))
MAKE_PSTR_LIST(hpTr3, F("hptr3"), F("refrigerant temperature liquid side (condenser output) (TR3)"))
MAKE_PSTR_LIST(hpTr4, F("hptr4"), F("evaporator inlet temperature (TR4)"))
MAKE_PSTR_LIST(hpTr5, F("hptr5"), F("compressor Inlet temperature (TR5)"))
MAKE_PSTR_LIST(hpTr6, F("hptr6"), F("compressor outlet temperature (TR6)"))
MAKE_PSTR_LIST(hpTr7, F("hptr7"), F("refrigerant temperature gas side (condenser input) (TR7)"))
MAKE_PSTR_LIST(hpTl2, F("hptl2"), F("air inlet temperature (TL2)"))
MAKE_PSTR_LIST(hpPl1, F("hppl1"), F("low pressure side temperature (PL1)"))
MAKE_PSTR_LIST(hpPh1, F("hpph1"), F("high pressure side temperature (PH1)"))
MAKE_PSTR_LIST(wWSelTemp, F("wwseltemp"), F("selected temperature"))
MAKE_PSTR_LIST(wWSetTemp, F("wwsettemp"), F("set temperature"))
MAKE_PSTR_LIST(wWType, F("wwtype"), F("type"))
MAKE_PSTR_LIST(wWComfort, F("wwcomfort"), F("comfort"))
MAKE_PSTR_LIST(wWFlowTempOffset, F("wwflowtempoffset"), F("flow temperature offset"))
MAKE_PSTR_LIST(wWMaxPower, F("wwmaxpower"), F("max power"))
MAKE_PSTR_LIST(wWCircPump, F("wwcircpump"), F("circulation pump available"))
MAKE_PSTR_LIST(wWChargeType, F("wwchargetype"), F("charging type"))
MAKE_PSTR_LIST(wWDisinfectionTemp, F("wwdisinfectiontemp"), F("disinfection temperature"))
MAKE_PSTR_LIST(wWCircMode, F("wwcircmode"), F("circulation pump freq"))
MAKE_PSTR_LIST(wWCirc, F("wwcirc"), F("circulation active"))
MAKE_PSTR_LIST(wWCurTemp, F("wwcurtemp"), F("current intern temperature"))
MAKE_PSTR_LIST(wWCurTemp2, F("wwcurtemp2"), F("current extern temperature"))
MAKE_PSTR_LIST(wWCurFlow, F("wwcurflow"), F("current tap water flow"))
MAKE_PSTR_LIST(wWStorageTemp1, F("wwstoragetemp1"), F("storage intern temperature"))
MAKE_PSTR_LIST(wWStorageTemp2, F("wwstoragetemp2"), F("storage extern temperature"))
MAKE_PSTR_LIST(wWActivated, F("wwactivated"), F("activated"))
MAKE_PSTR_LIST(wWOneTime, F("wwonetime"), F("one time charging"))
MAKE_PSTR_LIST(wWDisinfecting, F("wwdisinfecting"), F("disinfecting"))
MAKE_PSTR_LIST(wWCharging, F("wwcharging"), F("charging"))
MAKE_PSTR_LIST(wWRecharging, F("wwrecharging"), F("recharging"))
MAKE_PSTR_LIST(wWTempOK, F("wwtempok"), F("temperature ok"))
MAKE_PSTR_LIST(wWActive, F("wwactive"), F("active"))
MAKE_PSTR_LIST(wWHeat, F("wwheat"), F("heating"))
MAKE_PSTR_LIST(wWSetPumpPower, F("wwsetpumppower"), F("pump set power"))
MAKE_PSTR_LIST(mixerTemp, F("mixertemp"), F("mixer temperature"))
MAKE_PSTR_LIST(tankMiddleTemp, F("tankmiddletemp"), F("tank middle temperature (TS3)"))
MAKE_PSTR_LIST(wWStarts, F("wwstarts"), F("# starts"))
MAKE_PSTR_LIST(wWStarts2, F("wwstarts2"), F("# control starts"))
MAKE_PSTR_LIST(wWWorkM, F("wwworkm"), F("active time"))
// thermostat
// extra commands, no long name, does not show on web
MAKE_PSTR_LIST(switchtime, F("switchtime"))
MAKE_PSTR_LIST(temp, F("temp"))
MAKE_PSTR_LIST(hatemp, F("hatemp"))
MAKE_PSTR_LIST(hamode, F("hamode"))
// mqtt values / commands
MAKE_PSTR_LIST(dateTime, F("datetime"), F("date/time"))
MAKE_PSTR_LIST(errorCode, F("errorcode"), F("error code"))
MAKE_PSTR_LIST(ibaMainDisplay, F("display"), F("display"))
MAKE_PSTR_LIST(ibaLanguage, F("language"), F("language"))
MAKE_PSTR_LIST(ibaClockOffset, F("clockoffset"), F("clock offset"))
MAKE_PSTR_LIST(ibaBuildingType, F("building"), F("building"))
MAKE_PSTR_LIST(ibaCalIntTemperature, F("intoffset"), F("offset internal temperature"))
MAKE_PSTR_LIST(ibaMinExtTemperature, F("minexttemp"), F("min external temperature"))
MAKE_PSTR_LIST(tempsensor1, F("inttemp1"), F("temperature sensor 1"))
MAKE_PSTR_LIST(tempsensor2, F("inttemp2"), F("temperature sensor 2"))
MAKE_PSTR_LIST(dampedoutdoortemp, F("dampedoutdoortemp"), F("damped outdoor temperature"))
MAKE_PSTR_LIST(floordrystatus, F("floordry"), F("floor drying"))
MAKE_PSTR_LIST(dampedoutdoortemp2, F("dampedoutdoortemp"), F("damped outdoor temperature"))
MAKE_PSTR_LIST(floordrytemp, F("floordrytemp"), F("floor drying temperature"))
MAKE_PSTR_LIST(wwMode, F("wwmode"), F("mode"))
MAKE_PSTR_LIST(wwSetTemp, F("wwsettemp"), F("set temperature"))
MAKE_PSTR_LIST(wwSetTempLow, F("wwsettemplow"), F("set temperature low"))
MAKE_PSTR_LIST(wwExtra1, F("wwextra1"), F("circuit 1 extra"))
MAKE_PSTR_LIST(wwExtra2, F("wwextra2"), F("circuit 2 extra"))
MAKE_PSTR_LIST(setpoint_roomTemp, F("seltemp"), F("selected room temperature"))
MAKE_PSTR_LIST(curr_roomTemp, F("currtemp"), F("current room temperature"))
MAKE_PSTR_LIST(mode, F("mode"), F("mode"))
MAKE_PSTR_LIST(modetype, F("modetype"), F("mode type"))
MAKE_PSTR_LIST(daytemp, F("daytemp"), F("day temperature"))
MAKE_PSTR_LIST(heattemp, F("heattemp"), F("heat temperature"))
MAKE_PSTR_LIST(nighttemp, F("nighttemp"), F("night temperature"))
MAKE_PSTR_LIST(ecotemp, F("ecotemp"), F("eco temperature"))
MAKE_PSTR_LIST(manualtemp, F("manualtemp"), F("manual temperature"))
MAKE_PSTR_LIST(tempautotemp, F("tempautotemp"), F("temporary room temperature automode"))
MAKE_PSTR_LIST(comforttemp, F("comforttemp"), F("comfort temperature"))
MAKE_PSTR_LIST(summertemp, F("summertemp"), F("summer temperature"))
MAKE_PSTR_LIST(designtemp, F("designtemp"), F("design temperature"))
MAKE_PSTR_LIST(offsettemp, F("offsettemp"), F("offset temperature"))
MAKE_PSTR_LIST(minflowtemp, F("minflowtemp"), F("min flow temperature"))
MAKE_PSTR_LIST(maxflowtemp, F("maxflowtemp"), F("max flow temperature"))
MAKE_PSTR_LIST(roominfluence, F("roominfluence"), F("room influence"))
MAKE_PSTR_LIST(nofrosttemp, F("nofrosttemp"), F("nofrost temperature"))
MAKE_PSTR_LIST(targetflowtemp, F("targetflowtemp"), F("target flow temperature"))
MAKE_PSTR_LIST(heatingtype, F("heatingtype"), F("heating type"))
MAKE_PSTR_LIST(summersetmode, F("summersetmode"), F("summer set mode"))
MAKE_PSTR_LIST(controlmode, F("controlmode"), F("control mode"))
MAKE_PSTR_LIST(control, F("control"), F("control device"))
MAKE_PSTR_LIST(program, F("program"), F("program"))
MAKE_PSTR_LIST(pause, F("pause"), F("pause time"))
MAKE_PSTR_LIST(party, F("party"), F("party time"))
MAKE_PSTR_LIST(holidaytemp, F("holidaytemp"), F("holiday temperature"))
MAKE_PSTR_LIST(summermode, F("summermode"), F("summer mode"))
MAKE_PSTR_LIST(holidaymode, F("holidaymode"), F("holiday mode"))
MAKE_PSTR_LIST(flowtempoffset, F("flowtempoffset"), F("flow temperature offset"))
MAKE_PSTR_LIST(reducemode, F("reducemode"), F("reduce mode"))
MAKE_PSTR_LIST(noreducetemp, F("noreducetemp"), F("no reduce below temperature"))
MAKE_PSTR_LIST(remotetemp, F("remotetemp"), F("room temperature from remote"))
// heatpump
MAKE_PSTR_LIST(airHumidity, F("airhumidity"), F("relative air humidity"))
MAKE_PSTR_LIST(dewTemperature, F("dewtemperature"), F("dew point temperature"))
// mixer
MAKE_PSTR_LIST(flowSetTemp, F("flowsettemp"), F("setpoint flow temperature"))
MAKE_PSTR_LIST(flowTempHc, F("flowtemphc"), F("flow temperature in assigned hc (TC1)"))
MAKE_PSTR_LIST(pumpStatus, F("pumpstatus"), F("pump status in assigned hc (PC1)"))
MAKE_PSTR_LIST(mixerStatus, F("valvestatus"), F("mixing valve actuator in assigned hc (VC1)"))
MAKE_PSTR_LIST(flowTempVf, F("flowtempvf"), F("flow temperature in header (T0/Vf)"))
MAKE_PSTR_LIST(wwPumpStatus, F("pumpstatus"), F("pump status in assigned wwc (PC1)"))
MAKE_PSTR_LIST(wwTempStatus, F("wwtempstatus"), F("temperature switch in assigned wwc (MC1)"))
MAKE_PSTR_LIST(wwTemp, F("wwtemp"), F("current warm water temperature"))
// solar
MAKE_PSTR_LIST(type, F("type"), F("type"))
MAKE_PSTR_LIST(collectorTemp, F("collectortemp"), F("collector temperature (TS1)"))
MAKE_PSTR_LIST(tankBottomTemp, F("tankbottomtemp"), F("tank bottom temperature (TS2)"))
MAKE_PSTR_LIST(tank2BottomTemp, F("tank2bottomtemp"), F("second tank bottom temperature (TS5)"))
MAKE_PSTR_LIST(heatExchangerTemp, F("heatexchangertemp"), F("heat exchanger temperature (TS6)"))
MAKE_PSTR_LIST(tankMaxTemp, F("tankmaxtemp"), F("maximum tank temperature"))
MAKE_PSTR_LIST(solarPumpModulation, F("solarpumpmodulation"), F("pump modulation (PS1)"))
MAKE_PSTR_LIST(cylinderPumpModulation, F("cylinderpumpmodulation"), F("cylinder pump modulation (PS5)"))
MAKE_PSTR_LIST(solarPump, F("solarpump"), F("pump (PS1)"))
MAKE_PSTR_LIST(valveStatus, F("valvestatus"), F("valve status"))
MAKE_PSTR_LIST(tankHeated, F("tankheated"), F("tank heated"))
MAKE_PSTR_LIST(collectorShutdown, F("collectorshutdown"), F("collector shutdown"))
MAKE_PSTR_LIST(pumpWorkTime, F("pumpWorktime"), F("pump working time"))
MAKE_PSTR_LIST(energyLastHour, F("energylasthour"), F("energy last hour"))
MAKE_PSTR_LIST(energyTotal, F("energytotal"), F("energy total"))
MAKE_PSTR_LIST(energyToday, F("energytoday"), F("energy today"))
MAKE_PSTR_LIST(wwTemp1, F("wwtemp1"), F("temperature 1"))
MAKE_PSTR_LIST(wwTemp3, F("wwtemp3"), F("temperature 3"))
MAKE_PSTR_LIST(wwTemp4, F("wwtemp4"), F("temperature 4"))
MAKE_PSTR_LIST(wwTemp5, F("wwtemp5"), F("temperature 5"))
MAKE_PSTR_LIST(wwTemp7, F("wwtemp7"), F("temperature 7"))
MAKE_PSTR_LIST(wwPump, F("wwpump"), F("pump"))
// switch
MAKE_PSTR_LIST(activated, F("activated"), F("activated"))
MAKE_PSTR_LIST(status, F("status"), F("status"))

View File

@@ -39,7 +39,7 @@ uint8_t Mqtt::dallas_format_;
uint8_t Mqtt::bool_format_; uint8_t Mqtt::bool_format_;
uint8_t Mqtt::ha_climate_format_; uint8_t Mqtt::ha_climate_format_;
bool Mqtt::ha_enabled_; bool Mqtt::ha_enabled_;
bool Mqtt::nested_format_; uint8_t Mqtt::nested_format_;
uint8_t Mqtt::subscribe_format_; uint8_t Mqtt::subscribe_format_;
std::deque<Mqtt::QueuedMqttMessage> Mqtt::mqtt_messages_; std::deque<Mqtt::QueuedMqttMessage> Mqtt::mqtt_messages_;
@@ -313,6 +313,7 @@ void Mqtt::on_message(const char * fulltopic, const char * payload, size_t len)
if (mf.mqtt_subfunction_) { if (mf.mqtt_subfunction_) {
if (!(mf.mqtt_subfunction_)(message)) { if (!(mf.mqtt_subfunction_)(message)) {
LOG_ERROR(F("MQTT error: invalid payload %s for this topic %s"), message, topic); LOG_ERROR(F("MQTT error: invalid payload %s for this topic %s"), message, topic);
Mqtt::publish(F_(response), "invalid");
} }
return; return;
} }
@@ -327,14 +328,15 @@ void Mqtt::on_message(const char * fulltopic, const char * payload, size_t len)
} }
cmd_only++; // skip the / cmd_only++; // skip the /
int8_t id = -1; int8_t id = -1;
// check for hcx/ prefix // check for hcx/ prefix, commented out, this is now in command::call
if (cmd_only[0] == 'h' && cmd_only[1] == 'c' && cmd_only[3] == '/') { // if (cmd_only[0] == 'h' && cmd_only[1] == 'c' && cmd_only[3] == '/') {
id = cmd_only[2] - '0'; // id = cmd_only[2] - '0';
cmd_only += 4; // cmd_only += 4;
} // }
// LOG_INFO(F("devicetype= %d, topic = %s, cmd = %s, message = %s, id = %d"), mf.device_type_, topic, cmd_only, message, id); // LOG_INFO(F("devicetype= %d, topic = %s, cmd = %s, message = %s, id = %d"), mf.device_type_, topic, cmd_only, message, id);
if (!Command::call(mf.device_type_, cmd_only, message, id)) { if (!Command::call(mf.device_type_, cmd_only, message, id)) {
LOG_ERROR(F("No matching cmd (%s) in topic %s, id %d, or invalid data"), cmd_only, topic, id); LOG_ERROR(F("No matching cmd (%s) in topic %s, id %d, or invalid data"), cmd_only, topic, id);
Mqtt::publish(F_(response), "unknown");
} }
return; return;
} }
@@ -374,11 +376,18 @@ void Mqtt::on_message(const char * fulltopic, const char * payload, size_t len)
char data_str[10]; char data_str[10];
cmd_known = Command::call(mf.device_type_, command, Helpers::render_value(data_str, (float)data.as<float>(), 2), n); cmd_known = Command::call(mf.device_type_, command, Helpers::render_value(data_str, (float)data.as<float>(), 2), n);
} else if (data.isNull()) { } else if (data.isNull()) {
cmd_known = Command::call(mf.device_type_, command, "", n); DynamicJsonDocument resp(EMSESP_JSON_SIZE_XLARGE_DYN);
JsonObject json = resp.to<JsonObject>();
cmd_known = Command::call(mf.device_type_, command, "", n, json);
if (cmd_known && json.size()) {
Mqtt::publish(F_(response), resp.as<JsonObject>());
return;
}
} }
if (!cmd_known) { if (!cmd_known) {
LOG_ERROR(F("No matching cmd (%s) or invalid data"), command); LOG_ERROR(F("No matching cmd (%s) or invalid data"), command);
Mqtt::publish(F_(response), "unknown");
} }
return; return;
@@ -672,20 +681,21 @@ void Mqtt::ha_status() {
ids.add("ems-esp"); ids.add("ems-esp");
char topic[MQTT_TOPIC_MAX_SIZE]; char topic[MQTT_TOPIC_MAX_SIZE];
snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/system/config"), mqtt_base_.c_str()); snprintf_P(topic, sizeof(topic), PSTR("sensor/%s/system/config"), mqtt_base_.c_str());
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
// create the sensors // create the sensors
// must match the MQTT payload keys
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("WiFi strength"), EMSdevice::DeviceType::SYSTEM, F("rssi")); publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("WiFi strength"), EMSdevice::DeviceType::SYSTEM, F("rssi"));
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Uptime"), EMSdevice::DeviceType::SYSTEM, F("uptime")); publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Uptime"), EMSdevice::DeviceType::SYSTEM, F("uptime"));
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Uptime (sec)"), EMSdevice::DeviceType::SYSTEM, F("uptime_sec")); publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Uptime (sec)"), EMSdevice::DeviceType::SYSTEM, F("uptime_sec"));
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Free heap memory"), EMSdevice::DeviceType::SYSTEM, F("freemem")); publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Free memory"), EMSdevice::DeviceType::SYSTEM, F("freemem"));
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("# Failed MQTT publishes"), EMSdevice::DeviceType::SYSTEM, F("mqttfails")); publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("# MQTT fails"), EMSdevice::DeviceType::SYSTEM, F("mqttfails"));
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("# Rx Sent"), EMSdevice::DeviceType::SYSTEM, F("rxsent")); publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("# Rx received"), EMSdevice::DeviceType::SYSTEM, F("rxreceived"));
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("# Rx Fails"), EMSdevice::DeviceType::SYSTEM, F("rxfails")); publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("# Rx fails"), EMSdevice::DeviceType::SYSTEM, F("rxfails"));
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("# Tx Reads"), EMSdevice::DeviceType::SYSTEM, F("txread")); publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("# Tx reads"), EMSdevice::DeviceType::SYSTEM, F("txread"));
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("# Tx Writes"), EMSdevice::DeviceType::SYSTEM, F("txwrite")); publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("# Tx writes"), EMSdevice::DeviceType::SYSTEM, F("txwrite"));
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("# Tx Fails"), EMSdevice::DeviceType::SYSTEM, F("txfails")); publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("# Tx fails"), EMSdevice::DeviceType::SYSTEM, F("txfails"));
} }
// add sub or pub task to the queue. // add sub or pub task to the queue.
@@ -788,14 +798,15 @@ void Mqtt::publish_ha(const std::string & topic, const JsonObject & payload) {
payload_text.reserve(measureJson(payload) + 1); payload_text.reserve(measureJson(payload) + 1);
serializeJson(payload, payload_text); // convert json to string serializeJson(payload, payload_text); // convert json to string
std::string fulltopic = uuid::read_flash_string(F_(homeassistant)) + topic;
#if defined(EMSESP_STANDALONE) #if defined(EMSESP_STANDALONE)
LOG_DEBUG(F("Publishing HA topic=%s, payload=%s"), topic.c_str(), payload_text.c_str()); LOG_DEBUG(F("Publishing HA topic=%s, payload=%s"), fulltopic.c_str(), payload_text.c_str());
#elif defined(EMSESP_DEBUG) #elif defined(EMSESP_DEBUG)
LOG_DEBUG(F("[debug] Publishing HA topic=%s, payload=%s"), topic.c_str(), payload_text.c_str()); LOG_DEBUG(F("[debug] Publishing HA topic=%s, payload=%s"), fulltopic.c_str(), payload_text.c_str());
#endif #endif
// queue messages if the MQTT connection is not yet established. to ensure we don't miss messages // queue messages if the MQTT connection is not yet established. to ensure we don't miss messages
queue_publish_message(topic, payload_text, true); // with retain true queue_publish_message(fulltopic, payload_text, true); // with retain true
} }
// take top from queue and perform the publish or subscribe action // take top from queue and perform the publish or subscribe action
@@ -809,7 +820,7 @@ void Mqtt::process_queue() {
auto mqtt_message = mqtt_messages_.front(); auto mqtt_message = mqtt_messages_.front();
auto message = mqtt_message.content_; auto message = mqtt_message.content_;
char topic[MQTT_TOPIC_MAX_SIZE]; char topic[MQTT_TOPIC_MAX_SIZE];
if ((strncmp(message->topic.c_str(), "homeassistant/", 13) == 0)) { if (message->topic.find(uuid::read_flash_string(F_(homeassistant))) == 0) {
// leave topic as it is // leave topic as it is
strcpy(topic, message->topic.c_str()); strcpy(topic, message->topic.c_str());
} else { } else {
@@ -833,7 +844,7 @@ void Mqtt::process_queue() {
// it will have a real packet ID // it will have a real packet ID
if (mqtt_message.packet_id_ > 0) { if (mqtt_message.packet_id_ > 0) {
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
LOG_DEBUG(F("[DEBUG] Waitig for QOS-ACK")); LOG_DEBUG(F("[DEBUG] Waiting for QOS-ACK"));
#endif #endif
return; return;
} }
@@ -892,7 +903,7 @@ void Mqtt::publish_mqtt_ha_sensor(uint8_t type, // EMSdevice
DynamicJsonDocument doc(EMSESP_JSON_SIZE_HA_CONFIG); DynamicJsonDocument doc(EMSESP_JSON_SIZE_HA_CONFIG);
bool have_tag = !EMSdevice::tag_to_string(tag).empty() && (device_type != EMSdevice::DeviceType::BOILER); // ignore boiler bool have_tag = !EMSdevice::tag_to_string(tag).empty() && (device_type != EMSdevice::DeviceType::BOILER); // ignore boiler
bool is_nested = nested_format_ || (device_type == EMSdevice::DeviceType::BOILER); // boiler never uses nested bool is_nested = (nested_format_ == 1) || (device_type == EMSdevice::DeviceType::BOILER); // boiler never uses nested
// create entity by add the tag if present, seperating with a . // create entity by add the tag if present, seperating with a .
char new_entity[50]; char new_entity[50];
@@ -904,7 +915,7 @@ void Mqtt::publish_mqtt_ha_sensor(uint8_t type, // EMSdevice
// device name // device name
char device_name[50]; char device_name[50];
strncpy(device_name, EMSdevice::device_type_2_device_name(device_type).c_str(), sizeof(device_name)); strlcpy(device_name, EMSdevice::device_type_2_device_name(device_type).c_str(), sizeof(device_name));
// build unique identifier which will be used in the topic // build unique identifier which will be used in the topic
// and replacing all . with _ as not to break HA // and replacing all . with _ as not to break HA
@@ -944,7 +955,7 @@ void Mqtt::publish_mqtt_ha_sensor(uint8_t type, // EMSdevice
// look at the device value type // look at the device value type
if (type == DeviceValueType::BOOL) { if (type == DeviceValueType::BOOL) {
// binary sensor // binary sensor
snprintf_P(topic, sizeof(topic), PSTR("homeassistant/binary_sensor/%s/%s/config"), mqtt_base_.c_str(), uniq.c_str()); // topic snprintf_P(topic, sizeof(topic), PSTR("binary_sensor/%s/%s/config"), mqtt_base_.c_str(), uniq.c_str()); // topic
// how to render boolean. HA only accepts String values // how to render boolean. HA only accepts String values
char result[10]; char result[10];
@@ -952,7 +963,7 @@ void Mqtt::publish_mqtt_ha_sensor(uint8_t type, // EMSdevice
doc[F("payload_off")] = Helpers::render_boolean(result, false); doc[F("payload_off")] = Helpers::render_boolean(result, false);
} else { } else {
// normal HA sensor, not a boolean one // normal HA sensor, not a boolean one
snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/%s/config"), mqtt_base_.c_str(), uniq.c_str()); // topic snprintf_P(topic, sizeof(topic), PSTR("sensor/%s/%s/config"), mqtt_base_.c_str(), uniq.c_str()); // topic
// unit of measure and map the HA icon // unit of measure and map the HA icon
if (uom != DeviceValueUOM::NONE && uom != DeviceValueUOM::PUMP) { if (uom != DeviceValueUOM::NONE && uom != DeviceValueUOM::PUMP) {
@@ -986,6 +997,7 @@ void Mqtt::publish_mqtt_ha_sensor(uint8_t type, // EMSdevice
ids.add(ha_device); ids.add(ha_device);
} }
doc.shrinkToFit();
publish_ha(topic, doc.as<JsonObject>()); publish_ha(topic, doc.as<JsonObject>());
} }
@@ -999,7 +1011,7 @@ const std::string Mqtt::tag_to_topic(uint8_t device_type, uint8_t tag) {
} }
// if there is a tag add it // if there is a tag add it
if ((EMSdevice::tag_to_mqtt(tag).empty()) || (nested_format_ && (device_type != EMSdevice::DeviceType::BOILER))) { if ((EMSdevice::tag_to_mqtt(tag).empty()) || ((nested_format_ == 1) && (device_type != EMSdevice::DeviceType::BOILER))) {
return EMSdevice::device_type_2_device_name(device_type) + "_data"; return EMSdevice::device_type_2_device_name(device_type) + "_data";
} else { } else {
return EMSdevice::device_type_2_device_name(device_type) + "_data_" + EMSdevice::tag_to_mqtt(tag); return EMSdevice::device_type_2_device_name(device_type) + "_data_" + EMSdevice::tag_to_mqtt(tag);

View File

@@ -158,11 +158,11 @@ class Mqtt {
return bool_format_; return bool_format_;
} }
static bool nested_format() { static uint8_t nested_format() {
return nested_format_; return nested_format_;
} }
static void nested_format(bool nested_format) { static void nested_format(uint8_t nested_format) {
nested_format_ = nested_format; nested_format_ = nested_format;
} }
@@ -274,7 +274,7 @@ class Mqtt {
static uint8_t bool_format_; static uint8_t bool_format_;
static uint8_t ha_climate_format_; static uint8_t ha_climate_format_;
static bool ha_enabled_; static bool ha_enabled_;
static bool nested_format_; static uint8_t nested_format_;
static uint8_t subscribe_format_; static uint8_t subscribe_format_;
}; };

View File

@@ -56,6 +56,7 @@ void Shower::loop() {
if (!shower_on_ && (time_now - timer_start_) > SHOWER_MIN_DURATION) { if (!shower_on_ && (time_now - timer_start_) > SHOWER_MIN_DURATION) {
shower_on_ = true; shower_on_ = true;
send_mqtt_stat(true); send_mqtt_stat(true);
publish_values();
LOG_DEBUG(F("[Shower] hot water still running, starting shower timer")); LOG_DEBUG(F("[Shower] hot water still running, starting shower timer"));
} }
// check if the shower has been on too long // check if the shower has been on too long
@@ -86,9 +87,16 @@ void Shower::loop() {
timer_start_ = 0; timer_start_ = 0;
timer_pause_ = 0; timer_pause_ = 0;
shower_on_ = false; shower_on_ = false;
shower_alert_stop(); doing_cold_shot_ = false;
alert_timer_start_ = 0;
} }
} }
return;
}
// at this point we're in the shower cold shot (doing_cold_shot_ == true)
// keep repeating until the time is up
if ((time_now - alert_timer_start_) > SHOWER_COLDSHOT_DURATION) {
} }
} }
@@ -115,7 +123,7 @@ void Shower::send_mqtt_stat(bool state, bool force) {
ids.add("ems-esp"); ids.add("ems-esp");
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf_P(topic, sizeof(topic), PSTR("homeassistant/binary_sensor/%s/shower_active/config"), Mqtt::base().c_str()); snprintf_P(topic, sizeof(topic), PSTR("binary_sensor/%s/shower_active/config"), Mqtt::base().c_str());
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
} }
} }
@@ -124,20 +132,17 @@ void Shower::send_mqtt_stat(bool state, bool force) {
void Shower::shower_alert_stop() { void Shower::shower_alert_stop() {
if (doing_cold_shot_) { if (doing_cold_shot_) {
LOG_DEBUG(F("Shower Alert stopped")); LOG_DEBUG(F("Shower Alert stopped"));
// Boiler::set_tapwarmwater_activated(true); Command::call(EMSdevice::DeviceType::BOILER, "wwtapactivated", "true");
doing_cold_shot_ = false; doing_cold_shot_ = false;
// showerColdShotStopTimer.detach(); // disable the timer
} }
} }
// turn off hot water to send a shot of cold // turn off hot water to send a shot of cold
void Shower::shower_alert_start() { void Shower::shower_alert_start() {
if (shower_alert_) { if (shower_alert_) {
LOG_DEBUG(F("Shower Alert started!")); LOG_DEBUG(F("Shower Alert started"));
// Boiler::set_tapwarmwater_activated(false); Command::call(EMSdevice::DeviceType::BOILER, "wwtapactivated", "false");
doing_cold_shot_ = true; doing_cold_shot_ = true;
// start the timer for n seconds which will reset the water back to hot alert_timer_start_ = uuid::get_uptime(); // timer starts now
// showerColdShotStopTimer.attach(SHOWER_COLDSHOT_DURATION, _showerColdShotStop);
} }
} }

View File

@@ -52,7 +52,7 @@ class Shower {
static constexpr uint32_t SHOWER_PAUSE_TIME = 15000; // in ms. 15 seconds, max time if water is switched off & on during a shower static constexpr uint32_t SHOWER_PAUSE_TIME = 15000; // in ms. 15 seconds, max time if water is switched off & on during a shower
static constexpr uint32_t SHOWER_MIN_DURATION = 120000; // in ms. 2 minutes, before recognizing its a shower static constexpr uint32_t SHOWER_MIN_DURATION = 120000; // in ms. 2 minutes, before recognizing its a shower
static constexpr uint32_t SHOWER_OFFSET_TIME = 5000; // in ms. 5 seconds grace time, to calibrate actual time under the shower static constexpr uint32_t SHOWER_OFFSET_TIME = 5000; // in ms. 5 seconds grace time, to calibrate actual time under the shower
static constexpr uint32_t SHOWER_COLDSHOT_DURATION = 10; // in seconds. 10 seconds for cold water before turning back hot water static constexpr uint32_t SHOWER_COLDSHOT_DURATION = 10000; // 10 seconds for cold water before turning back hot water
static constexpr uint32_t SHOWER_MAX_DURATION = 420000; // in ms. 7 minutes, before trigger a shot of cold water static constexpr uint32_t SHOWER_MAX_DURATION = 420000; // in ms. 7 minutes, before trigger a shot of cold water
void publish_values(); void publish_values();
@@ -66,6 +66,9 @@ class Shower {
uint32_t timer_start_; // ms uint32_t timer_start_; // ms
uint32_t timer_pause_; // ms uint32_t timer_pause_; // ms
uint32_t duration_; // ms uint32_t duration_; // ms
// cold shot
uint32_t alert_timer_start_; // ms
bool doing_cold_shot_; // true if we've just sent a jolt of cold water bool doing_cold_shot_; // true if we've just sent a jolt of cold water
}; };

View File

@@ -19,7 +19,7 @@
#include "system.h" #include "system.h"
#include "emsesp.h" // for send_raw_telegram() command #include "emsesp.h" // for send_raw_telegram() command
#if defined(EMSESP_TEST) #if defined(EMSESP_DEBUG)
#include "test/test.h" #include "test/test.h"
#endif #endif
@@ -40,6 +40,7 @@ PButton System::myPButton_;
// e.g. http://ems-esp/api?device=system&cmd=pin&data=1&id=2 // e.g. http://ems-esp/api?device=system&cmd=pin&data=1&id=2
bool System::command_pin(const char * value, const int8_t id) { bool System::command_pin(const char * value, const int8_t id) {
if (!is_valid_gpio(id)) { if (!is_valid_gpio(id)) {
LOG_INFO(F("invalid GPIO number"));
return false; return false;
} }
@@ -69,12 +70,30 @@ bool System::command_fetch(const char * value, const int8_t id) {
// mqtt publish // mqtt publish
bool System::command_publish(const char * value, const int8_t id) { bool System::command_publish(const char * value, const int8_t id) {
std::string ha(10, '\0'); std::string ha(14, '\0');
if (Helpers::value2string(value, ha)) { if (Helpers::value2string(value, ha)) {
if (ha == "ha") { if (ha == "ha") {
EMSESP::publish_all(true); // includes HA EMSESP::publish_all(true); // includes HA
LOG_INFO(F("Publishing all data to MQTT, including HA configs")); LOG_INFO(F("Publishing all data to MQTT, including HA configs"));
return true; return true;
} else if (ha == "boiler") {
EMSESP::publish_device_values(EMSdevice::DeviceType::BOILER);
return true;
} else if (ha == "thermostat") {
EMSESP::publish_device_values(EMSdevice::DeviceType::THERMOSTAT);
return true;
} else if (ha == "solar") {
EMSESP::publish_device_values(EMSdevice::DeviceType::SOLAR);
return true;
} else if (ha == "mixer") {
EMSESP::publish_device_values(EMSdevice::DeviceType::MIXER);
return true;
} else if (ha == "other") {
EMSESP::publish_other_values();
return true;
} else if (ha == "dallassensor") {
EMSESP::publish_sensor_values(true);
return true;
} }
} }
@@ -123,6 +142,7 @@ void System::syslog_start() {
if (syslog_enabled_) { if (syslog_enabled_) {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
syslog_.start(); syslog_.start();
syslog_.log_level((uuid::log::Level)syslog_level_);
#endif #endif
EMSESP::logger().info(F("Starting Syslog")); EMSESP::logger().info(F("Starting Syslog"));
} }
@@ -292,7 +312,7 @@ void System::button_OnVLongPress(PButton & b) {
LOG_WARNING(F("Performing factory reset...")); LOG_WARNING(F("Performing factory reset..."));
EMSESP::console_.loop(); EMSESP::console_.loop();
#ifdef EMSESP_TEST #ifdef EMSESP_DEBUG
Test::listDir(LITTLEFS, FS_CONFIG_DIRECTORY, 3); Test::listDir(LITTLEFS, FS_CONFIG_DIRECTORY, 3);
#endif #endif
@@ -397,23 +417,16 @@ void System::show_mem(const char * note) {
#endif #endif
} }
// send periodic MQTT message with system information // create the json for heartbeat
void System::send_heartbeat() { bool System::heartbeat_json(JsonObject & doc) {
// don't send heartbeat if WiFi or MQTT is not connected
if (!Mqtt::connected()) {
return;
}
int8_t rssi; int8_t rssi;
if (!ethernet_connected_) { if (!ethernet_connected_) {
rssi = wifi_quality(); rssi = wifi_quality();
if (rssi == -1) { if (rssi == -1) {
return; return false;
} }
} }
StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> doc;
uint8_t ems_status = EMSESP::bus_status(); uint8_t ems_status = EMSESP::bus_status();
if (ems_status == EMSESP::BUS_STATUS_TX_ERRORS) { if (ems_status == EMSESP::BUS_STATUS_TX_ERRORS) {
doc["status"] = FJSON("txerror"); doc["status"] = FJSON("txerror");
@@ -428,13 +441,18 @@ void System::send_heartbeat() {
} }
doc["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3); doc["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
doc["uptime_sec"] = uuid::get_uptime_sec(); doc["uptime_sec"] = uuid::get_uptime_sec();
doc["mqttfails"] = Mqtt::publish_fails();
doc["rxreceived"] = EMSESP::rxservice_.telegram_count(); doc["rxreceived"] = EMSESP::rxservice_.telegram_count();
doc["rxfails"] = EMSESP::rxservice_.telegram_error_count(); doc["rxfails"] = EMSESP::rxservice_.telegram_error_count();
doc["txread"] = EMSESP::txservice_.telegram_read_count(); doc["txread"] = EMSESP::txservice_.telegram_read_count();
doc["txwrite"] = EMSESP::txservice_.telegram_write_count(); doc["txwrite"] = EMSESP::txservice_.telegram_write_count();
doc["txfails"] = EMSESP::txservice_.telegram_fail_count(); doc["txfails"] = EMSESP::txservice_.telegram_fail_count();
if (Mqtt::enabled()) {
doc["mqttfails"] = Mqtt::publish_fails();
}
if (EMSESP::dallas_enabled()) {
doc["dallasfails"] = EMSESP::sensor_fails(); doc["dallasfails"] = EMSESP::sensor_fails();
}
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
doc["freemem"] = ESP.getFreeHeap(); doc["freemem"] = ESP.getFreeHeap();
#endif #endif
@@ -443,7 +461,22 @@ void System::send_heartbeat() {
doc["adc"] = analog_; doc["adc"] = analog_;
} }
Mqtt::publish(F("heartbeat"), doc.as<JsonObject>()); // send to MQTT with retain off. This will add to MQTT queue. return (doc.size() > 0);
}
// send periodic MQTT message with system information
void System::send_heartbeat() {
// don't send heartbeat if WiFi or MQTT is not connected
if (!Mqtt::connected()) {
return;
}
StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> doc;
JsonObject json = doc.to<JsonObject>();
if (heartbeat_json(json)) {
Mqtt::publish(F_(heartbeat), doc.as<JsonObject>()); // send to MQTT with retain off. This will add to MQTT queue.
}
} }
// measure and moving average adc // measure and moving average adc
@@ -572,7 +605,7 @@ void System::commands_init() {
Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch); Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch);
Command::add_with_json(EMSdevice::DeviceType::SYSTEM, F_(info), System::command_info); Command::add_with_json(EMSdevice::DeviceType::SYSTEM, F_(info), System::command_info);
Command::add_with_json(EMSdevice::DeviceType::SYSTEM, F_(settings), System::command_settings); Command::add_with_json(EMSdevice::DeviceType::SYSTEM, F_(settings), System::command_settings);
#if defined(EMSESP_TEST) #if defined(EMSESP_DEBUG)
Command::add(EMSdevice::DeviceType::SYSTEM, F("test"), System::command_test); Command::add(EMSdevice::DeviceType::SYSTEM, F("test"), System::command_test);
#endif #endif
} }
@@ -639,7 +672,7 @@ void System::show_system(uuid::console::Shell & shell) {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
shell.printfln(F("SDK version: %s"), ESP.getSdkVersion()); shell.printfln(F("SDK version: %s"), ESP.getSdkVersion());
shell.printfln(F("CPU frequency: %u MHz"), ESP.getCpuFreqMHz()); shell.printfln(F("CPU frequency: %lu MHz"), ESP.getCpuFreqMHz());
shell.printfln(F("Free heap: %lu bytes"), (uint32_t)ESP.getFreeHeap()); shell.printfln(F("Free heap: %lu bytes"), (uint32_t)ESP.getFreeHeap());
shell.println(); shell.println();
@@ -656,7 +689,7 @@ void System::show_system(uuid::console::Shell & shell) {
shell.printfln(F("WiFi: Network scan complete")); shell.printfln(F("WiFi: Network scan complete"));
break; break;
case WL_CONNECTED: { case WL_CONNECTED:
shell.printfln(F("WiFi: Connected")); shell.printfln(F("WiFi: Connected"));
shell.printfln(F("SSID: %s"), WiFi.SSID().c_str()); shell.printfln(F("SSID: %s"), WiFi.SSID().c_str());
shell.printfln(F("BSSID: %s"), WiFi.BSSIDstr().c_str()); shell.printfln(F("BSSID: %s"), WiFi.BSSIDstr().c_str());
@@ -666,7 +699,7 @@ void System::show_system(uuid::console::Shell & shell) {
shell.printfln(F("IPv4 address: %s/%s"), uuid::printable_to_string(WiFi.localIP()).c_str(), uuid::printable_to_string(WiFi.subnetMask()).c_str()); shell.printfln(F("IPv4 address: %s/%s"), uuid::printable_to_string(WiFi.localIP()).c_str(), uuid::printable_to_string(WiFi.subnetMask()).c_str());
shell.printfln(F("IPv4 gateway: %s"), uuid::printable_to_string(WiFi.gatewayIP()).c_str()); shell.printfln(F("IPv4 gateway: %s"), uuid::printable_to_string(WiFi.gatewayIP()).c_str());
shell.printfln(F("IPv4 nameserver: %s"), uuid::printable_to_string(WiFi.dnsIP()).c_str()); shell.printfln(F("IPv4 nameserver: %s"), uuid::printable_to_string(WiFi.dnsIP()).c_str());
} break; break;
case WL_CONNECT_FAILED: case WL_CONNECT_FAILED:
shell.printfln(F("WiFi: Connection failed")); shell.printfln(F("WiFi: Connection failed"));
@@ -718,150 +751,6 @@ void System::show_system(uuid::console::Shell & shell) {
#endif #endif
} }
// console commands to add
void System::console_commands(Shell & shell, unsigned int context) {
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
CommandFlags::ADMIN,
flash_string_vector{F_(restart)},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::system_.restart(); });
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
CommandFlags::ADMIN,
flash_string_vector{F_(wifi), F_(reconnect)},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::system_.wifi_reconnect(); });
EMSESPShell::commands->add_command(ShellContext::SYSTEM, CommandFlags::ADMIN, flash_string_vector{F_(format)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.enter_password(F_(password_prompt), [=](Shell & shell, bool completed, const std::string & password) {
if (completed) {
EMSESP::esp8266React.getSecuritySettingsService()->read([&](SecuritySettings & securitySettings) {
if (securitySettings.jwtSecret.equals(password.c_str())) {
EMSESP::system_.format(shell);
} else {
shell.println(F("incorrect password"));
}
});
}
});
});
EMSESPShell::commands->add_command(ShellContext::SYSTEM, CommandFlags::ADMIN, flash_string_vector{F_(passwd)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.enter_password(F_(new_password_prompt1), [](Shell & shell, bool completed, const std::string & password1) {
if (completed) {
shell.enter_password(F_(new_password_prompt2), [password1](Shell & shell, bool completed, const std::string & password2) {
if (completed) {
if (password1 == password2) {
EMSESP::esp8266React.getSecuritySettingsService()->update(
[&](SecuritySettings & securitySettings) {
securitySettings.jwtSecret = password2.c_str();
return StateUpdateResult::CHANGED;
},
"local");
shell.println(F("su password updated"));
} else {
shell.println(F("Passwords do not match"));
}
}
});
}
});
});
EMSESPShell::commands->add_command(ShellContext::SYSTEM, CommandFlags::USER, flash_string_vector{F_(show)}, [=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::system_.show_system(shell);
shell.println();
});
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(hostname)},
flash_string_vector{F_(name_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
shell.println("The network connection will be reset...");
Shell::loop_all();
delay(1000); // wait a second
EMSESP::esp8266React.getNetworkSettingsService()->update(
[&](NetworkSettings & networkSettings) {
networkSettings.hostname = arguments.front().c_str();
return StateUpdateResult::CHANGED;
},
"local");
});
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(wifi), F_(ssid)},
flash_string_vector{F_(name_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
EMSESP::esp8266React.getNetworkSettingsService()->updateWithoutPropagation([&](NetworkSettings & networkSettings) {
networkSettings.ssid = arguments.front().c_str();
return StateUpdateResult::CHANGED;
});
shell.println("Use `wifi reconnect` to save and apply the new settings");
});
EMSESPShell::commands->add_command(ShellContext::SYSTEM, CommandFlags::ADMIN, flash_string_vector{F_(set), F_(wifi), F_(password)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.enter_password(F_(new_password_prompt1), [](Shell & shell, bool completed, const std::string & password1) {
if (completed) {
shell.enter_password(F_(new_password_prompt2), [password1](Shell & shell, bool completed, const std::string & password2) {
if (completed) {
if (password1 == password2) {
EMSESP::esp8266React.getNetworkSettingsService()->updateWithoutPropagation([&](NetworkSettings & networkSettings) {
networkSettings.password = password2.c_str();
return StateUpdateResult::CHANGED;
});
shell.println("Use `wifi reconnect` to save and apply the new settings");
} else {
shell.println(F("Passwords do not match"));
}
}
});
}
});
});
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(board_profile)},
flash_string_vector{F_(name_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
std::vector<uint8_t> data; // led, dallas, rx, tx, button
std::string board_profile = Helpers::toUpper(arguments.front());
if (!load_board_profile(data, board_profile)) {
shell.println(F("Invalid board profile"));
return;
}
EMSESP::webSettingsService.update(
[&](WebSettings & settings) {
settings.board_profile = board_profile.c_str();
settings.led_gpio = data[0];
settings.dallas_gpio = data[1];
settings.rx_gpio = data[2];
settings.tx_gpio = data[3];
settings.pbutton_gpio = data[4];
return StateUpdateResult::CHANGED;
},
"local");
shell.printfln("Loaded board profile %s (%d,%d,%d,%d,%d)", board_profile.c_str(), data[0], data[1], data[2], data[3], data[4]);
EMSESP::system_.network_init(true);
});
EMSESPShell::commands->add_command(ShellContext::SYSTEM, CommandFlags::USER, flash_string_vector{F_(set)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) {
shell.printfln(F_(hostname_fmt), networkSettings.hostname.isEmpty() ? uuid::read_flash_string(F_(unset)).c_str() : networkSettings.hostname.c_str());
shell.printfln(F_(wifi_ssid_fmt), networkSettings.ssid.isEmpty() ? uuid::read_flash_string(F_(unset)).c_str() : networkSettings.ssid.c_str());
shell.printfln(F_(wifi_password_fmt), networkSettings.ssid.isEmpty() ? F_(unset) : F_(asterisks));
});
EMSESP::webSettingsService.read([&](WebSettings & settings) { shell.printfln(F_(board_profile_fmt), settings.board_profile.c_str()); });
});
EMSESPShell::commands->add_command(ShellContext::SYSTEM, CommandFlags::ADMIN, flash_string_vector{F_(show), F_(users)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::system_.show_users(shell);
});
// enter the context
Console::enter_custom_context(shell, context);
}
// upgrade from previous versions of EMS-ESP // upgrade from previous versions of EMS-ESP
// returns true if an upgrade was done // returns true if an upgrade was done
bool System::check_upgrade() { bool System::check_upgrade() {
@@ -971,12 +860,20 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject &
// export status information including some basic settings // export status information including some basic settings
// e.g. http://ems-esp/api?device=system&cmd=info // e.g. http://ems-esp/api?device=system&cmd=info
bool System::command_info(const char * value, const int8_t id, JsonObject & json) { bool System::command_info(const char * value, const int8_t id, JsonObject & json) {
if (id == 0) {
return EMSESP::system_.heartbeat_json(json);
}
JsonObject node; JsonObject node;
node = json.createNestedObject("System"); node = json.createNestedObject("System");
node["version"] = EMSESP_APP_VERSION; node["version"] = EMSESP_APP_VERSION;
node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3); node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
#ifndef EMSESP_STANDALONE
node["freemem"] = ESP.getFreeHeap();
#endif
node = json.createNestedObject("Status"); node = json.createNestedObject("Status");
@@ -999,13 +896,17 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & json
node["#read requests sent"] = EMSESP::txservice_.telegram_read_count(); node["#read requests sent"] = EMSESP::txservice_.telegram_read_count();
node["#write requests sent"] = EMSESP::txservice_.telegram_write_count(); node["#write requests sent"] = EMSESP::txservice_.telegram_write_count();
node["#incomplete telegrams"] = EMSESP::rxservice_.telegram_error_count(); node["#incomplete telegrams"] = EMSESP::rxservice_.telegram_error_count();
node["#tx fails"] = TxService::MAXIMUM_TX_RETRIES, EMSESP::txservice_.telegram_fail_count(); node["#tx fails"] = EMSESP::txservice_.telegram_fail_count();
node["rx line quality"] = EMSESP::rxservice_.quality(); node["rx line quality"] = EMSESP::rxservice_.quality();
node["tx line quality"] = EMSESP::txservice_.quality(); node["tx line quality"] = EMSESP::txservice_.quality();
if (Mqtt::enabled()) {
node["#MQTT publish fails"] = Mqtt::publish_fails(); node["#MQTT publish fails"] = Mqtt::publish_fails();
}
if (EMSESP::dallas_enabled()) {
node["#dallas sensors"] = EMSESP::sensor_devices().size(); node["#dallas sensors"] = EMSESP::sensor_devices().size();
node["#dallas fails"] = EMSESP::sensor_fails(); node["#dallas fails"] = EMSESP::sensor_fails();
} }
}
JsonArray devices2 = json.createNestedArray("Devices"); JsonArray devices2 = json.createNestedArray("Devices");
@@ -1020,11 +921,16 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & json
} }
} }
} }
if (EMSESP::sensor_devices().size()) {
JsonObject obj = devices2.createNestedObject();
obj["type"] = F("Dallassensor");
obj["name"] = F("Dallassensor");
}
return true; return true;
} }
#if defined(EMSESP_TEST) #if defined(EMSESP_DEBUG)
// run a test, e.g. http://ems-esp/api?device=system&cmd=test&data=boiler // run a test, e.g. http://ems-esp/api?device=system&cmd=test&data=boiler
bool System::command_test(const char * value, const int8_t id) { bool System::command_test(const char * value, const int8_t id) {
Test::run_test(value, id); Test::run_test(value, id);
@@ -1037,11 +943,11 @@ bool System::command_test(const char * value, const int8_t id) {
// returns false if profile is not found // returns false if profile is not found
bool System::load_board_profile(std::vector<uint8_t> & data, const std::string & board_profile) { bool System::load_board_profile(std::vector<uint8_t> & data, const std::string & board_profile) {
if (board_profile == "S32") { if (board_profile == "S32") {
data = {2, 3, 23, 5, 0}; // BBQKees Gateway S32 data = {2, 18, 23, 5, 0}; // BBQKees Gateway S32
} else if (board_profile == "E32") { } else if (board_profile == "E32") {
data = {2, 4, 5, 17, 33}; // BBQKees Gateway E32 data = {2, 4, 5, 17, 33}; // BBQKees Gateway E32
} else if (board_profile == "MT-ET") { } else if (board_profile == "MH-ET") {
data = {2, 18, 23, 5, 0}; // MT-ET Live D1 Mini data = {2, 18, 23, 5, 0}; // MH-ET Live D1 Mini
} else if (board_profile == "NODEMCU") { } else if (board_profile == "NODEMCU") {
data = {2, 18, 23, 5, 0}; // NodeMCU 32S data = {2, 18, 23, 5, 0}; // NodeMCU 32S
} else if (board_profile == "LOLIN") { } else if (board_profile == "LOLIN") {

View File

@@ -48,14 +48,13 @@ class System {
void loop(); void loop();
// commands // commands
static void console_commands(Shell & shell, unsigned int context);
static bool command_pin(const char * value, const int8_t id); static bool command_pin(const char * value, const int8_t id);
static bool command_send(const char * value, const int8_t id); static bool command_send(const char * value, const int8_t id);
static bool command_publish(const char * value, const int8_t id); static bool command_publish(const char * value, const int8_t id);
static bool command_fetch(const char * value, const int8_t id); static bool command_fetch(const char * value, const int8_t id);
static bool command_info(const char * value, const int8_t id, JsonObject & json); static bool command_info(const char * value, const int8_t id, JsonObject & json);
static bool command_settings(const char * value, const int8_t id, JsonObject & json); static bool command_settings(const char * value, const int8_t id, JsonObject & json);
#if defined(EMSESP_TEST) #if defined(EMSESP_DEBUG)
static bool command_test(const char * value, const int8_t id); static bool command_test(const char * value, const int8_t id);
#endif #endif
@@ -68,6 +67,7 @@ class System {
void wifi_tweak(); void wifi_tweak();
void syslog_start(); void syslog_start();
bool check_upgrade(); bool check_upgrade();
bool heartbeat_json(JsonObject & json);
void send_heartbeat(); void send_heartbeat();
void led_init(bool refresh); void led_init(bool refresh);
@@ -103,6 +103,9 @@ class System {
return true; return true;
#endif #endif
} }
void show_system(uuid::console::Shell & shell);
void wifi_reconnect();
void show_users(uuid::console::Shell & shell);
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;
@@ -135,9 +138,6 @@ class System {
void system_check(); void system_check();
void measure_analog(); void measure_analog();
void show_system(uuid::console::Shell & shell);
void show_users(uuid::console::Shell & shell);
void wifi_reconnect();
int8_t wifi_quality(); int8_t wifi_quality();
bool system_healthy_ = false; bool system_healthy_ = false;

View File

@@ -419,7 +419,7 @@ void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t lengt
} }
// build header. src, dest and offset have fixed positions // build header. src, dest and offset have fixed positions
uint8_t src = data[0]; uint8_t src = ems_bus_id(); // data[0]; we can only send data with own bus_id.
uint8_t dest = data[1]; uint8_t dest = data[1];
uint8_t offset = data[3]; uint8_t offset = data[3];
@@ -485,11 +485,16 @@ void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t lengt
} }
// send a Tx telegram to request data from an EMS device // send a Tx telegram to request data from an EMS device
void TxService::read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset) { void TxService::read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t length) {
LOG_DEBUG(F("Tx read request to device 0x%02X for type ID 0x%02X"), dest, type_id); LOG_DEBUG(F("Tx read request to device 0x%02X for type ID 0x%02X"), dest, type_id);
uint8_t message_data[1] = {EMS_MAX_TELEGRAM_LENGTH}; // request all data, 32 bytes uint8_t message_data[1] = {EMS_MAX_TELEGRAM_LENGTH}; // request all data, 32 bytes
add(Telegram::Operation::TX_READ, dest, type_id, offset, message_data, 1, 0); // if length set, publish result and set telegram to front
if (length) {
message_data[0] = length;
EMSESP::set_read_id(type_id);
}
add(Telegram::Operation::TX_READ, dest, type_id, offset, message_data, 1, 0, length != 0);
} }
// Send a raw telegram to the bus, telegram is a text string of hex values // Send a raw telegram to the bus, telegram is a text string of hex values
@@ -541,7 +546,7 @@ void TxService::retry_tx(const uint8_t operation, const uint8_t * data, const ui
reset_retry_count(); // give up reset_retry_count(); // give up
increment_telegram_fail_count(); // another Tx fail increment_telegram_fail_count(); // another Tx fail
LOG_ERROR(F("Last Tx %s operation failed after %d retries. Ignoring request."), (operation == Telegram::Operation::TX_WRITE) ? F("Write") : F("Read"), MAXIMUM_TX_RETRIES); LOG_ERROR(F("Last Tx %s operation failed after %d retries. Ignoring request: %s"), (operation == Telegram::Operation::TX_WRITE) ? F("Write") : F("Read"), MAXIMUM_TX_RETRIES), telegram_last_->to_string().c_str();
return; return;
} }

View File

@@ -264,7 +264,7 @@ class TxService : public EMSbus {
void send(); void send();
void add(const uint8_t operation, const uint8_t dest, const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validateid, const bool front = false); void add(const uint8_t operation, const uint8_t dest, const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validateid, const bool front = false);
void add(const uint8_t operation, const uint8_t * data, const uint8_t length, const uint16_t validateid, const bool front = false); void add(const uint8_t operation, const uint8_t * data, const uint8_t length, const uint16_t validateid, const bool front = false);
void read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset = 0); void read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset = 0, const uint8_t length = 0);
void send_raw(const char * telegram_data); void send_raw(const char * telegram_data);
void send_poll(); void send_poll();
void retry_tx(const uint8_t operation, const uint8_t * data, const uint8_t length); void retry_tx(const uint8_t operation, const uint8_t * data, const uint8_t length);

View File

@@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#if defined(EMSESP_TEST) #if defined(EMSESP_DEBUG)
#include "test.h" #include "test.h"
@@ -191,7 +191,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
std::string command(20, '\0'); std::string command(20, '\0');
if ((cmd.empty()) || (cmd == "default")) { if ((cmd.empty()) || (cmd == "default")) {
command = EMSESP_TEST_DEFAULT; command = EMSESP_DEBUG_DEFAULT;
} else { } else {
command = cmd; command = cmd;
} }
@@ -376,7 +376,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
if (command == "boiler") { if (command == "boiler") {
shell.printfln(F("Testing boiler...")); shell.printfln(F("Testing boiler..."));
Mqtt::ha_enabled(false); Mqtt::ha_enabled(false);
Mqtt::nested_format(true); Mqtt::nested_format(1);
run_test("boiler"); run_test("boiler");
shell.invoke_command("show devices"); shell.invoke_command("show devices");
@@ -399,6 +399,15 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
shell.invoke_command("show mqtt"); shell.invoke_command("show mqtt");
} }
if (command == "shower_alert") {
shell.printfln(F("Testing Shower Alert..."));
run_test("boiler");
// device type, command, data
Command::call(EMSdevice::DeviceType::BOILER, "wwtapactivated", "false");
}
if (command == "fr120") { if (command == "fr120") {
shell.printfln(F("Testing adding a thermostat FR120...")); shell.printfln(F("Testing adding a thermostat FR120..."));
@@ -414,8 +423,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
if (command == "ha") { if (command == "ha") {
shell.printfln(F("Testing HA discovery")); shell.printfln(F("Testing HA discovery"));
Mqtt::ha_enabled(true); Mqtt::ha_enabled(true);
// Mqtt::nested_format(true); // Mqtt::nested_format(1);
Mqtt::nested_format(false); Mqtt::nested_format(2);
// run_test("boiler"); // run_test("boiler");
run_test("thermostat"); run_test("thermostat");
@@ -436,11 +445,11 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
run_test("mixer"); run_test("mixer");
// first with nested // first with nested
Mqtt::nested_format(true); Mqtt::nested_format(1);
shell.invoke_command("call system publish"); shell.invoke_command("call system publish");
// then without nested // then without nested
Mqtt::nested_format(false); Mqtt::nested_format(2);
shell.invoke_command("call system publish"); shell.invoke_command("call system publish");
shell.invoke_command("show mqtt"); shell.invoke_command("show mqtt");
} }

View File

@@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#if defined(EMSESP_TEST) #if defined(EMSESP_DEBUG)
#ifndef EMSESP_TEST_H #ifndef EMSESP_TEST_H
#define EMSESP_TEST_H #define EMSESP_TEST_H
@@ -25,16 +25,18 @@
namespace emsesp { namespace emsesp {
// #define EMSESP_TEST_DEFAULT "thermostat" // #define EMSESP_DEBUG_DEFAULT "thermostat"
// #define EMSESP_TEST_DEFAULT "solar" // #define EMSESP_DEBUG_DEFAULT "solar"
// #define EMSESP_TEST_DEFAULT "mixer" // #define EMSESP_DEBUG_DEFAULT "mixer"
// #define EMSESP_TEST_DEFAULT "web" // #define EMSESP_DEBUG_DEFAULT "web"
// #define EMSESP_TEST_DEFAULT "general" // #define EMSESP_DEBUG_DEFAULT "general"
// #define EMSESP_TEST_DEFAULT "boiler" // #define EMSESP_DEBUG_DEFAULT "boiler"
// #define EMSESP_TEST_DEFAULT "mqtt2" // #define EMSESP_DEBUG_DEFAULT "mqtt2"
// #define EMSESP_TEST_DEFAULT "mqtt_nested" // #define EMSESP_DEBUG_DEFAULT "mqtt_nested"
// #define EMSESP_TEST_DEFAULT "ha" // #define EMSESP_DEBUG_DEFAULT "ha"
#define EMSESP_TEST_DEFAULT "board_profile" // #define EMSESP_DEBUG_DEFAULT "board_profile"
#define EMSESP_DEBUG_DEFAULT "shower_alert"
class Test { class Test {
public: public:

View File

@@ -1,2 +1,2 @@
#define EMSESP_APP_VERSION "3.0.1" #define EMSESP_APP_VERSION "3.1.0"
#define EMSESP_PLATFORM "ESP32" #define EMSESP_PLATFORM "ESP32"