Merge pull request #2575 from proddy/dev

some minor fixes and updates
This commit is contained in:
Proddy
2025-06-05 19:31:29 +02:00
committed by GitHub
16 changed files with 493 additions and 457 deletions

View File

@@ -25,6 +25,8 @@ For more details go to [docs.emsesp.org](https://docs.emsesp.org/).
- IPM DHW module, [#2524](https://github.com/emsesp/EMS-ESP32/issues/2524) - IPM DHW module, [#2524](https://github.com/emsesp/EMS-ESP32/issues/2524)
- charge optimization [#2543](https://github.com/emsesp/EMS-ESP32/issues/2543) - charge optimization [#2543](https://github.com/emsesp/EMS-ESP32/issues/2543)
- shower active state retained, shows correctly in HA - shower active state retained, shows correctly in HA
- MQTT Command Topic with slashes [#2571](https://github.com/emsesp/EMS-ESP32/issues/2571)
- Add pulsed water meter input to V1.3 gateway with Lilygo S3 [#2550](https://github.com/emsesp/EMS-ESP32/issues/2550)
## Changed ## Changed

View File

@@ -12,7 +12,7 @@
"dev": "vite dev", "dev": "vite dev",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"build-hosted": "typesafe-i18n --no-watch && vite build --mode hosted", "build-hosted": "typesafe-i18n && vite build --mode hosted",
"preview-standalone": "typesafe-i18n --no-watch && vite build && concurrently -c \"auto\" \"pnpm:mock-rest\" \"vite preview\"", "preview-standalone": "typesafe-i18n --no-watch && vite build && concurrently -c \"auto\" \"pnpm:mock-rest\" \"vite preview\"",
"mock-rest": "bun --watch ../mock-api/restServer.ts", "mock-rest": "bun --watch ../mock-api/restServer.ts",
"standalone": "concurrently -c \"auto\" \"typesafe-i18n\" \"pnpm:mock-rest\" \"vite\"", "standalone": "concurrently -c \"auto\" \"typesafe-i18n\" \"pnpm:mock-rest\" \"vite\"",
@@ -25,8 +25,8 @@
"@alova/adapter-xhr": "2.1.1", "@alova/adapter-xhr": "2.1.1",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0", "@emotion/styled": "^11.14.0",
"@mui/icons-material": "^7.1.0", "@mui/icons-material": "^7.1.1",
"@mui/material": "^7.1.0", "@mui/material": "^7.1.1",
"@table-library/react-table-library": "4.1.15", "@table-library/react-table-library": "4.1.15",
"alova": "3.2.11", "alova": "3.2.11",
"async-validator": "^4.2.5", "async-validator": "^4.2.5",
@@ -34,30 +34,30 @@
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"magic-string": "^0.30.17", "magic-string": "^0.30.17",
"mime-types": "^3.0.1", "mime-types": "^3.0.1",
"preact": "^10.26.7", "preact": "^10.26.8",
"react": "^19.1.0", "react": "^19.1.0",
"react-dom": "^19.1.0", "react-dom": "^19.1.0",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-router": "^7.6.0", "react-router": "^7.6.2",
"react-toastify": "^11.0.5", "react-toastify": "^11.0.5",
"typesafe-i18n": "^5.26.2", "typesafe-i18n": "^5.26.2",
"typescript": "^5.8.3" "typescript": "^5.8.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.27.1", "@babel/core": "^7.27.4",
"@eslint/js": "^9.27.0", "@eslint/js": "^9.28.0",
"@preact/compat": "^18.3.1", "@preact/compat": "^18.3.1",
"@preact/preset-vite": "^2.10.1", "@preact/preset-vite": "^2.10.1",
"@trivago/prettier-plugin-sort-imports": "^5.2.2", "@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/node": "^22.15.21", "@types/node": "^22.15.29",
"@types/react": "^19.1.5", "@types/react": "^19.1.6",
"@types/react-dom": "^19.1.5", "@types/react-dom": "^19.1.6",
"concurrently": "^9.1.2", "concurrently": "^9.1.2",
"eslint": "^9.27.0", "eslint": "^9.28.0",
"eslint-config-prettier": "^10.1.5", "eslint-config-prettier": "^10.1.5",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"rollup-plugin-visualizer": "^5.14.0", "rollup-plugin-visualizer": "^5.14.0",
"terser": "^5.39.2", "terser": "^5.41.0",
"typescript-eslint": "8.32.1", "typescript-eslint": "8.32.1",
"vite": "^6.3.5", "vite": "^6.3.5",
"vite-plugin-imagemin": "^0.6.1", "vite-plugin-imagemin": "^0.6.1",

784
interface/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -143,7 +143,7 @@ const CustomEntitiesDialog = ({
name="hide" name="hide"
/> />
} }
label="API/Mqtt" label="API/MQTT"
/> />
</Grid> </Grid>
<Grid> <Grid>

View File

@@ -30,8 +30,8 @@ packages:
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/generator@7.27.1': '@babel/generator@7.27.5':
resolution: {integrity: sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==} resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/helper-string-parser@7.27.1': '@babel/helper-string-parser@7.27.1':
@@ -42,8 +42,8 @@ packages:
resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/parser@7.27.2': '@babel/parser@7.27.5':
resolution: {integrity: sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==} resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
hasBin: true hasBin: true
@@ -51,12 +51,12 @@ packages:
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/traverse@7.27.1': '@babel/traverse@7.27.4':
resolution: {integrity: sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==} resolution: {integrity: sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/types@7.27.1': '@babel/types@7.27.6':
resolution: {integrity: sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==} resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@jridgewell/gen-mapping@0.3.8': '@jridgewell/gen-mapping@0.3.8':
@@ -169,10 +169,10 @@ snapshots:
js-tokens: 4.0.0 js-tokens: 4.0.0
picocolors: 1.1.1 picocolors: 1.1.1
'@babel/generator@7.27.1': '@babel/generator@7.27.5':
dependencies: dependencies:
'@babel/parser': 7.27.2 '@babel/parser': 7.27.5
'@babel/types': 7.27.1 '@babel/types': 7.27.6
'@jridgewell/gen-mapping': 0.3.8 '@jridgewell/gen-mapping': 0.3.8
'@jridgewell/trace-mapping': 0.3.25 '@jridgewell/trace-mapping': 0.3.25
jsesc: 3.1.0 jsesc: 3.1.0
@@ -181,29 +181,29 @@ snapshots:
'@babel/helper-validator-identifier@7.27.1': {} '@babel/helper-validator-identifier@7.27.1': {}
'@babel/parser@7.27.2': '@babel/parser@7.27.5':
dependencies: dependencies:
'@babel/types': 7.27.1 '@babel/types': 7.27.6
'@babel/template@7.27.2': '@babel/template@7.27.2':
dependencies: dependencies:
'@babel/code-frame': 7.27.1 '@babel/code-frame': 7.27.1
'@babel/parser': 7.27.2 '@babel/parser': 7.27.5
'@babel/types': 7.27.1 '@babel/types': 7.27.6
'@babel/traverse@7.27.1': '@babel/traverse@7.27.4':
dependencies: dependencies:
'@babel/code-frame': 7.27.1 '@babel/code-frame': 7.27.1
'@babel/generator': 7.27.1 '@babel/generator': 7.27.5
'@babel/parser': 7.27.2 '@babel/parser': 7.27.5
'@babel/template': 7.27.2 '@babel/template': 7.27.2
'@babel/types': 7.27.1 '@babel/types': 7.27.6
debug: 4.4.1 debug: 4.4.1
globals: 11.12.0 globals: 11.12.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@babel/types@7.27.1': '@babel/types@7.27.6':
dependencies: dependencies:
'@babel/helper-string-parser': 7.27.1 '@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.27.1 '@babel/helper-validator-identifier': 7.27.1
@@ -235,10 +235,10 @@ snapshots:
'@trivago/prettier-plugin-sort-imports@5.2.2(prettier@3.5.3)': '@trivago/prettier-plugin-sort-imports@5.2.2(prettier@3.5.3)':
dependencies: dependencies:
'@babel/generator': 7.27.1 '@babel/generator': 7.27.5
'@babel/parser': 7.27.2 '@babel/parser': 7.27.5
'@babel/traverse': 7.27.1 '@babel/traverse': 7.27.4
'@babel/types': 7.27.1 '@babel/types': 7.27.6
javascript-natural-sort: 0.7.1 javascript-natural-sort: 0.7.1
lodash: 4.17.21 lodash: 4.17.21
prettier: 3.5.3 prettier: 3.5.3

View File

@@ -53,7 +53,7 @@ custom_password = admin
; example override for lib_deps and using locally built modules ; example override for lib_deps and using locally built modules
; lib_deps = ; lib_deps =
; bblanchon/ArduinoJson @ 7.4.1 ; bblanchon/ArduinoJson @ 7.4.1
; ESP32Async/AsyncTCP @ 3.4.0 ; ESP32Async/AsyncTCP @ 3.4.4
; ESP32Async/ESPAsyncWebServer @ 3.7.7 ; ESP32Async/ESPAsyncWebServer @ 3.7.7
; file://${PROJECT_DIR}/../modules/EMS-ESP-Modules ; file://${PROJECT_DIR}/../modules/EMS-ESP-Modules
; ; https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.7 ; ; https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.7

View File

@@ -54,7 +54,7 @@ framework = arduino
board_build.partitions = partitions/esp32_partition_16M.csv board_build.partitions = partitions/esp32_partition_16M.csv
board_upload.flash_size = 16MB board_upload.flash_size = 16MB
board_build.app_partition_name = app0 board_build.app_partition_name = app0
platform = espressif32@6.10.0 ; Arduino Core v2.0.17 / IDF v4.4.7 platform = espressif32@6.11.0 ; Arduino Core v2.0.17 / IDF v4.4.7
; 32MB Flash variants ; 32MB Flash variants
[espressif32_base_32M] [espressif32_base_32M]
@@ -62,7 +62,7 @@ framework = arduino
board_build.partitions = partitions/esp32_partition_32M.csv board_build.partitions = partitions/esp32_partition_32M.csv
board_upload.flash_size = 32MB board_upload.flash_size = 32MB
board_build.app_partition_name = app0 board_build.app_partition_name = app0
platform = espressif32@6.10.0 ; Arduino Core 2.0.17 / IDF 4.4.7 platform = espressif32@6.11.0 ; Arduino Core 2.0.17 / IDF 4.4.7
; use Tasmota's library for 4MB Flash variants. ; use Tasmota's library for 4MB Flash variants.
; Removes libs (like mbedtsl, so no WiFi_secure.h) to increase available heap ; Removes libs (like mbedtsl, so no WiFi_secure.h) to increase available heap
@@ -101,7 +101,7 @@ build_type = release
board_build.filesystem = littlefs board_build.filesystem = littlefs
lib_deps = lib_deps =
bblanchon/ArduinoJson @ 7.4.1 bblanchon/ArduinoJson @ 7.4.1
ESP32Async/AsyncTCP @ 3.4.1 ESP32Async/AsyncTCP @ 3.4.4
ESP32Async/ESPAsyncWebServer @ 3.7.7 ESP32Async/ESPAsyncWebServer @ 3.7.7
https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.7 https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.7

View File

@@ -9,7 +9,7 @@ def buildWeb():
print("Building web interface...") print("Building web interface...")
try: try:
env.Execute("pnpm install") env.Execute("pnpm install")
env.Execute("pnpm typesafe-i18n --no-watch") env.Execute("pnpm typesafe-i18n")
with open("./src/i18n/i18n-util.ts") as r: with open("./src/i18n/i18n-util.ts") as r:
text = r.read().replace("Locales = 'pl'", "Locales = 'en'") text = r.read().replace("Locales = 'pl'", "Locales = 'en'")
with open("./src/i18n/i18n-util.ts", "w") as w: with open("./src/i18n/i18n-util.ts", "w") as w:

View File

@@ -187,7 +187,7 @@ class AnalogSensor {
std::vector<Sensor> sensors_; // our list of sensors std::vector<Sensor> sensors_; // our list of sensors
bool analog_enabled_; bool analog_enabled_;
bool changed_ = false; bool changed_ = true; // this will force a publish of all sensors when initialising
uint32_t sensorfails_ = 0; uint32_t sensorfails_ = 0;
uint32_t sensorreads_ = 0; uint32_t sensorreads_ = 0;
}; };

View File

@@ -26,7 +26,7 @@ MqttClient * Mqtt::mqttClient_;
// static parameters we make global // static parameters we make global
std::string Mqtt::mqtt_base_; std::string Mqtt::mqtt_base_;
std::string Mqtt::mqtt_basename_; std::string Mqtt::mqtt_basename_; // base name for MQTT topics with / replaced with _. Used for uniq_id in HA
uint8_t Mqtt::mqtt_qos_; uint8_t Mqtt::mqtt_qos_;
bool Mqtt::mqtt_retain_; bool Mqtt::mqtt_retain_;
uint32_t Mqtt::publish_time_boiler_; uint32_t Mqtt::publish_time_boiler_;
@@ -359,10 +359,8 @@ void Mqtt::load_settings() {
publish_time_heartbeat_ = mqttSettings.publish_time_heartbeat * 1000; publish_time_heartbeat_ = mqttSettings.publish_time_heartbeat * 1000;
}); });
// create basename from the mqtt base // create unique ID from the mqtt base replacing all / with underscores, in case it's a path
// and replacing all / with underscores, in case it's a path basename(mqtt_base_);
mqtt_basename_ = mqtt_base_;
std::replace(mqtt_basename_.begin(), mqtt_basename_.end(), '/', '_');
} }
// start mqtt // start mqtt
@@ -494,7 +492,7 @@ void Mqtt::on_connect() {
load_settings(); // reload MQTT settings - in case they have changes load_settings(); // reload MQTT settings - in case they have changes
if (ha_enabled_) { if (ha_enabled_) {
queue_unsubscribe_message(discovery_prefix_ + "/+/" + mqtt_basename_ + "/#"); queue_unsubscribe_message(discovery_prefix_ + "/+/" + Mqtt::basename() + "/#");
EMSESP::reset_mqtt_ha(); // re-create all HA devices if there are any EMSESP::reset_mqtt_ha(); // re-create all HA devices if there are any
ha_status(); // create the EMS-ESP device in HA, which is MQTT retained ha_status(); // create the EMS-ESP device in HA, which is MQTT retained
ha_climate_reset(true); ha_climate_reset(true);
@@ -504,7 +502,7 @@ void Mqtt::on_connect() {
// If HA is enabled the subscriptions are removed. // If HA is enabled the subscriptions are removed.
// As described in the doc (https://docs.emsesp.org/Troubleshooting?id=home-assistant): // As described in the doc (https://docs.emsesp.org/Troubleshooting?id=home-assistant):
// disable HA, wait 5 minutes (to allow the broker to send all), than reenable HA again. // disable HA, wait 5 minutes (to allow the broker to send all), than reenable HA again.
queue_subscribe_message(discovery_prefix_ + "/+/" + mqtt_basename_ + "/#"); queue_subscribe_message(discovery_prefix_ + "/+/" + Mqtt::basename() + "/#");
} }
// send initial MQTT messages for some of our services // send initial MQTT messages for some of our services
@@ -556,7 +554,7 @@ void Mqtt::ha_status() {
ids.add(Mqtt::basename()); ids.add(Mqtt::basename());
char topic[MQTT_TOPIC_MAX_SIZE]; char topic[MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "binary_sensor/%s/system_status/config", mqtt_basename_.c_str()); snprintf(topic, sizeof(topic), "binary_sensor/%s/system_status/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
// create the HA sensors - must match the MQTT payload keys in the heartbeat topic // create the HA sensors - must match the MQTT payload keys in the heartbeat topic
@@ -733,7 +731,7 @@ bool Mqtt::queue_remove_topic(const char * topic) {
} }
} }
// queue a Home Assistant config topic and payload, with retain flag off. // queue a Home Assistant config topic and payload, with retain flag set
bool Mqtt::queue_ha(const char * topic, const JsonObjectConst payload) { bool Mqtt::queue_ha(const char * topic, const JsonObjectConst payload) {
if (!enabled()) { if (!enabled()) {
return false; return false;
@@ -861,7 +859,7 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
// create the uniq_d based on the entity format // create the uniq_d based on the entity format
if (Mqtt::entity_format() == entityFormat::MULTI_SHORT) { if (Mqtt::entity_format() == entityFormat::MULTI_SHORT) {
// base name + shortname // base name + shortname
snprintf(uniq_id, sizeof(uniq_id), "%s_%s_%s", mqtt_basename_.c_str(), device_name, entity_with_tag); snprintf(uniq_id, sizeof(uniq_id), "%s_%s_%s", Mqtt::basename().c_str(), device_name, entity_with_tag);
} else if (Mqtt::entity_format() == entityFormat::SINGLE_SHORT) { } else if (Mqtt::entity_format() == entityFormat::SINGLE_SHORT) {
// shortname only (=default) // shortname only (=default)
snprintf(uniq_id, sizeof(uniq_id), "%s_%s", device_name, entity_with_tag); snprintf(uniq_id, sizeof(uniq_id), "%s_%s", device_name, entity_with_tag);
@@ -895,15 +893,15 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
snprintf(entity_with_tag, sizeof(entity_with_tag), "%sww", dhw_old[i]); snprintf(entity_with_tag, sizeof(entity_with_tag), "%sww", dhw_old[i]);
} }
} }
snprintf(uniq_id, sizeof(uniq_id), "%s_%s_%s", mqtt_basename_.c_str(), device_name, entity_with_tag); snprintf(uniq_id, sizeof(uniq_id), "%s_%s_%s", Mqtt::basename().c_str(), device_name, entity_with_tag);
} else if (has_tag && device_type == EMSdevice::DeviceType::WATER && tag >= DeviceValue::DeviceValueTAG::TAG_DHW3) { } else if (has_tag && device_type == EMSdevice::DeviceType::WATER && tag >= DeviceValue::DeviceValueTAG::TAG_DHW3) {
snprintf(entity_with_tag, sizeof(entity_with_tag), "wwc%d_%s", tag - DeviceValue::DeviceValueTAG::TAG_DHW1 + 1, entity); snprintf(entity_with_tag, sizeof(entity_with_tag), "wwc%d_%s", tag - DeviceValue::DeviceValueTAG::TAG_DHW1 + 1, entity);
snprintf(uniq_id, sizeof(uniq_id), "%s_solar_%s", mqtt_basename_.c_str(), entity_with_tag); snprintf(uniq_id, sizeof(uniq_id), "%s_solar_%s", Mqtt::basename().c_str(), entity_with_tag);
} else if (has_tag && device_type == EMSdevice::DeviceType::WATER && tag >= DeviceValue::DeviceValueTAG::TAG_DHW1) { } else if (has_tag && device_type == EMSdevice::DeviceType::WATER && tag >= DeviceValue::DeviceValueTAG::TAG_DHW1) {
snprintf(entity_with_tag, sizeof(entity_with_tag), "wwc%d_%s", tag - DeviceValue::DeviceValueTAG::TAG_DHW1 + 1, entity); snprintf(entity_with_tag, sizeof(entity_with_tag), "wwc%d_%s", tag - DeviceValue::DeviceValueTAG::TAG_DHW1 + 1, entity);
snprintf(uniq_id, sizeof(uniq_id), "%s_mixer_%s", mqtt_basename_.c_str(), entity_with_tag); snprintf(uniq_id, sizeof(uniq_id), "%s_mixer_%s", Mqtt::basename().c_str(), entity_with_tag);
} else { } else {
snprintf(uniq_id, sizeof(uniq_id), "%s_%s_%s", mqtt_basename_.c_str(), device_name, entity_with_tag); snprintf(uniq_id, sizeof(uniq_id), "%s_%s_%s", Mqtt::basename().c_str(), device_name, entity_with_tag);
} }
} else { } else {
// entity_format is 0, the old v3.4 style // entity_format is 0, the old v3.4 style
@@ -936,7 +934,7 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
// build a config topic that will be prefix onto a HA type (e.g. number, switch) // build a config topic that will be prefix onto a HA type (e.g. number, switch)
char config_topic[70]; char config_topic[70];
snprintf(config_topic, sizeof(config_topic), "%s/%s_%s/config", mqtt_basename_.c_str(), device_name, entity_with_tag); snprintf(config_topic, sizeof(config_topic), "%s/%s_%s/config", Mqtt::base().c_str(), device_name, entity_with_tag);
// create the topic // create the topic
// depending on the type and whether the device entity is writable (i.e. a command) // depending on the type and whether the device entity is writable (i.e. a command)
@@ -1012,9 +1010,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_basename_.c_str(), device_name, EMSdevice::tag_to_mqtt(tag), entity); snprintf(command_topic, sizeof(command_topic), "%s/%s/%s/%s", Mqtt::base().c_str(), device_name, EMSdevice::tag_to_mqtt(tag), entity);
} else { } else {
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", mqtt_basename_.c_str(), device_name, entity); snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), device_name, entity);
} }
doc["cmd_t"] = command_topic; doc["cmd_t"] = command_topic;
@@ -1344,6 +1342,10 @@ bool Mqtt::publish_ha_climate_config(const int8_t tag, const bool has_roomtemp,
doc["temp_step"] = "0.5"; doc["temp_step"] = "0.5";
doc["mode_cmd_t"] = mode_cmd_s; doc["mode_cmd_t"] = mode_cmd_s;
// add hvac_action - https://github.com/emsesp/EMS-ESP32/discussions/2562
doc["action_topic"] = "~/boiler_data";
doc["action_template"] = "{% if value_json.heatingactive=='on'%}heating{%else%}idle{%endif%}";
// the HA climate component only responds to auto, heat and off // the HA climate component only responds to auto, heat and off
JsonArray modes = doc["modes"].to<JsonArray>(); JsonArray modes = doc["modes"].to<JsonArray>();

View File

@@ -144,6 +144,13 @@ class Mqtt {
return mqtt_basename_; return mqtt_basename_;
} }
// create basename from the mqtt base
// and replacing all / with underscores, in case it's a path
static void basename(const std::string & base) {
mqtt_basename_ = base;
std::replace(mqtt_basename_.begin(), mqtt_basename_.end(), '/', '_');
}
// returns the discovery MQTT topic prefix and adds a / // returns the discovery MQTT topic prefix and adds a /
static std::string discovery_prefix() { static std::string discovery_prefix() {
if (discovery_prefix_.empty()) { if (discovery_prefix_.empty()) {
@@ -299,7 +306,7 @@ class Mqtt {
// settings, copied over // settings, copied over
static std::string mqtt_base_; static std::string mqtt_base_;
static std::string mqtt_basename_; static std::string mqtt_basename_; // base name for MQTT topics with / replaced with _
static uint8_t mqtt_qos_; static uint8_t mqtt_qos_;
static bool mqtt_retain_; static bool mqtt_retain_;
static uint32_t publish_time_; static uint32_t publish_time_;

View File

@@ -4171,12 +4171,15 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
// add the write command to the Tx queue. value is *2 // add the write command to the Tx queue. value is *2
// post validate is the corresponding monitor or set type IDs as they can differ per model // post validate is the corresponding monitor or set type IDs as they can differ per model
write_command(set_typeid, offset, (uint8_t)(temperature * (float)factor), validate_typeid); write_command(set_typeid, offset, (uint8_t)(temperature * (float)factor), validate_typeid);
// update selTemp now, readback from monitor telegram takes a while // update selTemp now, readback from monitor telegram takes a while
if (mode == HeatingCircuit::Mode::AUTO) { if (mode == HeatingCircuit::Mode::AUTO) {
has_update(hc->selTemp,(int16_t)(temperature * factor)); has_update(hc->selTemp, (int16_t)(temperature * factor));
// read_command(0x08, 0x18); // UBAMonitorFast
} }
return true; return true;
} }
LOG_DEBUG("temperature mode %d not found", mode); LOG_DEBUG("temperature mode %d not found", mode);
return false; return false;
} }

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.7.3-dev.11" #define EMSESP_APP_VERSION "3.7.3-dev.12"

View File

@@ -921,6 +921,27 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
ok = true; ok = true;
} }
if (command == "hpmode") {
shell.printfln("Testing MQTT with hpmode...");
Mqtt::ha_enabled(false);
Mqtt::enabled(true);
Mqtt::entity_format(Mqtt::entityFormat::SINGLE_LONG); // SINGLE_LONG, SINGLE_SHORT, MULTI_SHORT
System::test_set_all_active(true); // include all entities and give them fake values
add_device(10, 158);
// EMSESP::mqtt_.incoming("ems-esp/thermostat/hc1/hpmode", "cooling");
EMSESP::mqtt_.incoming("ems-esp/thermostat/hc1/hpmode", "heating & cooling");
EMSESP::publish_all(true);
Mqtt::resubscribe();
Mqtt::show_mqtt(shell); // show queue
ok = true;
}
if (command == "healthcheck") { if (command == "healthcheck") {
// n=1 = EMSESP::system_.HEALTHCHECK_NO_BUS // n=1 = EMSESP::system_.HEALTHCHECK_NO_BUS
// n=2 = EMSESP::system_.HEALTHCHECK_NO_NETWORK // n=2 = EMSESP::system_.HEALTHCHECK_NO_NETWORK

View File

@@ -56,11 +56,12 @@ namespace emsesp {
// #define EMSESP_DEBUG_DEFAULT "telegram_dump" // #define EMSESP_DEBUG_DEFAULT "telegram_dump"
// #define EMSESP_DEBUG_DEFAULT "memory" // #define EMSESP_DEBUG_DEFAULT "memory"
// #define EMSESP_DEBUG_DEFAULT "coldshot" // #define EMSESP_DEBUG_DEFAULT "coldshot"
#define EMSESP_DEBUG_DEFAULT "custom" // #define EMSESP_DEBUG_DEFAULT "custom"
// #define EMSESP_DEBUG_DEFAULT "scheduler" // #define EMSESP_DEBUG_DEFAULT "scheduler"
// #define EMSESP_DEBUG_DEFAULT "heat_exchange" // #define EMSESP_DEBUG_DEFAULT "heat_exchange"
// #define EMSESP_DEBUG_DEFAULT "ls" // #define EMSESP_DEBUG_DEFAULT "ls"
// #define EMSESP_DEBUG_DEFAULT "upload" // #define EMSESP_DEBUG_DEFAULT "upload"
#define EMSESP_DEBUG_DEFAULT "hpmode"
#ifndef EMSESP_DEBUG_DEFAULT #ifndef EMSESP_DEBUG_DEFAULT
#define EMSESP_DEBUG_DEFAULT "general" #define EMSESP_DEBUG_DEFAULT "general"

View File

@@ -433,7 +433,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::basename().c_str(), F_(custom), entityItem.name.c_str()); snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), 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) {