This commit is contained in:
MichaelDvP
2024-11-07 18:37:23 +01:00
24 changed files with 266 additions and 232 deletions

View File

@@ -533,7 +533,7 @@ void AnalogSensor::publish_values(const bool force) {
char val_obj[50];
char val_cond[95];
if (Mqtt::is_nested()) {
snprintf(val_obj, sizeof(val_obj), "value_json['%02d'].value", sensor.gpio()); // TODO change for Domoticz
snprintf(val_obj, sizeof(val_obj), "value_json['%02d']['value']", sensor.gpio());
snprintf(val_cond, sizeof(val_cond), "value_json['%02d'] is defined and %s is defined", sensor.gpio(), val_obj);
} else {
snprintf(val_obj, sizeof(val_obj), "value_json['%s']", sensor.name().c_str());

View File

@@ -379,17 +379,6 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha
return CommandRet::NOT_ALLOWED; // command not allowed
}
// build up the log string for reporting back
// We send the log message as Warning so it appears in the log (debug is only enabled when compiling with DEBUG)
std::string ro = EMSESP::system_.readonly_mode() ? "[readonly] " : "";
auto description = Helpers::translated_word(cf->description_);
char info_s[100];
if (strlen(description)) {
snprintf(info_s, sizeof(info_s), "%s/%s (%s)", dname, cmd, description);
} else {
snprintf(info_s, sizeof(info_s), "%s/%s", dname, cmd);
}
// call the function based on command function type
// commands return true or false only (bool)
uint8_t return_code = CommandRet::OK;
@@ -406,7 +395,7 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha
}
}
// report back. If not OK show output from error, otherwise return the HTTP code
// report back. If not OK show output from error, otherwise return the error code
if (return_code != CommandRet::OK) {
char error[100];
if (single_command) {
@@ -418,6 +407,16 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha
output["message"] = error;
LOG_WARNING(error);
} else {
// build up the log string for reporting back
// We send the log message as Warning so it appears in the log (debug is only enabled when compiling with DEBUG)
std::string ro = EMSESP::system_.readonly_mode() ? "[readonly] " : "";
auto description = Helpers::translated_word(cf->description_);
char info_s[100];
if (strlen(description)) {
snprintf(info_s, sizeof(info_s), "%s/%s (%s)", dname, cmd, description);
} else {
snprintf(info_s, sizeof(info_s), "%s/%s", dname, cmd);
}
if (single_command) {
// log as DEBUG (TRACE) regardless if compiled with EMSESP_DEBUG
logger_.debug("%sCalled command %s", ro.c_str(), info_s);

View File

@@ -52,11 +52,16 @@ using string_vector = std::vector<const char *>;
#define F_(string_name) (__pstr__##string_name)
#define FL_(list_name) (__pstr__L_##list_name)
#if defined(EMSESP_TEST) || defined(EMSESP_EN_ONLY)
// In testing just take one language (en) to save on Flash space
#if defined(EMSESP_TEST)
// in Test mode use two languages (en & de) to save flash memory needed for the tests
#define MAKE_WORD_TRANSLATION(list_name, en, de, ...) static const char * const __pstr__L_##list_name[] = {de, nullptr};
#define MAKE_TRANSLATION(list_name, shortname, en, de, ...) static const char * const __pstr__L_##list_name[] = {shortname, de, nullptr};
#elif defined(EMSESP_EN_ONLY)
// EN only
#define MAKE_WORD_TRANSLATION(list_name, en, ...) static const char * const __pstr__L_##list_name[] = {en, nullptr};
#define MAKE_TRANSLATION(list_name, shortname, en, ...) static const char * const __pstr__L_##list_name[] = {shortname, en, nullptr};
#elif defined(EMSESP_DE_ONLY)
// EN + DE
#define MAKE_WORD_TRANSLATION(list_name, en, de, ...) static const char * const __pstr__L_##list_name[] = {de, nullptr};
#define MAKE_TRANSLATION(list_name, shortname, en, de, ...) static const char * const __pstr__L_##list_name[] = {shortname, de, nullptr};
#else

View File

@@ -4912,7 +4912,7 @@ void Thermostat::register_device_values_dhw(std::shared_ptr<Thermostat::DhwCircu
DeviceValueUOM::MINUTES,
MAKE_CF_CB(set_wwchargeduration));
register_device_value(tag, &dhw->wwCharge_, DeviceValueType::BOOL, FL_(wwCharge), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwcharge));
register_device_value(tag, &dhw->wwExtra_, DeviceValueType::UINT8, FL_(wwExtra), DeviceValueUOM::DEGREES);
register_device_value(tag, &dhw->wwExtra_, DeviceValueType::BOOL, FL_(wwExtra), DeviceValueUOM::NONE);
register_device_value(tag, &dhw->wwDisinfecting_, DeviceValueType::BOOL, FL_(wwDisinfecting), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwDisinfect));
register_device_value(
tag, &dhw->wwDisinfectDay_, DeviceValueType::ENUM, FL_(enum_dayOfWeek), FL_(wwDisinfectDay), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwDisinfectDay));

