mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-07 00:09:51 +03:00
Compare commits
51 Commits
a738cc36dd
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64058b0f61 | ||
|
|
d7b5c81b0e | ||
|
|
02e8dba971 | ||
|
|
59878fb190 | ||
|
|
9ff0f83af9 | ||
|
|
e6f825371e | ||
|
|
45f3f23033 | ||
|
|
ffd27db208 | ||
|
|
a452d6131b | ||
|
|
03ef981765 | ||
|
|
9ca9f25fd3 | ||
|
|
41122dddb2 | ||
|
|
1e0c94d007 | ||
|
|
3e42a7fb4c | ||
|
|
a8fcc1fb44 | ||
|
|
e43416019d | ||
|
|
9f467ecec1 | ||
|
|
273d87dbf1 | ||
|
|
befd21f8cb | ||
|
|
15e05c4abc | ||
|
|
748a2f5fcf | ||
|
|
37ba42faf8 | ||
|
|
19e343e517 | ||
|
|
8f39129bf8 | ||
|
|
9c3521caf2 | ||
|
|
40fc0fd2f9 | ||
|
|
ff8566498f | ||
|
|
dd06882860 | ||
|
|
fb2294c945 | ||
|
|
26ea8320ce | ||
|
|
0f4963d91e | ||
|
|
91020abc90 | ||
|
|
64906f3ea0 | ||
|
|
28f85b4c5a | ||
|
|
b44a0d6813 | ||
|
|
8af7cde2d6 | ||
|
|
3fcd656bb6 | ||
|
|
76c827257e | ||
|
|
8ca9f7ee30 | ||
|
|
da7a06646a | ||
|
|
0a36f1df7a | ||
|
|
80e5d30781 | ||
|
|
9c4beba3b1 | ||
|
|
0cf932f57e | ||
|
|
5cb9f3b014 | ||
|
|
3eb581142a | ||
|
|
d6c460e7fd | ||
|
|
a2baa50530 | ||
|
|
6569b8c038 | ||
|
|
48b4bf02a3 | ||
|
|
693054a92a |
24
.github/workflows/dev_release.yml
vendored
24
.github/workflows/dev_release.yml
vendored
@@ -64,29 +64,7 @@ jobs:
|
|||||||
- name: Commit the generated files
|
- name: Commit the generated files
|
||||||
uses: stefanzweifel/git-auto-commit-action@v5
|
uses: stefanzweifel/git-auto-commit-action@v5
|
||||||
with:
|
with:
|
||||||
commit_message: "chore: update generated files"
|
commit_message: "chore: update generated files for v${{steps.build_info.outputs.VERSION}}"
|
||||||
|
|
||||||
- name: Configure Git
|
|
||||||
run: |
|
|
||||||
git config --local user.email "action@github.com"
|
|
||||||
git config --local user.name "GitHub Action"
|
|
||||||
|
|
||||||
- name: Check for changes and commit
|
|
||||||
run: |
|
|
||||||
if [ -n "$(git status --porcelain)" ]; then
|
|
||||||
echo "Changes detected, committing..."
|
|
||||||
git add .
|
|
||||||
git commit -m "Auto-commit build artifacts and configuration updates
|
|
||||||
|
|
||||||
- Updated build configurations
|
|
||||||
- Generated build artifacts
|
|
||||||
- Version: ${{steps.build_info.outputs.VERSION}}"
|
|
||||||
|
|
||||||
echo "Pushing changes to repository..."
|
|
||||||
git push origin dev
|
|
||||||
else
|
|
||||||
echo "No changes to commit"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Create GitHub Release
|
- name: Create GitHub Release
|
||||||
id: 'automatic_releases'
|
id: 'automatic_releases'
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,7 +2,6 @@
|
|||||||
.vscode/c_cpp_properties.json
|
.vscode/c_cpp_properties.json
|
||||||
.vscode/extensions.json
|
.vscode/extensions.json
|
||||||
.vscode/launch.json
|
.vscode/launch.json
|
||||||
.vscode/settings.json
|
|
||||||
|
|
||||||
# c++ compiling
|
# c++ compiling
|
||||||
.clang_complete
|
.clang_complete
|
||||||
@@ -73,7 +72,6 @@ logs/*
|
|||||||
sdkconfig.*
|
sdkconfig.*
|
||||||
sdkconfig_tasmota_esp32
|
sdkconfig_tasmota_esp32
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
package.json
|
|
||||||
.cache/
|
.cache/
|
||||||
interface/.tsbuildinfo
|
interface/.tsbuildinfo
|
||||||
test/test_api/package-lock.json
|
test/test_api/package-lock.json
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ For more details go to [docs.emsesp.org](https://docs.emsesp.org/).
|
|||||||
- pumpmode enum for HT3 boilers, add commands for manual defrost, chimneysweeper [#2727](https://github.com/emsesp/EMS-ESP32/issues/2727)
|
- pumpmode enum for HT3 boilers, add commands for manual defrost, chimneysweeper [#2727](https://github.com/emsesp/EMS-ESP32/issues/2727)
|
||||||
- pid settings [#2735](https://github.com/emsesp/EMS-ESP32/issues/2735)
|
- pid settings [#2735](https://github.com/emsesp/EMS-ESP32/issues/2735)
|
||||||
- refresh MQTT button added to MQTT Settings page
|
- refresh MQTT button added to MQTT Settings page
|
||||||
|
- added LWT (Last Will and Testament) to MQTT entities in Home Assistant
|
||||||
|
- added api/metrics endpoint for prometheus integration by @gr3enk
|
||||||
|
[#2774](https://github.com/emsesp/EMS-ESP32/pull/2774)
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
|
|
||||||
|
|||||||
5
Makefile
5
Makefile
@@ -21,13 +21,14 @@ endif
|
|||||||
|
|
||||||
# Optimize parallel build configuration
|
# Optimize parallel build configuration
|
||||||
UNAME_S := $(shell uname -s)
|
UNAME_S := $(shell uname -s)
|
||||||
|
JOBS ?= 1
|
||||||
ifeq ($(UNAME_S),Linux)
|
ifeq ($(UNAME_S),Linux)
|
||||||
EXTRA_CPPFLAGS = -D LINUX
|
EXTRA_CPPFLAGS = -D LINUX
|
||||||
JOBS ?= $(shell nproc)
|
JOBS := $(shell nproc)
|
||||||
endif
|
endif
|
||||||
ifeq ($(UNAME_S),Darwin)
|
ifeq ($(UNAME_S),Darwin)
|
||||||
EXTRA_CPPFLAGS = -D OSX -Wno-tautological-constant-out-of-range-compare
|
EXTRA_CPPFLAGS = -D OSX -Wno-tautological-constant-out-of-range-compare
|
||||||
JOBS ?= $(shell sysctl -n hw.ncpu)
|
JOBS := $(shell sysctl -n hw.ncpu)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# Set optimal parallel build settings
|
# Set optimal parallel build settings
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
"upload": {
|
"upload": {
|
||||||
"flash_size": "32MB",
|
"flash_size": "32MB",
|
||||||
"maximum_ram_size": 327680,
|
"maximum_ram_size": 327680,
|
||||||
"maximum_size": 16777216,
|
"maximum_size": 33554432,
|
||||||
"require_upload_port": true,
|
"require_upload_port": true,
|
||||||
"speed": 460800
|
"speed": 460800
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -23,25 +23,25 @@
|
|||||||
"standalone-devcontainer": "concurrently -c \"auto\" \"typesafe-i18n\" \"pnpm:mock-rest\" \"vite --host\""
|
"standalone-devcontainer": "concurrently -c \"auto\" \"typesafe-i18n\" \"pnpm:mock-rest\" \"vite --host\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alova/adapter-xhr": "2.2.1",
|
"@alova/adapter-xhr": "2.3.0",
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.1",
|
"@emotion/styled": "^11.14.1",
|
||||||
"@mui/icons-material": "^7.3.5",
|
"@mui/icons-material": "^7.3.6",
|
||||||
"@mui/material": "^7.3.5",
|
"@mui/material": "^7.3.6",
|
||||||
"@preact/compat": "^18.3.1",
|
"@preact/compat": "^18.3.1",
|
||||||
"@table-library/react-table-library": "4.1.15",
|
"@table-library/react-table-library": "4.1.15",
|
||||||
"alova": "3.3.4",
|
"alova": "3.4.0",
|
||||||
"async-validator": "^4.2.5",
|
"async-validator": "^4.2.5",
|
||||||
"etag": "^1.8.1",
|
"etag": "^1.8.1",
|
||||||
"formidable": "^3.5.4",
|
"formidable": "^3.5.4",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"magic-string": "^0.30.21",
|
"magic-string": "^0.30.21",
|
||||||
"mime-types": "^3.0.2",
|
"mime-types": "^3.0.2",
|
||||||
"preact": "^10.27.2",
|
"preact": "^10.28.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.1",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.1",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-router": "^7.9.6",
|
"react-router": "^7.10.1",
|
||||||
"react-toastify": "^11.0.5",
|
"react-toastify": "^11.0.5",
|
||||||
"typesafe-i18n": "^5.26.2",
|
"typesafe-i18n": "^5.26.2",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
@@ -59,13 +59,13 @@
|
|||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.39.1",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.7.4",
|
||||||
"rollup-plugin-visualizer": "^6.0.5",
|
"rollup-plugin-visualizer": "^6.0.5",
|
||||||
"terser": "^5.44.1",
|
"terser": "^5.44.1",
|
||||||
"typescript-eslint": "^8.48.0",
|
"typescript-eslint": "^8.48.1",
|
||||||
"vite": "^7.2.4",
|
"vite": "^7.2.6",
|
||||||
"vite-plugin-imagemin": "^0.6.1",
|
"vite-plugin-imagemin": "^0.6.1",
|
||||||
"vite-tsconfig-paths": "^5.1.4"
|
"vite-tsconfig-paths": "^5.1.4"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.23.0+sha512.21c4e5698002ade97e4efe8b8b4a89a8de3c85a37919f957e7a0f30f38fbc5bbdd05980ffe29179b2fb6e6e691242e098d945d1601772cad0fef5fb6411e2a4b"
|
"packageManager": "pnpm@10.24.0+sha512.01ff8ae71b4419903b65c60fb2dc9d34cf8bb6e06d03bde112ef38f7a34d6904c424ba66bea5cdcf12890230bf39f9580473140ed9c946fef328b6e5238a345a"
|
||||||
}
|
}
|
||||||
|
|||||||
514
interface/pnpm-lock.yaml
generated
514
interface/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -266,6 +266,7 @@ const MqttSettings = () => {
|
|||||||
label={LL.CERT()}
|
label={LL.CERT()}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
value={data.rootCA}
|
value={data.rootCA}
|
||||||
|
sx={{ width: '50ch' }}
|
||||||
onChange={updateFormValue}
|
onChange={updateFormValue}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
"@trivago/prettier-plugin-sort-imports": "^6.0.0",
|
"@trivago/prettier-plugin-sort-imports": "^6.0.0",
|
||||||
"formidable": "^3.5.4",
|
"formidable": "^3.5.4",
|
||||||
"itty-router": "^5.0.22",
|
"itty-router": "^5.0.22",
|
||||||
"prettier": "^3.6.2"
|
"prettier": "^3.7.4"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.23.0+sha512.21c4e5698002ade97e4efe8b8b4a89a8de3c85a37919f957e7a0f30f38fbc5bbdd05980ffe29179b2fb6e6e691242e098d945d1601772cad0fef5fb6411e2a4b"
|
"packageManager": "pnpm@10.24.0+sha512.01ff8ae71b4419903b65c60fb2dc9d34cf8bb6e06d03bde112ef38f7a34d6904c424ba66bea5cdcf12890230bf39f9580473140ed9c946fef328b6e5238a345a"
|
||||||
}
|
}
|
||||||
|
|||||||
16
mock-api/pnpm-lock.yaml
generated
16
mock-api/pnpm-lock.yaml
generated
@@ -13,7 +13,7 @@ importers:
|
|||||||
version: 3.1.2
|
version: 3.1.2
|
||||||
'@trivago/prettier-plugin-sort-imports':
|
'@trivago/prettier-plugin-sort-imports':
|
||||||
specifier: ^6.0.0
|
specifier: ^6.0.0
|
||||||
version: 6.0.0(prettier@3.6.2)
|
version: 6.0.0(prettier@3.7.4)
|
||||||
formidable:
|
formidable:
|
||||||
specifier: ^3.5.4
|
specifier: ^3.5.4
|
||||||
version: 3.5.4
|
version: 3.5.4
|
||||||
@@ -21,8 +21,8 @@ importers:
|
|||||||
specifier: ^5.0.22
|
specifier: ^5.0.22
|
||||||
version: 5.0.22
|
version: 5.0.22
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.6.2
|
specifier: ^3.7.4
|
||||||
version: 3.6.2
|
version: 3.7.4
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -167,8 +167,8 @@ packages:
|
|||||||
picocolors@1.1.1:
|
picocolors@1.1.1:
|
||||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||||
|
|
||||||
prettier@3.6.2:
|
prettier@3.7.4:
|
||||||
resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
|
resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -246,7 +246,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@noble/hashes': 1.8.0
|
'@noble/hashes': 1.8.0
|
||||||
|
|
||||||
'@trivago/prettier-plugin-sort-imports@6.0.0(prettier@3.6.2)':
|
'@trivago/prettier-plugin-sort-imports@6.0.0(prettier@3.7.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/generator': 7.28.5
|
'@babel/generator': 7.28.5
|
||||||
'@babel/parser': 7.28.5
|
'@babel/parser': 7.28.5
|
||||||
@@ -256,7 +256,7 @@ snapshots:
|
|||||||
lodash-es: 4.17.21
|
lodash-es: 4.17.21
|
||||||
minimatch: 9.0.5
|
minimatch: 9.0.5
|
||||||
parse-imports-exports: 0.2.4
|
parse-imports-exports: 0.2.4
|
||||||
prettier: 3.6.2
|
prettier: 3.7.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -311,6 +311,6 @@ snapshots:
|
|||||||
|
|
||||||
picocolors@1.1.1: {}
|
picocolors@1.1.1: {}
|
||||||
|
|
||||||
prettier@3.6.2: {}
|
prettier@3.7.4: {}
|
||||||
|
|
||||||
wrappy@1.0.2: {}
|
wrappy@1.0.2: {}
|
||||||
|
|||||||
@@ -569,14 +569,15 @@ let mqtt_settings = {
|
|||||||
publish_time_heartbeat: 60,
|
publish_time_heartbeat: 60,
|
||||||
publish_time_water: 60,
|
publish_time_water: 60,
|
||||||
mqtt_qos: 0,
|
mqtt_qos: 0,
|
||||||
rootCA: '',
|
|
||||||
mqtt_retain: false,
|
mqtt_retain: false,
|
||||||
ha_enabled: true,
|
ha_enabled: true,
|
||||||
nested_format: 1,
|
nested_format: 1,
|
||||||
discovery_type: 0,
|
discovery_type: 0,
|
||||||
discovery_prefix: 'homeassistant',
|
discovery_prefix: 'homeassistant',
|
||||||
send_response: true,
|
send_response: true,
|
||||||
publish_single: false
|
publish_single: false,
|
||||||
|
enableTLS: true,
|
||||||
|
rootCA: ''
|
||||||
};
|
};
|
||||||
const mqtt_status = {
|
const mqtt_status = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ board_build.filesystem = littlefs
|
|||||||
lib_deps =
|
lib_deps =
|
||||||
bblanchon/ArduinoJson @ 7.4.2
|
bblanchon/ArduinoJson @ 7.4.2
|
||||||
ESP32Async/AsyncTCP @ 3.4.9
|
ESP32Async/AsyncTCP @ 3.4.9
|
||||||
ESP32Async/ESPAsyncWebServer @ 3.9.0
|
ESP32Async/ESPAsyncWebServer @ 3.9.2
|
||||||
https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.8
|
https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.8
|
||||||
|
|
||||||
|
|
||||||
@@ -214,23 +214,20 @@ lib_ldf_mode = off
|
|||||||
lib_deps =
|
lib_deps =
|
||||||
|
|
||||||
; unit tests
|
; unit tests
|
||||||
; The code is in ./test/test_api.*
|
; The test code is in ./test/test_api.cpp and the test_api.h file is created by the native-test-create environment.
|
||||||
; to run use `platformio run -e native-test -t exec`. All tests should PASS.
|
; to run use `platformio run -e native-test -t exec`. All tests should PASS.
|
||||||
; to update the test results, compile with -DEMSESP_UNITY_CREATE by uncommenting the line below
|
|
||||||
; then re-run and capture the output between "START - CUT HERE" and "END - CUT HERE" into the test_api.h file
|
|
||||||
; tip: use https://jsondiff.com/ to compare the expected and actual responses.
|
; tip: use https://jsondiff.com/ to compare the expected and actual responses.
|
||||||
[env:native-test]
|
[env:native-test]
|
||||||
platform = native
|
platform = native
|
||||||
test_build_src = true
|
test_build_src = true
|
||||||
build_flags =
|
build_flags =
|
||||||
; -DEMSESP_UNITY_CREATE
|
|
||||||
-DARDUINOJSON_ENABLE_ARDUINO_STRING=1
|
|
||||||
-DEMSESP_STANDALONE -DEMSESP_TEST
|
|
||||||
-DEMSESP_UNITY
|
|
||||||
-DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.7.3-dev.0\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\"
|
|
||||||
-std=gnu++17 -Og -ggdb
|
|
||||||
build_type = debug
|
build_type = debug
|
||||||
build_src_flags =
|
build_src_flags =
|
||||||
|
-DEMSESP_STANDALONE -DEMSESP_TEST
|
||||||
|
-DEMSESP_UNITY
|
||||||
|
-DARDUINOJSON_ENABLE_ARDUINO_STRING=1
|
||||||
|
-DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.7.3-dev.0\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\"
|
||||||
|
-std=gnu++17 -Og -ggdb
|
||||||
-Wall -Wextra
|
-Wall -Wextra
|
||||||
-Wno-unused-parameter -Wno-sign-compare -Wno-missing-braces
|
-Wno-unused-parameter -Wno-sign-compare -Wno-missing-braces
|
||||||
-Wno-vla-cxx-extension -Wno-tautological-constant-out-of-range-compare
|
-Wno-vla-cxx-extension -Wno-tautological-constant-out-of-range-compare
|
||||||
@@ -260,6 +257,12 @@ lib_deps = Unity
|
|||||||
test_testing_command =
|
test_testing_command =
|
||||||
${platformio.build_dir}/${this.__env__}/program
|
${platformio.build_dir}/${this.__env__}/program
|
||||||
|
|
||||||
|
; builds the test cases and creates the test_api.h file
|
||||||
|
; run with `pio run -e native-test-create -t exec` and capture the output between "START - CUT HERE" and "END - CUT HERE" and paste it into the test_api.h file
|
||||||
|
[env:native-test-create]
|
||||||
|
extends = env:native-test
|
||||||
|
build_flags =
|
||||||
|
-DEMSESP_UNITY_CREATE
|
||||||
;
|
;
|
||||||
; Building and testing locally on OS, which we call "standalone" without an ESP32.
|
; Building and testing locally on OS, which we call "standalone" without an ESP32.
|
||||||
; See https://docs.platformio.org/en/latest/platforms/native.html
|
; See https://docs.platformio.org/en/latest/platforms/native.html
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ bool MqttSettingsService::configureMqtt() {
|
|||||||
|
|
||||||
// only connect if WiFi is connected and MQTT is enabled
|
// only connect if WiFi is connected and MQTT is enabled
|
||||||
if (_state.enabled && emsesp::EMSESP::system_.network_connected() && !_state.host.isEmpty()) {
|
if (_state.enabled && emsesp::EMSESP::system_.network_connected() && !_state.host.isEmpty()) {
|
||||||
// create last will topic with the base prefixed. It has to be static because the client destroys the reference
|
// create the Last Will Testament topic (LWT) with the base prefixed. It has to be static because the client destroys the reference
|
||||||
static char will_topic[FACTORY_MQTT_MAX_TOPIC_LENGTH];
|
static char will_topic[FACTORY_MQTT_MAX_TOPIC_LENGTH];
|
||||||
if (_state.base.isEmpty()) {
|
if (_state.base.isEmpty()) {
|
||||||
snprintf(will_topic, sizeof(will_topic), "status");
|
snprintf(will_topic, sizeof(will_topic), "status");
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef FACTORY_MQTT_KEEP_ALIVE
|
#ifndef FACTORY_MQTT_KEEP_ALIVE
|
||||||
#define FACTORY_MQTT_KEEP_ALIVE 16
|
#define FACTORY_MQTT_KEEP_ALIVE 60
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef FACTORY_MQTT_CLEAN_SESSION
|
#ifndef FACTORY_MQTT_CLEAN_SESSION
|
||||||
|
|||||||
@@ -667,9 +667,10 @@ void AnalogSensor::publish_values(const bool force) {
|
|||||||
LOG_DEBUG("Recreating HA config for analog sensor GPIO %02d", sensor.gpio());
|
LOG_DEBUG("Recreating HA config for analog sensor GPIO %02d", sensor.gpio());
|
||||||
|
|
||||||
JsonDocument config;
|
JsonDocument config;
|
||||||
|
config["~"] = Mqtt::base();
|
||||||
|
|
||||||
char stat_t[50];
|
char stat_t[50];
|
||||||
snprintf(stat_t, sizeof(stat_t), "%s/%s_data", Mqtt::base().c_str(), F_(analogsensor)); // use base path
|
snprintf(stat_t, sizeof(stat_t), "~/%s_data", F_(analogsensor)); // use base path
|
||||||
config["stat_t"] = stat_t;
|
config["stat_t"] = stat_t;
|
||||||
|
|
||||||
char val_obj[50];
|
char val_obj[50];
|
||||||
@@ -699,6 +700,7 @@ void AnalogSensor::publish_values(const bool force) {
|
|||||||
snprintf(uniq_s, sizeof(uniq_s), "%s_%02d", F_(analogsensor), sensor.gpio());
|
snprintf(uniq_s, sizeof(uniq_s), "%s_%02d", F_(analogsensor), sensor.gpio());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config["~"] = Mqtt::base();
|
||||||
config["uniq_id"] = uniq_s;
|
config["uniq_id"] = uniq_s;
|
||||||
|
|
||||||
char name[50];
|
char name[50];
|
||||||
|
|||||||
@@ -763,6 +763,8 @@ void Command::show_all(uuid::console::Shell & shell) {
|
|||||||
shell.println(COLOR_RESET);
|
shell.println(COLOR_RESET);
|
||||||
shell.printf(" entities \t\t\t%slist all entities %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN);
|
shell.printf(" entities \t\t\t%slist all entities %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN);
|
||||||
shell.println(COLOR_RESET);
|
shell.println(COLOR_RESET);
|
||||||
|
shell.printf(" metrics \t\t\t%slist all prometheus metrics %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN);
|
||||||
|
shell.println(COLOR_RESET);
|
||||||
|
|
||||||
// show system ones first
|
// show system ones first
|
||||||
show(shell, EMSdevice::DeviceType::SYSTEM, true);
|
show(shell, EMSdevice::DeviceType::SYSTEM, true);
|
||||||
|
|||||||
@@ -87,8 +87,8 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
|
|||||||
Command::show_all(shell);
|
Command::show_all(shell);
|
||||||
} else if (command == F_(system)) {
|
} else if (command == F_(system)) {
|
||||||
EMSESP::system_.show_system(shell);
|
EMSESP::system_.show_system(shell);
|
||||||
} else if (command == F_(users) && (shell.has_flags(CommandFlags::ADMIN))) {
|
} else if (command == F_(users)) {
|
||||||
EMSESP::system_.show_users(shell); // admin only
|
EMSESP::system_.show_users(shell);
|
||||||
} else if (command == F_(devices)) {
|
} else if (command == F_(devices)) {
|
||||||
EMSESP::show_devices(shell);
|
EMSESP::show_devices(shell);
|
||||||
} else if (command == F_(log)) {
|
} else if (command == F_(log)) {
|
||||||
|
|||||||
@@ -632,9 +632,6 @@ void EMSdevice::add_device_value(int8_t tag, // to b
|
|||||||
devicevalues_.emplace_back(
|
devicevalues_.emplace_back(
|
||||||
device_type_, tag, value_p, type, options, options_single, numeric_operator, short_name, fullname, custom_fullname, uom, has_cmd, min, max, state);
|
device_type_, tag, value_p, type, options, options_single, numeric_operator, short_name, fullname, custom_fullname, uom, has_cmd, min, max, state);
|
||||||
|
|
||||||
// add to index for fast lookup by (tag, short_name)
|
|
||||||
devicevalue_index_[{static_cast<uint8_t>(tag), short_name}] = devicevalues_.size() - 1;
|
|
||||||
|
|
||||||
// add a new command if it has a function attached
|
// add a new command if it has a function attached
|
||||||
if (has_cmd) {
|
if (has_cmd) {
|
||||||
uint8_t flags = CommandFlag::ADMIN_ONLY; // executing commands require admin privileges
|
uint8_t flags = CommandFlag::ADMIN_ONLY; // executing commands require admin privileges
|
||||||
@@ -1533,6 +1530,14 @@ bool EMSdevice::get_value_info(JsonObject output, const char * cmd, const int8_t
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (!strcmp(cmd, F_(metrics))) {
|
||||||
|
std::string metrics = get_metrics_prometheus(tag);
|
||||||
|
if (!metrics.empty()) {
|
||||||
|
output["api_data"] = metrics;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// search device value with this tag
|
// search device value with this tag
|
||||||
// make a copy of cmd and split attribute (leave cmd untouched for other devices)
|
// make a copy of cmd and split attribute (leave cmd untouched for other devices)
|
||||||
@@ -1696,6 +1701,175 @@ void EMSdevice::get_value_json(JsonObject json, DeviceValue & dv) {
|
|||||||
json["visible"] = !dv.has_state(DeviceValueState::DV_WEB_EXCLUDE);
|
json["visible"] = !dv.has_state(DeviceValueState::DV_WEB_EXCLUDE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generate Prometheus metrics format from device values
|
||||||
|
std::string EMSdevice::get_metrics_prometheus(const int8_t tag) {
|
||||||
|
std::string result;
|
||||||
|
std::unordered_map<std::string, bool> seen_metrics;
|
||||||
|
|
||||||
|
for (auto & dv : devicevalues_) {
|
||||||
|
if (tag >= 0 && tag != dv.tag) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only process number and boolean types for now
|
||||||
|
if (dv.type != DeviceValueType::BOOL && dv.type != DeviceValueType::UINT8 && dv.type != DeviceValueType::INT8 && dv.type != DeviceValueType::UINT16
|
||||||
|
&& dv.type != DeviceValueType::INT16 && dv.type != DeviceValueType::UINT24 && dv.type != DeviceValueType::UINT32 && dv.type != DeviceValueType::TIME) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has_value = false;
|
||||||
|
double metric_value = 0.0;
|
||||||
|
|
||||||
|
switch (dv.type) {
|
||||||
|
case DeviceValueType::BOOL:
|
||||||
|
if (Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) {
|
||||||
|
has_value = true;
|
||||||
|
metric_value = (bool)*(uint8_t *)(dv.value_p) ? 1.0 : 0.0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DeviceValueType::UINT8:
|
||||||
|
if (Helpers::hasValue(*(uint8_t *)(dv.value_p))) {
|
||||||
|
has_value = true;
|
||||||
|
metric_value = *(uint8_t *)(dv.value_p);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DeviceValueType::INT8:
|
||||||
|
if (Helpers::hasValue(*(int8_t *)(dv.value_p))) {
|
||||||
|
has_value = true;
|
||||||
|
metric_value = *(int8_t *)(dv.value_p);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DeviceValueType::UINT16:
|
||||||
|
if (Helpers::hasValue(*(uint16_t *)(dv.value_p))) {
|
||||||
|
has_value = true;
|
||||||
|
metric_value = *(uint16_t *)(dv.value_p);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DeviceValueType::INT16:
|
||||||
|
if (Helpers::hasValue(*(int16_t *)(dv.value_p))) {
|
||||||
|
has_value = true;
|
||||||
|
metric_value = *(int16_t *)(dv.value_p);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DeviceValueType::UINT24:
|
||||||
|
case DeviceValueType::UINT32:
|
||||||
|
case DeviceValueType::TIME:
|
||||||
|
if (Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
|
||||||
|
has_value = true;
|
||||||
|
metric_value = *(uint32_t *)(dv.value_p);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!has_value) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string metric_name = dv.short_name;
|
||||||
|
size_t last_dot = metric_name.find_last_of('.');
|
||||||
|
if (last_dot != std::string::npos) {
|
||||||
|
metric_name = metric_name.substr(last_dot + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (char & c : metric_name) {
|
||||||
|
if (!isalnum(c) && c != '_') {
|
||||||
|
c = '_';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string full_metric_name = "emsesp_" + metric_name;
|
||||||
|
|
||||||
|
std::string circuit_label;
|
||||||
|
if (dv.tag != DeviceValueTAG::TAG_NONE) {
|
||||||
|
const char * circuit = tag_to_mqtt(dv.tag);
|
||||||
|
if (circuit && strlen(circuit) > 0) {
|
||||||
|
circuit_label = circuit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto fullname = dv.get_fullname();
|
||||||
|
std::string help_text;
|
||||||
|
if (!fullname.empty()) {
|
||||||
|
help_text = fullname;
|
||||||
|
} else {
|
||||||
|
help_text = metric_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string uom_str;
|
||||||
|
if (dv.type == DeviceValueType::BOOL) {
|
||||||
|
uom_str = "boolean";
|
||||||
|
} else if (dv.uom != DeviceValueUOM::NONE) {
|
||||||
|
uom_str = uom_to_string(dv.uom);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string help_line = help_text;
|
||||||
|
if (!uom_str.empty()) {
|
||||||
|
help_line += ", " + uom_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool readable = dv.type != DeviceValueType::CMD && !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE);
|
||||||
|
bool writeable = dv.has_cmd && !dv.has_state(DeviceValueState::DV_READONLY);
|
||||||
|
bool visible = !dv.has_state(DeviceValueState::DV_WEB_EXCLUDE);
|
||||||
|
|
||||||
|
if (readable) {
|
||||||
|
help_line += ", readable";
|
||||||
|
}
|
||||||
|
if (writeable) {
|
||||||
|
help_line += ", writeable";
|
||||||
|
}
|
||||||
|
if (visible) {
|
||||||
|
help_line += ", visible";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string escaped_help;
|
||||||
|
for (char c : help_line) {
|
||||||
|
if (c == '\\') {
|
||||||
|
escaped_help += "\\\\";
|
||||||
|
} else if (c == '\n') {
|
||||||
|
escaped_help += "\\n";
|
||||||
|
} else {
|
||||||
|
escaped_help += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seen_metrics.find(full_metric_name) == seen_metrics.end()) {
|
||||||
|
result += "# HELP " + full_metric_name + " " + escaped_help + "\n";
|
||||||
|
result += "# TYPE " + full_metric_name + " gauge\n";
|
||||||
|
seen_metrics[full_metric_name] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
result += full_metric_name;
|
||||||
|
if (!circuit_label.empty()) {
|
||||||
|
result += "{circuit=\"" + circuit_label + "\"}";
|
||||||
|
}
|
||||||
|
result += " ";
|
||||||
|
|
||||||
|
char val_str[30];
|
||||||
|
double final_value = metric_value;
|
||||||
|
|
||||||
|
if (dv.numeric_operator != 0) {
|
||||||
|
if (dv.numeric_operator > 0) {
|
||||||
|
final_value = metric_value / dv.numeric_operator;
|
||||||
|
} else {
|
||||||
|
final_value = metric_value * (-dv.numeric_operator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double rounded = (final_value >= 0) ? (double)((int64_t)(final_value + 0.5)) : (double)((int64_t)(final_value - 0.5));
|
||||||
|
if (dv.type == DeviceValueType::BOOL || (final_value == rounded)) {
|
||||||
|
snprintf(val_str, sizeof(val_str), "%.0f", final_value);
|
||||||
|
} else {
|
||||||
|
snprintf(val_str, sizeof(val_str), "%.2f", final_value);
|
||||||
|
}
|
||||||
|
result += val_str;
|
||||||
|
result += "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// mqtt publish all single values from one device (used for time schedule)
|
// mqtt publish all single values from one device (used for time schedule)
|
||||||
void EMSdevice::publish_all_values() {
|
void EMSdevice::publish_all_values() {
|
||||||
for (const auto & dv : devicevalues_) {
|
for (const auto & dv : devicevalues_) {
|
||||||
@@ -2035,13 +2209,14 @@ std::string EMSdevice::name() {
|
|||||||
// copy a raw value (i.e. without applying the numeric_operator) to the output buffer.
|
// copy a raw value (i.e. without applying the numeric_operator) to the output buffer.
|
||||||
// returns true on success.
|
// returns true on success.
|
||||||
int EMSdevice::get_modbus_value(uint8_t tag, const std::string & shortname, std::vector<uint16_t> & result) {
|
int EMSdevice::get_modbus_value(uint8_t tag, const std::string & shortname, std::vector<uint16_t> & result) {
|
||||||
// find device value by shortname using hash map index
|
// find device value by shortname
|
||||||
auto index_it = devicevalue_index_.find({tag, shortname});
|
// TODO replace linear search which is inefficient
|
||||||
if (index_it == devicevalue_index_.end()) {
|
const auto & it = std::find_if(devicevalues_.begin(), devicevalues_.end(), [&](const DeviceValue & x) { return x.tag == tag && x.short_name == shortname; });
|
||||||
|
if (it == devicevalues_.end() && (it->short_name != shortname || it->tag != tag)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto & dv = devicevalues_[index_it->second];
|
auto & dv = *it;
|
||||||
|
|
||||||
// check if it exists, there is a value for the entity. Set the flag to ACTIVE
|
// check if it exists, there is a value for the entity. Set the flag to ACTIVE
|
||||||
// not that this will override any previously removed states
|
// not that this will override any previously removed states
|
||||||
@@ -2122,13 +2297,13 @@ int EMSdevice::get_modbus_value(uint8_t tag, const std::string & shortname, std:
|
|||||||
int EMSdevice::modbus_value_to_json(uint8_t tag, const std::string & shortname, const std::vector<uint8_t> & modbus_data, JsonObject jsonValue) {
|
int EMSdevice::modbus_value_to_json(uint8_t tag, const std::string & shortname, const std::vector<uint8_t> & modbus_data, JsonObject jsonValue) {
|
||||||
// LOG_DEBUG("modbus_value_to_json(%d,%s,[%d bytes])\n", tag, shortname.c_str(), modbus_data.size());
|
// LOG_DEBUG("modbus_value_to_json(%d,%s,[%d bytes])\n", tag, shortname.c_str(), modbus_data.size());
|
||||||
|
|
||||||
// find device value by shortname using hash map index
|
// find device value by shortname
|
||||||
auto index_it = devicevalue_index_.find({tag, shortname});
|
const auto & it = std::find_if(devicevalues_.begin(), devicevalues_.end(), [&](const DeviceValue & x) { return x.tag == tag && x.short_name == shortname; });
|
||||||
if (index_it == devicevalue_index_.end()) {
|
if (it == devicevalues_.end() && (it->short_name != shortname || it->tag != tag)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto & dv = devicevalues_[index_it->second];
|
auto & dv = *it;
|
||||||
|
|
||||||
// handle Booleans
|
// handle Booleans
|
||||||
if (dv.type == DeviceValueType::BOOL) {
|
if (dv.type == DeviceValueType::BOOL) {
|
||||||
|
|||||||
@@ -251,6 +251,7 @@ class EMSdevice {
|
|||||||
std::string get_value_uom(const std::string & shortname) const;
|
std::string get_value_uom(const std::string & shortname) const;
|
||||||
bool get_value_info(JsonObject root, const char * cmd, const int8_t id);
|
bool get_value_info(JsonObject root, const char * cmd, const int8_t id);
|
||||||
void get_value_json(JsonObject output, DeviceValue & dv);
|
void get_value_json(JsonObject output, DeviceValue & dv);
|
||||||
|
std::string get_metrics_prometheus(const int8_t tag = -1);
|
||||||
void get_dv_info(JsonObject json);
|
void get_dv_info(JsonObject json);
|
||||||
|
|
||||||
enum OUTPUT_TARGET : uint8_t { API_VERBOSE, API_SHORTNAMES, MQTT, CONSOLE };
|
enum OUTPUT_TARGET : uint8_t { API_VERBOSE, API_SHORTNAMES, MQTT, CONSOLE };
|
||||||
@@ -555,26 +556,6 @@ class EMSdevice {
|
|||||||
#endif
|
#endif
|
||||||
std::vector<TelegramFunction> telegram_functions_; // each EMS device has its own set of registered telegram types
|
std::vector<TelegramFunction> telegram_functions_; // each EMS device has its own set of registered telegram types
|
||||||
std::vector<DeviceValue> devicevalues_; // all the device values
|
std::vector<DeviceValue> devicevalues_; // all the device values
|
||||||
|
|
||||||
// added for modbus
|
|
||||||
// Hash map for O(1) lookup of device values by (tag, short_name) key
|
|
||||||
struct DeviceValueKey {
|
|
||||||
uint8_t tag;
|
|
||||||
std::string short_name;
|
|
||||||
|
|
||||||
bool operator==(const DeviceValueKey & other) const {
|
|
||||||
return tag == other.tag && short_name == other.short_name;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DeviceValueKeyHash {
|
|
||||||
std::size_t operator()(const DeviceValueKey & key) const {
|
|
||||||
// Combine hash of tag and short_name
|
|
||||||
return std::hash<uint8_t>()(key.tag) ^ (std::hash<std::string>()(key.short_name) << 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unordered_map<DeviceValueKey, size_t, DeviceValueKeyHash> devicevalue_index_; // index: key -> devicevalues_ position
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace emsesp
|
} // namespace emsesp
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ MAKE_WORD(info)
|
|||||||
MAKE_WORD(settings)
|
MAKE_WORD(settings)
|
||||||
MAKE_WORD(value)
|
MAKE_WORD(value)
|
||||||
MAKE_WORD(entities)
|
MAKE_WORD(entities)
|
||||||
|
MAKE_WORD(metrics)
|
||||||
MAKE_WORD(coldshot)
|
MAKE_WORD(coldshot)
|
||||||
|
|
||||||
// device types - lowercase, used in MQTT
|
// device types - lowercase, used in MQTT
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ MAKE_WORD_TRANSLATION(pool_device, "Pool Module", "Poolmodul", "", "Poolmodul",
|
|||||||
MAKE_WORD_TRANSLATION(info_cmd, "list all values (verbose)", "Liste aller Werte", "lijst van alle waardes", "lista alla värden", "wyświetl wszystkie wartości", "Viser alle verdier", "", "Tüm değerleri listele", "elenca tutti i valori", "zobraziť všetky hodnoty", "vypsat všechny hodnoty (podrobně)") // TODO translate
|
MAKE_WORD_TRANSLATION(info_cmd, "list all values (verbose)", "Liste aller Werte", "lijst van alle waardes", "lista alla värden", "wyświetl wszystkie wartości", "Viser alle verdier", "", "Tüm değerleri listele", "elenca tutti i valori", "zobraziť všetky hodnoty", "vypsat všechny hodnoty (podrobně)") // TODO translate
|
||||||
MAKE_WORD_TRANSLATION(commands_cmd, "list all commands", "Liste aller Kommandos", "lijst van alle commando's", "lista alla kommandon", "wyświetl wszystkie komendy", "Viser alle kommandoer", "", "Tüm komutları listele", "elencaa tutti i comandi", "zobraziť všetky príkazy", "vypsat všechny příkazy") // TODO translate
|
MAKE_WORD_TRANSLATION(commands_cmd, "list all commands", "Liste aller Kommandos", "lijst van alle commando's", "lista alla kommandon", "wyświetl wszystkie komendy", "Viser alle kommandoer", "", "Tüm komutları listele", "elencaa tutti i comandi", "zobraziť všetky príkazy", "vypsat všechny příkazy") // TODO translate
|
||||||
MAKE_WORD_TRANSLATION(entities_cmd, "list all entities", "Liste aller Entitäten", "lijst van alle entiteiten", "lista all entiteter", "wyświetl wszsytkie encje", "Viser alle enheter", "", "Tüm varlıkları listele", "elenca tutte le entità", "zobraziť všetky entity", "vypsat všechny entity") // TODO translate
|
MAKE_WORD_TRANSLATION(entities_cmd, "list all entities", "Liste aller Entitäten", "lijst van alle entiteiten", "lista all entiteter", "wyświetl wszsytkie encje", "Viser alle enheter", "", "Tüm varlıkları listele", "elenca tutte le entità", "zobraziť všetky entity", "vypsat všechny entity") // TODO translate
|
||||||
|
MAKE_WORD_TRANSLATION(metrics_cmd, "list all prometheus metrics", "Liste aller Prometheus Metriken", "lijst van alle Prometheus metriken", "lista alla Prometheus metriker", "wyświetl wszystkie Prometheus metryki", "Viser alle Prometheus metrikker", "", "Tüm Prometheus metriklerini listele", "elenca tutte le metriche Prometheus", "zobraziť všetky Prometheus metriky", "vypsat všechny Prometheus metriky") // TODO translate
|
||||||
MAKE_WORD_TRANSLATION(send_cmd, "send a telegram", "Sende EMS-Telegramm", "stuur een telegram", "skicka ett telegram", "wyślij telegram", "send et telegram", "", "Bir telegram gönder", "invia un telegramma", "poslať telegram", "odeslat telegram") // TODO translate
|
MAKE_WORD_TRANSLATION(send_cmd, "send a telegram", "Sende EMS-Telegramm", "stuur een telegram", "skicka ett telegram", "wyślij telegram", "send et telegram", "", "Bir telegram gönder", "invia un telegramma", "poslať telegram", "odeslat telegram") // TODO translate
|
||||||
MAKE_WORD_TRANSLATION(read_cmd, "send read request", "", "", "skicka en läsförfrågan", "", "", "", "", "", "odoslať žiadosť o prečítanie", "") // TODO translate
|
MAKE_WORD_TRANSLATION(read_cmd, "send read request", "", "", "skicka en läsförfrågan", "", "", "", "", "", "odoslať žiadosť o prečítanie", "") // TODO translate
|
||||||
MAKE_WORD_TRANSLATION(setiovalue_cmd, "set I/O value", "Setze Werte E/A", "instellen standaardwaarde", "sätt ett I/O-värde", "ustaw wartość", "sett en io verdi", "", "Giriş/Çıkış değerlerini ayarla", "imposta valore io", "nastaviť hodnotu io", "nastavit hodnotu I/O") // TODO translate
|
MAKE_WORD_TRANSLATION(setiovalue_cmd, "set I/O value", "Setze Werte E/A", "instellen standaardwaarde", "sätt ett I/O-värde", "ustaw wartość", "sett en io verdi", "", "Giriş/Çıkış değerlerini ayarla", "imposta valore io", "nastaviť hodnotu io", "nastavit hodnotu I/O") // TODO translate
|
||||||
|
|||||||
@@ -509,14 +509,14 @@ void Mqtt::on_connect() {
|
|||||||
queue_subscribe_message(discovery_prefix_ + "/+/" + Mqtt::basename() + "/#");
|
queue_subscribe_message(discovery_prefix_ + "/+/" + Mqtt::basename() + "/#");
|
||||||
}
|
}
|
||||||
|
|
||||||
// send initial MQTT messages for some of our services
|
|
||||||
EMSESP::system_.send_heartbeat(); // send heartbeat
|
|
||||||
|
|
||||||
// re-subscribe to all custom registered MQTT topics
|
// re-subscribe to all custom registered MQTT topics
|
||||||
resubscribe();
|
resubscribe();
|
||||||
|
|
||||||
// publish to the last will topic (see Mqtt::start() function) to say we're alive
|
// publish to the last will topic (see Mqtt::start() function) to say we're alive
|
||||||
queue_publish_retain("status", "online"); // retain: https://github.com/emsesp/EMS-ESP32/discussions/2086
|
queue_publish_retain("status", "online"); // retain: https://github.com/emsesp/EMS-ESP32/discussions/2086
|
||||||
|
|
||||||
|
// send initial MQTT messages for some of our services
|
||||||
|
EMSESP::system_.send_heartbeat(); // send heartbeat
|
||||||
}
|
}
|
||||||
|
|
||||||
// Home Assistant Discovery - the main master Device called EMS-ESP
|
// Home Assistant Discovery - the main master Device called EMS-ESP
|
||||||
@@ -532,9 +532,10 @@ void Mqtt::ha_status() {
|
|||||||
strcpy(uniq, "system_status");
|
strcpy(uniq, "system_status");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
doc["~"] = Mqtt::base();
|
||||||
doc["uniq_id"] = uniq;
|
doc["uniq_id"] = uniq;
|
||||||
doc["def_ent_id"] = (std::string) "binary_sensor." + uniq;
|
doc["def_ent_id"] = (std::string) "binary_sensor." + uniq;
|
||||||
doc["stat_t"] = Mqtt::base() + "/status";
|
doc["stat_t"] = "~/status";
|
||||||
doc["name"] = "System status";
|
doc["name"] = "System status";
|
||||||
doc["pl_on"] = "online";
|
doc["pl_on"] = "online";
|
||||||
doc["pl_off"] = "offline";
|
doc["pl_off"] = "offline";
|
||||||
@@ -981,8 +982,9 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
|
|||||||
return queue_remove_topic(topic);
|
return queue_remove_topic(topic);
|
||||||
}
|
}
|
||||||
|
|
||||||
// build the full payload
|
// build the full topic's payload
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
|
doc["~"] = Mqtt::base();
|
||||||
doc["uniq_id"] = uniq_id;
|
doc["uniq_id"] = uniq_id;
|
||||||
|
|
||||||
// set the entity_id. This is breaking change in HA 2025.10.0 - see https://github.com/home-assistant/core/pull/151775
|
// set the entity_id. This is breaking change in HA 2025.10.0 - see https://github.com/home-assistant/core/pull/151775
|
||||||
@@ -1000,9 +1002,9 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
|
|||||||
char command_topic[MQTT_TOPIC_MAX_SIZE];
|
char command_topic[MQTT_TOPIC_MAX_SIZE];
|
||||||
// add command topic
|
// add command topic
|
||||||
if (tag >= DeviceValueTAG::TAG_HC1) {
|
if (tag >= DeviceValueTAG::TAG_HC1) {
|
||||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s/%s", Mqtt::base().c_str(), device_name, EMSdevice::tag_to_mqtt(tag), entity);
|
snprintf(command_topic, sizeof(command_topic), "~/%s/%s/%s", device_name, EMSdevice::tag_to_mqtt(tag), entity);
|
||||||
} else {
|
} else {
|
||||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), device_name, entity);
|
snprintf(command_topic, sizeof(command_topic), "~/%s/%s", device_name, entity);
|
||||||
}
|
}
|
||||||
doc["cmd_t"] = command_topic;
|
doc["cmd_t"] = command_topic;
|
||||||
|
|
||||||
@@ -1063,9 +1065,9 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
|
|||||||
// This is where we determine which MQTT topic to pull the data from
|
// This is where we determine which MQTT topic to pull the data from
|
||||||
// There is one exception for DeviceType::SYSTEM, which uses the heartbeat topic, and when fetching the version we want to take this from the info topic instead
|
// There is one exception for DeviceType::SYSTEM, which uses the heartbeat topic, and when fetching the version we want to take this from the info topic instead
|
||||||
if ((device_type == EMSdevice::DeviceType::SYSTEM) && (strncmp(entity, "version", 7) == 0)) {
|
if ((device_type == EMSdevice::DeviceType::SYSTEM) && (strncmp(entity, "version", 7) == 0)) {
|
||||||
snprintf(stat_t, sizeof(stat_t), "%s/%s", Mqtt::base().c_str(), F_(info));
|
snprintf(stat_t, sizeof(stat_t), "~/%s", F_(info));
|
||||||
} else {
|
} else {
|
||||||
snprintf(stat_t, sizeof(stat_t), "%s/%s", Mqtt::base().c_str(), tag_to_topic(device_type, tag).c_str());
|
snprintf(stat_t, sizeof(stat_t), "~/%s", tag_to_topic(device_type, tag).c_str());
|
||||||
}
|
}
|
||||||
doc["stat_t"] = stat_t;
|
doc["stat_t"] = stat_t;
|
||||||
|
|
||||||
@@ -1484,6 +1486,11 @@ void Mqtt::add_ha_avail_section(JsonObject doc, const char * state_t, const bool
|
|||||||
avty.add(avty_json); // returns 0 if no mem
|
avty.add(avty_json); // returns 0 if no mem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add LWT (Last Will and Testament)
|
||||||
|
avty_json.clear();
|
||||||
|
avty_json["t"] = "~/status"; // as a topic
|
||||||
|
avty.add(avty_json);
|
||||||
|
|
||||||
doc["avty_mode"] = "all";
|
doc["avty_mode"] = "all";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -197,6 +197,8 @@ void Shower::create_ha_discovery() {
|
|||||||
char str[70];
|
char str[70];
|
||||||
char stat_t[50];
|
char stat_t[50];
|
||||||
|
|
||||||
|
doc["~"] = Mqtt::base();
|
||||||
|
|
||||||
// shower active
|
// shower active
|
||||||
doc["name"] = "Shower Active";
|
doc["name"] = "Shower Active";
|
||||||
|
|
||||||
@@ -207,9 +209,7 @@ void Shower::create_ha_discovery() {
|
|||||||
}
|
}
|
||||||
doc["uniq_id"] = str;
|
doc["uniq_id"] = str;
|
||||||
doc["def_ent_id"] = (std::string) "binary_sensor." + str;
|
doc["def_ent_id"] = (std::string) "binary_sensor." + str;
|
||||||
|
doc["stat_t"] = "~/shower_active";
|
||||||
snprintf(stat_t, sizeof(stat_t), "%s/shower_active", Mqtt::base().c_str());
|
|
||||||
doc["stat_t"] = stat_t;
|
|
||||||
|
|
||||||
Mqtt::add_ha_bool(doc.as<JsonObject>());
|
Mqtt::add_ha_bool(doc.as<JsonObject>());
|
||||||
Mqtt::add_ha_dev_section(doc.as<JsonObject>(), "Shower Sensor", nullptr, nullptr, nullptr, false);
|
Mqtt::add_ha_dev_section(doc.as<JsonObject>(), "Shower Sensor", nullptr, nullptr, nullptr, false);
|
||||||
@@ -225,10 +225,7 @@ void Shower::create_ha_discovery() {
|
|||||||
|
|
||||||
doc["uniq_id"] = str;
|
doc["uniq_id"] = str;
|
||||||
doc["def_ent_id"] = (std::string) "sensor." + str;
|
doc["def_ent_id"] = (std::string) "sensor." + str;
|
||||||
|
doc["stat_t"] = "~/shower_data",
|
||||||
snprintf(stat_t, sizeof(stat_t), "%s/shower_data", Mqtt::base().c_str());
|
|
||||||
doc["stat_t"] = stat_t;
|
|
||||||
|
|
||||||
doc["name"] = "Shower Duration";
|
doc["name"] = "Shower Duration";
|
||||||
|
|
||||||
// don't bother with value template conditions if using Domoticz which doesn't fully support MQTT Discovery
|
// don't bother with value template conditions if using Domoticz which doesn't fully support MQTT Discovery
|
||||||
@@ -248,29 +245,6 @@ void Shower::create_ha_discovery() {
|
|||||||
|
|
||||||
snprintf(topic, sizeof(topic), "sensor/%s/shower_duration/config", Mqtt::basename().c_str());
|
snprintf(topic, sizeof(topic), "sensor/%s/shower_duration/config", Mqtt::basename().c_str());
|
||||||
Mqtt::queue_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
|
Mqtt::queue_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
|
||||||
|
|
||||||
//
|
|
||||||
// shower timestamp
|
|
||||||
//
|
|
||||||
/* commented out as the publish of timestamp
|
|
||||||
doc.clear();
|
|
||||||
|
|
||||||
snprintf(str, sizeof(str), "%s_shower_timestamp", Mqtt::basename().c_str());
|
|
||||||
|
|
||||||
doc["uniq_id"] = str;
|
|
||||||
|
|
||||||
snprintf(stat_t, sizeof(stat_t), "%s/shower_data", Mqtt::base().c_str());
|
|
||||||
doc["stat_t"] = stat_t;
|
|
||||||
|
|
||||||
doc["name"] = "Shower Timestamp";
|
|
||||||
doc["val_tpl"] = "{{value_json.timestamp if value_json.timestamp is defined else 0}}";
|
|
||||||
// doc["ent_cat"] = "diagnostic";
|
|
||||||
|
|
||||||
Mqtt::add_ha_sections_to_doc("shower", stat_t, doc, false, "value_json.timestamp is defined");
|
|
||||||
|
|
||||||
snprintf(topic, sizeof(topic), "sensor/%s/shower_timestamp/config", Mqtt::basename().c_str());
|
|
||||||
Mqtt::queue_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -731,11 +731,6 @@ void System::heartbeat_json(JsonObject output) {
|
|||||||
|
|
||||||
// send periodic MQTT message with system information
|
// send periodic MQTT message with system information
|
||||||
void System::send_heartbeat() {
|
void System::send_heartbeat() {
|
||||||
// don't send heartbeat if WiFi or MQTT is not connected
|
|
||||||
if (!Mqtt::connected()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshHeapMem(); // refresh free heap and max alloc heap
|
refreshHeapMem(); // refresh free heap and max alloc heap
|
||||||
|
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
@@ -997,6 +992,11 @@ int8_t System::wifi_quality(int8_t dBm) {
|
|||||||
|
|
||||||
// print users to console
|
// print users to console
|
||||||
void System::show_users(uuid::console::Shell & shell) {
|
void System::show_users(uuid::console::Shell & shell) {
|
||||||
|
if (!shell.has_flags(CommandFlags::ADMIN)) {
|
||||||
|
shell.printfln("Unauthorized. You need to be an admin to view users.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
shell.printfln("Users:");
|
shell.printfln("Users:");
|
||||||
|
|
||||||
#ifndef EMSESP_STANDALONE
|
#ifndef EMSESP_STANDALONE
|
||||||
|
|||||||
@@ -507,11 +507,12 @@ void TemperatureSensor::publish_values(const bool force) {
|
|||||||
LOG_DEBUG("Recreating HA config for sensor ID %s", sensor.id().c_str());
|
LOG_DEBUG("Recreating HA config for sensor ID %s", sensor.id().c_str());
|
||||||
|
|
||||||
JsonDocument config;
|
JsonDocument config;
|
||||||
|
config["~"] = Mqtt::base();
|
||||||
config["dev_cla"] = "temperature";
|
config["dev_cla"] = "temperature";
|
||||||
config["stat_cla"] = "measurement";
|
config["stat_cla"] = "measurement";
|
||||||
|
|
||||||
char stat_t[50];
|
char stat_t[50];
|
||||||
snprintf(stat_t, sizeof(stat_t), "%s/%s_data", Mqtt::base().c_str(), F_(temperaturesensor)); // use base path
|
snprintf(stat_t, sizeof(stat_t), "~/%s_data", F_(temperaturesensor)); // use base path
|
||||||
config["stat_t"] = stat_t;
|
config["stat_t"] = stat_t;
|
||||||
|
|
||||||
config["unit_of_meas"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES);
|
config["unit_of_meas"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES);
|
||||||
|
|||||||
@@ -964,17 +964,17 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
|||||||
48,
|
48,
|
||||||
63);
|
63);
|
||||||
register_device_value(
|
register_device_value(
|
||||||
DeviceValueTAG::TAG_DHW1, &wwComfDiffTemp_, DeviceValueType::UINT8, FL_(wwComfDiffTemp), DeviceValueUOM::K, MAKE_CF_CB(set_wwComfDiffTemp), 4, 12);
|
DeviceValueTAG::TAG_DHW1, &wwComfDiffTemp_, DeviceValueType::UINT8, FL_(wwComfDiffTemp), DeviceValueUOM::K, MAKE_CF_CB(set_wwComfDiffTemp), 4, 15);
|
||||||
register_device_value(
|
register_device_value(
|
||||||
DeviceValueTAG::TAG_DHW1, &wwEcoDiffTemp_, DeviceValueType::UINT8, FL_(wwEcoDiffTemp), DeviceValueUOM::K, MAKE_CF_CB(set_wwEcoDiffTemp), 4, 12);
|
DeviceValueTAG::TAG_DHW1, &wwEcoDiffTemp_, DeviceValueType::UINT8, FL_(wwEcoDiffTemp), DeviceValueUOM::K, MAKE_CF_CB(set_wwEcoDiffTemp), 4, 15);
|
||||||
register_device_value(DeviceValueTAG::TAG_DHW1,
|
register_device_value(DeviceValueTAG::TAG_DHW1,
|
||||||
&wwEcoPlusDiffTemp_,
|
&wwEcoPlusDiffTemp_,
|
||||||
DeviceValueType::UINT8,
|
DeviceValueType::UINT8,
|
||||||
FL_(wwEcoPlusDiffTemp),
|
FL_(wwEcoPlusDiffTemp),
|
||||||
DeviceValueUOM::K,
|
DeviceValueUOM::K,
|
||||||
MAKE_CF_CB(set_wwEcoPlusDiffTemp),
|
MAKE_CF_CB(set_wwEcoPlusDiffTemp),
|
||||||
6,
|
4,
|
||||||
12);
|
15);
|
||||||
register_device_value(DeviceValueTAG::TAG_DHW1,
|
register_device_value(DeviceValueTAG::TAG_DHW1,
|
||||||
&wwComfStopTemp_,
|
&wwComfStopTemp_,
|
||||||
DeviceValueType::UINT8,
|
DeviceValueType::UINT8,
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
#define EMSESP_APP_VERSION "3.7.3-dev.32"
|
#define EMSESP_APP_VERSION "3.7.3-dev.34"
|
||||||
|
|||||||
@@ -144,6 +144,8 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
api_count_++;
|
||||||
|
|
||||||
// send the json that came back from the command call
|
// send the json that came back from the command call
|
||||||
// sequence matches CommandRet in command.h (FAIL, OK, NOT_FOUND, ERROR, NOT_ALLOWED, INVALID, NO_VALUE)
|
// sequence matches CommandRet in command.h (FAIL, OK, NOT_FOUND, ERROR, NOT_ALLOWED, INVALID, NO_VALUE)
|
||||||
// 400 (bad request)
|
// 400 (bad request)
|
||||||
@@ -153,16 +155,23 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
|
|||||||
// 400 (invalid)
|
// 400 (invalid)
|
||||||
int ret_codes[7] = {400, 200, 404, 400, 401, 400, 404};
|
int ret_codes[7] = {400, 200, 404, 400, 401, 400, 404};
|
||||||
|
|
||||||
|
response->setCode(ret_codes[return_code]);
|
||||||
|
response->setLength();
|
||||||
|
response->setContentType("application/json; charset=utf-8");
|
||||||
|
request->send(response);
|
||||||
|
|
||||||
// serialize JSON to string to ensure correct content-length and avoid HTTP parsing errors (issue #2752)
|
// serialize JSON to string to ensure correct content-length and avoid HTTP parsing errors (issue #2752)
|
||||||
std::string output_str;
|
// std::string output_str;
|
||||||
serializeJson(output, output_str);
|
// serializeJson(output, output_str);
|
||||||
request->send(ret_codes[return_code], "application/json; charset=utf-8", output_str.c_str());
|
// request->send(ret_codes[return_code], "application/json; charset=utf-8", output_str.c_str());
|
||||||
|
|
||||||
#if defined(EMSESP_UNITY)
|
#if defined(EMSESP_UNITY)
|
||||||
// store the result so we can test with Unity later
|
// store the result so we can test with Unity later
|
||||||
storeResponse(output);
|
storeResponse(output);
|
||||||
#endif
|
#endif
|
||||||
#if defined(EMSESP_STANDALONE) && !defined(EMSESP_UNITY)
|
#if defined(EMSESP_STANDALONE) && !defined(EMSESP_UNITY)
|
||||||
|
std::string output_str;
|
||||||
|
serializeJson(output, output_str);
|
||||||
Serial.printf("%sweb output: %s[%s] %s(%d)%s %s%s",
|
Serial.printf("%sweb output: %s[%s] %s(%d)%s %s%s",
|
||||||
COLOR_WHITE,
|
COLOR_WHITE,
|
||||||
COLOR_BRIGHT_CYAN,
|
COLOR_BRIGHT_CYAN,
|
||||||
@@ -175,9 +184,6 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
|
|||||||
Serial.println();
|
Serial.println();
|
||||||
EMSESP::logger().debug("web output: %s %s", request->url().c_str(), output_str.c_str());
|
EMSESP::logger().debug("web output: %s %s", request->url().c_str(), output_str.c_str());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
api_count_++;
|
|
||||||
delete response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(EMSESP_UNITY)
|
#if defined(EMSESP_UNITY)
|
||||||
|
|||||||
@@ -411,8 +411,10 @@ void WebCustomEntityService::publish(const bool force) {
|
|||||||
// create HA config
|
// create HA config
|
||||||
if (Mqtt::ha_enabled() && !ha_registered_) {
|
if (Mqtt::ha_enabled() && !ha_registered_) {
|
||||||
JsonDocument config;
|
JsonDocument config;
|
||||||
|
config["~"] = Mqtt::base();
|
||||||
|
|
||||||
char stat_t[50];
|
char stat_t[50];
|
||||||
snprintf(stat_t, sizeof(stat_t), "%s/%s_data", Mqtt::base().c_str(), F_(custom));
|
snprintf(stat_t, sizeof(stat_t), "~/%s_data", F_(custom));
|
||||||
config["stat_t"] = stat_t;
|
config["stat_t"] = stat_t;
|
||||||
|
|
||||||
char val_obj[50];
|
char val_obj[50];
|
||||||
@@ -445,7 +447,7 @@ void WebCustomEntityService::publish(const bool force) {
|
|||||||
snprintf(topic, sizeof(topic), "sensor/%s/%s_%s/config", Mqtt::basename().c_str(), F_(custom), entityItem.name.c_str());
|
snprintf(topic, sizeof(topic), "sensor/%s/%s_%s/config", Mqtt::basename().c_str(), F_(custom), entityItem.name.c_str());
|
||||||
}
|
}
|
||||||
char command_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
char command_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(custom), entityItem.name.c_str());
|
snprintf(command_topic, sizeof(command_topic), "~/%s/%s", F_(custom), entityItem.name.c_str());
|
||||||
config["cmd_t"] = command_topic;
|
config["cmd_t"] = command_topic;
|
||||||
} else {
|
} else {
|
||||||
if (entityItem.value_type == DeviceValueType::BOOL) {
|
if (entityItem.value_type == DeviceValueType::BOOL) {
|
||||||
|
|||||||
@@ -263,9 +263,12 @@ void WebSchedulerService::publish(const bool force) {
|
|||||||
|
|
||||||
// create HA config
|
// create HA config
|
||||||
if (Mqtt::ha_enabled() && !ha_registered_) {
|
if (Mqtt::ha_enabled() && !ha_registered_) {
|
||||||
|
|
||||||
JsonDocument config;
|
JsonDocument config;
|
||||||
|
config["~"] = Mqtt::base();
|
||||||
|
|
||||||
char stat_t[50];
|
char stat_t[50];
|
||||||
snprintf(stat_t, sizeof(stat_t), "%s/%s_data", Mqtt::base().c_str(), F_(scheduler));
|
snprintf(stat_t, sizeof(stat_t), "~/%s_data", F_(scheduler));
|
||||||
config["stat_t"] = stat_t;
|
config["stat_t"] = stat_t;
|
||||||
|
|
||||||
char val_obj[50];
|
char val_obj[50];
|
||||||
@@ -290,7 +293,7 @@ void WebSchedulerService::publish(const bool force) {
|
|||||||
char command_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
char command_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||||
|
|
||||||
snprintf(topic, sizeof(topic), "switch/%s/%s_%s/config", Mqtt::basename().c_str(), F_(scheduler), scheduleItem.name.c_str());
|
snprintf(topic, sizeof(topic), "switch/%s/%s_%s/config", Mqtt::basename().c_str(), F_(scheduler), scheduleItem.name.c_str());
|
||||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(scheduler), scheduleItem.name.c_str());
|
snprintf(command_topic, sizeof(command_topic), "~/%s/%s", F_(scheduler), scheduleItem.name.c_str());
|
||||||
config["cmd_t"] = command_topic;
|
config["cmd_t"] = command_topic;
|
||||||
|
|
||||||
Mqtt::add_ha_bool(config.as<JsonObject>());
|
Mqtt::add_ha_bool(config.as<JsonObject>());
|
||||||
|
|||||||
@@ -422,7 +422,7 @@ void WebSettings::set_board_profile(WebSettings & settings) {
|
|||||||
|
|
||||||
// load the board profile into the data vector
|
// load the board profile into the data vector
|
||||||
// 0=led, 1=dallas, 2=rx, 3=tx, 4=button, 5=phy_type, 6=eth_power, 7=eth_phy_addr, 8=eth_clock_mode, 9=led_type
|
// 0=led, 1=dallas, 2=rx, 3=tx, 4=button, 5=phy_type, 6=eth_power, 7=eth_phy_addr, 8=eth_clock_mode, 9=led_type
|
||||||
std::vector<int8_t> data(99, 0); // initialize with 99 for all values, just as a safe guard to catch bad gpios
|
std::vector<int8_t> data(10, 99); // initialize with 99 for all values, just as a safe guard to catch bad gpios
|
||||||
if (settings.board_profile != "default") {
|
if (settings.board_profile != "default") {
|
||||||
if (!System::load_board_profile(data, settings.board_profile.c_str())) {
|
if (!System::load_board_profile(data, settings.board_profile.c_str())) {
|
||||||
#if defined(EMSESP_DEBUG)
|
#if defined(EMSESP_DEBUG)
|
||||||
|
|||||||
@@ -2,34 +2,63 @@
|
|||||||
// node api_test.js
|
// node api_test.js
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
|
|
||||||
async function testAPI(ip = "ems-esp.local", apiPath = "system") {
|
async function testAPI(ip = "ems-esp.local", apiPath = "system", loopCount = 1, delayMs = 1000) {
|
||||||
const baseUrl = `http://${ip}/api`;
|
const baseUrl = `http://${ip}`;
|
||||||
const url = `${baseUrl}/${apiPath}`;
|
const url = `${baseUrl}/${apiPath}`;
|
||||||
|
const results = [];
|
||||||
|
const testStartTime = Date.now();
|
||||||
|
|
||||||
try {
|
for (let i = 0; i < loopCount; i++) {
|
||||||
const response = await axios.get(url, {
|
let logMessage = '';
|
||||||
timeout: 5000,
|
if (loopCount > 1) {
|
||||||
headers: {
|
const totalElapsed = ((Date.now() - testStartTime) / 1000).toFixed(1);
|
||||||
'Content-Type': 'application/json'
|
logMessage = `[${totalElapsed}s] Request: ${i + 1}/${loopCount},`;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
try {
|
||||||
console.log('Status:', response.status);
|
const startTime = Date.now();
|
||||||
console.log('Data:', JSON.stringify(response.data, null, 2));
|
const response = await axios.get(url, {
|
||||||
|
timeout: 5000,
|
||||||
return response.data;
|
headers: {
|
||||||
} catch (error) {
|
'Content-Type': 'application/json'
|
||||||
console.error('Error:', error.message);
|
}
|
||||||
if (error.response) {
|
});
|
||||||
console.error('Response status:', error.response.status);
|
logMessage += (logMessage ? ' ' : '') + `URL: ${url}, Status: ${response.status}`;
|
||||||
console.error('Response data:', error.response.data);
|
} catch (error) {
|
||||||
|
console.error('Error:', error.message);
|
||||||
|
// if (error.response) {
|
||||||
|
// console.error('Response status:', error.response.status);
|
||||||
|
// console.error('Response data:', error.response.data);
|
||||||
|
// }
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if successful make another request to the /api/system/info endpoint to fetch the freeMem
|
||||||
|
const response = await axios.get(`${baseUrl}/api/system/info`);
|
||||||
|
const freeMem = response.data?.freeMem || response.data?.system?.freeMem;
|
||||||
|
if (freeMem !== undefined) {
|
||||||
|
logMessage += `, freeMem: ${freeMem}`;
|
||||||
|
} else {
|
||||||
|
logMessage += 'freeMem not found in response';
|
||||||
|
}
|
||||||
|
console.log(logMessage);
|
||||||
|
|
||||||
|
// Delay before next request (except for the last one)
|
||||||
|
if (i < loopCount - 1) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, delayMs));
|
||||||
}
|
}
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return loopCount === 1 ? results[0] : results;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the test
|
// Run the test
|
||||||
testAPI("192.168.1.223", "system")
|
// Examples:
|
||||||
|
// testAPI("192.168.1.65", "api/system") - single call
|
||||||
|
// testAPI("192.168.1.65", "api/system", 5) - 5 calls with 1000ms delay
|
||||||
|
// testAPI("192.168.1.65", "api/system", 10, 2000) - 10 calls with 2000ms delay
|
||||||
|
// testAPI("192.168.1.65", "status", 20000, 5)
|
||||||
|
testAPI("192.168.1.65", "api/custom/test_custom", 1000, 5)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log('Test completed successfully');
|
console.log('Test completed successfully');
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
|
|||||||
5
test/test_api/package.json
Normal file
5
test/test_api/package.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.13.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -288,6 +288,36 @@ void manual_test7() {
|
|||||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/custom/test_ram", data));
|
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/custom/test_ram", data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void manual_test8() {
|
||||||
|
const char * response = call_url("/api/boiler/metrics");
|
||||||
|
|
||||||
|
TEST_ASSERT_NOT_NULL(response);
|
||||||
|
TEST_ASSERT_TRUE(strlen(response) > 0);
|
||||||
|
|
||||||
|
TEST_ASSERT_TRUE(strstr(response, "# HELP") != nullptr);
|
||||||
|
TEST_ASSERT_TRUE(strstr(response, "# TYPE") != nullptr);
|
||||||
|
TEST_ASSERT_TRUE(strstr(response, "emsesp_") != nullptr);
|
||||||
|
TEST_ASSERT_TRUE(strstr(response, " gauge") != nullptr);
|
||||||
|
|
||||||
|
TEST_ASSERT_TRUE(strstr(response, "emsesp_tapwateractive") != nullptr || strstr(response, "emsesp_selflowtemp") != nullptr
|
||||||
|
|| strstr(response, "emsesp_curflowtemp") != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void manual_test9() {
|
||||||
|
const char * response = call_url("/api/thermostat/metrics");
|
||||||
|
|
||||||
|
TEST_ASSERT_NOT_NULL(response);
|
||||||
|
TEST_ASSERT_TRUE(strlen(response) > 0);
|
||||||
|
|
||||||
|
TEST_ASSERT_TRUE(strstr(response, "# HELP") != nullptr);
|
||||||
|
TEST_ASSERT_TRUE(strstr(response, "# TYPE") != nullptr);
|
||||||
|
TEST_ASSERT_TRUE(strstr(response, "emsesp_") != nullptr);
|
||||||
|
|
||||||
|
if (strstr(response, "circuit=") != nullptr) {
|
||||||
|
TEST_ASSERT_TRUE(strstr(response, "{circuit=") != nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void run_manual_tests() {
|
void run_manual_tests() {
|
||||||
RUN_TEST(manual_test1);
|
RUN_TEST(manual_test1);
|
||||||
RUN_TEST(manual_test2);
|
RUN_TEST(manual_test2);
|
||||||
@@ -296,6 +326,8 @@ void run_manual_tests() {
|
|||||||
RUN_TEST(manual_test5);
|
RUN_TEST(manual_test5);
|
||||||
RUN_TEST(manual_test6);
|
RUN_TEST(manual_test6);
|
||||||
RUN_TEST(manual_test7);
|
RUN_TEST(manual_test7);
|
||||||
|
RUN_TEST(manual_test8);
|
||||||
|
RUN_TEST(manual_test9);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char * run_console_command(const char * command) {
|
const char * run_console_command(const char * command) {
|
||||||
@@ -353,6 +385,7 @@ void create_tests() {
|
|||||||
capture("/api/boiler/values");
|
capture("/api/boiler/values");
|
||||||
capture("/api/boiler/info");
|
capture("/api/boiler/info");
|
||||||
// capture("/api/boiler/entities"); // skipping since payload is too large
|
// capture("/api/boiler/entities"); // skipping since payload is too large
|
||||||
|
capture("/api/boiler/metrics");
|
||||||
capture("/api/boiler/comfort");
|
capture("/api/boiler/comfort");
|
||||||
capture("/api/boiler/comfort/value");
|
capture("/api/boiler/comfort/value");
|
||||||
capture("/api/boiler/comfort/fullname");
|
capture("/api/boiler/comfort/fullname");
|
||||||
@@ -364,6 +397,7 @@ void create_tests() {
|
|||||||
// thermostat
|
// thermostat
|
||||||
capture("/api/thermostat");
|
capture("/api/thermostat");
|
||||||
capture("/api/thermostat/hc1/values");
|
capture("/api/thermostat/hc1/values");
|
||||||
|
capture("/api/thermostat/metrics");
|
||||||
capture("/api/thermostat/hc1/seltemp");
|
capture("/api/thermostat/hc1/seltemp");
|
||||||
capture("/api/thermostat/hc2/seltemp");
|
capture("/api/thermostat/hc2/seltemp");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user