Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
hpanther
2020-11-14 22:54:53 +01:00
66 changed files with 2648 additions and 1854 deletions

1
.gitignore vendored
View File

@@ -22,3 +22,4 @@ firmware
/lib/framework/WWWData.h /lib/framework/WWWData.h
/interface/build/ /interface/build/
/interface/node_modules/ /interface/node_modules/
.VSCodeCounter/

View File

@@ -1,13 +1,21 @@
# Changelog # Changelog
### Added ### Added
- function keys in editor: cursor, del, pos1, end. F1=help, F2=show, F10=report - function keys in editor: cursor, del, home, end. F1=help, F2=show, and other shortcuts
- SM100 pump working time and energy units
- heating curve parameters for RC300
- `wwonetime` for RC300 thermostat
### Fixed ### Fixed
- mixer IPM pumpstatus - mixer IPM pumpstatus
- Mixing devices in HA were incorrectly named
### Changed ### Changed
- optimized MQTT for HA to reduce mem fragmentation issues - optimized MQTT for HA to reduce mem fragmentation issues
- change syslog settings without reboot
- HA-config split in smaller blocks
- commands `fetch` and `publish [ha]` as call
- mqtt json package size
### Removed ### Removed
- old scripts - old scripts

31
debug_pio_local.ini Normal file
View File

@@ -0,0 +1,31 @@
[platformio]
; default_envs = esp32-local
default_envs = esp8266-local
[env]
; upload_port = COM3
upload_port = COM7
; upload_protocol = espota
; upload_flags =
; --port=8266
; --auth=ems-esp-neo
; upload_port = ems-esp.local
[common]
debug_flags = -DEMSESP_DEBUG
[env:esp32-local]
monitor_filters = esp32_exception_decoder
debug_tool = esp-prog
debug_init_break = tbreak setup
build_type = debug
extra_scripts =
; pre:scripts/build_interface.py
[env:esp8266-local]
monitor_filters = esp8266_exception_decoder
extra_scripts =
; pre:scripts/build_interface.py
scripts/main_script.py

View File

@@ -1,6 +1,25 @@
[platformio]
; default_envs = esp32-local
default_envs = esp8266-local
[env] [env]
upload_protocol = espota ; upload_port = COM3
upload_flags = upload_port = COM7
--port=8266
--auth=ems-esp-neo ; upload_protocol = espota
upload_port = ems-esp.local ; upload_flags =
; --port=8266
; --auth=ems-esp-neo
; upload_port = ems-esp.local
[common]
[env:esp32-local]
extra_scripts =
; pre:scripts/build_interface.py
[env:esp8266-local]
extra_scripts =
; pre:scripts/build_interface.py
scripts/main_script.py

File diff suppressed because it is too large Load Diff

View File

@@ -3,30 +3,30 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@material-ui/core": "^4.9.8", "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1", "@material-ui/icons": "^4.9.1",
"@types/jwt-decode": "^2.2.1", "@types/jwt-decode": "^3.1.0",
"@types/lodash": "^4.14.157", "@types/lodash": "^4.14.165",
"@types/node": "^12.12.32", "@types/node": "^12.12.32",
"@types/react": "^16.9.27", "@types/react": "^16.9.56",
"@types/react-dom": "^16.9.5", "@types/react-dom": "^16.9.9",
"@types/react-material-ui-form-validator": "^2.0.5", "@types/react-material-ui-form-validator": "^2.1.0",
"@types/react-router": "^5.1.3", "@types/react-router": "^5.1.8",
"@types/react-router-dom": "^5.1.3", "@types/react-router-dom": "^5.1.6",
"compression-webpack-plugin": "^4.0.0", "compression-webpack-plugin": "^4.0.0",
"jwt-decode": "^2.2.0", "jwt-decode": "^3.1.1",
"lodash": "^4.17.19", "lodash": "^4.17.20",
"mime-types": "^2.1.25", "mime-types": "^2.1.27",
"moment": "^2.26.0", "moment": "^2.29.1",
"notistack": "^0.9.17", "notistack": "^1.0.1",
"react": "^16.13.1", "react": "^16.14.0",
"react-dom": "^16.13.1", "react-dom": "^16.14.0",
"react-dropzone": "^11.0.1", "react-dropzone": "^11.2.4",
"react-form-validator-core": "^0.6.4", "react-form-validator-core": "^1.0.0",
"react-material-ui-form-validator": "^2.0.10", "react-material-ui-form-validator": "^2.1.1",
"react-router": "^5.1.2", "react-router": "^5.2.0",
"react-router-dom": "^5.1.2", "react-router-dom": "^5.2.0",
"react-scripts": "3.4.3", "react-scripts": "3.4.4",
"sockette": "^2.0.6", "sockette": "^2.0.6",
"typescript": "^4.0.2", "typescript": "^4.0.2",
"zlib": "^1.0.5" "zlib": "^1.0.5"

View File

