This commit is contained in:
MichaelDvP
2022-12-07 07:41:25 +01:00
20 changed files with 97 additions and 45 deletions

View File

@@ -5,7 +5,6 @@
## **IMPORTANT! BREAKING CHANGES**
- When upgrading to v3.5 for the first time from v3.4 on a BBQKees Gateway board you will need to use the [EMS-EPS Flasher](https://github.com/emsesp/EMS-ESP-Flasher/releases) to correctly re-partition the flash. Make sure you backup the settings and customizations from the WebUI (System->Upload/Download) and restore after the upgrade.
- Since 3.5.0b11 we added support for multiple EMS-ESPs [#759] so they can co-exist with MQTT Discovery. The mqtt base is now included in each of the entity names to make them unique. Unfortunately this means in Home Assistant the entity IDs have also been renamed (for example from `number.boiler_burnminperiod` to `number.ems_esp_boiler_burnminperiod`) and any dashboards and automation scripts need to be adjusted accordingly.
## Added

View File

@@ -15,12 +15,12 @@
"@mui/material": "^5.10.16",
"@table-library/react-table-library": "4.0.23",
"@types/lodash": "^4.14.191",
"@types/node": "^18.11.10",
"@types/node": "^18.11.11",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@types/react-router-dom": "^5.3.3",
"async-validator": "^4.2.5",
"axios": "^1.2.0",
"axios": "^1.2.1",
"http-proxy-middleware": "^2.0.6",
"jwt-decode": "^3.1.2",
"lodash": "^4.17.21",
@@ -4017,9 +4017,9 @@
"integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA=="
},
"node_modules/@types/node": {
"version": "18.11.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.10.tgz",
"integrity": "sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ=="
"version": "18.11.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.11.tgz",
"integrity": "sha512-KJ021B1nlQUBLopzZmPBVuGU9un7WJd/W4ya7Ih02B4Uwky5Nja0yGYav2EfYIk0RR2Q9oVhf60S2XR1BCWJ2g=="
},
"node_modules/@types/parse-json": {
"version": "4.0.0",
@@ -5030,9 +5030,9 @@
}
},
"node_modules/axios": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.2.0.tgz",
"integrity": "sha512-zT7wZyNYu3N5Bu0wuZ6QccIf93Qk1eV8LOewxgjOZFd2DenOs98cJ7+Y6703d0wkaXGY6/nZd4EweJaHz9uzQw==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz",
"integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==",
"dependencies": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
@@ -20230,9 +20230,9 @@
"integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA=="
},
"@types/node": {
"version": "18.11.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.10.tgz",
"integrity": "sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ=="
"version": "18.11.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.11.tgz",
"integrity": "sha512-KJ021B1nlQUBLopzZmPBVuGU9un7WJd/W4ya7Ih02B4Uwky5Nja0yGYav2EfYIk0RR2Q9oVhf60S2XR1BCWJ2g=="
},
"@types/parse-json": {
"version": "4.0.0",
@@ -20998,9 +20998,9 @@
"integrity": "sha512-u2MVsXfew5HBvjsczCv+xlwdNnB1oQR9HlAcsejZttNjKKSkeDNVwB1vMThIUIFI9GoT57Vtk8iQLwqOfAkboA=="
},
"axios": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.2.0.tgz",
"integrity": "sha512-zT7wZyNYu3N5Bu0wuZ6QccIf93Qk1eV8LOewxgjOZFd2DenOs98cJ7+Y6703d0wkaXGY6/nZd4EweJaHz9uzQw==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz",
"integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==",
"requires": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",

View File

@@ -11,12 +11,12 @@
"@mui/material": "^5.10.16",
"@table-library/react-table-library": "4.0.23",
"@types/lodash": "^4.14.191",
"@types/node": "^18.11.10",
"@types/node": "^18.11.11",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@types/react-router-dom": "^5.3.3",
"async-validator": "^4.2.5",
"axios": "^1.2.0",
"axios": "^1.2.1",
"http-proxy-middleware": "^2.0.6",
"jwt-decode": "^3.1.2",
"lodash": "^4.17.21",

View File

