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)
- charge optimization [#2543](https://github.com/emsesp/EMS-ESP32/issues/2543)
- 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

View File

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

View File

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

View File

@@ -53,7 +53,7 @@ custom_password = admin
; example override for lib_deps and using locally built modules
; lib_deps =
; bblanchon/ArduinoJson @ 7.4.1
; ESP32Async/AsyncTCP @ 3.4.0
; ESP32Async/AsyncTCP @ 3.4.4
; ESP32Async/ESPAsyncWebServer @ 3.7.7
; file://${PROJECT_DIR}/../modules/EMS-ESP-Modules
; ; 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_upload.flash_size = 16MB
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
[espressif32_base_32M]
@@ -62,7 +62,7 @@ framework = arduino
board_build.partitions = partitions/esp32_partition_32M.csv
board_upload.flash_size = 32MB
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.
; Removes libs (like mbedtsl, so no WiFi_secure.h) to increase available heap
@@ -101,7 +101,7 @@ build_type = release
board_build.filesystem = littlefs
lib_deps =
bblanchon/ArduinoJson @ 7.4.1
ESP32Async/AsyncTCP @ 3.4.1
ESP32Async/AsyncTCP @ 3.4.4
ESP32Async/ESPAsyncWebServer @ 3.7.7
https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.7

View File

@@ -9,7 +9,7 @@ def buildWeb():
print("Building web interface...")
try:
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:
text = r.read().replace("Locales = 'pl'", "Locales = 'en'")
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
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 sensorreads_ = 0;
};

View File

@@ -26,7 +26,7 @@ MqttClient * Mqtt::mqttClient_;
// static parameters we make global
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_;
bool Mqtt::mqtt_retain_;
uint32_t Mqtt::publish_time_boiler_;
@@ -359,10 +359,8 @@ void Mqtt::load_settings() {
publish_time_heartbeat_ = mqttSettings.publish_time_heartbeat * 1000;
});
// create basename from the mqtt base
// and replacing all / with underscores, in case it's a path
mqtt_basename_ = mqtt_base_;
std::replace(mqtt_basename_.begin(), mqtt_basename_.end(), '/', '_');
// create unique ID from the mqtt base replacing all / with underscores, in case it's a path
basename(mqtt_base_);
}
// start mqtt
@@ -494,7 +492,7 @@ void Mqtt::on_connect() {
load_settings(); // reload MQTT settings - in case they have changes
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
ha_status(); // create the EMS-ESP device in HA, which is MQTT retained
ha_climate_reset(true);
@@ -504,7 +502,7 @@ void Mqtt::on_connect() {
// If HA is enabled the subscriptions are removed.
// 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.
queue_subscribe_message(discovery_prefix_ + "/+/" + mqtt_basename_ + "/#");
queue_subscribe_message(discovery_prefix_ + "/+/" + Mqtt::basename() + "/#");
}
// send initial MQTT messages for some of our services
@@ -556,7 +554,7 @@ void Mqtt::ha_status() {
ids.add(Mqtt::basename());
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
// 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) {
if (!enabled()) {
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
if (Mqtt::entity_format() == entityFormat::MULTI_SHORT) {
// 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) {
// shortname only (=default)
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(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) {
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) {
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 {
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 {
// 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)
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
// 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];
// add command topic
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 {
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;
@@ -1344,6 +1342,10 @@ bool Mqtt::publish_ha_climate_config(const int8_t tag, const bool has_roomtemp,
doc["temp_step"] = "0.5";
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
JsonArray modes = doc["modes"].to<JsonArray>();

View File

@@ -144,6 +144,13 @@ class Mqtt {
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 /
static std::string discovery_prefix() {
if (discovery_prefix_.empty()) {
@@ -299,7 +306,7 @@ class Mqtt {
// settings, copied over
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 bool mqtt_retain_;
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
// 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);
// update selTemp now, readback from monitor telegram takes a while
if (mode == HeatingCircuit::Mode::AUTO) {
has_update(hc->selTemp, (int16_t)(temperature * factor));
// read_command(0x08, 0x18); // UBAMonitorFast
}
return true;
}
LOG_DEBUG("temperature mode %d not found", mode);
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;
}
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") {
// n=1 = EMSESP::system_.HEALTHCHECK_NO_BUS
// 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 "memory"
// #define EMSESP_DEBUG_DEFAULT "coldshot"
#define EMSESP_DEBUG_DEFAULT "custom"
// #define EMSESP_DEBUG_DEFAULT "custom"
// #define EMSESP_DEBUG_DEFAULT "scheduler"
// #define EMSESP_DEBUG_DEFAULT "heat_exchange"
// #define EMSESP_DEBUG_DEFAULT "ls"
// #define EMSESP_DEBUG_DEFAULT "upload"
#define EMSESP_DEBUG_DEFAULT "hpmode"
#ifndef EMSESP_DEBUG_DEFAULT
#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());
}
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;
} else {
if (entityItem.value_type == DeviceValueType::BOOL) {