87 Commits

Author SHA1 Message Date
proddy
4841e42286 Merge remote-tracking branch 'origin/dev' into main 2021-03-30 16:35:18 +02:00
proddy
df1c227f2c fix heartbeat on Ethernet 2021-03-30 15:52:27 +02:00
proddy
06008fcf6c Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2021-03-30 12:00:16 +02:00
proddy
15c4a3e9a5 don't disable bluetooth or adc for now 2021-03-30 12:00:09 +02:00
proddy
89f1fc8282 remove redundant code 2021-03-30 11:59:43 +02:00
proddy
ca083166a1 show board profile on boot 2021-03-30 11:59:25 +02:00
proddy
ed177396b2 cleaned up pio scripts 2021-03-30 11:59:06 +02:00
proddy
6dd901880e remove redundant code 2021-03-30 11:58:53 +02:00
MichaelDvP
d9b577d944 fix syslog reboots without eth 2021-03-30 11:12:16 +02:00
proddy
324a6da0d5 add ubadevices, which is 1st telegram sent 2021-03-29 22:26:21 +02:00
proddy
391fecadd0 formatting 2021-03-29 22:26:08 +02:00
proddy
4d0032441f start syslog when network connected 2021-03-29 22:25:44 +02:00
proddy
8e59460845 start sys;log after network to prevent crash on Ethernet 2021-03-29 11:15:07 +02:00
proddy
4b6c676992 text changes 2021-03-29 11:14:49 +02:00
proddy
0237cc1ca4 add comment 2021-03-29 11:14:29 +02:00
proddy
fbef1ca69a remove comment 2021-03-29 11:14:17 +02:00
proddy
3b4bfaa319 remove ESP8266 references 2021-03-28 21:35:30 +02:00
proddy
2b6a986c4a text changes 2021-03-28 21:35:20 +02:00
proddy
494827299c fix MQTT when only on Ethernet 2021-03-28 21:34:40 +02:00
proddy
a920e89ea2 uppercase esp32 2021-03-28 21:33:33 +02:00
proddy
6a4b7a1ac7 if on ethernet, show it's IP and not WiFi 2021-03-28 21:33:16 +02:00
proddy
621c73ab03 move around build defines 2021-03-28 20:09:33 +02:00
proddy
ac7003124e fix ld issue 2021-03-28 20:09:23 +02:00
proddy
4208c3551a fix lint errors in formatting 2021-03-28 16:55:44 +02:00
proddy
1938c93faf rename subscribes to subscribe_format 2021-03-28 16:53:01 +02:00
proddy
2a070ef55f fallback to AP when Ethernet is dropped 2021-03-28 16:28:13 +02:00
proddy
0c17e8deb3 don't process dallas if gpio is 0 2021-03-28 16:27:54 +02:00
proddy
22b4b66cff added logger so external functions can use 2021-03-28 16:27:40 +02:00
proddy
942d062506 formatting, remove wemos from board profile, fix olimex 2021-03-28 16:27:26 +02:00
proddy
7c3b8954fe added info messages for NTP 2021-03-28 16:26:48 +02:00
proddy
bf90056c61 syslog also works when Ethernet connected 2021-03-28 16:26:33 +02:00
proddy
bcd79bc250 formatting 2021-03-28 16:26:18 +02:00
proddy
07c7ef22cf remove wemos mini d1 2021-03-28 16:26:08 +02:00
proddy
6d420662e1 formatting 2021-03-28 16:25:44 +02:00
proddy
fca458687e fix flashing led on ethernet connection 2021-03-27 16:19:12 +01:00
proddy
e34620e1e8 local ip mods 2021-03-27 16:18:41 +01:00
proddy
bcdb49ffff show "quality" next to the line quality % 2021-03-27 16:04:19 +01:00
proddy
ebb71c7724 fix GH URL 2021-03-27 16:03:49 +01:00
proddy
b8dca3db32 mention that blank SSID = disabling wifi 2021-03-27 16:03:39 +01:00
proddy
94d704730f rename network scan to wifi scan 2021-03-27 16:03:13 +01:00
proddy
0c76ed2c4c custom board profile is allowed 2021-03-27 16:02:57 +01:00
proddy
8bac9f687e hide led is default off/false 2021-03-27 16:02:35 +01:00
proddy
56b597d45f update to new version 2021-03-27 16:02:13 +01:00
proddy
96b83e3eb3 fix ems line quality calculation 2021-03-27 16:02:01 +01:00
proddy
e21ad6a6ba re-enable ethernet detection code 2021-03-27 12:48:16 +01:00
proddy
7fe4b99cef fix bug when CUSTOM is chosen as board profile 2021-03-27 12:48:02 +01:00
proddy
0c8dd1d8cf added generic LAN8720 2021-03-27 12:47:46 +01:00
proddy
cafc6103ea board profiles: pre-configured pin layouts #11 2021-03-27 10:30:28 +01:00
proddy
6d3feaf81c MQTT base from std::string to String 2021-03-27 10:30:15 +01:00
proddy
c8d8b50d47 MQTT base from std::string to String 2021-03-27 10:29:47 +01:00
proddy
0c89d90d56 dallas pin fix for mt-et & nodemcu 2021-03-26 22:39:46 +01:00
proddy
d0fc09fc01 snack popups from 5 to 3 seconds 2021-03-26 17:29:54 +01:00
proddy
c8b6d1e69c rename WiFi to Network 2021-03-26 17:29:40 +01:00
proddy
49d719770c remove comment 2021-03-26 17:29:23 +01:00
proddy
c75a1c9e1e added toUpper 2021-03-26 17:29:13 +01:00
proddy
da7b0e9597 feat: board profiles (#11) 2021-03-26 17:29:00 +01:00
proddy
8f1243850f more tests 2021-03-26 17:27:39 +01:00
proddy
b931e282f2 added extra gpio pins to avoid 2021-03-24 07:48:45 +01:00
proddy
66df8031ed use gpio checker. wrong values will cause crash 2021-03-23 22:22:14 +01:00
proddy
966f82e38c gpio checker 2021-03-23 22:21:51 +01:00
proddy
118cbd9224 improve value detection 2021-03-23 22:21:29 +01:00
proddy
def585fa04 bump to b3 2021-03-23 22:21:08 +01:00
proddy
56a3dfd41a on ESP32 no need to use flash strings for MQTT names 2021-03-23 22:20:58 +01:00
proddy
cc0f4c43ae formatting 2021-03-23 22:20:38 +01:00
proddy
c341148009 minor cleanup 2021-03-23 22:19:57 +01:00
MichaelDvP
9089e5d334 mixer IPM add header temperature for unmixed circuits 2021-03-23 15:30:47 +01:00
MichaelDvP
720a82b3da thermostat datetime command only for supported models 2021-03-23 15:30:08 +01:00
MichaelDvP
a83d3a12fb individual subscriptions: resubscribe, show, some system commands 2021-03-23 15:28:50 +01:00
proddy
1dae9f8beb fix LED when connection made 2021-03-22 22:36:31 +01:00
proddy
e25d6e4d0b quit if no valid board profile 2021-03-22 22:33:23 +01:00
proddy
c01c098f7e added more board profiles for ethernet 2021-03-22 22:09:09 +01:00
proddy
fecfe9d791 added text for board profiles 2021-03-22 21:18:22 +01:00
proddy
b996c4dcf6 feat: board profiles (#11) 2021-03-22 21:12:19 +01:00
MichaelDvP
273efbcb65 update changelog 2021-03-22 19:38:17 +01:00
MichaelDvP
7d177ca049 fix compile error standalone? 2021-03-22 19:28:43 +01:00
MichaelDvP
83f46ffd6c add back reset command 2021-03-22 17:28:11 +01:00
MichaelDvP
03e43ba839 add mqtt subscribe settings, thermostat switchtime, boiler heatingsources 2021-03-22 17:17:56 +01:00
MichaelDvP
71dfc0e1eb fix uart out of bounds warning 2021-03-22 17:05:56 +01:00
proddy
355b71cacf minor tidyups 2021-03-22 09:23:57 +01:00
proddy
c660440996 fix: show all devices, except system (#31) 2021-03-22 09:23:34 +01:00
MichaelDvP
70033017fd fix #33, Junkers mode names sorted 2021-03-22 07:37:56 +01:00
proddy
b9c08a58ad fix: only create mqtt subs for Boiler (expose individual commands via MQTT topics #31) 2021-03-21 17:31:52 +01:00
proddy
4db69760c6 fix: rendering of floats 2021-03-21 13:14:05 +01:00
proddy
8ec0731ca2 update upload scripts 2021-03-21 13:02:21 +01:00
proddy
25b1957dbf minor cleanup 2021-03-21 12:48:03 +01:00
proddy
f2dbc26491 feat: expose cmd's via MQTT directly #31 2021-03-21 12:47:47 +01:00
MichaelDvP
fd11a09882 fix negative rounding 2021-03-19 19:49:07 +01:00
100 changed files with 21893 additions and 1439 deletions

View File

@@ -5,12 +5,68 @@ 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.0.1] March 30 2021
## Added
- power settings, disabling BLE and turning off Wifi sleep
- Rx and Tx counts to Heartbeat MQTT payload
- ethernet support
- id to info command to show only a heatingcircuit
- add sending devices that are not listed to 0x07
- extra MQTT boolean option for "ON" and "OFF"
- support for chunked MQTT payloads to allow large data sets > 2kb
- external Button support (#708) for resetting to factory defaults and other actions
- new console set command in `system`, `set board_profile <profile>` for quickly enabling cabled ethernet connections without using the captive wifi portal
- added in MQTT nested mode, for thermostat and mixer, like we had back in v2
- cascade MC400 (product-id 210) (3.0.0b6), power values for heating sources (3.0.1b1)
- values for wwMaxPower, wwFlowtempOffset
- RC300 `thermostat temp -1` to clear temporary setpoint in auto mode
- syslog port selectable (#744)
- individual mqtt commands (#31)
- board Profiles (#11)
## Fixed
- telegrams matched to masterthermostat 0x18
- multiple roomcontrollers
- readback after write with delay (give ems-devices time to set the value)
- thermostat ES72/RC20 device 66 to command-set RC20_2
- MQTT payloads not adding to queue when MQTT is re-connecting (fixes #369)
- fix for HA topics with invalid command formats (#728)
- wrong position of values #723, #732
- OTA Upload via Web on OSX
- Rx and Tx quality % would sometimes show > 100
## Changed
- changed how telegram parameters are rendered for mqtt, console and web (#632)
- split `show values` in smaller packages (edited)
- extended length of IP/hostname from 32 to 48 chars (#676)
- check flowsensor for `tap_water_active`
- mqtt prefixed with `Base`
- count Dallas sensor fails
- switch from SPIFFS to LITTLEFS
- added ID to MQTT payloads which is the Device's product ID and used in HA to identify a unique HA device
- increased MQTT buffer and reduced wait time between publishes
- updated to the latest ArduinoJson library
- some names of mqtt-tags like in v2.2.1
- new ESP32 partition side to allow for smoother OTA and fallback
- network Gateway IP is optional (#682)emsesp/EMS-ESP
- moved to a new GitHub repo https://github.com/emsesp/EMS-ESP32
- invert LED changed to Hide LED. Default is off.
- renamed Scan Network to Scan WiFi Network
- added version to cmd=settings
- Allow both WiFi and Ethernet together, fall back to AP when Ethernet disconnects
## Removed
- Shower Alert (disabled for now)
## [3.0.0] March 18 2021 ## [3.0.0] March 18 2021
## **ESP32 version based off ESP-ESP v2.1**
### Added ### Added
- Power settings, disabling BLE and turning off Wifi sleep - Power settings, disabling BLE and turning off Wifi sleep
- Rx and Tx counts to Heartbeat MQTT payload - Rx and Tx counts to Heartbeat MQTT payload
- Ethernet support - Ethernet support
@@ -27,6 +83,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Syslog port selectable (#744) - Syslog port selectable (#744)
### Fixed ### Fixed
- telegrams matched to masterthermostat 0x18 - telegrams matched to masterthermostat 0x18
- multiple roomcontrollers - multiple roomcontrollers
- readback after write with delay (give ems-devices time to set the value) - readback after write with delay (give ems-devices time to set the value)
@@ -37,6 +94,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- OTA Upload via Web on OSX - OTA Upload via Web on OSX
### Changed ### Changed
- changed how telegram parameters are rendered for mqtt, console and web (#632) - changed how telegram parameters are rendered for mqtt, console and web (#632)
- split `show values` in smaller packages (edited) - split `show values` in smaller packages (edited)
- extended length of IP/hostname from 32 to 48 chars (#676) - extended length of IP/hostname from 32 to 48 chars (#676)
@@ -51,4 +109,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- new ESP32 partition side to allow for smoother OTA and fallback - new ESP32 partition side to allow for smoother OTA and fallback
- Network Gateway IP is optional (#682)emsesp/EMS-ESP - Network Gateway IP is optional (#682)emsesp/EMS-ESP
- moved to a new GitHub repo https://github.com/emsesp/EMS-ESP32 - moved to a new GitHub repo https://github.com/emsesp/EMS-ESP32

View File

@@ -1,12 +1,9 @@
# Changelog # Changelog
### Added ## Added
## Fixed
### Fixed ## Changed
### Changed
### Removed
## Removed

View File

@@ -1,10 +1,11 @@
# 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. # Remember to also enable CORS in platformio.ini before uploading the code to the device
# with -DENABLE_CORS
# ESP32 dev # my Wifi
REACT_APP_HTTP_ROOT=http://10.10.10.101 #REACT_APP_HTTP_ROOT=http://10.10.10.101
REACT_APP_WEB_SOCKET_ROOT=ws://10.10.10.101 #REACT_APP_WEB_SOCKET_ROOT=ws://10.10.10.101
# ESP8266 dev # my Ethernet
#REACT_APP_HTTP_ROOT=http://10.10.10.140 REACT_APP_HTTP_ROOT=http://192.168.1.134
#REACT_APP_WEB_SOCKET_ROOT=ws://10.10.10.140 REACT_APP_WEB_SOCKET_ROOT=ws://http://192.168.1.134

20166
interface/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -28,7 +28,7 @@ class App extends Component {
render() { render() {
return ( return (
<CustomMuiTheme> <CustomMuiTheme>
<SnackbarProvider maxSnack={3} anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }} <SnackbarProvider autoHideDuration={3000} maxSnack={3} anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
ref={this.notistackRef} ref={this.notistackRef}
action={(key) => ( action={(key) => (
<IconButton onClick={this.onClickDismiss(key)} size="small"> <IconButton onClick={this.onClickDismiss(key)} size="small">

View File

@@ -30,7 +30,7 @@ class APSettingsForm extends React.Component<APSettingsFormProps> {
onChange={handleValueChange('provision_mode')} onChange={handleValueChange('provision_mode')}
margin="normal"> margin="normal">
<MenuItem value={APProvisionMode.AP_MODE_ALWAYS}>Always</MenuItem> <MenuItem value={APProvisionMode.AP_MODE_ALWAYS}>Always</MenuItem>
<MenuItem value={APProvisionMode.AP_MODE_DISCONNECTED}>When WiFi Disconnected</MenuItem> <MenuItem value={APProvisionMode.AP_MODE_DISCONNECTED}>When Network Disconnected</MenuItem>
<MenuItem value={APProvisionMode.AP_NEVER}>Never</MenuItem> <MenuItem value={APProvisionMode.AP_NEVER}>Never</MenuItem>
</SelectValidator> </SelectValidator>
{ {

View File

@@ -173,6 +173,17 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
<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"
label="Subscribe Format"
value={data.subscribe_format}
fullWidth
variant="outlined"
onChange={handleValueChange('subscribe_format')}
margin="normal">
<MenuItem value={0}>general device topic</MenuItem>
<MenuItem value={1}>individual topics, main heating circuit</MenuItem>
<MenuItem value={2}>individual topics, all heating circuits</MenuItem>
</SelectValidator>
<BlockFormControlLabel <BlockFormControlLabel
control={ control={
<Checkbox <Checkbox

View File

@@ -41,4 +41,5 @@ export interface MqttSettings {
ha_enabled: boolean; ha_enabled: boolean;
ha_climate_format: number; ha_climate_format: number;
nested_format: boolean; nested_format: boolean;
subscribe_format: number;
} }

View File

@@ -45,7 +45,7 @@ class NetworkConnection extends Component<NetworkConnectionProps, NetworkConnect
<MenuAppBar sectionTitle="Network Connection"> <MenuAppBar sectionTitle="Network Connection">
<Tabs value={this.props.match.url} onChange={this.handleTabChange} variant="fullWidth"> <Tabs value={this.props.match.url} onChange={this.handleTabChange} variant="fullWidth">
<Tab value="/network/status" label="Network Status" /> <Tab value="/network/status" label="Network Status" />
<Tab value="/network/scan" label="Scan Networks" disabled={!authenticatedContext.me.admin} /> <Tab value="/network/scan" label="Scan WiFi Networks" disabled={!authenticatedContext.me.admin} />
<Tab value="/network/settings" label="Network Settings" disabled={!authenticatedContext.me.admin} /> <Tab value="/network/settings" label="Network Settings" disabled={!authenticatedContext.me.admin} />
</Tabs> </Tabs>
<Switch> <Switch>

View File

@@ -1,5 +1,5 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { TextValidator, SelectValidator, ValidatorForm } from 'react-material-ui-form-validator'; import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator';
import { Checkbox, List, ListItem, ListItemText, ListItemAvatar, ListItemSecondaryAction } from '@material-ui/core'; import { Checkbox, List, ListItem, ListItemText, ListItemAvatar, ListItemSecondaryAction } from '@material-ui/core';
@@ -9,7 +9,6 @@ import LockIcon from '@material-ui/icons/Lock';
import LockOpenIcon from '@material-ui/icons/LockOpen'; import LockOpenIcon from '@material-ui/icons/LockOpen';
import DeleteIcon from '@material-ui/icons/Delete'; import DeleteIcon from '@material-ui/icons/Delete';
import SaveIcon from '@material-ui/icons/Save'; import SaveIcon from '@material-ui/icons/Save';
import MenuItem from '@material-ui/core/MenuItem';
import { RestFormProps, PasswordValidator, BlockFormControlLabel, FormActions, FormButton } from '../components'; import { RestFormProps, PasswordValidator, BlockFormControlLabel, FormActions, FormButton } from '../components';
import { isIP, isHostname, optional } from '../validators'; import { isIP, isHostname, optional } from '../validators';
@@ -34,7 +33,6 @@ class NetworkSettingsForm extends React.Component<NetworkStatusFormProps> {
ssid: selectedNetwork.ssid, ssid: selectedNetwork.ssid,
password: "", password: "",
hostname: props.data.hostname, hostname: props.data.hostname,
ethernet_profile: 0,
static_ip_config: false, static_ip_config: false,
} }
props.setData(networkSettings); props.setData(networkSettings);
@@ -86,7 +84,7 @@ class NetworkSettingsForm extends React.Component<NetworkStatusFormProps> {
validators={['matchRegexp:^.{0,32}$']} validators={['matchRegexp:^.{0,32}$']}
errorMessages={['SSID must be 32 characters or less']} errorMessages={['SSID must be 32 characters or less']}
name="ssid" name="ssid"
label="SSID" label="SSID (leave blank to disable WiFi)"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.ssid} value={data.ssid}
@@ -119,17 +117,6 @@ class NetworkSettingsForm extends React.Component<NetworkStatusFormProps> {
onChange={handleValueChange('hostname')} onChange={handleValueChange('hostname')}
margin="normal" margin="normal"
/> />
<SelectValidator name="ems_bus_id"
label="Ethernet Profile"
value={data.ethernet_profile}
fullWidth
variant="outlined"
onChange={handleValueChange('ethernet_profile')}
margin="normal">
<MenuItem value={0}>None (wifi only)</MenuItem>
<MenuItem value={1}>Profile 1 (LAN8720)</MenuItem>
<MenuItem value={2}>Profile 2 (TLK110)</MenuItem>
</SelectValidator>
<BlockFormControlLabel <BlockFormControlLabel
control={ control={
<Checkbox <Checkbox

View File

@@ -36,7 +36,6 @@ export interface NetworkSettings {
ssid: string; ssid: string;
password: string; password: string;
hostname: string; hostname: string;
ethernet_profile: number;
static_ip_config: boolean; static_ip_config: boolean;
local_ip?: string; local_ip?: string;
gateway_ip?: string; gateway_ip?: string;

View File

@@ -0,0 +1,23 @@
import React from 'react';
import MenuItem from '@material-ui/core/MenuItem';
type BoardProfiles = {
[name: string]: string
};
export const BOARD_PROFILES: BoardProfiles = {
"S32": "BBQKees Gateway S32",
"E32": "BBQKees Gateway E32",
"NODEMCU": "NodeMCU 32S",
"MT-ET": "MT-ET Live D1 Mini",
"LOLIN": "Lolin D32",
"OLIMEX": "Olimex ESP32-EVB",
"TLK110": "Generic Ethernet (TLK110)",
"LAN8720": "Generic Ethernet (LAN8720)"
}
export function boardProfileSelectItems() {
return Object.keys(BOARD_PROFILES).map(code => (
<MenuItem key={code} value={code}>{BOARD_PROFILES[code]}</MenuItem>
));
}

View File

@@ -2,40 +2,19 @@ import React, { Component, Fragment } from "react";
import { withStyles, Theme, createStyles } from "@material-ui/core/styles"; import { withStyles, Theme, createStyles } from "@material-ui/core/styles";
import { import {
Table, Table, TableBody, TableCell, TableHead, TableRow, TableContainer, withWidth, WithWidthProps, isWidthDown,
TableBody, Button, Tooltip, DialogTitle, DialogContent, DialogActions, Box, Dialog, Typography
TableCell,
TableHead,
TableRow,
TableContainer,
withWidth,
WithWidthProps,
isWidthDown,
Button,
Tooltip,
DialogTitle,
DialogContent,
DialogActions,
Box,
Dialog,
Typography,
} from "@material-ui/core"; } from "@material-ui/core";
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 { import { redirectingAuthorizedFetch, withAuthenticatedContext, AuthenticatedContextProps } from "../authentication";
redirectingAuthorizedFetch,
withAuthenticatedContext,
AuthenticatedContextProps,
} from "../authentication";
import { RestFormProps, FormButton } from "../components"; import { RestFormProps, FormButton } from "../components";
import { EMSESPDevices, EMSESPDeviceData, Device } from "./EMSESPtypes"; import { EMSESPDevices, EMSESPDeviceData, Device } from "./EMSESPtypes";
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";
@@ -73,22 +52,19 @@ type EMSESPDevicesFormProps = RestFormProps<EMSESPDevices> &
function formatTemp(t: string) { function formatTemp(t: string) {
if (t == null) { if (t == null) {
return "(not available)"; return "n/a";
} }
return t + " °C"; return t + " °C";
} }
function formatUnit(u: string) { function formatUnit(u: string) {
if (u == null) { if (u == null) {
return u; return u;
} }
return " " + u; return " " + u;
} }
class EMSESPDevicesForm extends Component< class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesFormState> {
EMSESPDevicesFormProps,
EMSESPDevicesFormState
> {
state: EMSESPDevicesFormState = { state: EMSESPDevicesFormState = {
confirmScanDevices: false, confirmScanDevices: false,
processing: false, processing: false,
@@ -106,7 +82,7 @@ class EMSESPDevicesForm extends Component<
return (this.state.deviceData?.data || []).length === 0; return (this.state.deviceData?.data || []).length === 0;
}; };
createDeviceItems() { renderDeviceItems() {
const { width, data } = this.props; const { width, data } = this.props;
return ( return (
<TableContainer> <TableContainer>
@@ -186,7 +162,7 @@ class EMSESPDevicesForm extends Component<
); );
} }
createSensorItems() { renderSensorItems() {
const { data } = this.props; const { data } = this.props;
return ( return (
<TableContainer> <TableContainer>
@@ -304,7 +280,6 @@ class EMSESPDevicesForm extends Component<
.then((response) => { .then((response) => {
if (response.status === 200) { if (response.status === 200) {
return response.json(); return response.json();
// this.setState({ errorMessage: undefined }, this.props.loadData);
} }
throw Error("Unexpected response code: " + response.status); throw Error("Unexpected response code: " + response.status);
}) })
@@ -379,9 +354,9 @@ class EMSESPDevicesForm extends Component<
return ( return (
<Fragment> <Fragment>
<br></br> <br></br>
{this.createDeviceItems()} {this.renderDeviceItems()}
{this.renderDeviceData()} {this.renderDeviceData()}
{this.createSensorItems()} {this.renderSensorItems()}
<br></br> <br></br>
<Box display="flex" flexWrap="wrap"> <Box display="flex" flexWrap="wrap">
<Box flexGrow={1} padding={1}> <Box flexGrow={1} padding={1}>

View File

@@ -1,14 +1,10 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { ValidatorForm, TextValidator, SelectValidator } from 'react-material-ui-form-validator'; // import { Container } from '@material-ui/core';
import { Checkbox, Typography, Box, Link } from '@material-ui/core';
import SaveIcon from '@material-ui/icons/Save';
import MenuItem from '@material-ui/core/MenuItem';
import { ENDPOINT_ROOT } from '../api'; import { ENDPOINT_ROOT } from '../api';
import { restController, RestControllerProps, RestFormLoader, RestFormProps, FormActions, FormButton, BlockFormControlLabel, SectionContent } from '../components'; import EMSESPSettingsForm from './EMSESPSettingsForm';
import { isIP, optional } from '../validators'; import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components';
import { EMSESPSettings } from './EMSESPtypes'; import { EMSESPSettings } from './EMSESPtypes';
@@ -19,303 +15,24 @@ type EMSESPSettingsControllerProps = RestControllerProps<EMSESPSettings>;
class EMSESPSettingsController extends Component<EMSESPSettingsControllerProps> { class EMSESPSettingsController extends Component<EMSESPSettingsControllerProps> {
componentDidMount() { componentDidMount() {
ValidatorForm.addValidationRule('isOptionalIP', optional(isIP));
this.props.loadData(); this.props.loadData();
} }
render() { render() {
return ( return (
<SectionContent title='EMS-ESP Settings' titleGutter> // <Container maxWidth="md" disableGutters>
<SectionContent title='' titleGutter>
<RestFormLoader <RestFormLoader
{...this.props} {...this.props}
render={props => ( render={formProps => (
<EMSESPSettingsControllerForm {...props} /> <EMSESPSettingsForm {...formProps} />
)} )}
/> />
</SectionContent> </SectionContent>
// </Container>
) )
} }
} }
export default restController(EMSESP_SETTINGS_ENDPOINT, EMSESPSettingsController); export default restController(EMSESP_SETTINGS_ENDPOINT, EMSESPSettingsController);
type EMSESPSettingsControllerFormProps = RestFormProps<EMSESPSettings>;
function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps) {
const { data, saveData, handleValueChange } = props;
return (
<ValidatorForm onSubmit={saveData}>
<Box bgcolor="info.main" p={2} mt={2} mb={2}>
<Typography variant="body1">
Change the default settings on this page. For help click <Link target="_blank" href="https://emsesp.github.io/docs/#/Configure-firmware?id=settings" color="primary">{'here'}</Link>.
</Typography>
</Box>
<br></br>
<Typography variant="h6" color="primary" >
EMS Bus
</Typography>
<SelectValidator name="tx_mode"
label="Tx Mode"
value={data.tx_mode}
fullWidth
variant="outlined"
onChange={handleValueChange('tx_mode')}
margin="normal">
<MenuItem value={0}>0 - Off</MenuItem>
<MenuItem value={1}>1 - Default</MenuItem>
<MenuItem value={2}>2 - EMS+</MenuItem>
<MenuItem value={3}>3 - HT3</MenuItem>
<MenuItem value={4}>4 - Hardware</MenuItem>
</SelectValidator>
<SelectValidator name="ems_bus_id"
label="Bus ID"
value={data.ems_bus_id}
fullWidth
variant="outlined"
onChange={handleValueChange('ems_bus_id')}
margin="normal">
<MenuItem value={0x0B}>Service Key (0x0B)</MenuItem>
<MenuItem value={0x0D}>Modem (0x0D)</MenuItem>
<MenuItem value={0x0A}>Terminal (0x0A)</MenuItem>
<MenuItem value={0x0F}>Time Module (0x0F)</MenuItem>
<MenuItem value={0x12}>Alarm Module (0x12)</MenuItem>
</SelectValidator>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:40']}
errorMessages={['Rx GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 40"]}
name="rx_gpio"
label="Rx GPIO pin"
fullWidth
variant="outlined"
value={data.rx_gpio}
type="number"
onChange={handleValueChange('rx_gpio')}
margin="normal"
/>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:40']}
errorMessages={['Tx GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 40"]}
name="tx_gpio"
label="Tx GPIO pin"
fullWidth
variant="outlined"
value={data.tx_gpio}
type="number"
onChange={handleValueChange('tx_gpio')}
margin="normal"
/>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:120']}
errorMessages={['Tx delay is required', "Must be a number", "Must be 0 or higher", "Max value is 120"]}
name="tx_delay"
label="Tx delayed start (seconds)"
fullWidth
variant="outlined"
value={data.tx_delay}
type="number"
onChange={handleValueChange('tx_delay')}
margin="normal"
/>
<br></br>
<Typography variant="h6" color="primary" >
External Button
</Typography>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:40']}
errorMessages={['Button GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 40"]}
name="pbutton_gpio"
label="Button GPIO pin"
fullWidth
variant="outlined"
value={data.pbutton_gpio}
type="number"
onChange={handleValueChange('pbutton_gpio')}
margin="normal"
/>
<br></br>
<Typography variant="h6" color="primary" >
Dallas Sensor
</Typography>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:40']}
errorMessages={['Dallas GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 40"]}
name="dallas_gpio"
label="Dallas GPIO pin (0=none)"
fullWidth
variant="outlined"
value={data.dallas_gpio}
type="number"
onChange={handleValueChange('dallas_gpio')}
margin="normal"
/>
<BlockFormControlLabel
control={
<Checkbox
checked={data.dallas_parasite}
onChange={handleValueChange('dallas_parasite')}
value="dallas_parasite"
/>
}
label="Dallas Parasite Mode"
/>
<br></br>
<Typography variant="h6" color="primary" >
LED
</Typography>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:40']}
errorMessages={['LED GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 40"]}
name="led_gpio"
label="LED GPIO pin (0=none)"
fullWidth
variant="outlined"
value={data.led_gpio}
type="number"
onChange={handleValueChange('led_gpio')}
margin="normal"
/>
<BlockFormControlLabel
control={
<Checkbox
checked={data.hide_led}
onChange={handleValueChange('hide_led')}
value="hide_led"
/>
}
label="Invert/Hide LED"
/>
<br></br>
<Typography variant="h6" color="primary" >
Shower
</Typography>
<BlockFormControlLabel
control={
<Checkbox
checked={data.shower_timer}
onChange={handleValueChange('shower_timer')}
value="shower_timer"
/>
}
label="Shower Timer"
/>
<BlockFormControlLabel
control={
<Checkbox
checked={data.shower_alert}
onChange={handleValueChange('shower_alert')}
value="shower_alert"
/>
}
label="Shower Alert"
/>
<br></br>
<Typography variant="h6" color="primary" >
API
</Typography>
<BlockFormControlLabel
control={
<Checkbox
checked={data.api_enabled}
onChange={handleValueChange('api_enabled')}
value="api_enabled"
/>
}
label="Allow WEB API to write commands"
/>
<br></br>
<Typography variant="h6" color="primary" >
Syslog
</Typography>
<BlockFormControlLabel
control={
<Checkbox
checked={data.syslog_enabled}
onChange={handleValueChange('syslog_enabled')}
value="syslog_enabled"
/>
}
label="Enable Syslog"
/>
<TextValidator
validators={['isOptionalIP']}
errorMessages={["Not a valid IP address"]}
name="syslog_host"
label="Syslog IP"
fullWidth
variant="outlined"
value={data.syslog_host}
onChange={handleValueChange('syslog_host')}
margin="normal"
/>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']}
errorMessages={['Port is required', "Must be a number", "Must be greater than 0 ", "Max value is 65535"]}
name="syslog_port"
label="Syslog Port (default 514)"
fullWidth
variant="outlined"
value={data.syslog_port}
type="number"
onChange={handleValueChange('syslog_port')}
margin="normal"
/>
<SelectValidator name="syslog_level"
label="Syslog Log Level"
value={data.syslog_level}
fullWidth
variant="outlined"
onChange={handleValueChange('syslog_level')}
margin="normal">
<MenuItem value={-1}>OFF</MenuItem>
<MenuItem value={3}>ERR</MenuItem>
<MenuItem value={5}>NOTICE</MenuItem>
<MenuItem value={6}>INFO</MenuItem>
<MenuItem value={7}>DEBUG</MenuItem>
<MenuItem value={8}>ALL</MenuItem>
</SelectValidator>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']}
errorMessages={['Syslog Mark is required', "Must be a number", "Must be 0 or higher", "Max value is 10"]}
name="syslog_mark_interval"
label="Syslog Mark Interval (seconds, 0=off)"
fullWidth
variant="outlined"
value={data.syslog_mark_interval}
type="number"
onChange={handleValueChange('syslog_mark_interval')}
margin="normal"
/>
<BlockFormControlLabel
control={
<Checkbox
checked={data.trace_raw}
onChange={handleValueChange('trace_raw')}
value="trace_raw"
/>
}
label="Trace EMS telegrams in raw format"
/>
<br></br>
<Typography variant="h6" color="primary" >
Analog Input
</Typography>
<BlockFormControlLabel
control={
<Checkbox
checked={data.analog_enabled}
onChange={handleValueChange('analog_enabled')}
value="analog_enabled"
/>
}
label="Enable ADC"
/>
<br></br>
<FormActions>
<FormButton startIcon={<SaveIcon />} variant="contained" color="primary" type="submit">
Save
</FormButton>
</FormActions>
</ValidatorForm>
);
}

View File

@@ -0,0 +1,417 @@
import React from 'react';
import { ValidatorForm, TextValidator, SelectValidator } from 'react-material-ui-form-validator';
import { Checkbox, Typography, Box, Link, withWidth, WithWidthProps } from '@material-ui/core';
import SaveIcon from '@material-ui/icons/Save';
import MenuItem from '@material-ui/core/MenuItem';
import Grid from '@material-ui/core/Grid';
import { redirectingAuthorizedFetch, withAuthenticatedContext, AuthenticatedContextProps } from "../authentication";
import { RestFormProps, FormActions, FormButton, BlockFormControlLabel } from '../components';
import { isIP, optional } from '../validators';
import { EMSESPSettings } from './EMSESPtypes';
import { boardProfileSelectItems } from './EMSESPBoardProfiles';
import { ENDPOINT_ROOT } from "../api";
export const BOARD_PROFILE_ENDPOINT = ENDPOINT_ROOT + "boardProfile";
type EMSESPSettingsFormProps = RestFormProps<EMSESPSettings> & AuthenticatedContextProps & WithWidthProps;
interface EMSESPSettingsFormState {
processing: boolean;
}
class EMSESPSettingsForm extends React.Component<EMSESPSettingsFormProps> {
state: EMSESPSettingsFormState = {
processing: false
}
componentDidMount() {
ValidatorForm.addValidationRule('isOptionalIP', optional(isIP));
}
changeBoardProfile = (event: React.ChangeEvent<HTMLSelectElement>) => {
const { data, setData } = this.props;
setData({
...data,
board_profile: event.target.value
});
if (event.target.value === "CUSTOM")
return;
this.setState({ processing: true });
redirectingAuthorizedFetch(BOARD_PROFILE_ENDPOINT, {
method: "POST",
body: JSON.stringify({ code: event.target.value }),
headers: {
"Content-Type": "application/json",
},
})
.then((response) => {
if (response.status === 200) {
return response.json();
}
throw Error("Unexpected response code: " + response.status);
})
.then((json) => {
this.props.enqueueSnackbar("Profile loaded", { variant: 'success' });
setData({
...data,
led_gpio: json.led_gpio,
dallas_gpio: json.dallas_gpio,
rx_gpio: json.rx_gpio,
tx_gpio: json.tx_gpio,
pbutton_gpio: json.pbutton_gpio,
board_profile: event.target.value
});
this.setState({ processing: false });
})
.catch((error) => {
this.props.enqueueSnackbar(
error.message || "Problem fetching board profile",
{ variant: "warning" }
);
this.setState({ processing: false });
});
};
render() {
const { data, saveData, handleValueChange } = this.props;
return (
<ValidatorForm onSubmit={saveData}>
<Box bgcolor="info.main" p={2} mt={2} mb={2}>
<Typography variant="body1">
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>.
</Typography>
</Box>
<br></br>
<Typography variant="h6" color="primary" >
EMS Bus
</Typography>
<Grid container spacing={1} direction="row" justify="flex-start" alignItems="flex-start">
<Grid item xs={5}>
<SelectValidator name="tx_mode"
label="Tx Mode"
value={data.tx_mode}
fullWidth
variant="outlined"
onChange={handleValueChange('tx_mode')}
margin="normal">
<MenuItem value={0}>Off</MenuItem>
<MenuItem value={1}>EMS</MenuItem>
<MenuItem value={2}>EMS+</MenuItem>
<MenuItem value={3}>HT3</MenuItem>
<MenuItem value={4}>Hardware</MenuItem>
</SelectValidator>
</Grid>
<Grid item xs={6}>
<SelectValidator name="ems_bus_id"
label="Bus ID"
value={data.ems_bus_id}
fullWidth
variant="outlined"
onChange={handleValueChange('ems_bus_id')}
margin="normal">
<MenuItem value={0x0B}>Service Key (0x0B)</MenuItem>
<MenuItem value={0x0D}>Modem (0x0D)</MenuItem>
<MenuItem value={0x0A}>Terminal (0x0A)</MenuItem>
<MenuItem value={0x0F}>Time Module (0x0F)</MenuItem>
<MenuItem value={0x12}>Alarm Module (0x12)</MenuItem>
</SelectValidator>
</Grid>
<Grid item xs={6}>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:120']}
errorMessages={['Tx delay is required', "Must be a number", "Must be 0 or higher", "Max value is 120"]}
name="tx_delay"
label="Tx start delay (seconds)"
fullWidth
variant="outlined"
value={data.tx_delay}
type="number"
onChange={handleValueChange('tx_delay')}
margin="normal"
/>
</Grid>
</Grid>
<br></br>
<Typography variant="h6" color="primary" >
Board Profile
</Typography>
<Box color="warning.main" p={0} mt={0} mb={0}>
<Typography variant="body2">
<i>Select a pre-configured board layout to automatically set the GPIO pins, or set your own custom configuration</i>
</Typography>
</Box>
<SelectValidator name="board_profile"
label="Board Profile"
value={data.board_profile}
fullWidth
variant="outlined"
onChange={this.changeBoardProfile}
margin="normal">
{boardProfileSelectItems()}
<MenuItem key={"CUSTOM"} value={"CUSTOM"}>Custom...</MenuItem>
</SelectValidator>
{ (data.board_profile === "CUSTOM") &&
<Grid container spacing={1} direction="row" justify="flex-start" alignItems="flex-start">
<Grid item xs={4}>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:40', 'matchRegexp:^((?!6|7|8|9|10|11|12|14|15|20|24|28|29|30|31)[0-9]*)$']}
errorMessages={['GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 40", "Not a valid GPIO"]}
name="rx_gpio"
label="Rx GPIO"
fullWidth
variant="outlined"
value={data.rx_gpio}
type="number"
onChange={handleValueChange('rx_gpio')}
margin="normal"
/>
</Grid>
<Grid item xs={4}>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:40', 'matchRegexp:^((?!6|7|8|9|10|11|12|14|15|20|24|28|29|30|31)[0-9]*)$']}
errorMessages={['GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 40", "Not a valid GPIO"]}
name="tx_gpio"
label="Tx GPIO"
fullWidth
variant="outlined"
value={data.tx_gpio}
type="number"
onChange={handleValueChange('tx_gpio')}
margin="normal"
/>
</Grid>
<Grid item xs={4}>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:40', 'matchRegexp:^((?!6|7|8|9|10|11|12|14|15|20|24|28|29|30|31)[0-9]*)$']}
errorMessages={['GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 40", "Not a valid GPIO"]}
name="pbutton_gpio"
label="Button GPIO"
fullWidth
variant="outlined"
value={data.pbutton_gpio}
type="number"
onChange={handleValueChange('pbutton_gpio')}
margin="normal"
/>
</Grid>
<Grid item xs={4}>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:40', 'matchRegexp:^((?!6|7|8|9|10|11|12|14|15|20|24|28|29|30|31)[0-9]*)$']}
errorMessages={['GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 40", "Not a valid GPIO"]}
name="dallas_gpio"
label="Dallas GPIO (0=none)"
fullWidth
variant="outlined"
value={data.dallas_gpio}
type="number"
onChange={handleValueChange('dallas_gpio')}
margin="normal"
/>
</Grid>
<Grid item xs={4}>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:40', 'matchRegexp:^((?!6|7|8|9|10|11|12|14|15|20|24|28|29|30|31)[0-9]*)$']}
errorMessages={['GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 40", "Not a valid GPIO"]}
name="led_gpio"
label="LED GPIO (0=none)"
fullWidth
variant="outlined"
value={data.led_gpio}
type="number"
onChange={handleValueChange('led_gpio')}
margin="normal"
/>
</Grid>
</Grid>
}
<br></br>
<Typography variant="h6" color="primary" >
Options
</Typography>
{ data.dallas_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
control={
<Checkbox
checked={data.hide_led}
onChange={handleValueChange('hide_led')}
value="hide_led"
/>
}
label = "Hide LED"
/>
}
<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="Shower Timer"
/>
{/* <BlockFormControlLabel
control={
<Checkbox
checked={data.shower_alert}
onChange={handleValueChange('shower_alert')}
value="shower_alert"
/>
}
label="Shower Alert"
/> */}
</Grid>
<BlockFormControlLabel
control={
<Checkbox
checked={data.api_enabled}
onChange={handleValueChange('api_enabled')}
value="api_enabled"
/>
}
label="Enable API write commands"
/>
<BlockFormControlLabel
control={
<Checkbox
checked={data.analog_enabled}
onChange={handleValueChange('analog_enabled')}
value="analog_enabled"
/>
}
label="Enable ADC"
/>
<br></br>
<Typography variant="h6" color="primary" >
Syslog
</Typography>
<BlockFormControlLabel
control={
<Checkbox
checked={data.syslog_enabled}
onChange={handleValueChange('syslog_enabled')}
value="syslog_enabled"
/>
}
label="Enable Syslog"
/>
{ data.syslog_enabled &&
<Grid container spacing={1} direction="row" justify="flex-start" alignItems="flex-start">
<Grid item xs={5}>
<TextValidator
validators={['isOptionalIP']}
errorMessages={["Not a valid IP address"]}
name="syslog_host"
label="IP"
fullWidth
variant="outlined"
value={data.syslog_host}
onChange={handleValueChange('syslog_host')}
margin="normal"
/>
</Grid>
<Grid item xs={6}>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']}
errorMessages={['Port is required', "Must be a number", "Must be greater than 0 ", "Max value is 65535"]}
name="syslog_port"
label="Port"
fullWidth
variant="outlined"
value={data.syslog_port}
type="number"
onChange={handleValueChange('syslog_port')}
margin="normal"
/>
</Grid>
<Grid item xs={5}>
<SelectValidator name="syslog_level"
label="Log Level"
value={data.syslog_level}
fullWidth
variant="outlined"
onChange={handleValueChange('syslog_level')}
margin="normal">
<MenuItem value={-1}>OFF</MenuItem>
<MenuItem value={3}>ERR</MenuItem>
<MenuItem value={5}>NOTICE</MenuItem>
<MenuItem value={6}>INFO</MenuItem>
<MenuItem value={7}>DEBUG</MenuItem>
<MenuItem value={8}>ALL</MenuItem>
</SelectValidator>
</Grid>
<Grid item xs={6}>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']}
errorMessages={['Syslog Mark is required', "Must be a number", "Must be 0 or higher", "Max value is 10"]}
name="syslog_mark_interval"
label="Mark Interval seconds (0=off)"
fullWidth
variant="outlined"
value={data.syslog_mark_interval}
type="number"
onChange={handleValueChange('syslog_mark_interval')}
margin="normal"
/>
</Grid>
<BlockFormControlLabel
control={
<Checkbox
checked={data.trace_raw}
onChange={handleValueChange('trace_raw')}
value="trace_raw"
/>
}
label="Output EMS telegrams in raw format"
/>
</Grid>
}
<br></br>
<FormActions>
<FormButton startIcon={<SaveIcon />} variant="contained" color="primary" type="submit">
Save
</FormButton>
</FormActions>
</ValidatorForm >
);
}
}
export default withAuthenticatedContext(withWidth()(EMSESPSettingsForm));

View File

@@ -62,14 +62,14 @@ class EMSESPStatusForm extends Component<EMSESPStatusFormProps> {
<TableCell> <TableCell>
# Telegrams Received # Telegrams Received
</TableCell> </TableCell>
<TableCell align="right">{formatNumber(data.rx_received)}&nbsp;({data.rx_quality}%) <TableCell align="right">{formatNumber(data.rx_received)}&nbsp;(quality {data.rx_quality}%)
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableCell > <TableCell >
# Telegrams Sent # Telegrams Sent
</TableCell > </TableCell >
<TableCell align="right">{formatNumber(data.tx_sent)}&nbsp;({data.tx_quality}%) <TableCell align="right">{formatNumber(data.tx_sent)}&nbsp;(quality {data.tx_quality}%)
</TableCell> </TableCell>
</TableRow> </TableRow>
</TableBody> </TableBody>

View File

@@ -20,6 +20,7 @@ export interface EMSESPSettings {
analog_enabled: boolean; analog_enabled: boolean;
pbutton_gpio: number; pbutton_gpio: number;
trace_raw: boolean; trace_raw: boolean;
board_profile: string;
} }
export enum busConnectionStatus { export enum busConnectionStatus {

View File

@@ -35,7 +35,7 @@ class SecuritySettingsForm extends React.Component<SecuritySettingsFormProps> {
/> />
<Box bgcolor="primary.main" color="primary.contrastText" p={2} mt={2} mb={2}> <Box bgcolor="primary.main" color="primary.contrastText" p={2} mt={2} mb={2}>
<Typography variant="body1"> <Typography variant="body1">
The Super User password is used to sign authentication tokens and also the Console's `su` password. If you modify this all users will be signed out. The Super User password is used to sign authentication tokens and is also the Console's `su` password. If you modify this all users will be signed out.
</Typography> </Typography>
</Box> </Box>
<FormActions> <FormActions>

View File

@@ -22,10 +22,8 @@ class UploadFirmwareForm extends React.Component<UploadFirmwareFormProps> {
return ( return (
<Fragment> <Fragment>
<Box py={2}> <Box py={2}>
Upload a new firmware file (.bin or .bin.gz) below to replace the Upload a new firmware file (.bin or .bin.gz) below to replace the existing firmware.
existing firmware. <p></p>This can take up to a minute. Wait until you see "Activating new Firmware" and EMS-ESP will then automatically restart.
<p></p>This can take up to a minute. Wait until you see "Activating
new Firmware" and EMS-ESP will automatically restart.
</Box> </Box>
<SingleUpload <SingleUpload
onDrop={this.handleDrop} onDrop={this.handleDrop}

View File

@@ -22,22 +22,20 @@ void APSettingsService::reconfigureAP() {
} }
void APSettingsService::loop() { void APSettingsService::loop() {
// if we have an ETH connection, quit
if (emsesp::EMSESP::system_.ethernet_connected()) {
return;
}
unsigned long currentMillis = uuid::get_uptime(); unsigned long currentMillis = uuid::get_uptime();
unsigned long manageElapsed = (uint32_t)(currentMillis - _lastManaged); unsigned long manageElapsed = (uint32_t)(currentMillis - _lastManaged);
if (manageElapsed >= MANAGE_NETWORK_DELAY) { if (manageElapsed >= MANAGE_NETWORK_DELAY) {
_lastManaged = currentMillis; _lastManaged = currentMillis;
manageAP(); manageAP();
} }
handleDNS(); handleDNS();
} }
void APSettingsService::manageAP() { void APSettingsService::manageAP() {
WiFiMode_t currentWiFiMode = WiFi.getMode(); WiFiMode_t currentWiFiMode = WiFi.getMode();
if (_state.provisionMode == AP_MODE_ALWAYS || (_state.provisionMode == AP_MODE_DISCONNECTED && WiFi.status() != WL_CONNECTED)) { bool network_connected = (emsesp::EMSESP::system_.ethernet_connected() || (WiFi.status() == WL_CONNECTED));
if (_state.provisionMode == AP_MODE_ALWAYS || (_state.provisionMode == AP_MODE_DISCONNECTED && !network_connected)) {
if (_reconfigureAp || currentWiFiMode == WIFI_OFF || currentWiFiMode == WIFI_STA) { if (_reconfigureAp || currentWiFiMode == WIFI_OFF || currentWiFiMode == WIFI_STA) {
startAP(); startAP();
} }
@@ -48,13 +46,11 @@ void APSettingsService::manageAP() {
} }
void APSettingsService::startAP() { void APSettingsService::startAP() {
// Serial.println(F("Starting software access point"));
WiFi.softAPConfig(_state.localIP, _state.gatewayIP, _state.subnetMask); WiFi.softAPConfig(_state.localIP, _state.gatewayIP, _state.subnetMask);
WiFi.softAP(_state.ssid.c_str(), _state.password.c_str()); WiFi.softAP(_state.ssid.c_str(), _state.password.c_str());
if (!_dnsServer) { if (!_dnsServer) {
IPAddress apIp = WiFi.softAPIP(); IPAddress apIp = WiFi.softAPIP();
// Serial.print(F("Starting captive portal on ")); emsesp::EMSESP::logger().info(F("Starting Access Point with captive portal on %s"), apIp.toString().c_str());
// Serial.println(apIp);
_dnsServer = new DNSServer; _dnsServer = new DNSServer;
_dnsServer->start(DNS_PORT, "*", apIp); _dnsServer->start(DNS_PORT, "*", apIp);
} }
@@ -62,12 +58,11 @@ void APSettingsService::startAP() {
void APSettingsService::stopAP() { void APSettingsService::stopAP() {
if (_dnsServer) { if (_dnsServer) {
// Serial.println(F("Stopping captive portal")); emsesp::EMSESP::logger().info(F("Stopping Access Point"));
_dnsServer->stop(); _dnsServer->stop();
delete _dnsServer; delete _dnsServer;
_dnsServer = nullptr; _dnsServer = nullptr;
} }
// Serial.println(F("Stopping software access point"));
WiFi.softAPdisconnect(true); WiFi.softAPdisconnect(true);
} }

View File

@@ -1,10 +1,10 @@
#include <APStatus.h> #include <APStatus.h>
using namespace std::placeholders; // for `_1` etc
APStatus::APStatus(AsyncWebServer * server, SecurityManager * securityManager, APSettingsService * apSettingsService) APStatus::APStatus(AsyncWebServer * server, SecurityManager * securityManager, APSettingsService * apSettingsService)
: _apSettingsService(apSettingsService) { : _apSettingsService(apSettingsService) {
server->on(AP_STATUS_SERVICE_PATH, server->on(AP_STATUS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&APStatus::apStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
HTTP_GET,
securityManager->wrapRequest(std::bind(&APStatus::apStatus, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED));
} }
void APStatus::apStatus(AsyncWebServerRequest * request) { void APStatus::apStatus(AsyncWebServerRequest * request) {

View File

@@ -1,13 +1,8 @@
#ifndef APStatus_h #ifndef APStatus_h
#define APStatus_h #define APStatus_h
#ifdef ESP32
#include <WiFi.h> #include <WiFi.h>
#include <AsyncTCP.h> #include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <AsyncJson.h> #include <AsyncJson.h>

View File

@@ -15,14 +15,13 @@ String ArduinoJsonJWT::getSecret() {
/* /*
* ESP32 uses mbedtls, ESP2866 uses bearssl. * ESP32 uses mbedtls, ESP2866 uses bearssl.
* *
* Both come with decent HMAC implmentations supporting sha256, as well as others. * Both come with decent HMAC implementations supporting sha256, as well as others.
* *
* No need to pull in additional crypto libraries - lets use what we already have. * No need to pull in additional crypto libraries - lets use what we already have.
*/ */
String ArduinoJsonJWT::sign(String & payload) { String ArduinoJsonJWT::sign(String & payload) {
unsigned char hmacResult[32]; unsigned char hmacResult[32];
{ {
#ifdef ESP32
mbedtls_md_context_t ctx; mbedtls_md_context_t ctx;
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256; mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
mbedtls_md_init(&ctx); mbedtls_md_init(&ctx);
@@ -31,14 +30,6 @@ String ArduinoJsonJWT::sign(String & payload) {
mbedtls_md_hmac_update(&ctx, (unsigned char *)payload.c_str(), payload.length()); mbedtls_md_hmac_update(&ctx, (unsigned char *)payload.c_str(), payload.length());
mbedtls_md_hmac_finish(&ctx, hmacResult); mbedtls_md_hmac_finish(&ctx, hmacResult);
mbedtls_md_free(&ctx); mbedtls_md_free(&ctx);
#elif defined(ESP8266)
br_hmac_key_context keyCtx;
br_hmac_key_init(&keyCtx, &br_sha256_vtable, _secret.c_str(), _secret.length());
br_hmac_context hmacCtx;
br_hmac_init(&hmacCtx, &keyCtx, 0);
br_hmac_update(&hmacCtx, payload.c_str(), payload.length());
br_hmac_out(&hmacCtx, hmacResult);
#endif
} }
return encode((char *)hmacResult, 32); return encode((char *)hmacResult, 32);
} }
@@ -94,13 +85,8 @@ void ArduinoJsonJWT::parseJWT(String jwt, JsonDocument & jsonDocument) {
String ArduinoJsonJWT::encode(const char * cstr, int inputLen) { String ArduinoJsonJWT::encode(const char * cstr, int inputLen) {
// prepare encoder // prepare encoder
base64_encodestate _state; base64_encodestate _state;
#ifdef ESP32
base64_init_encodestate(&_state); base64_init_encodestate(&_state);
size_t encodedLength = base64_encode_expected_len(inputLen) + 1; size_t encodedLength = base64_encode_expected_len(inputLen) + 1;
#elif defined(ESP8266)
base64_init_encodestate_nonewlines(&_state);
size_t encodedLength = base64_encode_expected_len_nonewlines(inputLen) + 1;
#endif
// prepare buffer of correct length, returning an empty string on failure // prepare buffer of correct length, returning an empty string on failure
char * buffer = (char *)malloc(encodedLength * sizeof(char)); char * buffer = (char *)malloc(encodedLength * sizeof(char));
if (buffer == nullptr) { if (buffer == nullptr) {

View File

@@ -5,12 +5,7 @@
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <libb64/cdecode.h> #include <libb64/cdecode.h>
#include <libb64/cencode.h> #include <libb64/cencode.h>
#ifdef ESP32
#include <mbedtls/md.h> #include <mbedtls/md.h>
#elif defined(ESP8266)
#include <bearssl/bearssl_hmac.h>
#endif
class ArduinoJsonJWT { class ArduinoJsonJWT {
private: private:

View File

@@ -1,11 +1,13 @@
#include <AuthenticationService.h> #include <AuthenticationService.h>
using namespace std::placeholders; // for `_1` etc
#if FT_ENABLED(FT_SECURITY) #if FT_ENABLED(FT_SECURITY)
AuthenticationService::AuthenticationService(AsyncWebServer * server, SecurityManager * securityManager) AuthenticationService::AuthenticationService(AsyncWebServer * server, SecurityManager * securityManager)
: _securityManager(securityManager) : _securityManager(securityManager)
, _signInHandler(SIGN_IN_PATH, std::bind(&AuthenticationService::signIn, this, std::placeholders::_1, std::placeholders::_2)) { , _signInHandler(SIGN_IN_PATH, std::bind(&AuthenticationService::signIn, this, _1, _2)) {
server->on(VERIFY_AUTHORIZATION_PATH, HTTP_GET, std::bind(&AuthenticationService::verifyAuthorization, this, std::placeholders::_1)); server->on(VERIFY_AUTHORIZATION_PATH, HTTP_GET, std::bind(&AuthenticationService::verifyAuthorization, this, _1));
_signInHandler.setMethod(HTTP_POST); _signInHandler.setMethod(HTTP_POST);
_signInHandler.setMaxContentLength(MAX_AUTHENTICATION_SIZE); _signInHandler.setMaxContentLength(MAX_AUTHENTICATION_SIZE);
server->addHandler(&_signInHandler); server->addHandler(&_signInHandler);

View File

@@ -6,11 +6,7 @@
class ESPUtils { class ESPUtils {
public: public:
static String defaultDeviceValue(String prefix = "") { static String defaultDeviceValue(String prefix = "") {
#ifdef ESP32
return prefix + String((uint32_t)ESP.getEfuseMac(), HEX); return prefix + String((uint32_t)ESP.getEfuseMac(), HEX);
#elif defined(ESP8266)
return prefix + String(ESP.getChipId(), HEX);
#endif
} }
}; };

View File

@@ -30,7 +30,7 @@ class FSPersistence {
// debug added by Proddy // debug added by Proddy
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
Serial.printf("Read File: %s: ", _filePath); Serial.printf("Reading file: %s: ", _filePath);
serializeJson(jsonDocument, Serial); serializeJson(jsonDocument, Serial);
Serial.println(); Serial.println();
#endif #endif
@@ -63,7 +63,7 @@ class FSPersistence {
// debug added by Proddy // debug added by Proddy
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
Serial.printf("Write File: %s: ", _filePath); Serial.printf("Writing to file: %s: ", _filePath);
serializeJson(jsonDocument, Serial); serializeJson(jsonDocument, Serial);
Serial.println(); Serial.println();
#endif #endif

View File

@@ -16,7 +16,6 @@ void FactoryResetService::handleRequest(AsyncWebServerRequest * request) {
* Delete function assumes that all files are stored flat, within the config directory. * Delete function assumes that all files are stored flat, within the config directory.
*/ */
void FactoryResetService::factoryReset() { void FactoryResetService::factoryReset() {
#ifdef ESP32
/* /*
* Based on LITTLEFS. Modified by proddy * Based on LITTLEFS. Modified by proddy
* Could be replaced with fs.rmdir(FS_CONFIG_DIRECTORY) in IDF 4.2 * Could be replaced with fs.rmdir(FS_CONFIG_DIRECTORY) in IDF 4.2
@@ -28,14 +27,5 @@ void FactoryResetService::factoryReset() {
file.close(); file.close();
fs->remove(pathStr); fs->remove(pathStr);
} }
#elif defined(ESP8266)
Dir configDirectory = fs->openDir(FS_CONFIG_DIRECTORY);
while (configDirectory.next()) {
String path = FS_CONFIG_DIRECTORY;
path.concat("/");
path.concat(configDirectory.fileName());
fs->remove(path);
}
#endif
RestartService::restartNow(); RestartService::restartNow();
} }

View File

@@ -1,14 +1,8 @@
#ifndef FactoryResetService_h #ifndef FactoryResetService_h
#define FactoryResetService_h #define FactoryResetService_h
#ifdef ESP32
#include <WiFi.h> #include <WiFi.h>
#include <AsyncTCP.h> #include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <SecurityManager.h> #include <SecurityManager.h>
#include <RestartService.h> #include <RestartService.h>

View File

@@ -1,11 +1,13 @@
#ifndef Features_h #ifndef Features_h
#define Features_h #define Features_h
// modified by Proddy
#define FT_ENABLED(feature) feature #define FT_ENABLED(feature) feature
// project feature off by default // project feature on by default
#ifndef FT_PROJECT #ifndef FT_PROJECT
#define FT_PROJECT 0 #define FT_PROJECT 1
#endif #endif
// security feature on by default // security feature on by default
@@ -23,14 +25,14 @@
#define FT_NTP 1 #define FT_NTP 1
#endif #endif
// mqtt feature on by default // ota feature on by default
#ifndef FT_OTA #ifndef FT_OTA
#define FT_OTA 1 #define FT_OTA 1
#endif #endif
// upload firmware feature off by default // upload firmware feature on by default
#ifndef FT_UPLOAD_FIRMWARE #ifndef FT_UPLOAD_FIRMWARE
#define FT_UPLOAD_FIRMWARE 0 #define FT_UPLOAD_FIRMWARE 1
#endif #endif

View File

@@ -1,7 +1,9 @@
#include <FeaturesService.h> #include <FeaturesService.h>
using namespace std::placeholders; // for `_1` etc
FeaturesService::FeaturesService(AsyncWebServer * server) { FeaturesService::FeaturesService(AsyncWebServer * server) {
server->on(FEATURES_SERVICE_PATH, HTTP_GET, std::bind(&FeaturesService::features, this, std::placeholders::_1)); server->on(FEATURES_SERVICE_PATH, HTTP_GET, std::bind(&FeaturesService::features, this, _1));
} }
void FeaturesService::features(AsyncWebServerRequest * request) { void FeaturesService::features(AsyncWebServerRequest * request) {

View File

@@ -3,14 +3,8 @@
#include <Features.h> #include <Features.h>
#ifdef ESP32
#include <WiFi.h> #include <WiFi.h>
#include <AsyncTCP.h> #include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <AsyncJson.h> #include <AsyncJson.h>
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>

View File

@@ -11,6 +11,8 @@
#define HTTP_ENDPOINT_ORIGIN_ID "http" #define HTTP_ENDPOINT_ORIGIN_ID "http"
using namespace std::placeholders; // for `_1` etc
template <class T> template <class T>
class HttpGetEndpoint { class HttpGetEndpoint {
public: public:
@@ -24,20 +26,14 @@ class HttpGetEndpoint {
: _stateReader(stateReader) : _stateReader(stateReader)
, _statefulService(statefulService) , _statefulService(statefulService)
, _bufferSize(bufferSize) { , _bufferSize(bufferSize) {
server->on(servicePath.c_str(), server->on(servicePath.c_str(), HTTP_GET, securityManager->wrapRequest(std::bind(&HttpGetEndpoint::fetchSettings, this, _1), authenticationPredicate));
HTTP_GET,
securityManager->wrapRequest(std::bind(&HttpGetEndpoint::fetchSettings, this, std::placeholders::_1), authenticationPredicate));
} }
HttpGetEndpoint(JsonStateReader<T> stateReader, HttpGetEndpoint(JsonStateReader<T> stateReader, StatefulService<T> * statefulService, AsyncWebServer * server, const String & servicePath, size_t bufferSize = DEFAULT_BUFFER_SIZE)
StatefulService<T> * statefulService,
AsyncWebServer * server,
const String & servicePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE)
: _stateReader(stateReader) : _stateReader(stateReader)
, _statefulService(statefulService) , _statefulService(statefulService)
, _bufferSize(bufferSize) { , _bufferSize(bufferSize) {
server->on(servicePath.c_str(), HTTP_GET, std::bind(&HttpGetEndpoint::fetchSettings, this, std::placeholders::_1)); server->on(servicePath.c_str(), HTTP_GET, std::bind(&HttpGetEndpoint::fetchSettings, this, _1));
} }
protected: protected:
@@ -69,25 +65,17 @@ class HttpPostEndpoint {
: _stateReader(stateReader) : _stateReader(stateReader)
, _stateUpdater(stateUpdater) , _stateUpdater(stateUpdater)
, _statefulService(statefulService) , _statefulService(statefulService)
, _updateHandler(servicePath, , _updateHandler(servicePath, securityManager->wrapCallback(std::bind(&HttpPostEndpoint::updateSettings, this, _1, _2), authenticationPredicate), bufferSize)
securityManager->wrapCallback(std::bind(&HttpPostEndpoint::updateSettings, this, std::placeholders::_1, std::placeholders::_2),
authenticationPredicate),
bufferSize)
, _bufferSize(bufferSize) { , _bufferSize(bufferSize) {
_updateHandler.setMethod(HTTP_POST); _updateHandler.setMethod(HTTP_POST);
server->addHandler(&_updateHandler); server->addHandler(&_updateHandler);
} }
HttpPostEndpoint(JsonStateReader<T> stateReader, HttpPostEndpoint(JsonStateReader<T> stateReader, JsonStateUpdater<T> stateUpdater, StatefulService<T> * statefulService, AsyncWebServer * server, const String & servicePath, size_t bufferSize = DEFAULT_BUFFER_SIZE)
JsonStateUpdater<T> stateUpdater,
StatefulService<T> * statefulService,
AsyncWebServer * server,
const String & servicePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE)
: _stateReader(stateReader) : _stateReader(stateReader)
, _stateUpdater(stateUpdater) , _stateUpdater(stateUpdater)
, _statefulService(statefulService) , _statefulService(statefulService)
, _updateHandler(servicePath, std::bind(&HttpPostEndpoint::updateSettings, this, std::placeholders::_1, std::placeholders::_2), bufferSize) , _updateHandler(servicePath, std::bind(&HttpPostEndpoint::updateSettings, this, _1, _2), bufferSize)
, _bufferSize(bufferSize) { , _bufferSize(bufferSize) {
_updateHandler.setMethod(HTTP_POST); _updateHandler.setMethod(HTTP_POST);
server->addHandler(&_updateHandler); server->addHandler(&_updateHandler);
@@ -137,12 +125,7 @@ class HttpEndpoint : public HttpGetEndpoint<T>, public HttpPostEndpoint<T> {
, HttpPostEndpoint<T>(stateReader, stateUpdater, statefulService, server, servicePath, securityManager, authenticationPredicate, bufferSize) { , HttpPostEndpoint<T>(stateReader, stateUpdater, statefulService, server, servicePath, securityManager, authenticationPredicate, bufferSize) {
} }
HttpEndpoint(JsonStateReader<T> stateReader, HttpEndpoint(JsonStateReader<T> stateReader, JsonStateUpdater<T> stateUpdater, StatefulService<T> * statefulService, AsyncWebServer * server, const String & servicePath, size_t bufferSize = DEFAULT_BUFFER_SIZE)
JsonStateUpdater<T> stateUpdater,
StatefulService<T> * statefulService,
AsyncWebServer * server,
const String & servicePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE)
: HttpGetEndpoint<T>(stateReader, statefulService, server, servicePath, bufferSize) : HttpGetEndpoint<T>(stateReader, statefulService, server, servicePath, bufferSize)
, HttpPostEndpoint<T>(stateReader, stateUpdater, statefulService, server, servicePath, bufferSize) { , HttpPostEndpoint<T>(stateReader, stateUpdater, statefulService, server, servicePath, bufferSize) {
} }

View File

@@ -4,6 +4,8 @@
#include <StatefulService.h> #include <StatefulService.h>
#include <AsyncMqttClient.h> #include <AsyncMqttClient.h>
using namespace std::placeholders; // for `_1` etc
#define MQTT_ORIGIN_ID "mqtt" #define MQTT_ORIGIN_ID "mqtt"
template <class T> template <class T>
@@ -31,11 +33,7 @@ class MqttConnector {
template <class T> template <class T>
class MqttPub : virtual public MqttConnector<T> { class MqttPub : virtual public MqttConnector<T> {
public: public:
MqttPub(JsonStateReader<T> stateReader, MqttPub(JsonStateReader<T> stateReader, StatefulService<T> * statefulService, AsyncMqttClient * mqttClient, const String & pubTopic = "", size_t bufferSize = DEFAULT_BUFFER_SIZE)
StatefulService<T> * statefulService,
AsyncMqttClient * mqttClient,
const String & pubTopic = "",
size_t bufferSize = DEFAULT_BUFFER_SIZE)
: MqttConnector<T>(statefulService, mqttClient, bufferSize) : MqttConnector<T>(statefulService, mqttClient, bufferSize)
, _stateReader(stateReader) , _stateReader(stateReader)
, _pubTopic(pubTopic) { , _pubTopic(pubTopic) {
@@ -76,22 +74,11 @@ class MqttPub : virtual public MqttConnector<T> {
template <class T> template <class T>
class MqttSub : virtual public MqttConnector<T> { class MqttSub : virtual public MqttConnector<T> {
public: public:
MqttSub(JsonStateUpdater<T> stateUpdater, MqttSub(JsonStateUpdater<T> stateUpdater, StatefulService<T> * statefulService, AsyncMqttClient * mqttClient, const String & subTopic = "", size_t bufferSize = DEFAULT_BUFFER_SIZE)
StatefulService<T> * statefulService,
AsyncMqttClient * mqttClient,
const String & subTopic = "",
size_t bufferSize = DEFAULT_BUFFER_SIZE)
: MqttConnector<T>(statefulService, mqttClient, bufferSize) : MqttConnector<T>(statefulService, mqttClient, bufferSize)
, _stateUpdater(stateUpdater) , _stateUpdater(stateUpdater)
, _subTopic(subTopic) { , _subTopic(subTopic) {
MqttConnector<T>::_mqttClient->onMessage(std::bind(&MqttSub::onMqttMessage, MqttConnector<T>::_mqttClient->onMessage(std::bind(&MqttSub::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
this,
std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3,
std::placeholders::_4,
std::placeholders::_5,
std::placeholders::_6));
} }
void setSubTopic(const String & subTopic) { void setSubTopic(const String & subTopic) {

View File

@@ -2,6 +2,8 @@
#include "../../src/emsesp_stub.hpp" // proddy added #include "../../src/emsesp_stub.hpp" // proddy added
using namespace std::placeholders; // for `_1` etc
/** /**
* Retains a copy of the cstr provided in the pointer provided using dynamic allocation. * Retains a copy of the cstr provided in the pointer provided using dynamic allocation.
* *
@@ -33,10 +35,9 @@ MqttSettingsService::MqttSettingsService(AsyncWebServer * server, FS * fs, Secur
, _disconnectedAt(0) , _disconnectedAt(0)
, _disconnectReason(AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) , _disconnectReason(AsyncMqttClientDisconnectReason::TCP_DISCONNECTED)
, _mqttClient() { , _mqttClient() {
WiFi.onEvent(std::bind(&MqttSettingsService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2), WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED); WiFi.onEvent(std::bind(&MqttSettingsService::WiFiEvent, this, _1, _2));
WiFi.onEvent(std::bind(&MqttSettingsService::onStationModeGotIP, this, std::placeholders::_1, std::placeholders::_2), WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP); _mqttClient.onConnect(std::bind(&MqttSettingsService::onMqttConnect, this, _1));
_mqttClient.onConnect(std::bind(&MqttSettingsService::onMqttConnect, this, std::placeholders::_1)); _mqttClient.onDisconnect(std::bind(&MqttSettingsService::onMqttDisconnect, this, _1));
_mqttClient.onDisconnect(std::bind(&MqttSettingsService::onMqttDisconnect, this, std::placeholders::_1));
addUpdateHandler([&](const String & originId) { onConfigUpdated(); }, false); addUpdateHandler([&](const String & originId) { onConfigUpdated(); }, false);
} }
@@ -79,17 +80,11 @@ AsyncMqttClient * MqttSettingsService::getMqttClient() {
} }
void MqttSettingsService::onMqttConnect(bool sessionPresent) { void MqttSettingsService::onMqttConnect(bool sessionPresent) {
// Serial.print(F("Connected to MQTT, ")); // emsesp::EMSESP::logger().info(F("Connected to MQTT, %s"), (sessionPresent) ? F("with persistent session") : F("without persistent session"));
// if (sessionPresent) {
// Serial.println(F("with persistent session"));
// } else {
// Serial.println(F("without persistent session"));
// }
} }
void MqttSettingsService::onMqttDisconnect(AsyncMqttClientDisconnectReason reason) { void MqttSettingsService::onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
// Serial.print(F("Disconnected from MQTT reason: ")); // emsesp::EMSESP::logger().info(F("Disconnected from MQTT reason: %s"), (uint8_t)reason);
// Serial.println((uint8_t)reason);
_disconnectReason = reason; _disconnectReason = reason;
_disconnectedAt = uuid::get_uptime(); _disconnectedAt = uuid::get_uptime();
} }
@@ -99,47 +94,38 @@ void MqttSettingsService::onConfigUpdated() {
_disconnectedAt = 0; _disconnectedAt = 0;
// added by proddy // added by proddy
// reload EMS-ESP MQTT settings emsesp::EMSESP::mqtt_.start(); // reload EMS-ESP MQTT settings
emsesp::EMSESP::mqtt_.start();
} }
#ifdef ESP32 void MqttSettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
void MqttSettingsService::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) { switch (event) {
case SYSTEM_EVENT_STA_GOT_IP:
case SYSTEM_EVENT_ETH_GOT_IP:
if (_state.enabled) { if (_state.enabled) {
// Serial.println(F("WiFi connection dropped, starting MQTT client.")); // emsesp::EMSESP::logger().info(F("Network connection found, starting MQTT client"));
onConfigUpdated(); onConfigUpdated();
} }
} break;
void MqttSettingsService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) { case SYSTEM_EVENT_STA_DISCONNECTED:
case SYSTEM_EVENT_ETH_DISCONNECTED:
if (_state.enabled) { if (_state.enabled) {
// Serial.println(F("WiFi connection dropped, stopping MQTT client.")); // emsesp::EMSESP::logger().info(F("Network connection dropped, stopping MQTT client"));
onConfigUpdated(); onConfigUpdated();
} }
} break;
#elif defined(ESP8266)
void MqttSettingsService::onStationModeGotIP(const WiFiEventStationModeGotIP & event) {
if (_state.enabled) {
// Serial.println(F("WiFi connection dropped, starting MQTT client."));
onConfigUpdated();
}
}
void MqttSettingsService::onStationModeDisconnected(const WiFiEventStationModeDisconnected & event) { default:
if (_state.enabled) { break;
// Serial.println(F("WiFi connection dropped, stopping MQTT client."));
onConfigUpdated();
} }
} }
#endif
void MqttSettingsService::configureMqtt() { void MqttSettingsService::configureMqtt() {
// disconnect if currently connected // disconnect if currently connected
_mqttClient.disconnect(); _mqttClient.disconnect();
// only connect if WiFi is connected and MQTT is enabled // only connect if WiFi is connected and MQTT is enabled
if (_state.enabled && WiFi.isConnected()) { if (_state.enabled && emsesp::EMSESP::system_.network_connected()) {
// Serial.println(F("Connecting to MQTT..."));
_mqttClient.setServer(retainCstr(_state.host.c_str(), &_retainedHost), _state.port); _mqttClient.setServer(retainCstr(_state.host.c_str(), &_retainedHost), _state.port);
if (_state.username.length() > 0) { if (_state.username.length() > 0) {
_mqttClient.setCredentials(retainCstr(_state.username.c_str(), &_retainedUsername), retainCstr(_state.password.length() > 0 ? _state.password.c_str() : nullptr, &_retainedPassword)); _mqttClient.setCredentials(retainCstr(_state.username.c_str(), &_retainedUsername), retainCstr(_state.password.length() > 0 ? _state.password.c_str() : nullptr, &_retainedPassword));
@@ -153,7 +139,7 @@ void MqttSettingsService::configureMqtt() {
_mqttClient.connect(); _mqttClient.connect();
} }
emsesp::EMSESP::dallassensor_.reload(); emsesp::EMSESP::dallassensor_.reload(); // added by Proddy for EMS-ESP
} }
void MqttSettings::read(MqttSettings & settings, JsonObject & root) { void MqttSettings::read(MqttSettings & settings, JsonObject & root) {
@@ -182,6 +168,7 @@ void MqttSettings::read(MqttSettings & settings, JsonObject & root) {
root["ha_climate_format"] = settings.ha_climate_format; root["ha_climate_format"] = settings.ha_climate_format;
root["ha_enabled"] = settings.ha_enabled; root["ha_enabled"] = settings.ha_enabled;
root["nested_format"] = settings.nested_format; root["nested_format"] = settings.nested_format;
root["subscribe_format"] = settings.subscribe_format;
} }
StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & settings) { StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & settings) {
@@ -213,6 +200,7 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting
newSettings.ha_climate_format = root["ha_climate_format"] | EMSESP_DEFAULT_HA_CLIMATE_FORMAT; newSettings.ha_climate_format = root["ha_climate_format"] | EMSESP_DEFAULT_HA_CLIMATE_FORMAT;
newSettings.ha_enabled = root["ha_enabled"] | EMSESP_DEFAULT_HA_ENABLED; newSettings.ha_enabled = root["ha_enabled"] | EMSESP_DEFAULT_HA_ENABLED;
newSettings.nested_format = root["nested_format"] | EMSESP_DEFAULT_NESTED_FORMAT; newSettings.nested_format = root["nested_format"] | EMSESP_DEFAULT_NESTED_FORMAT;
newSettings.subscribe_format = root["subscribe_format"] | EMSESP_DEFAULT_SUBSCRIBE_FORMAT;
if (newSettings.mqtt_qos != settings.mqtt_qos) { if (newSettings.mqtt_qos != settings.mqtt_qos) {
emsesp::EMSESP::mqtt_.set_qos(newSettings.mqtt_qos); emsesp::EMSESP::mqtt_.set_qos(newSettings.mqtt_qos);
@@ -228,6 +216,10 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting
changed = true; changed = true;
} }
if (newSettings.subscribe_format != settings.subscribe_format) {
changed = true;
}
if (newSettings.ha_climate_format != settings.ha_climate_format) { if (newSettings.ha_climate_format != settings.ha_climate_format) {
emsesp::EMSESP::mqtt_.ha_climate_format(newSettings.ha_climate_format); emsesp::EMSESP::mqtt_.ha_climate_format(newSettings.ha_climate_format);
changed = true; changed = true;

View File

@@ -41,11 +41,7 @@
#ifndef FACTORY_MQTT_CLIENT_ID #ifndef FACTORY_MQTT_CLIENT_ID
#define FACTORY_MQTT_CLIENT_ID generateClientId() #define FACTORY_MQTT_CLIENT_ID generateClientId()
static String generateClientId() { static String generateClientId() {
#ifdef ESP32
return ESPUtils::defaultDeviceValue("esp32-"); return ESPUtils::defaultDeviceValue("esp32-");
#elif defined(ESP8266)
return ESPUtils::defaultDeviceValue("esp8266-");
#endif
} }
#endif #endif
@@ -69,6 +65,9 @@ static String generateClientId() {
#define EMSESP_DEFAULT_HA_ENABLED false #define EMSESP_DEFAULT_HA_ENABLED false
#define EMSESP_DEFAULT_PUBLISH_TIME 10 #define EMSESP_DEFAULT_PUBLISH_TIME 10
#define EMSESP_DEFAULT_NESTED_FORMAT true #define EMSESP_DEFAULT_NESTED_FORMAT true
#define EMSESP_DEFAULT_SUBSCRIBE_FORMAT 0
#define EMSESP_DEFAULT_BOARD_PROFILE "S32"
class MqttSettings { class MqttSettings {
public: public:
@@ -76,7 +75,6 @@ class MqttSettings {
bool enabled; bool enabled;
String host; String host;
uint16_t port; uint16_t port;
String base;
// username and password // username and password
String username; String username;
@@ -91,6 +89,7 @@ class MqttSettings {
uint16_t maxTopicLength; uint16_t maxTopicLength;
// proddy EMS-ESP specific // proddy EMS-ESP specific
String base;
uint16_t publish_time_boiler; uint16_t publish_time_boiler;
uint16_t publish_time_thermostat; uint16_t publish_time_thermostat;
uint16_t publish_time_solar; uint16_t publish_time_solar;
@@ -104,6 +103,7 @@ class MqttSettings {
uint8_t ha_climate_format; uint8_t ha_climate_format;
bool ha_enabled; bool ha_enabled;
bool nested_format; bool nested_format;
uint8_t subscribe_format;
static void read(MqttSettings & settings, JsonObject & root); static void read(MqttSettings & settings, JsonObject & root);
static StateUpdateResult update(JsonObject & root, MqttSettings & settings); static StateUpdateResult update(JsonObject & root, MqttSettings & settings);
@@ -146,8 +146,7 @@ class MqttSettingsService : public StatefulService<MqttSettings> {
// the MQTT client instance // the MQTT client instance
AsyncMqttClient _mqttClient; AsyncMqttClient _mqttClient;
void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info); void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info);
void onMqttConnect(bool sessionPresent); void onMqttConnect(bool sessionPresent);
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason); void onMqttDisconnect(AsyncMqttClientDisconnectReason reason);
void configureMqtt(); void configureMqtt();

View File

@@ -2,11 +2,13 @@
#include "../../src/emsesp_stub.hpp" // proddy added #include "../../src/emsesp_stub.hpp" // proddy added
using namespace std::placeholders; // for `_1` etc
MqttStatus::MqttStatus(AsyncWebServer * server, MqttSettingsService * mqttSettingsService, SecurityManager * securityManager) MqttStatus::MqttStatus(AsyncWebServer * server, MqttSettingsService * mqttSettingsService, SecurityManager * securityManager)
: _mqttSettingsService(mqttSettingsService) { : _mqttSettingsService(mqttSettingsService) {
server->on(MQTT_STATUS_SERVICE_PATH, server->on(MQTT_STATUS_SERVICE_PATH,
HTTP_GET, HTTP_GET,
securityManager->wrapRequest(std::bind(&MqttStatus::mqttStatus, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED)); securityManager->wrapRequest(std::bind(&MqttStatus::mqttStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
} }
void MqttStatus::mqttStatus(AsyncWebServerRequest * request) { void MqttStatus::mqttStatus(AsyncWebServerRequest * request) {

View File

@@ -1,14 +1,8 @@
#ifndef MqttStatus_h #ifndef MqttStatus_h
#define MqttStatus_h #define MqttStatus_h
#ifdef ESP32
#include <WiFi.h> #include <WiFi.h>
#include <AsyncTCP.h> #include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <MqttSettingsService.h> #include <MqttSettingsService.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <AsyncJson.h> #include <AsyncJson.h>

View File

@@ -1,14 +1,18 @@
#include <NTPSettingsService.h> #include <NTPSettingsService.h>
#include "../../src/emsesp_stub.hpp" // proddy added
using namespace std::placeholders; // for `_1` etc
NTPSettingsService::NTPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) NTPSettingsService::NTPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(NTPSettings::read, NTPSettings::update, this, server, NTP_SETTINGS_SERVICE_PATH, securityManager) : _httpEndpoint(NTPSettings::read, NTPSettings::update, this, server, NTP_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(NTPSettings::read, NTPSettings::update, this, fs, NTP_SETTINGS_FILE) , _fsPersistence(NTPSettings::read, NTPSettings::update, this, fs, NTP_SETTINGS_FILE)
, _timeHandler(TIME_PATH, securityManager->wrapCallback(std::bind(&NTPSettingsService::configureTime, this, std::placeholders::_1, std::placeholders::_2), AuthenticationPredicates::IS_ADMIN)) { , _timeHandler(TIME_PATH, securityManager->wrapCallback(std::bind(&NTPSettingsService::configureTime, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) {
_timeHandler.setMethod(HTTP_POST); _timeHandler.setMethod(HTTP_POST);
_timeHandler.setMaxContentLength(MAX_TIME_SIZE); _timeHandler.setMaxContentLength(MAX_TIME_SIZE);
server->addHandler(&_timeHandler); server->addHandler(&_timeHandler);
WiFi.onEvent(std::bind(&NTPSettingsService::WiFiEvent, this, std::placeholders::_1)); WiFi.onEvent(std::bind(&NTPSettingsService::WiFiEvent, this, _1));
addUpdateHandler([&](const String & originId) { configureNTP(); }, false); addUpdateHandler([&](const String & originId) { configureNTP(); }, false);
} }
@@ -22,14 +26,15 @@ void NTPSettingsService::begin() {
void NTPSettingsService::WiFiEvent(WiFiEvent_t event) { void NTPSettingsService::WiFiEvent(WiFiEvent_t event) {
switch (event) { switch (event) {
case SYSTEM_EVENT_STA_DISCONNECTED: case SYSTEM_EVENT_STA_DISCONNECTED:
// Serial.println(F("WiFi connection dropped, stopping NTP.")); case SYSTEM_EVENT_ETH_DISCONNECTED:
emsesp::EMSESP::logger().info(F("WiFi connection dropped, stopping NTP"));
connected_ = false; connected_ = false;
configureNTP(); configureNTP();
break; break;
case SYSTEM_EVENT_STA_GOT_IP: case SYSTEM_EVENT_STA_GOT_IP:
case SYSTEM_EVENT_ETH_GOT_IP: case SYSTEM_EVENT_ETH_GOT_IP:
// Serial.println(F("Got IP address, starting NTP Synchronization")); // emsesp::EMSESP::logger().info(F("Got IP address, starting NTP synchronization"));
connected_ = true; connected_ = true;
configureNTP(); configureNTP();
break; break;
@@ -41,7 +46,7 @@ void NTPSettingsService::WiFiEvent(WiFiEvent_t event) {
void NTPSettingsService::configureNTP() { void NTPSettingsService::configureNTP() {
if (connected_ && _state.enabled) { if (connected_ && _state.enabled) {
// Serial.println(F("Starting NTP...")); emsesp::EMSESP::logger().info(F("Starting NTP"));
configTzTime(_state.tzFormat.c_str(), _state.server.c_str()); configTzTime(_state.tzFormat.c_str(), _state.server.c_str());
} else { } else {
setenv("TZ", _state.tzFormat.c_str(), 1); setenv("TZ", _state.tzFormat.c_str(), 1);

View File

@@ -1,7 +1,9 @@
#include <NTPStatus.h> #include <NTPStatus.h>
using namespace std::placeholders; // for `_1` etc
NTPStatus::NTPStatus(AsyncWebServer * server, SecurityManager * securityManager) { NTPStatus::NTPStatus(AsyncWebServer * server, SecurityManager * securityManager) {
server->on(NTP_STATUS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&NTPStatus::ntpStatus, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED)); server->on(NTP_STATUS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&NTPStatus::ntpStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
} }
/* /*

View File

@@ -2,15 +2,10 @@
#define NTPStatus_h #define NTPStatus_h
#include <time.h> #include <time.h>
#ifdef ESP32
#include <WiFi.h> #include <WiFi.h>
#include <AsyncTCP.h> #include <AsyncTCP.h>
#include <lwip/apps/sntp.h> #include <lwip/apps/sntp.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <sntp.h>
#endif
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <AsyncJson.h> #include <AsyncJson.h>

View File

@@ -1,5 +1,7 @@
#include <NetworkSettingsService.h> #include <NetworkSettingsService.h>
using namespace std::placeholders; // for `_1` etc
NetworkSettingsService::NetworkSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) NetworkSettingsService::NetworkSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(NetworkSettings::read, NetworkSettings::update, this, server, NETWORK_SETTINGS_SERVICE_PATH, securityManager) : _httpEndpoint(NetworkSettings::read, NetworkSettings::update, this, server, NETWORK_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(NetworkSettings::read, NetworkSettings::update, this, fs, NETWORK_SETTINGS_FILE) , _fsPersistence(NetworkSettings::read, NetworkSettings::update, this, fs, NETWORK_SETTINGS_FILE)
@@ -17,7 +19,7 @@ NetworkSettingsService::NetworkSettingsService(AsyncWebServer * server, FS * fs,
WiFi.mode(WIFI_MODE_MAX); WiFi.mode(WIFI_MODE_MAX);
WiFi.mode(WIFI_MODE_NULL); WiFi.mode(WIFI_MODE_NULL);
WiFi.onEvent(std::bind(&NetworkSettingsService::WiFiEvent, this, std::placeholders::_1)); WiFi.onEvent(std::bind(&NetworkSettingsService::WiFiEvent, this, _1));
addUpdateHandler([&](const String & originId) { reconfigureWiFiConnection(); }, false); addUpdateHandler([&](const String & originId) { reconfigureWiFiConnection(); }, false);
} }

View File

@@ -31,7 +31,6 @@ class NetworkSettings {
String password; String password;
String hostname; String hostname;
bool staticIPConfig; bool staticIPConfig;
uint8_t ethernet_profile;
// optional configuration for static IP address // optional configuration for static IP address
IPAddress localIP; IPAddress localIP;
@@ -46,7 +45,6 @@ class NetworkSettings {
root["password"] = settings.password; root["password"] = settings.password;
root["hostname"] = settings.hostname; root["hostname"] = settings.hostname;
root["static_ip_config"] = settings.staticIPConfig; root["static_ip_config"] = settings.staticIPConfig;
root["ethernet_profile"] = settings.ethernet_profile;
// extended settings // extended settings
JsonUtils::writeIP(root, "local_ip", settings.localIP); JsonUtils::writeIP(root, "local_ip", settings.localIP);
@@ -61,7 +59,6 @@ class NetworkSettings {
settings.password = root["password"] | FACTORY_WIFI_PASSWORD; settings.password = root["password"] | FACTORY_WIFI_PASSWORD;
settings.hostname = root["hostname"] | FACTORY_WIFI_HOSTNAME; settings.hostname = root["hostname"] | FACTORY_WIFI_HOSTNAME;
settings.staticIPConfig = root["static_ip_config"] | false; settings.staticIPConfig = root["static_ip_config"] | false;
settings.ethernet_profile = root["ethernet_profile"] | 0; // no ethernet
// extended settings // extended settings
JsonUtils::readIP(root, "local_ip", settings.localIP); JsonUtils::readIP(root, "local_ip", settings.localIP);

View File

@@ -2,8 +2,10 @@
#include "../../src/emsesp_stub.hpp" // proddy added #include "../../src/emsesp_stub.hpp" // proddy added
using namespace std::placeholders; // for `_1` etc
NetworkStatus::NetworkStatus(AsyncWebServer * server, SecurityManager * securityManager) { NetworkStatus::NetworkStatus(AsyncWebServer * server, SecurityManager * securityManager) {
server->on(NETWORK_STATUS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&NetworkStatus::networkStatus, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED)); server->on(NETWORK_STATUS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&NetworkStatus::networkStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
} }
void NetworkStatus::networkStatus(AsyncWebServerRequest * request) { void NetworkStatus::networkStatus(AsyncWebServerRequest * request) {

View File

@@ -2,15 +2,13 @@
#include "../../src/emsesp_stub.hpp" // proddy added #include "../../src/emsesp_stub.hpp" // proddy added
using namespace std::placeholders; // for `_1` etc
OTASettingsService::OTASettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) OTASettingsService::OTASettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(OTASettings::read, OTASettings::update, this, server, OTA_SETTINGS_SERVICE_PATH, securityManager) : _httpEndpoint(OTASettings::read, OTASettings::update, this, server, OTA_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(OTASettings::read, OTASettings::update, this, fs, OTA_SETTINGS_FILE) , _fsPersistence(OTASettings::read, OTASettings::update, this, fs, OTA_SETTINGS_FILE)
, _arduinoOTA(nullptr) { , _arduinoOTA(nullptr) {
#ifdef ESP32 WiFi.onEvent(std::bind(&OTASettingsService::WiFiEvent, this, _1, _2));
WiFi.onEvent(std::bind(&OTASettingsService::onStationModeGotIP, this, std::placeholders::_1, std::placeholders::_2), WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP);
#elif defined(ESP8266)
_onStationModeGotIPHandler = WiFi.onStationModeGotIP(std::bind(&OTASettingsService::onStationModeGotIP, this, std::placeholders::_1));
#endif
addUpdateHandler([&](const String & originId) { configureArduinoOTA(); }, false); addUpdateHandler([&](const String & originId) { configureArduinoOTA(); }, false);
} }
@@ -27,54 +25,51 @@ void OTASettingsService::loop() {
void OTASettingsService::configureArduinoOTA() { void OTASettingsService::configureArduinoOTA() {
if (_arduinoOTA) { if (_arduinoOTA) {
#ifdef ESP32
_arduinoOTA->end(); _arduinoOTA->end();
#endif
delete _arduinoOTA; delete _arduinoOTA;
_arduinoOTA = nullptr; _arduinoOTA = nullptr;
} }
if (_state.enabled) { if (_state.enabled) {
// Serial.println(F("Starting OTA Update Service..."));
_arduinoOTA = new ArduinoOTAClass; _arduinoOTA = new ArduinoOTAClass;
_arduinoOTA->setPort(_state.port); _arduinoOTA->setPort(_state.port);
_arduinoOTA->setPassword(_state.password.c_str()); _arduinoOTA->setPassword(_state.password.c_str());
_arduinoOTA->onStart([]() { _arduinoOTA->onStart([]() {
// Serial.println(F("Starting")); Serial.println(F("Starting"));
emsesp::EMSESP::system_.upload_status(true); emsesp::EMSESP::system_.upload_status(true);
}); });
_arduinoOTA->onEnd([]() { _arduinoOTA->onEnd([]() {
// Serial.println(F("\r\nEnd")); Serial.println(F("\r\nEnd"));
emsesp::EMSESP::system_.upload_status(false); emsesp::EMSESP::system_.upload_status(false);
}); });
// _arduinoOTA->onProgress([](unsigned int progress, unsigned int total) { _arduinoOTA->onProgress([](unsigned int progress, unsigned int total) { Serial.printf_P(PSTR("Progress: %u%%\r\n"), (progress / (total / 100))); });
// Serial.printf_P(PSTR("Progress: %u%%\r\n"), (progress / (total / 100))); _arduinoOTA->onError([](ota_error_t error) {
// }); Serial.printf("Error[%u]: ", error);
// _arduinoOTA->onError([](ota_error_t error) { if (error == OTA_AUTH_ERROR)
// Serial.printf("Error[%u]: ", error); Serial.println(F("Auth Failed"));
// if (error == OTA_AUTH_ERROR) else if (error == OTA_BEGIN_ERROR)
// Serial.println(F("Auth Failed")); Serial.println(F("Begin Failed"));
// else if (error == OTA_BEGIN_ERROR) else if (error == OTA_CONNECT_ERROR)
// Serial.println(F("Begin Failed")); Serial.println(F("Connect Failed"));
// else if (error == OTA_CONNECT_ERROR) else if (error == OTA_RECEIVE_ERROR)
// Serial.println(F("Connect Failed")); Serial.println(F("Receive Failed"));
// else if (error == OTA_RECEIVE_ERROR) else if (error == OTA_END_ERROR)
// Serial.println(F("Receive Failed")); Serial.println(F("End Failed"));
// else if (error == OTA_END_ERROR) });
// Serial.println(F("End Failed"));
// });
_arduinoOTA->begin(); _arduinoOTA->begin();
} }
} }
#ifdef ESP32 void OTASettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
void OTASettingsService::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) { switch (event) {
case SYSTEM_EVENT_STA_GOT_IP:
case SYSTEM_EVENT_ETH_GOT_IP:
configureArduinoOTA(); configureArduinoOTA();
break;
default:
break;
}
} }
#elif defined(ESP8266)
void OTASettingsService::onStationModeGotIP(const WiFiEventStationModeGotIP & event) {
configureArduinoOTA();
}
#endif

View File

@@ -4,11 +4,7 @@
#include <HttpEndpoint.h> #include <HttpEndpoint.h>
#include <FSPersistence.h> #include <FSPersistence.h>
#ifdef ESP32
#include <ESPmDNS.h> #include <ESPmDNS.h>
#elif defined(ESP8266)
#include <ESP8266mDNS.h>
#endif
#include <ArduinoOTA.h> #include <ArduinoOTA.h>
#include <WiFiUdp.h> #include <WiFiUdp.h>
@@ -61,12 +57,7 @@ class OTASettingsService : public StatefulService<OTASettings> {
ArduinoOTAClass * _arduinoOTA; ArduinoOTAClass * _arduinoOTA;
void configureArduinoOTA(); void configureArduinoOTA();
#ifdef ESP32 void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info);
#elif defined(ESP8266)
WiFiEventHandler _onStationModeGotIPHandler;
void onStationModeGotIP(const WiFiEventStationModeGotIP & event);
#endif
}; };
#endif // end OTASettingsService_h #endif // end OTASettingsService_h

View File

@@ -1,9 +1,9 @@
#include <RestartService.h> #include <RestartService.h>
using namespace std::placeholders; // for `_1` etc
RestartService::RestartService(AsyncWebServer * server, SecurityManager * securityManager) { RestartService::RestartService(AsyncWebServer * server, SecurityManager * securityManager) {
server->on(RESTART_SERVICE_PATH, server->on(RESTART_SERVICE_PATH, HTTP_POST, securityManager->wrapRequest(std::bind(&RestartService::restart, this, _1), AuthenticationPredicates::IS_ADMIN));
HTTP_POST,
securityManager->wrapRequest(std::bind(&RestartService::restart, this, std::placeholders::_1), AuthenticationPredicates::IS_ADMIN));
} }
void RestartService::restart(AsyncWebServerRequest * request) { void RestartService::restart(AsyncWebServerRequest * request) {

View File

@@ -1,13 +1,8 @@
#ifndef RestartService_h #ifndef RestartService_h
#define RestartService_h #define RestartService_h
#ifdef ESP32
#include <WiFi.h> #include <WiFi.h>
#include <AsyncTCP.h> #include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <SecurityManager.h> #include <SecurityManager.h>

View File

@@ -6,10 +6,8 @@
#include <list> #include <list>
#include <functional> #include <functional>
#ifdef ESP32
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include <freertos/semphr.h> #include <freertos/semphr.h>
#endif
#ifndef DEFAULT_BUFFER_SIZE #ifndef DEFAULT_BUFFER_SIZE
#define DEFAULT_BUFFER_SIZE 1024 #define DEFAULT_BUFFER_SIZE 1024
@@ -45,16 +43,10 @@ template <class T>
class StatefulService { class StatefulService {
public: public:
template <typename... Args> template <typename... Args>
#ifdef ESP32
StatefulService(Args &&... args) StatefulService(Args &&... args)
: _state(std::forward<Args>(args)...) : _state(std::forward<Args>(args)...)
, _accessMutex(xSemaphoreCreateRecursiveMutex()) { , _accessMutex(xSemaphoreCreateRecursiveMutex()) {
} }
#else
StatefulService(Args &&... args)
: _state(std::forward<Args>(args)...) {
}
#endif
update_handler_id_t addUpdateHandler(StateUpdateCallback cb, bool allowRemove = true) { update_handler_id_t addUpdateHandler(StateUpdateCallback cb, bool allowRemove = true) {
if (!cb) { if (!cb) {
@@ -131,21 +123,15 @@ class StatefulService {
T _state; T _state;
inline void beginTransaction() { inline void beginTransaction() {
#ifdef ESP32
xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY); xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY);
#endif
} }
inline void endTransaction() { inline void endTransaction() {
#ifdef ESP32
xSemaphoreGiveRecursive(_accessMutex); xSemaphoreGiveRecursive(_accessMutex);
#endif
} }
private: private:
#ifdef ESP32
SemaphoreHandle_t _accessMutex; SemaphoreHandle_t _accessMutex;
#endif
std::list<StateUpdateHandlerInfo_t> _updateHandlers; std::list<StateUpdateHandlerInfo_t> _updateHandlers;
}; };

View File

@@ -1,13 +1,15 @@
#include <SystemStatus.h> #include <SystemStatus.h>
using namespace std::placeholders; // for `_1` etc
SystemStatus::SystemStatus(AsyncWebServer * server, SecurityManager * securityManager) { SystemStatus::SystemStatus(AsyncWebServer * server, SecurityManager * securityManager) {
server->on(SYSTEM_STATUS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&SystemStatus::systemStatus, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED)); server->on(SYSTEM_STATUS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&SystemStatus::systemStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
} }
void SystemStatus::systemStatus(AsyncWebServerRequest * request) { void SystemStatus::systemStatus(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_ESP_STATUS_SIZE); AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_ESP_STATUS_SIZE);
JsonObject root = response->getRoot(); JsonObject root = response->getRoot();
root["esp_platform"] = "esp32"; root["esp_platform"] = "ESP32";
root["max_alloc_heap"] = ESP.getMaxAllocHeap(); root["max_alloc_heap"] = ESP.getMaxAllocHeap();
root["psram_size"] = ESP.getPsramSize(); root["psram_size"] = ESP.getPsramSize();
root["free_psram"] = ESP.getFreePsram(); root["free_psram"] = ESP.getFreePsram();

View File

@@ -1,21 +1,10 @@
#include <UploadFirmwareService.h> #include <UploadFirmwareService.h>
using namespace std::placeholders; // for `_1` etc
UploadFirmwareService::UploadFirmwareService(AsyncWebServer * server, SecurityManager * securityManager) UploadFirmwareService::UploadFirmwareService(AsyncWebServer * server, SecurityManager * securityManager)
: _securityManager(securityManager) { : _securityManager(securityManager) {
server->on(UPLOAD_FIRMWARE_PATH, server->on(UPLOAD_FIRMWARE_PATH, HTTP_POST, std::bind(&UploadFirmwareService::uploadComplete, this, _1), std::bind(&UploadFirmwareService::handleUpload, this, _1, _2, _3, _4, _5, _6));
HTTP_POST,
std::bind(&UploadFirmwareService::uploadComplete, this, std::placeholders::_1),
std::bind(&UploadFirmwareService::handleUpload,
this,
std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3,
std::placeholders::_4,
std::placeholders::_5,
std::placeholders::_6));
#ifdef ESP8266
Update.runAsync(true);
#endif
} }
void UploadFirmwareService::handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) { void UploadFirmwareService::handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) {
@@ -72,9 +61,5 @@ void UploadFirmwareService::handleError(AsyncWebServerRequest * request, int cod
} }
void UploadFirmwareService::handleEarlyDisconnect() { void UploadFirmwareService::handleEarlyDisconnect() {
#ifdef ESP32
Update.abort(); Update.abort();
#elif defined(ESP8266)
Update.end();
#endif
} }

View File

@@ -3,14 +3,9 @@
#include <Arduino.h> #include <Arduino.h>
#ifdef ESP32
#include <Update.h> #include <Update.h>
#include <WiFi.h> #include <WiFi.h>
#include <AsyncTCP.h> #include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <SecurityManager.h> #include <SecurityManager.h>

View File

@@ -10,6 +10,8 @@
#define WEB_SOCKET_ORIGIN "websocket" #define WEB_SOCKET_ORIGIN "websocket"
#define WEB_SOCKET_ORIGIN_CLIENT_ID_PREFIX "websocket:" #define WEB_SOCKET_ORIGIN_CLIENT_ID_PREFIX "websocket:"
using namespace std::placeholders; // for `_1` etc
template <class T> template <class T>
class WebSocketConnector { class WebSocketConnector {
protected: protected:
@@ -18,27 +20,15 @@ class WebSocketConnector {
AsyncWebSocket _webSocket; AsyncWebSocket _webSocket;
size_t _bufferSize; size_t _bufferSize;
WebSocketConnector(StatefulService<T> * statefulService, WebSocketConnector(StatefulService<T> * statefulService, AsyncWebServer * server, const char * webSocketPath, SecurityManager * securityManager, AuthenticationPredicate authenticationPredicate, size_t bufferSize)
AsyncWebServer * server,
const char * webSocketPath,
SecurityManager * securityManager,
AuthenticationPredicate authenticationPredicate,
size_t bufferSize)
: _statefulService(statefulService) : _statefulService(statefulService)
, _server(server) , _server(server)
, _webSocket(webSocketPath) , _webSocket(webSocketPath)
, _bufferSize(bufferSize) { , _bufferSize(bufferSize) {
_webSocket.setFilter(securityManager->filterRequest(authenticationPredicate)); _webSocket.setFilter(securityManager->filterRequest(authenticationPredicate));
_webSocket.onEvent(std::bind(&WebSocketConnector::onWSEvent, _webSocket.onEvent(std::bind(&WebSocketConnector::onWSEvent, this, _1, _2, _3, _4, _5, _6));
this,
std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3,
std::placeholders::_4,
std::placeholders::_5,
std::placeholders::_6));
_server->addHandler(&_webSocket); _server->addHandler(&_webSocket);
_server->on(webSocketPath, HTTP_GET, std::bind(&WebSocketConnector::forbidden, this, std::placeholders::_1)); _server->on(webSocketPath, HTTP_GET, std::bind(&WebSocketConnector::forbidden, this, _1));
} }
WebSocketConnector(StatefulService<T> * statefulService, AsyncWebServer * server, const char * webSocketPath, size_t bufferSize) WebSocketConnector(StatefulService<T> * statefulService, AsyncWebServer * server, const char * webSocketPath, size_t bufferSize)
@@ -46,14 +36,7 @@ class WebSocketConnector {
, _server(server) , _server(server)
, _webSocket(webSocketPath) , _webSocket(webSocketPath)
, _bufferSize(bufferSize) { , _bufferSize(bufferSize) {
_webSocket.onEvent(std::bind(&WebSocketConnector::onWSEvent, _webSocket.onEvent(std::bind(&WebSocketConnector::onWSEvent, this, _1, _2, _3, _4, _5, _6));
this,
std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3,
std::placeholders::_4,
std::placeholders::_5,
std::placeholders::_6));
_server->addHandler(&_webSocket); _server->addHandler(&_webSocket);
} }
@@ -84,11 +67,7 @@ class WebSocketTx : virtual public WebSocketConnector<T> {
WebSocketConnector<T>::_statefulService->addUpdateHandler([&](const String & originId) { transmitData(nullptr, originId); }, false); WebSocketConnector<T>::_statefulService->addUpdateHandler([&](const String & originId) { transmitData(nullptr, originId); }, false);
} }
WebSocketTx(JsonStateReader<T> stateReader, WebSocketTx(JsonStateReader<T> stateReader, StatefulService<T> * statefulService, AsyncWebServer * server, const char * webSocketPath, size_t bufferSize = DEFAULT_BUFFER_SIZE)
StatefulService<T> * statefulService,
AsyncWebServer * server,
const char * webSocketPath,
size_t bufferSize = DEFAULT_BUFFER_SIZE)
: WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize) : WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize)
, _stateReader(stateReader) { , _stateReader(stateReader) {
WebSocketConnector<T>::_statefulService->addUpdateHandler([&](const String & originId) { transmitData(nullptr, originId); }, false); WebSocketConnector<T>::_statefulService->addUpdateHandler([&](const String & originId) { transmitData(nullptr, originId); }, false);
@@ -161,11 +140,7 @@ class WebSocketRx : virtual public WebSocketConnector<T> {
, _stateUpdater(stateUpdater) { , _stateUpdater(stateUpdater) {
} }
WebSocketRx(JsonStateUpdater<T> stateUpdater, WebSocketRx(JsonStateUpdater<T> stateUpdater, StatefulService<T> * statefulService, AsyncWebServer * server, const char * webSocketPath, size_t bufferSize = DEFAULT_BUFFER_SIZE)
StatefulService<T> * statefulService,
AsyncWebServer * server,
const char * webSocketPath,
size_t bufferSize = DEFAULT_BUFFER_SIZE)
: WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize) : WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize)
, _stateUpdater(stateUpdater) { , _stateUpdater(stateUpdater) {
} }
@@ -207,12 +182,7 @@ class WebSocketTxRx : public WebSocketTx<T>, public WebSocketRx<T> {
, WebSocketRx<T>(stateUpdater, statefulService, server, webSocketPath, securityManager, authenticationPredicate, bufferSize) { , WebSocketRx<T>(stateUpdater, statefulService, server, webSocketPath, securityManager, authenticationPredicate, bufferSize) {
} }
WebSocketTxRx(JsonStateReader<T> stateReader, WebSocketTxRx(JsonStateReader<T> stateReader, JsonStateUpdater<T> stateUpdater, StatefulService<T> * statefulService, AsyncWebServer * server, const char * webSocketPath, size_t bufferSize = DEFAULT_BUFFER_SIZE)
JsonStateUpdater<T> stateUpdater,
StatefulService<T> * statefulService,
AsyncWebServer * server,
const char * webSocketPath,
size_t bufferSize = DEFAULT_BUFFER_SIZE)
: WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize) : WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize)
, WebSocketTx<T>(stateReader, statefulService, server, webSocketPath, bufferSize) , WebSocketTx<T>(stateReader, statefulService, server, webSocketPath, bufferSize)
, WebSocketRx<T>(stateUpdater, statefulService, server, webSocketPath, bufferSize) { , WebSocketRx<T>(stateUpdater, statefulService, server, webSocketPath, bufferSize) {

View File

@@ -1,8 +1,10 @@
#include <WiFiScanner.h> #include <WiFiScanner.h>
using namespace std::placeholders; // for `_1` etc
WiFiScanner::WiFiScanner(AsyncWebServer * server, SecurityManager * securityManager) { WiFiScanner::WiFiScanner(AsyncWebServer * server, SecurityManager * securityManager) {
server->on(SCAN_NETWORKS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WiFiScanner::scanNetworks, this, std::placeholders::_1), AuthenticationPredicates::IS_ADMIN)); server->on(SCAN_NETWORKS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WiFiScanner::scanNetworks, this, _1), AuthenticationPredicates::IS_ADMIN));
server->on(LIST_NETWORKS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WiFiScanner::listNetworks, this, std::placeholders::_1), AuthenticationPredicates::IS_ADMIN)); server->on(LIST_NETWORKS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WiFiScanner::listNetworks, this, _1), AuthenticationPredicates::IS_ADMIN));
}; };
void WiFiScanner::scanNetworks(AsyncWebServerRequest * request) { void WiFiScanner::scanNetworks(AsyncWebServerRequest * request) {

View File

@@ -1,13 +1,8 @@
#ifndef WiFiScanner_h #ifndef WiFiScanner_h
#define WiFiScanner_h #define WiFiScanner_h
#ifdef ESP32
#include <WiFi.h> #include <WiFi.h>
#include <AsyncTCP.h> #include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <AsyncJson.h> #include <AsyncJson.h>
@@ -26,10 +21,6 @@ class WiFiScanner {
private: private:
void scanNetworks(AsyncWebServerRequest * request); void scanNetworks(AsyncWebServerRequest * request);
void listNetworks(AsyncWebServerRequest * request); void listNetworks(AsyncWebServerRequest * request);
#ifdef ESP8266
uint8_t convertEncryptionType(uint8_t encryptionType);
#endif
}; };
#endif // end WiFiScanner_h #endif // end WiFiScanner_h

View File

@@ -19,13 +19,11 @@
#include "uuid/syslog.h" #include "uuid/syslog.h"
#include <Arduino.h> #include <Arduino.h>
#ifdef ARDUINO_ARCH_ESP8266
#include <ESP8266WiFi.h>
#else
#include <WiFi.h> #include <WiFi.h>
#endif
#include <WiFiUdp.h> #include <WiFiUdp.h>
#include "../../../src/emsesp.h"
#ifndef UUID_SYSLOG_HAVE_GETTIMEOFDAY #ifndef UUID_SYSLOG_HAVE_GETTIMEOFDAY
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
// time() does not return UTC on the ESP8266: https://github.com/esp8266/Arduino/issues/4637 // time() does not return UTC on the ESP8266: https://github.com/esp8266/Arduino/issues/4637
@@ -191,7 +189,8 @@ void SyslogService::mark_interval(unsigned long interval) {
SyslogService::QueuedLogMessage::QueuedLogMessage(unsigned long id, std::shared_ptr<uuid::log::Message> && content) SyslogService::QueuedLogMessage::QueuedLogMessage(unsigned long id, std::shared_ptr<uuid::log::Message> && content)
: id_(id) : id_(id)
, content_(std::move(content)) { , content_(std::move(content)) {
if (time_good_ || WiFi.status() == WL_CONNECTED) { // Added by proddy - check for Ethernet too. This assumes the network has already started.
if (time_good_ || emsesp::EMSESP::system_.network_connected()) {
#if UUID_SYSLOG_HAVE_GETTIMEOFDAY #if UUID_SYSLOG_HAVE_GETTIMEOFDAY
if (gettimeofday(&time_, nullptr) != 0) { if (gettimeofday(&time_, nullptr) != 0) {
time_.tv_sec = (time_t)-1; time_.tv_sec = (time_t)-1;
@@ -269,8 +268,8 @@ bool SyslogService::can_transmit() {
} }
#endif #endif
if (WiFi.status() != WL_CONNECTED) { if (!emsesp::EMSESP::system_.network_connected()) {
return false; return false; // added by proddy. Check Ethernet
} }
const uint64_t now = uuid::get_uptime_ms(); const uint64_t now = uuid::get_uptime_ms();

View File

@@ -36,7 +36,8 @@ class DummySettings {
bool nested_format = true; bool nested_format = true;
uint8_t ha_climate_format = 1; uint8_t ha_climate_format = 1;
bool ha_enabled = true; bool ha_enabled = true;
std::string base = "ems-esp"; String base = "ems-esp";
uint8_t subscribe_format = 0;
String hostname = "ems-esp"; String hostname = "ems-esp";
String jwtSecret = "ems-esp"; String jwtSecret = "ems-esp";
@@ -48,7 +49,7 @@ class DummySettings {
String staticIPConfig = ""; String staticIPConfig = "";
String dnsIP1 = ""; String dnsIP1 = "";
String dnsIP2 = ""; String dnsIP2 = "";
uint8_t ethernet_profile = 0; String board_profile = "CUSTOM";
uint16_t publish_time_boiler = 10; uint16_t publish_time_boiler = 10;
uint16_t publish_time_thermostat = 10; uint16_t publish_time_thermostat = 10;
uint16_t publish_time_solar = 10; uint16_t publish_time_solar = 10;

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 DEFINES += -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_TEST -DEMSESP_DEFAULT_BOARD_PROFILE=\"LOLIN\"
#---------------------------------------------------------------------- #----------------------------------------------------------------------
# Sources & Files # Sources & Files

View File

@@ -1,3 +1,5 @@
; example custom platformio.ini file for EMS-ESP
[env] [env]
upload_protocol = espota upload_protocol = espota
upload_flags = upload_flags =
@@ -6,9 +8,9 @@ upload_flags =
upload_port = 10.10.10.101 upload_port = 10.10.10.101
[common] [common]
; debug_flags = -DENABLE_CORS -DEMSESP_TEST ; options are EMSESP_DEBUG EMSESP_UART_DEBUG EMSESP_TEST ENABLE_CORS DEMSESP_DEFAULT_BOARD_PROFILE
; debug_flags = -DEMSESP_DEBUG -DEMSESP_TEST ; debug_flags = -DENABLE_CORS -DEMSESP_DEBUG -DEMSESP_TEST -DCORS_ORIGIN=\"http://localhost:3000\"
debug_flags = -DEMSESP_TEST ; debug_flags = -DEMSESP_DEFAULT_BOARD_PROFILE=\"NODEMCU\"
[env:esp32] [env:esp32]
monitor_filters = esp32_exception_decoder monitor_filters = esp32_exception_decoder
@@ -16,4 +18,5 @@ debug_tool = esp-prog
debug_init_break = tbreak setup debug_init_break = tbreak setup
extra_scripts = extra_scripts =
; pre:scripts/build_interface.py ; pre:scripts/build_interface.py
; scripts/rename_fw.py ; scripts/upload_fw.py

View File

@@ -1,50 +1,37 @@
; PlatformIO Project Configuration File for EMS-ESP ; PlatformIO Project Configuration File for EMS-ESP
; override any settings with your own local ones in pio_local.ini
[platformio] [platformio]
default_envs = esp32 default_envs = esp32
# override any settings with your own local ones in pio_local.ini
extra_configs = extra_configs =
factory_settings.ini factory_settings.ini
pio_local.ini pio_local.ini
[common] [common]
; default platformio compile flags are: -fno-rtti -std=c++11 -Os -mlongcalls -mtext-section-literals -falign-functions=4 -ffunction-sections -fdata-sections -fno-exceptions -Wall core_build_flags =
core_build_flags = -Wno-deprecated-declarations -Wall
-Wreturn-type -D CORE_DEBUG_LEVEL=0
-DCORE_DEBUG_LEVEL=0 -D NDEBUG
-DNDEBUG -D ARDUINO_ARCH_ESP32=1
-D ESP32=1
; -std=c++17 -std=gnu++17
esp32_build_flags = -DARDUINO_ARCH_ESP32=1 core_unbuild_flags =
-DESP32=1 ; -std=gnu++11
-DBOARD_HAS_PSRAM
; -std=c17 -std=c++17 -std=gnu++17
build_flags = build_flags =
${common.core_build_flags} ${common.core_build_flags}
${factory_settings.build_flags} ${factory_settings.build_flags}
-D FT_PROJECT=1
-D FT_SECURITY=1
-D FT_MQTT=1
-D FT_OTA=1
-D FT_NTP=1
-D FT_UPLOAD_FIRMWARE=1
-D ONEWIRE_CRC16=0 -D ONEWIRE_CRC16=0
-D NO_GLOBAL_ARDUINOOTA -D NO_GLOBAL_ARDUINOOTA
-D ARDUINOJSON_ENABLE_STD_STRING=1 -D ARDUINOJSON_ENABLE_STD_STRING=1
-D CORS_ORIGIN=\"http://localhost:3000\"
build_unflags = -Wall unbuild_flags =
-Wdeprecated-declarations ${common.core_unbuild_flags}
esp32_build_unflags =
; -std=gnu++11
; these are set in your pio_local.ini
debug_flags = debug_flags =
; -D EMSESP_DEBUG
; -D EMSESP_UART_DEBUG
; -D EMSESP_TEST
; -D ENABLE_CORS
[env] [env]
framework = arduino framework = arduino
@@ -52,7 +39,6 @@ monitor_speed = 115200
upload_speed = 921600 upload_speed = 921600
build_type = release build_type = release
lib_ldf_mode = chain+ lib_ldf_mode = chain+
; lib_compat_mode = strict
check_tool = cppcheck, clangtidy check_tool = cppcheck, clangtidy
check_severity = high, medium check_severity = high, medium
@@ -62,13 +48,12 @@ check_flags =
; build for GitHub Actions CI ; build for GitHub Actions CI
[env:ci] [env:ci]
extra_scripts = extra_scripts = scripts/rename_fw.py
scripts/rename_fw.py
board = esp32dev board = esp32dev
platform = espressif32 platform = espressif32
board_build.partitions = esp32_partition_app1984k_spiffs64k.csv board_build.partitions = esp32_partition_app1984k_spiffs64k.csv
build_flags = ${common.build_flags} ${common.esp32_build_flags} build_flags = ${common.build_flags}
build_unflags = ${common.build_unflags} ${common.esp32_build_unflags} build_unflags = ${common.unbuild_flags}
[env:esp32] [env:esp32]
extra_scripts = extra_scripts =
@@ -76,11 +61,11 @@ extra_scripts =
scripts/rename_fw.py scripts/rename_fw.py
board = esp32dev board = esp32dev
platform = espressif32 platform = espressif32
;platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#idf-release/v4.2 ; platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#idf-release/v4.2
; ; toolchain-xtensa32 @ 2.80200.200226 ; toolchain-xtensa32 @ 2.80200.200226
; ; toolchain-xtensa32 @ 5.100200.201223 ; toolchain-xtensa32 @ 5.100200.201223
; toolchain-xtensa32 @ 2.80400.2020 ; c70ec8a-toolchain-xtensa32-linux_x86_64-2.80400.2020.tar.gz ; toolchain-xtensa32 @ 2.80400.2020
; platform = https://github.com/platformio/platform-espressif32.git ; platform = https://github.com/platformio/platform-espressif32.git
board_build.partitions = esp32_partition_app1984k_spiffs64k.csv ; https://github.com/espressif/arduino-esp32/blob/master/tools/partitions/ board_build.partitions = esp32_partition_app1984k_spiffs64k.csv
build_flags = ${common.build_flags} ${common.esp32_build_flags} ${common.debug_flags} build_flags = ${common.build_flags} ${common.debug_flags}
build_unflags = ${common.build_unflags} ${common.esp32_build_unflags} build_unflags = ${common.unbuild_flags}

View File

@@ -10,9 +10,9 @@ def upload(source, target, env):
platform = "esp" + env['PIOPLATFORM'].strip("espressif") platform = "esp" + env['PIOPLATFORM'].strip("espressif")
if platform == 'esp8266': if platform == 'esp8266':
call(["cmd.exe", "/c", "C:\\Users\\Paul\\OneDrive\\Desktop\\com8266.bat"]) call(["cmd.exe", "/c", "C:\\Users\\Paul\\Desktop\\ems-esp8266.bat"])
if platform == 'esp32': if platform == 'esp32':
call(["cmd.exe", "/c", "C:\\Users\\Paul\\OneDrive\\Desktop\\com32.bat"]) call(["cmd.exe", "/c", "C:\\Users\\Paul\\Desktop\\ems-esp32.bat"])
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", [upload]) env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", [upload])

View File

@@ -18,10 +18,12 @@
#include "emsesp.h" #include "emsesp.h"
using namespace std::placeholders; // for `_1` etc
namespace emsesp { namespace emsesp {
WebAPIService::WebAPIService(AsyncWebServer * server) { WebAPIService::WebAPIService(AsyncWebServer * server) {
server->on(EMSESP_API_SERVICE_PATH, HTTP_GET, std::bind(&WebAPIService::webAPIService, this, std::placeholders::_1)); server->on(EMSESP_API_SERVICE_PATH, HTTP_GET, std::bind(&WebAPIService::webAPIService, this, _1));
} }
// e.g. http://ems-esp/api?device=boiler&cmd=wwtemp&data=20&id=1 // e.g. http://ems-esp/api?device=boiler&cmd=wwtemp&data=20&id=1

View File

@@ -23,15 +23,10 @@ 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, : _device_dataHandler(DEVICE_DATA_SERVICE_PATH, securityManager->wrapCallback(std::bind(&WebDevicesService::device_data, this, _1, _2), AuthenticationPredicates::IS_AUTHENTICATED)) {
securityManager->wrapCallback(std::bind(&WebDevicesService::device_data, this, _1, _2), AuthenticationPredicates::IS_AUTHENTICATED)) { server->on(EMSESP_DEVICES_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WebDevicesService::all_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
server->on(EMSESP_DEVICES_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WebDevicesService::all_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
server->on(SCAN_DEVICES_SERVICE_PATH, server->on(SCAN_DEVICES_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WebDevicesService::scan_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
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);

View File

@@ -22,9 +22,16 @@ namespace emsesp {
uint8_t WebSettings::flags_; uint8_t WebSettings::flags_;
using namespace std::placeholders; // for `_1` etc
WebSettingsService::WebSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) WebSettingsService::WebSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(WebSettings::read, WebSettings::update, this, server, EMSESP_SETTINGS_SERVICE_PATH, securityManager) : _httpEndpoint(WebSettings::read, WebSettings::update, this, server, EMSESP_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(WebSettings::read, WebSettings::update, this, fs, EMSESP_SETTINGS_FILE) { , _fsPersistence(WebSettings::read, WebSettings::update, this, fs, EMSESP_SETTINGS_FILE)
, _boardProfileHandler(EMSESP_BOARD_PROFILE_SERVICE_PATH, securityManager->wrapCallback(std::bind(&WebSettingsService::board_profile, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) {
_boardProfileHandler.setMethod(HTTP_POST);
_boardProfileHandler.setMaxContentLength(256);
server->addHandler(&_boardProfileHandler);
addUpdateHandler([&](const String & originId) { onUpdate(); }, false); addUpdateHandler([&](const String & originId) { onUpdate(); }, false);
} }
@@ -50,80 +57,111 @@ void WebSettings::read(WebSettings & settings, JsonObject & root) {
root["api_enabled"] = settings.api_enabled; root["api_enabled"] = settings.api_enabled;
root["analog_enabled"] = settings.analog_enabled; root["analog_enabled"] = settings.analog_enabled;
root["pbutton_gpio"] = settings.pbutton_gpio; root["pbutton_gpio"] = settings.pbutton_gpio;
root["board_profile"] = settings.board_profile;
} }
// call on initialization and also when settings are updated via web or console
StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings) { StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings) {
std::string crc_before(40, '\0'); // load default GPIO configuration based on board profile
std::string crc_after(40, '\0'); std::vector<uint8_t> data; // led, dallas, rx, tx, button
settings.board_profile = root["board_profile"] | EMSESP_DEFAULT_BOARD_PROFILE;
if (!System::load_board_profile(data, settings.board_profile.c_str())) {
settings.board_profile = EMSESP_DEFAULT_BOARD_PROFILE; // invalid board configuration, override the default in case it has been misspelled
}
uint8_t default_led_gpio = data[0];
uint8_t default_dallas_gpio = data[1];
uint8_t default_rx_gpio = data[2];
uint8_t default_tx_gpio = data[3];
uint8_t default_pbutton_gpio = data[4];
EMSESP::logger().info(F("EMS-ESP version %s"), EMSESP_APP_VERSION);
// check to see if we have a settings file, if not it's a fresh install
if (!root.size()) {
EMSESP::logger().info(F("Initializing configuration with board profile %s"), settings.board_profile.c_str());
} else {
EMSESP::logger().info(F("Using configuration from board profile %s"), settings.board_profile.c_str());
}
int prev;
reset_flags(); reset_flags();
// tx_mode, rx and tx pins // tx_mode, rx and tx pins
snprintf_P(&crc_before[0], crc_before.capacity() + 1, PSTR("%d%d%d"), settings.tx_mode, settings.rx_gpio, settings.tx_gpio); prev = settings.tx_mode;
settings.tx_mode = root["tx_mode"] | EMSESP_DEFAULT_TX_MODE; settings.tx_mode = root["tx_mode"] | EMSESP_DEFAULT_TX_MODE;
check_flag(prev, settings.tx_mode, ChangeFlags::UART);
prev = settings.tx_delay;
settings.tx_delay = root["tx_delay"] | EMSESP_DEFAULT_TX_DELAY; settings.tx_delay = root["tx_delay"] | EMSESP_DEFAULT_TX_DELAY;
settings.rx_gpio = root["rx_gpio"] | EMSESP_DEFAULT_RX_GPIO; check_flag(prev, settings.tx_delay, ChangeFlags::UART);
settings.tx_gpio = root["tx_gpio"] | EMSESP_DEFAULT_TX_GPIO; prev = settings.rx_gpio;
snprintf_P(&crc_after[0], crc_after.capacity() + 1, PSTR("%d%d%d"), settings.tx_mode, settings.rx_gpio, settings.tx_gpio); settings.rx_gpio = root["rx_gpio"] | default_rx_gpio;
if (crc_before != crc_after) { check_flag(prev, settings.rx_gpio, ChangeFlags::UART);
add_flags(ChangeFlags::UART); prev = settings.tx_gpio;
} settings.tx_gpio = root["tx_gpio"] | default_tx_gpio;
check_flag(prev, settings.tx_gpio, ChangeFlags::UART);
// syslog // syslog
snprintf_P(&crc_before[0], crc_before.capacity() + 1, PSTR("%d%d%d%s"), settings.syslog_enabled, settings.syslog_level, settings.syslog_mark_interval, settings.syslog_host.c_str()); prev = settings.syslog_enabled;
settings.syslog_enabled = root["syslog_enabled"] | EMSESP_DEFAULT_SYSLOG_ENABLED; settings.syslog_enabled = root["syslog_enabled"] | EMSESP_DEFAULT_SYSLOG_ENABLED;
check_flag(prev, settings.syslog_enabled, ChangeFlags::SYSLOG);
prev = settings.syslog_level;
settings.syslog_level = root["syslog_level"] | EMSESP_DEFAULT_SYSLOG_LEVEL; settings.syslog_level = root["syslog_level"] | EMSESP_DEFAULT_SYSLOG_LEVEL;
check_flag(prev, settings.syslog_level, ChangeFlags::SYSLOG);
prev = settings.syslog_mark_interval;
settings.syslog_mark_interval = root["syslog_mark_interval"] | EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL; settings.syslog_mark_interval = root["syslog_mark_interval"] | EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL;
check_flag(prev, settings.syslog_mark_interval, ChangeFlags::SYSLOG);
String old_syslog_host = settings.syslog_host;
settings.syslog_host = root["syslog_host"] | EMSESP_DEFAULT_SYSLOG_HOST; settings.syslog_host = root["syslog_host"] | EMSESP_DEFAULT_SYSLOG_HOST;
settings.syslog_port = root["syslog_port"] | EMSESP_DEFAULT_SYSLOG_PORT; if (old_syslog_host.equals(settings.syslog_host.c_str())) {
settings.trace_raw = root["trace_raw"] | EMSESP_DEFAULT_TRACELOG_RAW;
EMSESP::trace_raw(settings.trace_raw);
snprintf_P(&crc_after[0], crc_after.capacity() + 1, PSTR("%d%d%d%d%s"), settings.syslog_enabled, settings.syslog_level, settings.syslog_mark_interval, settings.syslog_port, settings.syslog_host.c_str());
if (crc_before != crc_after) {
add_flags(ChangeFlags::SYSLOG); add_flags(ChangeFlags::SYSLOG);
} }
prev = settings.syslog_port;
settings.syslog_port = root["syslog_port"] | EMSESP_DEFAULT_SYSLOG_PORT;
check_flag(prev, settings.syslog_port, ChangeFlags::SYSLOG);
prev = settings.trace_raw;
settings.trace_raw = root["trace_raw"] | EMSESP_DEFAULT_TRACELOG_RAW;
check_flag(prev, settings.trace_raw, ChangeFlags::SYSLOG);
EMSESP::trace_raw(settings.trace_raw);
// adc // adc
snprintf_P(&crc_before[0], crc_before.capacity() + 1, PSTR("%d"), settings.analog_enabled); prev = settings.analog_enabled;
settings.analog_enabled = root["analog_enabled"] | EMSESP_DEFAULT_ANALOG_ENABLED; settings.analog_enabled = root["analog_enabled"] | EMSESP_DEFAULT_ANALOG_ENABLED;
snprintf_P(&crc_after[0], crc_after.capacity() + 1, PSTR("%d"), settings.analog_enabled); check_flag(prev, settings.analog_enabled, ChangeFlags::ADC);
if (crc_before != crc_after) {
add_flags(ChangeFlags::ADC);
}
// button // button
snprintf_P(&crc_before[0], crc_before.capacity() + 1, PSTR("%d"), settings.pbutton_gpio); prev = settings.pbutton_gpio;
settings.pbutton_gpio = root["pbutton_gpio"] | EMSESP_DEFAULT_PBUTTON_GPIO; settings.pbutton_gpio = root["pbutton_gpio"] | default_pbutton_gpio;
snprintf_P(&crc_after[0], crc_after.capacity() + 1, PSTR("%d"), settings.pbutton_gpio); check_flag(prev, settings.pbutton_gpio, ChangeFlags::BUTTON);
if (crc_before != crc_after) {
add_flags(ChangeFlags::BUTTON);
}
// dallas // dallas
snprintf_P(&crc_before[0], crc_before.capacity() + 1, PSTR("%d%d"), settings.dallas_gpio, settings.dallas_parasite); prev = settings.dallas_gpio;
settings.dallas_gpio = root["dallas_gpio"] | EMSESP_DEFAULT_DALLAS_GPIO; settings.dallas_gpio = root["dallas_gpio"] | default_dallas_gpio;
check_flag(prev, settings.dallas_gpio, ChangeFlags::DALLAS);
prev = settings.dallas_parasite;
settings.dallas_parasite = root["dallas_parasite"] | EMSESP_DEFAULT_DALLAS_PARASITE; settings.dallas_parasite = root["dallas_parasite"] | EMSESP_DEFAULT_DALLAS_PARASITE;
snprintf_P(&crc_after[0], crc_after.capacity() + 1, PSTR("%d%d"), settings.dallas_gpio, settings.dallas_parasite); check_flag(prev, settings.dallas_parasite, ChangeFlags::DALLAS);
if (crc_before != crc_after) {
add_flags(ChangeFlags::DALLAS);
}
// shower // shower
snprintf_P(&crc_before[0], crc_before.capacity() + 1, PSTR("%d%d"), settings.shower_timer, settings.shower_alert); prev = settings.shower_timer;
settings.shower_timer = root["shower_timer"] | EMSESP_DEFAULT_SHOWER_TIMER; settings.shower_timer = root["shower_timer"] | EMSESP_DEFAULT_SHOWER_TIMER;
check_flag(prev, settings.shower_timer, ChangeFlags::SHOWER);
prev = settings.shower_alert;
settings.shower_alert = root["shower_alert"] | EMSESP_DEFAULT_SHOWER_ALERT; settings.shower_alert = root["shower_alert"] | EMSESP_DEFAULT_SHOWER_ALERT;
snprintf_P(&crc_after[0], crc_after.capacity() + 1, PSTR("%d%d"), settings.shower_timer, settings.shower_alert); check_flag(prev, settings.shower_alert, ChangeFlags::SHOWER);
if (crc_before != crc_after) {
add_flags(ChangeFlags::SHOWER);
}
// led // led
snprintf_P(&crc_before[0], crc_before.capacity() + 1, PSTR("%d%d"), settings.led_gpio, settings.hide_led); prev = settings.led_gpio;
settings.led_gpio = root["led_gpio"] | EMSESP_DEFAULT_LED_GPIO; settings.led_gpio = root["led_gpio"] | default_led_gpio;
check_flag(prev, settings.led_gpio, ChangeFlags::LED);
prev = settings.hide_led;
settings.hide_led = root["hide_led"] | EMSESP_DEFAULT_HIDE_LED; settings.hide_led = root["hide_led"] | EMSESP_DEFAULT_HIDE_LED;
snprintf_P(&crc_after[0], crc_after.capacity() + 1, PSTR("%d%d"), settings.led_gpio, settings.hide_led); check_flag(prev, settings.hide_led, ChangeFlags::LED);
if (crc_before != crc_after) {
add_flags(ChangeFlags::LED);
}
// these both need reboots to be applied // these both need reboots to be applied
settings.ems_bus_id = root["ems_bus_id"] | EMSESP_DEFAULT_EMS_BUS_ID; settings.ems_bus_id = root["ems_bus_id"] | EMSESP_DEFAULT_EMS_BUS_ID;
@@ -147,11 +185,12 @@ void WebSettingsService::onUpdate() {
} }
if (WebSettings::has_flags(WebSettings::ChangeFlags::UART)) { if (WebSettings::has_flags(WebSettings::ChangeFlags::UART)) {
EMSESP::init_tx(); EMSESP::init_uart();
} }
if (WebSettings::has_flags(WebSettings::ChangeFlags::SYSLOG)) { if (WebSettings::has_flags(WebSettings::ChangeFlags::SYSLOG)) {
EMSESP::system_.syslog_init(true); // reload settings EMSESP::system_.syslog_init(true); // reload settings
EMSESP::system_.syslog_start(); // re-start (or stop)
} }
if (WebSettings::has_flags(WebSettings::ChangeFlags::ADC)) { if (WebSettings::has_flags(WebSettings::ChangeFlags::ADC)) {
@@ -175,4 +214,35 @@ void WebSettingsService::save() {
_fsPersistence.writeToFS(); _fsPersistence.writeToFS();
} }
// build the json profile to send back
void WebSettingsService::board_profile(AsyncWebServerRequest * request, JsonVariant & json) {
if (json.is<JsonObject>()) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_MEDIUM);
JsonObject root = response->getRoot();
if (json.containsKey("code")) {
String board_profile = json["code"];
std::vector<uint8_t> data; // led, dallas, rx, tx, button
// check for valid board
if (System::load_board_profile(data, board_profile.c_str())) {
root["led_gpio"] = data[0];
root["dallas_gpio"] = data[1];
root["rx_gpio"] = data[2];
root["tx_gpio"] = data[3];
root["pbutton_gpio"] = data[4];
} else {
AsyncWebServerResponse * response = request->beginResponse(200);
request->send(response);
return;
}
response->setLength();
request->send(response);
return;
}
}
AsyncWebServerResponse * response = request->beginResponse(200);
request->send(response);
}
} // namespace emsesp } // namespace emsesp

View File

@@ -24,6 +24,7 @@
#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_DEFAULT_TX_MODE 1 // EMS1.0 #define EMSESP_DEFAULT_TX_MODE 1 // EMS1.0
#define EMSESP_DEFAULT_TX_DELAY 0 // no delay #define EMSESP_DEFAULT_TX_DELAY 0 // no delay
@@ -37,28 +38,23 @@
#define EMSESP_DEFAULT_MASTER_THERMOSTAT 0 // not set #define EMSESP_DEFAULT_MASTER_THERMOSTAT 0 // not set
#define EMSESP_DEFAULT_SHOWER_TIMER false #define EMSESP_DEFAULT_SHOWER_TIMER false
#define EMSESP_DEFAULT_SHOWER_ALERT false #define EMSESP_DEFAULT_SHOWER_ALERT false
#define EMSESP_DEFAULT_HIDE_LED true #define EMSESP_DEFAULT_HIDE_LED false
#define EMSESP_DEFAULT_DALLAS_PARASITE false #define EMSESP_DEFAULT_DALLAS_PARASITE false
#define EMSESP_DEFAULT_API_ENABLED false // turn off, because its insecure #define EMSESP_DEFAULT_API_ENABLED false // turn off, because its insecure
#define EMSESP_DEFAULT_BOOL_FORMAT 1 // on/off #define EMSESP_DEFAULT_BOOL_FORMAT 1 // on/off
#define EMSESP_DEFAULT_ANALOG_ENABLED false #define EMSESP_DEFAULT_ANALOG_ENABLED false
// Default GPIO PIN definitions #ifndef EMSESP_DEFAULT_BOARD_PROFILE
#if defined(ESP32) #define EMSESP_DEFAULT_BOARD_PROFILE "S32" // Gateway S32
#define EMSESP_DEFAULT_RX_GPIO 23 // D7 on Wemos D1-32, OR 17 for UART2 on Lolin D32
#define EMSESP_DEFAULT_TX_GPIO 5 // D8 on Wemos D1-32, OR 16 for UART2 on Lolin D32
#define EMSESP_DEFAULT_DALLAS_GPIO 18 // 18 on Wemos D1-32, 14 on LOLIN D32
#define EMSESP_DEFAULT_LED_GPIO 2 // 2 on Wemos D1-32, 5 on LOLIN D32
#define EMSESP_DEFAULT_PBUTTON_GPIO 0 // default GPIO is 0 (off)
#else
// for standalone
#define EMSESP_DEFAULT_RX_GPIO 0
#define EMSESP_DEFAULT_TX_GPIO 0
#define EMSESP_DEFAULT_DALLAS_GPIO 0
#define EMSESP_DEFAULT_LED_GPIO 0
#define EMSESP_DEFAULT_PBUTTON_GPIO 0
#endif #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 {
@@ -84,6 +80,7 @@ class WebSettings {
bool api_enabled; bool api_enabled;
bool analog_enabled; bool analog_enabled;
uint8_t pbutton_gpio; uint8_t pbutton_gpio;
String board_profile;
static void read(WebSettings & settings, JsonObject & root); static void read(WebSettings & settings, JsonObject & root);
static StateUpdateResult update(JsonObject & root, WebSettings & settings); static StateUpdateResult update(JsonObject & root, WebSettings & settings);
@@ -91,16 +88,22 @@ class WebSettings {
enum ChangeFlags : uint8_t { enum ChangeFlags : uint8_t {
NONE = 0, NONE = 0,
UART = (1 << 0), UART = (1 << 0), // 1
SYSLOG = (1 << 1), SYSLOG = (1 << 1), // 2
ADC = (1 << 2), ADC = (1 << 2), // 4
DALLAS = (1 << 3), DALLAS = (1 << 3), // 8
SHOWER = (1 << 4), SHOWER = (1 << 4), // 16
LED = (1 << 5), LED = (1 << 5), // 32
BUTTON = (1 << 6) BUTTON = (1 << 6) // 64
}; };
static void check_flag(int prev_v, int new_v, uint8_t flag) {
if (prev_v != new_v) {
add_flags(flag);
}
}
static void add_flags(uint8_t flags) { static void add_flags(uint8_t flags) {
flags_ |= flags; flags_ |= flags;
} }
@@ -113,6 +116,10 @@ class WebSettings {
flags_ = ChangeFlags::NONE; flags_ = ChangeFlags::NONE;
} }
static uint8_t get_flags() {
return flags_;
}
private: private:
static uint8_t flags_; static uint8_t flags_;
}; };
@@ -127,6 +134,9 @@ class WebSettingsService : public StatefulService<WebSettings> {
private: private:
HttpEndpoint<WebSettings> _httpEndpoint; HttpEndpoint<WebSettings> _httpEndpoint;
FSPersistence<WebSettings> _fsPersistence; FSPersistence<WebSettings> _fsPersistence;
AsyncCallbackJsonWebHandler _boardProfileHandler;
void board_profile(AsyncWebServerRequest * request, JsonVariant & json);
void onUpdate(); void onUpdate();
}; };

View File

@@ -18,12 +18,14 @@
#include "emsesp.h" #include "emsesp.h"
using namespace std::placeholders; // for `_1` etc
namespace emsesp { namespace emsesp {
WebStatusService::WebStatusService(AsyncWebServer * server, SecurityManager * securityManager) { WebStatusService::WebStatusService(AsyncWebServer * server, SecurityManager * securityManager) {
// rest endpoint for web page // rest endpoint for web page
server->on(EMSESP_STATUS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WebStatusService::webStatusService, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED)); server->on(EMSESP_STATUS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WebStatusService::webStatusService, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
WiFi.onEvent(std::bind(&WebStatusService::WiFiEvent, this, std::placeholders::_1, std::placeholders::_2)); WiFi.onEvent(std::bind(&WebStatusService::WiFiEvent, this, _1, _2));
} }
// handles both WiFI and Ethernet // handles both WiFI and Ethernet
@@ -39,10 +41,11 @@ void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
#endif #endif
EMSESP::system_.wifi_tweak(); EMSESP::system_.wifi_tweak();
EMSESP::system_.send_heartbeat(); EMSESP::system_.send_heartbeat();
EMSESP::system_.syslog_start();
break; break;
case SYSTEM_EVENT_ETH_START: case SYSTEM_EVENT_ETH_START:
EMSESP::logger().info(F("Ethernet Started")); EMSESP::logger().info(F("Ethernet initialized"));
ETH.setHostname(EMSESP::system_.hostname().c_str()); ETH.setHostname(EMSESP::system_.hostname().c_str());
break; break;
@@ -53,6 +56,7 @@ void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
EMSESP::logger().info(F("Ethernet Connected with IP=%s, speed %d Mbps"), ETH.localIP().toString().c_str(), ETH.linkSpeed()); EMSESP::logger().info(F("Ethernet Connected with IP=%s, speed %d Mbps"), ETH.localIP().toString().c_str(), ETH.linkSpeed());
#endif #endif
EMSESP::system_.send_heartbeat(); EMSESP::system_.send_heartbeat();
EMSESP::system_.syslog_start();
EMSESP::system_.ethernet_connected(true); EMSESP::system_.ethernet_connected(true);
} }
break; break;

View File

@@ -26,16 +26,8 @@ uuid::log::Logger Command::logger_{F_(command), uuid::log::Facility::DAEMON};
std::vector<Command::CmdFunction> Command::cmdfunctions_; std::vector<Command::CmdFunction> Command::cmdfunctions_;
/*
static emsesp::array<Command::CmdFunction> cmdfunctions_(90, 255, 16); // reserve space for 90 commands
emsesp::array<Command::CmdFunction> * Command::commands() {
return &cmdfunctions_;
}
*/
// calls a command // calls a command
// id may be used to represent a heating circuit for example // 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); auto cf = find_command(device_type, cmd);
@@ -93,25 +85,17 @@ bool Command::call(const uint8_t device_type, const char * cmd, const char * val
} }
// 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) { 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
if (find_command(device_type, uuid::read_flash_string(cmd).c_str()) != nullptr) { if (find_command(device_type, uuid::read_flash_string(cmd).c_str()) != nullptr) {
return; return;
} }
/* cmdfunctions_.emplace_back(device_type, flag, cmd, cb, nullptr);
CmdFunction cf;
cf.cmd_ = cmd;
cf.device_type_ = device_type;
cf.cmdfunction_json_ = nullptr; // empty
cf.cmdfunction_ = cb;
cmdfunctions_.push(cf);
*/
cmdfunctions_.emplace_back(device_type, cmd, cb, nullptr);
// see if we need to subscribe // see if we need to subscribe
if (Mqtt::enabled()) { if (Mqtt::enabled()) {
Mqtt::register_command(device_type, cmd, cb); Mqtt::register_command(device_type, cmd, cb, flag);
} }
} }
@@ -122,16 +106,7 @@ void Command::add_with_json(const uint8_t device_type, const __FlashStringHelper
return; return;
} }
/* cmdfunctions_.emplace_back(device_type, MqttSubFlag::FLAG_NOSUB, cmd, nullptr, cb); // add command
CmdFunction cf;
cf.cmd_ = cmd;
cf.device_type_ = device_type;
cf.cmdfunction_json_ = cb;
cf.cmdfunction_ = nullptr; // empty
cmdfunctions_.push(cf);
*/
cmdfunctions_.emplace_back(device_type, cmd, nullptr, cb); // add command
} }
// see if a command exists for that device type // see if a command exists for that device type

View File

@@ -26,8 +26,6 @@
#include <vector> #include <vector>
#include <functional> #include <functional>
// #include "containers.h"
#include "console.h" #include "console.h"
#include <uuid/log.h> #include <uuid/log.h>
@@ -43,12 +41,14 @@ class Command {
public: public:
struct CmdFunction { struct CmdFunction {
uint8_t device_type_; // DeviceType:: uint8_t device_type_; // DeviceType::
uint8_t flag_;
const __FlashStringHelper * cmd_; const __FlashStringHelper * cmd_;
cmdfunction_p cmdfunction_; cmdfunction_p cmdfunction_;
cmdfunction_json_p cmdfunction_json_; cmdfunction_json_p cmdfunction_json_;
CmdFunction(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cmdfunction, cmdfunction_json_p cmdfunction_json) CmdFunction(const uint8_t device_type, const uint8_t flag, const __FlashStringHelper * cmd, cmdfunction_p cmdfunction, cmdfunction_json_p cmdfunction_json)
: device_type_(device_type) : device_type_(device_type)
, flag_(flag)
, cmd_(cmd) , cmd_(cmd)
, cmdfunction_(cmdfunction) , cmdfunction_(cmdfunction)
, cmdfunction_json_(cmdfunction_json) { , cmdfunction_json_(cmdfunction_json) {
@@ -59,11 +59,9 @@ class Command {
return cmdfunctions_; return cmdfunctions_;
} }
// static emsesp::array<Command::CmdFunction> * commands();
static bool call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id, JsonObject & json); static bool call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id, JsonObject & json);
static bool call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id); static bool call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id = 0);
static void add(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb); static void add(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb, uint8_t flag = 0);
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);

View File

@@ -65,7 +65,7 @@ void EMSESPShell::display_banner() {
println(); println();
printfln(F("┌──────────────────────────────────────┐")); printfln(F("┌──────────────────────────────────────┐"));
printfln(F("│ %sEMS-ESP version %-10s%s │"), COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_BOLD_OFF); printfln(F("│ %sEMS-ESP version %-10s%s │"), COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_BOLD_OFF);
printfln(F("│ %s%shttps://github.com/emsesp/EMS-ESP%s "), COLOR_BRIGHT_GREEN, COLOR_UNDERLINE, COLOR_RESET); printfln(F("│ %s%shttps://github.com/emsesp/EMS-ESP32%s │"), COLOR_BRIGHT_GREEN, COLOR_UNDERLINE, COLOR_RESET);
printfln(F("│ │")); printfln(F("│ │"));
printfln(F("│ type %shelp%s to show available commands │"), COLOR_UNDERLINE, COLOR_RESET); printfln(F("│ type %shelp%s to show available commands │"), COLOR_UNDERLINE, COLOR_RESET);
printfln(F("└──────────────────────────────────────┘")); printfln(F("└──────────────────────────────────────┘"));
@@ -300,7 +300,7 @@ void EMSESPShell::add_console_commands() {
// if logging is off, the watch won't show anything, show force it back to NOTICE // if logging is off, the watch won't show anything, show force it back to NOTICE
if (shell.log_level() < Level::NOTICE) { if (shell.log_level() < Level::NOTICE) {
shell.log_level(Level::NOTICE); shell.log_level(Level::NOTICE);
shell.printfln(F("Force log level to notice")); shell.printfln(F("Setting log level to Notice"));
} }
if (watch == EMSESP::WATCH_ON) { if (watch == EMSESP::WATCH_ON) {
@@ -615,7 +615,7 @@ std::string EMSESPStreamConsole::console_name() {
// Log order is off, err, warning, notice, info, debug, trace, all // Log order is off, err, warning, notice, info, debug, trace, all
void Console::start() { void Console::start() {
shell = std::make_shared<EMSESPStreamConsole>(Serial, true); shell = std::make_shared<EMSESPStreamConsole>(Serial, true);
shell->maximum_log_messages(100); // default is 50 shell->maximum_log_messages(100); // default was 50
shell->start(); shell->start();
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
@@ -623,8 +623,7 @@ void Console::start() {
#endif #endif
#if defined(EMSESP_STANDALONE) #if defined(EMSESP_STANDALONE)
// always start in su/admin mode when running tests shell->add_flags(CommandFlags::ADMIN); // always start in su/admin mode when running tests
shell->add_flags(CommandFlags::ADMIN);
#endif #endif
// start the telnet service // start the telnet service

View File

@@ -35,14 +35,14 @@ uuid::log::Logger DallasSensor::logger_{F_(dallassensor), uuid::log::Facility::D
void DallasSensor::start() { void DallasSensor::start() {
reload(); reload();
#ifndef EMSESP_STANDALONE // disabled if dallas gpio is 0
if (dallas_gpio_) { if (dallas_gpio_) {
#ifndef EMSESP_STANDALONE
bus_.begin(dallas_gpio_); bus_.begin(dallas_gpio_);
}
#endif #endif
// API call // API call
Command::add_with_json(EMSdevice::DeviceType::DALLASSENSOR, F_(info), [&](const char * value, const int8_t id, JsonObject & json) { return command_info(value, id, json); }); Command::add_with_json(EMSdevice::DeviceType::DALLASSENSOR, F_(info), [&](const char * value, const int8_t id, JsonObject & json) { return command_info(value, id, json); });
}
} }
// load the MQTT settings // load the MQTT settings
@@ -59,6 +59,10 @@ void DallasSensor::reload() {
} }
void DallasSensor::loop() { void DallasSensor::loop() {
if (!dallas_gpio_) {
return; // dallas gpio is 0 (disabled)
}
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
uint32_t time_now = uuid::get_uptime(); uint32_t time_now = uuid::get_uptime();
@@ -385,7 +389,7 @@ void DallasSensor::publish_values(const bool force) {
JsonArray ids = dev.createNestedArray("ids"); JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp-dallas"); // Different ids as the other portions of the EMS-ESP ids.add("ems-esp-dallas"); // Different ids as the other portions of the EMS-ESP
char topic[100]; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
if (dallas_format == Mqtt::Dallas_Format::SENSORID) { if (dallas_format == Mqtt::Dallas_Format::SENSORID) {
// 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();

View File

@@ -28,20 +28,20 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
LOG_DEBUG(F("Adding new Boiler with device ID 0x%02X"), device_id); LOG_DEBUG(F("Adding new Boiler with device ID 0x%02X"), device_id);
// cascaded heatingsources, add some values with new tags later // cascaded heatingsources, only some values per individual heatsource (hs)
if (device_id != EMSdevice::EMS_DEVICE_ID_BOILER) { if (device_id != EMSdevice::EMS_DEVICE_ID_BOILER) {
// uint8_t hs = device_id - EMSdevice::EMS_DEVICE_ID_BOILER_1; // heating source number uint8_t hs = device_id - EMSdevice::EMS_DEVICE_ID_BOILER_1; // heating source id, count from 0
// Runtime of each heatingsource in 6DC, ff // Runtime of each heatingsource in 0x06DC, ff
// register_telegram_type(0x6DC + hs, F("CascadeMessage"), false, [&](std::shared_ptr<const Telegram> t) { process_CascadeMessage(t); }); 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);
// selBurnpower in D2 and E4 // selBurnpower in D2 and E4
// register_telegram_type(0xD2, F("CascadePowerMessage"), false, [&](std::shared_ptr<const Telegram> t) { process_CascadePowerMessage(t); }); // register_telegram_type(0xD2, F("CascadePowerMessage"), false, MAKE_PF_CB(process_CascadePowerMessage));
// idividual Flowtemps and powervalues for each heatingsource in E4 // individual Flowtemps and powervalues for each heatingsource in E4
// register_telegram_type(0xE4, F("UBAMonitorFastPlus"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAMonitorFastPlus(t); }); 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, F("selFlowTemp"), F("selected flow temperature"), 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, F("selBurnPow"), F("burner selected max power"), 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), F("curFlowTemp"), F("current flow temperature"), 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, F("curBurnPow"), F("burner current power"), DeviceValueUOM::PERCENT);
return; return;
} }
// register values for master boiler/cascade module // register values for master boiler/cascade module
@@ -49,59 +49,54 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
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...
register_telegram_type(0x10, F("UBAErrorMessage1"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAErrorMessage(t); }); register_telegram_type(0x10, F("UBAErrorMessage1"), false, MAKE_PF_CB(process_UBAErrorMessage));
register_telegram_type(0x11, F("UBAErrorMessage2"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAErrorMessage(t); }); register_telegram_type(0x11, F("UBAErrorMessage2"), false, MAKE_PF_CB(process_UBAErrorMessage));
register_telegram_type(0x14, F("UBATotalUptime"), true, [&](std::shared_ptr<const Telegram> t) { process_UBATotalUptime(t); }); register_telegram_type(0x14, F("UBATotalUptime"), true, MAKE_PF_CB(process_UBATotalUptime));
register_telegram_type(0x15, F("UBAMaintenanceData"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAMaintenanceData(t); }); register_telegram_type(0x15, F("UBAMaintenanceData"), false, MAKE_PF_CB(process_UBAMaintenanceData));
register_telegram_type(0x16, F("UBAParameters"), true, [&](std::shared_ptr<const Telegram> t) { process_UBAParameters(t); }); register_telegram_type(0x16, F("UBAParameters"), true, MAKE_PF_CB(process_UBAParameters));
register_telegram_type(0x18, F("UBAMonitorFast"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAMonitorFast(t); }); register_telegram_type(0x18, F("UBAMonitorFast"), false, MAKE_PF_CB(process_UBAMonitorFast));
register_telegram_type(0x19, F("UBAMonitorSlow"), true, [&](std::shared_ptr<const Telegram> t) { process_UBAMonitorSlow(t); }); register_telegram_type(0x19, F("UBAMonitorSlow"), true, MAKE_PF_CB(process_UBAMonitorSlow));
register_telegram_type(0x1A, F("UBASetPoints"), false, [&](std::shared_ptr<const Telegram> t) { process_UBASetPoints(t); }); register_telegram_type(0x1A, F("UBASetPoints"), false, MAKE_PF_CB(process_UBASetPoints));
register_telegram_type(0x1C, F("UBAMaintenanceStatus"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAMaintenanceStatus(t); }); register_telegram_type(0x1C, F("UBAMaintenanceStatus"), false, MAKE_PF_CB(process_UBAMaintenanceStatus));
register_telegram_type(0x26, F("UBASettingsWW"), true, [&](std::shared_ptr<const Telegram> t) { process_UBASettingsWW(t); }); register_telegram_type(0x26, F("UBASettingsWW"), true, MAKE_PF_CB(process_UBASettingsWW));
register_telegram_type(0x2A, F("MC10Status"), false, [&](std::shared_ptr<const Telegram> t) { process_MC10Status(t); }); register_telegram_type(0x2A, F("MC10Status"), false, MAKE_PF_CB(process_MC10Status));
register_telegram_type(0x33, F("UBAParameterWW"), true, [&](std::shared_ptr<const Telegram> t) { process_UBAParameterWW(t); }); register_telegram_type(0x33, F("UBAParameterWW"), true, MAKE_PF_CB(process_UBAParameterWW));
register_telegram_type(0x34, F("UBAMonitorWW"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAMonitorWW(t); }); register_telegram_type(0x34, F("UBAMonitorWW"), false, MAKE_PF_CB(process_UBAMonitorWW));
register_telegram_type(0x35, F("UBAFlags"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAFlags(t); }); register_telegram_type(0x35, F("UBAFlags"), false, MAKE_PF_CB(process_UBAFlags));
register_telegram_type(0xD1, F("UBAOutdoorTemp"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAOutdoorTemp(t); }); register_telegram_type(0xD1, F("UBAOutdoorTemp"), false, MAKE_PF_CB(process_UBAOutdoorTemp));
register_telegram_type(0xE3, F("UBAMonitorSlowPlus"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAMonitorSlowPlus2(t); }); register_telegram_type(0xE3, F("UBAMonitorSlowPlus"), false, MAKE_PF_CB(process_UBAMonitorSlowPlus2));
register_telegram_type(0xE4, F("UBAMonitorFastPlus"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAMonitorFastPlus(t); }); register_telegram_type(0xE4, F("UBAMonitorFastPlus"), false, MAKE_PF_CB(process_UBAMonitorFastPlus));
register_telegram_type(0xE5, F("UBAMonitorSlowPlus"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAMonitorSlowPlus(t); }); register_telegram_type(0xE5, F("UBAMonitorSlowPlus"), false, MAKE_PF_CB(process_UBAMonitorSlowPlus));
register_telegram_type(0xE6, F("UBAParametersPlus"), true, [&](std::shared_ptr<const Telegram> t) { process_UBAParametersPlus(t); }); register_telegram_type(0xE6, F("UBAParametersPlus"), true, MAKE_PF_CB(process_UBAParametersPlus));
register_telegram_type(0xE9, F("UBADHWStatus"), false, [&](std::shared_ptr<const Telegram> t) { process_UBADHWStatus(t); }); register_telegram_type(0xE9, F("UBADHWStatus"), false, MAKE_PF_CB(process_UBADHWStatus));
register_telegram_type(0xEA, F("UBAParameterWWPlus"), true, [&](std::shared_ptr<const Telegram> t) { process_UBAParameterWWPlus(t); }); register_telegram_type(0xEA, F("UBAParameterWWPlus"), true, MAKE_PF_CB(process_UBAParameterWWPlus));
register_telegram_type(0x494, F("UBAEnergySupplied"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAEnergySupplied(t); }); register_telegram_type(0x494, F("UBAEnergySupplied"), false, MAKE_PF_CB(process_UBAEnergySupplied));
register_telegram_type(0x495, F("UBAInformation"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAInformation(t); }); register_telegram_type(0x495, F("UBAInformation"), false, MAKE_PF_CB(process_UBAInformation));
EMSESP::send_read_request(0x10, device_id); // read last errorcode on start (only published on errors)
EMSESP::send_read_request(0x11, device_id); // read last errorcode on start (only published on errors)
EMSESP::send_read_request(0x15, device_id); // read maintenace data on start (only published on change)
EMSESP::send_read_request(0x1C, device_id); // read maintenace status on start (only published on change)
// MQTT commands for boiler topic // MQTT commands for boiler topic
register_mqtt_cmd(F("comfort"), [&](const char * value, const int8_t id) { return set_warmwater_mode(value, id); }); register_mqtt_cmd(F("comfort"), MAKE_CF_CB(set_warmwater_mode));
register_mqtt_cmd(F("wwactivated"), [&](const char * value, const int8_t id) { return set_warmwater_activated(value, id); }); register_mqtt_cmd(F("wwactivated"), MAKE_CF_CB(set_warmwater_activated));
register_mqtt_cmd(F("wwtapactivated"), [&](const char * value, const int8_t id) { return set_tapwarmwater_activated(value, id); }); register_mqtt_cmd(F("wwtapactivated"), MAKE_CF_CB(set_tapwarmwater_activated));
register_mqtt_cmd(F("wwflowtempoffset"), [&](const char * value, const int8_t id) { return set_wWFlowTempOffset(value, id); }); register_mqtt_cmd(F("wwflowtempoffset"), MAKE_CF_CB(set_wWFlowTempOffset));
register_mqtt_cmd(F("wwmaxpower"), [&](const char * value, const int8_t id) { return set_warmwater_maxpower(value, id); }); register_mqtt_cmd(F("wwmaxpower"), MAKE_CF_CB(set_warmwater_maxpower));
register_mqtt_cmd(F("wwonetime"), [&](const char * value, const int8_t id) { return set_warmwater_onetime(value, id); }); register_mqtt_cmd(F("wwonetime"), MAKE_CF_CB(set_warmwater_onetime));
register_mqtt_cmd(F("wwcircpump"), [&](const char * value, const int8_t id) { return set_warmwater_circulation_pump(value, id); }); register_mqtt_cmd(F("wwcircpump"), MAKE_CF_CB(set_warmwater_circulation_pump));
register_mqtt_cmd(F("wwcirculation"), [&](const char * value, const int8_t id) { return set_warmwater_circulation(value, id); }); register_mqtt_cmd(F("wwcirculation"), MAKE_CF_CB(set_warmwater_circulation));
register_mqtt_cmd(F("wwcircmode"), [&](const char * value, const int8_t id) { return set_warmwater_circulation_mode(value, id); }); register_mqtt_cmd(F("wwcircmode"), MAKE_CF_CB(set_warmwater_circulation_mode));
register_mqtt_cmd(F("flowtemp"), [&](const char * value, const int8_t id) { return set_flow_temp(value, id); }); register_mqtt_cmd(F("flowtemp"), MAKE_CF_CB(set_flow_temp));
register_mqtt_cmd(F("wwsettemp"), [&](const char * value, const int8_t id) { return set_warmwater_temp(value, id); }); register_mqtt_cmd(F("wwsettemp"), MAKE_CF_CB(set_warmwater_temp));
register_mqtt_cmd(F("heatingactivated"), [&](const char * value, const int8_t id) { return set_heating_activated(value, id); }); register_mqtt_cmd(F("heatingactivated"), MAKE_CF_CB(set_heating_activated));
register_mqtt_cmd(F("heatingtemp"), [&](const char * value, const int8_t id) { return set_heating_temp(value, id); }); register_mqtt_cmd(F("heatingtemp"), MAKE_CF_CB(set_heating_temp));
register_mqtt_cmd(F("burnmaxpower"), [&](const char * value, const int8_t id) { return set_max_power(value, id); }); register_mqtt_cmd(F("burnmaxpower"), MAKE_CF_CB(set_max_power));
register_mqtt_cmd(F("burnminpower"), [&](const char * value, const int8_t id) { return set_min_power(value, id); }); register_mqtt_cmd(F("burnminpower"), MAKE_CF_CB(set_min_power));
register_mqtt_cmd(F("boilhyston"), [&](const char * value, const int8_t id) { return set_hyst_on(value, id); }); register_mqtt_cmd(F("boilhyston"), MAKE_CF_CB(set_hyst_on));
register_mqtt_cmd(F("boilhystoff"), [&](const char * value, const int8_t id) { return set_hyst_off(value, id); }); register_mqtt_cmd(F("boilhystoff"), MAKE_CF_CB(set_hyst_off));
register_mqtt_cmd(F("burnperiod"), [&](const char * value, const int8_t id) { return set_burn_period(value, id); }); register_mqtt_cmd(F("burnperiod"), MAKE_CF_CB(set_burn_period));
register_mqtt_cmd(F("pumpdelay"), [&](const char * value, const int8_t id) { return set_pump_delay(value, id); }); register_mqtt_cmd(F("pumpdelay"), MAKE_CF_CB(set_pump_delay));
register_mqtt_cmd(F("maintenance"), [&](const char * value, const int8_t id) { return set_maintenance(value, id); }); register_mqtt_cmd(F("maintenance"), MAKE_CF_CB(set_maintenance));
register_mqtt_cmd(F("pumpmodmax"), [&](const char * value, const int8_t id) { return set_max_pump(value, id); }); register_mqtt_cmd(F("pumpmodmax"), MAKE_CF_CB(set_max_pump));
register_mqtt_cmd(F("pumpmodmin"), [&](const char * value, const int8_t id) { return set_min_pump(value, id); }); register_mqtt_cmd(F("pumpmodmin"), MAKE_CF_CB(set_min_pump));
// register_mqtt_cmd(F("reset"), [&](const char * value, const int8_t id) { return set_reset(value, id); }); register_mqtt_cmd(F("reset"), MAKE_CF_CB(set_reset));
// add values // add values
reserve_device_values(50); reserve_device_values(50);
@@ -178,12 +173,12 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_device_value(TAG_BOILER_DATA_WW, &wWSetTemp_, DeviceValueType::UINT, nullptr, F("wWSetTemp"), F("set temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_BOILER_DATA_WW, &wWSetTemp_, DeviceValueType::UINT, nullptr, F("wWSetTemp"), F("set temperature"), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA_WW, &wWType_, DeviceValueType::ENUM, FL_(enum_flow), F("wWType"), F("type")); register_device_value(TAG_BOILER_DATA_WW, &wWType_, DeviceValueType::ENUM, FL_(enum_flow), F("wWType"), F("type"));
register_device_value(TAG_BOILER_DATA_WW, &wWComfort_, DeviceValueType::ENUM, FL_(enum_comfort), F("wWComfort"), F("comfort")); register_device_value(TAG_BOILER_DATA_WW, &wWComfort_, DeviceValueType::ENUM, FL_(enum_comfort), F("wWComfort"), F("comfort"));
register_device_value(TAG_BOILER_DATA_WW, &wWFlowTempOffset_, DeviceValueType::UINT, nullptr, F("wWFlowTempOffset"), F("flow offset temperature")); register_device_value(TAG_BOILER_DATA_WW, &wWFlowTempOffset_, DeviceValueType::UINT, nullptr, F("wWFlowTempOffset"), F("flow temperature offset"));
register_device_value(TAG_BOILER_DATA_WW, &wWMaxPower_, DeviceValueType::UINT, nullptr, F("wWMaxPower"), F("max power"), DeviceValueUOM::PERCENT); register_device_value(TAG_BOILER_DATA_WW, &wWMaxPower_, DeviceValueType::UINT, nullptr, F("wWMaxPower"), F("max power"), DeviceValueUOM::PERCENT);
register_device_value(TAG_BOILER_DATA_WW, &wWCircPump_, DeviceValueType::BOOL, nullptr, F("wWCircPump"), F("circulation pump available")); register_device_value(TAG_BOILER_DATA_WW, &wWCircPump_, DeviceValueType::BOOL, nullptr, F("wWCircPump"), F("circulation pump available"));
register_device_value(TAG_BOILER_DATA_WW, &wWChargeType_, DeviceValueType::BOOL, FL_(enum_charge), F("wWChargeType"), F("charging type")); register_device_value(TAG_BOILER_DATA_WW, &wWChargeType_, DeviceValueType::BOOL, FL_(enum_charge), F("wWChargeType"), F("charging type"));
register_device_value(TAG_BOILER_DATA_WW, &wWDisinfectionTemp_, DeviceValueType::UINT, nullptr, F("wWDisinfectionTemp"), F("disinfection temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_BOILER_DATA_WW, &wWDisinfectionTemp_, DeviceValueType::UINT, nullptr, F("wWDisinfectionTemp"), F("disinfection temperature"), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA_WW, &wWCircPumpMode_, DeviceValueType::ENUM, FL_(enum_freq), F("wWCircPumpMode"), F("circulation pump freq")); register_device_value(TAG_BOILER_DATA_WW, &wWCircMode_, DeviceValueType::ENUM, FL_(enum_freq), F("wWCircMode"), F("circulation pump freq"));
register_device_value(TAG_BOILER_DATA_WW, &wWCirc_, DeviceValueType::BOOL, nullptr, F("wWCirc"), F("circulation active")); register_device_value(TAG_BOILER_DATA_WW, &wWCirc_, DeviceValueType::BOOL, nullptr, F("wWCirc"), F("circulation active"));
register_device_value(TAG_BOILER_DATA_WW, &wWCurTemp_, DeviceValueType::USHORT, FL_(div10), F("wWCurTemp"), F("current intern temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_BOILER_DATA_WW, &wWCurTemp_, DeviceValueType::USHORT, FL_(div10), F("wWCurTemp"), F("current intern temperature"), 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_BOILER_DATA_WW, &wWCurTemp2_, DeviceValueType::USHORT, FL_(div10), F("wWCurTemp2"), F("current extern temperature"), DeviceValueUOM::DEGREES);
@@ -204,6 +199,12 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_device_value(TAG_BOILER_DATA_WW, &wWStarts_, DeviceValueType::ULONG, nullptr, F("wWStarts"), F("# starts")); register_device_value(TAG_BOILER_DATA_WW, &wWStarts_, DeviceValueType::ULONG, nullptr, F("wWStarts"), F("# starts"));
register_device_value(TAG_BOILER_DATA_WW, &wWStarts2_, DeviceValueType::ULONG, nullptr, F("wWStarts2"), F("# control starts")); register_device_value(TAG_BOILER_DATA_WW, &wWStarts2_, DeviceValueType::ULONG, nullptr, F("wWStarts2"), F("# control starts"));
register_device_value(TAG_BOILER_DATA_WW, &wWWorkM_, DeviceValueType::TIME, nullptr, F("wWWorkM"), F("active time"), DeviceValueUOM::MINUTES); register_device_value(TAG_BOILER_DATA_WW, &wWWorkM_, DeviceValueType::TIME, nullptr, F("wWWorkM"), F("active time"), DeviceValueUOM::MINUTES);
// fetch some initial data
EMSESP::send_read_request(0x10, device_id); // read last errorcode on start (only published on errors)
EMSESP::send_read_request(0x11, device_id); // read last errorcode on start (only published on errors)
EMSESP::send_read_request(0x15, device_id); // read maintenace data on start (only published on change)
EMSESP::send_read_request(0x1C, device_id); // read maintenace status on start (only published on change)
} }
// publish HA config // publish HA config
@@ -227,8 +228,7 @@ bool Boiler::publish_ha_config() {
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("homeassistant/sensor/%s/boiler/config"), Mqtt::base().c_str());
Mqtt::publish_ha(topic, Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
doc.as<JsonObject>()); // publish the config payload with retain flag
return true; return true;
} }
@@ -277,7 +277,7 @@ void Boiler::check_active(const bool force) {
void Boiler::process_UBAParameterWW(std::shared_ptr<const Telegram> telegram) { void Boiler::process_UBAParameterWW(std::shared_ptr<const Telegram> telegram) {
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(wWCircPump_, 6)); // 0xFF means on has_update(telegram->read_value(wWCircPump_, 6)); // 0xFF means on
has_update(telegram->read_value(wWCircPumpMode_, 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(wWChargeType_, 10)); // 0 = charge pump, 0xff = 3-way valve
has_update(telegram->read_value(wWSelTemp_, 2)); has_update(telegram->read_value(wWSelTemp_, 2));
has_update(telegram->read_value(wWDisinfectionTemp_, 8)); has_update(telegram->read_value(wWDisinfectionTemp_, 8));
@@ -506,7 +506,7 @@ void Boiler::process_UBAParametersPlus(std::shared_ptr<const Telegram> telegram)
void Boiler::process_UBAParameterWWPlus(std::shared_ptr<const Telegram> telegram) { void Boiler::process_UBAParameterWWPlus(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(wWActivated_, 5)); // 0x01 means on has_update(telegram->read_value(wWActivated_, 5)); // 0x01 means on
has_update(telegram->read_value(wWCircPump_, 10)); // 0x01 means yes has_update(telegram->read_value(wWCircPump_, 10)); // 0x01 means yes
has_update(telegram->read_value(wWCircPumpMode_, 11)); // 1=1x3min... 6=6x3min, 7=continuous has_update(telegram->read_value(wWCircMode_, 11)); // 1=1x3min... 6=6x3min, 7=continuous
// has_update(telegram->read_value(wWDisinfectTemp_, 12)); // settings, status in E9 // has_update(telegram->read_value(wWDisinfectTemp_, 12)); // settings, status in E9
// has_update(telegram->read_value(wWSelTemp_, 6)); // settings, status in E9 // has_update(telegram->read_value(wWSelTemp_, 6)); // settings, status in E9
} }
@@ -601,19 +601,17 @@ void Boiler::process_UBASetPoints(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(wWSetPumpPower_, 2)); // ww pump speed/power? has_update(telegram->read_value(wWSetPumpPower_, 2)); // ww pump speed/power?
} }
// 0x6DC, ff for cascaded heatsources (hs)
void Boiler::process_CascadeMessage(std::shared_ptr<const Telegram> telegram) {
// uint8_t hsActivated;
// has_update(telegram->read_value(hsActivated, 0));
telegram->read_value(burnWorkMin_, 3); // this is in seconds
burnWorkMin_ /= 60;
}
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wunused-parameter"
// 0x6DC, ff for cascaded heatsources (hs)
// not implemented yet, see #739
void Boiler::process_CascadeMessage(std::shared_ptr<const Telegram> telegram) {
uint8_t hs = telegram->dest - EMSdevice::EMS_DEVICE_ID_BOILER_1;
// uint8_t hsActivated;
// uint32_t hsRuntime;
// has_update(telegram->read_value(hsActivated, 0));
// has_update(telegram->read_value(hsRuntime, 3));
}
// 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) {
} }
@@ -1102,6 +1100,7 @@ bool Boiler::set_warmwater_circulation_mode(const char * value, const int8_t id)
return true; return true;
} }
// Reset command // Reset command
// 0 & 1 Reset-Mode (Manuel, others) // 0 & 1 Reset-Mode (Manuel, others)
// 8 reset maintenance message Hxx // 8 reset maintenance message Hxx

View File

@@ -57,7 +57,7 @@ class Boiler : public EMSdevice {
uint8_t wWCircPump_; // Warm Water circulation pump available uint8_t wWCircPump_; // Warm Water circulation pump available
uint8_t wWChargeType_; // Warm Water charge type (pump or 3-way-valve) uint8_t wWChargeType_; // Warm Water charge type (pump or 3-way-valve)
uint8_t wWDisinfectionTemp_; // Warm Water disinfection temperature to prevent infection uint8_t wWDisinfectionTemp_; // Warm Water disinfection temperature to prevent infection
uint8_t wWCircPumpMode_; // Warm Water circulation pump mode uint8_t wWCircMode_; // Warm Water circulation pump mode
uint8_t wWCirc_; // Circulation on/off uint8_t wWCirc_; // Circulation on/off
uint16_t wWCurTemp_; // Warm Water current temperature uint16_t wWCurTemp_; // Warm Water current temperature
uint16_t wWCurTemp2_; // Warm Water current temperature storage uint16_t wWCurTemp2_; // Warm Water current temperature storage

View File

@@ -29,8 +29,8 @@ Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, c
LOG_DEBUG(F("Adding new Heat Pump module with device ID 0x%02X"), device_id); LOG_DEBUG(F("Adding new Heat Pump module with device ID 0x%02X"), device_id);
// telegram handlers // telegram handlers
register_telegram_type(0x042B, F("HP1"), true, [&](std::shared_ptr<const Telegram> t) { process_HPMonitor1(t); }); register_telegram_type(0x042B, F("HP1"), true, MAKE_PF_CB(process_HPMonitor1));
register_telegram_type(0x047B, F("HP2"), true, [&](std::shared_ptr<const Telegram> t) { process_HPMonitor2(t); }); 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, F("id"), nullptr); // empty full name to prevent being shown in web or console

View File

@@ -31,23 +31,25 @@ Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
if (flags == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) { if (flags == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) {
if (device_id <= 0x27) { if (device_id <= 0x27) {
// telegram handlers 0x20 - 0x27 for HC // telegram handlers 0x20 - 0x27 for HC
register_telegram_type(device_id - 0x20 + 0x02D7, F("MMPLUSStatusMessage_HC"), true, [&](std::shared_ptr<const Telegram> t) { process_MMPLUSStatusMessage_HC(t); }); register_telegram_type(device_id - 0x20 + 0x02D7, F("MMPLUSStatusMessage_HC"), true, MAKE_PF_CB(process_MMPLUSStatusMessage_HC));
} else { } else {
// telegram handlers for warm water/DHW 0x28, 0x29 // telegram handlers for warm water/DHW 0x28, 0x29
register_telegram_type(device_id - 0x28 + 0x0331, F("MMPLUSStatusMessage_WWC"), true, [&](std::shared_ptr<const Telegram> t) { process_MMPLUSStatusMessage_WWC(t); }); register_telegram_type(device_id - 0x28 + 0x0331, F("MMPLUSStatusMessage_WWC"), true, MAKE_PF_CB(process_MMPLUSStatusMessage_WWC));
} }
} }
// EMS 1.0 // EMS 1.0
if (flags == EMSdevice::EMS_DEVICE_FLAG_MM10) { if (flags == EMSdevice::EMS_DEVICE_FLAG_MM10) {
register_telegram_type(0x00AA, F("MMConfigMessage"), false, [&](std::shared_ptr<const Telegram> t) { process_MMConfigMessage(t); }); register_telegram_type(0x00AA, F("MMConfigMessage"), false, MAKE_PF_CB(process_MMConfigMessage));
register_telegram_type(0x00AB, F("MMStatusMessage"), true, [&](std::shared_ptr<const Telegram> t) { process_MMStatusMessage(t); }); register_telegram_type(0x00AB, F("MMStatusMessage"), true, MAKE_PF_CB(process_MMStatusMessage));
register_telegram_type(0x00AC, F("MMSetMessage"), false, [&](std::shared_ptr<const Telegram> t) { process_MMSetMessage(t); }); register_telegram_type(0x00AC, F("MMSetMessage"), false, MAKE_PF_CB(process_MMSetMessage));
} }
// HT3 // HT3
if (flags == EMSdevice::EMS_DEVICE_FLAG_IPM) { if (flags == EMSdevice::EMS_DEVICE_FLAG_IPM) {
register_telegram_type(0x010C, F("IPMSetMessage"), false, [&](std::shared_ptr<const Telegram> t) { process_IPMStatusMessage(t); }); register_telegram_type(0x010C, F("IPMStatusMessage"), false, MAKE_PF_CB(process_IPMStatusMessage));
register_telegram_type(0x001E, F("IPMTempMessage"), false, MAKE_PF_CB(process_IPMTempMessage));
// register_telegram_type(0x0023, F("IPMSetMessage"), false, MAKE_PF_CB(process_IPMSetMessage));
} }
// register the device values and set hc_ and type_ // register the device values and set hc_ and type_
@@ -60,6 +62,7 @@ Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
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), F("flowTempHc"), F("flow temperature in assigned hc (TC1)"), 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, F("pumpStatus"), F("pump status in assigned hc (PC1)"), 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, F("valveStatus"), F("mixing valve actuator in assigned hc (VC1)"), DeviceValueUOM::PERCENT);
register_device_value(tag, &flowTempVf_, DeviceValueType::USHORT, FL_(div10), F("flowTempVf"), F("flow temperature in header (T0/Vf)"), DeviceValueUOM::DEGREES);
} else { } else {
type_ = Type::WWC; type_ = Type::WWC;
hc_ = device_id - 0x28 + 1; hc_ = device_id - 0x28 + 1;
@@ -135,7 +138,7 @@ void Mixer::process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> tele
has_update(telegram->read_value(status_, 11)); // temp status has_update(telegram->read_value(status_, 11)); // temp status
} }
// Mixer IMP - 0x010C // Mixer IPM - 0x010C
// e.g. A0 00 FF 00 00 0C 01 00 00 00 00 00 54 // e.g. A0 00 FF 00 00 0C 01 00 00 00 00 00 54
// A1 00 FF 00 00 0C 02 04 00 01 1D 00 82 // A1 00 FF 00 00 0C 02 04 00 01 1D 00 82
void Mixer::process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram) { void Mixer::process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram) {
@@ -156,6 +159,13 @@ void Mixer::process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(flowSetTemp_, 5)); // flowSettemp is also in unmixed circuits, see #711 has_update(telegram->read_value(flowSetTemp_, 5)); // flowSettemp is also in unmixed circuits, see #711
} }
// Mixer IPM - 0x001E Temperature Message in unmixed circuits
// 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) {
has_update(telegram->read_value(flowTempVf_, 0)); // TC1, is * 10
}
// Mixer on a MM10 - 0xAB // Mixer on a MM10 - 0xAB
// e.g. Mixer Module -> All, type 0xAB, telegram: 21 00 AB 00 2D 01 BE 64 04 01 00 (CRC=15) #data=7 // e.g. Mixer Module -> All, type 0xAB, telegram: 21 00 AB 00 2D 01 BE 64 04 01 00 (CRC=15) #data=7
// see also https://github.com/emsesp/EMS-ESP/issues/386 // see also https://github.com/emsesp/EMS-ESP/issues/386
@@ -187,6 +197,12 @@ void Mixer::process_MMSetMessage(std::shared_ptr<const Telegram> telegram) {
// pos 1: position in % // pos 1: position in %
} }
// Thermostat(0x10) -> Mixer(0x21), ?(0x23), data: 1A 64 00 90 21 23 00 1A 64 00 89
void Mixer::process_IPMSetMessage(std::shared_ptr<const Telegram> telegram) {
// pos 0: flowtemp setpoint 1A = 26°C
// pos 1: position in %?
}
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
} // namespace emsesp } // namespace emsesp

View File

@@ -35,6 +35,8 @@ class Mixer : public EMSdevice {
void process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram); void process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram);
void process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram); void process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram);
void process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram); void process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram);
void process_IPMTempMessage(std::shared_ptr<const Telegram> telegram);
void process_IPMSetMessage(std::shared_ptr<const Telegram> telegram);
void process_MMStatusMessage(std::shared_ptr<const Telegram> telegram); void process_MMStatusMessage(std::shared_ptr<const Telegram> telegram);
void process_MMConfigMessage(std::shared_ptr<const Telegram> telegram); void process_MMConfigMessage(std::shared_ptr<const Telegram> telegram);
void process_MMSetMessage(std::shared_ptr<const Telegram> telegram); void process_MMSetMessage(std::shared_ptr<const Telegram> telegram);
@@ -47,6 +49,7 @@ class Mixer : public EMSdevice {
private: private:
uint16_t flowTempHc_; uint16_t flowTempHc_;
uint16_t flowTempVf_;
uint8_t pumpStatus_; uint8_t pumpStatus_;
int8_t status_; int8_t status_;
uint8_t flowSetTemp_; uint8_t flowSetTemp_;

View File

@@ -30,34 +30,34 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
// telegram handlers // telegram handlers
if (flags == EMSdevice::EMS_DEVICE_FLAG_SM10) { if (flags == EMSdevice::EMS_DEVICE_FLAG_SM10) {
register_telegram_type(0x0097, F("SM10Monitor"), true, [&](std::shared_ptr<const Telegram> t) { process_SM10Monitor(t); }); register_telegram_type(0x0097, F("SM10Monitor"), true, MAKE_PF_CB(process_SM10Monitor));
} }
if (flags == EMSdevice::EMS_DEVICE_FLAG_SM100) { if (flags == EMSdevice::EMS_DEVICE_FLAG_SM100) {
if (device_id == 0x2A) { if (device_id == 0x2A) {
register_telegram_type(0x07D6, F("SM100wwTemperature"), false, [&](std::shared_ptr<const Telegram> t) { process_SM100wwTemperature(t); }); register_telegram_type(0x07D6, F("SM100wwTemperature"), false, MAKE_PF_CB(process_SM100wwTemperature));
register_telegram_type(0x07AA, F("SM100wwStatus"), false, [&](std::shared_ptr<const Telegram> t) { process_SM100wwStatus(t); }); register_telegram_type(0x07AA, F("SM100wwStatus"), false, MAKE_PF_CB(process_SM100wwStatus));
register_telegram_type(0x07AB, F("SM100wwCommand"), false, [&](std::shared_ptr<const Telegram> t) { process_SM100wwCommand(t); }); register_telegram_type(0x07AB, F("SM100wwCommand"), false, MAKE_PF_CB(process_SM100wwCommand));
} else { } else {
register_telegram_type(0xF9, F("ParamCfg"), false, [&](std::shared_ptr<const Telegram> t) { process_SM100ParamCfg(t); }); register_telegram_type(0xF9, F("ParamCfg"), false, MAKE_PF_CB(process_SM100ParamCfg));
register_telegram_type(0x0358, F("SM100SystemConfig"), true, [&](std::shared_ptr<const Telegram> t) { process_SM100SystemConfig(t); }); register_telegram_type(0x0358, F("SM100SystemConfig"), true, MAKE_PF_CB(process_SM100SystemConfig));
register_telegram_type(0x035A, F("SM100SolarCircuitConfig"), true, [&](std::shared_ptr<const Telegram> t) { process_SM100SolarCircuitConfig(t); }); register_telegram_type(0x035A, F("SM100SolarCircuitConfig"), true, MAKE_PF_CB(process_SM100SolarCircuitConfig));
register_telegram_type(0x0362, F("SM100Monitor"), true, [&](std::shared_ptr<const Telegram> t) { process_SM100Monitor(t); }); register_telegram_type(0x0362, F("SM100Monitor"), true, MAKE_PF_CB(process_SM100Monitor));
register_telegram_type(0x0363, F("SM100Monitor2"), true, [&](std::shared_ptr<const Telegram> t) { process_SM100Monitor2(t); }); register_telegram_type(0x0363, F("SM100Monitor2"), true, MAKE_PF_CB(process_SM100Monitor2));
register_telegram_type(0x0366, F("SM100Config"), true, [&](std::shared_ptr<const Telegram> t) { process_SM100Config(t); }); register_telegram_type(0x0366, F("SM100Config"), true, MAKE_PF_CB(process_SM100Config));
register_telegram_type(0x0364, F("SM100Status"), false, [&](std::shared_ptr<const Telegram> t) { process_SM100Status(t); }); register_telegram_type(0x0364, F("SM100Status"), false, MAKE_PF_CB(process_SM100Status));
register_telegram_type(0x036A, F("SM100Status2"), false, [&](std::shared_ptr<const Telegram> t) { process_SM100Status2(t); }); register_telegram_type(0x036A, F("SM100Status2"), false, MAKE_PF_CB(process_SM100Status2));
register_telegram_type(0x0380, F("SM100CollectorConfig"), true, [&](std::shared_ptr<const Telegram> t) { process_SM100CollectorConfig(t); }); register_telegram_type(0x0380, F("SM100CollectorConfig"), true, MAKE_PF_CB(process_SM100CollectorConfig));
register_telegram_type(0x038E, F("SM100Energy"), true, [&](std::shared_ptr<const Telegram> t) { process_SM100Energy(t); }); register_telegram_type(0x038E, F("SM100Energy"), true, MAKE_PF_CB(process_SM100Energy));
register_telegram_type(0x0391, F("SM100Time"), true, [&](std::shared_ptr<const Telegram> t) { process_SM100Time(t); }); register_telegram_type(0x0391, F("SM100Time"), true, MAKE_PF_CB(process_SM100Time));
register_mqtt_cmd(F("SM100TankBottomMaxTemp"), [&](const char * value, const int8_t id) { return set_SM100TankBottomMaxTemp(value, id); }); register_mqtt_cmd(F("SM100TankBottomMaxTemp"), MAKE_CF_CB(set_SM100TankBottomMaxTemp));
} }
} }
if (flags == EMSdevice::EMS_DEVICE_FLAG_ISM) { if (flags == EMSdevice::EMS_DEVICE_FLAG_ISM) {
register_telegram_type(0x0103, F("ISM1StatusMessage"), true, [&](std::shared_ptr<const Telegram> t) { process_ISM1StatusMessage(t); }); register_telegram_type(0x0103, F("ISM1StatusMessage"), true, MAKE_PF_CB(process_ISM1StatusMessage));
register_telegram_type(0x0101, F("ISM1Set"), false, [&](std::shared_ptr<const Telegram> t) { process_ISM1Set(t); }); register_telegram_type(0x0101, F("ISM1Set"), false, MAKE_PF_CB(process_ISM1Set));
} }
// device values... // device values...

View File

@@ -30,9 +30,9 @@ Switch::Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
LOG_DEBUG(F("Adding new Switch with device ID 0x%02X"), device_id); LOG_DEBUG(F("Adding new Switch with device ID 0x%02X"), device_id);
register_telegram_type(0x9C, F("WM10MonitorMessage"), false, [&](std::shared_ptr<const Telegram> t) { process_WM10MonitorMessage(t); }); register_telegram_type(0x9C, F("WM10MonitorMessage"), false, MAKE_PF_CB(process_WM10MonitorMessage));
register_telegram_type(0x9D, F("WM10SetMessage"), false, [&](std::shared_ptr<const Telegram> t) { process_WM10SetMessage(t); }); register_telegram_type(0x9D, F("WM10SetMessage"), false, MAKE_PF_CB(process_WM10SetMessage));
register_telegram_type(0x1E, F("WM10TempMessage"), false, [&](std::shared_ptr<const Telegram> t) { process_WM10TempMessage(t); }); 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, &activated_, DeviceValueType::BOOL, nullptr, F("activated"), F("activated"));
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, &flowTempHc_, DeviceValueType::USHORT, FL_(div10), F("flowTempHc"), F("flow temperature in assigned hc (TC1)"), DeviceValueUOM::DEGREES);

View File

@@ -44,18 +44,18 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
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
// common telegram handlers // common telegram handlers
register_telegram_type(EMS_TYPE_RCOutdoorTemp, F("RCOutdoorTemp"), false, [&](std::shared_ptr<const Telegram> t) { process_RCOutdoorTemp(t); }); register_telegram_type(EMS_TYPE_RCOutdoorTemp, F("RCOutdoorTemp"), false, MAKE_PF_CB(process_RCOutdoorTemp));
register_telegram_type(EMS_TYPE_RCTime, F("RCTime"), false, [&](std::shared_ptr<const Telegram> t) { process_RCTime(t); }); register_telegram_type(EMS_TYPE_RCTime, F("RCTime"), false, MAKE_PF_CB(process_RCTime));
register_telegram_type(0xA2, F("RCError"), false, [&](std::shared_ptr<const Telegram> t) { process_RCError(t); }); register_telegram_type(0xA2, F("RCError"), false, MAKE_PF_CB(process_RCError));
register_telegram_type(0x12, F("RCErrorMessage"), false, [&](std::shared_ptr<const Telegram> t) { process_RCErrorMessage(t); }); register_telegram_type(0x12, F("RCErrorMessage"), false, MAKE_PF_CB(process_RCErrorMessage));
} }
// RC10 // RC10
if (model == EMSdevice::EMS_DEVICE_FLAG_RC10) { if (model == EMSdevice::EMS_DEVICE_FLAG_RC10) {
monitor_typeids = {0xB1}; monitor_typeids = {0xB1};
set_typeids = {0xB0}; set_typeids = {0xB0};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(monitor_typeids[i], F("RC10Monitor"), false, [&](std::shared_ptr<const Telegram> t) { process_RC10Monitor(t); }); register_telegram_type(monitor_typeids[i], F("RC10Monitor"), false, MAKE_PF_CB(process_RC10Monitor));
register_telegram_type(set_typeids[i], F("RC10Set"), false, [&](std::shared_ptr<const Telegram> t) { process_RC10Set(t); }); register_telegram_type(set_typeids[i], F("RC10Set"), false, MAKE_PF_CB(process_RC10Set));
} }
// RC35 // RC35
@@ -64,12 +64,12 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
set_typeids = {0x3D, 0x47, 0x51, 0x5B}; set_typeids = {0x3D, 0x47, 0x51, 0x5B};
timer_typeids = {0x3F, 0x49, 0x53, 0x5D}; timer_typeids = {0x3F, 0x49, 0x53, 0x5D};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(monitor_typeids[i], F("RC35Monitor"), false, [&](std::shared_ptr<const Telegram> t) { process_RC35Monitor(t); }); register_telegram_type(monitor_typeids[i], F("RC35Monitor"), false, MAKE_PF_CB(process_RC35Monitor));
register_telegram_type(set_typeids[i], F("RC35Set"), false, [&](std::shared_ptr<const Telegram> t) { process_RC35Set(t); }); register_telegram_type(set_typeids[i], F("RC35Set"), false, MAKE_PF_CB(process_RC35Set));
register_telegram_type(timer_typeids[i], F("RC35Timer"), false, [&](std::shared_ptr<const Telegram> t) { process_RC35Timer(t); }); register_telegram_type(timer_typeids[i], F("RC35Timer"), false, MAKE_PF_CB(process_RC35Timer));
} }
register_telegram_type(EMS_TYPE_IBASettings, F("IBASettings"), true, [&](std::shared_ptr<const Telegram> t) { process_IBASettings(t); }); register_telegram_type(EMS_TYPE_IBASettings, F("IBASettings"), true, MAKE_PF_CB(process_IBASettings));
register_telegram_type(EMS_TYPE_wwSettings, F("WWSettings"), true, [&](std::shared_ptr<const Telegram> t) { process_RC35wwSettings(t); }); register_telegram_type(EMS_TYPE_wwSettings, F("WWSettings"), true, MAKE_PF_CB(process_RC35wwSettings));
// RC20 // RC20
} else if (model == EMSdevice::EMS_DEVICE_FLAG_RC20) { } else if (model == EMSdevice::EMS_DEVICE_FLAG_RC20) {
@@ -77,11 +77,11 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
set_typeids = {0xA8}; set_typeids = {0xA8};
if (actual_master_thermostat == device_id) { if (actual_master_thermostat == device_id) {
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(monitor_typeids[i], F("RC20Monitor"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Monitor(t); }); register_telegram_type(monitor_typeids[i], F("RC20Monitor"), false, MAKE_PF_CB(process_RC20Monitor));
register_telegram_type(set_typeids[i], F("RC20Set"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Set(t); }); register_telegram_type(set_typeids[i], F("RC20Set"), false, MAKE_PF_CB(process_RC20Set));
} }
} else { } else {
register_telegram_type(0xAF, F("RC20Remote"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Remote(t); }); 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_2) {
@@ -89,26 +89,26 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
set_typeids = {0xAD}; set_typeids = {0xAD};
if (actual_master_thermostat == device_id) { if (actual_master_thermostat == device_id) {
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(monitor_typeids[i], F("RC20Monitor"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Monitor_2(t); }); register_telegram_type(monitor_typeids[i], F("RC20Monitor"), false, MAKE_PF_CB(process_RC20Monitor_2));
register_telegram_type(set_typeids[i], F("RC20Set"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Set_2(t); }); register_telegram_type(set_typeids[i], F("RC20Set"), false, MAKE_PF_CB(process_RC20Set_2));
} }
} else { } else {
register_telegram_type(0xAF, F("RC20Remote"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Remote(t); }); register_telegram_type(0xAF, F("RC20Remote"), false, MAKE_PF_CB(process_RC20Remote));
} }
// RC30 // RC30
} else if (model == EMSdevice::EMS_DEVICE_FLAG_RC30) { } else if (model == EMSdevice::EMS_DEVICE_FLAG_RC30) {
monitor_typeids = {0x41}; monitor_typeids = {0x41};
set_typeids = {0xA7}; set_typeids = {0xA7};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(monitor_typeids[i], F("RC30Monitor"), false, [&](std::shared_ptr<const Telegram> t) { process_RC30Monitor(t); }); register_telegram_type(monitor_typeids[i], F("RC30Monitor"), false, MAKE_PF_CB(process_RC30Monitor));
register_telegram_type(set_typeids[i], F("RC30Set"), false, [&](std::shared_ptr<const Telegram> t) { process_RC30Set(t); }); register_telegram_type(set_typeids[i], F("RC30Set"), false, MAKE_PF_CB(process_RC30Set));
} }
// EASY // EASY
} else if (model == EMSdevice::EMS_DEVICE_FLAG_EASY) { } else if (model == EMSdevice::EMS_DEVICE_FLAG_EASY) {
monitor_typeids = {0x0A}; monitor_typeids = {0x0A};
set_typeids = {}; set_typeids = {};
register_telegram_type(monitor_typeids[0], F("EasyMonitor"), true, [&](std::shared_ptr<const Telegram> t) { process_EasyMonitor(t); }); register_telegram_type(monitor_typeids[0], F("EasyMonitor"), true, MAKE_PF_CB(process_EasyMonitor));
// 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)) {
@@ -117,36 +117,36 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
summer_typeids = {0x02AF, 0x02B0, 0x02B1, 0x02B2}; summer_typeids = {0x02AF, 0x02B0, 0x02B1, 0x02B2};
curve_typeids = {0x029B, 0x029C, 0x029D, 0x029E}; curve_typeids = {0x029B, 0x029C, 0x029D, 0x029E};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(monitor_typeids[i], F("RC300Monitor"), false, [&](std::shared_ptr<const Telegram> t) { process_RC300Monitor(t); }); register_telegram_type(monitor_typeids[i], F("RC300Monitor"), false, MAKE_PF_CB(process_RC300Monitor));
register_telegram_type(set_typeids[i], F("RC300Set"), false, [&](std::shared_ptr<const Telegram> t) { process_RC300Set(t); }); register_telegram_type(set_typeids[i], F("RC300Set"), false, MAKE_PF_CB(process_RC300Set));
register_telegram_type(summer_typeids[i], F("RC300Summer"), false, [&](std::shared_ptr<const Telegram> t) { process_RC300Summer(t); }); register_telegram_type(summer_typeids[i], F("RC300Summer"), false, MAKE_PF_CB(process_RC300Summer));
register_telegram_type(curve_typeids[i], F("RC300Curves"), false, [&](std::shared_ptr<const Telegram> t) { process_RC300Curve(t); }); register_telegram_type(curve_typeids[i], F("RC300Curves"), false, MAKE_PF_CB(process_RC300Curve));
} }
register_telegram_type(0x2F5, F("RC300WWmode"), true, [&](std::shared_ptr<const Telegram> t) { process_RC300WWmode(t); }); register_telegram_type(0x2F5, F("RC300WWmode"), true, MAKE_PF_CB(process_RC300WWmode));
register_telegram_type(0x31B, F("RC300WWtemp"), true, [&](std::shared_ptr<const Telegram> t) { process_RC300WWtemp(t); }); register_telegram_type(0x31B, F("RC300WWtemp"), true, MAKE_PF_CB(process_RC300WWtemp));
register_telegram_type(0x31D, F("RC300WWmode2"), false, [&](std::shared_ptr<const Telegram> t) { process_RC300WWmode2(t); }); register_telegram_type(0x31D, F("RC300WWmode2"), false, MAKE_PF_CB(process_RC300WWmode2));
register_telegram_type(0x31E, F("RC300WWmode2"), false, [&](std::shared_ptr<const Telegram> t) { process_RC300WWmode2(t); }); register_telegram_type(0x31E, F("RC300WWmode2"), false, MAKE_PF_CB(process_RC300WWmode2));
register_telegram_type(0x23A, F("RC300OutdoorTemp"), true, [&](std::shared_ptr<const Telegram> t) { process_RC300OutdoorTemp(t); }); register_telegram_type(0x23A, F("RC300OutdoorTemp"), true, MAKE_PF_CB(process_RC300OutdoorTemp));
register_telegram_type(0x267, F("RC300Floordry"), false, [&](std::shared_ptr<const Telegram> t) { process_RC300Floordry(t); }); register_telegram_type(0x267, F("RC300Floordry"), false, MAKE_PF_CB(process_RC300Floordry));
register_telegram_type(0x240, F("RC300Settings"), true, [&](std::shared_ptr<const Telegram> t) { process_RC300Settings(t); }); register_telegram_type(0x240, F("RC300Settings"), true, MAKE_PF_CB(process_RC300Settings));
// JUNKERS/HT3 // JUNKERS/HT3
} else if (model == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) { } else if (model == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
monitor_typeids = {0x016F, 0x0170, 0x0171, 0x0172}; monitor_typeids = {0x016F, 0x0170, 0x0171, 0x0172};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(monitor_typeids[i], F("JunkersMonitor"), false, [&](std::shared_ptr<const Telegram> t) { process_JunkersMonitor(t); }); register_telegram_type(monitor_typeids[i], F("JunkersMonitor"), false, MAKE_PF_CB(process_JunkersMonitor));
} }
if (has_flags(EMS_DEVICE_FLAG_JUNKERS_OLD)) { if (has_flags(EMS_DEVICE_FLAG_JUNKERS_OLD)) {
// FR120, FR100 // FR120, FR100
set_typeids = {0x0179, 0x017A, 0x017B, 0x017C}; set_typeids = {0x0179, 0x017A, 0x017B, 0x017C};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(set_typeids[i], F("JunkersSet"), false, [&](std::shared_ptr<const Telegram> t) { process_JunkersSet2(t); }); register_telegram_type(set_typeids[i], F("JunkersSet"), false, MAKE_PF_CB(process_JunkersSet2));
} }
} else { } else {
set_typeids = {0x0165, 0x0166, 0x0167, 0x0168}; set_typeids = {0x0165, 0x0166, 0x0167, 0x0168};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(set_typeids[i], F("JunkersSet"), false, [&](std::shared_ptr<const Telegram> t) { process_JunkersSet(t); }); register_telegram_type(set_typeids[i], F("JunkersSet"), false, MAKE_PF_CB(process_JunkersSet));
} }
} }
} }
@@ -377,7 +377,7 @@ void Thermostat::register_mqtt_ha_config_hc(uint8_t hc_num) {
doc["temp_cmd_t"] = str3; doc["temp_cmd_t"] = str3;
doc["~"] = Mqtt::base(); // ems-esp doc["~"] = Mqtt::base(); // ems-esp
char topic_t[80]; char topic_t[Mqtt::MQTT_TOPIC_MAX_SIZE];
if (Mqtt::nested_format()) { if (Mqtt::nested_format()) {
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());
@@ -428,7 +428,7 @@ void Thermostat::register_mqtt_ha_config_hc(uint8_t 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(100, '\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); });
@@ -448,9 +448,9 @@ bool Thermostat::thermostat_ha_cmd(const char * message, uint8_t hc_num) {
return false; return false;
} }
// check for mode first // check for mode first, which is a string
if (!set_mode(message, hc_num)) { if (!set_mode(message, hc_num)) {
// handle as a numerical temperature value // otherwise handle as a numerical temperature value and set the setpoint temp
float f = strtof((char *)message, 0); float f = strtof((char *)message, 0);
set_temperature(f, HeatingCircuit::Mode::AUTO, hc_num); set_temperature(f, HeatingCircuit::Mode::AUTO, hc_num);
} }
@@ -1249,7 +1249,6 @@ bool Thermostat::set_wwonetime(const char * value, const int8_t id) {
LOG_WARNING(F("Set warm water onetime: Invalid value")); LOG_WARNING(F("Set warm water onetime: Invalid value"));
return false; return false;
} }
char s[7];
LOG_INFO(F("Setting warm water onetime to %s"), b ? F_(on) : F_(off)); LOG_INFO(F("Setting warm water onetime to %s"), b ? F_(on) : F_(off));
write_command(0x02F5, 11, b ? 0xFF : 0x00, 0x031D); write_command(0x02F5, 11, b ? 0xFF : 0x00, 0x031D);
return true; return true;
@@ -1394,6 +1393,7 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
data[5] = (dt[6] - '0') * 10 + (dt[7] - '0'); // sec data[5] = (dt[6] - '0') * 10 + (dt[7] - '0'); // sec
data[6] = (dt[20] - '0'); // day of week data[6] = (dt[20] - '0'); // day of week
data[7] = (dt[22] - '0') + 2; // DST and flag data[7] = (dt[22] - '0') + 2; // DST and flag
LOG_INFO(F("Date and time: %02d.%02d.2%03d-%02d:%02d:%02d"), data[3], data[1], data[0], data[2], data[4], data[5]);
} }
LOG_INFO(F("Setting date and time")); LOG_INFO(F("Setting date and time"));
@@ -1608,6 +1608,49 @@ bool Thermostat::set_controlmode(const char * value, const int8_t id) {
return false; return false;
} }
// sets a single switchtime in the thermostat program for RC35
// format "01:0,1,15:30" Number, day, on, time
bool Thermostat::set_switchtime(const char * value, const int8_t id) {
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id;
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num);
if (hc == nullptr) {
LOG_WARNING(F("Setting switchtime: Heating Circuit %d not found or activated"), hc_num);
return false;
}
if (strlen(value) != 12) {
LOG_WARNING(F("Setting switchtime: Invalid data"));
return false;
}
uint8_t no = (value[0] - '0') * 10 + (value[1] - '0');
uint8_t day = value[3] - '0';
uint8_t on = value[5] - '0';
uint8_t time = 6 * ((value[7] - '0') * 10 + (value[8] - '0')) + (value[10] - '0');
uint8_t data[2] = {0xE7, 0x90}; // unset switchtime
if (day != 7 && on != 7) {
data[0] = (day << 5) + on;
data[1] = time;
}
if (no > 41 || day > 7 || (on > 1 && on != 7) || time > 0x90) {
LOG_WARNING(F("Setting switchtime: Invalid data"));
return false;
}
if ((model() == EMS_DEVICE_FLAG_RC35 || model() == EMS_DEVICE_FLAG_RC30_1)) {
write_command(timer_typeids[hc->hc_num() - 1], no * 2, (uint8_t *)&data, 2, timer_typeids[hc->hc_num() - 1]);
} else {
LOG_WARNING(F("Setting switchtime: thermostat not supported"));
return false;
}
if (data[0] == 0xE7) {
LOG_INFO(F("Setting switchtime no %d for heating circuit %d undefined"), no, hc->hc_num());
} else {
LOG_INFO(F("Setting switchtime no %d for heating circuit %d to day %d, %s, %02d:%d0"), no, hc->hc_num(), day, (on == 1) ? "on" : "off", time / 6, time % 6);
}
return true;
}
// sets the thermostat program for RC35 and RC20 // sets the thermostat program for RC35 and RC20
bool Thermostat::set_program(const char * value, const int8_t id) { bool Thermostat::set_program(const char * value, const int8_t id) {
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id;
@@ -1986,74 +2029,78 @@ void Thermostat::add_commands() {
} }
// common to all thermostats // common to all thermostats
register_mqtt_cmd(F("temp"), [&](const char * value, const int8_t id) { return set_temp(value, id); }); register_mqtt_cmd(F("temp"), MAKE_CF_CB(set_temp), FLAG_HC);
register_mqtt_cmd(F("mode"), [&](const char * value, const int8_t id) { return set_mode(value, id); }); register_mqtt_cmd(F("mode"), MAKE_CF_CB(set_mode), FLAG_HC);
register_mqtt_cmd(F("datetime"), [&](const char * value, const int8_t id) { return set_datetime(value, id); }); if (model() == EMS_DEVICE_FLAG_RC35) { // section is together with RC30
register_mqtt_cmd(F("datetime"), MAKE_CF_CB(set_datetime));
}
switch (model()) { 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("manualtemp"), [&](const char * value, const int8_t id) { return set_manualtemp(value, id); }); register_mqtt_cmd(F("datetime"), MAKE_CF_CB(set_datetime));
register_mqtt_cmd(F("ecotemp"), [&](const char * value, const int8_t id) { return set_ecotemp(value, id); }); register_mqtt_cmd(F("manualtemp"), MAKE_CF_CB(set_manualtemp), FLAG_HC);
register_mqtt_cmd(F("comforttemp"), [&](const char * value, const int8_t id) { return set_comforttemp(value, id); }); register_mqtt_cmd(F("ecotemp"), MAKE_CF_CB(set_ecotemp), FLAG_HC);
register_mqtt_cmd(F("summermode"), [&](const char * value, const int8_t id) { return set_summermode(value, id); }); register_mqtt_cmd(F("comforttemp"), MAKE_CF_CB(set_comforttemp), FLAG_HC);
register_mqtt_cmd(F("summertemp"), [&](const char * value, const int8_t id) { return set_summertemp(value, id); }); register_mqtt_cmd(F("summermode"), MAKE_CF_CB(set_summermode), FLAG_HC);
register_mqtt_cmd(F("wwmode"), [&](const char * value, const int8_t id) { return set_wwmode(value, id); }); register_mqtt_cmd(F("summertemp"), MAKE_CF_CB(set_summertemp), FLAG_HC);
register_mqtt_cmd(F("wwsettemp"), [&](const char * value, const int8_t id) { return set_wwtemp(value, id); }); register_mqtt_cmd(F("wwmode"), MAKE_CF_CB(set_wwmode));
register_mqtt_cmd(F("wwsettemplow"), [&](const char * value, const int8_t id) { return set_wwtemplow(value, id); }); register_mqtt_cmd(F("wwsettemp"), MAKE_CF_CB(set_wwtemp));
register_mqtt_cmd(F("wwonetime"), [&](const char * value, const int8_t id) { return set_wwonetime(value, id); }); register_mqtt_cmd(F("wwsettemplow"), MAKE_CF_CB(set_wwtemplow));
register_mqtt_cmd(F("wwcircmode"), [&](const char * value, const int8_t id) { return set_wwcircmode(value, id); }); register_mqtt_cmd(F("wwonetime"), MAKE_CF_CB(set_wwonetime));
register_mqtt_cmd(F("building"), [&](const char * value, const int8_t id) { return set_building(value, id); }); register_mqtt_cmd(F("wwcircmode"), MAKE_CF_CB(set_wwcircmode));
register_mqtt_cmd(F("nofrosttemp"), [&](const char * value, const int8_t id) { return set_nofrosttemp(value, id); }); register_mqtt_cmd(F("building"), MAKE_CF_CB(set_building));
register_mqtt_cmd(F("designtemp"), [&](const char * value, const int8_t id) { return set_designtemp(value, id); }); register_mqtt_cmd(F("nofrosttemp"), MAKE_CF_CB(set_nofrosttemp), FLAG_HC);
register_mqtt_cmd(F("offsettemp"), [&](const char * value, const int8_t id) { return set_offsettemp(value, id); }); register_mqtt_cmd(F("designtemp"), MAKE_CF_CB(set_designtemp), FLAG_HC);
register_mqtt_cmd(F("minflowtemp"), [&](const char * value, const int8_t id) { return set_minflowtemp(value, id); }); register_mqtt_cmd(F("offsettemp"), MAKE_CF_CB(set_offsettemp), FLAG_HC);
register_mqtt_cmd(F("maxflowtemp"), [&](const char * value, const int8_t id) { return set_maxflowtemp(value, id); }); register_mqtt_cmd(F("minflowtemp"), MAKE_CF_CB(set_minflowtemp), FLAG_HC);
register_mqtt_cmd(F("minexttemp"), [&](const char * value, const int8_t id) { return set_minexttemp(value, id); }); register_mqtt_cmd(F("maxflowtemp"), MAKE_CF_CB(set_maxflowtemp), FLAG_HC);
register_mqtt_cmd(F("roominfluence"), [&](const char * value, const int8_t id) { return set_roominfluence(value, id); }); register_mqtt_cmd(F("minexttemp"), MAKE_CF_CB(set_minexttemp));
register_mqtt_cmd(F("program"), [&](const char * value, const int8_t id) { return set_program(value, id); }); register_mqtt_cmd(F("roominfluence"), MAKE_CF_CB(set_roominfluence), FLAG_HC);
register_mqtt_cmd(F("controlmode"), [&](const char * value, const int8_t id) { return set_controlmode(value, id); }); 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_2:
register_mqtt_cmd(F("nighttemp"), [&](const char * value, const int8_t id) { return set_nighttemp(value, id); }); register_mqtt_cmd(F("nighttemp"), MAKE_CF_CB(set_nighttemp));
register_mqtt_cmd(F("daytemp"), [&](const char * value, const int8_t id) { return set_daytemp(value, id); }); register_mqtt_cmd(F("daytemp"), MAKE_CF_CB(set_daytemp));
register_mqtt_cmd(F("program"), [&](const char * value, const int8_t id) { return set_program(value, id); }); 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_1: // only RC30_1
register_mqtt_cmd(F("clockoffset"), [&](const char * value, const int8_t id) { return set_clockoffset(value, id); }); register_mqtt_cmd(F("clockoffset"), MAKE_CF_CB(set_clockoffset));
register_mqtt_cmd(F("language"), [&](const char * value, const int8_t id) { return set_language(value, id); }); register_mqtt_cmd(F("language"), MAKE_CF_CB(set_language));
register_mqtt_cmd(F("display"), [&](const char * value, const int8_t id) { return set_display(value, id); }); register_mqtt_cmd(F("display"), MAKE_CF_CB(set_display));
break;
case EMS_DEVICE_FLAG_RC35: // RC30 and RC35 case EMS_DEVICE_FLAG_RC35: // RC30 and RC35
register_mqtt_cmd(F("nighttemp"), [&](const char * value, const int8_t id) { return set_nighttemp(value, id); }); register_mqtt_cmd(F("nighttemp"), MAKE_CF_CB(set_nighttemp), FLAG_HC);
register_mqtt_cmd(F("daytemp"), [&](const char * value, const int8_t id) { return set_daytemp(value, id); }); register_mqtt_cmd(F("daytemp"), MAKE_CF_CB(set_daytemp), FLAG_HC);
register_mqtt_cmd(F("nofrosttemp"), [&](const char * value, const int8_t id) { return set_nofrosttemp(value, id); }); register_mqtt_cmd(F("nofrosttemp"), MAKE_CF_CB(set_nofrosttemp), FLAG_HC);
register_mqtt_cmd(F("remotetemp"), [&](const char * value, const int8_t id) { return set_remotetemp(value, id); }); register_mqtt_cmd(F("remotetemp"), MAKE_CF_CB(set_remotetemp), FLAG_HC);
register_mqtt_cmd(F("minexttemp"), [&](const char * value, const int8_t id) { return set_minexttemp(value, id); }); register_mqtt_cmd(F("minexttemp"), MAKE_CF_CB(set_minexttemp));
register_mqtt_cmd(F("calinttemp"), [&](const char * value, const int8_t id) { return set_calinttemp(value, id); }); register_mqtt_cmd(F("calinttemp"), MAKE_CF_CB(set_calinttemp));
register_mqtt_cmd(F("building"), [&](const char * value, const int8_t id) { return set_building(value, id); }); register_mqtt_cmd(F("building"), MAKE_CF_CB(set_building));
register_mqtt_cmd(F("control"), [&](const char * value, const int8_t id) { return set_control(value, id); }); register_mqtt_cmd(F("control"), MAKE_CF_CB(set_control), FLAG_HC);
register_mqtt_cmd(F("pause"), [&](const char * value, const int8_t id) { return set_pause(value, id); }); register_mqtt_cmd(F("pause"), MAKE_CF_CB(set_pause), FLAG_HC);
register_mqtt_cmd(F("party"), [&](const char * value, const int8_t id) { return set_party(value, id); }); register_mqtt_cmd(F("party"), MAKE_CF_CB(set_party), FLAG_HC);
register_mqtt_cmd(F("holiday"), [&](const char * value, const int8_t id) { return set_holiday(value, id); }); register_mqtt_cmd(F("holiday"), MAKE_CF_CB(set_holiday), FLAG_HC);
register_mqtt_cmd(F("summertemp"), [&](const char * value, const int8_t id) { return set_summertemp(value, id); }); register_mqtt_cmd(F("summertemp"), MAKE_CF_CB(set_summertemp), FLAG_HC);
register_mqtt_cmd(F("designtemp"), [&](const char * value, const int8_t id) { return set_designtemp(value, id); }); register_mqtt_cmd(F("designtemp"), MAKE_CF_CB(set_designtemp), FLAG_HC);
register_mqtt_cmd(F("offsettemp"), [&](const char * value, const int8_t id) { return set_offsettemp(value, id); }); register_mqtt_cmd(F("offsettemp"), MAKE_CF_CB(set_offsettemp), FLAG_HC);
register_mqtt_cmd(F("holidaytemp"), [&](const char * value, const int8_t id) { return set_holidaytemp(value, id); }); register_mqtt_cmd(F("holidaytemp"), MAKE_CF_CB(set_holidaytemp), FLAG_HC);
register_mqtt_cmd(F("wwmode"), [&](const char * value, const int8_t id) { return set_wwmode(value, id); }); register_mqtt_cmd(F("wwmode"), MAKE_CF_CB(set_wwmode));
register_mqtt_cmd(F("wwcircmode"), [&](const char * value, const int8_t id) { return set_wwcircmode(value, id); }); register_mqtt_cmd(F("wwcircmode"), MAKE_CF_CB(set_wwcircmode));
register_mqtt_cmd(F("roominfluence"), [&](const char * value, const int8_t id) { return set_roominfluence(value, id); }); register_mqtt_cmd(F("roominfluence"), MAKE_CF_CB(set_roominfluence), FLAG_HC);
register_mqtt_cmd(F("flowtempoffset"), [&](const char * value, const int8_t id) { return set_flowtempoffset(value, id); }); register_mqtt_cmd(F("flowtempoffset"), MAKE_CF_CB(set_flowtempoffset), FLAG_HC);
register_mqtt_cmd(F("minflowtemp"), [&](const char * value, const int8_t id) { return set_minflowtemp(value, id); }); register_mqtt_cmd(F("minflowtemp"), MAKE_CF_CB(set_minflowtemp), FLAG_HC);
register_mqtt_cmd(F("maxflowtemp"), [&](const char * value, const int8_t id) { return set_maxflowtemp(value, id); }); register_mqtt_cmd(F("maxflowtemp"), MAKE_CF_CB(set_maxflowtemp), FLAG_HC);
register_mqtt_cmd(F("reducemode"), [&](const char * value, const int8_t id) { return set_reducemode(value, id); }); register_mqtt_cmd(F("reducemode"), MAKE_CF_CB(set_reducemode), FLAG_HC);
register_mqtt_cmd(F("program"), [&](const char * value, const int8_t id) { return set_program(value, id); }); register_mqtt_cmd(F("program"), MAKE_CF_CB(set_program), FLAG_HC);
register_mqtt_cmd(F("controlmode"), [&](const char * value, const int8_t id) { return set_controlmode(value, id); }); 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("nofrosttemp"), [&](const char * value, const int8_t id) { return set_nofrosttemp(value, id); }); register_mqtt_cmd(F("datetime"), MAKE_CF_CB(set_datetime));
register_mqtt_cmd(F("ecotemp"), [&](const char * value, const int8_t id) { return set_ecotemp(value, id); }); register_mqtt_cmd(F("nofrosttemp"), MAKE_CF_CB(set_nofrosttemp), FLAG_HC);
register_mqtt_cmd(F("heattemp"), [&](const char * value, const int8_t id) { return set_heattemp(value, id); }); 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;

View File

@@ -315,6 +315,7 @@ class Thermostat : public EMSdevice {
bool set_minflowtemp(const char * value, const int8_t id); bool set_minflowtemp(const char * value, const int8_t id);
bool set_maxflowtemp(const char * value, const int8_t id); bool set_maxflowtemp(const char * value, const int8_t id);
bool set_reducemode(const char * value, const int8_t id); bool set_reducemode(const char * value, const int8_t id);
bool set_switchtime(const char * value, const int8_t id);
bool set_program(const char * value, const int8_t id); bool set_program(const char * value, const int8_t id);
bool set_controlmode(const char * value, const int8_t id); bool set_controlmode(const char * value, const int8_t id);

View File

@@ -53,7 +53,23 @@ static const __FlashStringHelper * const DeviceValueTAG_s[] PROGMEM = {
F_(tag_wwc1), // "wwc1" F_(tag_wwc1), // "wwc1"
F_(tag_wwc2), // "Wwc2" F_(tag_wwc2), // "Wwc2"
F_(tag_wwc3), // "wwc3" F_(tag_wwc3), // "wwc3"
F_(tag_wwc4) // "wwc4" F_(tag_wwc4), // "wwc4"
F_(tag_hs1), // "hs1"
F_(tag_hs2), // "hs2"
F_(tag_hs3), // "hs3"
F_(tag_hs4), // "hs4"
F_(tag_hs5), // "hs5"
F_(tag_hs6), // "hs6"
F_(tag_hs7), // "hs7"
F_(tag_hs8), // "hs8"
F_(tag_hs9), // "hs9"
F_(tag_hs10), // "hs10"
F_(tag_hs11), // "hs11"
F_(tag_hs12), // "hs12"
F_(tag_hs13), // "hs13"
F_(tag_hs14), // "hs14"
F_(tag_hs15), // "hs15"
F_(tag_hs16) // "hs16"
}; };
@@ -72,7 +88,23 @@ static const __FlashStringHelper * const DeviceValueTAG_mqtt[] PROGMEM = {
F_(tag_wwc1), // "wwc1" F_(tag_wwc1), // "wwc1"
F_(tag_wwc2), // "Wwc2" F_(tag_wwc2), // "Wwc2"
F_(tag_wwc3), // "wwc3" F_(tag_wwc3), // "wwc3"
F_(tag_wwc4) // "wwc4" F_(tag_wwc4), // "wwc4"
F_(tag_hs1), // "hs1"
F_(tag_hs2), // "hs2"
F_(tag_hs3), // "hs3"
F_(tag_hs4), // "hs4"
F_(tag_hs5), // "hs5"
F_(tag_hs6), // "hs6"
F_(tag_hs7), // "hs7"
F_(tag_hs8), // "hs8"
F_(tag_hs9), // "hs9"
F_(tag_hs10), // "hs10"
F_(tag_hs11), // "hs11"
F_(tag_hs12), // "hs12"
F_(tag_hs13), // "hs13"
F_(tag_hs14), // "hs14"
F_(tag_hs15), // "hs15"
F_(tag_hs16) // "hs16"
}; };
@@ -124,7 +156,7 @@ std::string EMSdevice::brand_to_string() const {
return std::string{}; return std::string{};
} }
// returns the name of the MQTT topic to use for a specific device // returns the name of the MQTT topic to use for a specific device, without the base
std::string EMSdevice::device_type_2_device_name(const uint8_t device_type) { std::string EMSdevice::device_type_2_device_name(const uint8_t device_type) {
switch (device_type) { switch (device_type) {
case DeviceType::SYSTEM: case DeviceType::SYSTEM:
@@ -308,6 +340,7 @@ bool EMSdevice::get_toggle_fetch(uint16_t telegram_id) {
} }
// list device values, only for EMSESP_DEBUG mode // list device values, only for EMSESP_DEBUG mode
#if defined(EMSESP_DEBUG)
void EMSdevice::show_device_values_debug(uuid::console::Shell & shell) { void EMSdevice::show_device_values_debug(uuid::console::Shell & shell) {
size_t total_s = 0; size_t total_s = 0;
uint8_t count = 0; uint8_t count = 0;
@@ -324,7 +357,7 @@ void EMSdevice::show_device_values_debug(uuid::console::Shell & shell) {
shell.printfln("Total size of %d elements: %d", count, total_s); shell.printfln("Total size of %d elements: %d", count, total_s);
shell.println(); shell.println();
} }
#endif
// list all the telegram type IDs for this device // list all the telegram type IDs for this device
void EMSdevice::show_telegram_handlers(uuid::console::Shell & shell) { void EMSdevice::show_telegram_handlers(uuid::console::Shell & shell) {
@@ -351,7 +384,6 @@ char * EMSdevice::show_telegram_handlers(char * result) {
char str[10]; char str[10];
uint8_t i = 0; uint8_t i = 0;
// for (const auto & tf : *telegram_functions_) {
for (const auto & tf : telegram_functions_) { for (const auto & tf : telegram_functions_) {
snprintf_P(str, sizeof(str), PSTR("0x%02X"), tf.telegram_type_id_); snprintf_P(str, sizeof(str), PSTR("0x%02X"), tf.telegram_type_id_);
strlcat(result, str, 200); strlcat(result, str, 200);
@@ -373,11 +405,11 @@ 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) { void EMSdevice::register_mqtt_cmd(const __FlashStringHelper * cmd, cmdfunction_p f, uint8_t flag) {
Command::add(device_type_, cmd, f); Command::add(device_type_, cmd, f, flag);
} }
// register a call back 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) {
telegram_functions_.emplace_back(telegram_type_id, telegram_type_name, fetch, f); telegram_functions_.emplace_back(telegram_type_id, telegram_type_name, fetch, f);
} }
@@ -571,8 +603,6 @@ bool EMSdevice::generate_values_json(JsonObject & root, const uint8_t tag_filter
// 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());
// EMSESP::logger().info(F("**HERE: console=%d nested=%d tag_filter=%d tag=%d type=%d short=%s"), console, nested, tag_filter, dv.tag, dv.type, uuid::read_flash_string(dv.short_name).c_str());
char name[80]; char name[80];
if (console) { if (console) {
// prefix the tag in brackets, unless it's Boiler because we're naughty and use tag for the MQTT topic // prefix the tag in brackets, unless it's Boiler because we're naughty and use tag for the MQTT topic

View File

@@ -83,6 +83,22 @@ MAKE_PSTR(tag_wwc1, "wwc1")
MAKE_PSTR(tag_wwc2, "wwc2") MAKE_PSTR(tag_wwc2, "wwc2")
MAKE_PSTR(tag_wwc3, "wwc3") MAKE_PSTR(tag_wwc3, "wwc3")
MAKE_PSTR(tag_wwc4, "wwc4") 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 // MQTT topic names
MAKE_PSTR(tag_heartbeat_mqtt, "heartbeat") MAKE_PSTR(tag_heartbeat_mqtt, "heartbeat")
@@ -102,10 +118,34 @@ enum DeviceValueTAG : uint8_t {
TAG_WWC1, TAG_WWC1,
TAG_WWC2, TAG_WWC2,
TAG_WWC3, TAG_WWC3,
TAG_WWC4 TAG_WWC4,
TAG_HS1,
TAG_HS2,
TAG_HS3,
TAG_HS4,
TAG_HS5,
TAG_HS6,
TAG_HS7,
TAG_HS8,
TAG_HS9,
TAG_HS10,
TAG_HS11,
TAG_HS12,
TAG_HS13,
TAG_HS14,
TAG_HS15,
TAG_HS16
}; };
// mqtt flags for command subscriptions
enum MqttSubFlag : uint8_t {
FLAG_NORMAL = 0,
FLAG_HC,
FLAG_WWC,
FLAG_NOSUB
};
class EMSdevice { class EMSdevice {
public: public:
virtual ~EMSdevice() = default; // destructor of base class must always be virtual because it's a polymorphic class virtual ~EMSdevice() = default; // destructor of base class must always be virtual because it's a polymorphic class
@@ -242,7 +282,7 @@ class EMSdevice {
void read_command(const uint16_t type_id); void read_command(const uint16_t type_id);
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); void register_mqtt_cmd(const __FlashStringHelper * cmd, cmdfunction_p f, uint8_t flag = 0);
void publish_mqtt_ha_sensor(); void publish_mqtt_ha_sensor();

View File

@@ -48,6 +48,9 @@ std::vector<std::unique_ptr<EMSdevice>> EMSESP::emsdevices; // array of all
std::vector<EMSESP::Device_record> EMSESP::device_library_; // library of all our known EMS devices, in heap std::vector<EMSESP::Device_record> EMSESP::device_library_; // library of all our known EMS devices, in heap
uuid::log::Logger EMSESP::logger_{F_(emsesp), uuid::log::Facility::KERN}; uuid::log::Logger EMSESP::logger_{F_(emsesp), uuid::log::Facility::KERN};
uuid::log::Logger EMSESP::logger() {
return logger_;
}
// The services // The services
RxService EMSESP::rxservice_; // incoming Telegram Rx handler RxService EMSESP::rxservice_; // incoming Telegram Rx handler
@@ -157,18 +160,27 @@ void EMSESP::watch_id(uint16_t watch_id) {
watch_id_ = watch_id; watch_id_ = watch_id;
} }
// change the tx_mode
// resets all counters and bumps the UART // resets all counters and bumps the UART
// this is called when the tx_mode is persisted in the FS either via Web UI or the console // this is called when the tx_mode is persisted in the FS either via Web UI or the console
void EMSESP::init_tx() { void EMSESP::init_uart() {
uint8_t tx_mode; uint8_t tx_mode;
uint8_t rx_gpio;
uint8_t tx_gpio;
EMSESP::webSettingsService.read([&](WebSettings & settings) { EMSESP::webSettingsService.read([&](WebSettings & settings) {
tx_mode = settings.tx_mode; tx_mode = settings.tx_mode;
tx_delay_ = settings.tx_delay * 1000; tx_delay_ = settings.tx_delay * 1000;
rx_gpio = settings.rx_gpio;
tx_gpio = settings.tx_gpio;
});
EMSuart::stop(); EMSuart::stop();
EMSuart::start(tx_mode, settings.rx_gpio, settings.tx_gpio);
}); // don't start UART if we have invalid GPIOs
if (System::is_valid_gpio(rx_gpio) && System::is_valid_gpio(tx_gpio)) {
EMSuart::start(tx_mode, rx_gpio, tx_gpio); // start UART
} else {
LOG_WARNING(F("Invalid UART Rx/Tx GPIOs. Check config."));
}
txservice_.start(); // sends out request to EMS bus for all devices txservice_.start(); // sends out request to EMS bus for all devices
@@ -187,20 +199,22 @@ uint8_t EMSESP::bus_status() {
// check if we have Tx issues. // check if we have Tx issues.
uint32_t total_sent = txservice_.telegram_read_count() + txservice_.telegram_write_count(); uint32_t total_sent = txservice_.telegram_read_count() + txservice_.telegram_write_count();
// nothing sent successfully, also no errors - must be ok // nothing sent and also no errors - must be ok
if ((total_sent == 0) && (txservice_.telegram_fail_count() == 0)) { if ((total_sent == 0) && (txservice_.telegram_fail_count() == 0)) {
return BUS_STATUS_CONNECTED; return BUS_STATUS_CONNECTED;
} }
// nothing sent successfully, but have Tx errors // nothing sent, but have Tx errors
if ((total_sent == 0) && (txservice_.telegram_fail_count() != 0)) { if ((total_sent == 0) && (txservice_.telegram_fail_count() != 0)) {
return BUS_STATUS_TX_ERRORS; return BUS_STATUS_TX_ERRORS;
} }
// Tx Failure rate > 5% // Tx Failure rate > 10%
if (txservice_.telegram_fail_count() < total_sent) {
if (((txservice_.telegram_fail_count() * 100) / total_sent) > EMSbus::EMS_TX_ERROR_LIMIT) { if (((txservice_.telegram_fail_count() * 100) / total_sent) > EMSbus::EMS_TX_ERROR_LIMIT) {
return BUS_STATUS_TX_ERRORS; return BUS_STATUS_TX_ERRORS;
} }
}
return BUS_STATUS_CONNECTED; return BUS_STATUS_CONNECTED;
} }
@@ -308,8 +322,6 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) {
char s[10]; char s[10];
shell.print(Helpers::render_value(s, (float)data.as<float>(), 1)); shell.print(Helpers::render_value(s, (float)data.as<float>(), 1));
} else if (data.is<bool>()) { } else if (data.is<bool>()) {
char s[10];
// shell.print(Helpers::render_boolean(s, data.as<bool>()));
shell.print(data.as<bool>() ? F_(on) : F_(off)); shell.print(data.as<bool>() ? F_(on) : F_(off));
} }
@@ -486,7 +498,7 @@ void EMSESP::publish_device_values(uint8_t device_type) {
// publish it under a single topic, only if we have data to publish // publish it under a single topic, only if we have data to publish
if (need_publish) { if (need_publish) {
char topic[20]; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf_P(topic, sizeof(topic), PSTR("%s_data"), EMSdevice::device_type_2_device_name(device_type).c_str()); snprintf_P(topic, sizeof(topic), PSTR("%s_data"), EMSdevice::device_type_2_device_name(device_type).c_str());
Mqtt::publish(topic, json); Mqtt::publish(topic, json);
} }
@@ -579,9 +591,11 @@ std::string EMSESP::pretty_telegram(std::shared_ptr<const Telegram> telegram) {
dest_name = device_tostring(dest); dest_name = device_tostring(dest);
} }
// check for global/common types like Version // check for global/common types like Version & UBADevices
if (telegram->type_id == EMSdevice::EMS_TYPE_VERSION) { if (telegram->type_id == EMSdevice::EMS_TYPE_VERSION) {
type_name = read_flash_string(F("Version")); type_name = read_flash_string(F("Version"));
} else if (telegram->type_id == EMSdevice::EMS_TYPE_UBADevices) {
type_name = read_flash_string(F("UBADevices"));
} }
// if we don't know the type show // if we don't know the type show
@@ -688,7 +702,7 @@ void EMSESP::process_version(std::shared_ptr<const Telegram> telegram) {
uint8_t product_id = telegram->message_data[offset]; // product ID uint8_t product_id = telegram->message_data[offset]; // product ID
// get version as XX.XX // get version as XX.XX
std::string version(5, '\0'); std::string version(6, '\0');
snprintf_P(&version[0], version.capacity() + 1, PSTR("%02d.%02d"), telegram->message_data[offset + 1], telegram->message_data[offset + 2]); snprintf_P(&version[0], version.capacity() + 1, PSTR("%02d.%02d"), telegram->message_data[offset + 1], telegram->message_data[offset + 2]);
// some devices store the protocol type (HT3, Buderus) in the last byte // some devices store the protocol type (HT3, Buderus) in the last byte
@@ -824,7 +838,7 @@ void EMSESP::show_devices(uuid::console::Shell & shell) {
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
emsdevice->show_mqtt_handlers(shell); emsdevice->show_mqtt_handlers(shell);
shell.println(); shell.println();
emsdevice->show_device_values_debug(shell); // emsdevice->show_device_values_debug(shell);
#endif #endif
shell.println(); shell.println();
@@ -1081,24 +1095,25 @@ void EMSESP::start() {
// start the file system // start the file system
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
if (!LITTLEFS.begin(true)) { if (!LITTLEFS.begin(true)) {
Serial.println("LITTLEFS Mount Failed"); Serial.println("LITTLEFS Mount Failed. EMS-ESP stopped.");
return; return;
} }
#endif #endif
esp8266React.begin(); // loads system settings (wifi, mqtt, etc) esp8266React.begin(); // loads system settings (network, mqtt, etc)
webSettingsService.begin(); // load EMS-ESP specific settings
system_.check_upgrade(); // do any upgrades system_.check_upgrade(); // do any system upgrades
// Load our library of known devices into stack mem. Names are stored in Flash memory (take about 960bytes) // Load our library of known devices into stack mem. Names are stored in Flash memory (takes up about 1kb)
device_library_ = { device_library_ = {
#include "device_library.h" #include "device_library.h"
}; };
console_.start(); // telnet and serial console console_.start(); // telnet and serial console
webSettingsService.begin(); // load EMS-ESP specific settings, like GPIO configurations
mqtt_.start(); // mqtt init mqtt_.start(); // mqtt init
system_.start(heap_start); // starts syslog, uart, sets version, initializes LED. Requires pre-loaded settings. system_.start(heap_start); // starts commands, led, adc, button, network, syslog & uart
shower_.start(); // initialize shower timer and shower alert shower_.start(); // initialize shower timer and shower alert
dallassensor_.start(); // dallas external sensors dallassensor_.start(); // dallas external sensors
webServer.begin(); // start web server webServer.begin(); // start web server
@@ -1115,25 +1130,24 @@ void EMSESP::start() {
// main loop calling all services // main loop calling all services
void EMSESP::loop() { void EMSESP::loop() {
esp8266React.loop(); // web esp8266React.loop(); // web
system_.loop(); // does LED and checks system health, and syslog service
// if we're doing an OTA upload, skip MQTT and EMS // if we're doing an OTA upload, skip MQTT and EMS
if (system_.upload_status()) { if (!system_.upload_status()) {
return;
}
system_.loop(); // does LED and checks system health, and syslog service
rxservice_.loop(); // process any incoming Rx telegrams rxservice_.loop(); // process any incoming Rx telegrams
shower_.loop(); // check for shower on/off shower_.loop(); // check for shower on/off
dallassensor_.loop(); // read dallas sensor temperatures dallassensor_.loop(); // read dallas sensor temperatures
publish_all_loop(); // with HA messages in parts to avoid flooding the mqtt queue publish_all_loop(); // with HA messages in parts to avoid flooding the mqtt queue
mqtt_.loop(); // sends out anything in the MQTT queue mqtt_.loop(); // sends out anything in the MQTT queue
console_.loop(); // telnet/serial console
// force a query on the EMS devices to fetch latest data at a set interval (1 min) // force a query on the EMS devices to fetch latest data at a set interval (1 min)
if ((uuid::get_uptime() - last_fetch_ > EMS_FETCH_FREQUENCY)) { if ((uuid::get_uptime() - last_fetch_ > EMS_FETCH_FREQUENCY)) {
last_fetch_ = uuid::get_uptime(); last_fetch_ = uuid::get_uptime();
fetch_device_values(); fetch_device_values();
} }
}
console_.loop(); // telnet/serial console
// delay(1); // helps telnet catch up. don't think its needed in ESP32 3.1.0 // delay(1); // helps telnet catch up. don't think its needed in ESP32 3.1.0
} }

View File

@@ -63,6 +63,10 @@
#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 5120 // for extra very very large json docs, using DynamicJsonDocument
// 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_CF_CB(__f) [&](const char * value, const int8_t id) { return __f(value, id); } // for command function callbacks to register_mqtt_cmd()
namespace emsesp { namespace emsesp {
class Shower; // forward declaration for compiler class Shower; // forward declaration for compiler
@@ -109,7 +113,7 @@ class EMSESP {
static void show_devices(uuid::console::Shell & shell); static void show_devices(uuid::console::Shell & shell);
static void show_ems(uuid::console::Shell & shell); static void show_ems(uuid::console::Shell & shell);
static void init_tx(); static void init_uart();
static void incoming_telegram(uint8_t * data, const uint8_t length); static void incoming_telegram(uint8_t * data, const uint8_t length);
@@ -187,9 +191,7 @@ class EMSESP {
static WebDevicesService webDevicesService; static WebDevicesService webDevicesService;
static WebAPIService webAPIService; static WebAPIService webAPIService;
static uuid::log::Logger logger() { static uuid::log::Logger logger();
return logger_;
}
private: private:
EMSESP() = delete; EMSESP() = delete;

View File

@@ -18,19 +18,21 @@
#ifndef EMSESP_EMSESP_STUB_H #ifndef EMSESP_EMSESP_STUB_H
#define EMSESP_EMSESP_STUB_H #define EMSESP_EMSESP_STUB_H
// forward declarator
// used to bind EMS-ESP functions to external frameworks
#include "system.h" #include "system.h"
#include "mqtt.h" #include "mqtt.h"
#include "dallassensor.h" #include "dallassensor.h"
#include "version.h" #include "version.h"
// forward declarators
// used to bind EMS-ESP functions to external frameworks
namespace emsesp { namespace emsesp {
class EMSESP { class EMSESP {
public: public:
static Mqtt mqtt_; static Mqtt mqtt_;
static System system_; static System system_;
static DallasSensor dallassensor_; static DallasSensor dallassensor_;
static uuid::log::Logger logger();
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -358,7 +358,13 @@ uint16_t Helpers::atoint(const char * value) {
// rounds a number to 2 decimal places // rounds a number to 2 decimal places
// example: round2(3.14159) -> 3.14 // example: round2(3.14159) -> 3.14
double Helpers::round2(double value, const uint8_t divider) { double Helpers::round2(double value, const uint8_t divider) {
return (int)((value / divider) * 100 + 0.5) / 100.0; uint8_t div = (divider ? divider : 1); // prevent div-by-zero
if (value >= 0) {
return (int)((value / div) * 100 + 0.5) / 100.0;
}
return (int)((value / div) * 100 - 0.5) / 100.0; // negative values
} }
// abs of a signed 32-bit integer // abs of a signed 32-bit integer
@@ -426,6 +432,12 @@ std::string Helpers::toLower(std::string const & s) {
return lc; return lc;
} }
std::string Helpers::toUpper(std::string const & s) {
std::string lc = s;
std::transform(lc.begin(), lc.end(), lc.begin(), [](unsigned char c) { return std::toupper(c); });
return lc;
}
// checks if we can convert a char string to a lowercase string // checks if we can convert a char string to a lowercase string
bool Helpers::value2string(const char * v, std::string & value) { bool Helpers::value2string(const char * v, std::string & value) {
if ((v == nullptr) || (strlen(v) == 0)) { if ((v == nullptr) || (strlen(v) == 0)) {

View File

@@ -23,8 +23,8 @@
#include "telegram.h" // for EMS_VALUE_* settings #include "telegram.h" // for EMS_VALUE_* settings
// #define FJSON(x) x #define FJSON(x) x
#define FJSON(x) F(x) // #define FJSON(x) F(x)
namespace emsesp { namespace emsesp {
@@ -52,6 +52,7 @@ class Helpers {
static uint32_t abs(const int32_t i); static uint32_t abs(const int32_t i);
static double round2(double value, const uint8_t divider); static double round2(double value, const uint8_t divider);
static std::string toLower(std::string const & s); static std::string toLower(std::string const & s);
static std::string toUpper(std::string const & s);
static bool hasValue(const uint8_t & v, const uint8_t isBool = 0); static bool hasValue(const uint8_t & v, const uint8_t isBool = 0);
static bool hasValue(const int8_t & v); static bool hasValue(const int8_t & v);

View File

@@ -68,7 +68,7 @@ MAKE_PSTR_WORD(master)
MAKE_PSTR_WORD(pin) MAKE_PSTR_WORD(pin)
MAKE_PSTR_WORD(publish) MAKE_PSTR_WORD(publish)
MAKE_PSTR_WORD(timeout) MAKE_PSTR_WORD(timeout)
MAKE_PSTR_WORD(ethernet) MAKE_PSTR_WORD(board_profile)
// for commands // for commands
MAKE_PSTR_WORD(call) MAKE_PSTR_WORD(call)
@@ -101,6 +101,7 @@ 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(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")
@@ -187,13 +188,6 @@ 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(internal_temperature, "internal temperature")
MAKE_PSTR(internal_setpoint, "internal setpoint")
MAKE_PSTR(external_temperature, "external temperature")
MAKE_PSTR(burner_temperature, "burner temperature")
MAKE_PSTR(WW_temperature, "WW temperature")
MAKE_PSTR(functioning_mode, "functioning mode")
MAKE_PSTR(smoke_temperature, "smoke temperature")
MAKE_PSTR_WORD(radiator) MAKE_PSTR_WORD(radiator)
MAKE_PSTR_WORD(convector) MAKE_PSTR_WORD(convector)
MAKE_PSTR_WORD(floor) MAKE_PSTR_WORD(floor)
@@ -212,6 +206,13 @@ 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(internal_temperature, "internal temperature")
MAKE_PSTR(internal_setpoint, "internal setpoint")
MAKE_PSTR(external_temperature, "external temperature")
MAKE_PSTR(burner_temperature, "burner temperature")
MAKE_PSTR(WW_temperature, "WW temperature")
MAKE_PSTR(functioning_mode, "functioning mode")
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))
@@ -229,12 +230,12 @@ MAKE_PSTR_LIST(enum_summermode, F_(summer), F_(auto), F_(winter))
MAKE_PSTR_LIST(enum_mode, F_(manual), F_(auto)) 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_(manual), F_(auto), F_(holiday)) MAKE_PSTR_LIST(enum_mode4, F_(blank), F_(manual), F_(auto), F_(holiday))
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_(heat), F_(eco), F_(nofrost)) MAKE_PSTR_LIST(enum_modetype4, F_(blank), F_(nofrost), F_(eco), F_(heat))
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))

View File

@@ -40,6 +40,7 @@ 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_; bool Mqtt::nested_format_;
uint8_t Mqtt::subscribe_format_;
std::deque<Mqtt::QueuedMqttMessage> Mqtt::mqtt_messages_; std::deque<Mqtt::QueuedMqttMessage> Mqtt::mqtt_messages_;
std::vector<Mqtt::MQTTSubFunction> Mqtt::mqtt_subfunctions_; std::vector<Mqtt::MQTTSubFunction> Mqtt::mqtt_subfunctions_;
@@ -64,11 +65,11 @@ void Mqtt::subscribe(const uint8_t device_type, const std::string & topic, mqtt_
if (!mqtt_subfunctions_.empty()) { if (!mqtt_subfunctions_.empty()) {
for (auto & mqtt_subfunction : mqtt_subfunctions_) { for (auto & mqtt_subfunction : mqtt_subfunctions_) {
if ((mqtt_subfunction.device_type_ == device_type) && (strcmp(mqtt_subfunction.topic_.c_str(), topic.c_str()) == 0)) { if ((mqtt_subfunction.device_type_ == device_type) && (strcmp(mqtt_subfunction.topic_.c_str(), topic.c_str()) == 0)) {
// add the function, in case its not there // add the function (in case its not there) and quit because it already exists
if (cb) { if (cb) {
mqtt_subfunction.mqtt_subfunction_ = cb; mqtt_subfunction.mqtt_subfunction_ = cb;
} }
return; // it exists, exit return;
} }
} }
} }
@@ -83,14 +84,15 @@ void Mqtt::subscribe(const uint8_t device_type, const std::string & topic, mqtt_
} }
// register in our libary with the callback function. // register in our libary with the callback function.
// We store both the original topic and the fully-qualified one // We store the original topic without base
mqtt_subfunctions_.emplace_back(device_type, std::move(topic), std::move(cb)); mqtt_subfunctions_.emplace_back(device_type, std::move(topic), std::move(cb));
} }
// subscribe to the command topic if it doesn't exist yet // subscribe to the command topic if it doesn't exist yet
void Mqtt::register_command(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb) { void Mqtt::register_command(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb, uint8_t flag) {
std::string cmd_topic = EMSdevice::device_type_2_device_name(device_type); std::string cmd_topic = EMSdevice::device_type_2_device_name(device_type); // thermostat, boiler, etc...
// see if we have already a handler for the device type (boiler, thermostat). If not add it
bool exists = false; bool exists = false;
if (!mqtt_subfunctions_.empty()) { if (!mqtt_subfunctions_.empty()) {
for (const auto & mqtt_subfunction : mqtt_subfunctions_) { for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
@@ -101,15 +103,32 @@ void Mqtt::register_command(const uint8_t device_type, const __FlashStringHelper
} }
if (!exists) { if (!exists) {
Mqtt::subscribe(device_type, cmd_topic, nullptr); // use an empty function handler to signal this is a command function Mqtt::subscribe(device_type, cmd_topic, nullptr); // use an empty function handler to signal this is a command function only (e.g. ems-esp/boiler)
LOG_DEBUG(F("Registering MQTT cmd %s with topic %s"), uuid::read_flash_string(cmd).c_str(), EMSdevice::device_type_2_device_name(device_type).c_str());
} }
LOG_DEBUG(F("Registering MQTT cmd %s with topic %s"), uuid::read_flash_string(cmd).c_str(), EMSdevice::device_type_2_device_name(device_type).c_str()); // register the individual commands too (e.g. ems-esp/boiler/wwonetime)
// https://github.com/emsesp/EMS-ESP32/issues/31
std::string topic(MQTT_TOPIC_MAX_SIZE, '\0');
if (subscribe_format_ == 2 && flag == MqttSubFlag::FLAG_HC) {
topic = cmd_topic + "/hc1/" + uuid::read_flash_string(cmd);
queue_subscribe_message(topic);
topic = cmd_topic + "/hc2/" + uuid::read_flash_string(cmd);
queue_subscribe_message(topic);
topic = cmd_topic + "/hc3/" + uuid::read_flash_string(cmd);
queue_subscribe_message(topic);
topic = cmd_topic + "/hc4/" + uuid::read_flash_string(cmd);
queue_subscribe_message(topic);
} else if (subscribe_format_ && flag != MqttSubFlag::FLAG_NOSUB) {
topic = cmd_topic + "/" + uuid::read_flash_string(cmd);
queue_subscribe_message(topic);
}
} }
// subscribe to an MQTT topic, and store the associated callback function. For generic functions not tied to a specific device // subscribe to an MQTT topic, and store the associated callback function
// For generic functions not tied to a specific device
void Mqtt::subscribe(const std::string & topic, mqtt_subfunction_p cb) { void Mqtt::subscribe(const std::string & topic, mqtt_subfunction_p cb) {
subscribe(0, topic, cb); // no device_id needed, if generic to EMS-ESP subscribe(0, topic, cb); // no device_id needed if generic to EMS-ESP
} }
// resubscribe to all MQTT topics // resubscribe to all MQTT topics
@@ -121,11 +140,27 @@ void Mqtt::resubscribe() {
for (const auto & mqtt_subfunction : mqtt_subfunctions_) { for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
queue_subscribe_message(mqtt_subfunction.topic_); queue_subscribe_message(mqtt_subfunction.topic_);
} }
for (const auto & cf : Command::commands()) {
std::string topic(MQTT_TOPIC_MAX_SIZE, '\0');
if (subscribe_format_ == 2 && cf.flag_ == MqttSubFlag::FLAG_HC) {
topic = EMSdevice::device_type_2_device_name(cf.device_type_) + "/hc1/" + uuid::read_flash_string(cf.cmd_);
queue_subscribe_message(topic);
topic = EMSdevice::device_type_2_device_name(cf.device_type_) + "/hc2/" + uuid::read_flash_string(cf.cmd_);
queue_subscribe_message(topic);
topic = EMSdevice::device_type_2_device_name(cf.device_type_) + "/hc3/" + uuid::read_flash_string(cf.cmd_);
queue_subscribe_message(topic);
topic = EMSdevice::device_type_2_device_name(cf.device_type_) + "/hc4/" + uuid::read_flash_string(cf.cmd_);
queue_subscribe_message(topic);
} else if (subscribe_format_ && cf.flag_ != MqttSubFlag::FLAG_NOSUB) {
topic = EMSdevice::device_type_2_device_name(cf.device_type_) + "/" + uuid::read_flash_string(cf.cmd_);
queue_subscribe_message(topic);
}
}
} }
// Main MQTT loop - sends out top item on publish queue // Main MQTT loop - sends out top item on publish queue
void Mqtt::loop() { void Mqtt::loop() {
// exit if MQTT is not enabled or if there is no WIFI // exit if MQTT is not enabled or if there is no network connection
if (!connected()) { if (!connected()) {
return; return;
} }
@@ -191,6 +226,16 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) {
for (const auto & mqtt_subfunction : mqtt_subfunctions_) { for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
shell.printfln(F(" %s/%s"), mqtt_base_.c_str(), mqtt_subfunction.topic_.c_str()); shell.printfln(F(" %s/%s"), mqtt_base_.c_str(), mqtt_subfunction.topic_.c_str());
} }
for (const auto & cf : Command::commands()) {
if (subscribe_format_ == 2 && cf.flag_ == MqttSubFlag::FLAG_HC) {
shell.printfln(F(" %s/%s/hc1/%s"), mqtt_base_.c_str(), EMSdevice::device_type_2_device_name(cf.device_type_).c_str(), uuid::read_flash_string(cf.cmd_).c_str());
shell.printfln(F(" %s/%s/hc2/%s"), mqtt_base_.c_str(), EMSdevice::device_type_2_device_name(cf.device_type_).c_str(), uuid::read_flash_string(cf.cmd_).c_str());
shell.printfln(F(" %s/%s/hc3/%s"), mqtt_base_.c_str(), EMSdevice::device_type_2_device_name(cf.device_type_).c_str(), uuid::read_flash_string(cf.cmd_).c_str());
shell.printfln(F(" %s/%s/hc4/%s"), mqtt_base_.c_str(), EMSdevice::device_type_2_device_name(cf.device_type_).c_str(), uuid::read_flash_string(cf.cmd_).c_str());
} else if (subscribe_format_ && cf.flag_ != MqttSubFlag::FLAG_NOSUB) {
shell.printfln(F(" %s/%s/%s"), mqtt_base_.c_str(), EMSdevice::device_type_2_device_name(cf.device_type_).c_str(), uuid::read_flash_string(cf.cmd_).c_str());
}
}
shell.println(); shell.println();
// show queues // show queues
@@ -248,6 +293,12 @@ void Mqtt::on_message(const char * fulltopic, const char * payload, size_t len)
char topic[100]; char topic[100];
strlcpy(topic, &fulltopic[1 + strlen(mqtt_base_.c_str())], 100); strlcpy(topic, &fulltopic[1 + strlen(mqtt_base_.c_str())], 100);
// strip the topic substrings
char * topic_end = strchr(topic, '/');
if (topic_end != nullptr) {
topic_end[0] = '\0';
}
// convert payload to a null-terminated char string // convert payload to a null-terminated char string
char message[len + 2]; char message[len + 2];
strlcpy(message, payload, len + 1); strlcpy(message, payload, len + 1);
@@ -257,7 +308,7 @@ void Mqtt::on_message(const char * fulltopic, const char * payload, size_t len)
// see if we have this topic in our subscription list, then call its callback handler // see if we have this topic in our subscription list, then call its callback handler
for (const auto & mf : mqtt_subfunctions_) { for (const auto & mf : mqtt_subfunctions_) {
if (strcmp(topic, mf.topic_.c_str()) == 0) { if (strcmp(topic, mf.topic_.c_str()) == 0) {
// if we have call back function then call it // if we have callback function then call it
// otherwise proceed as process as a command // otherwise proceed as process as a command
if (mf.mqtt_subfunction_) { if (mf.mqtt_subfunction_) {
if (!(mf.mqtt_subfunction_)(message)) { if (!(mf.mqtt_subfunction_)(message)) {
@@ -266,6 +317,28 @@ void Mqtt::on_message(const char * fulltopic, const char * payload, size_t len)
return; return;
} }
// check if it's not json, then try and extract the command from the topic name
if (message[0] != '{') {
// get topic with substrings again
strlcpy(topic, &fulltopic[1 + strlen(mqtt_base_.c_str())], 100);
char * cmd_only = strchr(topic, '/');
if (cmd_only == NULL) {
return; // invalid topic name
}
cmd_only++; // skip the /
int8_t id = -1;
// check for hcx/ prefix
if (cmd_only[0] == 'h' && cmd_only[1] == 'c' && cmd_only[3] == '/') {
id = cmd_only[2] - '0';
cmd_only += 4;
}
// 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)) {
LOG_ERROR(F("No matching cmd (%s) in topic %s, id %d, or invalid data"), cmd_only, topic, id);
}
return;
}
// It's a command then with the payload being JSON like {"cmd":"<cmd>", "data":<data>, "id":<n>} // It's a command then with the payload being JSON like {"cmd":"<cmd>", "data":<data>, "id":<n>}
// Find the command from the json and call it directly // Find the command from the json and call it directly
StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> doc; StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> doc;
@@ -305,7 +378,7 @@ void Mqtt::on_message(const char * fulltopic, const char * payload, size_t len)
} }
if (!cmd_known) { if (!cmd_known) {
LOG_ERROR(F("No matching cmd (%s), invalid data or command failed"), command); LOG_ERROR(F("No matching cmd (%s) or invalid data"), command);
} }
return; return;
@@ -388,6 +461,7 @@ void Mqtt::load_settings() {
dallas_format_ = mqttSettings.dallas_format; dallas_format_ = mqttSettings.dallas_format;
bool_format_ = mqttSettings.bool_format; bool_format_ = mqttSettings.bool_format;
nested_format_ = mqttSettings.nested_format; nested_format_ = mqttSettings.nested_format;
subscribe_format_ = mqttSettings.subscribe_format;
// convert to milliseconds // convert to milliseconds
publish_time_boiler_ = mqttSettings.publish_time_boiler * 1000; publish_time_boiler_ = mqttSettings.publish_time_boiler * 1000;
@@ -462,7 +536,7 @@ void Mqtt::start() {
}); });
// create space for command buffer, to avoid heap memory fragmentation // create space for command buffer, to avoid heap memory fragmentation
mqtt_subfunctions_.reserve(50); mqtt_subfunctions_.reserve(5);
} }
void Mqtt::set_publish_time_boiler(uint16_t publish_time) { void Mqtt::set_publish_time_boiler(uint16_t publish_time) {
@@ -515,7 +589,7 @@ bool Mqtt::get_publish_onchange(uint8_t device_type) {
// MQTT onConnect - when an MQTT connect is established // MQTT onConnect - when an MQTT connect is established
// send out some inital MQTT messages // send out some inital MQTT messages
void Mqtt::on_connect() { void Mqtt::on_connect() {
if (connecting_) { // prevent duplicating connections if (connecting_) { // prevent duplicated connections
return; return;
} }
@@ -537,7 +611,11 @@ void Mqtt::on_connect() {
doc["version"] = EMSESP_APP_VERSION; doc["version"] = EMSESP_APP_VERSION;
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
if (EMSESP::system_.ethernet_connected()) {
doc["ip"] = ETH.localIP().toString();
} else {
doc["ip"] = WiFi.localIP().toString(); doc["ip"] = WiFi.localIP().toString();
}
#endif #endif
publish(F_(info), doc.as<JsonObject>()); publish(F_(info), doc.as<JsonObject>());
@@ -550,7 +628,6 @@ void Mqtt::on_connect() {
EMSESP::shower_.send_mqtt_stat(false); // Send shower_activated as false EMSESP::shower_.send_mqtt_stat(false); // Send shower_activated as false
EMSESP::system_.send_heartbeat(); // send heatbeat EMSESP::system_.send_heartbeat(); // send heatbeat
// } else {
if (connectcount_ > 1) { if (connectcount_ > 1) {
// we doing a re-connect from a TCP break // we doing a re-connect from a TCP break
// only re-subscribe again to all MQTT topics // only re-subscribe again to all MQTT topics
@@ -599,7 +676,7 @@ void Mqtt::ha_status() {
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
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 heap memory"), EMSdevice::DeviceType::SYSTEM, F("freemem"));

View File

@@ -107,7 +107,7 @@ class Mqtt {
static void publish_ha(const __FlashStringHelper * topic, const JsonObject & payload); static void publish_ha(const __FlashStringHelper * topic, const JsonObject & payload);
static void publish_mqtt_ha_sensor(uint8_t type, uint8_t tag, const __FlashStringHelper * name, const uint8_t device_type, const __FlashStringHelper * entity, const uint8_t uom = 0); static void publish_mqtt_ha_sensor(uint8_t type, uint8_t tag, const __FlashStringHelper * name, const uint8_t device_type, const __FlashStringHelper * entity, const uint8_t uom = 0);
static void register_command(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb); static void register_command(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb, uint8_t tag = 0);
static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type); static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type);
static void show_mqtt(uuid::console::Shell & shell); static void show_mqtt(uuid::console::Shell & shell);
@@ -275,6 +275,7 @@ class Mqtt {
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 bool nested_format_;
static uint8_t subscribe_format_;
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -114,7 +114,7 @@ void Shower::send_mqtt_stat(bool state, bool force) {
JsonArray ids = dev.createNestedArray("ids"); JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp"); ids.add("ems-esp");
char topic[100]; 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("homeassistant/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
} }

View File

@@ -39,7 +39,7 @@ PButton System::myPButton_;
// value: true = HIGH, false = LOW // value: true = HIGH, false = LOW
// 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 (id < 0) { if (!is_valid_gpio(id)) {
return false; return false;
} }
@@ -119,19 +119,29 @@ void System::format(uuid::console::Shell & shell) {
System::restart(); System::restart();
} }
void System::syslog_start() {
if (syslog_enabled_) {
#ifndef EMSESP_STANDALONE
syslog_.start();
#endif
EMSESP::logger().info(F("Starting Syslog"));
}
}
void System::syslog_init(bool refresh) { void System::syslog_init(bool refresh) {
if (refresh) { if (refresh) {
get_settings(); get_settings();
} }
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
// check for empty hostname // check for empty or invalid hostname
IPAddress addr; IPAddress addr;
if (!addr.fromString(syslog_host_.c_str())) { if (!addr.fromString(syslog_host_.c_str())) {
syslog_enabled_ = false; syslog_enabled_ = false;
} }
// in case service is still running, this flushes the queue - https://github.com/emsesp/EMS-ESP/issues/496 // in case service is still running, this flushes the queue
// https://github.com/emsesp/EMS-ESP/issues/496
if (!syslog_enabled_) { if (!syslog_enabled_) {
syslog_.log_level((uuid::log::Level)-1); syslog_.log_level((uuid::log::Level)-1);
syslog_.mark_interval(0); syslog_.mark_interval(0);
@@ -140,26 +150,23 @@ void System::syslog_init(bool refresh) {
} }
// start & configure syslog // start & configure syslog
syslog_.start();
syslog_.log_level((uuid::log::Level)syslog_level_); syslog_.log_level((uuid::log::Level)syslog_level_);
syslog_.mark_interval(syslog_mark_interval_); syslog_.mark_interval(syslog_mark_interval_);
syslog_.destination(addr, syslog_port_); syslog_.destination(addr, syslog_port_);
syslog_.hostname(hostname_.c_str()); syslog_.hostname(hostname().c_str());
EMSESP::logger().info(F("Syslog started"));
#endif #endif
} }
// read all the settings from the config files and store locally // read all the settings from the config files and store locally
void System::get_settings() { void System::get_settings() {
EMSESP::webSettingsService.read([&](WebSettings & settings) { EMSESP::webSettingsService.read([&](WebSettings & settings) {
// BUTTON // Button
pbutton_gpio_ = settings.pbutton_gpio; pbutton_gpio_ = settings.pbutton_gpio;
// ADC // ADC
analog_enabled_ = settings.analog_enabled; analog_enabled_ = settings.analog_enabled;
// SYSLOG // Syslog
syslog_enabled_ = settings.syslog_enabled; syslog_enabled_ = settings.syslog_enabled;
syslog_level_ = settings.syslog_level; syslog_level_ = settings.syslog_level;
syslog_mark_interval_ = settings.syslog_mark_interval; syslog_mark_interval_ = settings.syslog_mark_interval;
@@ -169,12 +176,9 @@ void System::get_settings() {
// LED // LED
hide_led_ = settings.hide_led; hide_led_ = settings.hide_led;
led_gpio_ = settings.led_gpio; led_gpio_ = settings.led_gpio;
});
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) { // Board profile
hostname(networkSettings.hostname.c_str()); board_profile_ = settings.board_profile;
LOG_INFO(F("System %s booted (EMS-ESP version %s)"), networkSettings.hostname.c_str(), EMSESP_APP_VERSION); // print boot message
ethernet_profile_ = networkSettings.ethernet_profile;
}); });
} }
@@ -201,10 +205,22 @@ void System::wifi_tweak() {
bool s1 = WiFi.getSleep(); bool s1 = WiFi.getSleep();
WiFi.setSleep(false); // turn off sleep - WIFI_PS_NONE WiFi.setSleep(false); // turn off sleep - WIFI_PS_NONE
bool s2 = WiFi.getSleep(); bool s2 = WiFi.getSleep();
LOG_INFO(F("Adjusting Wifi - Tx power %d->%d, Sleep %d->%d"), p1, p2, s1, s2); LOG_DEBUG(F("Adjusting WiFi - Tx power %d->%d, Sleep %d->%d"), p1, p2, s1, s2);
#endif #endif
} }
// check for valid ESP32 pins. This is very dependent on which ESP32 board is being used.
// Typically you can't use 1, 6-11, 12, 14, 15, 20, 24, 28-31 and 40+
// we allow 0 as it has a special function on the NodeMCU apparently
// See https://diyprojects.io/esp32-how-to-use-gpio-digital-io-arduino-code/#.YFpVEq9KhjG
// and https://nodemcu.readthedocs.io/en/dev-esp32/modules/gpio/
bool System::is_valid_gpio(uint8_t pin) {
if ((pin == 1) || (pin >= 6 && pin <= 12) || (pin >= 14 && pin <= 15) || (pin == 20) || (pin == 24) || (pin >= 28 && pin <= 31) || (pin > 40)) {
return false; // bad pin
}
return true;
}
// first call. Sets memory and starts up the UART Serial bridge // first call. Sets memory and starts up the UART Serial bridge
void System::start(uint32_t heap_start) { void System::start(uint32_t heap_start) {
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
@@ -219,14 +235,19 @@ void System::start(uint32_t heap_start) {
// load in all the settings first // load in all the settings first
get_settings(); get_settings();
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) {
hostname(networkSettings.hostname.c_str()); // sets the hostname
LOG_INFO(F("System name: %s"), hostname().c_str());
});
commands_init(); // console & api commands commands_init(); // console & api commands
led_init(false); // init LED led_init(false); // init LED
adc_init(false); // analog ADC adc_init(false); // analog ADC
syslog_init(false); // init SysLog
button_init(false); // the special button button_init(false); // the special button
network_init(false); // network network_init(false); // network
syslog_init(false); // init SysLog
EMSESP::init_tx(); // start UART EMSESP::init_uart(); // start UART
} }
// adc and bluetooth // adc and bluetooth
@@ -237,12 +258,14 @@ void System::adc_init(bool refresh) {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
// setCpuFrequencyMhz(160); // default is 240 // setCpuFrequencyMhz(160); // default is 240
// disable bluetooth // disable bluetooth & ADC
/*
btStop(); btStop();
esp_bt_controller_disable(); esp_bt_controller_disable();
if (!analog_enabled_) { if (!analog_enabled_) {
adc_power_off(); // turn off ADC to save power if not needed adc_power_release(); // turn off ADC to save power if not needed
} }
*/
#endif #endif
} }
@@ -283,19 +306,20 @@ void System::button_init(bool refresh) {
get_settings(); get_settings();
} }
// Allow 0 for Boot-button on NodeMCU-32s? if (is_valid_gpio(pbutton_gpio_)) {
// if (pbutton_gpio_) {
if (!myPButton_.init(pbutton_gpio_, HIGH)) { if (!myPButton_.init(pbutton_gpio_, HIGH)) {
LOG_INFO(F("External multi-functional button not detected")); LOG_INFO(F("Multi-functional button not detected"));
} else { } else {
LOG_INFO(F("External multi-functional button enabled")); LOG_INFO(F("Multi-functional button enabled"));
}
} else {
LOG_WARNING(F("Invalid button GPIO. Check config."));
} }
myPButton_.onClick(BUTTON_Debounce, button_OnClick); myPButton_.onClick(BUTTON_Debounce, button_OnClick);
myPButton_.onDblClick(BUTTON_DblClickDelay, button_OnDblClick); myPButton_.onDblClick(BUTTON_DblClickDelay, button_OnDblClick);
myPButton_.onLongPress(BUTTON_LongPressDelay, button_OnLongPress); myPButton_.onLongPress(BUTTON_LongPressDelay, button_OnLongPress);
myPButton_.onVLongPress(BUTTON_VLongPressDelay, button_OnVLongPress); myPButton_.onVLongPress(BUTTON_VLongPressDelay, button_OnVLongPress);
// }
} }
// set the LED to on or off when in normal operating mode // set the LED to on or off when in normal operating mode
@@ -304,9 +328,9 @@ void System::led_init(bool refresh) {
get_settings(); get_settings();
} }
if (led_gpio_) { if ((led_gpio_ != 0) && is_valid_gpio(led_gpio_)) {
pinMode(led_gpio_, OUTPUT); // 0 means disabled pinMode(led_gpio_, OUTPUT); // 0 means disabled
digitalWrite(led_gpio_, hide_led_ ? !LED_ON : LED_ON); // LED on, for ever digitalWrite(led_gpio_, hide_led_ ? !LED_ON : LED_ON);
} }
} }
@@ -380,10 +404,13 @@ void System::send_heartbeat() {
return; return;
} }
int8_t rssi = wifi_quality(); int8_t rssi;
if (!ethernet_connected_) {
rssi = wifi_quality();
if (rssi == -1) { if (rssi == -1) {
return; return;
} }
}
StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> doc; StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> doc;
@@ -396,11 +423,13 @@ void System::send_heartbeat() {
doc["status"] = FJSON("disconnected"); doc["status"] = FJSON("disconnected");
} }
if (!ethernet_connected_) {
doc["rssi"] = rssi; doc["rssi"] = rssi;
}
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["mqttfails"] = Mqtt::publish_fails();
doc["rxsent"] = 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();
@@ -452,9 +481,12 @@ void System::network_init(bool refresh) {
get_settings(); get_settings();
} }
// check ethernet profile last_system_check_ = 0; // force the LED to go from fast flash to pulse
send_heartbeat();
// check board profile for those which use ethernet
// ethernet uses lots of additional memory so we only start it when it's explicitly set in the config // ethernet uses lots of additional memory so we only start it when it's explicitly set in the config
if (ethernet_profile_ == 0) { if (!board_profile_.equals("E32") && !board_profile_.equals("TLK110") && !board_profile_.equals("LAN8720") && !board_profile_.equals("OLIMEX")) {
return; return;
} }
@@ -465,45 +497,36 @@ void System::network_init(bool refresh) {
eth_phy_type_t type; // Type of the Ethernet PHY (LAN8720 or TLK110) eth_phy_type_t type; // Type of the Ethernet PHY (LAN8720 or TLK110)
eth_clock_mode_t clock_mode; // ETH_CLOCK_GPIO0_IN or ETH_CLOCK_GPIO0_OUT, ETH_CLOCK_GPIO16_OUT, ETH_CLOCK_GPIO17_OUT for 50Hz inverted clock eth_clock_mode_t clock_mode; // ETH_CLOCK_GPIO0_IN or ETH_CLOCK_GPIO0_OUT, ETH_CLOCK_GPIO16_OUT, ETH_CLOCK_GPIO17_OUT for 50Hz inverted clock
if (ethernet_profile_ == 1) { if (board_profile_.equals("E32") || board_profile_.equals("LAN8720")) {
// LAN8720 // BBQKees Gateway E32 (LAN8720)
phy_addr = 1;
power = 16;
mdc = 23;
mdio = 18;
type = ETH_PHY_LAN8720;
clock_mode = ETH_CLOCK_GPIO0_IN;
} else if (board_profile_.equals("OLIMEX")) {
// Olimex ESP32-EVB (LAN8720)
phy_addr = 0; phy_addr = 0;
power = -1; power = -1;
mdc = 23; mdc = 23;
mdio = 18; mdio = 18;
type = ETH_PHY_LAN8720; type = ETH_PHY_LAN8720;
clock_mode = ETH_CLOCK_GPIO0_IN; clock_mode = ETH_CLOCK_GPIO0_IN;
} else if (ethernet_profile_ == 2) { } else if (board_profile_.equals("TLK110")) {
// TLK110 // Ethernet (TLK110)
phy_addr = 31; phy_addr = 31;
power = -1; power = -1;
mdc = 23; mdc = 23;
mdio = 18; mdio = 18;
type = ETH_PHY_TLK110; type = ETH_PHY_TLK110;
clock_mode = ETH_CLOCK_GPIO0_IN; clock_mode = ETH_CLOCK_GPIO0_IN;
} else {
return; // invalid combi
} }
#ifndef EMSESP_STANDALONE // bool have_ethernet = ETH.begin(phy_addr, power, mdc, mdio, type, clock_mode);
if (ETH.begin(phy_addr, power, mdc, mdio, type, clock_mode)) { (void)ETH.begin(phy_addr, power, mdc, mdio, type, clock_mode);
// disable ssid and AP when using Ethernet
EMSESP::esp8266React.getNetworkSettingsService()->update(
[&](NetworkSettings & settings) {
settings.ssid == ""; // remove SSID
return StateUpdateResult::CHANGED;
},
"local");
EMSESP::esp8266React.getAPSettingsService()->update(
[&](APSettings & settings) {
settings.provisionMode = AP_MODE_NEVER;
return StateUpdateResult::CHANGED;
},
"local");
}
#endif
last_system_check_ = 0; // force the LED to go from fast flash to pulse
send_heartbeat();
} }
// check health of system, done every few seconds // check health of system, done every few seconds
@@ -512,7 +535,7 @@ void System::system_check() {
last_system_check_ = uuid::get_uptime(); last_system_check_ = uuid::get_uptime();
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
if (WiFi.status() != WL_CONNECTED) { if (!ethernet_connected() && (WiFi.status() != WL_CONNECTED)) {
set_led_speed(LED_WARNING_BLINK_FAST); set_led_speed(LED_WARNING_BLINK_FAST);
system_healthy_ = false; system_healthy_ = false;
return; return;
@@ -532,7 +555,7 @@ void System::system_check() {
system_healthy_ = true; system_healthy_ = true;
send_heartbeat(); send_heartbeat();
if (led_gpio_) { if (led_gpio_) {
digitalWrite(led_gpio_, hide_led_ ? !LED_ON : LED_ON); // LED on, for ever digitalWrite(led_gpio_, hide_led_ ? !LED_ON : LED_ON);
} }
} }
} }
@@ -541,8 +564,9 @@ void System::system_check() {
// commands - takes static function pointers // commands - takes static function pointers
// these commands respond to the topic "system" and take a payload like {cmd:"", data:"", id:""} // these commands respond to the topic "system" and take a payload like {cmd:"", data:"", id:""}
// no individual subscribe for pin command because id is needed
void System::commands_init() { void System::commands_init() {
Command::add(EMSdevice::DeviceType::SYSTEM, F_(pin), System::command_pin); Command::add(EMSdevice::DeviceType::SYSTEM, F_(pin), System::command_pin, MqttSubFlag::FLAG_NOSUB);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), System::command_send); Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), System::command_send);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(publish), System::command_publish); Command::add(EMSdevice::DeviceType::SYSTEM, F_(publish), System::command_publish);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch); Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch);
@@ -751,7 +775,7 @@ void System::console_commands(Shell & shell, unsigned int context) {
CommandFlags::ADMIN, CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(hostname)}, flash_string_vector{F_(set), F_(hostname)},
flash_string_vector{F_(name_mandatory)}, flash_string_vector{F_(name_mandatory)},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) { [](Shell & shell, const std::vector<std::string> & arguments) {
shell.println("The network connection will be reset..."); shell.println("The network connection will be reset...");
Shell::loop_all(); Shell::loop_all();
delay(1000); // wait a second delay(1000); // wait a second
@@ -795,41 +819,39 @@ void System::console_commands(Shell & shell, unsigned int context) {
}); });
}); });
EMSESPShell::commands->add_command( EMSESPShell::commands->add_command(ShellContext::SYSTEM,
ShellContext::SYSTEM,
CommandFlags::ADMIN, CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(ethernet)}, flash_string_vector{F_(set), F_(board_profile)},
flash_string_vector{F_(n_mandatory)}, flash_string_vector{F_(name_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) { [](Shell & shell, const std::vector<std::string> & arguments) {
uint8_t n = Helpers::hextoint(arguments.front().c_str()); std::vector<uint8_t> data; // led, dallas, rx, tx, button
if (n <= 2) { std::string board_profile = Helpers::toUpper(arguments.front());
EMSESP::esp8266React.getNetworkSettingsService()->update( if (!load_board_profile(data, board_profile)) {
[&](NetworkSettings & networkSettings) { shell.println(F("Invalid board profile"));
networkSettings.ethernet_profile = n; return;
shell.printfln(F_(ethernet_option_fmt), networkSettings.ethernet_profile); }
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; return StateUpdateResult::CHANGED;
}, },
"local"); "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); EMSESP::system_.network_init(true);
} else {
shell.println(F("Must be 0, 1 or 2"));
}
},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> {
return std::vector<std::string>{read_flash_string(F("0")), read_flash_string(F("1")), read_flash_string(F("2"))};
}); });
EMSESPShell::commands->add_command(ShellContext::SYSTEM, CommandFlags::USER, flash_string_vector{F_(set)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { 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) { EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) {
shell.print(F(" "));
shell.printfln(F_(hostname_fmt), networkSettings.hostname.isEmpty() ? uuid::read_flash_string(F_(unset)).c_str() : networkSettings.hostname.c_str()); shell.printfln(F_(hostname_fmt), networkSettings.hostname.isEmpty() ? uuid::read_flash_string(F_(unset)).c_str() : networkSettings.hostname.c_str());
shell.print(F(" "));
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_ssid_fmt), networkSettings.ssid.isEmpty() ? uuid::read_flash_string(F_(unset)).c_str() : networkSettings.ssid.c_str());
shell.print(F(" "));
shell.printfln(F_(wifi_password_fmt), networkSettings.ssid.isEmpty() ? F_(unset) : F_(asterisks)); shell.printfln(F_(wifi_password_fmt), networkSettings.ssid.isEmpty() ? F_(unset) : F_(asterisks));
shell.print(F(" "));
shell.printfln(F_(ethernet_option_fmt), networkSettings.ethernet_profile);
}); });
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))) { 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))) {
@@ -850,8 +872,13 @@ bool System::check_upgrade() {
// e.g. http://ems-esp/api?device=system&cmd=settings // e.g. http://ems-esp/api?device=system&cmd=settings
// value and id are ignored // value and id are ignored
bool System::command_settings(const char * value, const int8_t id, JsonObject & json) { bool System::command_settings(const char * value, const int8_t id, JsonObject & json) {
JsonObject node;
node = json.createNestedObject("System");
node["version"] = EMSESP_APP_VERSION;
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & settings) { EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & settings) {
JsonObject node = json.createNestedObject("WIFI"); node = json.createNestedObject("WIFI");
node["ssid"] = settings.ssid; node["ssid"] = settings.ssid;
node["hostname"] = settings.hostname; node["hostname"] = settings.hostname;
node["static_ip_config"] = settings.staticIPConfig; node["static_ip_config"] = settings.staticIPConfig;
@@ -864,7 +891,7 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject &
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
EMSESP::esp8266React.getAPSettingsService()->read([&](APSettings & settings) { EMSESP::esp8266React.getAPSettingsService()->read([&](APSettings & settings) {
JsonObject node = json.createNestedObject("AP"); node = json.createNestedObject("AP");
node["provision_mode"] = settings.provisionMode; node["provision_mode"] = settings.provisionMode;
node["ssid"] = settings.ssid; node["ssid"] = settings.ssid;
node["local_ip"] = settings.localIP.toString(); node["local_ip"] = settings.localIP.toString();
@@ -874,7 +901,7 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject &
#endif #endif
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) { EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
JsonObject node = json.createNestedObject("MQTT"); node = json.createNestedObject("MQTT");
node["enabled"] = settings.enabled; node["enabled"] = settings.enabled;
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
node["host"] = settings.host; node["host"] = settings.host;
@@ -900,7 +927,7 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject &
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
EMSESP::esp8266React.getNTPSettingsService()->read([&](NTPSettings & settings) { EMSESP::esp8266React.getNTPSettingsService()->read([&](NTPSettings & settings) {
JsonObject node = json.createNestedObject("NTP"); node = json.createNestedObject("NTP");
node["enabled"] = settings.enabled; node["enabled"] = settings.enabled;
node["server"] = settings.server; node["server"] = settings.server;
node["tz_label"] = settings.tzLabel; node["tz_label"] = settings.tzLabel;
@@ -908,14 +935,14 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject &
}); });
EMSESP::esp8266React.getOTASettingsService()->read([&](OTASettings & settings) { EMSESP::esp8266React.getOTASettingsService()->read([&](OTASettings & settings) {
JsonObject node = json.createNestedObject("OTA"); node = json.createNestedObject("OTA");
node["enabled"] = settings.enabled; node["enabled"] = settings.enabled;
node["port"] = settings.port; node["port"] = settings.port;
}); });
#endif #endif
EMSESP::webSettingsService.read([&](WebSettings & settings) { EMSESP::webSettingsService.read([&](WebSettings & settings) {
JsonObject node = json.createNestedObject("Settings"); node = json.createNestedObject("Settings");
node["tx_mode"] = settings.tx_mode; node["tx_mode"] = settings.tx_mode;
node["ems_bus_id"] = settings.ems_bus_id; node["ems_bus_id"] = settings.ems_bus_id;
node["syslog_enabled"] = settings.syslog_enabled; node["syslog_enabled"] = settings.syslog_enabled;
@@ -935,6 +962,7 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject &
node["api_enabled"] = settings.api_enabled; node["api_enabled"] = settings.api_enabled;
node["analog_enabled"] = settings.analog_enabled; node["analog_enabled"] = settings.analog_enabled;
node["pbutton_gpio"] = settings.pbutton_gpio; node["pbutton_gpio"] = settings.pbutton_gpio;
node["board_profile"] = settings.board_profile;
}); });
return true; return true;
@@ -1004,5 +1032,32 @@ bool System::command_test(const char * value, const int8_t id) {
} }
#endif #endif
// takes a board profile and populates a data array with GPIO configurations
// data = led, dallas, rx, tx, button
// returns false if profile is not found
bool System::load_board_profile(std::vector<uint8_t> & data, const std::string & board_profile) {
if (board_profile == "S32") {
data = {2, 3, 23, 5, 0}; // BBQKees Gateway S32
} else if (board_profile == "E32") {
data = {2, 4, 5, 17, 33}; // BBQKees Gateway E32
} else if (board_profile == "MT-ET") {
data = {2, 18, 23, 5, 0}; // MT-ET Live D1 Mini
} else if (board_profile == "NODEMCU") {
data = {2, 18, 23, 5, 0}; // NodeMCU 32S
} else if (board_profile == "LOLIN") {
data = {2, 18, 17, 16, 0}; // Lolin D32
} else if (board_profile == "OLIMEX") {
data = {0, 0, 36, 4, 34}; // Olimex ESP32-EVB (uses U1TXD/U1RXD/BUTTON, no LED or Dallas)
} else if (board_profile == "TLK110") {
data = {2, 4, 5, 17, 33}; // Generic Ethernet (TLK110)
} else if (board_profile == "LAN8720") {
data = {2, 4, 5, 17, 33}; // Generic Ethernet (LAN8720)
} else {
data = {EMSESP_DEFAULT_LED_GPIO, EMSESP_DEFAULT_DALLAS_GPIO, EMSESP_DEFAULT_RX_GPIO, EMSESP_DEFAULT_TX_GPIO, EMSESP_DEFAULT_PBUTTON_GPIO};
return (board_profile == "CUSTOM");
}
return true;
}
} // namespace emsesp } // namespace emsesp

View File

@@ -66,6 +66,9 @@ class System {
void show_mem(const char * note); void show_mem(const char * note);
void get_settings(); void get_settings();
void wifi_tweak(); void wifi_tweak();
void syslog_start();
bool check_upgrade();
void send_heartbeat();
void led_init(bool refresh); void led_init(bool refresh);
void syslog_init(bool refresh); void syslog_init(bool refresh);
@@ -74,8 +77,8 @@ class System {
void button_init(bool refresh); void button_init(bool refresh);
void commands_init(); void commands_init();
bool check_upgrade(); static bool is_valid_gpio(uint8_t pin);
void send_heartbeat(); static bool load_board_profile(std::vector<uint8_t> & data, const std::string & board_profile);
std::string hostname() { std::string hostname() {
return hostname_; return hostname_;
@@ -93,6 +96,14 @@ class System {
ethernet_connected_ = b; ethernet_connected_ = b;
} }
bool network_connected() {
#ifndef EMSESP_STANDALONE
return (ethernet_connected_ || WiFi.isConnected());
#else
return true;
#endif
}
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;
static uint32_t heap_start_; static uint32_t heap_start_;
@@ -113,7 +124,7 @@ class System {
static constexpr uint32_t LED_WARNING_BLINK_FAST = 100; // flash quickly for boot up sequence static constexpr uint32_t LED_WARNING_BLINK_FAST = 100; // flash quickly for boot up sequence
static constexpr uint32_t SYSTEM_HEARTBEAT_INTERVAL = 60000; // in milliseconds, how often the MQTT heartbeat is sent (1 min) static constexpr uint32_t SYSTEM_HEARTBEAT_INTERVAL = 60000; // in milliseconds, how often the MQTT heartbeat is sent (1 min)
static constexpr uint32_t SYSTEM_MEASURE_ANALOG_INTERVAL = 500; static constexpr uint32_t SYSTEM_MEASURE_ANALOG_INTERVAL = 500;
static constexpr uint8_t LED_ON = LOW; // internal LED static constexpr uint8_t LED_ON = HIGH; // LED
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
static uuid::syslog::SyslogService syslog_; static uuid::syslog::SyslogService syslog_;
@@ -134,16 +145,16 @@ class System {
uint32_t last_heartbeat_ = 0; uint32_t last_heartbeat_ = 0;
uint32_t last_system_check_ = 0; uint32_t last_system_check_ = 0;
bool upload_status_ = false; // true if we're in the middle of a OTA firmware upload bool upload_status_ = false; // true if we're in the middle of a OTA firmware upload
bool ethernet_connected_; bool ethernet_connected_ = false;
uint16_t analog_; uint16_t analog_;
// settings // settings
std::string hostname_; std::string hostname_ = "ems-esp";
bool hide_led_; bool hide_led_;
uint8_t led_gpio_; uint8_t led_gpio_;
bool syslog_enabled_; bool syslog_enabled_;
bool analog_enabled_; bool analog_enabled_;
uint8_t ethernet_profile_; String board_profile_;
uint8_t pbutton_gpio_; uint8_t pbutton_gpio_;
int8_t syslog_level_; int8_t syslog_level_;
uint32_t syslog_mark_interval_; uint32_t syslog_mark_interval_;

View File

@@ -22,19 +22,15 @@
namespace emsesp { namespace emsesp {
// CRC lookup table with poly 12 for faster checking // CRC lookup table with poly 12 for faster checking
const uint8_t ems_crc_table[] = {0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1C, 0x1E, 0x20, 0x22, 0x24, 0x26, const uint8_t ems_crc_table[] = {0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1C, 0x1E, 0x20, 0x22, 0x24, 0x26, 0x28, 0x2A, 0x2C, 0x2E, 0x30, 0x32, 0x34, 0x36, 0x38,
0x28, 0x2A, 0x2C, 0x2E, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3A, 0x3C, 0x3E, 0x40, 0x42, 0x44, 0x46, 0x48, 0x4A, 0x4C, 0x4E, 0x3A, 0x3C, 0x3E, 0x40, 0x42, 0x44, 0x46, 0x48, 0x4A, 0x4C, 0x4E, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5A, 0x5C, 0x5E, 0x60, 0x62, 0x64, 0x66, 0x68, 0x6A, 0x6C, 0x6E, 0x70, 0x72,
0x50, 0x52, 0x54, 0x56, 0x58, 0x5A, 0x5C, 0x5E, 0x60, 0x62, 0x64, 0x66, 0x68, 0x6A, 0x6C, 0x6E, 0x70, 0x72, 0x74, 0x76, 0x74, 0x76, 0x78, 0x7A, 0x7C, 0x7E, 0x80, 0x82, 0x84, 0x86, 0x88, 0x8A, 0x8C, 0x8E, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9A, 0x9C, 0x9E, 0xA0, 0xA2, 0xA4, 0xA6, 0xA8, 0xAA, 0xAC,
0x78, 0x7A, 0x7C, 0x7E, 0x80, 0x82, 0x84, 0x86, 0x88, 0x8A, 0x8C, 0x8E, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9A, 0x9C, 0x9E, 0xAE, 0xB0, 0xB2, 0xB4, 0xB6, 0xB8, 0xBA, 0xBC, 0xBE, 0xC0, 0xC2, 0xC4, 0xC6, 0xC8, 0xCA, 0xCC, 0xCE, 0xD0, 0xD2, 0xD4, 0xD6, 0xD8, 0xDA, 0xDC, 0xDE, 0xE0, 0xE2, 0xE4, 0xE6,
0xA0, 0xA2, 0xA4, 0xA6, 0xA8, 0xAA, 0xAC, 0xAE, 0xB0, 0xB2, 0xB4, 0xB6, 0xB8, 0xBA, 0xBC, 0xBE, 0xC0, 0xC2, 0xC4, 0xC6, 0xE8, 0xEA, 0xEC, 0xEE, 0xF0, 0xF2, 0xF4, 0xF6, 0xF8, 0xFA, 0xFC, 0xFE, 0x19, 0x1B, 0x1D, 0x1F, 0x11, 0x13, 0x15, 0x17, 0x09, 0x0B, 0x0D, 0x0F, 0x01, 0x03, 0x05, 0x07, 0x39,
0xC8, 0xCA, 0xCC, 0xCE, 0xD0, 0xD2, 0xD4, 0xD6, 0xD8, 0xDA, 0xDC, 0xDE, 0xE0, 0xE2, 0xE4, 0xE6, 0xE8, 0xEA, 0xEC, 0xEE, 0x3B, 0x3D, 0x3F, 0x31, 0x33, 0x35, 0x37, 0x29, 0x2B, 0x2D, 0x2F, 0x21, 0x23, 0x25, 0x27, 0x59, 0x5B, 0x5D, 0x5F, 0x51, 0x53, 0x55, 0x57, 0x49, 0x4B, 0x4D, 0x4F, 0x41, 0x43,
0xF0, 0xF2, 0xF4, 0xF6, 0xF8, 0xFA, 0xFC, 0xFE, 0x19, 0x1B, 0x1D, 0x1F, 0x11, 0x13, 0x15, 0x17, 0x09, 0x0B, 0x0D, 0x0F, 0x45, 0x47, 0x79, 0x7B, 0x7D, 0x7F, 0x71, 0x73, 0x75, 0x77, 0x69, 0x6B, 0x6D, 0x6F, 0x61, 0x63, 0x65, 0x67, 0x99, 0x9B, 0x9D, 0x9F, 0x91, 0x93, 0x95, 0x97, 0x89, 0x8B, 0x8D,
0x01, 0x03, 0x05, 0x07, 0x39, 0x3B, 0x3D, 0x3F, 0x31, 0x33, 0x35, 0x37, 0x29, 0x2B, 0x2D, 0x2F, 0x21, 0x23, 0x25, 0x27, 0x8F, 0x81, 0x83, 0x85, 0x87, 0xB9, 0xBB, 0xBD, 0xBF, 0xB1, 0xB3, 0xB5, 0xB7, 0xA9, 0xAB, 0xAD, 0xAF, 0xA1, 0xA3, 0xA5, 0xA7, 0xD9, 0xDB, 0xDD, 0xDF, 0xD1, 0xD3, 0xD5, 0xD7,
0x59, 0x5B, 0x5D, 0x5F, 0x51, 0x53, 0x55, 0x57, 0x49, 0x4B, 0x4D, 0x4F, 0x41, 0x43, 0x45, 0x47, 0x79, 0x7B, 0x7D, 0x7F, 0xC9, 0xCB, 0xCD, 0xCF, 0xC1, 0xC3, 0xC5, 0xC7, 0xF9, 0xFB, 0xFD, 0xFF, 0xF1, 0xF3, 0xF5, 0xF7, 0xE9, 0xEB, 0xED, 0xEF, 0xE1, 0xE3, 0xE5, 0xE7};
0x71, 0x73, 0x75, 0x77, 0x69, 0x6B, 0x6D, 0x6F, 0x61, 0x63, 0x65, 0x67, 0x99, 0x9B, 0x9D, 0x9F, 0x91, 0x93, 0x95, 0x97,
0x89, 0x8B, 0x8D, 0x8F, 0x81, 0x83, 0x85, 0x87, 0xB9, 0xBB, 0xBD, 0xBF, 0xB1, 0xB3, 0xB5, 0xB7, 0xA9, 0xAB, 0xAD, 0xAF,
0xA1, 0xA3, 0xA5, 0xA7, 0xD9, 0xDB, 0xDD, 0xDF, 0xD1, 0xD3, 0xD5, 0xD7, 0xC9, 0xCB, 0xCD, 0xCF, 0xC1, 0xC3, 0xC5, 0xC7,
0xF9, 0xFB, 0xFD, 0xFF, 0xF1, 0xF3, 0xF5, 0xF7, 0xE9, 0xEB, 0xED, 0xEF, 0xE1, 0xE3, 0xE5, 0xE7};
uint32_t EMSbus::last_bus_activity_ = 0; // timestamp of last time a valid Rx came in uint32_t EMSbus::last_bus_activity_ = 0; // timestamp of last time a valid Rx came in
bool EMSbus::bus_connected_ = false; // start assuming the bus hasn't been connected bool EMSbus::bus_connected_ = false; // start assuming the bus hasn't been connected
@@ -59,13 +55,7 @@ uint8_t EMSbus::calculate_crc(const uint8_t * data, const uint8_t length) {
// creates a telegram object // creates a telegram object
// stores header in separate member objects and the rest in the message_data block // stores header in separate member objects and the rest in the message_data block
Telegram::Telegram(const uint8_t operation, Telegram::Telegram(const uint8_t operation, const uint8_t src, const uint8_t dest, const uint16_t type_id, const uint8_t offset, const uint8_t * data, const uint8_t message_length)
const uint8_t src,
const uint8_t dest,
const uint16_t type_id,
const uint8_t offset,
const uint8_t * data,
const uint8_t message_length)
: operation(operation) : operation(operation)
, src(src) , src(src)
, dest(dest) , dest(dest)
@@ -202,8 +192,7 @@ void RxService::add(uint8_t * data, uint8_t length) {
// if we're watching and "raw" print out actual telegram as bytes to the console // if we're watching and "raw" print out actual telegram as bytes to the console
if (EMSESP::watch() == EMSESP::Watch::WATCH_RAW) { if (EMSESP::watch() == EMSESP::Watch::WATCH_RAW) {
uint16_t trace_watch_id = EMSESP::watch_id(); uint16_t trace_watch_id = EMSESP::watch_id();
if ((trace_watch_id == WATCH_ID_NONE) || (type_id == trace_watch_id) if ((trace_watch_id == WATCH_ID_NONE) || (type_id == trace_watch_id) || ((trace_watch_id < 0x80) && ((src == trace_watch_id) || (dest == trace_watch_id)))) {
|| ((trace_watch_id < 0x80) && ((src == trace_watch_id) || (dest == trace_watch_id)))) {
LOG_NOTICE(F("Rx: %s"), Helpers::data_to_hex(data, length).c_str()); LOG_NOTICE(F("Rx: %s"), Helpers::data_to_hex(data, length).c_str());
} else if (EMSESP::trace_raw()) { } else if (EMSESP::trace_raw()) {
LOG_TRACE(F("Rx: %s"), Helpers::data_to_hex(data, length).c_str()); LOG_TRACE(F("Rx: %s"), Helpers::data_to_hex(data, length).c_str());
@@ -234,13 +223,6 @@ void RxService::add(uint8_t * data, uint8_t length) {
} }
rx_telegrams_.emplace_back(rx_telegram_id_++, std::move(telegram)); // add to queue rx_telegrams_.emplace_back(rx_telegram_id_++, std::move(telegram)); // add to queue
/*
QueuedRxTelegram qrxt;
qrxt.telegram_ = std::make_shared<Telegram>(operation, src, dest, type_id, offset, message_data, message_length);
qrxt.id_ = rx_telegram_id_++;
rx_telegrams_.push(qrxt);
*/
} }
// start and initialize Tx // start and initialize Tx
@@ -285,15 +267,11 @@ void TxService::send() {
} }
delayed_send_ = 0; delayed_send_ = 0;
// auto telegram = tx_telegrams_.pop(); // get the Telegram, also removes from queue
// if we're in read-only mode (tx_mode 0) forget the Tx call // if we're in read-only mode (tx_mode 0) forget the Tx call
if (tx_mode() != 0) { if (tx_mode() != 0) {
// send_telegram(telegram);
send_telegram(tx_telegrams_.front()); send_telegram(tx_telegrams_.front());
} }
// auto telegram = tx_telegrams_.pop();
tx_telegrams_.pop_front(); // remove the telegram from the queue tx_telegrams_.pop_front(); // remove the telegram from the queue
} }
@@ -371,10 +349,7 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
length++; // add one since we want to now include the CRC length++; // add one since we want to now include the CRC
LOG_DEBUG(F("Sending %s Tx [#%d], telegram: %s"), LOG_DEBUG(F("Sending %s Tx [#%d], telegram: %s"), (telegram->operation == Telegram::Operation::TX_WRITE) ? F("write") : F("read"), tx_telegram.id_, Helpers::data_to_hex(telegram_raw, length).c_str());
(telegram->operation == Telegram::Operation::TX_WRITE) ? F("write") : F("read"),
tx_telegram.id_,
Helpers::data_to_hex(telegram_raw, length).c_str());
set_post_send_query(tx_telegram.validateid_); set_post_send_query(tx_telegram.validateid_);
// send the telegram to the UART Tx // send the telegram to the UART Tx
@@ -414,14 +389,7 @@ void TxService::send_telegram(const uint8_t * data, const uint8_t length) {
} }
*/ */
void TxService::add(const uint8_t operation, void TxService::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) {
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) {
auto telegram = std::make_shared<Telegram>(operation, ems_bus_id(), dest, type_id, offset, message_data, message_length); auto telegram = std::make_shared<Telegram>(operation, ems_bus_id(), dest, type_id, offset, message_data, message_length);
#ifdef EMSESP_DEBUG #ifdef EMSESP_DEBUG
@@ -507,13 +475,6 @@ void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t lengt
LOG_DEBUG(F("[DEBUG] New Tx [#%d] telegram, length %d"), tx_telegram_id_, message_length); LOG_DEBUG(F("[DEBUG] New Tx [#%d] telegram, length %d"), tx_telegram_id_, message_length);
#endif #endif
/*
QueuedTxTelegram qtxt;
qtxt.id_ = tx_telegram_id_++;
qtxt.retry_ = false;
qtxt.telegram_ = std::make_shared<Telegram>(operation, ems_bus_id(), dest, type_id, offset, message_data, message_length);
*/
if (front) { if (front) {
// tx_telegrams_.push_front(qtxt); // add to front of queue // tx_telegrams_.push_front(qtxt); // add to front of queue
tx_telegrams_.emplace_front(tx_telegram_id_++, std::move(telegram), false, validate_id); // add to front of queue tx_telegrams_.emplace_front(tx_telegram_id_++, std::move(telegram), false, validate_id); // add to front of queue
@@ -580,9 +541,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."), 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);
(operation == Telegram::Operation::TX_WRITE) ? F("Write") : F("Read"),
MAXIMUM_TX_RETRIES);
return; return;
} }
@@ -600,14 +559,6 @@ void TxService::retry_tx(const uint8_t operation, const uint8_t * data, const ui
} }
tx_telegrams_.emplace_front(tx_telegram_id_++, std::move(telegram_last_), true, get_post_send_query()); tx_telegrams_.emplace_front(tx_telegram_id_++, std::move(telegram_last_), true, get_post_send_query());
/*
QueuedTxTelegram qtxt;
qtxt.id_ = tx_telegram_id_++;
qtxt.retry_ = true; // this time it is a retry
qtxt.telegram_ = telegram_last_;
tx_telegrams_.push_front(qtxt); // add to front of queue
*/
} }
uint16_t TxService::read_next_tx() { uint16_t TxService::read_next_tx() {

View File

@@ -31,7 +31,6 @@
#include <uuid/log.h> #include <uuid/log.h>
// #include "containers.h"
#include "helpers.h" #include "helpers.h"
#define MAX_RX_TELEGRAMS 10 // size of Rx queue #define MAX_RX_TELEGRAMS 10 // size of Rx queue
@@ -219,7 +218,11 @@ class RxService : public EMSbus {
if (telegram_error_count_ == 0) { if (telegram_error_count_ == 0) {
return 100; // all good, 100% return 100; // all good, 100%
} }
if (telegram_error_count_ >= telegram_count_) {
return 100;
}
uint8_t q = ((float)telegram_error_count_ / telegram_count_ * 100); uint8_t q = ((float)telegram_error_count_ / telegram_count_ * 100);
return (q <= EMS_BUS_QUALITY_RX_THRESHOLD ? 100 : 100 - q); return (q <= EMS_BUS_QUALITY_RX_THRESHOLD ? 100 : 100 - q);
} }
@@ -239,17 +242,6 @@ class RxService : public EMSbus {
return rx_telegrams_; return rx_telegrams_;
} }
/*
struct QueuedRxTelegram {
uint16_t id_;
std::shared_ptr<const Telegram> telegram_;
};
const emsesp::queue<QueuedRxTelegram> queue() const {
return rx_telegrams_;
}
*/
private: private:
static constexpr uint8_t EMS_BUS_QUALITY_RX_THRESHOLD = 5; // % threshold before reporting quality issues static constexpr uint8_t EMS_BUS_QUALITY_RX_THRESHOLD = 5; // % threshold before reporting quality issues
@@ -258,7 +250,6 @@ class RxService : public EMSbus {
uint32_t telegram_error_count_ = 0; // # Rx CRC errors uint32_t telegram_error_count_ = 0; // # Rx CRC errors
std::shared_ptr<const Telegram> rx_telegram; // the incoming Rx telegram std::shared_ptr<const Telegram> rx_telegram; // the incoming Rx telegram
std::deque<QueuedRxTelegram> rx_telegrams_; // the Rx Queue std::deque<QueuedRxTelegram> rx_telegrams_; // the Rx Queue
// emsesp::queue<QueuedRxTelegram> rx_telegrams_ = emsesp::queue<QueuedRxTelegram>(MAX_RX_TELEGRAMS); // the Rx Queue
}; };
class TxService : public EMSbus { class TxService : public EMSbus {
@@ -321,6 +312,9 @@ class TxService : public EMSbus {
if (telegram_fail_count_ == 0) { if (telegram_fail_count_ == 0) {
return 100; // all good, 100% return 100; // all good, 100%
} }
if (telegram_fail_count_ >= telegram_read_count_) {
return 100;
}
return (100 - (uint8_t)(((float)telegram_fail_count_ / telegram_read_count_ * 100))); return (100 - (uint8_t)(((float)telegram_fail_count_ / telegram_read_count_ * 100)));
} }
@@ -359,18 +353,6 @@ class TxService : public EMSbus {
return tx_telegrams_; return tx_telegrams_;
} }
/*
struct QueuedTxTelegram {
uint16_t id_;
std::shared_ptr<const Telegram> telegram_;
bool retry_; // true if its a retry
};
const emsesp::queue<QueuedTxTelegram> queue() const {
return tx_telegrams_;
}
*/
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
static constexpr uint8_t MAXIMUM_TX_RETRIES = 0; // when compiled with EMSESP_DEBUG don't retry static constexpr uint8_t MAXIMUM_TX_RETRIES = 0; // when compiled with EMSESP_DEBUG don't retry
#else #else
@@ -380,7 +362,6 @@ class TxService : public EMSbus {
private: private:
std::deque<QueuedTxTelegram> tx_telegrams_; // the Tx queue std::deque<QueuedTxTelegram> tx_telegrams_; // the Tx queue
// emsesp::queue<QueuedTxTelegram> tx_telegrams_ = emsesp::queue<QueuedTxTelegram>(MAX_TX_TELEGRAMS); // the Tx Queue
uint32_t telegram_read_count_ = 0; // # Tx successful reads uint32_t telegram_read_count_ = 0; // # Tx successful reads
uint32_t telegram_write_count_ = 0; // # Tx successful writes uint32_t telegram_write_count_ = 0; // # Tx successful writes

View File

@@ -21,8 +21,6 @@
#include "test.h" #include "test.h"
// create some fake test data
namespace emsesp { namespace emsesp {
// no shell // no shell
@@ -366,14 +364,39 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
return; return;
} }
if (command == "board_profile") {
shell.printfln(F("Testing board profile..."));
shell.invoke_command("system");
shell.invoke_command("set board_profile wemos");
shell.invoke_command("exit");
shell.invoke_command("call system settings");
}
if (command == "boiler") { if (command == "boiler") {
shell.printfln(F("Testing boiler...")); shell.printfln(F("Testing boiler..."));
Mqtt::ha_enabled(false);
Mqtt::nested_format(true);
run_test("boiler"); run_test("boiler");
shell.invoke_command("show devices"); shell.invoke_command("show devices");
shell.invoke_command("show"); shell.invoke_command("show");
// shell.invoke_command("call boiler info"); shell.invoke_command("call boiler info");
// shell.invoke_command("call system publish"); shell.invoke_command("call system publish");
// shell.invoke_command("show mqtt");
// test all permutations
EMSESP::mqtt_.incoming("ems-esp/boiler/wwonetime", "1");
EMSESP::mqtt_.incoming("ems-esp/boiler/wwonetime", "0");
EMSESP::mqtt_.incoming("ems-esp/boiler/wwonetime", "on");
EMSESP::mqtt_.incoming("ems-esp/boiler/heatingtemp", "24");
EMSESP::mqtt_.incoming("ems-esp/boiler/wwonetime", "test"); // should fail
EMSESP::mqtt_.incoming("ems-esp/boiler", "{\"cmd\":\"flowtemp\",\"id\":0,\"data\":22}");
EMSESP::mqtt_.incoming("ems-esp/boiler", "{\"cmd\":\"wwonetime\",\"id\":0,\"data\":1}");
EMSESP::mqtt_.incoming("ems-esp/boiler", "{\"cmd\":\"wwonetime\",\"id\":0,\"data\":\"off\"}");
EMSESP::mqtt_.incoming("ems-esp/boiler", "{\"cmd\":\"wwonetime\",\"hc\":1,\"data\":\"on\"}");
EMSESP::mqtt_.incoming("ems-esp/boiler", "{\"cmd\":\"wwonetime\",\"data\":\"on\",\"hc\":1}");
shell.invoke_command("show mqtt");
} }
if (command == "fr120") { if (command == "fr120") {

View File

@@ -32,8 +32,9 @@ namespace emsesp {
// #define EMSESP_TEST_DEFAULT "general" // #define EMSESP_TEST_DEFAULT "general"
// #define EMSESP_TEST_DEFAULT "boiler" // #define EMSESP_TEST_DEFAULT "boiler"
// #define EMSESP_TEST_DEFAULT "mqtt2" // #define EMSESP_TEST_DEFAULT "mqtt2"
#define EMSESP_TEST_DEFAULT "mqtt_nested" // #define EMSESP_TEST_DEFAULT "mqtt_nested"
// #define EMSESP_TEST_DEFAULT "ha" // #define EMSESP_TEST_DEFAULT "ha"
#define EMSESP_TEST_DEFAULT "board_profile"
class Test { class Test {
public: public:

View File

@@ -67,9 +67,11 @@ void IRAM_ATTR EMSuart::emsuart_rx_intr_handler(void * para) {
drop_next_rx_ = true; // we have a overflow drop_next_rx_ = true; // we have a overflow
} }
} }
if (length > 0 && length < EMS_MAXBUFFERSIZE) {
if (rxbuf[length - 1]) { // check if last byte is break if (rxbuf[length - 1]) { // check if last byte is break
length++; length++;
} }
}
if ((!drop_next_rx_) && ((length == 2) || (length > 4))) { if ((!drop_next_rx_) && ((length == 2) || (length > 4))) {
int baseType = 0; int baseType = 0;
xRingbufferSendFromISR(buf_handle_, rxbuf, length - 1, &baseType); xRingbufferSendFromISR(buf_handle_, rxbuf, length - 1, &baseType);

View File

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