@@ -162,6 +162,10 @@ const MqttSettingsForm: FC = () => {
</ValidatedTextField>
</Grid>
</Grid>
<BlockFormControlLabel
control={<Checkbox name="multiple_instances" checked={data.multiple_instances} onChange={updateFormValue} />}
label={LL.MQTT_MULTIPLE_INSTANCES()}
/>
<BlockFormControlLabel
control={<Checkbox name="clean_session" checked={data.clean_session} onChange={updateFormValue} />}
label={LL.MQTT_CLEAN_SESSION()}

View File

@@ -248,6 +248,7 @@ const de: Translation = {
MQTT_INT_HEARTBEAT: 'Heartbeat',
MQTT_QUEUE: 'MQTT Queue',
DEFAULT: 'Standard',
MQTT_MULTIPLE_INSTANCES: 'Enable Multiple Instances of EMS-ESP',
MQTT_CLEAN_SESSION: 'Setze `Clean Session`',
MQTT_RETAIN_FLAG: 'Setze `Retain flag` immer',
INACTIVE: 'Inaktiv',

View File

@@ -248,6 +248,7 @@ const en: Translation = {
MQTT_INT_HEARTBEAT: 'Heartbeat',
MQTT_QUEUE: 'MQTT Queue',
DEFAULT: 'Default',
MQTT_MULTIPLE_INSTANCES: 'Enable Multiple Instances of EMS-ESP',
MQTT_CLEAN_SESSION: 'Set Clean Session',
MQTT_RETAIN_FLAG: 'Always set Retain flag',
INACTIVE: 'Inactive',

View File

@@ -248,6 +248,7 @@ const nl: Translation = {
MQTT_INT_HEARTBEAT: 'Heartbeat',
MQTT_QUEUE: 'MQTT Queue',
DEFAULT: 'Default',
MQTT_MULTIPLE_INSTANCES: 'Enable Multiple Instances of EMS-ESP',
MQTT_CLEAN_SESSION: 'Clean Session aan',
MQTT_RETAIN_FLAG: 'Retain flag aan',
INACTIVE: 'Inactief',

View File

@@ -248,6 +248,7 @@ const no: Translation = {
MQTT_INT_HEARTBEAT: 'Heartbeat',
MQTT_QUEUE: 'MQTT Queue',
DEFAULT: 'Standard',
MQTT_MULTIPLE_INSTANCES: 'Enable Multiple Instances of EMS-ESP',
MQTT_CLEAN_SESSION: 'Benytt Clean Session',
MQTT_RETAIN_FLAG: 'Alltid sett Retain flag',
INACTIVE: 'Innaktiv',

View File

@@ -248,6 +248,7 @@ const pl: BaseTranslation = {
MQTT_INT_HEARTBEAT: 'Heartbeat',
MQTT_QUEUE: 'Kolejka MQTT',
DEFAULT: '{{Pozostałe|Domyślna|}}',
MQTT_MULTIPLE_INSTANCES: 'Enable Multiple Instances of EMS-ESP',
MQTT_CLEAN_SESSION: 'Ustawiaj flagę "Clean session"',
MQTT_RETAIN_FLAG: 'Ustawiaj flagę "Retain"',
INACTIVE: 'nieaktywny',

View File

@@ -248,6 +248,7 @@ const se: Translation = {
MQTT_INT_HEARTBEAT: 'Heartbeat',
MQTT_QUEUE: 'MQTT Queue',
DEFAULT: 'Standard',
MQTT_MULTIPLE_INSTANCES: 'Enable Multiple Instances of EMS-ESP',
MQTT_CLEAN_SESSION: 'Använd "Clean Session"-flaggan',
MQTT_RETAIN_FLAG: 'Använd "Always Retain"-flaggan',
INACTIVE: 'Inaktiv',

View File

@@ -29,6 +29,7 @@ export interface MqttSettings {
client_id: string;
keep_alive: number;
clean_session: boolean;
multiple_instances: boolean;
publish_time_boiler: number;
publish_time_thermostat: number;
publish_time_solar: number;

View File

@@ -145,15 +145,16 @@ void MqttSettingsService::configureMqtt() {
}
void MqttSettings::read(MqttSettings & settings, JsonObject & root) {
root["enabled"] = settings.enabled;
root["host"] = settings.host;
root["port"] = settings.port;
root["base"] = settings.base;
root["username"] = settings.username;
root["password"] = settings.password;
root["client_id"] = settings.clientId;
root["keep_alive"] = settings.keepAlive;
root["clean_session"] = settings.cleanSession;
root["enabled"] = settings.enabled;
root["host"] = settings.host;
root["port"] = settings.port;
root["base"] = settings.base;
root["username"] = settings.username;
root["password"] = settings.password;
root["client_id"] = settings.clientId;
root["keep_alive"] = settings.keepAlive;
root["clean_session"] = settings.cleanSession;
root["multiple_instances"] = settings.multiple_instances;
// added by proddy for EMS-ESP
root["publish_time_boiler"] = settings.publish_time_boiler;
@@ -177,17 +178,19 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting
MqttSettings newSettings = {};
bool changed = false;
newSettings.enabled = root["enabled"] | FACTORY_MQTT_ENABLED;
newSettings.host = root["host"] | FACTORY_MQTT_HOST;
newSettings.port = root["port"] | FACTORY_MQTT_PORT;
newSettings.base = root["base"] | FACTORY_MQTT_BASE;
newSettings.username = root["username"] | FACTORY_MQTT_USERNAME;
newSettings.password = root["password"] | FACTORY_MQTT_PASSWORD;
newSettings.clientId = root["client_id"] | FACTORY_MQTT_CLIENT_ID;
newSettings.keepAlive = root["keep_alive"] | FACTORY_MQTT_KEEP_ALIVE;
newSettings.cleanSession = root["clean_session"] | FACTORY_MQTT_CLEAN_SESSION;
newSettings.mqtt_qos = root["mqtt_qos"] | EMSESP_DEFAULT_MQTT_QOS;
newSettings.mqtt_retain = root["mqtt_retain"] | EMSESP_DEFAULT_MQTT_RETAIN;
newSettings.enabled = root["enabled"] | FACTORY_MQTT_ENABLED;
newSettings.host = root["host"] | FACTORY_MQTT_HOST;
newSettings.port = root["port"] | FACTORY_MQTT_PORT;
newSettings.base = root["base"] | FACTORY_MQTT_BASE;
newSettings.username = root["username"] | FACTORY_MQTT_USERNAME;
newSettings.password = root["password"] | FACTORY_MQTT_PASSWORD;
newSettings.clientId = root["client_id"] | FACTORY_MQTT_CLIENT_ID;
newSettings.keepAlive = root["keep_alive"] | FACTORY_MQTT_KEEP_ALIVE;
newSettings.cleanSession = root["clean_session"] | FACTORY_MQTT_CLEAN_SESSION;
newSettings.multiple_instances = root["multiple_instances"] | FACTORY_MQTT_MULTIPLE_INSTANCES;
newSettings.mqtt_qos = root["mqtt_qos"] | EMSESP_DEFAULT_MQTT_QOS;
newSettings.mqtt_retain = root["mqtt_retain"] | EMSESP_DEFAULT_MQTT_RETAIN;
newSettings.publish_time_boiler = root["publish_time_boiler"] | EMSESP_DEFAULT_PUBLISH_TIME;
newSettings.publish_time_thermostat = root["publish_time_thermostat"] | EMSESP_DEFAULT_PUBLISH_TIME;

View File

@@ -57,6 +57,10 @@ static String generateClientId() {
#define FACTORY_MQTT_MAX_TOPIC_LENGTH 128
#endif
#ifndef FACTORY_MQTT_MULTIPLE_INSTANCES
#define FACTORY_MQTT_MULTIPLE_INSTANCES false
#endif
class MqttSettings {
public:
// host and port - if enabled
@@ -75,6 +79,9 @@ class MqttSettings {
uint16_t keepAlive;
bool cleanSession;
// multiple instances
bool multiple_instances;
// proddy EMS-ESP specific
String base;
uint16_t publish_time_boiler;

View File

@@ -81,6 +81,7 @@ class DummySettings {
String username = "";
uint16_t keepAlive = 60;
bool cleanSession = false;
bool multiple_instances = false;
uint16_t publish_time_boiler = 10;
uint16_t publish_time_thermostat = 10;

View File

@@ -219,6 +219,7 @@ mqtt_settings = {
client_id: 'ems-esp',
keep_alive: 60,
clean_session: true,
multiple_instances: false,
publish_time_boiler: 10,
publish_time_thermostat: 10,
publish_time_solar: 10,

View File

@@ -451,7 +451,12 @@ void AnalogSensor::publish_values(const bool force) {
}
config["val_tpl"] = str;
snprintf(str, sizeof(str), "%s_analogsensor_%d", Mqtt::basename().c_str(), sensor.gpio());
if (Mqtt::multiple_instances()) {
snprintf(str, sizeof(str), "%s_analogsensor_%d", Mqtt::basename().c_str(), sensor.gpio());
} else {
snprintf(str, sizeof(str), "analogsensor_%d", sensor.gpio());
}
config["object_id"] = str;
config["uniq_id"] = str; // same as object_id

View File

@@ -518,15 +518,20 @@ void DallasSensor::publish_values(const bool force) {
}
config["val_tpl"] = str;
snprintf(str, sizeof(str), "%s_dallassensor_%s", Mqtt::basename().c_str(), sensor.id().c_str());
if (Mqtt::multiple_instances()) {
snprintf(str, sizeof(str), "%s_dallassensor_%s", Mqtt::basename().c_str(), sensor.id().c_str());
} else {
snprintf(str, sizeof(str), "dallassensor_%s", sensor.id().c_str());
}
config["object_id"] = str;
config["uniq_id"] = str; // same as object_id
snprintf(str, sizeof(str), "%s", sensor.name().c_str());
config["name"] = str;
JsonObject dev = config.createNestedObject("dev");
JsonArray ids = dev.createNestedArray("ids");
JsonObject dev = config.createNestedObject("dev");
JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp");
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];

View File

@@ -38,6 +38,7 @@ uint32_t Mqtt::publish_time_sensor_;
uint32_t Mqtt::publish_time_other_;
uint32_t Mqtt::publish_time_heartbeat_;
bool Mqtt::mqtt_enabled_;
bool Mqtt::multiple_instances_;
bool Mqtt::ha_enabled_;
uint8_t Mqtt::nested_format_;
std::string Mqtt::discovery_prefix_;
@@ -429,6 +430,7 @@ void Mqtt::load_settings() {
publish_single2cmd_ = mqttSettings.publish_single2cmd;
send_response_ = mqttSettings.send_response;
discovery_prefix_ = mqttSettings.discovery_prefix.c_str();
multiple_instances_ = mqttSettings.multiple_instances;
// convert to milliseconds
publish_time_boiler_ = mqttSettings.publish_time_boiler * 1000;
@@ -606,10 +608,10 @@ void Mqtt::ha_status() {
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
char uniq[70];
snprintf(uniq, sizeof(uniq), "%s_status", mqtt_basename_.c_str());
snprintf(uniq, sizeof(uniq), "%s_status", mqtt_basename_.c_str()); // always use basename
doc["uniq_id"] = uniq;
doc["object_id"] = uniq;
doc["~"] = mqtt_base_; // default ems-esp
doc["~"] = mqtt_base_;
// doc["avty_t"] = "~/status"; // commented out, as it causes errors in HA sometimes
// doc["json_attr_t"] = "~/heartbeat"; // store also as HA attributes
doc["stat_t"] = "~/status";
@@ -796,6 +798,7 @@ void Mqtt::process_queue() {
if (message->topic.find(discovery_prefix_) == 0) {
strlcpy(topic, message->topic.c_str(), sizeof(topic)); // leave topic as it is
} else {
// it's a discovery topic, added the mqtt base to the topic path
snprintf(topic, MQTT_TOPIC_MAX_SIZE, "%s/%s", mqtt_base_.c_str(), message->topic.c_str()); // uses base
}
@@ -978,7 +981,12 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
// build unique identifier which will be used in the topic, also used as object_id
char uniq_id[70];
snprintf(uniq_id, sizeof(uniq_id), "%s_%s_%s", mqtt_basename_.c_str(), device_name, entity_with_tag);
if (multiple_instances_) {
// prefix base name to each uniq_id
snprintf(uniq_id, sizeof(uniq_id), "%s_%s_%s", mqtt_basename_.c_str(), device_name, entity_with_tag);
} else {
snprintf(uniq_id, sizeof(uniq_id), "%s_%s", device_name, entity_with_tag);
}
// build a config topic that will be prefix onto a HA type (e.g. number, switch)
// e.g. homeassistant/number/ems-esp/thermostat_hc1_manualtemp
@@ -1280,7 +1288,13 @@ void Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp,
hc_mode_s);
snprintf(name_s, sizeof(name_s), "Hc%d", hc_num);
snprintf(uniq_id_s, sizeof(uniq_id_s), "%s_thermostat_hc%d", mqtt_basename_.c_str(), hc_num); // add basename
if (Mqtt::multiple_instances()) {
snprintf(uniq_id_s, sizeof(uniq_id_s), "%s_thermostat_hc%d", mqtt_basename_.c_str(), hc_num); // add basename
} else {
snprintf(uniq_id_s, sizeof(uniq_id_s), "thermostat_hc%d", hc_num); // backward compatible with v3.4
}
snprintf(temp_cmd_s, sizeof(temp_cmd_s), "~/thermostat/hc%d/seltemp", hc_num);
snprintf(mode_cmd_s, sizeof(temp_cmd_s), "~/thermostat/hc%d/mode", hc_num);

View File

@@ -180,6 +180,10 @@ class Mqtt {
return nested_format_ == NestedFormat::NESTED;
}
static bool multiple_instances() {
return multiple_instances_;
}
static void nested_format(uint8_t nested_format) {
nested_format_ = nested_format;
}
@@ -317,6 +321,7 @@ class Mqtt {
static bool mqtt_enabled_;
static bool ha_enabled_;
static uint8_t nested_format_;
static bool multiple_instances_;
static std::string discovery_prefix_;
static bool publish_single_;
static bool publish_single2cmd_;

View File

@@ -1147,6 +1147,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
node["client id"] = settings.clientId;
node["keep alive"] = settings.keepAlive;
node["clean session"] = settings.cleanSession;
node["multiple instances"] = settings.multiple_instances;
node["base"] = settings.base;
node["discovery prefix"] = settings.discovery_prefix;
node["nested format"] = settings.nested_format;