Multi-language/I18n support #22

This commit is contained in:
Proddy
2022-08-24 21:50:19 +02:00
parent 763337db3f
commit 1a4ce643fc
84 changed files with 5506 additions and 4196 deletions

View File

@@ -51,6 +51,7 @@ std::string EMSdevice::tag_to_mqtt(uint8_t tag) {
return read_flash_string(DeviceValue::DeviceValueTAG_mqtt[tag]);
}
// convert UOM to a string - these don't need translating
std::string EMSdevice::uom_to_string(uint8_t uom) {
if (EMSESP::system_.fahrenheit() && (uom == DeviceValueUOM::DEGREES || uom == DeviceValueUOM::DEGREES_R)) {
return read_flash_string(DeviceValue::DeviceValueUOM_s[DeviceValueUOM::FAHRENHEIT]);
@@ -107,7 +108,7 @@ std::string EMSdevice::device_type_2_device_name(const uint8_t device_type) {
case DeviceType::GATEWAY:
return read_flash_string(F_(gateway));
default:
return read_flash_string(F_(unknown));
return Helpers::translated_word(FL_(unknown));
}
}
@@ -258,7 +259,8 @@ bool EMSdevice::has_tag(const uint8_t tag) const {
// called from the command 'entities'
void EMSdevice::list_device_entries(JsonObject & output) const {
for (const auto & dv : devicevalues_) {
if (!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && dv.type != DeviceValueType::CMD && dv.full_name) {
auto fullname = Helpers::translated_fword(dv.fullname);
if (!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && dv.type != DeviceValueType::CMD && fullname) {
// if we have a tag prefix it
char key[50];
if (!EMSdevice::tag_to_mqtt(dv.tag).empty()) {
@@ -270,7 +272,7 @@ void EMSdevice::list_device_entries(JsonObject & output) const {
JsonArray details = output.createNestedArray(key);
// add the full name description
details.add(dv.full_name);
details.add(fullname);
// add uom
if (!uom_to_string(dv.uom).empty() && uom_to_string(dv.uom) != " ") {
@@ -386,23 +388,31 @@ void EMSdevice::register_telegram_type(const uint16_t telegram_type_id, const __
// tag: to be used to group mqtt together, either as separate topics as a nested object
// value_p: pointer to the value from the .h file
// type: one of DeviceValueType
// options: options for enum or a divider for int (e.g. F("10"))
// options: options for enum, which are translated as a list of lists
// options_single: list of names
// numeric_operatpr: to divide or multiply, see DeviceValueNumOps::
// short_name: used in Mqtt as keys
// full_name: used in Web and Console unless empty (nullptr)
// fullname: used in Web and Console unless empty (nullptr) - can be translated
// uom: unit of measure from DeviceValueUOM
// has_cmd: true if this is an associated command
// min: min allowed value
// max: max allowed value
void EMSdevice::register_device_value(uint8_t tag,
void * value_p,
uint8_t type,
const __FlashStringHelper * const * options,
const __FlashStringHelper * short_name,
const __FlashStringHelper * full_name,
uint8_t uom,
bool has_cmd,
int16_t min,
uint16_t max) {
void EMSdevice::add_device_value(uint8_t tag,
void * value_p,
uint8_t type,
const __FlashStringHelper * const ** options,
const __FlashStringHelper * const * options_single,
int8_t numeric_operator,
const __FlashStringHelper * const * name,
uint8_t uom,
const cmd_function_p f,
int16_t min,
uint16_t max) {
bool has_cmd = (f != nullptr);
auto short_name = name[0];
const class __FlashStringHelper * const * fullname = &name[1]; // translations start at index 1
// initialize the device value depending on it's type
if (type == DeviceValueType::STRING) {
*(char *)(value_p) = {'\0'}; // this is important for string functions like strlen() to work later
@@ -420,15 +430,6 @@ void EMSdevice::register_device_value(uint8_t tag,
*(uint8_t *)(value_p) = EMS_VALUE_UINT_NOTSET; // enums behave as uint8_t
}
// count #options
uint8_t options_size = 0;
if (options != nullptr) {
uint8_t i = 0;
while (options[i++]) {
options_size++;
}
}
// determine state
uint8_t state = DeviceValueState::DV_DEFAULT;
@@ -448,28 +449,11 @@ void EMSdevice::register_device_value(uint8_t tag,
}
});
// add the device
devicevalues_.emplace_back(device_type_, tag, value_p, type, options, options_size, short_name, full_name, uom, 0, has_cmd, min, max, state);
}
// function with min and max values
// adds a new command to the command list
void EMSdevice::register_device_value(uint8_t tag,
void * value_p,
uint8_t type,
const __FlashStringHelper * const * options,
const __FlashStringHelper * const * name,
uint8_t uom,
const cmd_function_p f,
int16_t min,
uint16_t max) {
auto short_name = name[0];
auto full_name = name[1];
register_device_value(tag, value_p, type, options, short_name, full_name, uom, (f != nullptr), min, max);
// add the device entity
devicevalues_.emplace_back(device_type_, tag, value_p, type, options, options_single, numeric_operator, short_name, fullname, uom, has_cmd, min, max, state);
// add a new command if it has a function attached
if (f == nullptr) {
if (!has_cmd) {
return;
}
@@ -484,29 +468,109 @@ void EMSdevice::register_device_value(uint8_t tag,
}
// add the command to our library
// cmd is the short_name and the description is the full_name
Command::add(device_type_, short_name, f, full_name, flags);
// cmd is the short_name and the description is the fullname
Command::add(device_type_, short_name, f, Helpers::translated_fword(fullname), flags);
}
// function with no min and max values (set to 0)
// single list of options
void EMSdevice::register_device_value(uint8_t tag,
void * value_p,
uint8_t type,
const __FlashStringHelper * const * options,
const __FlashStringHelper * const * options_single,
const __FlashStringHelper * const * name,
uint8_t uom,
const cmd_function_p f) {
register_device_value(tag, value_p, type, options, name, uom, f, 0, 0);
}
// create a multi-list from the options
add_device_value(tag, value_p, type, nullptr, options_single, 0, name, uom, f, 0, 0);
};
// no associated command function, or min/max values
// single list of options, with no translations, with min and max
void EMSdevice::register_device_value(uint8_t tag,
void * value_p,
uint8_t type,
const __FlashStringHelper * const * options,
const __FlashStringHelper * const * options_single,
const __FlashStringHelper * const * name,
uint8_t uom) {
register_device_value(tag, value_p, type, options, name, uom, nullptr, 0, 0);
uint8_t uom,
const cmd_function_p f,
int16_t min,
uint16_t max) {
// create a multi-list from the options
add_device_value(tag, value_p, type, nullptr, options_single, 0, name, uom, f, min, max);
};
void EMSdevice::register_device_value(uint8_t tag,
void * value_p,
uint8_t type,
int8_t numeric_operator,
const __FlashStringHelper * const * name,
uint8_t uom,
const cmd_function_p f) {
add_device_value(tag, value_p, type, nullptr, nullptr, numeric_operator, name, uom, f, 0, 0);
}
void EMSdevice::register_device_value(uint8_t tag,
void * value_p,
uint8_t type,
int8_t numeric_operator,
const __FlashStringHelper * const * name,
uint8_t uom,
const cmd_function_p f,
int16_t min,
uint16_t max) {
add_device_value(tag, value_p, type, nullptr, nullptr, numeric_operator, name, uom, f, min, max);
}
// no options, no function
void EMSdevice::register_device_value(uint8_t tag, void * value_p, uint8_t type, const __FlashStringHelper * const * name, uint8_t uom, const cmd_function_p f) {
add_device_value(tag, value_p, type, nullptr, nullptr, 0, name, uom, f, 0, 0);
};
// no options, with min/max
void EMSdevice::register_device_value(uint8_t tag,
void * value_p,
uint8_t type,
const __FlashStringHelper * const * name,
uint8_t uom,
const cmd_function_p f,
int16_t min,
uint16_t max) {
add_device_value(tag, value_p, type, nullptr, nullptr, 0, name, uom, f, min, max);
};
// function with min and max values
// adds a new command to the command list
// in this function we separate out the short and long names and take any translations
void EMSdevice::register_device_value(uint8_t tag,
void * value_p,
uint8_t type,
const __FlashStringHelper * const ** options,
const __FlashStringHelper * const * name,
uint8_t uom,
const cmd_function_p f,
int16_t min,
uint16_t max) {
add_device_value(tag, value_p, type, options, nullptr, 0, name, uom, f, min, max);
}
// function with no min and max values (set to 0)
void EMSdevice::register_device_value(uint8_t tag,
void * value_p,
uint8_t type,
const __FlashStringHelper * const ** options,
const __FlashStringHelper * const * name,
uint8_t uom,
const cmd_function_p f) {
add_device_value(tag, value_p, type, options, nullptr, 0, name, uom, f, 0, 0);
}
// no associated command function, or min/max values
void EMSdevice::register_device_value(uint8_t tag,
void * value_p,
uint8_t type,
const __FlashStringHelper * const ** options,
const __FlashStringHelper * const * name,
uint8_t uom) {
add_device_value(tag, value_p, type, options, nullptr, 0, name, uom, nullptr, 0, 0);
}
// check if value is readable via mqtt/api
@@ -572,7 +636,8 @@ void EMSdevice::publish_value(void * value_p) const {
snprintf(topic, sizeof(topic), "%s/%s", Mqtt::tag_to_topic(device_type_, dv.tag).c_str(), read_flash_string(dv.short_name).c_str());
}
int8_t divider = (dv.options_size == 1) ? Helpers::atoint(read_flash_string(dv.options[0]).c_str()) : 0;
int8_t num_op = dv.numeric_operator;
char payload[55] = {'\0'};
uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (dv.uom == DeviceValueUOM::DEGREES) ? 2 : (dv.uom == DeviceValueUOM::DEGREES_R) ? 1 : 0;
@@ -585,32 +650,32 @@ void EMSdevice::publish_value(void * value_p) const {
if (EMSESP::system_.enum_format() == ENUM_FORMAT_INDEX) {
Helpers::render_value(payload, *(uint8_t *)(value_p), 0);
} else {
strlcpy(payload, read_flash_string(dv.options[*(uint8_t *)(value_p)]).c_str(), sizeof(payload));
auto enum_str = Helpers::translated_word(dv.options[*(uint8_t *)(value_p)]);
strlcpy(payload, enum_str.c_str(), sizeof(payload));
}
}
break;
}
case DeviceValueType::USHORT:
Helpers::render_value(payload, *(uint16_t *)(value_p), divider, fahrenheit);
Helpers::render_value(payload, *(uint16_t *)(value_p), num_op, fahrenheit);
break;
case DeviceValueType::UINT:
Helpers::render_value(payload, *(uint8_t *)(value_p), divider, fahrenheit);
Helpers::render_value(payload, *(uint8_t *)(value_p), num_op, fahrenheit);
break;
case DeviceValueType::SHORT:
Helpers::render_value(payload, *(int16_t *)(value_p), divider, fahrenheit);
Helpers::render_value(payload, *(int16_t *)(value_p), num_op, fahrenheit);
break;
case DeviceValueType::INT:
Helpers::render_value(payload, *(int8_t *)(value_p), divider, fahrenheit);
Helpers::render_value(payload, *(int8_t *)(value_p), num_op, fahrenheit);
break;
case DeviceValueType::ULONG:
Helpers::render_value(payload, *(uint32_t *)(value_p), divider, fahrenheit);
Helpers::render_value(payload, *(uint32_t *)(value_p), num_op, fahrenheit);
break;
case DeviceValueType::BOOL: {
case DeviceValueType::BOOL:
Helpers::render_boolean(payload, (bool)*(uint8_t *)(value_p));
break;
}
case DeviceValueType::TIME:
Helpers::render_value(payload, *(uint32_t *)(value_p), divider);
Helpers::render_value(payload, *(uint32_t *)(value_p), num_op);
break;
case DeviceValueType::STRING:
if (Helpers::hasValue((char *)(value_p))) {
@@ -648,7 +713,8 @@ std::string EMSdevice::get_value_uom(const char * key) const {
// look up key in our device value list
for (const auto & dv : devicevalues_) {
if ((!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && dv.full_name) && (read_flash_string(dv.full_name) == key_p)) {
auto fullname = Helpers::translated_fword(dv.fullname);
if ((!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && fullname) && (read_flash_string(fullname) == key_p)) {
// ignore TIME since "minutes" is already added to the string value
if ((dv.uom == DeviceValueUOM::NONE) || (dv.uom == DeviceValueUOM::MINUTES)) {
break;
@@ -669,11 +735,13 @@ void EMSdevice::generate_values_web(JsonObject & output) {
JsonArray data = output.createNestedArray("data");
for (auto & dv : devicevalues_) {
auto fullname = Helpers::translated_fword(dv.fullname);
// check conditions:
// 1. full_name cannot be empty
// 1. fullname cannot be empty
// 2. it must have a valid value, if it is not a command like 'reset'
// 3. show favorites first
if (!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && dv.full_name && (dv.hasValue() || (dv.type == DeviceValueType::CMD))) {
if (!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && fullname && (dv.hasValue() || (dv.type == DeviceValueType::CMD))) {
JsonObject obj = data.createNestedObject(); // create the object, we know there is a value
uint8_t fahrenheit = 0;
@@ -691,33 +759,28 @@ void EMSdevice::generate_values_web(JsonObject & output) {
// handle ENUMs
else if ((dv.type == DeviceValueType::ENUM) && (*(uint8_t *)(dv.value_p) < dv.options_size)) {
obj["v"] = dv.options[*(uint8_t *)(dv.value_p)];
obj["v"] = Helpers::translated_word(dv.options[*(uint8_t *)(dv.value_p)]);
}
// handle numbers
else {
// If a divider is specified, do the division to 2 decimals places and send back as double/float
// otherwise force as an integer whole
// the nested if's is necessary due to the way the ArduinoJson templates are pre-processed by the compiler
int8_t divider = (dv.options_size == 1) ? Helpers::atoint(read_flash_string(dv.options[0]).c_str()) : 0;
fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (dv.uom == DeviceValueUOM::DEGREES) ? 2 : (dv.uom == DeviceValueUOM::DEGREES_R) ? 1 : 0;
// note, the nested if's is necessary due to the way the ArduinoJson templates are pre-processed by the compiler
fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (dv.uom == DeviceValueUOM::DEGREES) ? 2 : (dv.uom == DeviceValueUOM::DEGREES_R) ? 1 : 0;
if ((dv.type == DeviceValueType::INT) && Helpers::hasValue(*(int8_t *)(dv.value_p))) {
obj["v"] = Helpers::round2(*(int8_t *)(dv.value_p), divider, fahrenheit);
obj["v"] = Helpers::transformNumFloat(*(int8_t *)(dv.value_p), dv.numeric_operator, fahrenheit);
} else if ((dv.type == DeviceValueType::UINT) && Helpers::hasValue(*(uint8_t *)(dv.value_p))) {
obj["v"] = Helpers::round2(*(uint8_t *)(dv.value_p), divider, fahrenheit);
obj["v"] = Helpers::transformNumFloat(*(uint8_t *)(dv.value_p), dv.numeric_operator, fahrenheit);
} else if ((dv.type == DeviceValueType::SHORT) && Helpers::hasValue(*(int16_t *)(dv.value_p))) {
obj["v"] = Helpers::round2(*(int16_t *)(dv.value_p), divider, fahrenheit);
obj["v"] = Helpers::transformNumFloat(*(int16_t *)(dv.value_p), dv.numeric_operator, fahrenheit);
} else if ((dv.type == DeviceValueType::USHORT) && Helpers::hasValue(*(uint16_t *)(dv.value_p))) {
obj["v"] = Helpers::round2(*(uint16_t *)(dv.value_p), divider, fahrenheit);
obj["v"] = Helpers::transformNumFloat(*(uint16_t *)(dv.value_p), dv.numeric_operator, fahrenheit);
} else if ((dv.type == DeviceValueType::ULONG) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
obj["v"] = Helpers::round2(*(uint32_t *)(dv.value_p), divider, fahrenheit);
obj["v"] = Helpers::transformNumFloat(*(uint32_t *)(dv.value_p), dv.numeric_operator, fahrenheit);
} else if ((dv.type == DeviceValueType::TIME) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
uint32_t time_value = *(uint32_t *)(dv.value_p);
obj["v"] = (divider > 0) ? time_value / divider : time_value; // sometimes we need to divide by 60
obj["v"] = dv.numeric_operator ? (*(uint32_t *)(dv.value_p) / dv.numeric_operator) : *(uint32_t *)(dv.value_p);
} else {
// must have a value for sorting to work
obj["v"] = "";
obj["v"] = ""; // must have a value for sorting to work
}
}
@@ -728,9 +791,9 @@ void EMSdevice::generate_values_web(JsonObject & output) {
// add name, prefixing the tag if it exists. This is the id used in the WebUI table and must be unique
if ((dv.tag == DeviceValueTAG::TAG_NONE) || tag_to_string(dv.tag).empty()) {
obj["id"] = mask + read_flash_string(dv.full_name);
obj["id"] = mask + Helpers::translated_word(dv.fullname);
} else {
obj["id"] = mask + tag_to_string(dv.tag) + " " + read_flash_string(dv.full_name);
obj["id"] = mask + tag_to_string(dv.tag) + " " + Helpers::translated_word(dv.fullname);
}
// add commands and options
@@ -741,12 +804,14 @@ void EMSdevice::generate_values_web(JsonObject & output) {
} else {
obj["c"] = dv.short_name;
}
// add the Command options
if (dv.type == DeviceValueType::ENUM || (dv.type == DeviceValueType::CMD && dv.options_size > 1)) {
JsonArray l = obj.createNestedArray("l");
for (uint8_t i = 0; i < dv.options_size; i++) {
if (!read_flash_string(dv.options[i]).empty()) {
l.add(read_flash_string(dv.options[i]));
auto enum_str = Helpers::translated_word(dv.options[i]);
if (!enum_str.empty()) {
l.add(enum_str);
}
}
} else if (dv.type == DeviceValueType::BOOL) {
@@ -758,18 +823,19 @@ void EMSdevice::generate_values_web(JsonObject & output) {
// add command help template
else if (dv.type == DeviceValueType::STRING || dv.type == DeviceValueType::CMD) {
if (dv.options_size == 1) {
obj["h"] = dv.options[0];
obj["h"] = dv.options_single[0]; // NOT translated
}
}
// add steps to numeric values with divider/multiplier
// handle INTs
// add steps to numeric values with numeric_operator
else {
int8_t divider = (dv.options_size == 1) ? Helpers::atoint(read_flash_string(dv.options[0]).c_str()) : 0;
char s[10];
if (divider > 0) {
obj["s"] = Helpers::render_value(s, (float)1 / divider, 1);
} else if (divider < 0) {
obj["s"] = Helpers::render_value(s, (-1) * divider, 0);
char s[10];
if (dv.numeric_operator > 0) {
obj["s"] = Helpers::render_value(s, (float)1 / dv.numeric_operator, 1);
} else if (dv.numeric_operator < 0) {
obj["s"] = Helpers::render_value(s, (-1) * dv.numeric_operator, 0);
}
int16_t dv_set_min, dv_set_max;
if (dv.get_min_max(dv_set_min, dv_set_max)) {
obj["m"] = Helpers::render_value(s, dv_set_min, 0);
@@ -804,39 +870,45 @@ void EMSdevice::generate_values_web_customization(JsonArray & output) {
// handle ENUMs
else if ((dv.type == DeviceValueType::ENUM) && (*(uint8_t *)(dv.value_p) < dv.options_size)) {
obj["v"] = dv.options[*(uint8_t *)(dv.value_p)];
obj["v"] = Helpers::translated_word(dv.options[*(uint8_t *)(dv.value_p)]);
}
// handle Integers and Floats
else {
// If a divider is specified, do the division to 2 decimals places and send back as double/float
// otherwise force as an integer whole
// the nested if's is necessary due to the way the ArduinoJson templates are pre-processed by the compiler
uint8_t divider = 0;
uint8_t factor = 1;
if (dv.options_size == 1) {
auto s_str = read_flash_string(dv.options[0]); // prevent object backing the pointer will be destroyed at the end of the full-expression
const char * s = s_str.c_str();
if (s[0] == '*') {
factor = Helpers::atoint(&s[1]);
} else {
divider = Helpers::atoint(s);
}
int8_t num_op = dv.numeric_operator;
bool make_float;
if (num_op == 0) {
// no changes to number
make_float = false;
num_op = 1; // so it gets *1
} else if (num_op < 0) {
// negative numbers, convert to a positive multiplier
make_float = false;
num_op *= -1;
} else {
// has a divider, make it a float
make_float = true;
}
// always convert temperatures to floats with 1 decimal place
if ((dv.uom == DeviceValueUOM::DEGREES) || (dv.uom == DeviceValueUOM::DEGREES_R)) {
make_float = true;
}
if (dv.type == DeviceValueType::INT) {
obj["v"] = divider ? Helpers::round2(*(int8_t *)(dv.value_p), divider) : *(int8_t *)(dv.value_p) * factor;
obj["v"] = make_float ? Helpers::transformNumFloat(*(int8_t *)(dv.value_p), num_op) : *(int8_t *)(dv.value_p) * num_op;
} else if (dv.type == DeviceValueType::UINT) {
obj["v"] = divider ? Helpers::round2(*(uint8_t *)(dv.value_p), divider) : *(uint8_t *)(dv.value_p) * factor;
obj["v"] = make_float ? Helpers::transformNumFloat(*(uint8_t *)(dv.value_p), num_op) : *(uint8_t *)(dv.value_p) * num_op;
} else if (dv.type == DeviceValueType::SHORT) {
obj["v"] = divider ? Helpers::round2(*(int16_t *)(dv.value_p), divider) : *(int16_t *)(dv.value_p) * factor;
obj["v"] = make_float ? Helpers::transformNumFloat(*(int16_t *)(dv.value_p), num_op) : *(int16_t *)(dv.value_p) * num_op;
} else if (dv.type == DeviceValueType::USHORT) {
obj["v"] = divider ? Helpers::round2(*(uint16_t *)(dv.value_p), divider) : *(uint16_t *)(dv.value_p) * factor;
obj["v"] = make_float ? Helpers::transformNumFloat(*(uint16_t *)(dv.value_p), num_op) : *(uint16_t *)(dv.value_p) * num_op;
} else if (dv.type == DeviceValueType::ULONG) {
obj["v"] = divider ? Helpers::round2(*(uint32_t *)(dv.value_p), divider) : *(uint32_t *)(dv.value_p) * factor;
obj["v"] = make_float ? Helpers::transformNumFloat(*(uint32_t *)(dv.value_p), num_op) : *(uint32_t *)(dv.value_p) * num_op;
} else if (dv.type == DeviceValueType::TIME) {
uint32_t time_value = *(uint32_t *)(dv.value_p);
obj["v"] = (divider > 0) ? time_value / divider : time_value * factor; // sometimes we need to divide by 60
// sometimes we need to divide by 60
obj["v"] = (num_op == DeviceValueNumOp::DV_NUMOP_DIV60) ? (uint32_t)Helpers::transformNumFloat(*(uint32_t *)(dv.value_p), num_op)
: *(uint32_t *)(dv.value_p);
}
}
}
@@ -850,13 +922,14 @@ void EMSdevice::generate_values_web_customization(JsonArray & output) {
// n is the fullname, and can be optional
// don't add the fullname if its a command
auto translated_full_name = Helpers::translated_fword(dv.fullname);
if (dv.type != DeviceValueType::CMD) {
if (dv.full_name) {
if (translated_full_name) {
if ((dv.tag == DeviceValueTAG::TAG_NONE) || tag_to_string(dv.tag).empty()) {
obj["n"] = dv.full_name;
obj["n"] = read_flash_string(translated_full_name);
} else {
char name[50];
snprintf(name, sizeof(name), "%s %s", tag_to_string(dv.tag).c_str(), read_flash_string(dv.full_name).c_str());
snprintf(name, sizeof(name), "%s %s", tag_to_string(dv.tag).c_str(), read_flash_string(translated_full_name).c_str());
obj["n"] = name;
}
}
@@ -930,7 +1003,8 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
// search device value with this tag
for (auto & dv : devicevalues_) {
if (strcmp(command_s, Helpers::toLower(read_flash_string(dv.short_name)).c_str()) == 0 && (tag <= 0 || tag == dv.tag)) {
int8_t divider = (dv.options_size == 1) ? Helpers::atoint(read_flash_string(dv.options[0]).c_str()) : 0;
int8_t num_op = dv.numeric_operator;
uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (dv.uom == DeviceValueUOM::DEGREES) ? 2 : (dv.uom == DeviceValueUOM::DEGREES_R) ? 1 : 0;
const char * type = "type";
@@ -938,12 +1012,12 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
json["name"] = dv.short_name;
if (dv.full_name != nullptr) {
const char * fullname = "fullname";
auto fullname = Helpers::translated_fword(dv.fullname);
if (fullname != nullptr) {
if ((dv.tag == DeviceValueTAG::TAG_NONE) || tag_to_string(dv.tag).empty()) {
json[fullname] = dv.full_name;
json["fullname"] = fullname;
} else {
json[fullname] = tag_to_string(dv.tag) + " " + read_flash_string(dv.full_name);
json["fullname"] = tag_to_string(dv.tag) + " " + read_flash_string(fullname);
}
}
@@ -957,48 +1031,48 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
if (EMSESP::system_.enum_format() == ENUM_FORMAT_INDEX) {
json[value] = (uint8_t)(*(uint8_t *)(dv.value_p));
} else {
json[value] = dv.options[*(uint8_t *)(dv.value_p)]; // text
json[value] = Helpers::translated_word(dv.options[*(uint8_t *)(dv.value_p)]); // text
}
}
json[type] = F_(enum);
JsonArray enum_ = json.createNestedArray(F_(enum));
for (uint8_t i = 0; i < dv.options_size; i++) {
enum_.add(dv.options[i]);
enum_.add(Helpers::translated_word(dv.options[i]));
}
break;
}
case DeviceValueType::USHORT:
if (Helpers::hasValue(*(uint16_t *)(dv.value_p))) {
json[value] = Helpers::round2(*(uint16_t *)(dv.value_p), divider, fahrenheit);
json[value] = Helpers::transformNumFloat(*(uint16_t *)(dv.value_p), num_op, fahrenheit);
}
json[type] = F_(number);
break;
case DeviceValueType::UINT:
if (Helpers::hasValue(*(uint8_t *)(dv.value_p))) {
json[value] = Helpers::round2(*(uint8_t *)(dv.value_p), divider, fahrenheit);
json[value] = Helpers::transformNumFloat(*(uint8_t *)(dv.value_p), num_op, fahrenheit);
}
json[type] = F_(number);
break;
case DeviceValueType::SHORT:
if (Helpers::hasValue(*(int16_t *)(dv.value_p))) {
json[value] = Helpers::round2(*(int16_t *)(dv.value_p), divider, fahrenheit);
json[value] = Helpers::transformNumFloat(*(int16_t *)(dv.value_p), num_op, fahrenheit);
}
json[type] = F_(number);
break;
case DeviceValueType::INT:
if (Helpers::hasValue(*(int8_t *)(dv.value_p))) {
json[value] = Helpers::round2(*(int8_t *)(dv.value_p), divider, fahrenheit);
json[value] = Helpers::transformNumFloat(*(int8_t *)(dv.value_p), num_op, fahrenheit);
}
json[type] = F_(number);
break;
case DeviceValueType::ULONG:
if (Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
json[value] = Helpers::round2(*(uint32_t *)(dv.value_p), divider);
json[value] = Helpers::transformNumFloat(*(uint32_t *)(dv.value_p), num_op);
}
json[type] = F_(number);
break;
@@ -1020,7 +1094,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
case DeviceValueType::TIME:
if (Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
json[value] = Helpers::round2(*(uint32_t *)(dv.value_p), divider);
json[value] = Helpers::transformNumFloat(*(uint32_t *)(dv.value_p), num_op);
}
json[type] = F_(number);
break;
@@ -1037,13 +1111,13 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
if (dv.options_size > 1) {
JsonArray enum_ = json.createNestedArray(F_(enum));
for (uint8_t i = 0; i < dv.options_size; i++) {
enum_.add(dv.options[i]);
enum_.add(Helpers::translated_word(dv.options[i]));
}
}
break;
default:
json[type] = F_(unknown);
json[type] = Helpers::translated_word(FL_(unknown));
break;
}
@@ -1122,12 +1196,14 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
dv.remove_state(DeviceValueState::DV_ACTIVE);
}
auto fullname = Helpers::translated_fword(dv.fullname);
// check conditions:
// 1. it must have a valid value (state is active)
// 2. it must have a visible flag
// 3. it must match the given tag filter or have an empty tag
// 4. it must not have the exclude flag set or outputs to console
if (dv.has_state(DeviceValueState::DV_ACTIVE) && dv.full_name && (tag_filter == DeviceValueTAG::TAG_NONE || tag_filter == dv.tag)
if (dv.has_state(DeviceValueState::DV_ACTIVE) && fullname && (tag_filter == DeviceValueTAG::TAG_NONE || tag_filter == dv.tag)
&& (output_target == OUTPUT_TARGET::CONSOLE || !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE))) {
has_values = true; // flagged if we actually have data
@@ -1136,11 +1212,12 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
// create the name for the JSON key
char name[80];
if (output_target == OUTPUT_TARGET::API_VERBOSE || output_target == OUTPUT_TARGET::CONSOLE) {
if (have_tag) {
snprintf(name, 80, "%s %s", tag_to_string(dv.tag).c_str(), read_flash_string(dv.full_name).c_str()); // prefix the tag
snprintf(name, 80, "%s %s", tag_to_string(dv.tag).c_str(), read_flash_string(fullname).c_str()); // prefix the tag
} else {
strlcpy(name, read_flash_string(dv.full_name).c_str(), sizeof(name)); // use full name
strlcpy(name, read_flash_string(fullname).c_str(), sizeof(name)); // use full name
}
} else {
strlcpy(name, read_flash_string(dv.short_name).c_str(), sizeof(name)); // use short name
@@ -1184,82 +1261,87 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
if (EMSESP::system_.enum_format() == ENUM_FORMAT_INDEX) {
json[name] = (uint8_t)(*(uint8_t *)(dv.value_p));
} else {
json[name] = dv.options[*(uint8_t *)(dv.value_p)];
json[name] = Helpers::translated_word(dv.options[*(uint8_t *)(dv.value_p)]);
}
}
// handle Numbers
// If a divider is specified, do the division to 2 decimals places and send back as double/float
// otherwise force as a whole integer
// note: the strange nested if's is necessary due to the way the ArduinoJson templates are pre-processed by the compiler
else {
// If a divider is specified, do the division to 2 decimals places and send back as double/float
// otherwise force as an integer whole
uint8_t divider = 0;
uint8_t factor = 1;
if (dv.options_size == 1) {
auto s_str = read_flash_string(dv.options[0]); // prevent object backing the pointer will be destroyed at the end of the full-expression
const char * s = s_str.c_str();
if (s[0] == '*') {
factor = Helpers::atoint(&s[1]);
} else {
divider = Helpers::atoint(s);
}
}
// fahrenheit, 0 is no converstion other 1 or 2. not sure why?
// fahrenheit, 0 is no conversion other 1 or 2. not sure why?
uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0
: (dv.uom == DeviceValueUOM::DEGREES) ? 2
: (dv.uom == DeviceValueUOM::DEGREES_R) ? 1
: 0;
int8_t num_op = dv.numeric_operator;
bool make_float;
if (num_op == 0) {
// no changes to number
make_float = false;
num_op = 1; // so it gets *1
} else if (num_op < 0) {
// negative numbers, convert to a positive multiplier
make_float = false;
num_op *= -1;
} else {
// has a divider, make it a float
make_float = true;
}
// always convert temperatures to floats with 1 decimal place
bool make_float = (divider || (dv.uom == DeviceValueUOM::DEGREES) || (dv.uom == DeviceValueUOM::DEGREES_R));
if ((dv.uom == DeviceValueUOM::DEGREES) || (dv.uom == DeviceValueUOM::DEGREES_R)) {
make_float = true;
}
if (dv.type == DeviceValueType::INT) {
if (make_float) {
json[name] = Helpers::round2(*(int8_t *)(dv.value_p), divider, fahrenheit);
json[name] = Helpers::transformNumFloat(*(int8_t *)(dv.value_p), num_op, fahrenheit);
} else {
json[name] = *(int8_t *)(dv.value_p) * factor;
json[name] = *(int8_t *)(dv.value_p) * num_op;
}
} else if (dv.type == DeviceValueType::UINT) {
if (make_float) {
json[name] = Helpers::round2(*(uint8_t *)(dv.value_p), divider, fahrenheit);
json[name] = Helpers::transformNumFloat(*(uint8_t *)(dv.value_p), num_op, fahrenheit);
} else {
json[name] = *(uint8_t *)(dv.value_p) * factor;
json[name] = *(uint8_t *)(dv.value_p) * num_op;
}
} else if (dv.type == DeviceValueType::SHORT) {
if (make_float) {
json[name] = Helpers::round2(*(int16_t *)(dv.value_p), divider, fahrenheit);
json[name] = Helpers::transformNumFloat(*(int16_t *)(dv.value_p), num_op, fahrenheit);
} else {
json[name] = *(int16_t *)(dv.value_p) * factor;
json[name] = *(int16_t *)(dv.value_p) * num_op;
}
} else if (dv.type == DeviceValueType::USHORT) {
if (make_float) {
json[name] = Helpers::round2(*(uint16_t *)(dv.value_p), divider, fahrenheit);
json[name] = Helpers::transformNumFloat(*(uint16_t *)(dv.value_p), num_op, fahrenheit);
} else {
json[name] = *(uint16_t *)(dv.value_p) * factor;
json[name] = *(uint16_t *)(dv.value_p) * num_op;
}
} else if (dv.type == DeviceValueType::ULONG) {
if (make_float) {
json[name] = Helpers::round2(*(uint32_t *)(dv.value_p), divider, fahrenheit);
json[name] = Helpers::transformNumFloat(*(uint32_t *)(dv.value_p), num_op, fahrenheit);
} else {
json[name] = *(uint32_t *)(dv.value_p) * factor;
json[name] = *(uint32_t *)(dv.value_p) * num_op;
}
} else if ((dv.type == DeviceValueType::TIME) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
uint32_t time_value = *(uint32_t *)(dv.value_p);
time_value = Helpers::round2(time_value, divider); // sometimes we need to divide by 60
uint32_t time_value;
if (num_op == DeviceValueNumOp::DV_NUMOP_DIV60) {
// sometimes we need to divide by 60
time_value = Helpers::transformNumFloat(*(uint32_t *)(dv.value_p), num_op);
} else {
time_value = *(uint32_t *)(dv.value_p);
}
if (output_target == OUTPUT_TARGET::API_VERBOSE || output_target == OUTPUT_TARGET::CONSOLE) {
char time_s[40];
char time_s[60];
snprintf(time_s,
sizeof(time_s),
"%d %s %d %s %d %s",
(time_value / 1440),
read_flash_string(F_(days)).c_str(),
Helpers::translated_word(FL_(days)).c_str(),
((time_value % 1440) / 60),
read_flash_string(F_(hours)).c_str(),
Helpers::translated_word(FL_(hours)).c_str(),
(time_value % 60),
read_flash_string(F_(minutes)).c_str());
Helpers::translated_word(FL_(minutes)).c_str());
json[name] = time_s;
} else {
json[name] = time_value;