@@ -9,7 +9,7 @@ import { AuthenticationContext, Me } from './AuthenticationContext';
import FullScreenLoading from '../components/FullScreenLoading'; import FullScreenLoading from '../components/FullScreenLoading';
import { withFeatures, WithFeaturesProps } from '../features/FeaturesContext'; import { withFeatures, WithFeaturesProps } from '../features/FeaturesContext';
export const decodeMeJWT = (accessToken: string): Me => jwtDecode(accessToken); export const decodeMeJWT = (accessToken: string): Me => jwtDecode(accessToken) as Me;
interface AuthenticationWrapperState { interface AuthenticationWrapperState {
context: AuthenticationContext; context: AuthenticationContext;
@@ -59,7 +59,7 @@ class AuthenticationWrapper extends React.Component<AuthenticationWrapperProps,
} }
refresh = () => { refresh = () => {
// proddy removed // commented out, always need security - proddy
// if (!this.props.features.security) { // if (!this.props.features.security) {
// this.setState({ initialized: true, context: { ...this.state.context, me: { admin: true, username: "admin" } } }); // this.setState({ initialized: true, context: { ...this.state.context, me: { admin: true, username: "admin" } } });
// return; // return;

View File

@@ -19,14 +19,14 @@ export const TIME_ZONES: TimeZones = {
"Africa/Brazzaville": "WAT-1", "Africa/Brazzaville": "WAT-1",
"Africa/Bujumbura": "CAT-2", "Africa/Bujumbura": "CAT-2",
"Africa/Cairo": "EET-2", "Africa/Cairo": "EET-2",
"Africa/Casablanca": "<+01>-1", "Africa/Casablanca": "UNK-1",
"Africa/Ceuta": "CET-1CEST,M3.5.0,M10.5.0/3", "Africa/Ceuta": "CET-1CEST,M3.5.0,M10.5.0/3",
"Africa/Conakry": "GMT0", "Africa/Conakry": "GMT0",
"Africa/Dakar": "GMT0", "Africa/Dakar": "GMT0",
"Africa/Dar_es_Salaam": "EAT-3", "Africa/Dar_es_Salaam": "EAT-3",
"Africa/Djibouti": "EAT-3", "Africa/Djibouti": "EAT-3",
"Africa/Douala": "WAT-1", "Africa/Douala": "WAT-1",
"Africa/El_Aaiun": "<+01>-1", "Africa/El_Aaiun": "UNK-1",
"Africa/Freetown": "GMT0", "Africa/Freetown": "GMT0",
"Africa/Gaborone": "CAT-2", "Africa/Gaborone": "CAT-2",
"Africa/Harare": "CAT-2", "Africa/Harare": "CAT-2",
@@ -62,63 +62,63 @@ export const TIME_ZONES: TimeZones = {
"America/Anchorage": "AKST9AKDT,M3.2.0,M11.1.0", "America/Anchorage": "AKST9AKDT,M3.2.0,M11.1.0",
"America/Anguilla": "AST4", "America/Anguilla": "AST4",
"America/Antigua": "AST4", "America/Antigua": "AST4",
"America/Araguaina": "<-03>3", "America/Araguaina": "UNK3",
"America/Argentina/Buenos_Aires": "<-03>3", "America/Argentina/Buenos_Aires": "UNK3",
"America/Argentina/Catamarca": "<-03>3", "America/Argentina/Catamarca": "UNK3",
"America/Argentina/Cordoba": "<-03>3", "America/Argentina/Cordoba": "UNK3",
"America/Argentina/Jujuy": "<-03>3", "America/Argentina/Jujuy": "UNK3",
"America/Argentina/La_Rioja": "<-03>3", "America/Argentina/La_Rioja": "UNK3",
"America/Argentina/Mendoza": "<-03>3", "America/Argentina/Mendoza": "UNK3",
"America/Argentina/Rio_Gallegos": "<-03>3", "America/Argentina/Rio_Gallegos": "UNK3",
"America/Argentina/Salta": "<-03>3", "America/Argentina/Salta": "UNK3",
"America/Argentina/San_Juan": "<-03>3", "America/Argentina/San_Juan": "UNK3",
"America/Argentina/San_Luis": "<-03>3", "America/Argentina/San_Luis": "UNK3",
"America/Argentina/Tucuman": "<-03>3", "America/Argentina/Tucuman": "UNK3",
"America/Argentina/Ushuaia": "<-03>3", "America/Argentina/Ushuaia": "UNK3",
"America/Aruba": "AST4", "America/Aruba": "AST4",
"America/Asuncion": "<-04>4<-03>,M10.1.0/0,M3.4.0/0", "America/Asuncion": "UNK4UNK,M10.1.0/0,M3.4.0/0",
"America/Atikokan": "EST5", "America/Atikokan": "EST5",
"America/Bahia": "<-03>3", "America/Bahia": "UNK3",
"America/Bahia_Banderas": "CST6CDT,M4.1.0,M10.5.0", "America/Bahia_Banderas": "CST6CDT,M4.1.0,M10.5.0",
"America/Barbados": "AST4", "America/Barbados": "AST4",
"America/Belem": "<-03>3", "America/Belem": "UNK3",
"America/Belize": "CST6", "America/Belize": "CST6",
"America/Blanc-Sablon": "AST4", "America/Blanc-Sablon": "AST4",
"America/Boa_Vista": "<-04>4", "America/Boa_Vista": "UNK4",
"America/Bogota": "<-05>5", "America/Bogota": "UNK5",
"America/Boise": "MST7MDT,M3.2.0,M11.1.0", "America/Boise": "MST7MDT,M3.2.0,M11.1.0",
"America/Cambridge_Bay": "MST7MDT,M3.2.0,M11.1.0", "America/Cambridge_Bay": "MST7MDT,M3.2.0,M11.1.0",
"America/Campo_Grande": "<-04>4", "America/Campo_Grande": "UNK4",
"America/Cancun": "EST5", "America/Cancun": "EST5",
"America/Caracas": "<-04>4", "America/Caracas": "UNK4",
"America/Cayenne": "<-03>3", "America/Cayenne": "UNK3",
"America/Cayman": "EST5", "America/Cayman": "EST5",
"America/Chicago": "CST6CDT,M3.2.0,M11.1.0", "America/Chicago": "CST6CDT,M3.2.0,M11.1.0",
"America/Chihuahua": "MST7MDT,M4.1.0,M10.5.0", "America/Chihuahua": "MST7MDT,M4.1.0,M10.5.0",
"America/Costa_Rica": "CST6", "America/Costa_Rica": "CST6",
"America/Creston": "MST7", "America/Creston": "MST7",
"America/Cuiaba": "<-04>4", "America/Cuiaba": "UNK4",
"America/Curacao": "AST4", "America/Curacao": "AST4",
"America/Danmarkshavn": "GMT0", "America/Danmarkshavn": "GMT0",
"America/Dawson": "PST8PDT,M3.2.0,M11.1.0", "America/Dawson": "MST7",
"America/Dawson_Creek": "MST7", "America/Dawson_Creek": "MST7",
"America/Denver": "MST7MDT,M3.2.0,M11.1.0", "America/Denver": "MST7MDT,M3.2.0,M11.1.0",
"America/Detroit": "EST5EDT,M3.2.0,M11.1.0", "America/Detroit": "EST5EDT,M3.2.0,M11.1.0",
"America/Dominica": "AST4", "America/Dominica": "AST4",
"America/Edmonton": "MST7MDT,M3.2.0,M11.1.0", "America/Edmonton": "MST7MDT,M3.2.0,M11.1.0",
"America/Eirunepe": "<-05>5", "America/Eirunepe": "UNK5",
"America/El_Salvador": "CST6", "America/El_Salvador": "CST6",
"America/Fortaleza": "<-03>3",
"America/Fort_Nelson": "MST7", "America/Fort_Nelson": "MST7",
"America/Fortaleza": "UNK3",
"America/Glace_Bay": "AST4ADT,M3.2.0,M11.1.0", "America/Glace_Bay": "AST4ADT,M3.2.0,M11.1.0",
"America/Godthab": "<-03>3<-02>,M3.5.0/-2,M10.5.0/-1", "America/Godthab": "UNK3UNK,M3.5.0/-2,M10.5.0/-1",
"America/Goose_Bay": "AST4ADT,M3.2.0,M11.1.0", "America/Goose_Bay": "AST4ADT,M3.2.0,M11.1.0",
"America/Grand_Turk": "EST5EDT,M3.2.0,M11.1.0", "America/Grand_Turk": "EST5EDT,M3.2.0,M11.1.0",
"America/Grenada": "AST4", "America/Grenada": "AST4",
"America/Guadeloupe": "AST4", "America/Guadeloupe": "AST4",
"America/Guatemala": "CST6", "America/Guatemala": "CST6",
"America/Guayaquil": "<-05>5", "America/Guayaquil": "UNK5",
"America/Guyana": "<-04>4", "America/Guyana": "UNK4",
"America/Halifax": "AST4ADT,M3.2.0,M11.1.0", "America/Halifax": "AST4ADT,M3.2.0,M11.1.0",
"America/Havana": "CST5CDT,M3.2.0/0,M11.1.0/1", "America/Havana": "CST5CDT,M3.2.0/0,M11.1.0/1",
"America/Hermosillo": "MST7", "America/Hermosillo": "MST7",
@@ -137,13 +137,13 @@ export const TIME_ZONES: TimeZones = {
"America/Kentucky/Louisville": "EST5EDT,M3.2.0,M11.1.0", "America/Kentucky/Louisville": "EST5EDT,M3.2.0,M11.1.0",
"America/Kentucky/Monticello": "EST5EDT,M3.2.0,M11.1.0", "America/Kentucky/Monticello": "EST5EDT,M3.2.0,M11.1.0",
"America/Kralendijk": "AST4", "America/Kralendijk": "AST4",
"America/La_Paz": "<-04>4", "America/La_Paz": "UNK4",
"America/Lima": "<-05>5", "America/Lima": "UNK5",
"America/Los_Angeles": "PST8PDT,M3.2.0,M11.1.0", "America/Los_Angeles": "PST8PDT,M3.2.0,M11.1.0",
"America/Lower_Princes": "AST4", "America/Lower_Princes": "AST4",
"America/Maceio": "<-03>3", "America/Maceio": "UNK3",
"America/Managua": "CST6", "America/Managua": "CST6",
"America/Manaus": "<-04>4", "America/Manaus": "UNK4",
"America/Marigot": "AST4", "America/Marigot": "AST4",
"America/Martinique": "AST4", "America/Martinique": "AST4",
"America/Matamoros": "CST6CDT,M3.2.0,M11.1.0", "America/Matamoros": "CST6CDT,M3.2.0,M11.1.0",
@@ -152,41 +152,41 @@ export const TIME_ZONES: TimeZones = {
"America/Merida": "CST6CDT,M4.1.0,M10.5.0", "America/Merida": "CST6CDT,M4.1.0,M10.5.0",
"America/Metlakatla": "AKST9AKDT,M3.2.0,M11.1.0", "America/Metlakatla": "AKST9AKDT,M3.2.0,M11.1.0",
"America/Mexico_City": "CST6CDT,M4.1.0,M10.5.0", "America/Mexico_City": "CST6CDT,M4.1.0,M10.5.0",
"America/Miquelon": "<-03>3<-02>,M3.2.0,M11.1.0", "America/Miquelon": "UNK3UNK,M3.2.0,M11.1.0",
"America/Moncton": "AST4ADT,M3.2.0,M11.1.0", "America/Moncton": "AST4ADT,M3.2.0,M11.1.0",
"America/Monterrey": "CST6CDT,M4.1.0,M10.5.0", "America/Monterrey": "CST6CDT,M4.1.0,M10.5.0",
"America/Montevideo": "<-03>3", "America/Montevideo": "UNK3",
"America/Montreal": "EST5EDT,M3.2.0,M11.1.0", "America/Montreal": "EST5EDT,M3.2.0,M11.1.0",
"America/Montserrat": "AST4", "America/Montserrat": "AST4",
"America/Nassau": "EST5EDT,M3.2.0,M11.1.0", "America/Nassau": "EST5EDT,M3.2.0,M11.1.0",
"America/New_York": "EST5EDT,M3.2.0,M11.1.0", "America/New_York": "EST5EDT,M3.2.0,M11.1.0",
"America/Nipigon": "EST5EDT,M3.2.0,M11.1.0", "America/Nipigon": "EST5EDT,M3.2.0,M11.1.0",
"America/Nome": "AKST9AKDT,M3.2.0,M11.1.0", "America/Nome": "AKST9AKDT,M3.2.0,M11.1.0",
"America/Noronha": "<-02>2", "America/Noronha": "UNK2",
"America/North_Dakota/Beulah": "CST6CDT,M3.2.0,M11.1.0", "America/North_Dakota/Beulah": "CST6CDT,M3.2.0,M11.1.0",
"America/North_Dakota/Center": "CST6CDT,M3.2.0,M11.1.0", "America/North_Dakota/Center": "CST6CDT,M3.2.0,M11.1.0",
"America/North_Dakota/New_Salem": "CST6CDT,M3.2.0,M11.1.0", "America/North_Dakota/New_Salem": "CST6CDT,M3.2.0,M11.1.0",
"America/Ojinaga": "MST7MDT,M3.2.0,M11.1.0", "America/Ojinaga": "MST7MDT,M3.2.0,M11.1.0",
"America/Panama": "EST5", "America/Panama": "EST5",
"America/Pangnirtung": "EST5EDT,M3.2.0,M11.1.0", "America/Pangnirtung": "EST5EDT,M3.2.0,M11.1.0",
"America/Paramaribo": "<-03>3", "America/Paramaribo": "UNK3",
"America/Phoenix": "MST7", "America/Phoenix": "MST7",
"America/Port-au-Prince": "EST5EDT,M3.2.0,M11.1.0", "America/Port-au-Prince": "EST5EDT,M3.2.0,M11.1.0",
"America/Port_of_Spain": "AST4", "America/Port_of_Spain": "AST4",
"America/Porto_Velho": "<-04>4", "America/Porto_Velho": "UNK4",
"America/Puerto_Rico": "AST4", "America/Puerto_Rico": "AST4",
"America/Punta_Arenas": "<-03>3", "America/Punta_Arenas": "UNK3",
"America/Rainy_River": "CST6CDT,M3.2.0,M11.1.0", "America/Rainy_River": "CST6CDT,M3.2.0,M11.1.0",
"America/Rankin_Inlet": "CST6CDT,M3.2.0,M11.1.0", "America/Rankin_Inlet": "CST6CDT,M3.2.0,M11.1.0",
"America/Recife": "<-03>3", "America/Recife": "UNK3",
"America/Regina": "CST6", "America/Regina": "CST6",
"America/Resolute": "CST6CDT,M3.2.0,M11.1.0", "America/Resolute": "CST6CDT,M3.2.0,M11.1.0",
"America/Rio_Branco": "<-05>5", "America/Rio_Branco": "UNK5",
"America/Santarem": "<-03>3", "America/Santarem": "UNK3",
"America/Santiago": "<-04>4<-03>,M9.1.6/24,M4.1.6/24", "America/Santiago": "UNK4UNK,M9.1.6/24,M4.1.6/24",
"America/Santo_Domingo": "AST4", "America/Santo_Domingo": "AST4",
"America/Sao_Paulo": "<-03>3", "America/Sao_Paulo": "UNK3",
"America/Scoresbysund": "<-01>1<+00>,M3.5.0/0,M10.5.0/1", "America/Scoresbysund": "UNK1UNK,M3.5.0/0,M10.5.0/1",
"America/Sitka": "AKST9AKDT,M3.2.0,M11.1.0", "America/Sitka": "AKST9AKDT,M3.2.0,M11.1.0",
"America/St_Barthelemy": "AST4", "America/St_Barthelemy": "AST4",
"America/St_Johns": "NST3:30NDT,M3.2.0,M11.1.0", "America/St_Johns": "NST3:30NDT,M3.2.0,M11.1.0",
@@ -202,129 +202,164 @@ export const TIME_ZONES: TimeZones = {
"America/Toronto": "EST5EDT,M3.2.0,M11.1.0", "America/Toronto": "EST5EDT,M3.2.0,M11.1.0",
"America/Tortola": "AST4", "America/Tortola": "AST4",
"America/Vancouver": "PST8PDT,M3.2.0,M11.1.0", "America/Vancouver": "PST8PDT,M3.2.0,M11.1.0",
"America/Whitehorse": "PST8PDT,M3.2.0,M11.1.0", "America/Whitehorse": "MST7",
"America/Winnipeg": "CST6CDT,M3.2.0,M11.1.0", "America/Winnipeg": "CST6CDT,M3.2.0,M11.1.0",
"America/Yakutat": "AKST9AKDT,M3.2.0,M11.1.0", "America/Yakutat": "AKST9AKDT,M3.2.0,M11.1.0",
"America/Yellowknife": "MST7MDT,M3.2.0,M11.1.0", "America/Yellowknife": "MST7MDT,M3.2.0,M11.1.0",
"Antarctica/Casey": "<+08>-8", "Antarctica/Casey": "UNK-8",
"Antarctica/Davis": "<+07>-7", "Antarctica/Davis": "UNK-7",
"Antarctica/DumontDUrville": "<+10>-10", "Antarctica/DumontDUrville": "UNK-10",
"Antarctica/Macquarie": "<+11>-11", "Antarctica/Macquarie": "UNK-11",
"Antarctica/Mawson": "<+05>-5", "Antarctica/Mawson": "UNK-5",
"Antarctica/McMurdo": "NZST-12NZDT,M9.5.0,M4.1.0/3", "Antarctica/McMurdo": "NZST-12NZDT,M9.5.0,M4.1.0/3",
"Antarctica/Palmer": "<-03>3", "Antarctica/Palmer": "UNK3",
"Antarctica/Rothera": "<-03>3", "Antarctica/Rothera": "UNK3",
"Antarctica/Syowa": "<+03>-3", "Antarctica/Syowa": "UNK-3",
"Antarctica/Troll": "<+00>0<+02>-2,M3.5.0/1,M10.5.0/3", "Antarctica/Troll": "UNK0UNK-2,M3.5.0/1,M10.5.0/3",
"Antarctica/Vostok": "<+06>-6", "Antarctica/Vostok": "UNK-6",
"Arctic/Longyearbyen": "CET-1CEST,M3.5.0,M10.5.0/3", "Arctic/Longyearbyen": "CET-1CEST,M3.5.0,M10.5.0/3",
"Asia/Aden": "<+03>-3", "Asia/Aden": "UNK-3",
"Asia/Almaty": "<+06>-6", "Asia/Almaty": "UNK-6",
"Asia/Amman": "EET-2EEST,M3.5.4/24,M10.5.5/1", "Asia/Amman": "EET-2EEST,M3.5.4/24,M10.5.5/1",
"Asia/Anadyr": "<+12>-12", "Asia/Anadyr": "UNK-12",
"Asia/Aqtau": "<+05>-5", "Asia/Aqtau": "UNK-5",
"Asia/Aqtobe": "<+05>-5", "Asia/Aqtobe": "UNK-5",
"Asia/Ashgabat": "<+05>-5", "Asia/Ashgabat": "UNK-5",
"Asia/Atyrau": "<+05>-5", "Asia/Atyrau": "UNK-5",
"Asia/Baghdad": "<+03>-3", "Asia/Baghdad": "UNK-3",
"Asia/Bahrain": "<+03>-3", "Asia/Bahrain": "UNK-3",
"Asia/Baku": "<+04>-4", "Asia/Baku": "UNK-4",
"Asia/Bangkok": "<+07>-7", "Asia/Bangkok": "UNK-7",
"Asia/Barnaul": "<+07>-7", "Asia/Barnaul": "UNK-7",
"Asia/Beirut": "EET-2EEST,M3.5.0/0,M10.5.0/0", "Asia/Beirut": "EET-2EEST,M3.5.0/0,M10.5.0/0",
"Asia/Bishkek": "<+06>-6", "Asia/Bishkek": "UNK-6",
"Asia/Brunei": "<+08>-8", "Asia/Brunei": "UNK-8",
"Asia/Chita": "<+09>-9", "Asia/Chita": "UNK-9",
"Asia/Choibalsan": "<+08>-8", "Asia/Choibalsan": "UNK-8",
"Asia/Colombo": "<+0530>-5:30", "Asia/Colombo": "UNK-5:30",
"Asia/Damascus": "EET-2EEST,M3.5.5/0,M10.5.5/0", "Asia/Damascus": "EET-2EEST,M3.5.5/0,M10.5.5/0",
"Asia/Dhaka": "<+06>-6", "Asia/Dhaka": "UNK-6",
"Asia/Dili": "<+09>-9", "Asia/Dili": "UNK-9",
"Asia/Dubai": "<+04>-4", "Asia/Dubai": "UNK-4",
"Asia/Dushanbe": "<+05>-5", "Asia/Dushanbe": "UNK-5",
"Asia/Famagusta": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Asia/Famagusta": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Asia/Gaza": "EET-2EEST,M3.5.5/0,M10.5.6/1", "Asia/Gaza": "EET-2EEST,M3.5.5/0,M10.5.6/1",
"Asia/Hebron": "EET-2EEST,M3.5.5/0,M10.5.6/1", "Asia/Hebron": "EET-2EEST,M3.5.5/0,M10.5.6/1",
"Asia/Ho_Chi_Minh": "<+07>-7", "Asia/Ho_Chi_Minh": "UNK-7",
"Asia/Hong_Kong": "HKT-8", "Asia/Hong_Kong": "HKT-8",
"Asia/Hovd": "<+07>-7", "Asia/Hovd": "UNK-7",
"Asia/Irkutsk": "<+08>-8", "Asia/Irkutsk": "UNK-8",
"Asia/Jakarta": "WIB-7", "Asia/Jakarta": "WIB-7",
"Asia/Jayapura": "WIT-9", "Asia/Jayapura": "WIT-9",
"Asia/Jerusalem": "IST-2IDT,M3.4.4/26,M10.5.0", "Asia/Jerusalem": "IST-2IDT,M3.4.4/26,M10.5.0",
"Asia/Kabul": "<+0430>-4:30", "Asia/Kabul": "UNK-4:30",
"Asia/Kamchatka": "<+12>-12", "Asia/Kamchatka": "UNK-12",
"Asia/Karachi": "PKT-5", "Asia/Karachi": "PKT-5",
"Asia/Kathmandu": "<+0545>-5:45", "Asia/Kathmandu": "UNK-5:45",
"Asia/Khandyga": "<+09>-9", "Asia/Khandyga": "UNK-9",
"Asia/Kolkata": "IST-5:30", "Asia/Kolkata": "IST-5:30",
"Asia/Krasnoyarsk": "<+07>-7", "Asia/Krasnoyarsk": "UNK-7",
"Asia/Kuala_Lumpur": "<+08>-8", "Asia/Kuala_Lumpur": "UNK-8",
"Asia/Kuching": "<+08>-8", "Asia/Kuching": "UNK-8",
"Asia/Kuwait": "<+03>-3", "Asia/Kuwait": "UNK-3",
"Asia/Macau": "CST-8", "Asia/Macau": "CST-8",
"Asia/Magadan": "<+11>-11", "Asia/Magadan": "UNK-11",
"Asia/Makassar": "WITA-8", "Asia/Makassar": "WITA-8",
"Asia/Manila": "PST-8", "Asia/Manila": "PST-8",
"Asia/Muscat": "<+04>-4", "Asia/Muscat": "UNK-4",
"Asia/Nicosia": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Asia/Nicosia": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Asia/Novokuznetsk": "<+07>-7", "Asia/Novokuznetsk": "UNK-7",
"Asia/Novosibirsk": "<+07>-7", "Asia/Novosibirsk": "UNK-7",
"Asia/Omsk": "<+06>-6", "Asia/Omsk": "UNK-6",
"Asia/Oral": "<+05>-5", "Asia/Oral": "UNK-5",
"Asia/Phnom_Penh": "<+07>-7", "Asia/Phnom_Penh": "UNK-7",
"Asia/Pontianak": "WIB-7", "Asia/Pontianak": "WIB-7",
"Asia/Pyongyang": "KST-9", "Asia/Pyongyang": "KST-9",
"Asia/Qatar": "<+03>-3", "Asia/Qatar": "UNK-3",
"Asia/Qyzylorda": "<+05>-5", "Asia/Qyzylorda": "UNK-5",
"Asia/Riyadh": "<+03>-3", "Asia/Riyadh": "UNK-3",
"Asia/Sakhalin": "<+11>-11", "Asia/Sakhalin": "UNK-11",
"Asia/Samarkand": "<+05>-5", "Asia/Samarkand": "UNK-5",
"Asia/Seoul": "KST-9", "Asia/Seoul": "KST-9",
"Asia/Shanghai": "CST-8", "Asia/Shanghai": "CST-8",
"Asia/Singapore": "<+08>-8", "Asia/Singapore": "UNK-8",
"Asia/Srednekolymsk": "<+11>-11", "Asia/Srednekolymsk": "UNK-11",
"Asia/Taipei": "CST-8", "Asia/Taipei": "CST-8",
"Asia/Tashkent": "<+05>-5", "Asia/Tashkent": "UNK-5",
"Asia/Tbilisi": "<+04>-4", "Asia/Tbilisi": "UNK-4",
"Asia/Tehran": "<+0330>-3:30<+0430>,J79/24,J263/24", "Asia/Tehran": "UNK-3:30UNK,J79/24,J263/24",
"Asia/Thimphu": "<+06>-6", "Asia/Thimphu": "UNK-6",
"Asia/Tokyo": "JST-9", "Asia/Tokyo": "JST-9",
"Asia/Tomsk": "<+07>-7", "Asia/Tomsk": "UNK-7",
"Asia/Ulaanbaatar": "<+08>-8", "Asia/Ulaanbaatar": "UNK-8",
"Asia/Urumqi": "<+06>-6", "Asia/Urumqi": "UNK-6",
"Asia/Ust-Nera": "<+10>-10", "Asia/Ust-Nera": "UNK-10",
"Asia/Vientiane": "<+07>-7", "Asia/Vientiane": "UNK-7",
"Asia/Vladivostok": "<+10>-10", "Asia/Vladivostok": "UNK-10",
"Asia/Yakutsk": "<+09>-9", "Asia/Yakutsk": "UNK-9",
"Asia/Yangon": "<+0630>-6:30", "Asia/Yangon": "UNK-6:30",
"Asia/Yekaterinburg": "<+05>-5", "Asia/Yekaterinburg": "UNK-5",
"Asia/Yerevan": "<+04>-4", "Asia/Yerevan": "UNK-4",
"Atlantic/Azores": "<-01>1<+00>,M3.5.0/0,M10.5.0/1", "Atlantic/Azores": "UNK1UNK,M3.5.0/0,M10.5.0/1",
"Atlantic/Bermuda": "AST4ADT,M3.2.0,M11.1.0", "Atlantic/Bermuda": "AST4ADT,M3.2.0,M11.1.0",
"Atlantic/Canary": "WET0WEST,M3.5.0/1,M10.5.0", "Atlantic/Canary": "WET0WEST,M3.5.0/1,M10.5.0",
"Atlantic/Cape_Verde": "<-01>1", "Atlantic/Cape_Verde": "UNK1",
"Atlantic/Faroe": "WET0WEST,M3.5.0/1,M10.5.0", "Atlantic/Faroe": "WET0WEST,M3.5.0/1,M10.5.0",
"Atlantic/Madeira": "WET0WEST,M3.5.0/1,M10.5.0", "Atlantic/Madeira": "WET0WEST,M3.5.0/1,M10.5.0",
"Atlantic/Reykjavik": "GMT0", "Atlantic/Reykjavik": "GMT0",
"Atlantic/South_Georgia": "<-02>2", "Atlantic/South_Georgia": "UNK2",
"Atlantic/Stanley": "<-03>3",
"Atlantic/St_Helena": "GMT0", "Atlantic/St_Helena": "GMT0",
"Atlantic/Stanley": "UNK3",
"Australia/Adelaide": "ACST-9:30ACDT,M10.1.0,M4.1.0/3", "Australia/Adelaide": "ACST-9:30ACDT,M10.1.0,M4.1.0/3",
"Australia/Brisbane": "AEST-10", "Australia/Brisbane": "AEST-10",
"Australia/Broken_Hill": "ACST-9:30ACDT,M10.1.0,M4.1.0/3", "Australia/Broken_Hill": "ACST-9:30ACDT,M10.1.0,M4.1.0/3",
"Australia/Currie": "AEST-10AEDT,M10.1.0,M4.1.0/3", "Australia/Currie": "AEST-10AEDT,M10.1.0,M4.1.0/3",
"Australia/Darwin": "ACST-9:30", "Australia/Darwin": "ACST-9:30",
"Australia/Eucla": "<+0845>-8:45", "Australia/Eucla": "UNK-8:45",
"Australia/Hobart": "AEST-10AEDT,M10.1.0,M4.1.0/3", "Australia/Hobart": "AEST-10AEDT,M10.1.0,M4.1.0/3",
"Australia/Lindeman": "AEST-10", "Australia/Lindeman": "AEST-10",
"Australia/Lord_Howe": "<+1030>-10:30<+11>-11,M10.1.0,M4.1.0", "Australia/Lord_Howe": "UNK-10:30UNK-11,M10.1.0,M4.1.0",
"Australia/Melbourne": "AEST-10AEDT,M10.1.0,M4.1.0/3", "Australia/Melbourne": "AEST-10AEDT,M10.1.0,M4.1.0/3",
"Australia/Perth": "AWST-8", "Australia/Perth": "AWST-8",
"Australia/Sydney": "AEST-10AEDT,M10.1.0,M4.1.0/3", "Australia/Sydney": "AEST-10AEDT,M10.1.0,M4.1.0/3",
"Etc/GMT": "GMT0",
"Etc/GMT+0": "GMT0",
"Etc/GMT+1": "UNK1",
"Etc/GMT+10": "UNK10",
"Etc/GMT+11": "UNK11",
"Etc/GMT+12": "UNK12",
"Etc/GMT+2": "UNK2",
"Etc/GMT+3": "UNK3",
"Etc/GMT+4": "UNK4",
"Etc/GMT+5": "UNK5",
"Etc/GMT+6": "UNK6",
"Etc/GMT+7": "UNK7",
"Etc/GMT+8": "UNK8",
"Etc/GMT+9": "UNK9",
"Etc/GMT-0": "GMT0",
"Etc/GMT-1": "UNK-1",
"Etc/GMT-10": "UNK-10",
"Etc/GMT-11": "UNK-11",
"Etc/GMT-12": "UNK-12",
"Etc/GMT-13": "UNK-13",
"Etc/GMT-14": "UNK-14",
"Etc/GMT-2": "UNK-2",
"Etc/GMT-3": "UNK-3",
"Etc/GMT-4": "UNK-4",
"Etc/GMT-5": "UNK-5",
"Etc/GMT-6": "UNK-6",
"Etc/GMT-7": "UNK-7",
"Etc/GMT-8": "UNK-8",
"Etc/GMT-9": "UNK-9",
"Etc/GMT0": "GMT0",
"Etc/Greenwich": "GMT0",
"Etc/UCT": "UTC0",
"Etc/UTC": "UTC0",
"Etc/Universal": "UTC0",
"Etc/Zulu": "UTC0",
"Europe/Amsterdam": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Amsterdam": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Andorra": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Andorra": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Astrakhan": "<+04>-4", "Europe/Astrakhan": "UNK-4",
"Europe/Athens": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Europe/Athens": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Belgrade": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Belgrade": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Berlin": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Berlin": "CET-1CEST,M3.5.0,M10.5.0/3",
@@ -340,11 +375,11 @@ export const TIME_ZONES: TimeZones = {
"Europe/Guernsey": "GMT0BST,M3.5.0/1,M10.5.0", "Europe/Guernsey": "GMT0BST,M3.5.0/1,M10.5.0",
"Europe/Helsinki": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Europe/Helsinki": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Isle_of_Man": "GMT0BST,M3.5.0/1,M10.5.0", "Europe/Isle_of_Man": "GMT0BST,M3.5.0/1,M10.5.0",
"Europe/Istanbul": "<+03>-3", "Europe/Istanbul": "UNK-3",
"Europe/Jersey": "GMT0BST,M3.5.0/1,M10.5.0", "Europe/Jersey": "GMT0BST,M3.5.0/1,M10.5.0",
"Europe/Kaliningrad": "EET-2", "Europe/Kaliningrad": "EET-2",
"Europe/Kiev": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Europe/Kiev": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Kirov": "<+03>-3", "Europe/Kirov": "UNK-3",
"Europe/Lisbon": "WET0WEST,M3.5.0/1,M10.5.0", "Europe/Lisbon": "WET0WEST,M3.5.0/1,M10.5.0",
"Europe/Ljubljana": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Ljubljana": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/London": "GMT0BST,M3.5.0/1,M10.5.0", "Europe/London": "GMT0BST,M3.5.0/1,M10.5.0",
@@ -352,7 +387,7 @@ export const TIME_ZONES: TimeZones = {
"Europe/Madrid": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Madrid": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Malta": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Malta": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Mariehamn": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Europe/Mariehamn": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Minsk": "<+03>-3", "Europe/Minsk": "UNK-3",
"Europe/Monaco": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Monaco": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Moscow": "MSK-3", "Europe/Moscow": "MSK-3",
"Europe/Oslo": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Oslo": "CET-1CEST,M3.5.0,M10.5.0/3",
@@ -361,111 +396,76 @@ export const TIME_ZONES: TimeZones = {
"Europe/Prague": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Prague": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Riga": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Europe/Riga": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Rome": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Rome": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Samara": "<+04>-4", "Europe/Samara": "UNK-4",
"Europe/San_Marino": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/San_Marino": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Sarajevo": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Sarajevo": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Saratov": "<+04>-4", "Europe/Saratov": "UNK-4",
"Europe/Simferopol": "MSK-3", "Europe/Simferopol": "MSK-3",
"Europe/Skopje": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Skopje": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Sofia": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Europe/Sofia": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Stockholm": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Stockholm": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Tallinn": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Europe/Tallinn": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Tirane": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Tirane": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Ulyanovsk": "<+04>-4", "Europe/Ulyanovsk": "UNK-4",
"Europe/Uzhgorod": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Europe/Uzhgorod": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Vaduz": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Vaduz": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Vatican": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Vatican": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Vienna": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Vienna": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Vilnius": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Europe/Vilnius": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Volgograd": "<+04>-4", "Europe/Volgograd": "UNK-4",
"Europe/Warsaw": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Warsaw": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Zagreb": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Zagreb": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Zaporozhye": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Europe/Zaporozhye": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Zurich": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Zurich": "CET-1CEST,M3.5.0,M10.5.0/3",
"Indian/Antananarivo": "EAT-3", "Indian/Antananarivo": "EAT-3",
"Indian/Chagos": "<+06>-6", "Indian/Chagos": "UNK-6",
"Indian/Christmas": "<+07>-7", "Indian/Christmas": "UNK-7",
"Indian/Cocos": "<+0630>-6:30", "Indian/Cocos": "UNK-6:30",
"Indian/Comoro": "EAT-3", "Indian/Comoro": "EAT-3",
"Indian/Kerguelen": "<+05>-5", "Indian/Kerguelen": "UNK-5",
"Indian/Mahe": "<+04>-4", "Indian/Mahe": "UNK-4",
"Indian/Maldives": "<+05>-5", "Indian/Maldives": "UNK-5",
"Indian/Mauritius": "<+04>-4", "Indian/Mauritius": "UNK-4",
"Indian/Mayotte": "EAT-3", "Indian/Mayotte": "EAT-3",
"Indian/Reunion": "<+04>-4", "Indian/Reunion": "UNK-4",
"Pacific/Apia": "<+13>-13<+14>,M9.5.0/3,M4.1.0/4", "Pacific/Apia": "UNK-13UNK,M9.5.0/3,M4.1.0/4",
"Pacific/Auckland": "NZST-12NZDT,M9.5.0,M4.1.0/3", "Pacific/Auckland": "NZST-12NZDT,M9.5.0,M4.1.0/3",
"Pacific/Bougainville": "<+11>-11", "Pacific/Bougainville": "UNK-11",
"Pacific/Chatham": "<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45", "Pacific/Chatham": "UNK-12:45UNK,M9.5.0/2:45,M4.1.0/3:45",
"Pacific/Chuuk": "<+10>-10", "Pacific/Chuuk": "UNK-10",
"Pacific/Easter": "<-06>6<-05>,M9.1.6/22,M4.1.6/22", "Pacific/Easter": "UNK6UNK,M9.1.6/22,M4.1.6/22",
"Pacific/Efate": "<+11>-11", "Pacific/Efate": "UNK-11",
"Pacific/Enderbury": "<+13>-13", "Pacific/Enderbury": "UNK-13",
"Pacific/Fakaofo": "<+13>-13", "Pacific/Fakaofo": "UNK-13",
"Pacific/Fiji": "<+12>-12<+13>,M11.2.0,M1.2.3/99", "Pacific/Fiji": "UNK-12UNK,M11.2.0,M1.2.3/99",
"Pacific/Funafuti": "<+12>-12", "Pacific/Funafuti": "UNK-12",
"Pacific/Galapagos": "<-06>6", "Pacific/Galapagos": "UNK6",
"Pacific/Gambier": "<-09>9", "Pacific/Gambier": "UNK9",
"Pacific/Guadalcanal": "<+11>-11", "Pacific/Guadalcanal": "UNK-11",
"Pacific/Guam": "ChST-10", "Pacific/Guam": "ChST-10",
"Pacific/Honolulu": "HST10", "Pacific/Honolulu": "HST10",
"Pacific/Kiritimati": "<+14>-14", "Pacific/Kiritimati": "UNK-14",
"Pacific/Kosrae": "<+11>-11", "Pacific/Kosrae": "UNK-11",
"Pacific/Kwajalein": "<+12>-12", "Pacific/Kwajalein": "UNK-12",
"Pacific/Majuro": "<+12>-12", "Pacific/Majuro": "UNK-12",
"Pacific/Marquesas": "<-0930>9:30", "Pacific/Marquesas": "UNK9:30",
"Pacific/Midway": "SST11", "Pacific/Midway": "SST11",
"Pacific/Nauru": "<+12>-12", "Pacific/Nauru": "UNK-12",
"Pacific/Niue": "<-11>11", "Pacific/Niue": "UNK11",
"Pacific/Norfolk": "<+11>-11<+12>,M10.1.0,M4.1.0/3", "Pacific/Norfolk": "UNK-11UNK,M10.1.0,M4.1.0/3",
"Pacific/Noumea": "<+11>-11", "Pacific/Noumea": "UNK-11",
"Pacific/Pago_Pago": "SST11", "Pacific/Pago_Pago": "SST11",
"Pacific/Palau": "<+09>-9", "Pacific/Palau": "UNK-9",
"Pacific/Pitcairn": "<-08>8", "Pacific/Pitcairn": "UNK8",
"Pacific/Pohnpei": "<+11>-11", "Pacific/Pohnpei": "UNK-11",
"Pacific/Port_Moresby": "<+10>-10", "Pacific/Port_Moresby": "UNK-10",
"Pacific/Rarotonga": "<-10>10", "Pacific/Rarotonga": "UNK10",
"Pacific/Saipan": "ChST-10", "Pacific/Saipan": "ChST-10",
"Pacific/Tahiti": "<-10>10", "Pacific/Tahiti": "UNK10",
"Pacific/Tarawa": "<+12>-12", "Pacific/Tarawa": "UNK-12",
"Pacific/Tongatapu": "<+13>-13", "Pacific/Tongatapu": "UNK-13",
"Pacific/Wake": "<+12>-12", "Pacific/Wake": "UNK-12",
"Pacific/Wallis": "<+12>-12", "Pacific/Wallis": "UNK-12"
"Etc/GMT": "GMT0",
"Etc/GMT-0": "GMT0",
"Etc/GMT-1": "<+01>-1",
"Etc/GMT-2": "<+02>-2",
"Etc/GMT-3": "<+03>-3",
"Etc/GMT-4": "<+04>-4",
"Etc/GMT-5": "<+05>-5",
"Etc/GMT-6": "<+06>-6",
"Etc/GMT-7": "<+07>-7",
"Etc/GMT-8": "<+08>-8",
"Etc/GMT-9": "<+09>-9",
"Etc/GMT-10": "<+10>-10",
"Etc/GMT-11": "<+11>-11",
"Etc/GMT-12": "<+12>-12",
"Etc/GMT-13": "<+13>-13",
"Etc/GMT-14": "<+14>-14",
"Etc/GMT0": "GMT0",
"Etc/GMT+0": "GMT0",
"Etc/GMT+1": "<-01>1",
"Etc/GMT+2": "<-02>2",
"Etc/GMT+3": "<-03>3",
"Etc/GMT+4": "<-04>4",
"Etc/GMT+5": "<-05>5",
"Etc/GMT+6": "<-06>6",
"Etc/GMT+7": "<-07>7",
"Etc/GMT+8": "<-08>8",
"Etc/GMT+9": "<-09>9",
"Etc/GMT+10": "<-10>10",
"Etc/GMT+11": "<-11>11",
"Etc/GMT+12": "<-12>12",
"Etc/UCT": "UTC0",
"Etc/UTC": "UTC0",
"Etc/Greenwich": "GMT0",
"Etc/Universal": "UTC0",
"Etc/Zulu": "UTC0"
} }
export function selectedTimeZone(label: string, format: string) { export function selectedTimeZone(label: string, format: string) {

View File

@@ -209,11 +209,21 @@ function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps)
<Typography variant="h6" color="primary" > <Typography variant="h6" color="primary" >
Syslog Syslog
</Typography> </Typography>
<BlockFormControlLabel
control={
<Checkbox
checked={data.syslog_enabled}
onChange={handleValueChange('syslog_enabled')}
value="syslog_enabled"
/>
}
label="Enable Syslog"
/>
<TextValidator <TextValidator
validators={['isIPOrHostname']} validators={['isIPOrHostname']}
errorMessages={["Not a valid IP address or hostname"]} errorMessages={["Not a valid IP address or hostname"]}
name="syslog_host" name="syslog_host"
label="Syslog IP/Host (optional)" label="Syslog IP/Host"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.syslog_host} value={data.syslog_host}
@@ -229,12 +239,13 @@ function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps)
margin="normal"> margin="normal">
<MenuItem value={-1}>OFF</MenuItem> <MenuItem value={-1}>OFF</MenuItem>
<MenuItem value={3}>ERR</MenuItem> <MenuItem value={3}>ERR</MenuItem>
<MenuItem value={5}>NOTICE</MenuItem>
<MenuItem value={6}>INFO</MenuItem> <MenuItem value={6}>INFO</MenuItem>
<MenuItem value={7}>DEBUG</MenuItem> <MenuItem value={7}>DEBUG</MenuItem>
</SelectValidator> </SelectValidator>
<TextValidator <TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']} validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']}
errorMessages={['Syslog Mark is required', "Must be a number", "Must be 0 or higher (0=off)", "Max value is 65535"]} errorMessages={['Syslog Mark is required', "Must be a number", "Must be 0 or higher", "Max value is 10"]}
name="syslog_mark_interval" name="syslog_mark_interval"
label="Syslog Mark Interval (seconds, 0=off)" label="Syslog Mark Interval (seconds, 0=off)"
fullWidth fullWidth

View File

@@ -1,6 +1,7 @@
export interface EMSESPSettings { export interface EMSESPSettings {
tx_mode: number; tx_mode: number;
ems_bus_id: number; ems_bus_id: number;
syslog_enabled: boolean;
syslog_level: number; syslog_level: number;
syslog_mark_interval: number; syslog_mark_interval: number;
syslog_host: string; syslog_host: string;

View File

@@ -1,6 +1,13 @@
ArduinoJson: change log ArduinoJson: change log
======================= =======================
v6.17.1 (2020-11-07)
-------
* Fixed error `ambiguous overload for 'operator|'` (issue #1411)
* Fixed `operator|(MemberProxy, JsonObject)` (issue #1415)
* Allowed more than 32767 values in non-embedded mode (issue #1414)
v6.17.0 (2020-10-19) v6.17.0 (2020-10-19)
------- -------

View File

@@ -0,0 +1,11 @@
# Contribution to ArduinoJson
First, thank you for taking the time to contribute to this project.
You can submit changes via GitHub Pull Requests.
Please:
1. Unit test every change in behavior
2. Use clang-format in "file" mode to format the code
3. Consider using the Continuous Integration (Travis and AppVeyor)

View File

@@ -2,7 +2,7 @@
--- ---
[![arduino-library-badge](https://www.ardu-badge.com/badge/ArduinoJson.svg?version=6.17.0)](https://www.ardu-badge.com/ArduinoJson/6.17.0) [![arduino-library-badge](https://www.ardu-badge.com/badge/ArduinoJson.svg?version=6.17.1)](https://www.ardu-badge.com/ArduinoJson/6.17.1)
[![Build Status](https://ci.appveyor.com/api/projects/status/m7s53wav1l0abssg/branch/6.x?svg=true)](https://ci.appveyor.com/project/bblanchon/arduinojson/branch/6.x) [![Build Status](https://ci.appveyor.com/api/projects/status/m7s53wav1l0abssg/branch/6.x?svg=true)](https://ci.appveyor.com/project/bblanchon/arduinojson/branch/6.x)
[![Build Status](https://travis-ci.org/bblanchon/ArduinoJson.svg?branch=6.x)](https://travis-ci.org/bblanchon/ArduinoJson) [![Build Status](https://travis-ci.org/bblanchon/ArduinoJson.svg?branch=6.x)](https://travis-ci.org/bblanchon/ArduinoJson)
[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/arduinojson.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:arduinojson) [![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/arduinojson.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:arduinojson)

View File

@@ -19,10 +19,13 @@ namespace ARDUINOJSON_NAMESPACE {
template <typename TArray> template <typename TArray>
class ElementProxy : public VariantOperators<ElementProxy<TArray> >, class ElementProxy : public VariantOperators<ElementProxy<TArray> >,
public VariantShortcuts<ElementProxy<TArray> >, public VariantShortcuts<ElementProxy<TArray> >,
public Visitable { public Visitable,
public VariantTag {
typedef ElementProxy<TArray> this_type; typedef ElementProxy<TArray> this_type;
public: public:
typedef VariantRef variant_type;
FORCE_INLINE ElementProxy(TArray array, size_t index) FORCE_INLINE ElementProxy(TArray array, size_t index)
: _array(array), _index(index) {} : _array(array), _index(index) {}

View File

@@ -83,6 +83,18 @@
#define ARDUINOJSON_DEFAULT_NESTING_LIMIT 10 #define ARDUINOJSON_DEFAULT_NESTING_LIMIT 10
#endif #endif
// Number of bits to store the pointer to next node
// (saves RAM but limits the number of values in a document)
#ifndef ARDUINOJSON_SLOT_OFFSET_SIZE
#if defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ == 2
// Address space == 16-bit => max 127 values
#define ARDUINOJSON_SLOT_OFFSET_SIZE 1
#else
// Address space > 16-bit => max 32767 values
#define ARDUINOJSON_SLOT_OFFSET_SIZE 2
#endif
#endif
#else // ARDUINOJSON_EMBEDDED_MODE #else // ARDUINOJSON_EMBEDDED_MODE
// On a computer we have plenty of memory so we can use doubles // On a computer we have plenty of memory so we can use doubles
@@ -114,6 +126,11 @@
#define ARDUINOJSON_DEFAULT_NESTING_LIMIT 50 #define ARDUINOJSON_DEFAULT_NESTING_LIMIT 50
#endif #endif
// Number of bits to store the pointer to next node
#ifndef ARDUINOJSON_SLOT_OFFSET_SIZE
#define ARDUINOJSON_SLOT_OFFSET_SIZE 4
#endif
#endif // ARDUINOJSON_EMBEDDED_MODE #endif // ARDUINOJSON_EMBEDDED_MODE
#ifdef ARDUINO #ifdef ARDUINO

View File

@@ -21,10 +21,13 @@ namespace ARDUINOJSON_NAMESPACE {
template <typename TObject, typename TStringRef> template <typename TObject, typename TStringRef>
class MemberProxy : public VariantOperators<MemberProxy<TObject, TStringRef> >, class MemberProxy : public VariantOperators<MemberProxy<TObject, TStringRef> >,
public VariantShortcuts<MemberProxy<TObject, TStringRef> >, public VariantShortcuts<MemberProxy<TObject, TStringRef> >,
public Visitable { public Visitable,
public VariantTag {
typedef MemberProxy<TObject, TStringRef> this_type; typedef MemberProxy<TObject, TStringRef> this_type;
public: public:
typedef VariantRef variant_type;
FORCE_INLINE MemberProxy(TObject variant, TStringRef key) FORCE_INLINE MemberProxy(TObject variant, TStringRef key)
: _object(variant), _key(key) {} : _object(variant), _key(key) {}

View File

@@ -0,0 +1,30 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2020
// MIT License
#pragma once
#include <stdint.h> // int8_t, int16_t
#include <ArduinoJson/Namespace.hpp>
namespace ARDUINOJSON_NAMESPACE {
template <int Bits>
struct int_t;
template <>
struct int_t<8> {
typedef int8_t type;
};
template <>
struct int_t<16> {
typedef int16_t type;
};
template <>
struct int_t<32> {
typedef int32_t type;
};
} // namespace ARDUINOJSON_NAMESPACE

View File

@@ -9,6 +9,7 @@
#include <ArduinoJson/Polyfills/attributes.hpp> #include <ArduinoJson/Polyfills/attributes.hpp>
#include <ArduinoJson/Polyfills/type_traits.hpp> #include <ArduinoJson/Polyfills/type_traits.hpp>
#include <ArduinoJson/Variant/VariantAs.hpp> #include <ArduinoJson/Variant/VariantAs.hpp>
#include <ArduinoJson/Variant/VariantTag.hpp>
namespace ARDUINOJSON_NAMESPACE { namespace ARDUINOJSON_NAMESPACE {
@@ -17,23 +18,23 @@ CompareResult compare(const T1 &lhs, const T2 &rhs); // VariantCompare.cpp
template <typename TVariant> template <typename TVariant>
struct VariantOperators { struct VariantOperators {
// Returns the default value if the VariantRef is undefined of incompatible // Returns the default value if the VariantRef is undefined or incompatible
template <typename T> template <typename T>
friend typename enable_if<!IsVisitable<T>::value, T>::type operator|( friend typename enable_if<!IsVariant<T>::value, T>::type operator|(
const TVariant &variant, const T &defaultValue) { const TVariant &variant, T defaultValue) {
if (variant.template is<T>()) if (variant.template is<T>())
return variant.template as<T>(); return variant.template as<T>();
else else
return defaultValue; return defaultValue;
} }
// Returns the default value if the VariantRef is undefined or incompatible
// Returns the default value if the VariantRef is undefined of incompatible
// Special case for string: null is treated as undefined
template <typename T> template <typename T>
friend typename enable_if<is_same<T, const char *>::value, T>::type operator|( friend typename enable_if<IsVariant<T>::value, typename T::variant_type>::type
const TVariant &variant, T defaultValue) { operator|(const TVariant &variant, T defaultValue) {
const char *value = variant.template as<const char *>(); if (variant)
return value ? value : defaultValue; return variant;
else
return defaultValue;
} }
// value == TVariant // value == TVariant

View File

@@ -16,6 +16,7 @@
#include <ArduinoJson/Variant/VariantOperators.hpp> #include <ArduinoJson/Variant/VariantOperators.hpp>
#include <ArduinoJson/Variant/VariantRef.hpp> #include <ArduinoJson/Variant/VariantRef.hpp>
#include <ArduinoJson/Variant/VariantShortcuts.hpp> #include <ArduinoJson/Variant/VariantShortcuts.hpp>
#include <ArduinoJson/Variant/VariantTag.hpp>
namespace ARDUINOJSON_NAMESPACE { namespace ARDUINOJSON_NAMESPACE {
@@ -23,12 +24,9 @@ namespace ARDUINOJSON_NAMESPACE {
class ArrayRef; class ArrayRef;
class ObjectRef; class ObjectRef;
template <typename, typename>
class MemberProxy;
// Contains the methods shared by VariantRef and VariantConstRef // Contains the methods shared by VariantRef and VariantConstRef
template <typename TData> template <typename TData>
class VariantRefBase { class VariantRefBase : public VariantTag {
public: public:
// Tells wether the variant has the specified type. // Tells wether the variant has the specified type.
// Returns true if the variant has type type T, false otherwise. // Returns true if the variant has type type T, false otherwise.

View File

@@ -4,15 +4,15 @@
#pragma once #pragma once
#include <stdint.h> // int8_t, int16_t #include <ArduinoJson/Polyfills/integer.hpp>
#include <ArduinoJson/Polyfills/limits.hpp>
#include <ArduinoJson/Polyfills/type_traits.hpp> #include <ArduinoJson/Polyfills/type_traits.hpp>
#include <ArduinoJson/Strings/StoragePolicy.hpp> #include <ArduinoJson/Strings/StoragePolicy.hpp>
#include <ArduinoJson/Variant/VariantContent.hpp> #include <ArduinoJson/Variant/VariantContent.hpp>
namespace ARDUINOJSON_NAMESPACE { namespace ARDUINOJSON_NAMESPACE {
typedef conditional<sizeof(void*) <= 2, int8_t, int16_t>::type VariantSlotDiff; typedef int_t<ARDUINOJSON_SLOT_OFFSET_SIZE * 8>::type VariantSlotDiff;
class VariantSlot { class VariantSlot {
// CAUTION: same layout as VariantData // CAUTION: same layout as VariantData
@@ -61,11 +61,19 @@ class VariantSlot {
} }
void setNext(VariantSlot* slot) { void setNext(VariantSlot* slot) {
ARDUINOJSON_ASSERT(!slot || slot - this >=
numeric_limits<VariantSlotDiff>::lowest());
ARDUINOJSON_ASSERT(!slot || slot - this <=
numeric_limits<VariantSlotDiff>::highest());
_next = VariantSlotDiff(slot ? slot - this : 0); _next = VariantSlotDiff(slot ? slot - this : 0);
} }
void setNextNotNull(VariantSlot* slot) { void setNextNotNull(VariantSlot* slot) {
ARDUINOJSON_ASSERT(slot != 0); ARDUINOJSON_ASSERT(slot != 0);
ARDUINOJSON_ASSERT(slot - this >=
numeric_limits<VariantSlotDiff>::lowest());
ARDUINOJSON_ASSERT(slot - this <=
numeric_limits<VariantSlotDiff>::highest());
_next = VariantSlotDiff(slot - this); _next = VariantSlotDiff(slot - this);
} }

View File

@@ -0,0 +1,16 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2020
// MIT License
#pragma once
#include <ArduinoJson/Namespace.hpp>
namespace ARDUINOJSON_NAMESPACE {
struct VariantTag {};
template <typename T>
struct IsVariant : is_base_of<VariantTag, T> {};
} // namespace ARDUINOJSON_NAMESPACE

View File

@@ -4,7 +4,7 @@
#pragma once #pragma once
#define ARDUINOJSON_VERSION "6.17.0" #define ARDUINOJSON_VERSION "6.17.1"
#define ARDUINOJSON_VERSION_MAJOR 6 #define ARDUINOJSON_VERSION_MAJOR 6
#define ARDUINOJSON_VERSION_MINOR 17 #define ARDUINOJSON_VERSION_MINOR 17
#define ARDUINOJSON_VERSION_REVISION 0 #define ARDUINOJSON_VERSION_REVISION 1

View File

@@ -1,90 +0,0 @@
# ArduinoJson - arduinojson.org
# Copyright Benoit Blanchon 2014-2020
# MIT License
# I have no idea what this is about, I simply followed the instructions from:
# https://dominikberner.ch/cmake-interface-lib/
add_library(ArduinoJson INTERFACE)
include(GNUInstallDirs)
# Adding the install interface generator expression makes sure that the include
# files are installed to the proper location (provided by GNUInstallDirs)
target_include_directories(ArduinoJson
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
target_compile_definitions(ArduinoJson
INTERFACE
ARDUINOJSON_DEBUG=$<CONFIG:Debug>
)
# locations are provided by GNUInstallDirs
install(
TARGETS
ArduinoJson
EXPORT
ArduinoJson_Targets
ARCHIVE DESTINATION
${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION
${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION
${CMAKE_INSTALL_BINDIR}
)
include(CMakePackageConfigHelpers)
if(${CMAKE_VERSION} VERSION_GREATER "3.14.0")
set(ARCH_INDEPENDENT "ARCH_INDEPENDENT")
endif()
write_basic_package_version_file(
"${PROJECT_BINARY_DIR}/ArduinoJsonConfigVersion.cmake"
VERSION
${PROJECT_VERSION}
COMPATIBILITY
SameMajorVersion
${ARCH_INDEPENDENT}
)
configure_package_config_file(
"${PROJECT_SOURCE_DIR}/extras/ArduinoJsonConfig.cmake.in"
"${PROJECT_BINARY_DIR}/ArduinoJsonConfig.cmake"
INSTALL_DESTINATION
${CMAKE_INSTALL_DATAROOTDIR}/ArduinoJson/cmake)
install(
EXPORT
ArduinoJson_Targets
FILE
ArduinoJsonTargets.cmake
DESTINATION
${CMAKE_INSTALL_DATAROOTDIR}/ArduinoJson/cmake
)
install(
FILES
"${PROJECT_BINARY_DIR}/ArduinoJsonConfig.cmake"
"${PROJECT_BINARY_DIR}/ArduinoJsonConfigVersion.cmake"
DESTINATION
"${CMAKE_INSTALL_DATAROOTDIR}/ArduinoJson/cmake"
)
install(
FILES
ArduinoJson.h
ArduinoJson.hpp
DESTINATION
include
)
install(
DIRECTORY
"${CMAKE_CURRENT_SOURCE_DIR}/ArduinoJson"
DESTINATION
include
)

View File

@@ -34,9 +34,11 @@ class FSPersistence {
JsonObject jsonObject = jsonDocument.as<JsonObject>(); JsonObject jsonObject = jsonDocument.as<JsonObject>();
// debug added by Proddy // debug added by Proddy
// Serial.printf("Read File: %s: ", _filePath); #if defined(EMSESP_FORCE_SERIAL)
// serializeJson(jsonDocument, Serial); Serial.printf("Read File: %s: ", _filePath);
// Serial.println(); serializeJson(jsonDocument, Serial);
Serial.println();
#endif
_statefulService->updateWithoutPropagation(jsonObject, _stateUpdater); _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
settingsFile.close(); settingsFile.close();
@@ -65,7 +67,7 @@ class FSPersistence {
} }
// debug added by Proddy // debug added by Proddy
#if defined(EMSESP_DEBUG) #if defined(EMSESP_FORCE_SERIAL)
Serial.printf("Write File: %s: ", _filePath); Serial.printf("Write File: %s: ", _filePath);
serializeJson(jsonDocument, Serial); serializeJson(jsonDocument, Serial);
Serial.println(); Serial.println();

View File

@@ -4,7 +4,6 @@
namespace emsesp { namespace emsesp {
class EMSESP { class EMSESP {
public: public:
static System system_;
static Mqtt mqtt_; static Mqtt mqtt_;
static DallasSensor dallassensor_; static DallasSensor dallassensor_;
}; };
@@ -93,14 +92,12 @@ AsyncMqttClient * MqttSettingsService::getMqttClient() {
} }
void MqttSettingsService::onMqttConnect(bool sessionPresent) { void MqttSettingsService::onMqttConnect(bool sessionPresent) {
/* // Serial.print(F("Connected to MQTT, "));
Serial.print(F("Connected to MQTT, ")); // if (sessionPresent) {
if (sessionPresent) { // Serial.println(F("with persistent session"));
Serial.println(F("with persistent session")); // } else {
} else { // Serial.println(F("without persistent session"));
Serial.println(F("without persistent session")); // }
}
*/
} }
void MqttSettingsService::onMqttDisconnect(AsyncMqttClientDisconnectReason reason) { void MqttSettingsService::onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
@@ -116,6 +113,7 @@ void MqttSettingsService::onConfigUpdated() {
// added by proddy // added by proddy
// reload EMS-ESP MQTT settings // reload EMS-ESP MQTT settings
emsesp::EMSESP::mqtt_.start();
} }
#ifdef ESP32 #ifdef ESP32

View File

@@ -46,24 +46,23 @@ void OTASettingsService::configureArduinoOTA() {
emsesp::System::upload_status(false); 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) {
_arduinoOTA->onError([](ota_error_t error) { // Serial.printf("Error[%u]: ", error);
Serial.printf("Error[%u]: ", error); // if (error == OTA_AUTH_ERROR)
if (error == OTA_AUTH_ERROR) // Serial.println(F("Auth Failed"));
Serial.println(F("Auth Failed")); // else if (error == OTA_BEGIN_ERROR)
else if (error == OTA_BEGIN_ERROR) // Serial.println(F("Begin Failed"));
Serial.println(F("Begin Failed")); // else if (error == OTA_CONNECT_ERROR)
else if (error == OTA_CONNECT_ERROR) // Serial.println(F("Connect Failed"));
Serial.println(F("Connect Failed")); // else if (error == OTA_RECEIVE_ERROR)
else if (error == OTA_RECEIVE_ERROR) // Serial.println(F("Receive Failed"));
Serial.println(F("Receive Failed")); // else if (error == OTA_END_ERROR)
else if (error == OTA_END_ERROR) // Serial.println(F("End Failed"));
Serial.println(F("End Failed")); // });
});
*/
_arduinoOTA->begin(); _arduinoOTA->begin();
} }
} }

View File

@@ -213,82 +213,70 @@ void Shell::loop_normal() {
default: default:
if (esc_) { if (esc_) {
if ((c == '[') || (c == 'O')) { if (c == 'A') { // cursor up
// start of sequence
} else if (c >= '0' && (c <= '9')) {
// numbers
esc_ = (esc_ & 0x7F) * 10 + c - '0';
} else if (c == 'A') {
// cursor up
line_buffer_ = line_old_; line_buffer_ = line_old_;
cursor_ = 0; cursor_ = 0;
esc_ = 0; } else if (c == 'B') { // cursor down
} else if (c == 'B') {
// cursor down
line_buffer_.clear(); line_buffer_.clear();
cursor_ = 0; cursor_ = 0;
esc_ = 0; } else if (c == 'C') { // cursor right
} else if (c == 'C') {
// cursor right
if (cursor_) { if (cursor_) {
cursor_--; cursor_--;
} }
esc_ = 0; } else if (c == 'D') { // cursor left
} else if (c == 'D') {
// cursor left
if (cursor_ < line_buffer_.length()) { if (cursor_ < line_buffer_.length()) {
cursor_++; cursor_++;
} }
esc_ = 0; } else if (c == 'H') { // Home
} else if (c == 'H') {
// Home
cursor_ = line_buffer_.length(); cursor_ = line_buffer_.length();
esc_ = 0; } else if (c == 'F') { // End
} else if (c == 'F') {
// End
cursor_ = 0; cursor_ = 0;
esc_ = 0; } else if (c >= 'P' && c <= 'Z') { // F1 - F11, Linux, VT100
} else if (c == 'P') { // set esc-number like ESCn~
// F1 esc_ = 11 + c - 'P';
set_command_str(F("help")); }
esc_ = 0; if (c == '~' || (c >= 'P' && c <= 'Z')) { // function keys with number ESCn~
} else if (c == 'Q') { if ((esc_ == 3) && cursor_) { // del
// F2
set_command_str(F("show"));
esc_ = 0;
} else if (c == '~') {
// function keys with number
if ((esc_ == 3) && cursor_) {
// del
cursor_--; cursor_--;
line_buffer_.erase(line_buffer_.length() - cursor_ - 1, 1); line_buffer_.erase(line_buffer_.length() - cursor_ - 1, 1);
} else if (esc_ == 4) { } else if (esc_ == 4) { // end
// end
cursor_ = 0; cursor_ = 0;
} else if (esc_ == 1) { } else if (esc_ == 1) { // pos1
// pos1
cursor_ = line_buffer_.length(); cursor_ = line_buffer_.length();
} else if (esc_ == 11) { } else if (esc_ == 11) { // F1
// F1 and F10
set_command_str(F("help")); set_command_str(F("help"));
} else if (esc_ == 12) { } else if (esc_ == 12) { // F2
// F2
set_command_str(F("show")); set_command_str(F("show"));
} else if (esc_ == 20) { } else if (esc_ == 13) { // F3
// F9 set_command_str(F("log notice"));
} else if (esc_ == 14) { // F4
set_command_str(F("log info"));
} else if (esc_ == 15) { // F5
set_command_str(F("log debug"));
} else if (esc_ == 17) { // F6
set_command_str(F("watch off"));
} else if (esc_ == 18) { // F7
set_command_str(F("watch on"));
} else if (esc_ == 19) { // F8
set_command_str(F("watch raw"));
} else if (esc_ == 20) { // F9
set_command_str(F("call system info"));
} else if (esc_ == 21) { // F10
set_command_str(F("call system report"));
} else if (esc_ == 23) { // F11
line_buffer_ = read_flash_string(F("send telegram \"0B \"")); line_buffer_ = read_flash_string(F("send telegram \"0B \""));
cursor_ = 1; cursor_ = 1;
} else if (esc_ == 15) { } else if (esc_ == 24) { // F12
// F5 set_command_str(F("log debug; watch raw"));
set_command_str(F("call system report"));
} }
esc_ = 0; esc_ = 0;
} else { } else if (c >= '0' && (c <= '9')) { // numbers
// all other chars end sequence esc_ = (esc_ & 0x7F) * 10 + c - '0';
} else if ((c != '[') && (c != 'O')) { // all other chars except start of sequence
esc_ = 0; esc_ = 0;
} }
// process normal ascii text
} else if (c >= '\x20' && c <= '\x7E') { } else if (c >= '\x20' && c <= '\x7E') {
// ASCII text
if (line_buffer_.length() < maximum_command_line_length_) { if (line_buffer_.length() < maximum_command_line_length_) {
line_buffer_.insert(line_buffer_.length() - cursor_, 1, c); line_buffer_.insert(line_buffer_.length() - cursor_, 1, c);
} }
@@ -506,7 +494,23 @@ void Shell::maximum_command_line_length(size_t length) {
} }
void Shell::process_command() { void Shell::process_command() {
CommandLine command_line{line_buffer_}; if (line_buffer_.empty()) {
println();
return;
}
line_old_ = line_buffer_;
while (!line_buffer_.empty()) {
size_t pos = line_buffer_.find(';');
std::string line1;
if (pos < line_buffer_.length()) {
line1 = line_buffer_.substr(0, pos);
line_buffer_.erase(0, pos + 1);
} else {
line1 = line_buffer_;
line_buffer_.clear();
cursor_ = 0;
}
CommandLine command_line{line1};
println(); println();
prompt_displayed_ = false; prompt_displayed_ = false;
@@ -514,35 +518,28 @@ void Shell::process_command() {
if (!command_line->empty()) { if (!command_line->empty()) {
if (commands_) { if (commands_) {
auto execution = commands_->execute_command(*this, std::move(command_line)); auto execution = commands_->execute_command(*this, std::move(command_line));
if (execution.error != nullptr) { if (execution.error != nullptr) {
println(execution.error); println(execution.error);
} }
} else { } else {
println(F("No commands configured")); println(F("No commands configured"));
} }
line_old_ = line_buffer_;
}
cursor_ = 0;
line_buffer_.clear();
if (running()) {
display_prompt();
} }
::yield(); ::yield();
} }
// if (running()) {
// display_prompt();
// }
}
void Shell::process_completion() { void Shell::process_completion() {
CommandLine command_line{line_buffer_}; CommandLine command_line{line_buffer_};
if (!command_line->empty() && commands_) { if (!command_line->empty() && commands_) {
auto completion = commands_->complete_command(*this, command_line); auto completion = commands_->complete_command(*this, command_line);
bool redisplay = false;
if (!completion.help.empty()) { if (!completion.help.empty()) {
println(); println();
redisplay = true;
for (auto & help : completion.help) { for (auto & help : completion.help) {
std::string help_line = help.to_string(maximum_command_line_length_); std::string help_line = help.to_string(maximum_command_line_length_);
@@ -552,18 +549,8 @@ void Shell::process_completion() {
} }
if (!completion.replacement->empty()) { if (!completion.replacement->empty()) {
if (!redisplay) {
erase_current_line();
prompt_displayed_ = false;
redisplay = true;
}
line_buffer_ = completion.replacement.to_string(maximum_command_line_length_); line_buffer_ = completion.replacement.to_string(maximum_command_line_length_);
} }
if (redisplay) {
display_prompt();
}
} }
::yield(); ::yield();
@@ -587,15 +574,10 @@ void Shell::process_password(bool completed) {
} }
void Shell::invoke_command(const std::string & line) { void Shell::invoke_command(const std::string & line) {
if (!line_buffer_.empty()) { erase_current_line();
println();
prompt_displayed_ = false; prompt_displayed_ = false;
}
if (!prompt_displayed_) {
display_prompt();
}
line_buffer_ = line; line_buffer_ = line;
print(line_buffer_); display_prompt();
process_command(); process_command();
} }

View File

@@ -15,7 +15,8 @@ class DummySettings {
public: public:
uint8_t tx_mode = 1; uint8_t tx_mode = 1;
uint8_t ems_bus_id = 0x0B; uint8_t ems_bus_id = 0x0B;
int8_t syslog_level = 1; // uuid::log::Level bool syslog_enabled = false;
int8_t syslog_level = 3; // uuid::log::Level
uint32_t syslog_mark_interval = 0; uint32_t syslog_mark_interval = 0;
String syslog_host = "192.168.1.4"; String syslog_host = "192.168.1.4";
uint8_t master_thermostat = 0; uint8_t master_thermostat = 0;

View File

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

View File

@@ -13,6 +13,7 @@ extra_configs =
debug_flags = debug_flags =
; -D EMSESP_DEBUG ; -D EMSESP_DEBUG
; -D EMSESP_UART_DEBUG ; -D EMSESP_UART_DEBUG
; -D EMSESP_TEST
; -D EMSESP_FORCE_SERIAL ; -D EMSESP_FORCE_SERIAL
; -D ENABLE_CORS ; -D ENABLE_CORS

View File

@@ -0,0 +1,26 @@
#!/usr/bin/env python
from subprocess import call
import os
# example stackdmp.txt would contain text like below copied & pasted from a 'crash dump' command:
# >>>stack>>>
# 3fffff20: 3fff32f0 00000003 3fff3028 402101b2
# 3fffff30: 3fffdad0 3fff3280 0000000d 402148aa
# 3fffff40: 3fffdad0 3fff3280 3fff326c 3fff32f0
# 3fffff50: 0000000d 3fff326c 3fff3028 402103bd
# 3fffff60: 0000000d 3fff34cc 40211de4 3fff34cc
# 3fffff70: 3fff3028 3fff14c4 3fff301c 3fff34cc
# 3fffff80: 3fffdad0 3fff14c4 3fff3028 40210493
# 3fffff90: 3fffdad0 00000000 3fff14c4 4020a738
# 3fffffa0: 3fffdad0 00000000 3fff349c 40211e90
# 3fffffb0: feefeffe feefeffe 3ffe8558 40100b01
# <<<stack<<<
call(['python', 'scripts/decoder.py ', '-s', '-e', os.getcwd()+"/.pio/build/esp12e/firmware.elf", 'scripts/stackdmp.txt'])
# example for linux:
# % cd EMS-ESP
# % python scripts/decoder_linux.py -s -e .pio/build/esp12e/firmware.elf scripts/stackdmp.txt
# python decoder_linux.py -s -e ../.pio/build/esp8266-debug/firmware.elf stackdmp.txt

319
scripts/decoder.py Normal file
View File

@@ -0,0 +1,319 @@
#!/usr/bin/env python3
"""ESP Exception Decoder
github: https://github.com/janLo/EspArduinoExceptionDecoder
license: GPL v3
author: Jan Losinski
"""
import argparse
import re
import subprocess
from collections import namedtuple
import sys
import os
EXCEPTIONS = [
"Illegal instruction",
"SYSCALL instruction",
"InstructionFetchError: Processor internal physical address or data error during instruction fetch",
"LoadStoreError: Processor internal physical address or data error during load or store",
"Level1Interrupt: Level-1 interrupt as indicated by set level-1 bits in the INTERRUPT register",
"Alloca: MOVSP instruction, if caller's registers are not in the register file",
"IntegerDivideByZero: QUOS, QUOU, REMS, or REMU divisor operand is zero",
"reserved",
"Privileged: Attempt to execute a privileged operation when CRING ? 0",
"LoadStoreAlignmentCause: Load or store to an unaligned address",
"reserved",
"reserved",
"InstrPIFDataError: PIF data error during instruction fetch",
"LoadStorePIFDataError: Synchronous PIF data error during LoadStore access",
"InstrPIFAddrError: PIF address error during instruction fetch",
"LoadStorePIFAddrError: Synchronous PIF address error during LoadStore access",
"InstTLBMiss: Error during Instruction TLB refill",
"InstTLBMultiHit: Multiple instruction TLB entries matched",
"InstFetchPrivilege: An instruction fetch referenced a virtual address at a ring level less than CRING",
"reserved",
"InstFetchProhibited: An instruction fetch referenced a page mapped with an attribute that does not permit instruction fetch",
"reserved",
"reserved",
"reserved",
"LoadStoreTLBMiss: Error during TLB refill for a load or store",
"LoadStoreTLBMultiHit: Multiple TLB entries matched for a load or store",
"LoadStorePrivilege: A load or store referenced a virtual address at a ring level less than CRING",
"reserved",
"LoadProhibited: A load referenced a page mapped with an attribute that does not permit loads",
"StoreProhibited: A store referenced a page mapped with an attribute that does not permit stores"
]
PLATFORMS = {
"ESP8266": "lx106",
"ESP32": "esp32"
}
BACKTRACE_REGEX = re.compile(r"(?:\s+(0x40[0-2](?:\d|[a-f]|[A-F]){5}):0x(?:\d|[a-f]|[A-F]){8})\b")
EXCEPTION_REGEX = re.compile("^Exception \\((?P<exc>[0-9]*)\\):$")
COUNTER_REGEX = re.compile('^epc1=(?P<epc1>0x[0-9a-f]+) epc2=(?P<epc2>0x[0-9a-f]+) epc3=(?P<epc3>0x[0-9a-f]+) '
'excvaddr=(?P<excvaddr>0x[0-9a-f]+) depc=(?P<depc>0x[0-9a-f]+)$')
CTX_REGEX = re.compile("^ctx: (?P<ctx>.+)$")
POINTER_REGEX = re.compile('^sp: (?P<sp>[0-9a-f]+) end: (?P<end>[0-9a-f]+) offset: (?P<offset>[0-9a-f]+)$')
STACK_BEGIN = '>>>stack>>>'
STACK_END = '<<<stack<<<'
STACK_REGEX = re.compile(
'^(?P<off>[0-9a-f]+):\W+(?P<c1>[0-9a-f]+) (?P<c2>[0-9a-f]+) (?P<c3>[0-9a-f]+) (?P<c4>[0-9a-f]+)(\W.*)?$')
StackLine = namedtuple("StackLine", ["offset", "content"])
class ExceptionDataParser(object):
def __init__(self):
self.exception = None
self.epc1 = None
self.epc2 = None
self.epc3 = None
self.excvaddr = None
self.depc = None
self.ctx = None
self.sp = None
self.end = None
self.offset = None
self.stack = []
def _parse_backtrace(self, line):
if line.startswith('Backtrace:'):
self.stack = [StackLine(offset=0, content=(addr,)) for addr in BACKTRACE_REGEX.findall(line)]
return None
return self._parse_backtrace
def _parse_exception(self, line):
match = EXCEPTION_REGEX.match(line)
if match is not None:
self.exception = int(match.group('exc'))
return self._parse_counters
return self._parse_exception
def _parse_counters(self, line):
match = COUNTER_REGEX.match(line)
if match is not None:
self.epc1 = match.group("epc1")
self.epc2 = match.group("epc2")
self.epc3 = match.group("epc3")
self.excvaddr = match.group("excvaddr")
self.depc = match.group("depc")
return self._parse_ctx
return self._parse_counters
def _parse_ctx(self, line):
match = CTX_REGEX.match(line)
if match is not None:
self.ctx = match.group("ctx")
return self._parse_pointers
return self._parse_ctx
def _parse_pointers(self, line):
match = POINTER_REGEX.match(line)
if match is not None:
self.sp = match.group("sp")
self.end = match.group("end")
self.offset = match.group("offset")
return self._parse_stack_begin
return self._parse_pointers
def _parse_stack_begin(self, line):
if line == STACK_BEGIN:
return self._parse_stack_line
return self._parse_stack_begin
def _parse_stack_line(self, line):
if line != STACK_END:
match = STACK_REGEX.match(line)
if match is not None:
self.stack.append(StackLine(offset=match.group("off"),
content=(match.group("c1"), match.group("c2"), match.group("c3"),
match.group("c4"))))
return self._parse_stack_line
return None
def parse_file(self, file, platform, stack_only=False):
if platform == 'ESP32':
func = self._parse_backtrace
else:
func = self._parse_exception
if stack_only:
func = self._parse_stack_begin
for line in file:
func = func(line.strip())
if func is None:
break
if func is not None:
print("ERROR: Parser not complete!")
sys.exit(1)
class AddressResolver(object):
def __init__(self, tool_path, elf_path):
self._tool = tool_path
self._elf = elf_path
self._address_map = {}
def _lookup(self, addresses):
cmd = [self._tool, "-aipfC", "-e", self._elf] + [addr for addr in addresses if addr is not None]
if sys.version_info[0] < 3:
output = subprocess.check_output(cmd)
else:
output = subprocess.check_output(cmd, encoding="utf-8")
line_regex = re.compile("^(?P<addr>[0-9a-fx]+): (?P<result>.+)$")
last = None
for line in output.splitlines():
line = line.strip()
match = line_regex.match(line)
if match is None:
if last is not None and line.startswith('(inlined by)'):
line = line [12:].strip()
self._address_map[last] += ("\n \-> inlined by: " + line)
continue
if match.group("result") == '?? ??:0':
continue
self._address_map[match.group("addr")] = match.group("result")
last = match.group("addr")
def fill(self, parser):
addresses = [parser.epc1, parser.epc2, parser.epc3, parser.excvaddr, parser.sp, parser.end, parser.offset]
for line in parser.stack:
addresses.extend(line.content)
self._lookup(addresses)
def _sanitize_addr(self, addr):
if addr.startswith("0x"):
addr = addr[2:]
fill = "0" * (8 - len(addr))
return "0x" + fill + addr
def resolve_addr(self, addr):
out = self._sanitize_addr(addr)
if out in self._address_map:
out += ": " + self._address_map[out]
return out
def resolve_stack_addr(self, addr, full=True):
addr = self._sanitize_addr(addr)
if addr in self._address_map:
return addr + ": " + self._address_map[addr]
if full:
return "[DATA (0x" + addr + ")]"
return None
def print_addr(name, value, resolver):
print("{}:{} {}".format(name, " " * (8 - len(name)), resolver.resolve_addr(value)))
def print_stack_full(lines, resolver):
print("stack:")
for line in lines:
print(line.offset + ":")
for content in line.content:
print(" " + resolver.resolve_stack_addr(content))
def print_stack(lines, resolver):
print("stack:")
for line in lines:
for content in line.content:
out = resolver.resolve_stack_addr(content, full=False)
if out is None:
continue
print(out)
def print_result(parser, resolver, platform, full=True, stack_only=False):
if platform == 'ESP8266' and not stack_only:
print('Exception: {} ({})'.format(parser.exception, EXCEPTIONS[parser.exception]))
print("")
print_addr("epc1", parser.epc1, resolver)
print_addr("epc2", parser.epc2, resolver)
print_addr("epc3", parser.epc3, resolver)
print_addr("excvaddr", parser.excvaddr, resolver)
print_addr("depc", parser.depc, resolver)
print("")
print("ctx: " + parser.ctx)
print("")
print_addr("sp", parser.sp, resolver)
print_addr("end", parser.end, resolver)
print_addr("offset", parser.offset, resolver)
print("")
if full:
print_stack_full(parser.stack, resolver)
else:
print_stack(parser.stack, resolver)
def parse_args():
parser = argparse.ArgumentParser(description="decode ESP Stacktraces.")
parser.add_argument("-p", "--platform", help="The platform to decode from", choices=PLATFORMS.keys(),
default="ESP8266")
parser.add_argument("-t", "--tool", help="Path to the xtensa toolchain",
default="~/.platformio/packages/toolchain-xtensa/")
parser.add_argument("-e", "--elf", help="path to elf file", required=True)
parser.add_argument("-f", "--full", help="Print full stack dump", action="store_true")
parser.add_argument("-s", "--stack_only", help="Decode only a stractrace", action="store_true")
parser.add_argument("file", help="The file to read the exception data from ('-' for STDIN)", default="-")
return parser.parse_args()
if __name__ == "__main__":
args = parse_args()
if args.file == "-":
file = sys.stdin
else:
if not os.path.exists(args.file):
print("ERROR: file " + args.file + " not found")
sys.exit(1)
file = open(args.file, "r")
addr2line = os.path.join(os.path.abspath(os.path.expanduser(args.tool)),
"bin/xtensa-" + PLATFORMS[args.platform] + "-elf-addr2line")
if os.name == 'nt':
addr2line += '.exe'
if not os.path.exists(addr2line):
print("ERROR: addr2line not found (" + addr2line + ")")
elf_file = os.path.abspath(os.path.expanduser(args.elf))
if not os.path.exists(elf_file):
print("ERROR: elf file not found (" + elf_file + ")")
parser = ExceptionDataParser()
resolver = AddressResolver(addr2line, elf_file)
parser.parse_file(file, args.platform, args.stack_only)
resolver.fill(parser)
print_result(parser, resolver, args.platform, args.full, args.stack_only)

4
scripts/decoder.sh Normal file
View File

@@ -0,0 +1,4 @@
# python decoder.py -s -e ../.pio/build/esp8266-local/firmware.elf stackdmp.txt
python decoder.py -p ESP32 -e ../.pio/build/esp32-local/firmware.elf stackdmp.txt -t ~/.platformio/packages/toolchain-xtensa32

View File

@@ -71,7 +71,7 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request) {
id = "-1"; id = "-1";
} }
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_LARGE); DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_DYN);
JsonObject json = doc.to<JsonObject>(); JsonObject json = doc.to<JsonObject>();
bool ok = false; bool ok = false;
@@ -98,10 +98,10 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request) {
cmd.c_str(), cmd.c_str(),
data.c_str(), data.c_str(),
id.c_str(), id.c_str(),
ok ? F("OK") : F("Invalid")); ok ? PSTR("OK") : PSTR("Invalid"));
EMSESP::logger().info(debug.c_str()); EMSESP::logger().info(debug.c_str());
if (json.size()) { if (json.size()) {
char buffer2[EMSESP_MAX_JSON_SIZE_LARGE]; char buffer2[EMSESP_MAX_JSON_SIZE_DYN];
serializeJson(doc, buffer2); serializeJson(doc, buffer2);
EMSESP::logger().info("json (max 255 chars): %s", buffer2); EMSESP::logger().info("json (max 255 chars): %s", buffer2);
} }
@@ -110,7 +110,7 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request) {
// if we have returned data in JSON format, send this to the WEB // if we have returned data in JSON format, send this to the WEB
if (json.size()) { if (json.size()) {
doc.shrinkToFit(); doc.shrinkToFit();
char buffer[EMSESP_MAX_JSON_SIZE_LARGE]; char buffer[EMSESP_MAX_JSON_SIZE_DYN];
serializeJsonPretty(doc, buffer); serializeJsonPretty(doc, buffer);
request->send(200, "text/plain", buffer); request->send(200, "text/plain", buffer);
} else { } else {

View File

@@ -30,6 +30,7 @@ WebSettingsService::WebSettingsService(AsyncWebServer * server, FS * fs, Securit
void WebSettings::read(WebSettings & settings, JsonObject & root) { void WebSettings::read(WebSettings & settings, JsonObject & root) {
root["tx_mode"] = settings.tx_mode; root["tx_mode"] = settings.tx_mode;
root["ems_bus_id"] = settings.ems_bus_id; root["ems_bus_id"] = settings.ems_bus_id;
root["syslog_enabled"] = settings.syslog_enabled;
root["syslog_level"] = settings.syslog_level; root["syslog_level"] = settings.syslog_level;
root["syslog_mark_interval"] = settings.syslog_mark_interval; root["syslog_mark_interval"] = settings.syslog_mark_interval;
root["syslog_host"] = settings.syslog_host; root["syslog_host"] = settings.syslog_host;
@@ -50,6 +51,7 @@ void WebSettings::read(WebSettings & settings, JsonObject & root) {
StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings) { StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings) {
settings.tx_mode = root["tx_mode"] | EMSESP_DEFAULT_TX_MODE; settings.tx_mode = root["tx_mode"] | EMSESP_DEFAULT_TX_MODE;
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;
settings.syslog_enabled = root["syslog_enabled"] | EMSESP_DEFAULT_SYSLOG_ENABLED;
settings.syslog_level = root["syslog_level"] | EMSESP_DEFAULT_SYSLOG_LEVEL; settings.syslog_level = root["syslog_level"] | EMSESP_DEFAULT_SYSLOG_LEVEL;
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;
settings.syslog_host = root["syslog_host"] | EMSESP_DEFAULT_SYSLOG_HOST; settings.syslog_host = root["syslog_host"] | EMSESP_DEFAULT_SYSLOG_HOST;

View File

@@ -27,7 +27,8 @@
#define EMSESP_DEFAULT_TX_MODE 1 // EMS1.0 #define EMSESP_DEFAULT_TX_MODE 1 // EMS1.0
#define EMSESP_DEFAULT_EMS_BUS_ID 0x0B // service key #define EMSESP_DEFAULT_EMS_BUS_ID 0x0B // service key
#define EMSESP_DEFAULT_SYSLOG_LEVEL -1 // OFF #define EMSESP_DEFAULT_SYSLOG_ENABLED false
#define EMSESP_DEFAULT_SYSLOG_LEVEL 3 // ERR
#define EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL 0 #define EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL 0
#define EMSESP_DEFAULT_SYSLOG_HOST "" #define EMSESP_DEFAULT_SYSLOG_HOST ""
#define EMSESP_DEFAULT_MASTER_THERMOSTAT 0 // not set #define EMSESP_DEFAULT_MASTER_THERMOSTAT 0 // not set
@@ -67,6 +68,7 @@ class WebSettings {
uint8_t master_thermostat; uint8_t master_thermostat;
bool shower_timer; bool shower_timer;
bool shower_alert; bool shower_alert;
bool syslog_enabled;
int8_t syslog_level; // uuid::log::Level int8_t syslog_level; // uuid::log::Level
uint32_t syslog_mark_interval; uint32_t syslog_mark_interval;
String syslog_host; String syslog_host;

View File

@@ -68,8 +68,10 @@ void Command::add(const uint8_t device_type, const uint8_t device_id, const __Fl
cmdfunctions_.emplace_back(device_type, cmd, cb, nullptr); cmdfunctions_.emplace_back(device_type, cmd, cb, nullptr);
// see if we need to subscribe // see if we need to subscribe
if (Mqtt::enabled()) {
Mqtt::register_command(device_type, device_id, cmd, cb); Mqtt::register_command(device_type, device_id, cmd, cb);
} }
}
// add a command to the list, which does return json object as output // add a command to the list, which does return json object as output
void Command::add_with_json(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_json_p cb) { void Command::add_with_json(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_json_p cb) {

View File

@@ -20,7 +20,7 @@
#include "emsesp.h" #include "emsesp.h"
#include "version.h" #include "version.h"
#if defined(EMSESP_DEBUG) #if defined(EMSESP_TEST)
#include "test/test.h" #include "test/test.h"
#endif #endif
@@ -110,28 +110,6 @@ void EMSESPShell::add_console_commands() {
// commands->remove_context_commands(ShellContext::MAIN); // commands->remove_context_commands(ShellContext::MAIN);
commands->remove_all_commands(); commands->remove_all_commands();
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(fetch)},
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.printfln(F("Requesting data from EMS devices"));
EMSESP::fetch_device_values();
});
commands->add_command(ShellContext::MAIN,
CommandFlags::ADMIN,
flash_string_vector{F_(publish)},
flash_string_vector{F_(ha_optional)},
[&](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.empty()) {
EMSESP::publish_all();
shell.printfln(F("Published all data to MQTT"));
} else {
EMSESP::publish_all(true);
shell.printfln(F("Published all data to MQTT, including HA configs"));
}
});
commands->add_command(ShellContext::MAIN, commands->add_command(ShellContext::MAIN,
CommandFlags::USER, CommandFlags::USER,
flash_string_vector{F_(show)}, flash_string_vector{F_(show)},
@@ -302,13 +280,17 @@ void EMSESPShell::add_console_commands() {
"local"); "local");
}); });
#ifndef EMSESP_STANDALONE
commands->add_command(ShellContext::MAIN, commands->add_command(ShellContext::MAIN,
CommandFlags::ADMIN, CommandFlags::USER,
flash_string_vector{F_(send), F_(telegram)}, flash_string_vector{F_(set), F_(timeout)},
flash_string_vector{F_(data_mandatory)}, flash_string_vector{F_(n_mandatory)},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) { [](Shell & shell, const std::vector<std::string> & arguments) {
EMSESP::send_raw_telegram(arguments.front().c_str()); uint16_t value = Helpers::atoint(arguments.front().c_str());
telnet_.initial_idle_timeout(value * 60);
shell.printfln(F("Telnet timout is %d minutes"), value);
}); });
#endif
commands->add_command(ShellContext::MAIN, commands->add_command(ShellContext::MAIN,
CommandFlags::USER, CommandFlags::USER,
@@ -446,11 +428,7 @@ void EMSESPShell::add_console_commands() {
return {}; return {};
}); });
/* // System context menu
* add all the submenu contexts...
*/
// System
commands->add_command(ShellContext::MAIN, commands->add_command(ShellContext::MAIN,
CommandFlags::USER, CommandFlags::USER,
flash_string_vector{F_(system)}, flash_string_vector{F_(system)},
@@ -491,33 +469,21 @@ void Console::enter_custom_context(Shell & shell, unsigned int context) {
// each custom context has the common commands like log, help, exit, su etc // each custom context has the common commands like log, help, exit, su etc
void Console::load_standard_commands(unsigned int context) { void Console::load_standard_commands(unsigned int context) {
#if defined(EMSESP_DEBUG) #if defined(EMSESP_TEST)
EMSESPShell::commands->add_command(context, EMSESPShell::commands->add_command(context,
CommandFlags::USER, CommandFlags::USER,
flash_string_vector{F_(test)}, flash_string_vector{F_(test)},
flash_string_vector{F_(name_optional)}, flash_string_vector{F_(name_optional)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { [](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.size() == 0) { if (arguments.size() == 0) {
Test::run_test(shell, "default"); Test::run_test_shell(shell, "default");
} else { } else {
Test::run_test(shell, arguments.front()); Test::run_test_shell(shell, arguments.front());
} }
}); });
#endif #endif
#if defined(EMSESP_STANDALONE)
EMSESPShell::commands->add_command(context,
CommandFlags::USER,
flash_string_vector{F("t")},
flash_string_vector{F_(name_optional)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
if (arguments.size() == 0) {
Test::run_test(shell, "default");
} else {
Test::run_test(shell, arguments.front());
}
});
#endif
EMSESPShell::commands->add_command( EMSESPShell::commands->add_command(
context, context,

View File

@@ -84,12 +84,12 @@ void DallasSensor::loop() {
bus_.reset_search(); bus_.reset_search();
state_ = State::SCANNING; state_ = State::SCANNING;
} else if (time_now - last_activity_ > READ_TIMEOUT_MS) { } else if (time_now - last_activity_ > READ_TIMEOUT_MS) {
LOG_ERROR(F("Sensor read timeout")); LOG_WARNING(F("Dallas sensor read timeout"));
state_ = State::IDLE; state_ = State::IDLE;
} }
} else if (state_ == State::SCANNING) { } else if (state_ == State::SCANNING) {
if (time_now - last_activity_ > SCAN_TIMEOUT_MS) { if (time_now - last_activity_ > SCAN_TIMEOUT_MS) {
LOG_ERROR(F("Sensor scan timeout")); LOG_ERROR(F("Dallas sensor scan timeout"));
state_ = State::IDLE; state_ = State::IDLE;
} else { } else {
uint8_t addr[ADDR_LEN] = {0}; uint8_t addr[ADDR_LEN] = {0};
@@ -129,11 +129,11 @@ void DallasSensor::loop() {
break; break;
default: default:
LOG_ERROR(F("Unknown sensor %s"), Sensor(addr).to_string().c_str()); LOG_ERROR(F("Unknown dallas sensor %s"), Sensor(addr).to_string().c_str());
break; break;
} }
} else { } else {
LOG_ERROR(F("Invalid sensor %s"), Sensor(addr).to_string().c_str()); LOG_ERROR(F("Invalid dallas sensor %s"), Sensor(addr).to_string().c_str());
} }
} else { } else {
if (!parasite_) { if (!parasite_) {
@@ -309,7 +309,7 @@ bool DallasSensor::export_values(JsonObject & json) {
} }
// send all dallas sensor values as a JSON package to MQTT // send all dallas sensor values as a JSON package to MQTT
void DallasSensor::publish_values() { void DallasSensor::publish_values(const bool force) {
uint8_t num_sensors = sensors_.size(); uint8_t num_sensors = sensors_.size();
if (num_sensors == 0) { if (num_sensors == 0) {
@@ -340,7 +340,7 @@ void DallasSensor::publish_values() {
// create the HA MQTT config // create the HA MQTT config
// to e.g. homeassistant/sensor/ems-esp/dallas_28-233D-9497-0C03/config // to e.g. homeassistant/sensor/ems-esp/dallas_28-233D-9497-0C03/config
if (mqtt_format_ == Mqtt::Format::HA) { if (mqtt_format_ == Mqtt::Format::HA) {
if (!(registered_ha_[sensor_no - 1])) { if (!(registered_ha_[sensor_no - 1]) || force) {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> config; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> config;
config["dev_cla"] = F("temperature"); config["dev_cla"] = F("temperature");

View File

@@ -58,7 +58,7 @@ class DallasSensor {
void start(); void start();
void loop(); void loop();
void publish_values(); void publish_values(const bool force);
void reload(); void reload();
bool updated_values(); bool updated_values();

View File

@@ -87,7 +87,7 @@
{107, DeviceType::THERMOSTAT, F("FR100"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_2}, // older model {107, DeviceType::THERMOSTAT, F("FR100"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_2}, // older model
{108, DeviceType::THERMOSTAT, F("FR110"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_2}, // older model {108, DeviceType::THERMOSTAT, F("FR110"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_2}, // older model
{111, DeviceType::THERMOSTAT, F("FR10"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS}, {111, DeviceType::THERMOSTAT, F("FR10"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},
{147, DeviceType::THERMOSTAT, F("FR50"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS}, {147, DeviceType::THERMOSTAT, F("FR50"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_2},
{191, DeviceType::THERMOSTAT, F("FR120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_2}, // older model {191, DeviceType::THERMOSTAT, F("FR120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_2}, // older model
{192, DeviceType::THERMOSTAT, F("FW120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS}, {192, DeviceType::THERMOSTAT, F("FW120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},

View File

@@ -74,11 +74,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
// create the config topics for Home Assistant MQTT Discovery // create the config topics for Home Assistant MQTT Discovery
// for each of the main elements // for each of the main elements
void Boiler::register_mqtt_ha_config(bool force) { void Boiler::register_mqtt_ha_config() {
if ((mqtt_ha_config_ && !force)) {
return;
}
if (!Mqtt::connected()) { if (!Mqtt::connected()) {
return; return;
} }
@@ -141,7 +137,16 @@ void Boiler::register_mqtt_ha_config(bool force) {
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(burnWorkMin), this->device_type(), "burnWorkMin", F_(min), nullptr); Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(burnWorkMin), this->device_type(), "burnWorkMin", F_(min), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(heatWorkMin), this->device_type(), "heatWorkMin", F_(min), nullptr); Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(heatWorkMin), this->device_type(), "heatWorkMin", F_(min), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(UBAuptime), this->device_type(), "UBAuptime", F_(min), nullptr); Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(UBAuptime), this->device_type(), "UBAuptime", F_(min), nullptr);
mqtt_ha_config_ = true; // done
}
// create the config topics for Home Assistant MQTT Discovery
// for each of the ww elements
void Boiler::register_mqtt_ha_config_ww() {
if (!Mqtt::connected()) {
return;
}
// ww // ww
Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWSelTemp), this->device_type(), "wWSelTemp", F_(degrees), F_(iconcruise)); Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWSelTemp), this->device_type(), "wWSelTemp", F_(degrees), F_(iconcruise));
Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWSetTemp), this->device_type(), "wWSetTemp", F_(degrees), F_(icontemperature)); Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWSetTemp), this->device_type(), "wWSetTemp", F_(degrees), F_(icontemperature));
@@ -170,13 +175,14 @@ void Boiler::register_mqtt_ha_config(bool force) {
Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWStarts), this->device_type(), "wWStarts", nullptr, nullptr); Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWStarts), this->device_type(), "wWStarts", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWWorkM), this->device_type(), "wWWorkM", F_(min), nullptr); Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWWorkM), this->device_type(), "wWWorkM", F_(min), nullptr);
mqtt_ha_config_ = true; // done mqtt_ha_config_ww_ = true; // done
} }
// send stuff to the Web UI // send stuff to the Web UI
void Boiler::device_info_web(JsonArray & root) { void Boiler::device_info_web(JsonArray & root) {
// fetch the values into a JSON document // fetch the values into a JSON document
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_LARGE); // DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_LARGE);
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc;
JsonObject json = doc.to<JsonObject>(); JsonObject json = doc.to<JsonObject>();
if (!export_values_main(json)) { if (!export_values_main(json)) {
return; // empty return; // empty
@@ -618,19 +624,29 @@ bool Boiler::export_values_main(JsonObject & json) {
void Boiler::publish_values(JsonObject & json, bool force) { void Boiler::publish_values(JsonObject & json, bool force) {
// handle HA first // handle HA first
if (Mqtt::mqtt_format() == Mqtt::Format::HA) { if (Mqtt::mqtt_format() == Mqtt::Format::HA) {
register_mqtt_ha_config(force); if (force) {
mqtt_ha_config_ = false;
mqtt_ha_config_ww_ = false;
}
// register ww in next cycle if both unregistered
if (!mqtt_ha_config_) {
register_mqtt_ha_config();
return;
} else if (!mqtt_ha_config_ww_) {
register_mqtt_ha_config_ww();
return;
}
} }
DynamicJsonDocument doc_main(EMSESP_MAX_JSON_SIZE_LARGE); StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc;
JsonObject json_main = doc_main.to<JsonObject>(); JsonObject json_data = doc.to<JsonObject>();
if (export_values_main(json_main)) { if (export_values_main(json_data)) {
Mqtt::publish(F("boiler_data"), doc_main.as<JsonObject>()); Mqtt::publish(F("boiler_data"), json_data);
} }
json_data.clear();
DynamicJsonDocument doc_ww(EMSESP_MAX_JSON_SIZE_LARGE); if (export_values_ww(json_data)) {
JsonObject json_ww = doc_ww.to<JsonObject>(); Mqtt::publish(F("boiler_data_ww"), json_data);
if (export_values_ww(json_ww)) {
Mqtt::publish(F("boiler_data_ww"), doc_ww.as<JsonObject>());
} }
// send out heating and tapwater status // send out heating and tapwater status
@@ -651,13 +667,14 @@ void Boiler::show_values(uuid::console::Shell & shell) {
EMSdevice::show_values(shell); // for showing the header EMSdevice::show_values(shell); // for showing the header
// fetch the values into a JSON document // fetch the values into a JSON document
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_LARGE); // DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_LARGE);
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc;
JsonObject json = doc.to<JsonObject>(); JsonObject json = doc.to<JsonObject>();
if (!export_values_main(json)) { if (!export_values_main(json)) {
return; // empty return; // empty
} }
export_values_ww(json); // append ww values export_values_ww(json); // append ww values
doc.shrinkToFit(); // doc.shrinkToFit();
print_value_json(shell, F("heatingActive"), nullptr, F_(heatingActive), nullptr, json); print_value_json(shell, F("heatingActive"), nullptr, F_(heatingActive), nullptr, json);
print_value_json(shell, F("tapwaterActive"), nullptr, F_(tapwaterActive), nullptr, json); print_value_json(shell, F("tapwaterActive"), nullptr, F_(tapwaterActive), nullptr, json);

View File

@@ -47,13 +47,15 @@ class Boiler : public EMSdevice {
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;
void register_mqtt_ha_config(bool force); void register_mqtt_ha_config();
void register_mqtt_ha_config_ww();
void check_active(); void check_active();
bool export_values_main(JsonObject & doc); bool export_values_main(JsonObject & doc);
bool export_values_ww(JsonObject & doc); bool export_values_ww(JsonObject & doc);
bool changed_ = false; bool changed_ = false;
bool mqtt_ha_config_ = false; // HA MQTT Discovery bool mqtt_ha_config_ = false; // HA MQTT Discovery
bool mqtt_ha_config_ww_ = false; // HA MQTT Discovery
static constexpr uint8_t EMS_TYPE_UBAParameterWW = 0x33; static constexpr uint8_t EMS_TYPE_UBAParameterWW = 0x33;
static constexpr uint8_t EMS_TYPE_UBAFunctionTest = 0x1D; static constexpr uint8_t EMS_TYPE_UBAFunctionTest = 0x1D;

View File

@@ -49,7 +49,7 @@ bool Heatpump::export_values(JsonObject & json) {
void Heatpump::device_info_web(JsonArray & root) { void Heatpump::device_info_web(JsonArray & root) {
// fetch the values into a JSON document // fetch the values into a JSON document
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
JsonObject json = doc.to<JsonObject>(); JsonObject json = doc.to<JsonObject>();
if (!export_values(json)) { if (!export_values(json)) {
return; // empty return; // empty
@@ -64,7 +64,7 @@ void Heatpump::show_values(uuid::console::Shell & shell) {
EMSdevice::show_values(shell); // always call this to show header EMSdevice::show_values(shell); // always call this to show header
// fetch the values into a JSON document // fetch the values into a JSON document
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
JsonObject json = doc.to<JsonObject>(); JsonObject json = doc.to<JsonObject>();
if (!export_values(json)) { if (!export_values(json)) {
return; // empty return; // empty
@@ -78,27 +78,26 @@ void Heatpump::show_values(uuid::console::Shell & shell) {
void Heatpump::publish_values(JsonObject & json, bool force) { void Heatpump::publish_values(JsonObject & json, bool force) {
// handle HA first // handle HA first
if (Mqtt::mqtt_format() == Mqtt::Format::HA) { if (Mqtt::mqtt_format() == Mqtt::Format::HA) {
register_mqtt_ha_config(force); if (!mqtt_ha_config_ || force) {
register_mqtt_ha_config();
return;
}
} }
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
JsonObject json_data = doc.to<JsonObject>(); JsonObject json_data = doc.to<JsonObject>();
if (export_values(json_data)) { if (export_values(json_data)) {
Mqtt::publish(F("heatpump_data"), doc.as<JsonObject>()); Mqtt::publish(F("heatpump_data"), doc.as<JsonObject>());
} }
} }
void Heatpump::register_mqtt_ha_config(bool force) { void Heatpump::register_mqtt_ha_config() {
if ((mqtt_ha_config_ && !force)) {
return;
}
if (!Mqtt::connected()) { if (!Mqtt::connected()) {
return; return;
} }
// Create the Master device // Create the Master device
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
doc["name"] = F_(EMSESP); doc["name"] = F_(EMSESP);
doc["uniq_id"] = F_(heatpump); doc["uniq_id"] = F_(heatpump);
doc["ic"] = F_(iconheatpump); doc["ic"] = F_(iconheatpump);

View File

@@ -45,7 +45,7 @@ class Heatpump : public EMSdevice {
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;
void register_mqtt_ha_config(bool force); void register_mqtt_ha_config();
uint8_t airHumidity_ = EMS_VALUE_UINT_NOTSET; uint8_t airHumidity_ = EMS_VALUE_UINT_NOTSET;
uint8_t dewTemperature_ = EMS_VALUE_UINT_NOTSET; uint8_t dewTemperature_ = EMS_VALUE_UINT_NOTSET;

View File

@@ -62,7 +62,7 @@ void Mixer::device_info_web(JsonArray & root) {
} }
// fetch the values into a JSON document // fetch the values into a JSON document
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
JsonObject json = doc.to<JsonObject>(); JsonObject json = doc.to<JsonObject>();
if (!export_values_format(Mqtt::Format::SINGLE, json)) { if (!export_values_format(Mqtt::Format::SINGLE, json)) {
return; // empty return; // empty
@@ -101,7 +101,7 @@ void Mixer::show_values(uuid::console::Shell & shell) {
} }
// fetch the values into a JSON document // fetch the values into a JSON document
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
JsonObject json = doc.to<JsonObject>(); JsonObject json = doc.to<JsonObject>();
if (!export_values_format(Mqtt::Format::SINGLE, json)) { if (!export_values_format(Mqtt::Format::SINGLE, json)) {
return; // empty return; // empty
@@ -128,13 +128,16 @@ void Mixer::show_values(uuid::console::Shell & shell) {
void Mixer::publish_values(JsonObject & json, bool force) { void Mixer::publish_values(JsonObject & json, bool force) {
// handle HA first // handle HA first
if (Mqtt::mqtt_format() == Mqtt::Format::HA) { if (Mqtt::mqtt_format() == Mqtt::Format::HA) {
register_mqtt_ha_config(force); if (!mqtt_ha_config_ || force) {
register_mqtt_ha_config();
return;
}
} }
if (Mqtt::mqtt_format() == Mqtt::Format::SINGLE) { if (Mqtt::mqtt_format() == Mqtt::Format::SINGLE) {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
JsonObject json = doc.to<JsonObject>(); JsonObject json_data = doc.to<JsonObject>();
if (export_values_format(Mqtt::mqtt_format(), json)) { if (export_values_format(Mqtt::mqtt_format(), json_data)) {
char topic[30]; char topic[30];
if (type() == Type::HC) { if (type() == Type::HC) {
snprintf_P(topic, 30, PSTR("mixer_data_hc%d"), hc_); snprintf_P(topic, 30, PSTR("mixer_data_hc%d"), hc_);
@@ -150,18 +153,13 @@ void Mixer::publish_values(JsonObject & json, bool force) {
} }
// publish config topic for HA MQTT Discovery // publish config topic for HA MQTT Discovery
void Mixer::register_mqtt_ha_config(bool force) { void Mixer::register_mqtt_ha_config() {
if ((mqtt_ha_config_ && !force)) {
return;
}
if (!Mqtt::connected()) { if (!Mqtt::connected()) {
return; return;
} }
// if we don't have valid values for this HC don't add it ever again // if we don't have valid values for this HC don't add it ever again
if (!Helpers::hasValue(status_)) { if (!Helpers::hasValue(pumpStatus_)) {
mqtt_ha_config_ = true;
return; return;
} }
@@ -194,7 +192,7 @@ void Mixer::register_mqtt_ha_config(bool force) {
std::string topic(100, '\0'); std::string topic(100, '\0');
if (this->type() == Type::HC) { if (this->type() == Type::HC) {
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/mixer_hc%d/config"), hc_); snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/sensor/ems-esp/mixer_hc%d/config"), hc_);
Mqtt::publish_retain(topic, doc.as<JsonObject>(), true); // publish the config payload with retain flag Mqtt::publish_retain(topic, doc.as<JsonObject>(), true); // publish the config payload with retain flag
char hc_name[10]; char hc_name[10];
snprintf_P(hc_name, sizeof(hc_name), PSTR("hc%d"), hc_); snprintf_P(hc_name, sizeof(hc_name), PSTR("hc%d"), hc_);
@@ -204,7 +202,7 @@ void Mixer::register_mqtt_ha_config(bool force) {
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(valveStatus), this->device_type(), "valveStatus", nullptr, nullptr); Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(valveStatus), this->device_type(), "valveStatus", nullptr, nullptr);
} else { } else {
// WWC // WWC
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/mixer_wwc%d/config"), hc_); snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/sensor/ems-esp/mixer_wwc%d/config"), hc_);
Mqtt::publish_retain(topic, doc.as<JsonObject>(), true); // publish the config payload with retain flag Mqtt::publish_retain(topic, doc.as<JsonObject>(), true); // publish the config payload with retain flag
char wwc_name[10]; char wwc_name[10];
snprintf_P(wwc_name, sizeof(wwc_name), PSTR("wwc%d"), hc_); snprintf_P(wwc_name, sizeof(wwc_name), PSTR("wwc%d"), hc_);

View File

@@ -46,7 +46,7 @@ class Mixer : public EMSdevice {
static uuid::log::Logger logger_; static uuid::log::Logger logger_;
bool export_values_format(uint8_t mqtt_format, JsonObject & doc); bool export_values_format(uint8_t mqtt_format, JsonObject & doc);
void register_mqtt_ha_config(bool force); void register_mqtt_ha_config();
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);

View File

@@ -49,6 +49,7 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
register_telegram_type(0x036A, F("SM100Status2"), false, [&](std::shared_ptr<const Telegram> t) { process_SM100Status2(t); }); register_telegram_type(0x036A, F("SM100Status2"), false, [&](std::shared_ptr<const Telegram> t) { process_SM100Status2(t); });
register_telegram_type(0x0380, F("SM100CollectorConfig"), true, [&](std::shared_ptr<const Telegram> t) { process_SM100CollectorConfig(t); }); register_telegram_type(0x0380, F("SM100CollectorConfig"), true, [&](std::shared_ptr<const Telegram> t) { process_SM100CollectorConfig(t); });
register_telegram_type(0x038E, F("SM100Energy"), true, [&](std::shared_ptr<const Telegram> t) { process_SM100Energy(t); }); register_telegram_type(0x038E, F("SM100Energy"), true, [&](std::shared_ptr<const Telegram> t) { process_SM100Energy(t); });
register_telegram_type(0x0391, F("SM100Time"), true, [&](std::shared_ptr<const Telegram> t) { process_SM100Time(t); });
register_mqtt_cmd(F("SM100Tank1MaxTemp"), [&](const char * value, const int8_t id) { return set_SM100Tank1MaxTemp(value, id); }); register_mqtt_cmd(F("SM100Tank1MaxTemp"), [&](const char * value, const int8_t id) { return set_SM100Tank1MaxTemp(value, id); });
} }
@@ -83,6 +84,7 @@ void Solar::device_info_web(JsonArray & root) {
print_value_json(root, F("energyLastHour"), nullptr, F_(energyLastHour), F_(wh), json); print_value_json(root, F("energyLastHour"), nullptr, F_(energyLastHour), F_(wh), json);
print_value_json(root, F("energyToday"), nullptr, F_(energyToday), F_(wh), json); print_value_json(root, F("energyToday"), nullptr, F_(energyToday), F_(wh), json);
print_value_json(root, F("energyTotal"), nullptr, F_(energyTotal), F_(kwh), json); print_value_json(root, F("energyTotal"), nullptr, F_(energyTotal), F_(kwh), json);
print_value_json(root, F("pumpWorkMin"), nullptr, F_(pumpWorkMin), F_(min), json);
if (Helpers::hasValue(pumpWorkMin_)) { if (Helpers::hasValue(pumpWorkMin_)) {
JsonObject dataElement = root.createNestedObject(); JsonObject dataElement = root.createNestedObject();
@@ -118,6 +120,7 @@ void Solar::show_values(uuid::console::Shell & shell) {
print_value_json(shell, F("energyLastHour"), nullptr, F_(energyLastHour), F_(wh), json); print_value_json(shell, F("energyLastHour"), nullptr, F_(energyLastHour), F_(wh), json);
print_value_json(shell, F("energyToday"), nullptr, F_(energyToday), F_(wh), json); print_value_json(shell, F("energyToday"), nullptr, F_(energyToday), F_(wh), json);
print_value_json(shell, F("energyTotal"), nullptr, F_(energyTotal), F_(kwh), json); print_value_json(shell, F("energyTotal"), nullptr, F_(energyTotal), F_(kwh), json);
print_value_json(shell, F("pumpWorkMin"), nullptr, F_(pumpWorkMin), F_(min), json);
if (Helpers::hasValue(pumpWorkMin_)) { if (Helpers::hasValue(pumpWorkMin_)) {
shell.printfln(F(" %s: %d days %d hours %d minutes"), shell.printfln(F(" %s: %d days %d hours %d minutes"),
@@ -132,7 +135,10 @@ void Solar::show_values(uuid::console::Shell & shell) {
void Solar::publish_values(JsonObject & json, bool force) { void Solar::publish_values(JsonObject & json, bool force) {
// handle HA first // handle HA first
if (Mqtt::mqtt_format() == Mqtt::Format::HA) { if (Mqtt::mqtt_format() == Mqtt::Format::HA) {
register_mqtt_ha_config(force); if ((!mqtt_ha_config_ || force)) {
register_mqtt_ha_config();
return;
}
} }
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
@@ -147,17 +153,13 @@ void Solar::publish_values(JsonObject & json, bool force) {
} }
// publish config topic for HA MQTT Discovery // publish config topic for HA MQTT Discovery
void Solar::register_mqtt_ha_config(bool force) { void Solar::register_mqtt_ha_config() {
if ((mqtt_ha_config_ && !force)) {
return;
}
if (!Mqtt::connected()) { if (!Mqtt::connected()) {
return; return;
} }
// Create the Master device // Create the Master device
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
doc["name"] = F_(EMSESP); doc["name"] = F_(EMSESP);
doc["uniq_id"] = F_(solar); doc["uniq_id"] = F_(solar);
doc["ic"] = F_(iconthermostat); doc["ic"] = F_(iconthermostat);
@@ -183,10 +185,10 @@ void Solar::register_mqtt_ha_config(bool force) {
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(heatExchangerTemp), this->device_type(), "heatExchangerTemp", F_(degrees), nullptr); Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(heatExchangerTemp), this->device_type(), "heatExchangerTemp", F_(degrees), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(solarPumpModulation), this->device_type(), "solarPumpModulation", F_(percent), nullptr); Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(solarPumpModulation), this->device_type(), "solarPumpModulation", F_(percent), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(cylinderPumpModulation), this->device_type(), "cylinderPumpModulation", F_(percent), nullptr); Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(cylinderPumpModulation), this->device_type(), "cylinderPumpModulation", F_(percent), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(pumpWorkMin), this->device_type(), "pumpWorkMin", nullptr, nullptr); Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(pumpWorkMin), this->device_type(), "pumpWorkMin", F_(min), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(energyLastHour), this->device_type(), "energyLastHour", nullptr, nullptr); Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(energyLastHour), this->device_type(), "energyLastHour", F_(wh), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(energyToday), this->device_type(), "energyToday", nullptr, nullptr); Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(energyToday), this->device_type(), "energyToday", F_(wh), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(energyTotal), this->device_type(), "energyTotal", nullptr, nullptr); Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(energyTotal), this->device_type(), "energyTotal", F_(kwh), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(solarPump), this->device_type(), "solarPump", nullptr, nullptr); Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(solarPump), this->device_type(), "solarPump", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(valveStatus), this->device_type(), "valveStatus", nullptr, nullptr); Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(valveStatus), this->device_type(), "valveStatus", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(tankHeated), this->device_type(), "tankHeated", nullptr, nullptr); Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(tankHeated), this->device_type(), "tankHeated", nullptr, nullptr);
@@ -450,6 +452,13 @@ void Solar::process_SM100Energy(std::shared_ptr<const Telegram> telegram) {
changed_ |= telegram->read_value(energyTotal_, 8); // total / 10 in kWh changed_ |= telegram->read_value(energyTotal_, 8); // total / 10 in kWh
} }
/*
* SM100Time - type 0x0391 EMS+ for pump working time
*/
void Solar::process_SM100Time(std::shared_ptr<const Telegram> telegram) {
changed_ |= telegram->read_value(pumpWorkMin_, 1, 3);
}
/* /*
* Junkers ISM1 Solar Module - type 0x0103 EMS+ for energy readings * Junkers ISM1 Solar Module - type 0x0103 EMS+ for energy readings
* e.g. B0 00 FF 00 00 03 32 00 00 00 00 13 00 D6 00 00 00 FB D0 F0 * e.g. B0 00 FF 00 00 03 32 00 00 00 00 13 00 D6 00 00 00 FB D0 F0

View File

@@ -44,7 +44,7 @@ class Solar : public EMSdevice {
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;
void register_mqtt_ha_config(bool force); void register_mqtt_ha_config();
int16_t collectorTemp_ = EMS_VALUE_SHORT_NOTSET; // TS1: Temperature sensor for collector array 1 int16_t collectorTemp_ = EMS_VALUE_SHORT_NOTSET; // TS1: Temperature sensor for collector array 1
int16_t tankBottomTemp_ = EMS_VALUE_SHORT_NOTSET; // TS2: Temperature sensor 1 cylinder, bottom (solar thermal system) int16_t tankBottomTemp_ = EMS_VALUE_SHORT_NOTSET; // TS2: Temperature sensor 1 cylinder, bottom (solar thermal system)
@@ -106,6 +106,7 @@ class Solar : public EMSdevice {
void process_SM100Status2(std::shared_ptr<const Telegram> telegram); void process_SM100Status2(std::shared_ptr<const Telegram> telegram);
void process_SM100CollectorConfig(std::shared_ptr<const Telegram> telegram); void process_SM100CollectorConfig(std::shared_ptr<const Telegram> telegram);
void process_SM100Energy(std::shared_ptr<const Telegram> telegram); void process_SM100Energy(std::shared_ptr<const Telegram> telegram);
void process_SM100Time(std::shared_ptr<const Telegram> telegram);
void process_SM100wwTemperature(std::shared_ptr<const Telegram> telegram); void process_SM100wwTemperature(std::shared_ptr<const Telegram> telegram);
void process_SM100wwStatus(std::shared_ptr<const Telegram> telegram); void process_SM100wwStatus(std::shared_ptr<const Telegram> telegram);

View File

@@ -114,10 +114,12 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
monitor_typeids = {0x02A5, 0x02A6, 0x02A7, 0x02A8}; monitor_typeids = {0x02A5, 0x02A6, 0x02A7, 0x02A8};
set_typeids = {0x02B9, 0x02BA, 0x02BB, 0x02BC}; set_typeids = {0x02B9, 0x02BA, 0x02BB, 0x02BC};
summer_typeids = {0x02AF, 0x02B0, 0x02B1, 0x02B2}; summer_typeids = {0x02AF, 0x02B0, 0x02B1, 0x02B2};
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, [&](std::shared_ptr<const Telegram> t) { process_RC300Monitor(t); });
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, [&](std::shared_ptr<const Telegram> t) { process_RC300Set(t); });
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, [&](std::shared_ptr<const Telegram> t) { process_RC300Summer(t); });
register_telegram_type(curve_typeids[i], F("RC300Curves"), false, [&](std::shared_ptr<const Telegram> t) { process_RC300Curve(t); });
} }
register_telegram_type(0x2F5, F("RC300WWmode"), true, [&](std::shared_ptr<const Telegram> t) { process_RC300WWmode(t); }); register_telegram_type(0x2F5, F("RC300WWmode"), true, [&](std::shared_ptr<const Telegram> t) { process_RC300WWmode(t); });
register_telegram_type(0x31B, F("RC300WWtemp"), true, [&](std::shared_ptr<const Telegram> t) { process_RC300WWtemp(t); }); register_telegram_type(0x31B, F("RC300WWtemp"), true, [&](std::shared_ptr<const Telegram> t) { process_RC300WWtemp(t); });
@@ -171,11 +173,14 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
for (uint8_t i = 0; i < summer_typeids.size(); i++) { for (uint8_t i = 0; i < summer_typeids.size(); i++) {
EMSESP::send_read_request(summer_typeids[i], device_id); EMSESP::send_read_request(summer_typeids[i], device_id);
} }
} // namespace emsesp for (uint8_t i = 0; i < curve_typeids.size(); i++) {
EMSESP::send_read_request(curve_typeids[i], device_id);
}
}
// prepare data for Web UI // prepare data for Web UI
void Thermostat::device_info_web(JsonArray & root) { void Thermostat::device_info_web(JsonArray & root) {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc_main; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc_main;
JsonObject json_main = doc_main.to<JsonObject>(); JsonObject json_main = doc_main.to<JsonObject>();
if (export_values_main(json_main)) { if (export_values_main(json_main)) {
print_value_json(root, F("time"), nullptr, F_(time), nullptr, json_main); print_value_json(root, F("time"), nullptr, F_(time), nullptr, json_main);
@@ -194,6 +199,7 @@ void Thermostat::device_info_web(JsonArray & root) {
print_value_json(root, F("wwmode"), nullptr, F_(wwmode), nullptr, json_main); print_value_json(root, F("wwmode"), nullptr, F_(wwmode), nullptr, json_main);
print_value_json(root, F("wwtemp"), nullptr, F_(wwtemp), nullptr, json_main); print_value_json(root, F("wwtemp"), nullptr, F_(wwtemp), nullptr, json_main);
print_value_json(root, F("wwtemplow"), nullptr, F_(wwtemplow), nullptr, json_main); print_value_json(root, F("wwtemplow"), nullptr, F_(wwtemplow), nullptr, json_main);
print_value_json(root, F("wwextra1"), nullptr, F_(wwextra1), nullptr, json_main);
print_value_json(root, F("wwcircmode"), nullptr, F_(wwcircmode), nullptr, json_main); print_value_json(root, F("wwcircmode"), nullptr, F_(wwcircmode), nullptr, json_main);
} }
@@ -226,6 +232,8 @@ void Thermostat::device_info_web(JsonArray & root) {
print_value_json(root, F("designtemp"), FPSTR(prefix_str), F_(designtemp), F_(degrees), json); print_value_json(root, F("designtemp"), FPSTR(prefix_str), F_(designtemp), F_(degrees), json);
print_value_json(root, F("roominfluence"), FPSTR(prefix_str), F_(roominfluence), F_(degrees), json); print_value_json(root, F("roominfluence"), FPSTR(prefix_str), F_(roominfluence), F_(degrees), json);
print_value_json(root, F("flowtempoffset"), FPSTR(prefix_str), F_(flowtempoffset), F_(degrees), json); print_value_json(root, F("flowtempoffset"), FPSTR(prefix_str), F_(flowtempoffset), F_(degrees), json);
print_value_json(root, F("minflowtemp"), FPSTR(prefix_str), F_(minflowtemp), F_(degrees), json);
print_value_json(root, F("maxflowtemp"), FPSTR(prefix_str), F_(maxflowtemp), F_(degrees), json);
print_value_json(root, F("summertemp"), FPSTR(prefix_str), F_(summertemp), F_(degrees), json); print_value_json(root, F("summertemp"), FPSTR(prefix_str), F_(summertemp), F_(degrees), json);
print_value_json(root, F("summermode"), FPSTR(prefix_str), F_(summermode), F_(degrees), json); print_value_json(root, F("summermode"), FPSTR(prefix_str), F_(summermode), F_(degrees), json);
print_value_json(root, F("mode"), FPSTR(prefix_str), F_(mode), nullptr, json); print_value_json(root, F("mode"), FPSTR(prefix_str), F_(mode), nullptr, json);
@@ -278,6 +286,7 @@ void Thermostat::show_values(uuid::console::Shell & shell) {
print_value_json(shell, F("wwmode"), nullptr, F_(wwmode), nullptr, json_main); print_value_json(shell, F("wwmode"), nullptr, F_(wwmode), nullptr, json_main);
print_value_json(shell, F("wwtemp"), nullptr, F_(wwtemp), nullptr, json_main); print_value_json(shell, F("wwtemp"), nullptr, F_(wwtemp), nullptr, json_main);
print_value_json(shell, F("wwtemplow"), nullptr, F_(wwtemplow), nullptr, json_main); print_value_json(shell, F("wwtemplow"), nullptr, F_(wwtemplow), nullptr, json_main);
print_value_json(shell, F("wwextra1"), nullptr, F_(wwextra1), nullptr, json_main);
print_value_json(shell, F("wwcircmode"), nullptr, F_(wwcircmode), nullptr, json_main); print_value_json(shell, F("wwcircmode"), nullptr, F_(wwcircmode), nullptr, json_main);
} }
@@ -310,6 +319,8 @@ void Thermostat::show_values(uuid::console::Shell & shell) {
print_value_json(shell, F("designtemp"), F_(2spaces), F_(designtemp), F_(degrees), json); print_value_json(shell, F("designtemp"), F_(2spaces), F_(designtemp), F_(degrees), json);
print_value_json(shell, F("roominfluence"), F_(2spaces), F_(roominfluence), F_(degrees), json); print_value_json(shell, F("roominfluence"), F_(2spaces), F_(roominfluence), F_(degrees), json);
print_value_json(shell, F("flowtempoffset"), F_(2spaces), F_(flowtempoffset), F_(degrees), json); print_value_json(shell, F("flowtempoffset"), F_(2spaces), F_(flowtempoffset), F_(degrees), json);
print_value_json(shell, F("minflowtemp"), F_(2spaces), F_(minflowtemp), F_(degrees), json);
print_value_json(shell, F("maxflowtemp"), F_(2spaces), F_(maxflowtemp), F_(degrees), json);
print_value_json(shell, F("summertemp"), F_(2spaces), F_(summertemp), F_(degrees), json); print_value_json(shell, F("summertemp"), F_(2spaces), F_(summertemp), F_(degrees), json);
print_value_json(shell, F("summermode"), F_(2spaces), F_(summermode), F_(degrees), json); print_value_json(shell, F("summermode"), F_(2spaces), F_(summermode), F_(degrees), json);
print_value_json(shell, F("mode"), F_(2spaces), F_(mode), nullptr, json); print_value_json(shell, F("mode"), F_(2spaces), F_(mode), nullptr, json);
@@ -325,8 +336,14 @@ void Thermostat::publish_values(JsonObject & json, bool force) {
if (EMSESP::actual_master_thermostat() != this->device_id()) { if (EMSESP::actual_master_thermostat() != this->device_id()) {
return; return;
} }
// see if we have already registered this with HA MQTT Discovery, if not send the config
if (Mqtt::mqtt_format() == Mqtt::Format::HA) {
if (!ha_config(force)) {
return;
}
}
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc;
JsonObject json_data = doc.to<JsonObject>(); JsonObject json_data = doc.to<JsonObject>();
bool has_data = false; bool has_data = false;
@@ -343,10 +360,6 @@ void Thermostat::publish_values(JsonObject & json, bool force) {
// if we're in HA or CUSTOM, send out the complete topic with all the data // if we're in HA or CUSTOM, send out the complete topic with all the data
if (Mqtt::mqtt_format() != Mqtt::Format::SINGLE && has_data) { if (Mqtt::mqtt_format() != Mqtt::Format::SINGLE && has_data) {
// see if we have already registered this with HA MQTT Discovery, if not send the config
if (Mqtt::mqtt_format() == Mqtt::Format::HA) {
ha_config(force);
}
Mqtt::publish(F("thermostat_data"), json_data); Mqtt::publish(F("thermostat_data"), json_data);
} }
} }
@@ -473,6 +486,16 @@ bool Thermostat::export_values_main(JsonObject & rootThermostat) {
rootThermostat["wwtemplow"] = wwTempLow_; rootThermostat["wwtemplow"] = wwTempLow_;
} }
// Warm water extra1
if (Helpers::hasValue(wwExtra1_)) {
rootThermostat["wwextra1"] = wwExtra1_;
}
// Warm water extra2
if (Helpers::hasValue(wwExtra2_)) {
rootThermostat["wwextra2"] = wwExtra2_;
}
// Warm Water circulation mode // Warm Water circulation mode
if (Helpers::hasValue(wwCircMode_)) { if (Helpers::hasValue(wwCircMode_)) {
char s[7]; char s[7];
@@ -600,6 +623,16 @@ bool Thermostat::export_values_hc(uint8_t mqtt_format, JsonObject & rootThermost
dataThermostat["flowtempoffset"] = hc->flowtempoffset; dataThermostat["flowtempoffset"] = hc->flowtempoffset;
} }
// Min Flow temperature offset
if (Helpers::hasValue(hc->minflowtemp)) {
dataThermostat["minflowtemp"] = hc->minflowtemp;
}
// Max Flow temperature offset
if (Helpers::hasValue(hc->maxflowtemp)) {
dataThermostat["maxflowtemp"] = hc->maxflowtemp;
}
// Summer temperature // Summer temperature
if (Helpers::hasValue(hc->summertemp)) { if (Helpers::hasValue(hc->summertemp)) {
dataThermostat["summertemp"] = hc->summertemp; dataThermostat["summertemp"] = hc->summertemp;
@@ -654,23 +687,32 @@ bool Thermostat::export_values_hc(uint8_t mqtt_format, JsonObject & rootThermost
} }
// set up HA MQTT Discovery // set up HA MQTT Discovery
void Thermostat::ha_config(bool force) { bool Thermostat::ha_config(bool force) {
if (!Mqtt::connected()) { if (!Mqtt::connected()) {
return; return false;
}
if (force) {
for (const auto & hc : heating_circuits_) {
hc->ha_registered(false);
}
ha_registered(false);
} }
if (force || !ha_registered()) { if (!ha_registered()) {
register_mqtt_ha_config(); register_mqtt_ha_config();
ha_registered(true); ha_registered(true);
return false;
} }
// check to see which heating circuits need publishing // check to see which heating circuits need publishing
for (const auto & hc : heating_circuits_) { for (const auto & hc : heating_circuits_) {
if (force || (hc->is_active() && !hc->ha_registered())) { if (hc->is_active() && !hc->ha_registered()) {
register_mqtt_ha_config(hc->hc_num()); register_mqtt_ha_config(hc->hc_num());
hc->ha_registered(true); hc->ha_registered(true);
return false;
} }
} }
return true;
} }
// returns the heating circuit object based on the hc number // returns the heating circuit object based on the hc number
@@ -679,13 +721,15 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(const ui
// if hc_num is 0 then return the first existing hc in the list // if hc_num is 0 then return the first existing hc in the list
if (hc_num == AUTO_HEATING_CIRCUIT) { if (hc_num == AUTO_HEATING_CIRCUIT) {
for (const auto & heating_circuit : heating_circuits_) { for (const auto & heating_circuit : heating_circuits_) {
if (heating_circuit->is_active()) {
return heating_circuit; return heating_circuit;
} }
} }
}
// otherwise find a match // otherwise find a match
for (const auto & heating_circuit : heating_circuits_) { for (const auto & heating_circuit : heating_circuits_) {
if (heating_circuit->hc_num() == hc_num) { if ((heating_circuit->hc_num() == hc_num) && heating_circuit->is_active()) {
return heating_circuit; return heating_circuit;
} }
} }
@@ -773,10 +817,10 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
return heating_circuits_.back(); // even after sorting, this should still point back to the newly created HC return heating_circuits_.back(); // even after sorting, this should still point back to the newly created HC
} }
// publish config topic for HA MQTT Discovery // publish config topic for HA MQTT Discovery for main thermostat values
// homeassistant/climate/ems-esp/thermostat/config // homeassistant/climate/ems-esp/thermostat/config
void Thermostat::register_mqtt_ha_config() { void Thermostat::register_mqtt_ha_config() {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
doc["uniq_id"] = F("thermostat"); doc["uniq_id"] = F("thermostat");
doc["ic"] = F("mdi:home-thermometer-outline"); doc["ic"] = F("mdi:home-thermometer-outline");
@@ -808,6 +852,7 @@ void Thermostat::register_mqtt_ha_config() {
if (model == EMS_DEVICE_FLAG_RC300 || model == EMS_DEVICE_FLAG_RC100) { if (model == EMS_DEVICE_FLAG_RC300 || model == EMS_DEVICE_FLAG_RC100) {
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(dampedtemp), this->device_type(), "dampedtemp", F_(degrees), F_(icontemperature)); Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(dampedtemp), this->device_type(), "dampedtemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(building), this->device_type(), "building", nullptr, nullptr); Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(building), this->device_type(), "building", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(minexttemp), this->device_type(), "minexttemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(floordry), this->device_type(), "floordry", nullptr, nullptr); Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(floordry), this->device_type(), "floordry", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(floordrytemp), this->device_type(), "floordrytemp", F_(degrees), F_(icontemperature)); Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(floordrytemp), this->device_type(), "floordrytemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(wwmode), this->device_type(), "wwmode", nullptr, nullptr); Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(wwmode), this->device_type(), "wwmode", nullptr, nullptr);
@@ -826,18 +871,18 @@ void Thermostat::register_mqtt_ha_config() {
} }
} }
// publish config topic for HA MQTT Discovery // publish config topic for HA MQTT Discovery for each of the heating circuit
// e.g. homeassistant/climate/ems-esp/thermostat_hc1/config // e.g. homeassistant/climate/ems-esp/thermostat_hc1/config
void Thermostat::register_mqtt_ha_config(uint8_t hc_num) { void Thermostat::register_mqtt_ha_config(uint8_t hc_num) {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
char str1[40]; char str1[20];
snprintf_P(str1, sizeof(str1), PSTR("Thermostat hc%d"), hc_num); snprintf_P(str1, sizeof(str1), PSTR("Thermostat hc%d"), hc_num);
char str2[40]; char str2[20];
snprintf_P(str2, sizeof(str2), PSTR("thermostat_hc%d"), hc_num); snprintf_P(str2, sizeof(str2), PSTR("thermostat_hc%d"), hc_num);
char str3[40]; char str3[25];
snprintf_P(str3, sizeof(str3), PSTR("~/%s"), str2); snprintf_P(str3, sizeof(str3), PSTR("~/%s"), str2);
doc["mode_cmd_t"] = str3; doc["mode_cmd_t"] = str3;
doc["temp_cmd_t"] = str3; doc["temp_cmd_t"] = str3;
@@ -909,7 +954,7 @@ void Thermostat::register_mqtt_ha_config(uint8_t hc_num) {
// for each of the heating circuits // for each of the heating circuits
std::string topic2(100, '\0'); std::string topic2(100, '\0');
snprintf_P(&topic2[0], topic2.capacity() + 1, PSTR("thermostat_hc%d"), hc_num); snprintf_P(&topic2[0], topic2.capacity() + 1, PSTR("thermostat_hc%d"), hc_num);
register_mqtt_topic(topic2, [=](const char * m) { return thermostat_ha_cmd(m, hc_num); }); register_mqtt_topic(topic2, [&](const char * m) { return thermostat_ha_cmd(m, hc_num); });
char hc_name[10]; // hc{1-4} char hc_name[10]; // hc{1-4}
strlcpy(hc_name, "hc", 10); strlcpy(hc_name, "hc", 10);
@@ -918,6 +963,9 @@ void Thermostat::register_mqtt_ha_config(uint8_t hc_num) {
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(mode), this->device_type(), "mode", nullptr, nullptr); Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(mode), this->device_type(), "mode", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(seltemp), this->device_type(), "seltemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(currtemp), this->device_type(), "currtemp", F_(degrees), F_(icontemperature));
uint8_t model = this->model(); uint8_t model = this->model();
switch (model) { switch (model) {
case EMS_DEVICE_FLAG_RC100: case EMS_DEVICE_FLAG_RC100:
@@ -927,6 +975,12 @@ void Thermostat::register_mqtt_ha_config(uint8_t hc_num) {
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(manualtemp), this->device_type(), "manualtemp", F_(degrees), F_(icontemperature)); Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(manualtemp), this->device_type(), "manualtemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(comforttemp), this->device_type(), "comforttemp", F_(degrees), F_(icontemperature)); Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(comforttemp), this->device_type(), "comforttemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(summertemp), this->device_type(), "summertemp", F_(degrees), F_(icontemperature)); Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(summertemp), this->device_type(), "summertemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(designtemp), this->device_type(), "designtemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(offsettemp), this->device_type(), "offsettemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(minflowtemp), this->device_type(), "minflowtemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(maxflowtemp), this->device_type(), "maxflowtemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(roominfluence), this->device_type(), "roominfluence", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(nofrosttemp), this->device_type(), "nofrosttemp", F_(degrees), F_(icontemperature));
break; break;
case EMS_DEVICE_FLAG_RC20_2: case EMS_DEVICE_FLAG_RC20_2:
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(daytemp), this->device_type(), "daytemp", F_(degrees), F_(icontemperature)); Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(daytemp), this->device_type(), "daytemp", F_(degrees), F_(icontemperature));
@@ -944,6 +998,8 @@ void Thermostat::register_mqtt_ha_config(uint8_t hc_num) {
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(summertemp), this->device_type(), "summertemp", F_(degrees), F_(icontemperature)); Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(summertemp), this->device_type(), "summertemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(nofrosttemp), this->device_type(), "nofrosttemp", F_(degrees), F_(icontemperature)); Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(nofrosttemp), this->device_type(), "nofrosttemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(roominfluence), this->device_type(), "roominfluence", F_(degrees), F_(icontemperature)); Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(roominfluence), this->device_type(), "roominfluence", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(minflowtemp), this->device_type(), "minflowtemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(maxflowtemp), this->device_type(), "maxflowtemp", F_(degrees), F_(icontemperature));
break; break;
case EMS_DEVICE_FLAG_JUNKERS: case EMS_DEVICE_FLAG_JUNKERS:
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(modetype), this->device_type(), "modetype", nullptr, nullptr); Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(modetype), this->device_type(), "modetype", nullptr, nullptr);
@@ -1094,6 +1150,15 @@ std::string Thermostat::mode_tostring(uint8_t mode) {
case HeatingCircuit::Mode::DESIGN: case HeatingCircuit::Mode::DESIGN:
return read_flash_string(F("design")); return read_flash_string(F("design"));
break; break;
case HeatingCircuit::Mode::MINFLOW:
return read_flash_string(F("minflow"));
break;
case HeatingCircuit::Mode::MAXFLOW:
return read_flash_string(F("maxflow"));
break;
case HeatingCircuit::Mode::ROOMINFLUENCE:
return read_flash_string(F("roominfluence"));
break;
default: default:
case HeatingCircuit::Mode::UNKNOWN: case HeatingCircuit::Mode::UNKNOWN:
return read_flash_string(F("unknown")); return read_flash_string(F("unknown"));
@@ -1268,8 +1333,28 @@ void Thermostat::process_RC300Set(std::shared_ptr<const Telegram> telegram) {
// types 0x2AF ff // types 0x2AF ff
void Thermostat::process_RC300Summer(std::shared_ptr<const Telegram> telegram) { void Thermostat::process_RC300Summer(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
changed_ |= telegram->read_value(hc->roominfluence, 0);
changed_ |= telegram->read_value(hc->offsettemp, 2);
changed_ |= telegram->read_value(hc->summertemp, 6); changed_ |= telegram->read_value(hc->summertemp, 6);
changed_ |= telegram->read_value(hc->summer_setmode, 7); changed_ |= telegram->read_value(hc->summer_setmode, 7);
if (hc->heatingtype < 3) {
changed_ |= telegram->read_value(hc->designtemp, 4);
} else {
changed_ |= telegram->read_value(hc->designtemp, 5);
}
changed_ |= telegram->read_value(hc->minflowtemp, 8);
}
// types 0x29B ff
void Thermostat::process_RC300Curve(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
changed_ |= telegram->read_value(hc->heatingtype, 1); // 1=radiator, 2=convector, 3=floor
changed_ |= telegram->read_value(hc->nofrosttemp, 6);
if (hc->heatingtype < 3) {
changed_ |= telegram->read_value(hc->maxflowtemp, 8);
} else {
changed_ |= telegram->read_value(hc->maxflowtemp, 7);
}
} }
// types 0x31B (and 0x31C?) // types 0x31B (and 0x31C?)
@@ -1306,6 +1391,7 @@ void Thermostat::process_RC300OutdoorTemp(std::shared_ptr<const Telegram> telegr
// 0x240 RC300 parameter // 0x240 RC300 parameter
void Thermostat::process_RC300Settings(std::shared_ptr<const Telegram> telegram) { void Thermostat::process_RC300Settings(std::shared_ptr<const Telegram> telegram) {
changed_ |= telegram->read_value(ibaBuildingType_, 9); // 1=light, 2=medium, 3=heavy changed_ |= telegram->read_value(ibaBuildingType_, 9); // 1=light, 2=medium, 3=heavy
changed_ |= telegram->read_value(ibaMinExtTemperature_, 10);
} }
// 0x267 RC300 floordrying // 0x267 RC300 floordrying
@@ -1370,10 +1456,13 @@ void Thermostat::process_RC35Set(std::shared_ptr<const Telegram> telegram) {
changed_ |= telegram->read_value(hc->summertemp, 22); // is * 1 changed_ |= telegram->read_value(hc->summertemp, 22); // is * 1
changed_ |= telegram->read_value(hc->nofrosttemp, 23); // is * 1 changed_ |= telegram->read_value(hc->nofrosttemp, 23); // is * 1
changed_ |= telegram->read_value(hc->flowtempoffset, 24); // is * 1, only in mixed circuits changed_ |= telegram->read_value(hc->flowtempoffset, 24); // is * 1, only in mixed circuits
changed_ |= telegram->read_value(hc->minflowtemp, 16);
if (hc->heatingtype == 3) { if (hc->heatingtype == 3) {
changed_ |= telegram->read_value(hc->designtemp, 36); // is * 1 changed_ |= telegram->read_value(hc->designtemp, 36); // is * 1
changed_ |= telegram->read_value(hc->maxflowtemp, 35); // is * 1
} else { } else {
changed_ |= telegram->read_value(hc->designtemp, 17); // is * 1 changed_ |= telegram->read_value(hc->designtemp, 17); // is * 1
changed_ |= telegram->read_value(hc->maxflowtemp, 15); // is * 1
} }
} }
@@ -1564,24 +1653,6 @@ bool Thermostat::set_control(const char * value, const int8_t id) {
return true; return true;
} }
// Set roominfluence
bool Thermostat::set_roominfluence(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("Set roominfluence: Heating Circuit %d not found or activated"), hc_num);
return false;
}
int t = 0;
if (!Helpers::value2number(value, t)) {
LOG_WARNING(F("Set roominfluence: Invalid value"));
return false;
}
LOG_INFO(F("Setting roominfluence to %d"), t);
write_command(set_typeids[hc->hc_num() - 1], 4, t, set_typeids[hc->hc_num() - 1]);
return true;
}
// sets the thermostat ww working mode, where mode is a string, ems and ems+ // sets the thermostat ww working mode, where mode is a string, ems and ems+
bool Thermostat::set_wwmode(const char * value, const int8_t id) { bool Thermostat::set_wwmode(const char * value, const int8_t id) {
uint8_t set = 0xFF; uint8_t set = 0xFF;
@@ -1627,6 +1698,19 @@ bool Thermostat::set_wwtemplow(const char * value, const int8_t id) {
return true; return true;
} }
// Set ww onetime RC300, ems+
bool Thermostat::set_wwonetime(const char * value, const int8_t id) {
bool b = false;
if (!Helpers::value2bool(value, b)) {
LOG_WARNING(F("Set warm water onetime: Invalid value"));
return false;
}
char s[7];
LOG_INFO(F("Setting warm water onetime to %s"), Helpers::render_boolean(s, b));
write_command(0x02F5, 11, b ? 0xFF : 0x00, 0x031D);
return true;
}
// sets the thermostat ww circulation working mode, where mode is a string // sets the thermostat ww circulation working mode, where mode is a string
bool Thermostat::set_wwcircmode(const char * value, const int8_t id) { bool Thermostat::set_wwcircmode(const char * value, const int8_t id) {
@@ -1957,6 +2041,15 @@ bool Thermostat::set_temperature(const float temperature, const std::string & mo
if (mode_tostring(HeatingCircuit::Mode::DESIGN) == mode) { if (mode_tostring(HeatingCircuit::Mode::DESIGN) == mode) {
return set_temperature(temperature, HeatingCircuit::Mode::DESIGN, hc_num); return set_temperature(temperature, HeatingCircuit::Mode::DESIGN, hc_num);
} }
if (mode_tostring(HeatingCircuit::Mode::MINFLOW) == mode) {
return set_temperature(temperature, HeatingCircuit::Mode::MINFLOW, hc_num);
}
if (mode_tostring(HeatingCircuit::Mode::MAXFLOW) == mode) {
return set_temperature(temperature, HeatingCircuit::Mode::MAXFLOW, hc_num);
}
if (mode_tostring(HeatingCircuit::Mode::ROOMINFLUENCE) == mode) {
return set_temperature(temperature, HeatingCircuit::Mode::ROOMINFLUENCE, hc_num);
}
LOG_WARNING(F("Set temperature: Invalid mode")); LOG_WARNING(F("Set temperature: Invalid mode"));
return false; return false;
@@ -2005,6 +2098,49 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
case HeatingCircuit::Mode::ECO: case HeatingCircuit::Mode::ECO:
offset = 0x04; // eco offset offset = 0x04; // eco offset
break; break;
case HeatingCircuit::Mode::OFFSET:
offset = 2;
set_typeid = summer_typeids[hc->hc_num() - 1];
validate_typeid = set_typeid;
break;
case HeatingCircuit::Mode::DESIGN:
set_typeid = summer_typeids[hc->hc_num() - 1];
validate_typeid = set_typeid;
if (hc->heatingtype == 3) {
offset = 5;
} else {
offset = 4;
}
factor = 1;
break;
case HeatingCircuit::Mode::MINFLOW:
set_typeid = summer_typeids[hc->hc_num() - 1];
validate_typeid = set_typeid;
offset = 8;
factor = 1;
break;
case HeatingCircuit::Mode::MAXFLOW:
set_typeid = curve_typeids[hc->hc_num() - 1];
validate_typeid = set_typeid;
if (hc->heatingtype == 3) {
offset = 7;
} else {
offset = 8;
}
factor = 1;
break;
case HeatingCircuit::Mode::NOFROST:
set_typeid = curve_typeids[hc->hc_num() - 1];
validate_typeid = set_typeid;
offset = 6;
factor = 1;
break;
case HeatingCircuit::Mode::ROOMINFLUENCE:
set_typeid = summer_typeids[hc->hc_num() - 1];
validate_typeid = set_typeid;
offset = 0;
factor = 1;
break;
default: default:
case HeatingCircuit::Mode::AUTO: case HeatingCircuit::Mode::AUTO:
uint8_t mode_ = hc->get_mode(this->flags()); uint8_t mode_ = hc->get_mode(this->flags());
@@ -2067,6 +2203,22 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
offset = EMS_OFFSET_RC35Set_temp_nofrost; offset = EMS_OFFSET_RC35Set_temp_nofrost;
factor = 1; factor = 1;
break; break;
case HeatingCircuit::Mode::ROOMINFLUENCE:
offset = 4;
factor = 1;
break;
case HeatingCircuit::Mode::MINFLOW:
offset = 16;
factor = 1;
break;
case HeatingCircuit::Mode::MAXFLOW:
if (hc->heatingtype == 3) {
offset = 35;
} else {
offset = 15;
}
factor = 1;
break;
default: default:
case HeatingCircuit::Mode::AUTO: // automatic selection, if no type is defined, we use the standard code case HeatingCircuit::Mode::AUTO: // automatic selection, if no type is defined, we use the standard code
validate_typeid = monitor_typeids[hc->hc_num() - 1]; //get setpoint roomtemp back validate_typeid = monitor_typeids[hc->hc_num() - 1]; //get setpoint roomtemp back
@@ -2226,6 +2378,18 @@ bool Thermostat::set_flowtempoffset(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::FLOWOFFSET); return set_temperature_value(value, id, HeatingCircuit::Mode::FLOWOFFSET);
} }
bool Thermostat::set_maxflowtemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::MAXFLOW);
}
bool Thermostat::set_minflowtemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::MINFLOW);
}
bool Thermostat::set_roominfluence(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::ROOMINFLUENCE);
}
// API commands for MQTT and Console // API commands for MQTT and Console
void Thermostat::add_commands() { void Thermostat::add_commands() {
// if this thermostat doesn't support write, don't add the commands // if this thermostat doesn't support write, don't add the commands
@@ -2250,7 +2414,15 @@ void Thermostat::add_commands() {
register_mqtt_cmd(F("wwmode"), [&](const char * value, const int8_t id) { return set_wwmode(value, id); }); register_mqtt_cmd(F("wwmode"), [&](const char * value, const int8_t id) { return set_wwmode(value, id); });
register_mqtt_cmd(F("wwtemp"), [&](const char * value, const int8_t id) { return set_wwtemp(value, id); }); register_mqtt_cmd(F("wwtemp"), [&](const char * value, const int8_t id) { return set_wwtemp(value, id); });
register_mqtt_cmd(F("wwtemplow"), [&](const char * value, const int8_t id) { return set_wwtemplow(value, id); }); register_mqtt_cmd(F("wwtemplow"), [&](const char * value, const int8_t id) { return set_wwtemplow(value, id); });
register_mqtt_cmd(F("wwonetime"), [&](const char * value, const int8_t id) { return set_wwonetime(value, id); });
register_mqtt_cmd(F("building"), [&](const char * value, const int8_t id) { return set_building(value, id); }); register_mqtt_cmd(F("building"), [&](const char * value, const int8_t id) { return set_building(value, id); });
register_mqtt_cmd(F("nofrosttemp"), [&](const char * value, const int8_t id) { return set_nofrosttemp(value, id); });
register_mqtt_cmd(F("designtemp"), [&](const char * value, const int8_t id) { return set_designtemp(value, id); });
register_mqtt_cmd(F("offsettemp"), [&](const char * value, const int8_t id) { return set_offsettemp(value, id); });
register_mqtt_cmd(F("minflowtemp"), [&](const char * value, const int8_t id) { return set_minflowtemp(value, id); });
register_mqtt_cmd(F("maxflowtemp"), [&](const char * value, const int8_t id) { return set_maxflowtemp(value, id); });
register_mqtt_cmd(F("minexttemp"), [&](const char * value, const int8_t id) { return set_minexttemp(value, id); });
register_mqtt_cmd(F("roominfluence"), [&](const char * value, const int8_t id) { return set_roominfluence(value, id); });
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"), [&](const char * value, const int8_t id) { return set_nighttemp(value, id); });
@@ -2280,6 +2452,8 @@ void Thermostat::add_commands() {
register_mqtt_cmd(F("wwcircmode"), [&](const char * value, const int8_t id) { return set_wwcircmode(value, id); }); register_mqtt_cmd(F("wwcircmode"), [&](const char * value, const int8_t id) { return set_wwcircmode(value, id); });
register_mqtt_cmd(F("roominfluence"), [&](const char * value, const int8_t id) { return set_roominfluence(value, id); }); register_mqtt_cmd(F("roominfluence"), [&](const char * value, const int8_t id) { return set_roominfluence(value, id); });
register_mqtt_cmd(F("flowtempoffset"), [&](const char * value, const int8_t id) { return set_flowtempoffset(value, id); }); register_mqtt_cmd(F("flowtempoffset"), [&](const char * value, const int8_t id) { return set_flowtempoffset(value, id); });
register_mqtt_cmd(F("minflowtemp"), [&](const char * value, const int8_t id) { return set_minflowtemp(value, id); });
register_mqtt_cmd(F("maxflowtemp"), [&](const char * value, const int8_t id) { return set_maxflowtemp(value, id); });
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("nofrosttemp"), [&](const char * value, const int8_t id) { return set_nofrosttemp(value, id); });
@@ -2291,5 +2465,4 @@ void Thermostat::add_commands() {
} }
} }
} // namespace emsesp } // namespace emsesp

View File

@@ -58,13 +58,15 @@ class Thermostat : public EMSdevice {
uint8_t heatingtype = EMS_VALUE_UINT_NOTSET; // type of heating: 1 radiator, 2 convectors, 3 floors, 4 room supply uint8_t heatingtype = EMS_VALUE_UINT_NOTSET; // type of heating: 1 radiator, 2 convectors, 3 floors, 4 room supply
uint8_t targetflowtemp = EMS_VALUE_UINT_NOTSET; uint8_t targetflowtemp = EMS_VALUE_UINT_NOTSET;
uint8_t summertemp = EMS_VALUE_UINT_NOTSET; uint8_t summertemp = EMS_VALUE_UINT_NOTSET;
uint8_t nofrosttemp = EMS_VALUE_UINT_NOTSET; int8_t nofrosttemp = EMS_VALUE_INT_NOTSET; // signed -20°C to +10°C
uint8_t designtemp = EMS_VALUE_UINT_NOTSET; // heating curve design temp at MinExtTemp uint8_t designtemp = EMS_VALUE_UINT_NOTSET; // heating curve design temp at MinExtTemp
int8_t offsettemp = EMS_VALUE_INT_NOTSET; // heating curve offest temp at roomtemp signed! int8_t offsettemp = EMS_VALUE_INT_NOTSET; // heating curve offest temp at roomtemp signed!
uint8_t manualtemp = EMS_VALUE_UINT_NOTSET; uint8_t manualtemp = EMS_VALUE_UINT_NOTSET;
uint8_t summer_setmode = EMS_VALUE_UINT_NOTSET; uint8_t summer_setmode = EMS_VALUE_UINT_NOTSET;
uint8_t roominfluence = EMS_VALUE_UINT_NOTSET; uint8_t roominfluence = EMS_VALUE_UINT_NOTSET;
uint8_t flowtempoffset = EMS_VALUE_UINT_NOTSET; uint8_t flowtempoffset = EMS_VALUE_UINT_NOTSET;
uint8_t minflowtemp = EMS_VALUE_UINT_NOTSET;
uint8_t maxflowtemp = EMS_VALUE_UINT_NOTSET;
uint8_t hc_num() const { uint8_t hc_num() const {
return hc_num_; return hc_num_;
@@ -86,7 +88,7 @@ class Thermostat : public EMSdevice {
uint8_t get_mode(uint8_t flags) const; uint8_t get_mode(uint8_t flags) const;
uint8_t get_mode_type(uint8_t flags) const; uint8_t get_mode_type(uint8_t flags) const;
enum Mode : uint8_t { UNKNOWN, OFF, MANUAL, AUTO, DAY, NIGHT, HEAT, NOFROST, ECO, HOLIDAY, COMFORT, OFFSET, DESIGN, SUMMER, FLOWOFFSET }; enum Mode : uint8_t { UNKNOWN, OFF, MANUAL, AUTO, DAY, NIGHT, HEAT, NOFROST, ECO, HOLIDAY, COMFORT, OFFSET, DESIGN, SUMMER, FLOWOFFSET, MINFLOW, MAXFLOW, ROOMINFLUENCE };
// for sorting based on hc number // for sorting based on hc number
friend inline bool operator<(const std::shared_ptr<HeatingCircuit> & lhs, const std::shared_ptr<HeatingCircuit> & rhs) { friend inline bool operator<(const std::shared_ptr<HeatingCircuit> & lhs, const std::shared_ptr<HeatingCircuit> & rhs) {
@@ -131,6 +133,7 @@ class Thermostat : public EMSdevice {
std::vector<uint16_t> set_typeids; std::vector<uint16_t> set_typeids;
std::vector<uint16_t> timer_typeids; std::vector<uint16_t> timer_typeids;
std::vector<uint16_t> summer_typeids; std::vector<uint16_t> summer_typeids;
std::vector<uint16_t> curve_typeids;
std::string datetime_; // date and time stamp std::string datetime_; // date and time stamp
std::string errorCode_; // code from 0xA2 as string i.e. "A22(816)" std::string errorCode_; // code from 0xA2 as string i.e. "A22(816)"
@@ -245,7 +248,7 @@ class Thermostat : public EMSdevice {
void register_mqtt_ha_config(); void register_mqtt_ha_config();
void register_mqtt_ha_config(uint8_t hc_num); void register_mqtt_ha_config(uint8_t hc_num);
void ha_config(bool force = false); bool ha_config(bool force = false);
bool thermostat_ha_cmd(const char * message, uint8_t hc_num); bool thermostat_ha_cmd(const char * message, uint8_t hc_num);
void process_RCOutdoorTemp(std::shared_ptr<const Telegram> telegram); void process_RCOutdoorTemp(std::shared_ptr<const Telegram> telegram);
@@ -273,6 +276,7 @@ class Thermostat : public EMSdevice {
void process_RC300OutdoorTemp(std::shared_ptr<const Telegram> telegram); void process_RC300OutdoorTemp(std::shared_ptr<const Telegram> telegram);
void process_RC300Settings(std::shared_ptr<const Telegram> telegram); void process_RC300Settings(std::shared_ptr<const Telegram> telegram);
void process_RC300Floordry(std::shared_ptr<const Telegram> telegram); void process_RC300Floordry(std::shared_ptr<const Telegram> telegram);
void process_RC300Curve(std::shared_ptr<const Telegram> telegram);
void process_JunkersMonitor(std::shared_ptr<const Telegram> telegram); void process_JunkersMonitor(std::shared_ptr<const Telegram> telegram);
void process_JunkersSet(std::shared_ptr<const Telegram> telegram); void process_JunkersSet(std::shared_ptr<const Telegram> telegram);
void process_JunkersSet2(std::shared_ptr<const Telegram> telegram); void process_JunkersSet2(std::shared_ptr<const Telegram> telegram);
@@ -308,11 +312,14 @@ class Thermostat : public EMSdevice {
bool set_remotetemp(const char * value, const int8_t id); bool set_remotetemp(const char * value, const int8_t id);
bool set_roominfluence(const char * value, const int8_t id); bool set_roominfluence(const char * value, const int8_t id);
bool set_flowtempoffset(const char * value, const int8_t id); bool set_flowtempoffset(const char * value, const int8_t id);
bool set_minflowtemp(const char * value, const int8_t id);
bool set_maxflowtemp(const char * value, const int8_t id);
// set functions - these don't use the id/hc, the parameters are ignored // set functions - these don't use the id/hc, the parameters are ignored
bool set_wwmode(const char * value, const int8_t id); bool set_wwmode(const char * value, const int8_t id);
bool set_wwtemp(const char * value, const int8_t id); bool set_wwtemp(const char * value, const int8_t id);
bool set_wwtemplow(const char * value, const int8_t id); bool set_wwtemplow(const char * value, const int8_t id);
bool set_wwonetime(const char * value, const int8_t id);
bool set_wwcircmode(const char * value, const int8_t id); bool set_wwcircmode(const char * value, const int8_t id);
bool set_datetime(const char * value, const int8_t id); bool set_datetime(const char * value, const int8_t id);
bool set_minexttemp(const char * value, const int8_t id); bool set_minexttemp(const char * value, const int8_t id);

View File

@@ -284,7 +284,6 @@ class EMSdevice {
uint16_t telegram_type_id_; // it's type_id uint16_t telegram_type_id_; // it's type_id
const __FlashStringHelper * telegram_type_name_; // e.g. RC20Message const __FlashStringHelper * telegram_type_name_; // e.g. RC20Message
bool fetch_; // if this type_id be queried automatically bool fetch_; // if this type_id be queried automatically
process_function_p process_function_; process_function_p process_function_;
TelegramFunction(uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, process_function_p process_function) TelegramFunction(uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, process_function_p process_function)

View File

@@ -63,6 +63,7 @@ bool EMSESP::read_next_ = false;
uint16_t EMSESP::publish_id_ = 0; uint16_t EMSESP::publish_id_ = 0;
bool EMSESP::tap_water_active_ = false; // for when Boiler states we having running warm water. used in Shower() bool EMSESP::tap_water_active_ = false; // for when Boiler states we having running warm water. used in Shower()
uint32_t EMSESP::last_fetch_ = 0; uint32_t EMSESP::last_fetch_ = 0;
uint8_t EMSESP::publish_all_idx_ = 0;
uint8_t EMSESP::unique_id_count_ = 0; uint8_t EMSESP::unique_id_count_ = 0;
// for a specific EMS device go and request data values // for a specific EMS device go and request data values
@@ -215,9 +216,8 @@ void EMSESP::show_ems(uuid::console::Shell & shell) {
shell.printfln(F(" #tx fails (after %d retries): %d"), TxService::MAXIMUM_TX_RETRIES, txservice_.telegram_fail_count()); shell.printfln(F(" #tx fails (after %d retries): %d"), TxService::MAXIMUM_TX_RETRIES, txservice_.telegram_fail_count());
shell.printfln(F(" Rx line quality: %d%%"), rxservice_.quality()); shell.printfln(F(" Rx line quality: %d%%"), rxservice_.quality());
shell.printfln(F(" Tx line quality: %d%%"), txservice_.quality()); shell.printfln(F(" Tx line quality: %d%%"), txservice_.quality());
}
shell.println(); shell.println();
}
// Rx queue // Rx queue
auto rx_telegrams = rxservice_.queue(); auto rx_telegrams = rxservice_.queue();
@@ -290,29 +290,74 @@ void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
// MQTT publish everything, immediately // MQTT publish everything, immediately
void EMSESP::publish_all(bool force) { void EMSESP::publish_all(bool force) {
if (force) {
publish_all_idx_ = 1;
return;
}
if (Mqtt::connected()) { if (Mqtt::connected()) {
publish_device_values(EMSdevice::DeviceType::BOILER, force); publish_device_values(EMSdevice::DeviceType::BOILER, false);
publish_device_values(EMSdevice::DeviceType::THERMOSTAT, force); publish_device_values(EMSdevice::DeviceType::THERMOSTAT, false);
publish_device_values(EMSdevice::DeviceType::SOLAR, force); publish_device_values(EMSdevice::DeviceType::SOLAR, false);
publish_device_values(EMSdevice::DeviceType::MIXER, force); publish_device_values(EMSdevice::DeviceType::MIXER, false);
publish_other_values(); publish_other_values();
publish_sensor_values(true); publish_sensor_values(true, false);
system_.send_heartbeat(); system_.send_heartbeat();
} }
} }
// on command "publish HA" loop and wait between devices for publishing all sensors
void EMSESP::publish_all_loop() {
static uint32_t last = 0;
if (!Mqtt::connected() || !publish_all_idx_) {
return;
}
// every HA-sensor takes 20 ms, wait ~2 sec to finish (boiler have ~70 sensors)
if ((uuid::get_uptime() - last < 2000)) {
return;
}
last = uuid::get_uptime();
switch (publish_all_idx_++) {
case 1:
publish_device_values(EMSdevice::DeviceType::BOILER, true);
break;
case 2:
publish_device_values(EMSdevice::DeviceType::THERMOSTAT, true);
break;
case 3:
publish_device_values(EMSdevice::DeviceType::SOLAR, true);
break;
case 4:
publish_device_values(EMSdevice::DeviceType::MIXER, true);
break;
case 5:
publish_other_values();
break;
case 6:
publish_sensor_values(true, true);
break;
case 7:
system_.send_heartbeat();
break;
default:
// all finished
publish_all_idx_ = 0;
last = 0;
}
}
// create json doc for the devices values and add to MQTT publish queue // create json doc for the devices values and add to MQTT publish queue
// special case for Mixer units, since we want to bundle all devices together into one payload // special case for Mixer units, since we want to bundle all devices together into one payload
void EMSESP::publish_device_values(uint8_t device_type, bool force) { void EMSESP::publish_device_values(uint8_t device_type, bool force) {
if (device_type == EMSdevice::DeviceType::MIXER && Mqtt::mqtt_format() != Mqtt::Format::SINGLE) { if (device_type == EMSdevice::DeviceType::MIXER && Mqtt::mqtt_format() != Mqtt::Format::SINGLE) {
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_LARGE); // DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_LARGE);
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc;
JsonObject json = doc.to<JsonObject>(); JsonObject json = doc.to<JsonObject>();
for (const auto & emsdevice : emsdevices) { for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_type)) { if (emsdevice && (emsdevice->device_type() == device_type)) {
emsdevice->publish_values(json, force); emsdevice->publish_values(json, force);
} }
} }
doc.shrinkToFit(); // doc.shrinkToFit();
Mqtt::publish("mixer_data", doc.as<JsonObject>()); Mqtt::publish("mixer_data", doc.as<JsonObject>());
return; return;
} }
@@ -335,9 +380,9 @@ void EMSESP::publish_other_values() {
} }
} }
void EMSESP::publish_sensor_values(const bool force) { void EMSESP::publish_sensor_values(const bool time, const bool force) {
if (dallassensor_.updated_values() || force) { if (dallassensor_.updated_values() || time || force) {
dallassensor_.publish_values(); dallassensor_.publish_values(force);
} }
} }
@@ -649,7 +694,7 @@ void EMSESP::show_devices(uuid::console::Shell & shell) {
// shell.printf(F("[factory ID: %d] "), device_class.first); // shell.printf(F("[factory ID: %d] "), device_class.first);
for (const auto & emsdevice : emsdevices) { for (const auto & emsdevice : emsdevices) {
if ((emsdevice) && (emsdevice->device_type() == device_class.first)) { if ((emsdevice) && (emsdevice->device_type() == device_class.first)) {
shell.printf(F("%s: %s"), emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str()); shell.printf(F("(%d) %s: %s"), emsdevice->unique_id(), emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str());
if ((emsdevice->device_type() == EMSdevice::DeviceType::THERMOSTAT) && (emsdevice->device_id() == actual_master_thermostat())) { if ((emsdevice->device_type() == EMSdevice::DeviceType::THERMOSTAT) && (emsdevice->device_id() == actual_master_thermostat())) {
shell.printf(F(" ** master device **")); shell.printf(F(" ** master device **"));
} }
@@ -722,17 +767,20 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
return false; // not found return false; // not found
} }
std::string name = uuid::read_flash_string(device_p->name); auto name = uuid::read_flash_string(device_p->name);
emsdevices.push_back(EMSFactory::add(device_p->device_type, device_id, device_p->product_id, version, name, device_p->flags, brand)); auto device_type = device_p->device_type;
emsdevices.back()->unique_id(++unique_id_count_); auto flags = device_p->flags;
LOG_DEBUG(F("Adding new device %s (device ID 0x%02X, product ID %d, version %s)"), name.c_str(), device_id, product_id, version.c_str()); LOG_DEBUG(F("Adding new device %s (device ID 0x%02X, product ID %d, version %s)"), name.c_str(), device_id, product_id, version.c_str());
emsdevices.push_back(EMSFactory::add(device_type, device_id, product_id, version, name, flags, brand));
emsdevices.back()->unique_id(++unique_id_count_);
fetch_device_values(device_id); // go and fetch its data fetch_device_values(device_id); // go and fetch its data
// add info command, but not for all devices // add info command, but not for all devices
uint8_t device_type = device_p->device_type;
if ((device_type == DeviceType::CONNECT) || (device_type == DeviceType::CONTROLLER) || (device_type == DeviceType::GATEWAY)) { if ((device_type == DeviceType::CONNECT) || (device_type == DeviceType::CONTROLLER) || (device_type == DeviceType::GATEWAY)) {
return true; return true;
} }
Command::add_with_json(device_type, F_(info), [device_type](const char * value, const int8_t id, JsonObject & json) { Command::add_with_json(device_type, F_(info), [device_type](const char * value, const int8_t id, JsonObject & json) {
return command_info(device_type, json); return command_info(device_type, json);
}); });
@@ -934,11 +982,12 @@ void EMSESP::loop() {
} }
system_.loop(); // does LED and checks system health, and syslog service system_.loop(); // does LED and checks system health, and syslog service
rxservice_.loop(); // process any incoming Rx telegrams
shower_.loop(); // check for shower on/off shower_.loop(); // check for shower on/off
dallassensor_.loop(); // this will also send out via MQTT dallassensor_.loop(); // this will also send out via MQTT
publish_all_loop();
mqtt_.loop(); // sends out anything in the queue via MQTT mqtt_.loop(); // sends out anything in the queue via MQTT
console_.loop(); // telnet/serial console console_.loop(); // telnet/serial console
rxservice_.loop(); // process any incoming Rx telegrams
// 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)) {

View File

@@ -63,7 +63,7 @@ class EMSESP {
static void publish_device_values(uint8_t device_type, bool force = false); static void publish_device_values(uint8_t device_type, bool force = false);
static void publish_other_values(); static void publish_other_values();
static void publish_sensor_values(const bool force = false); static void publish_sensor_values(const bool time, const bool force = false);
static void publish_all(bool force = false); static void publish_all(bool force = false);
#ifdef EMSESP_STANDALONE #ifdef EMSESP_STANDALONE
@@ -184,6 +184,7 @@ class EMSESP {
static void process_UBADevices(std::shared_ptr<const Telegram> telegram); static void process_UBADevices(std::shared_ptr<const Telegram> telegram);
static void process_version(std::shared_ptr<const Telegram> telegram); static void process_version(std::shared_ptr<const Telegram> telegram);
static void publish_response(std::shared_ptr<const Telegram> telegram); static void publish_response(std::shared_ptr<const Telegram> telegram);
static void publish_all_loop();
static bool command_info(uint8_t device_type, JsonObject & json); static bool command_info(uint8_t device_type, JsonObject & json);
@@ -206,6 +207,7 @@ class EMSESP {
static bool read_next_; static bool read_next_;
static uint16_t publish_id_; static uint16_t publish_id_;
static bool tap_water_active_; static bool tap_water_active_;
static uint8_t publish_all_idx_;
static uint8_t unique_id_count_; static uint8_t unique_id_count_;
}; };

View File

@@ -66,10 +66,7 @@ MAKE_PSTR_WORD(publish)
MAKE_PSTR_WORD(bar) MAKE_PSTR_WORD(bar)
MAKE_PSTR_WORD(min) MAKE_PSTR_WORD(min)
MAKE_PSTR_WORD(uA) MAKE_PSTR_WORD(uA)
MAKE_PSTR_WORD(timeout)
#if defined(EMSESP_DEBUG)
MAKE_PSTR_WORD(test)
#endif
// for commands // for commands
MAKE_PSTR_WORD(call) MAKE_PSTR_WORD(call)
@@ -81,6 +78,7 @@ MAKE_PSTR_WORD(command)
MAKE_PSTR_WORD(commands) MAKE_PSTR_WORD(commands)
MAKE_PSTR_WORD(info) MAKE_PSTR_WORD(info)
MAKE_PSTR_WORD(report) MAKE_PSTR_WORD(report)
MAKE_PSTR_WORD(test)
// devices // devices
MAKE_PSTR_WORD(boiler) MAKE_PSTR_WORD(boiler)
@@ -116,7 +114,7 @@ MAKE_PSTR(bus_id_fmt, "Bus ID = %02X")
MAKE_PSTR(watchid_optional, "[ID]") MAKE_PSTR(watchid_optional, "[ID]")
MAKE_PSTR(watch_format_optional, "[off | on | raw]") MAKE_PSTR(watch_format_optional, "[off | on | raw]")
MAKE_PSTR(invalid_watch, "Invalid watch type") MAKE_PSTR(invalid_watch, "Invalid watch type")
MAKE_PSTR(data_mandatory, "<\"XX XX ...\">") MAKE_PSTR(data_mandatory, "\"XX XX ...\"")
MAKE_PSTR(percent, "%") MAKE_PSTR(percent, "%")
MAKE_PSTR(degrees, "°C") MAKE_PSTR(degrees, "°C")
MAKE_PSTR(asterisks, "********") MAKE_PSTR(asterisks, "********")
@@ -246,6 +244,8 @@ MAKE_PSTR(floordrytemp, "Floordrying temperature")
MAKE_PSTR(wwmode, "Warm water mode") MAKE_PSTR(wwmode, "Warm water mode")
MAKE_PSTR(wwtemp, "Warm water high temperature") MAKE_PSTR(wwtemp, "Warm water high temperature")
MAKE_PSTR(wwtemplow, "Warm water low temperature") MAKE_PSTR(wwtemplow, "Warm water low temperature")
MAKE_PSTR(wwextra1, "Warm water circuit 1 extra")
MAKE_PSTR(wwextra2, "Warm water circuit 2 extra")
MAKE_PSTR(wwcircmode, "Warm water circulation mode") MAKE_PSTR(wwcircmode, "Warm water circulation mode")
// thermostat - per heating circuit // thermostat - per heating circuit
@@ -267,6 +267,8 @@ MAKE_PSTR(summertemp, "Summer temperature")
MAKE_PSTR(summermode, "Summer mode") MAKE_PSTR(summermode, "Summer mode")
MAKE_PSTR(roominfluence, "Room influence") MAKE_PSTR(roominfluence, "Room influence")
MAKE_PSTR(flowtempoffset, "Flow temperature offset") MAKE_PSTR(flowtempoffset, "Flow temperature offset")
MAKE_PSTR(minflowtemp, "Min. flow temperature")
MAKE_PSTR(maxflowtemp, "Max. flow temperature")
MAKE_PSTR(mode, "Mode") MAKE_PSTR(mode, "Mode")
MAKE_PSTR(modetype, "Mode type") MAKE_PSTR(modetype, "Mode type")

View File

@@ -40,7 +40,7 @@ bool Mqtt::mqtt_enabled_;
std::vector<Mqtt::MQTTSubFunction> Mqtt::mqtt_subfunctions_; std::vector<Mqtt::MQTTSubFunction> Mqtt::mqtt_subfunctions_;
uint16_t Mqtt::mqtt_publish_fails_ = 0; uint16_t Mqtt::mqtt_publish_fails_ = 0;
size_t Mqtt::maximum_mqtt_messages_ = Mqtt::MAX_MQTT_MESSAGES; // size_t Mqtt::maximum_mqtt_messages_ = Mqtt::MAX_MQTT_MESSAGES;
uint16_t Mqtt::mqtt_message_id_ = 0; uint16_t Mqtt::mqtt_message_id_ = 0;
std::list<Mqtt::QueuedMqttMessage> Mqtt::mqtt_messages_; std::list<Mqtt::QueuedMqttMessage> Mqtt::mqtt_messages_;
char will_topic_[Mqtt::MQTT_TOPIC_MAX_SIZE]; // because MQTT library keeps only char pointer char will_topic_[Mqtt::MQTT_TOPIC_MAX_SIZE]; // because MQTT library keeps only char pointer
@@ -181,7 +181,7 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) {
return; return;
} }
shell.printfln(F("MQTT queue (%d messages):"), mqtt_messages_.size()); shell.printfln(F("MQTT queue (%d/%d messages):"), mqtt_messages_.size(), MAX_MQTT_MESSAGES);
for (const auto & message : mqtt_messages_) { for (const auto & message : mqtt_messages_) {
auto content = message.content_; auto content = message.content_;
@@ -211,14 +211,12 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) {
} }
} }
shell.println(); shell.println();
} // namespace emsesp }
#if defined(EMSESP_DEBUG)
// simulate receiving a MQTT message, used only for testing // simulate receiving a MQTT message, used only for testing
void Mqtt::incoming(const char * topic, const char * payload) { void Mqtt::incoming(const char * topic, const char * payload) {
on_message(topic, payload, strlen(payload)); on_message(topic, payload, strlen(payload));
} }
#endif
// received an MQTT message that we subscribed too // received an MQTT message that we subscribed too
void Mqtt::on_message(const char * topic, const char * payload, size_t len) { void Mqtt::on_message(const char * topic, const char * payload, size_t len) {
@@ -358,6 +356,11 @@ void Mqtt::start() {
mqtt_enabled_ = mqttSettings.enabled; mqtt_enabled_ = mqttSettings.enabled;
}); });
// if MQTT disabled, quit
if (!mqtt_enabled_) {
return;
}
mqttClient_->onConnect([this](bool sessionPresent) { on_connect(); }); mqttClient_->onConnect([this](bool sessionPresent) { on_connect(); });
mqttClient_->onDisconnect([this](AsyncMqttClientDisconnectReason reason) { mqttClient_->onDisconnect([this](AsyncMqttClientDisconnectReason reason) {
@@ -529,7 +532,7 @@ std::shared_ptr<const MqttMessage> Mqtt::queue_message(const uint8_t operation,
} }
// if the queue is full, make room but removing the last one // if the queue is full, make room but removing the last one
if (mqtt_messages_.size() >= maximum_mqtt_messages_) { if (mqtt_messages_.size() >= MAX_MQTT_MESSAGES) {
mqtt_messages_.pop_front(); mqtt_messages_.pop_front();
} }
mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message)); mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message));
@@ -611,26 +614,6 @@ void Mqtt::process_queue() {
return; return;
} }
// show queue - Debug only
/*
Serial.printf("MQTT queue:\n\r");
for (const auto & message : mqtt_messages_) {
auto content = message.content_;
if (content->operation == Operation::PUBLISH) {
// Publish messages
Serial.printf(" [%02d] (Pub) topic=%s payload=%s (pid %d, retry #%d)\n\r",
message.id_,
content->topic.c_str(),
content->payload.c_str(),
message.packet_id_,
message.retry_count_);
} else {
// Subscribe messages
Serial.printf(" [%02d] (Sub) topic=%s\n\r", message.id_, content->topic.c_str());
}
}
*/
// fetch first from queue and create the full topic name // fetch first from queue and create the full topic name
auto mqtt_message = mqtt_messages_.front(); auto mqtt_message = mqtt_messages_.front();
auto message = mqtt_message.content_; auto message = mqtt_message.content_;
@@ -697,9 +680,8 @@ void Mqtt::register_mqtt_ha_binary_sensor(const __FlashStringHelper * name, cons
return; return;
} }
return; // TODO DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_SMALL);
// StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
doc["name"] = name; doc["name"] = name;
doc["uniq_id"] = entity; doc["uniq_id"] = entity;
@@ -727,11 +709,13 @@ void Mqtt::register_mqtt_ha_binary_sensor(const __FlashStringHelper * name, cons
snprintf_P(ha_device, sizeof(ha_device), PSTR("ems-esp-%s"), EMSdevice::device_type_2_device_name(device_type).c_str()); snprintf_P(ha_device, sizeof(ha_device), PSTR("ems-esp-%s"), EMSdevice::device_type_2_device_name(device_type).c_str());
ids.add(ha_device); ids.add(ha_device);
doc.shrinkToFit();
char topic[MQTT_TOPIC_MAX_SIZE]; char topic[MQTT_TOPIC_MAX_SIZE];
snprintf_P(topic, sizeof(topic), PSTR("homeassistant/binary_sensor/ems-esp/%s/config"), entity); snprintf_P(topic, sizeof(topic), PSTR("homeassistant/binary_sensor/ems-esp/%s/config"), entity);
// convert json to string and publish immediately with retain forced to true // convert json to string and publish immediately with retain forced to true
char payload_text[300]; char payload_text[256];
serializeJson(doc, payload_text); // convert json to string serializeJson(doc, payload_text); // convert json to string
uint16_t packet_id = mqttClient_->publish(topic, 0, true, payload_text); uint16_t packet_id = mqttClient_->publish(topic, 0, true, payload_text);
#if defined(EMSESP_STANDALONE) #if defined(EMSESP_STANDALONE)
@@ -744,7 +728,8 @@ void Mqtt::register_mqtt_ha_binary_sensor(const __FlashStringHelper * name, cons
} }
#endif #endif
delay(MQTT_PUBLISH_WAIT); // delay(MQTT_PUBLISH_WAIT);
delay(50);
} }
// HA config for a normal 'sensor' type // HA config for a normal 'sensor' type
@@ -765,14 +750,15 @@ void Mqtt::register_mqtt_ha_sensor(const char * prefix,
if (prefix != nullptr) { if (prefix != nullptr) {
snprintf_P(new_entity, sizeof(new_entity), PSTR("%s.%s"), prefix, entity); snprintf_P(new_entity, sizeof(new_entity), PSTR("%s.%s"), prefix, entity);
} else { } else {
strcpy(new_entity, entity); strncpy(new_entity, entity, sizeof(new_entity));
} }
std::string device_name = EMSdevice::device_type_2_device_name(device_type); char device_name[50];
strncpy(device_name, EMSdevice::device_type_2_device_name(device_type).c_str(), sizeof(device_name));
// build unique identifier, replacing all . with _ as not to break HA // build unique identifier, replacing all . with _ as not to break HA
std::string uniq(50, '\0'); std::string uniq(50, '\0');
snprintf_P(&uniq[0], uniq.capacity() + 1, PSTR("%s_%s"), device_name.c_str(), new_entity); snprintf_P(&uniq[0], uniq.capacity() + 1, PSTR("%s_%s"), device_name, new_entity);
std::replace(uniq.begin(), uniq.end(), '.', '_'); std::replace(uniq.begin(), uniq.end(), '.', '_');
// topic // topic
@@ -782,9 +768,9 @@ void Mqtt::register_mqtt_ha_sensor(const char * prefix,
// state topic // state topic
char stat_t[MQTT_TOPIC_MAX_SIZE]; char stat_t[MQTT_TOPIC_MAX_SIZE];
if (suffix != nullptr) { if (suffix != nullptr) {
snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s_data%s"), hostname_.c_str(), device_name.c_str(), uuid::read_flash_string(suffix).c_str()); snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s_data%s"), hostname_.c_str(), device_name, uuid::read_flash_string(suffix).c_str());
} else { } else {
snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s_data"), hostname_.c_str(), device_name.c_str()); snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s_data"), hostname_.c_str(), device_name);
} }
// value template // value template
@@ -793,20 +779,22 @@ void Mqtt::register_mqtt_ha_sensor(const char * prefix,
// ha device // ha device
char ha_device[40]; char ha_device[40];
snprintf_P(ha_device, sizeof(ha_device), PSTR("ems-esp-%s"), device_name.c_str()); snprintf_P(ha_device, sizeof(ha_device), PSTR("ems-esp-%s"), device_name);
// name // name
char new_name[50]; char new_name[50];
if (prefix != nullptr) { if (prefix != nullptr) {
snprintf_P(new_name, sizeof(new_name), PSTR("%s %s %s"), device_name.c_str(), prefix, uuid::read_flash_string(name).c_str()); snprintf_P(new_name, sizeof(new_name), PSTR("%s %s %s"), device_name, prefix, uuid::read_flash_string(name).c_str());
} else { } else {
snprintf_P(new_name, sizeof(new_name), PSTR("%s %s"), device_name.c_str(), uuid::read_flash_string(name).c_str()); snprintf_P(new_name, sizeof(new_name), PSTR("%s %s"), device_name, uuid::read_flash_string(name).c_str());
} }
new_name[0] = toupper(new_name[0]); // capitalize first letter new_name[0] = toupper(new_name[0]); // capitalize first letter
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc; DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_SMALL);
// StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
doc["name"] = new_name; doc["name"] = new_name;
doc["uniq_id"] = uniq; doc["uniq_id"] = uniq.c_str();
if (uom != nullptr) { if (uom != nullptr) {
doc["unit_of_meas"] = uom; doc["unit_of_meas"] = uom;
} }
@@ -819,9 +807,9 @@ void Mqtt::register_mqtt_ha_sensor(const char * prefix,
JsonArray ids = dev.createNestedArray("ids"); JsonArray ids = dev.createNestedArray("ids");
ids.add(ha_device); ids.add(ha_device);
doc.shrinkToFit();
// convert json to string and publish immediately with retain forced to true // convert json to string and publish immediately with retain forced to true
// std::string payload_text; char payload_text[256];
char payload_text[300];
serializeJson(doc, payload_text); // convert json to string serializeJson(doc, payload_text); // convert json to string
uint16_t packet_id = mqttClient_->publish(topic, 0, true, payload_text); uint16_t packet_id = mqttClient_->publish(topic, 0, true, payload_text);
@@ -836,6 +824,6 @@ void Mqtt::register_mqtt_ha_sensor(const char * prefix,
} }
// delay(MQTT_PUBLISH_WAIT); // don't flood asynctcp // delay(MQTT_PUBLISH_WAIT); // don't flood asynctcp
delay(50); // enough time to send the short message out
} }
} // namespace emsesp } // namespace emsesp

View File

@@ -38,9 +38,10 @@
using uuid::console::Shell; using uuid::console::Shell;
#define EMSESP_MAX_JSON_SIZE_SMALL 256 // for smaller json docs when using StaticJsonDocument #define EMSESP_MAX_JSON_SIZE_SMALL 384 // for smaller json docs when using StaticJsonDocument
#define EMSESP_MAX_JSON_SIZE_MEDIUM 768 // for smaller json docs from ems devices, when using StaticJsonDocument #define EMSESP_MAX_JSON_SIZE_MEDIUM 768 // for medium json docs from ems devices, when using StaticJsonDocument
#define EMSESP_MAX_JSON_SIZE_LARGE 2048 // for large json docs from ems devices, like boiler or thermostat data. Using DynamicJsonDocument #define EMSESP_MAX_JSON_SIZE_LARGE 1024 // for large json docs from ems devices, like boiler or thermostat data. Using StaticJsonDocument
#define EMSESP_MAX_JSON_SIZE_DYN 2048 // for large json docs from web. Using DynamicJsonDocument
namespace emsesp { namespace emsesp {
@@ -118,9 +119,7 @@ class Mqtt {
mqttClient_->disconnect(); mqttClient_->disconnect();
} }
#if defined(EMSESP_DEBUG)
void incoming(const char * topic, const char * payload); // for testing only void incoming(const char * topic, const char * payload); // for testing only
#endif
static bool connected() { static bool connected() {
#if defined(EMSESP_STANDALONE) #if defined(EMSESP_STANDALONE)
@@ -171,11 +170,14 @@ class Mqtt {
static std::list<QueuedMqttMessage> mqtt_messages_; static std::list<QueuedMqttMessage> mqtt_messages_;
static AsyncMqttClient * mqttClient_; static AsyncMqttClient * mqttClient_;
static size_t maximum_mqtt_messages_;
static uint16_t mqtt_message_id_; static uint16_t mqtt_message_id_;
#if defined(EMSESP_STANDALONE)
static constexpr size_t MAX_MQTT_MESSAGES = 70; // size of queue static constexpr size_t MAX_MQTT_MESSAGES = 70; // size of queue
#else
static constexpr size_t MAX_MQTT_MESSAGES = 20; // size of queue
#endif
static constexpr uint32_t MQTT_PUBLISH_WAIT = 200; // delay between sending publishes, to account for large payloads static constexpr uint32_t MQTT_PUBLISH_WAIT = 200; // delay between sending publishes, to account for large payloads
static constexpr uint8_t MQTT_PUBLISH_MAX_RETRY = 3; // max retries for giving up on publishing static constexpr uint8_t MQTT_PUBLISH_MAX_RETRY = 3; // max retries for giving up on publishing

View File

@@ -28,8 +28,10 @@ void Shower::start() {
shower_alert_ = settings.shower_alert; shower_alert_ = settings.shower_alert;
}); });
if (Mqtt::enabled()) {
send_mqtt_stat(false); // send first MQTT publish send_mqtt_stat(false); // send first MQTT publish
} }
}
void Shower::loop() { void Shower::loop() {
if (!shower_timer_) { if (!shower_timer_) {

View File

@@ -21,6 +21,10 @@
#include "version.h" // firmware version of EMS-ESP #include "version.h" // firmware version of EMS-ESP
#if defined(EMSESP_TEST)
#include "test/test.h"
#endif
namespace emsesp { namespace emsesp {
uuid::log::Logger System::logger_{F_(system), uuid::log::Facility::KERN}; uuid::log::Logger System::logger_{F_(system), uuid::log::Facility::KERN};
@@ -37,11 +41,22 @@ bool System::hide_led_ = false;
uint8_t System::led_gpio_ = 0; uint8_t System::led_gpio_ = 0;
uint16_t System::analog_ = 0; uint16_t System::analog_ = 0;
bool System::analog_enabled_ = false; bool System::analog_enabled_ = false;
bool System::syslog_enabled_ = false;
std::string System::hostname_; std::string System::hostname_;
int8_t System::syslog_level_ = -1;
uint32_t System::syslog_mark_interval_ = 0;
String System::syslog_host_;
// send on/off to a gpio pin // send on/off to a gpio pin
// value: true = HIGH, false = LOW // value: true = HIGH, false = LOW
// 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) {
return false;
}
bool v = false; bool v = false;
if (Helpers::value2bool(value, v)) { if (Helpers::value2bool(value, v)) {
pinMode(id, OUTPUT); pinMode(id, OUTPUT);
@@ -53,12 +68,34 @@ bool System::command_pin(const char * value, const int8_t id) {
return false; return false;
} }
// send raw // send raw to ems
bool System::command_send(const char * value, const int8_t id) { bool System::command_send(const char * value, const int8_t id) {
EMSESP::send_raw_telegram(value); // ignore id EMSESP::send_raw_telegram(value); // ignore id
return true; return true;
} }
// fetch device values
bool System::command_fetch(const char * value, const int8_t id) {
LOG_INFO(F("Requesting data from EMS devices"));
EMSESP::fetch_device_values();
return true;
}
// mqtt publish
bool System::command_publish(const char * value, const int8_t id) {
std::string ha(10, '\0');
if (Helpers::value2string(value, ha)) {
if (ha == "ha") {
EMSESP::publish_all(true); // includes HA
LOG_INFO(F("Publishing all data to MQTT, including HA configs"));
return true;
}
}
EMSESP::publish_all(); // ignore value and id
LOG_INFO(F("Publishing all data to MQTT"));
return true;
}
// restart EMS-ESP // restart EMS-ESP
void System::restart() { void System::restart() {
LOG_NOTICE(F("Restarting system...")); LOG_NOTICE(F("Restarting system..."));
@@ -112,12 +149,20 @@ uint8_t System::free_mem() {
void System::syslog_init() { void System::syslog_init() {
// fetch settings // fetch settings
EMSESP::webSettingsService.read([&](WebSettings & settings) { EMSESP::webSettingsService.read([&](WebSettings & settings) {
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;
syslog_host_ = settings.syslog_host; syslog_host_ = settings.syslog_host;
}); });
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
if (!syslog_enabled_) {
syslog_.log_level((uuid::log::Level)-1);
syslog_.mark_interval(0);
syslog_.destination((IPAddress)((uint32_t)0));
return;
}
syslog_.start(); // syslog service re-start syslog_.start(); // syslog service re-start
// configure syslog // configure syslog
@@ -151,11 +196,16 @@ void System::start() {
EMSESP::webSettingsService.read([&](WebSettings & settings) { EMSESP::webSettingsService.read([&](WebSettings & settings) {
Command::add(EMSdevice::DeviceType::SYSTEM, settings.ems_bus_id, F_(pin), System::command_pin); Command::add(EMSdevice::DeviceType::SYSTEM, settings.ems_bus_id, F_(pin), System::command_pin);
Command::add(EMSdevice::DeviceType::SYSTEM, settings.ems_bus_id, F_(send), System::command_send); Command::add(EMSdevice::DeviceType::SYSTEM, settings.ems_bus_id, F_(send), System::command_send);
Command::add(EMSdevice::DeviceType::SYSTEM, settings.ems_bus_id, F_(publish), System::command_publish);
Command::add(EMSdevice::DeviceType::SYSTEM, settings.ems_bus_id, F_(fetch), System::command_fetch);
Command::add_with_json(EMSdevice::DeviceType::SYSTEM, F_(info), System::command_info); Command::add_with_json(EMSdevice::DeviceType::SYSTEM, F_(info), System::command_info);
Command::add_with_json(EMSdevice::DeviceType::SYSTEM, F_(report), System::command_report); Command::add_with_json(EMSdevice::DeviceType::SYSTEM, F_(report), System::command_report);
#if defined(EMSESP_TEST)
Command::add(EMSdevice::DeviceType::SYSTEM, settings.ems_bus_id, F_(test), System::command_test);
#endif
}); });
syslog_init(); // init SysLog
init(); init();
} }
@@ -169,6 +219,7 @@ void System::init() {
Helpers::bool_format(settings.bool_format); Helpers::bool_format(settings.bool_format);
analog_enabled_ = settings.analog_enabled; analog_enabled_ = settings.analog_enabled;
}); });
syslog_init(); // init SysLog
EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & settings) { hostname(settings.hostname.c_str()); }); EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & settings) { hostname(settings.hostname.c_str()); });
@@ -207,8 +258,11 @@ void System::upload_status(bool in_progress) {
// checks system health and handles LED flashing wizardry // checks system health and handles LED flashing wizardry
void System::loop() { void System::loop() {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
if (syslog_enabled_) {
syslog_.loop(); syslog_.loop();
#endif }
led_monitor(); // check status and report back using the LED led_monitor(); // check status and report back using the LED
system_check(); // check system health system_check(); // check system health
if (analog_enabled_) { if (analog_enabled_) {
@@ -230,6 +284,8 @@ void System::loop() {
show_mem("core"); show_mem("core");
} }
#endif #endif
#endif
#endif #endif
} }
@@ -481,6 +537,10 @@ void System::show_system(uuid::console::Shell & shell) {
EMSESP::webSettingsService.read([&](WebSettings & settings) { EMSESP::webSettingsService.read([&](WebSettings & settings) {
shell.println(); shell.println();
if (!settings.syslog_enabled) {
shell.printfln(F("Syslog: disabled"));
} else {
shell.printfln(F("Syslog:")); shell.printfln(F("Syslog:"));
shell.print(F_(1space)); shell.print(F_(1space));
shell.printfln(F_(host_fmt), !settings.syslog_host.isEmpty() ? settings.syslog_host.c_str() : uuid::read_flash_string(F_(unset)).c_str()); shell.printfln(F_(host_fmt), !settings.syslog_host.isEmpty() ? settings.syslog_host.c_str() : uuid::read_flash_string(F_(unset)).c_str());
@@ -488,6 +548,7 @@ void System::show_system(uuid::console::Shell & shell) {
shell.printfln(F_(log_level_fmt), uuid::log::format_level_lowercase(static_cast<uuid::log::Level>(settings.syslog_level))); shell.printfln(F_(log_level_fmt), uuid::log::format_level_lowercase(static_cast<uuid::log::Level>(settings.syslog_level)));
shell.print(F_(1space)); shell.print(F_(1space));
shell.printfln(F_(mark_interval_fmt), settings.syslog_mark_interval); shell.printfln(F_(mark_interval_fmt), settings.syslog_mark_interval);
}
}); });
#endif #endif
@@ -652,7 +713,7 @@ bool System::check_upgrade() {
l_cfg.setAutoFormat(false); l_cfg.setAutoFormat(false);
LittleFS.setConfig(l_cfg); // do not auto format if it can't find LittleFS LittleFS.setConfig(l_cfg); // do not auto format if it can't find LittleFS
if (LittleFS.begin()) { if (LittleFS.begin()) {
#if defined(EMSESP_DEBUG) #if defined(EMSESP_FORCE_SERIAL)
Serial.begin(115200); Serial.begin(115200);
Serial.println(F("FS is Littlefs")); Serial.println(F("FS is Littlefs"));
Serial.end(); Serial.end();
@@ -667,7 +728,7 @@ bool System::check_upgrade() {
cfg.setAutoFormat(false); // prevent formatting when opening SPIFFS filesystem cfg.setAutoFormat(false); // prevent formatting when opening SPIFFS filesystem
SPIFFS.setConfig(cfg); SPIFFS.setConfig(cfg);
if (!SPIFFS.begin()) { if (!SPIFFS.begin()) {
#if defined(EMSESP_DEBUG) #if defined(EMSESP_FORCE_SERIAL)
Serial.begin(115200); Serial.begin(115200);
Serial.println(F("No old SPIFFS found!")); Serial.println(F("No old SPIFFS found!"));
Serial.end(); Serial.end();
@@ -766,9 +827,7 @@ bool System::check_upgrade() {
file.close(); file.close();
if (failed) { if (failed) {
#if defined(EMSESP_DEBUG)
Serial.println(F("Failed to read system config. Quitting.")); Serial.println(F("Failed to read system config. Quitting."));
#endif
SPIFFS.end(); SPIFFS.end();
Serial.end(); Serial.end();
return false; return false;
@@ -791,10 +850,6 @@ bool System::check_upgrade() {
Serial.printf(PSTR("Error. Failed to deserialize custom json, error %s\n"), error.c_str()); Serial.printf(PSTR("Error. Failed to deserialize custom json, error %s\n"), error.c_str());
failed = true; failed = true;
} else { } else {
#if defined(EMSESP_DEBUG)
serializeJson(doc, Serial);
Serial.println();
#endif
custom_settings = doc["settings"]; custom_settings = doc["settings"];
EMSESP::webSettingsService.update( EMSESP::webSettingsService.update(
[&](WebSettings & settings) { [&](WebSettings & settings) {
@@ -803,6 +858,7 @@ bool System::check_upgrade() {
settings.shower_timer = custom_settings["shower_timer"] | EMSESP_DEFAULT_SHOWER_TIMER; settings.shower_timer = custom_settings["shower_timer"] | EMSESP_DEFAULT_SHOWER_TIMER;
settings.master_thermostat = custom_settings["master_thermostat"] | EMSESP_DEFAULT_MASTER_THERMOSTAT; settings.master_thermostat = custom_settings["master_thermostat"] | EMSESP_DEFAULT_MASTER_THERMOSTAT;
settings.ems_bus_id = custom_settings["bus_id"] | EMSESP_DEFAULT_EMS_BUS_ID; settings.ems_bus_id = custom_settings["bus_id"] | EMSESP_DEFAULT_EMS_BUS_ID;
settings.syslog_enabled = false;
settings.syslog_host = EMSESP_DEFAULT_SYSLOG_HOST; settings.syslog_host = EMSESP_DEFAULT_SYSLOG_HOST;
settings.syslog_level = EMSESP_DEFAULT_SYSLOG_LEVEL; settings.syslog_level = EMSESP_DEFAULT_SYSLOG_LEVEL;
settings.syslog_mark_interval = EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL; settings.syslog_mark_interval = EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL;
@@ -821,9 +877,7 @@ bool System::check_upgrade() {
SPIFFS.end(); SPIFFS.end();
if (failed) { if (failed) {
#if defined(EMSESP_DEBUG)
Serial.println(F("Failed to read custom config. Quitting.")); Serial.println(F("Failed to read custom config. Quitting."));
#endif
Serial.end(); Serial.end();
return false; return false;
} }
@@ -918,6 +972,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & json
JsonObject node = json.createNestedObject("Settings"); JsonObject 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_level"] = settings.syslog_level; node["syslog_level"] = settings.syslog_level;
node["syslog_mark_interval"] = settings.syslog_mark_interval; node["syslog_mark_interval"] = settings.syslog_mark_interval;
node["syslog_host"] = settings.syslog_host; node["syslog_host"] = settings.syslog_host;
@@ -1030,4 +1085,13 @@ bool System::command_report(const char * value, const int8_t id, JsonObject & js
return true; return true;
} }
#if defined(EMSESP_TEST)
// run a test
// e.g. http://ems-esp/api?device=system&cmd=test&data=boiler
bool System::command_test(const char * value, const int8_t id) {
Test::run_test(value, id);
return true;
}
#endif
} // namespace emsesp } // namespace emsesp

View File

@@ -50,18 +50,24 @@ class System {
static bool command_pin(const char * value, const int8_t id); static bool command_pin(const char * value, const int8_t id);
static bool command_send(const char * value, const int8_t id); static bool command_send(const char * value, const int8_t id);
static bool command_publish(const char * value, const int8_t id);
static bool command_fetch(const char * value, const int8_t id);
static bool command_info(const char * value, const int8_t id, JsonObject & json); static bool command_info(const char * value, const int8_t id, JsonObject & json);
static bool command_report(const char * value, const int8_t id, JsonObject & json); static bool command_report(const char * value, const int8_t id, JsonObject & json);
#if defined(EMSESP_TEST)
static bool command_test(const char * value, const int8_t id);
#endif
static uint8_t free_mem(); static uint8_t free_mem();
static void upload_status(bool in_progress); static void upload_status(bool in_progress);
static bool upload_status(); static bool upload_status();
static void show_mem(const char * note); static void show_mem(const char * note);
static void set_led(); static void set_led();
static void init(); static void init();
static void syslog_init();
bool check_upgrade(); bool check_upgrade();
void syslog_init();
void send_heartbeat(); void send_heartbeat();
static std::string hostname() { static std::string hostname() {
@@ -109,9 +115,10 @@ class System {
// settings // settings
static bool hide_led_; static bool hide_led_;
uint8_t syslog_level_; static bool syslog_enabled_;
uint32_t syslog_mark_interval_; static int8_t syslog_level_;
String syslog_host_; static uint32_t syslog_mark_interval_;
static String syslog_host_;
static uint8_t led_gpio_; static uint8_t led_gpio_;
static bool analog_enabled_; static bool analog_enabled_;
}; };

View File

@@ -429,6 +429,11 @@ void TxService::add(const uint8_t operation,
// format is EMS 1.0 (src, dest, type_id, offset, data) // format is EMS 1.0 (src, dest, type_id, offset, data)
// length is the length of the whole telegram data, excluding the CRC // length is the length of the whole telegram data, excluding the CRC
void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t length, const bool front) { void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t length, const bool front) {
// check length
if (length < 5) {
return;
}
// build header. src, dest and offset have fixed positions // build header. src, dest and offset have fixed positions
uint8_t src = data[0]; uint8_t src = data[0];
uint8_t dest = data[1]; uint8_t dest = data[1];

View File

@@ -384,7 +384,7 @@ class TxService : public EMSbus {
void send_telegram(const QueuedTxTelegram & tx_telegram); void send_telegram(const QueuedTxTelegram & tx_telegram);
void send_telegram(const uint8_t * data, const uint8_t length); void send_telegram(const uint8_t * data, const uint8_t length);
}; // namespace emsesp };
} // namespace emsesp } // namespace emsesp

View File

@@ -17,24 +17,145 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#if defined(EMSESP_DEBUG) #if defined(EMSESP_TEST)
#include "test.h" #include "test.h"
// create some fake test data
namespace emsesp { namespace emsesp {
// create some fake test data // no shell
// used with the 'test' command, under su/admin void Test::run_test(const char * command, int8_t id) {
void Test::run_test(uuid::console::Shell & shell, const std::string & command) { if ((command == nullptr) || (strlen(command) == 0)) {
if (command == "default") { return;
run_test(shell, "mixer"); // add the default test case here
} }
if (command.empty()) { if (strcmp(command, "general") == 0) {
run_test(shell, "default"); EMSESP::logger().info(F("Testing general..."));
add_device(0x08, 123); // Nefit Trendline
add_device(0x18, 157); // Bosch CR100
// add some data
// Boiler -> Me, UBAMonitorFast(0x18), telegram: 08 00 18 00 00 02 5A 73 3D 0A 10 65 40 02 1A 80 00 01 E1 01 76 0E 3D 48 00 C9 44 02 00 (#data=25)
uart_telegram({0x08, 0x00, 0x18, 0x00, 0x00, 0x02, 0x5A, 0x73, 0x3D, 0x0A, 0x10, 0x65, 0x40, 0x02, 0x1A,
0x80, 0x00, 0x01, 0xE1, 0x01, 0x76, 0x0E, 0x3D, 0x48, 0x00, 0xC9, 0x44, 0x02, 0x00});
// Boiler -> Thermostat, UBAParameterWW(0x33), telegram: 08 97 33 00 23 24 (#data=2)
uart_telegram({0x08, 0x98, 0x33, 0x00, 0x23, 0x24});
// Boiler -> Me, UBAParameterWW(0x33), telegram: 08 0B 33 00 08 FF 34 FB 00 28 00 00 46 00 FF FF 00 (#data=13)
uart_telegram({0x08, 0x0B, 0x33, 0x00, 0x08, 0xFF, 0x34, 0xFB, 0x00, 0x28, 0x00, 0x00, 0x46, 0x00, 0xFF, 0xFF, 0x00});
// Thermostat RCPLUSStatusMessage_HC1(0x01A5)
uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
return;
}
if (strcmp(command, "gateway") == 0) {
EMSESP::logger().info(F("Testing gateway..."));
// add 0x48 KM200, via a version command
rx_telegram({0x48, 0x0B, 0x02, 0x00, 0xBD, 0x04, 0x06, 00, 00, 00, 00, 00, 00, 00});
// Boiler(0x08) -> All(0x00), UBADevices(0x07), data: 09 01 00 00 00 00 00 00 01 00 00 00 00
// check: make sure 0x48 is not detected again !
rx_telegram({0x08, 0x00, 0x07, 0x00, 0x09, 01, 00, 00, 00, 00, 00, 00, 01, 00, 00, 00, 00});
// add thermostat - Thermostat: RC300/RC310/Moduline 3000/CW400/Sense II (DeviceID:0x10, ProductID:158, Version:03.03) ** master device **
add_device(0x10, 158); // Nefit Trendline
// simulate incoming telegram
// Thermostat(0x10) -> 48(0x48), ?(0x26B), data: 6B 08 4F 00 00 00 02 00 00 00 02 00 03 00 03 00 03
rx_telegram({0x10, 0x48, 0xFF, 00, 01, 0x6B, 00, 0x6B, 0x08, 0x4F, 00, 00, 00, 02, 00, 00, 00, 02, 00, 03, 00, 03, 00, 03});
return;
}
if (strcmp(command, "boiler") == 0) {
// EMSESP::logger().info(F("Testing boiler..."));
add_device(0x08, 123); // Nefit Trendline
// UBAuptime
uart_telegram({0x08, 0x0B, 0x14, 00, 0x3C, 0x1F, 0xAC, 0x70});
return;
}
if (strcmp(command, "thermostat") == 0) {
EMSESP::logger().info(F("Testing thermostat..."));
add_device(0x10, 192); // FW120
// HC1
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x6F, 0x00, 0xCF, 0x21, 0x2E, 0x20, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
// HC2
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x70, 0x00, 0xCF, 0x22, 0x2F, 0x10, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
// HC3
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
return;
}
if (strcmp(command, "solar") == 0) {
EMSESP::logger().info(F("Testing solar..."));
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
add_device(0x30, 163); // SM100
// SM100Monitor - type 0x0362 EMS+ - for SM100 and SM200
// B0 0B FF 00 02 62 00 44 02 7A 80 00 80 00 80 00 80 00 80 00 80 00 00 7C 80 00 80 00 80 00 80
rx_telegram({0xB0, 0x0B, 0xFF, 00, 0x02, 0x62, 00, 0x44, 0x02, 0x7A, 0x80, 00, 0x80, 0x00, 0x80, 00,
0x80, 00, 0x80, 00, 0x80, 00, 00, 0x7C, 0x80, 00, 0x80, 00, 0x80, 00, 0x80});
rx_telegram({0xB0, 0x0B, 0xFF, 0x00, 0x02, 0x62, 0x01, 0x44, 0x03, 0x30, 0x80, 00, 0x80, 00, 0x80, 00,
0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 0x33});
rx_telegram({0xB0, 00, 0xFF, 0x18, 02, 0x62, 0x80, 00, 0xB8});
EMSESP::send_raw_telegram("B0 00 FF 18 02 62 80 00 B8");
return;
}
if (strcmp(command, "heatpump") == 0) {
EMSESP::logger().info(F("Testing heatpump..."));
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
add_device(0x38, 200); // Enviline module
add_device(0x10, 192); // FW120 thermostat
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x6F, 0x00, 0xCF, 0x21, 0x2E, 0x20, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03}); // HC1
uart_telegram("38 0B FF 00 03 7B 0C 34 00 74");
return;
}
}
// used with the 'test' command, under su/admin
void Test::run_test_shell(uuid::console::Shell & shell, const std::string & command) {
// switch to su
shell.add_flags(CommandFlags::ADMIN);
if ((command == "default") || (command == "general") || (command.empty())) {
shell.printfln(F("Testing adding a general boiler & thermostat..."));
run_test("general");
shell.invoke_command("show devices");
shell.invoke_command("show");
shell.invoke_command("call system publish");
shell.invoke_command("show mqtt");
} }
if (command == "render") { if (command == "render") {
shell.printfln(F("Testing render..."));
uint8_t test1 = 12; uint8_t test1 = 12;
int8_t test2 = -12; int8_t test2 = -12;
uint16_t test3 = 456; uint16_t test3 = 456;
@@ -130,47 +251,34 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
} }
if (command == "devices") { if (command == "devices") {
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS); // this is important otherwise nothing will be picked up! shell.printfln(F("Testing devices..."));
//emsdevices.push_back(EMSFactory::add(EMSdevice::DeviceType::BOILER, EMSdevice::EMS_DEVICE_ID_BOILER, 0, "", "My Boiler", 0, 0)); EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS); // this is important otherwise nothing will be picked up!
// A fake response - UBADevices(0x07) // A fake response - UBADevices(0x07)
rx_telegram({0x08, 0x00, 0x07, 0x00, 0x0B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); rx_telegram({0x08, 0x00, 0x07, 0x00, 0x0B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
} }
if (command == "boiler") {
// question: do we need to set the mask?
std::string version("1.2.3");
EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
// UBAuptime
uart_telegram({0x08, 0x0B, 0x14, 00, 0x3C, 0x1F, 0xAC, 0x70});
shell.invoke_command("show");
shell.invoke_command("call boiler info");
}
// check for boiler and controller on same product_id // check for boiler and controller on same product_id
if (command == "double") { if (command == "double") {
// question: do we need to set the mask? shell.printfln(F("Testing double..."));
std::string version("1.2.3");
EMSESP::add_device(0x08, 206, version, EMSdevice::Brand::BUDERUS); // Nefit Excellent HR30 add_device(0x08, 206); // Nefit Excellent HR30
EMSESP::add_device(0x09, 206, version, EMSdevice::Brand::BUDERUS); // Nefit Excellent HR30 Controller add_device(0x09, 206); // Nefit Excellent HR30 Controller
// UBAuptime // UBAuptime
uart_telegram({0x08, 0x0B, 0x14, 00, 0x3C, 0x1F, 0xAC, 0x70}); uart_telegram({0x08, 0x0B, 0x14, 00, 0x3C, 0x1F, 0xAC, 0x70});
} }
// unknown device - // unknown device
if (command == "unknown") { if (command == "unknown") {
// question: do we need to set the mask? shell.printfln(F("Testing unknown..."));
std::string version("1.2.3");
// add boiler // add boiler
EMSESP::add_device(0x08, 84, version, EMSdevice::Brand::BUDERUS); add_device(0x08, 84);
// add Controller - BC10 GB142 - but using the same device_id to see what happens // add Controller - BC10 GB142 - but using the same product_id to see what happens
EMSESP::add_device(0x09, 84, version, EMSdevice::Brand::BUDERUS); add_device(0x09, 84);
// simulate getting version information back from an unknown device // simulate getting version information back from an unknown device
// note there is no brand (byte 9) // note there is no brand (byte 9)
@@ -181,33 +289,22 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
} }
if (command == "unknown2") { if (command == "unknown2") {
shell.printfln(F("Testing unknown2..."));
// simulate getting version information back from an unknown device // simulate getting version information back from an unknown device
rx_telegram({0x09, 0x0B, 0x02, 0x00, 0x5A, 0x01, 0x02}); // product id is 90 which doesn't exist rx_telegram({0x09, 0x0B, 0x02, 0x00, 0x5A, 0x01, 0x02}); // product id is 90 which doesn't exist
} }
if (command == "gateway") { if (command == "gateway") {
// add 0x48 KM200, via a version command shell.printfln(F("Testing Gateway..."));
rx_telegram({0x48, 0x0B, 0x02, 0x00, 0xBD, 0x04, 0x06, 00, 00, 00, 00, 00, 00, 00}); run_test("gateway");
// Boiler(0x08) -> All(0x00), UBADevices(0x07), data: 09 01 00 00 00 00 00 00 01 00 00 00 00
// check: make sure 0x48 is not detected again !
rx_telegram({0x08, 0x00, 0x07, 0x00, 0x09, 01, 00, 00, 00, 00, 00, 00, 01, 00, 00, 00, 00});
// add thermostat - Thermostat: RC300/RC310/Moduline 3000/CW400/Sense II (DeviceID:0x10, ProductID:158, Version:03.03) ** master device **
std::string version("01.03");
EMSESP::add_device(0x10, 158, version, EMSdevice::Brand::BUDERUS);
// simulate incoming telegram
// Thermostat(0x10) -> 48(0x48), ?(0x26B), data: 6B 08 4F 00 00 00 02 00 00 00 02 00 03 00 03 00 03
rx_telegram({0x10, 0x48, 0xFF, 00, 01, 0x6B, 00, 0x6B, 0x08, 0x4F, 00, 00, 00, 02, 00, 00, 00, 02, 00, 03, 00, 03, 00, 03});
} }
if (command == "web") { if (command == "web") {
shell.printfln(F("Testing Web...")); shell.printfln(F("Testing Web..."));
std::string version("1.2.3"); add_device(0x08, 123); // Nefit Trendline
EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline add_device(0x18, 157); // Bosch CR100
EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
// add some data // add some data
// Boiler -> Me, UBAMonitorFast(0x18), telegram: 08 00 18 00 00 02 5A 73 3D 0A 10 65 40 02 1A 80 00 01 E1 01 76 0E 3D 48 00 C9 44 02 00 (#data=25) // Boiler -> Me, UBAMonitorFast(0x18), telegram: 08 00 18 00 00 02 5A 73 3D 0A 10 65 40 02 1A 80 00 01 E1 01 76 0E 3D 48 00 C9 44 02 00 (#data=25)
@@ -228,57 +325,25 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
uart_telegram({0x98, 0x00, 0x06, 0x00, 0x00, 0x03, 0x04, 0x0C, 0x02, 0x33, 0x06, 00, 00, 00, 00, 00, 00}); uart_telegram({0x98, 0x00, 0x06, 0x00, 0x00, 0x03, 0x04, 0x0C, 0x02, 0x33, 0x06, 00, 00, 00, 00, 00, 00});
shell.invoke_command("show"); shell.invoke_command("show");
StaticJsonDocument<2000> doc;
StaticJsonDocument<500> doc;
JsonObject root = doc.to<JsonObject>(); JsonObject root = doc.to<JsonObject>();
EMSESP::device_info_web(2, root); // show thermostat. use 1 for boiler EMSESP::device_info_web(2, root); // show thermostat. use 1 for boiler
serializeJsonPretty(doc, shell); serializeJsonPretty(doc, shell);
shell.println(); shell.println();
} }
if (command == "general") { if (command == "boiler") {
shell.printfln(F("Testing adding a boiler & thermostat...")); shell.printfln(F("Testing boiler..."));
run_test("boiler");
std::string version("1.2.3");
EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
// add some data
// Boiler -> Me, UBAMonitorFast(0x18), telegram: 08 00 18 00 00 02 5A 73 3D 0A 10 65 40 02 1A 80 00 01 E1 01 76 0E 3D 48 00 C9 44 02 00 (#data=25)
uart_telegram({0x08, 0x00, 0x18, 0x00, 0x00, 0x02, 0x5A, 0x73, 0x3D, 0x0A, 0x10, 0x65, 0x40, 0x02, 0x1A,
0x80, 0x00, 0x01, 0xE1, 0x01, 0x76, 0x0E, 0x3D, 0x48, 0x00, 0xC9, 0x44, 0x02, 0x00});
// Boiler -> Thermostat, UBAParameterWW(0x33), telegram: 08 97 33 00 23 24 (#data=2)
uart_telegram({0x08, 0x98, 0x33, 0x00, 0x23, 0x24});
// Boiler -> Me, UBAParameterWW(0x33), telegram: 08 0B 33 00 08 FF 34 FB 00 28 00 00 46 00 FF FF 00 (#data=13)
uart_telegram({0x08, 0x0B, 0x33, 0x00, 0x08, 0xFF, 0x34, 0xFB, 0x00, 0x28, 0x00, 0x00, 0x46, 0x00, 0xFF, 0xFF, 0x00});
// Thermostat RCPLUSStatusMessage_HC1(0x01A5)
uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
shell.invoke_command("show"); shell.invoke_command("show");
shell.invoke_command("publish"); shell.invoke_command("call boiler info");
shell.invoke_command("show mqtt");
} }
if (command == "fr120") { if (command == "fr120") {
shell.printfln(F("Testing adding a thermostat FR120...")); shell.printfln(F("Testing adding a thermostat FR120..."));
// add_device(0x10, 165, version, EMSdevice::Brand::BUDERUS); add_device(0x10, 191); // FR120 thermostat
// add_device(0x17, 125, version, EMSdevice::Brand::BUDERUS); // test unknown class test
// add_device(0x17, 93, version, EMSdevice::Brand::BUDERUS);
// add_device(0x17, 254, version, EMSdevice::Brand::BUDERUS); // test unknown product_id
// EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
std::string version("1.2.3");
// add a boiler
// EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
// add a thermostat
EMSESP::add_device(0x10, 191, version, EMSdevice::Brand::JUNKERS); // FR120
// HC1 // HC1
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x6F, 0x00, 0xCF, 0x21, 0x2E, 0x20, 0x00, 0x2E, 0x24, uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x6F, 0x00, 0xCF, 0x21, 0x2E, 0x20, 0x00, 0x2E, 0x24,
@@ -290,35 +355,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
if (command == "thermostat") { if (command == "thermostat") {
shell.printfln(F("Testing adding a thermostat FW120...")); shell.printfln(F("Testing adding a thermostat FW120..."));
run_test("thermostat");
// add_device(0x10, 165, version, EMSdevice::Brand::BUDERUS);
// add_device(0x17, 125, version, EMSdevice::Brand::BUDERUS); // test unknown class test
// add_device(0x17, 93, version, EMSdevice::Brand::BUDERUS);
// add_device(0x17, 254, version, EMSdevice::Brand::BUDERUS); // test unknown product_id
// EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
std::string version("1.2.3");
// add a boiler
// EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
// add a thermostat
EMSESP::add_device(0x10, 192, version, EMSdevice::Brand::JUNKERS); // FW120
// HC1
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x6F, 0x00, 0xCF, 0x21, 0x2E, 0x20, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
// HC2
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x70, 0x00, 0xCF, 0x22, 0x2F, 0x10, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
// HC3
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
shell.invoke_command("show"); shell.invoke_command("show");
EMSESP::mqtt_.incoming("ems-esp/thermostat_hc1", "heat"); EMSESP::mqtt_.incoming("ems-esp/thermostat_hc1", "heat");
EMSESP::mqtt_.incoming("ems-esp/thermostat_hc2", "28.8"); EMSESP::mqtt_.incoming("ems-esp/thermostat_hc2", "28.8");
EMSESP::mqtt_.incoming("ems-esp/thermostat", "{\"cmd\":\"temp\",\"id\":2,\"data\":22}"); EMSESP::mqtt_.incoming("ems-esp/thermostat", "{\"cmd\":\"temp\",\"id\":2,\"data\":22}");
@@ -327,13 +365,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
if (command == "tc100") { if (command == "tc100") {
shell.printfln(F("Testing adding a TC100 thermostat to the EMS bus...")); shell.printfln(F("Testing adding a TC100 thermostat to the EMS bus..."));
std::string version("02.21");
// add a boiler
// EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
// add a thermostat // add a thermostat
EMSESP::add_device(0x18, 202, version, EMSdevice::Brand::BOSCH); // Bosch TC100 - https://github.com/proddy/EMS-ESP/issues/474 add_device(0x18, 202); // Bosch TC100 - https://github.com/proddy/EMS-ESP/issues/474
// 0x0A // 0x0A
uart_telegram({0x98, 0x0B, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, uart_telegram({0x98, 0x0B, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -342,51 +375,19 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
if (command == "solar") { if (command == "solar") {
shell.printfln(F("Testing Solar")); shell.printfln(F("Testing Solar"));
run_test("solar");
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
std::string version("1.2.3");
EMSESP::add_device(0x30, 163, version, EMSdevice::Brand::BUDERUS); // SM100
// SM100Monitor - type 0x0362 EMS+ - for SM100 and SM200
// B0 0B FF 00 02 62 00 44 02 7A 80 00 80 00 80 00 80 00 80 00 80 00 00 7C 80 00 80 00 80 00 80
rx_telegram({0xB0, 0x0B, 0xFF, 00, 0x02, 0x62, 00, 0x44, 0x02, 0x7A, 0x80, 00, 0x80, 0x00, 0x80, 00,
0x80, 00, 0x80, 00, 0x80, 00, 00, 0x7C, 0x80, 00, 0x80, 00, 0x80, 00, 0x80});
rx_telegram({0xB0, 0x0B, 0xFF, 0x00, 0x02, 0x62, 0x01, 0x44, 0x03, 0x30, 0x80, 00, 0x80, 00, 0x80, 00,
0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 0x33});
rx_telegram({0xB0, 00, 0xFF, 0x18, 02, 0x62, 0x80, 00, 0xB8});
EMSESP::send_raw_telegram("B0 00 FF 18 02 62 80 00 B8");
uart_telegram("30 00 FF 0A 02 6A 04"); // SM100 pump on 1 uart_telegram("30 00 FF 0A 02 6A 04"); // SM100 pump on 1
uart_telegram("30 00 FF 00 02 64 00 00 00 04 00 00 FF 00 00 1E 0B 09 64 00 00 00 00"); // SM100 modulation uart_telegram("30 00 FF 00 02 64 00 00 00 04 00 00 FF 00 00 1E 0B 09 64 00 00 00 00"); // SM100 modulation
EMSESP::show_device_values(shell); EMSESP::show_device_values(shell);
uart_telegram("30 00 FF 0A 02 6A 03"); // SM100 pump off 0 uart_telegram("30 00 FF 0A 02 6A 03"); // SM100 pump off 0
EMSESP::show_device_values(shell); EMSESP::show_device_values(shell);
} }
if (command == "heatpump") { if (command == "heatpump") {
shell.printfln(F("Testing Heat Pump")); shell.printfln(F("Testing Heat Pump"));
run_test("heatpump");
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
std::string version("1.2.3");
// add heatpump
EMSESP::add_device(0x38, 200, version, EMSdevice::Brand::BUDERUS); // Enviline module
// add a thermostat
EMSESP::add_device(0x10, 192, version, EMSdevice::Brand::JUNKERS); // FW120
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x6F, 0x00, 0xCF, 0x21, 0x2E, 0x20, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03}); // HC1
uart_telegram("38 0B FF 00 03 7B 0C 34 00 74");
shell.invoke_command("call"); shell.invoke_command("call");
shell.invoke_command("call heatpump info"); shell.invoke_command("call heatpump info");
EMSESP::show_device_values(shell);
} }
if (command == "solar200") { if (command == "solar200") {
@@ -394,32 +395,26 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS); EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
std::string version("1.2.3"); add_device(0x30, 164); // SM200
EMSESP::add_device(0x30, 164, version, EMSdevice::Brand::BUDERUS); // SM200
// SM100Monitor - type 0x0362 EMS+ - for SM100 and SM200 // SM100Monitor - type 0x0362 EMS+ - for SM100 and SM200
// B0 0B FF 00 02 62 00 44 02 7A 80 00 80 00 80 00 80 00 80 00 80 00 00 7C 80 00 80 00 80 00 80 // B0 0B FF 00 02 62 00 44 02 7A 80 00 80 00 80 00 80 00 80 00 80 00 00 7C 80 00 80 00 80 00 80
rx_telegram({0xB0, 0x0B, 0xFF, 00, 0x02, 0x62, 00, 0x44, 0x02, 0x7A, 0x80, 00, 0x80, 0x00, 0x80, 00, rx_telegram({0xB0, 0x0B, 0xFF, 00, 0x02, 0x62, 00, 0x44, 0x02, 0x7A, 0x80, 00, 0x80, 0x00, 0x80, 00,
0x80, 00, 0x80, 00, 0x80, 00, 00, 0x7C, 0x80, 00, 0x80, 00, 0x80, 00, 0x80}); 0x80, 00, 0x80, 00, 0x80, 00, 00, 0x7C, 0x80, 00, 0x80, 00, 0x80, 00, 0x80});
EMSESP::show_device_values(shell);
rx_telegram({0xB0, 0x0B, 0xFF, 0x00, 0x02, 0x62, 0x01, 0x44, 0x03, 0x30, 0x80, 00, 0x80, 00, 0x80, 00, rx_telegram({0xB0, 0x0B, 0xFF, 0x00, 0x02, 0x62, 0x01, 0x44, 0x03, 0x30, 0x80, 00, 0x80, 00, 0x80, 00,
0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 0x33}); 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 0x33});
EMSESP::show_device_values(shell);
rx_telegram({0xB0, 00, 0xFF, 0x18, 02, 0x62, 0x80, 00, 0xB8}); rx_telegram({0xB0, 00, 0xFF, 0x18, 02, 0x62, 0x80, 00, 0xB8});
EMSESP::show_device_values(shell);
EMSESP::send_raw_telegram("B0 00 FF 18 02 62 80 00 B8"); EMSESP::send_raw_telegram("B0 00 FF 18 02 62 80 00 B8");
uart_telegram("30 00 FF 0A 02 6A 04"); // SM100 pump on 1 uart_telegram("30 00 FF 0A 02 6A 04"); // SM100 pump on 1
uart_telegram("30 00 FF 00 02 64 00 00 00 04 00 00 FF 00 00 1E 0B 09 64 00 00 00 00"); // SM100 modulation uart_telegram("30 00 FF 00 02 64 00 00 00 04 00 00 FF 00 00 1E 0B 09 64 00 00 00 00"); // SM100 modulation
EMSESP::show_device_values(shell);
uart_telegram("30 00 FF 0A 02 6A 03"); // SM100 pump off 0 uart_telegram("30 00 FF 0A 02 6A 03"); // SM100 pump off 0
EMSESP::show_device_values(shell); shell.invoke_command("show");
} }
if (command == "km") { if (command == "km") {
@@ -429,13 +424,11 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS); EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
std::string version("1.2.3"); add_device(0x10, 158); // RC300
EMSESP::add_device(0x10, 158, version, EMSdevice::Brand::BUDERUS); // RC300 add_device(0x48, 189); // KM200
EMSESP::add_device(0x48, 189, version, EMSdevice::Brand::BUDERUS); // KM200
// see https://github.com/proddy/EMS-ESP/issues/390 // see https://github.com/proddy/EMS-ESP/issues/390
/*
uart_telegram_withCRC("90 48 FF 04 01 A6 5C"); uart_telegram_withCRC("90 48 FF 04 01 A6 5C");
uart_telegram_withCRC("90 48 FF 00 01 A6 4C"); uart_telegram_withCRC("90 48 FF 00 01 A6 4C");
uart_telegram_withCRC("90 48 FF 08 01 A7 6D"); uart_telegram_withCRC("90 48 FF 08 01 A7 6D");
@@ -473,7 +466,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
uart_telegram_withCRC("C8 90 FF 00 02 01 A6 D0"); uart_telegram_withCRC("C8 90 FF 00 02 01 A6 D0");
// uart_telegram_withCRC("10 00 FF 00 01 A5 00 D7 21 00 00 00 00 30 01 84 01 01 03 01 84 01 F1 00 00 11 01 00 08 63 00"); // uart_telegram_withCRC("10 00 FF 00 01 A5 00 D7 21 00 00 00 00 30 01 84 01 01 03 01 84 01 F1 00 00 11 01 00 08 63 00");
*/
uart_telegram_withCRC("C8 90 F7 02 01 FF 01 A6 BA"); uart_telegram_withCRC("C8 90 F7 02 01 FF 01 A6 BA");
@@ -494,8 +486,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_HT3); // switch to junkers EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_HT3); // switch to junkers
std::string version("1.2.3"); add_device(0x18, 157); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
// RCPLUSStatusMessage_HC1(0x01A5) // RCPLUSStatusMessage_HC1(0x01A5)
// 98 00 FF 00 01 A5 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03 (no CRC) // 98 00 FF 00 01 A5 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03 (no CRC)
@@ -582,12 +573,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
uart_telegram({0x88, 00, 0x2A, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0xD2, 00, 00, 0x80, 00, 00, 01, 0x9D, 0x80, 0x00, 0x02, 0x79, 00}); uart_telegram({0x88, 00, 0x2A, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0xD2, 00, 00, 0x80, 00, 00, 01, 0x9D, 0x80, 0x00, 0x02, 0x79, 00});
} }
if (command == "send") {
shell.printfln(F("Sending to Tx..."));
EMSESP::show_ems(shell);
EMSESP::txservice_.send(); // send it to UART
}
if (command == "tx") { if (command == "tx") {
shell.printfln(F("Testing Tx...")); shell.printfln(F("Testing Tx..."));
@@ -626,8 +611,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
EMSESP::txservice_.send(); // send it to UART EMSESP::txservice_.send(); // send it to UART
} }
shell.loop_all();
EMSESP::txservice_.flush_tx_queue(); EMSESP::txservice_.flush_tx_queue();
} }
@@ -665,8 +648,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
shell.printfln(F("Testing Commands...")); shell.printfln(F("Testing Commands..."));
// add a thermostat with 3 HCs // add a thermostat with 3 HCs
std::string version("1.2.3"); add_device(0x10, 192); // FW120
EMSESP::add_device(0x10, 192, version, EMSdevice::Brand::JUNKERS); // FW120
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x6F, 0x00, 0xCF, 0x21, 0x2E, 0x20, 0x00, 0x2E, 0x24, uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x6F, 0x00, 0xCF, 0x21, 0x2E, 0x20, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03}); // HC1 0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03}); // HC1
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x70, 0x00, 0xCF, 0x22, 0x2F, 0x10, 0x00, 0x2E, 0x24, uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x70, 0x00, 0xCF, 0x22, 0x2F, 0x10, 0x00, 0x2E, 0x24,
@@ -678,9 +661,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
shell.invoke_command("call"); shell.invoke_command("call");
shell.invoke_command("call system info"); shell.invoke_command("call system info");
char system_topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; EMSESP::mqtt_.incoming("ems-esp/system", "{\"cmd\":\"info\"}"); // this should fail
strcpy(system_topic, "ems-esp/system");
EMSESP::mqtt_.incoming(system_topic, "{\"cmd\":\"info\"}"); // this should fail
shell.invoke_command("call thermostat wwmode"); // should do nothing shell.invoke_command("call thermostat wwmode"); // should do nothing
shell.invoke_command("call thermostat mode auto 2"); // should error, no hc2 shell.invoke_command("call thermostat mode auto 2"); // should error, no hc2
@@ -689,8 +670,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
if (command == "pin") { if (command == "pin") {
shell.printfln(F("Testing pin...")); shell.printfln(F("Testing pin..."));
shell.invoke_command("su");
shell.invoke_command("call system pin"); shell.invoke_command("call system pin");
shell.invoke_command("call system pin 1 true"); shell.invoke_command("call system pin 1 true");
} }
@@ -707,12 +686,10 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
}); });
// add a boiler // add a boiler
// question: do we need to set the mask? add_device(0x08, 123); // Nefit Trendline
std::string version("1.2.3");
EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
// add a thermostat // add a thermostat
EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355 add_device(0x18, 157); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
// RCPLUSStatusMessage_HC1(0x01A5) - HC1 // RCPLUSStatusMessage_HC1(0x01A5) - HC1
uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24, uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24,
@@ -787,23 +764,31 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
} }
if (command == "rx2") { if (command == "rx2") {
shell.printfln(F("Testing rx2..."));
uart_telegram({0x1B, 0x5B, 0xFD, 0x2D, 0x9E, 0x3A, 0xB6, 0xE5, 0x02, 0x20, 0x33, 0x30, 0x32, 0x3A, 0x20, 0x5B, uart_telegram({0x1B, 0x5B, 0xFD, 0x2D, 0x9E, 0x3A, 0xB6, 0xE5, 0x02, 0x20, 0x33, 0x30, 0x32, 0x3A, 0x20, 0x5B,
0x73, 0xFF, 0xFF, 0xCB, 0xDF, 0xB7, 0xA7, 0xB5, 0x67, 0x77, 0x77, 0xE4, 0xFF, 0xFD, 0x77, 0xFF}); 0x73, 0xFF, 0xFF, 0xCB, 0xDF, 0xB7, 0xA7, 0xB5, 0x67, 0x77, 0x77, 0xE4, 0xFF, 0xFD, 0x77, 0xFF});
} }
// https://github.com/proddy/EMS-ESP/issues/380#issuecomment-633663007 // https://github.com/proddy/EMS-ESP/issues/380#issuecomment-633663007
if (command == "rx3") { if (command == "rx3") {
shell.printfln(F("Testing rx3..."));
uart_telegram({0x21, 0x0B, 0xFF, 0x00}); uart_telegram({0x21, 0x0B, 0xFF, 0x00});
} }
// testing the UART tx command, without a queue // testing the UART tx command, without a queue
if (command == "tx2") { if (command == "tx2") {
shell.printfln(F("Testing tx2..."));
uint8_t t[] = {0x0B, 0x88, 0x18, 0x00, 0x20, 0xD4}; // including CRC uint8_t t[] = {0x0B, 0x88, 0x18, 0x00, 0x20, 0xD4}; // including CRC
EMSuart::transmit(t, sizeof(t)); EMSuart::transmit(t, sizeof(t));
} }
// send read request with offset // send read request with offset
if (command == "offset") { if (command == "offset") {
shell.printfln(F("Testing offset..."));
// send_read_request(0x18, 0x08); // send_read_request(0x18, 0x08);
EMSESP::txservice_.read_request(0x18, 0x08, 27); // no offset EMSESP::txservice_.read_request(0x18, 0x08, 27); // no offset
} }
@@ -820,14 +805,13 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
}); });
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS); EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
std::string version("1.2.3");
// add controller // add controller
EMSESP::add_device(0x09, 114, version, EMSdevice::Brand::BUDERUS); add_device(0x09, 114);
EMSESP::add_device(0x28, 160, version, EMSdevice::Brand::BUDERUS); // MM100, WWC add_device(0x28, 160); // MM100, WWC
EMSESP::add_device(0x29, 161, version, EMSdevice::Brand::BUDERUS); // MM200, WWC add_device(0x29, 161); // MM200, WWC
EMSESP::add_device(0x20, 160, version, EMSdevice::Brand::BOSCH); // MM100 add_device(0x20, 160); // MM100
// WWC1 on 0x29 // WWC1 on 0x29
uart_telegram({0xA9, 0x00, 0xFF, 0x00, 0x02, 0x32, 0x02, 0x6C, 0x00, 0x3C, 0x00, 0x3C, 0x3C, 0x46, 0x02, 0x03, 0x03, 0x00, 0x3C}); uart_telegram({0xA9, 0x00, 0xFF, 0x00, 0x02, 0x32, 0x02, 0x6C, 0x00, 0x3C, 0x00, 0x3C, 0x3C, 0x46, 0x02, 0x03, 0x03, 0x00, 0x3C});
@@ -838,16 +822,11 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
// check for error "No telegram type handler found for ID 0x255 (src 0x20)" // check for error "No telegram type handler found for ID 0x255 (src 0x20)"
uart_telegram({0xA0, 0x00, 0xFF, 0x00, 0x01, 0x55, 0x00, 0x1A}); uart_telegram({0xA0, 0x00, 0xFF, 0x00, 0x01, 0x55, 0x00, 0x1A});
shell.invoke_command("show"); // shell.invoke_command("show");
shell.invoke_command("call"); // shell.invoke_command("call mixer info");
shell.invoke_command("call mixer info"); // shell.invoke_command("call system publish");
shell.invoke_command("publish"); // shell.invoke_command("show mqtt");
shell.invoke_command("show mqtt");
shell.invoke_command("call mixer");
} }
// finally dump to console
EMSESP::loop();
} }
// simulates a telegram in the Rx queue, but without the CRC which is added automatically // simulates a telegram in the Rx queue, but without the CRC which is added automatically
@@ -861,6 +840,7 @@ void Test::rx_telegram(const std::vector<uint8_t> & rx_data) {
} }
data[i] = EMSESP::rxservice_.calculate_crc(data, i); data[i] = EMSESP::rxservice_.calculate_crc(data, i);
EMSESP::rxservice_.add(data, len + 1); EMSESP::rxservice_.add(data, len + 1);
EMSESP::loop();
} }
// simulates a telegram straight from UART, but without the CRC which is added automatically // simulates a telegram straight from UART, but without the CRC which is added automatically
@@ -874,7 +854,7 @@ void Test::uart_telegram(const std::vector<uint8_t> & rx_data) {
} }
data[i] = EMSESP::rxservice_.calculate_crc(data, i); data[i] = EMSESP::rxservice_.calculate_crc(data, i);
EMSESP::incoming_telegram(data, i + 1); EMSESP::incoming_telegram(data, i + 1);
EMSESP::rxservice_.loop(); EMSESP::loop();
} }
// takes raw string, assuming it contains the CRC. This is what is output from 'watch raw' // takes raw string, assuming it contains the CRC. This is what is output from 'watch raw'
@@ -912,7 +892,7 @@ void Test::uart_telegram_withCRC(const char * rx_data) {
} }
EMSESP::incoming_telegram(data, count + 1); EMSESP::incoming_telegram(data, count + 1);
EMSESP::rxservice_.loop(); EMSESP::loop();
} }
// takes raw string, adds CRC to end // takes raw string, adds CRC to end
@@ -952,14 +932,14 @@ void Test::uart_telegram(const char * rx_data) {
data[count + 1] = EMSESP::rxservice_.calculate_crc(data, count + 1); // add CRC data[count + 1] = EMSESP::rxservice_.calculate_crc(data, count + 1); // add CRC
EMSESP::incoming_telegram(data, count + 2); EMSESP::incoming_telegram(data, count + 2);
EMSESP::rxservice_.loop(); EMSESP::loop();
} }
#pragma GCC diagnostic push // Sends version telegram. Version is hardcoded to 1.0
#pragma GCC diagnostic ignored "-Wunused-parameter" void Test::add_device(uint8_t device_id, uint8_t product_id) {
// Send version: 09 0B 02 00 PP V1 V2
uart_telegram({device_id, EMSESP_DEFAULT_EMS_BUS_ID, EMSdevice::EMS_TYPE_VERSION, 0, product_id, 1, 0});
#pragma GCC diagnostic pop }
} // namespace emsesp } // namespace emsesp

View File

@@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#if defined(EMSESP_DEBUG) #if defined(EMSESP_TEST)
#ifndef EMSESP_TEST_H #ifndef EMSESP_TEST_H
#define EMSESP_TEST_H #define EMSESP_TEST_H
@@ -40,12 +40,14 @@ namespace emsesp {
class Test { class Test {
public: public:
static void run_test(uuid::console::Shell & shell, const std::string & command); // only for testing static void run_test_shell(uuid::console::Shell & shell, const std::string & command);
static void run_test(const char * command, int8_t id = 0);
static void dummy_mqtt_commands(const char * message); static void dummy_mqtt_commands(const char * message);
static void rx_telegram(const std::vector<uint8_t> & data); static void rx_telegram(const std::vector<uint8_t> & data);
static void uart_telegram(const std::vector<uint8_t> & rx_data); static void uart_telegram(const std::vector<uint8_t> & rx_data);
static void uart_telegram(const char * rx_data); static void uart_telegram(const char * rx_data);
static void uart_telegram_withCRC(const char * rx_data); static void uart_telegram_withCRC(const char * rx_data);
static void add_device(uint8_t device_id, uint8_t product_id);
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "2.1.1b1" #define EMSESP_APP_VERSION "2.1.1b2"