View File

@@ -910,14 +910,14 @@ std::string EMSESP::pretty_telegram(std::shared_ptr<const Telegram> telegram) {
std::string str;
str.reserve(200);
if (telegram->operation == Telegram::Operation::RX_READ) {
str = src_name + "(" + Helpers::hextoa(src) + ") -R-> " + dest_name + "(" + Helpers::hextoa(dest) + "), " + type_name + "("
str = src_name + "(" + Helpers::hextoa(src) + ") -> " + dest_name + "(" + Helpers::hextoa(dest) + "), R, " + type_name + "("
+ Helpers::hextoa(telegram->type_id) + "), length: " + Helpers::itoa(telegram->message_data[0])
+ ((telegram->message_length > 1) ? ", data: " + Helpers::data_to_hex(telegram->message_data + 1, telegram->message_length - 1) : "");
} else if (telegram->dest == 0) {
str = src_name + "(" + Helpers::hextoa(src) + ") -B-> " + dest_name + "(" + Helpers::hextoa(dest) + "), " + type_name + "("
str = src_name + "(" + Helpers::hextoa(src) + ") -> " + dest_name + "(" + Helpers::hextoa(dest) + "), B, " + type_name + "("
+ Helpers::hextoa(telegram->type_id) + "), data: " + telegram->to_string_message();
} else {
str = src_name + "(" + Helpers::hextoa(src) + ") -W-> " + dest_name + "(" + Helpers::hextoa(dest) + "), " + type_name + "("
str = src_name + "(" + Helpers::hextoa(src) + ") -> " + dest_name + "(" + Helpers::hextoa(dest) + "), W, " + type_name + "("
+ Helpers::hextoa(telegram->type_id) + "), data: " + telegram->to_string_message();
}
@@ -1016,13 +1016,14 @@ void EMSESP::process_version(std::shared_ptr<const Telegram> telegram) {
// some devices store the protocol type (HT3, Buderus) in the last byte
uint8_t brand;
if (telegram->message_length >= 10) {
brand = EMSdevice::decode_brand(telegram->message_data[9]); // TODO should be offset + 9?
brand = EMSdevice::decode_brand(telegram->message_data[9]);
} else {
brand = EMSdevice::Brand::NO_BRAND; // unknown
}
// add it - will be overwritten if device already exists
(void)add_device(device_id, product_id, version, brand);
// request the deviceName from telegram 0x01
send_read_request(EMSdevice::EMS_TYPE_NAME, device_id, 27);
}

View File

@@ -603,6 +603,10 @@ bool Helpers::value2bool(const char * value, bool & value_b) {
return true; // is a bool
}
#ifdef EMSESP_STANDALONE
emsesp::EMSESP::logger().debug("Error. value2bool: %s is not a boolean", value);
#endif
return false; // not a bool
}
@@ -764,7 +768,7 @@ uint8_t Helpers::count_items(const char * const ** list) {
// if force_en is true always take the EN non-translated word
const char * Helpers::translated_word(const char * const * strings, const bool force_en) {
uint8_t language_index = EMSESP::system_.language_index();
uint8_t index = 0;
uint8_t index = 0; // default en
if (!strings) {
return ""; // no translations
@@ -774,6 +778,7 @@ const char * Helpers::translated_word(const char * const * strings, const bool f
if (!force_en && (Helpers::count_items(strings) >= language_index + 1 && strlen(strings[language_index]))) {
index = language_index;
}
return strings[index];
}

View File

@@ -934,14 +934,13 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
char config_topic[70];
snprintf(config_topic, sizeof(config_topic), "%s/%s_%s/config", mqtt_basename_.c_str(), device_name, entity_with_tag);
bool add_ha_classes = true; // default we'll add the "unit_of_meas", "stat_cla" and "dev_cla" attributes
// create the topic
// depending on the type and whether the device entity is writable (a command)
// depending on the type and whether the device entity is writable (i.e. a command)
// https://developers.home-assistant.io/docs/core/entity
char topic[MQTT_TOPIC_MAX_SIZE];
// if it's a command then we can use Number, Switch, Select or Text. Otherwise stick to Sensor
topic[0] = '\0'; // nullify, making it empty
if (has_cmd) {
// if it's a command then we can use Number, Switch, Select or Text. Otherwise stick to Sensor
switch (type) {
case DeviceValueType::INT8:
case DeviceValueType::UINT8:
@@ -950,45 +949,40 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
case DeviceValueType::UINT24:
case DeviceValueType::UINT32:
// number - https://www.home-assistant.io/integrations/number.mqtt
// older Domoticz does not support number, use sensor
// older Domoticz does not support number, will default to Sensor
if (discovery_type() == discoveryType::HOMEASSISTANT || discovery_type() == discoveryType::DOMOTICZ_LATEST) {
snprintf(topic, sizeof(topic), "number/%s", config_topic);
} else {
snprintf(topic, sizeof(topic), "sensor/%s", config_topic);
}
break;
case DeviceValueType::BOOL:
// switch - https://www.home-assistant.io/integrations/switch.mqtt
snprintf(topic, sizeof(topic), "switch/%s", config_topic);
add_ha_classes = false;
break;
case DeviceValueType::ENUM:
snprintf(topic, sizeof(topic), "select/%s", config_topic);
add_ha_classes = false;
break;
case DeviceValueType::CMD: // hardcoded commands are always ENUMS
// select - https://www.home-assistant.io/integrations/select.mqtt
snprintf(topic, sizeof(topic), "select/%s", config_topic);
break;
case DeviceValueType::CMD:
if (uom == DeviceValueUOM::NONE) {
snprintf(topic, sizeof(topic), "select/%s", config_topic);
snprintf(topic, sizeof(topic), "select/%s", config_topic); // hardcoded commands are always ENUMS
} else if (discovery_type() == discoveryType::HOMEASSISTANT || discovery_type() == discoveryType::DOMOTICZ_LATEST) {
snprintf(topic, sizeof(topic), "number/%s", config_topic);
} else {
snprintf(topic, sizeof(topic), "sensor/%s", config_topic);
}
add_ha_classes = false;
break;
case DeviceValueType::STRING:
// text - https://www.home-assistant.io/integrations/text.mqtt
snprintf(topic, sizeof(topic), "text/%s", config_topic); // e.g. set_datetime, set_holiday, set_wwswitchtime
add_ha_classes = false;
// Domoticz does not support text, will default to Sensor
if (discovery_type() == discoveryType::HOMEASSISTANT) {
snprintf(topic, sizeof(topic), "text/%s", config_topic); // e.g. set_datetime, set_holiday, set_wwswitchtime
}
break;
default:
// plain old sensor
snprintf(topic, sizeof(topic), "sensor/%s", config_topic);
break;
}
} else {
// it is not a command and a read-only sensor. Use then either sensor or binary_sensor
}
// if at this point we don't have a topic created yet, create a default sensor one. We always need a topic.
if (!strnlen(topic, sizeof(topic))) {
snprintf(topic, sizeof(topic), (type == DeviceValueType::BOOL) ? "binary_sensor/%s" : "sensor/%s", config_topic); // binary sensor (for booleans)
}
@@ -1054,21 +1048,6 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
doc["max"] = dv_set_max;
snprintf(sample_val, sizeof(sample_val), "%i", dv_set_min);
}
// set icons
// since these don't have a device class we need to add the icon ourselves
switch (uom) {
case DeviceValueUOM::DEGREES:
case DeviceValueUOM::DEGREES_R:
case DeviceValueUOM::K:
doc["ic"] = F_(icondegrees);
break;
case DeviceValueUOM::PERCENT:
doc["ic"] = F_(iconpercent);
break;
default:
break;
}
}
// friendly name = <tag> <name>
@@ -1129,26 +1108,23 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
// Domoticz doesn't support value templates, so we just use the value directly
// Also omit the uom and other state classes
doc["val_tpl"] = (std::string) "{{" + val_obj + "}}";
// add_ha_classes = false; // don't add the classes, categories of uom (dev_cla, stat_cla)
}
}
// Add the state class, device class and sometimes the icon.
// Used only for read-only sensors like Sensor and Binary Sensor but also Numbers
if (add_ha_classes) {
// first set the catagory for System entities
// https://github.com/emsesp/EMS-ESP32/discussions/1459#discussioncomment-7694873
if (device_type == EMSdevice::DeviceType::SYSTEM) {
doc["ent_cat"] = "diagnostic";
}
add_ha_uom(doc.as<JsonObject>(), type, uom, entity); // add the UoM, device and state class
// Add the state class, device class and an optional icon based on the uom
// first set the catagory for System entities
// https://github.com/emsesp/EMS-ESP32/discussions/1459#discussioncomment-7694873
if (device_type == EMSdevice::DeviceType::SYSTEM) {
doc["ent_cat"] = "diagnostic"; // instead of config
}
add_ha_uom(doc.as<JsonObject>(), type, uom, entity);
doc["dev"] = dev_json;
return queue_ha(topic, doc.as<JsonObject>());
}
// Add the state class, device class and an optional icon based on the uom
void Mqtt::add_ha_uom(JsonObject doc, const uint8_t type, const uint8_t uom, const char * entity) {
const char * dc_ha = "dev_cla"; // device class
const char * sc_ha = "stat_cla"; // state class
@@ -1169,16 +1145,19 @@ void Mqtt::add_ha_uom(JsonObject doc, const uint8_t type, const uint8_t uom, con
}
// set state and device class
// also icon, when there is no device class that sets one
switch (uom) {
case DeviceValueUOM::DEGREES:
case DeviceValueUOM::DEGREES_R:
case DeviceValueUOM::K:
doc[sc_ha] = F_(measurement);
doc[dc_ha] = "temperature";
doc["ic"] = F_(icondegrees); // icon
break;
case DeviceValueUOM::PERCENT:
doc[sc_ha] = F_(measurement);
doc[dc_ha] = "power_factor";
doc["ic"] = F_(iconpercent); // icon
break;
case DeviceValueUOM::SECONDS:
case DeviceValueUOM::MINUTES:
@@ -1256,8 +1235,6 @@ void Mqtt::add_ha_uom(JsonObject doc, const uint8_t type, const uint8_t uom, con
}
bool Mqtt::publish_ha_climate_config(const int8_t tag, const bool has_roomtemp, const bool remove, const int16_t min, const uint32_t max) {
// TODO: check if Domoticz supports climate via MQTT discovery, otherwise exit this function if (discovery_type() != discoveryType::HOMEASSISTANT
uint8_t hc_num = tag;
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];

View File

@@ -52,12 +52,17 @@
namespace emsesp {
// Languages supported. Note: the order is important and must match locale_translations.h
#if defined(EMSESP_TEST) || defined(EMSESP_EN_ONLY)
// in Debug mode use one language (en) to save flash memory needed for the tests
// Languages supported. Note: the order is important
// and must match locale_translations.h and common.h
#if defined(EMSESP_TEST)
// in Test mode use two languages (en & de) to save flash memory needed for the tests
const char * const languages[] = {EMSESP_LOCALE_EN, EMSESP_LOCALE_DE};
#elif defined(EMSESP_EN_ONLY)
// EN only
const char * const languages[] = {EMSESP_LOCALE_EN};
#elif defined(EMSESP_DE_ONLY)
const char * const languages[] = {EMSESP_LOCALE_DE};
// EN + DE
const char * const languages[] = {EMSESP_LOCALE_EN, EMSESP_LOCALE_DE};
#else
const char * const languages[] = {EMSESP_LOCALE_EN,
EMSESP_LOCALE_DE,
@@ -92,7 +97,7 @@ uint8_t System::language_index() {
return i;
}
}
return 0; // EN
return 0; // EN only
}
// send raw to ems

View File

@@ -482,7 +482,7 @@ void TemperatureSensor::publish_values(const bool force) {
char val_obj[70];
char val_cond[170];
if (Mqtt::is_nested()) {
snprintf(val_obj, sizeof(val_obj), "value_json['%s'].temp", sensor.id().c_str()); // TODO change for Domoticz
snprintf(val_obj, sizeof(val_obj), "value_json['%s']['temp']", sensor.id().c_str());
snprintf(val_cond, sizeof(val_cond), "value_json['%s'] is defined and %s is defined", sensor.id().c_str(), val_obj);
} else {
snprintf(val_obj, sizeof(val_obj), "value_json['%s']", sensor.name().c_str());

View File

@@ -307,7 +307,15 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
}
shell.printfln("Testing Adding a device (product_id %d), with all values...", id2);
test("add", id1, id2);
shell.invoke_command("show values");
shell.invoke_command("show devices");
ok = true;
}
// set the language
if (command == "locale") {
shell.printfln("Testing setting locale to %s", id1_s.c_str());
EMSESP::system_.locale(id1_s.c_str());
shell.invoke_command("show");
ok = true;
}

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.7.1-dev.3"
#define EMSESP_APP_VERSION "3.7.1-dev.4"