mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 07:49:52 +03:00
Merge branch 'emsesp:dev' into dev
This commit is contained in:
@@ -763,6 +763,8 @@ void Command::show_all(uuid::console::Shell & shell) {
|
||||
shell.println(COLOR_RESET);
|
||||
shell.printf(" entities \t\t\t%slist all entities %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN);
|
||||
shell.println(COLOR_RESET);
|
||||
shell.printf(" metrics \t\t\t%slist all prometheus metrics %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN);
|
||||
shell.println(COLOR_RESET);
|
||||
|
||||
// show system ones first
|
||||
show(shell, EMSdevice::DeviceType::SYSTEM, true);
|
||||
|
||||
@@ -1533,6 +1533,14 @@ bool EMSdevice::get_value_info(JsonObject output, const char * cmd, const int8_t
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!strcmp(cmd, F_(metrics))) {
|
||||
std::string metrics = get_metrics_prometheus(tag);
|
||||
if (!metrics.empty()) {
|
||||
output["api_data"] = metrics;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// search device value with this tag
|
||||
// make a copy of cmd and split attribute (leave cmd untouched for other devices)
|
||||
@@ -1696,6 +1704,176 @@ void EMSdevice::get_value_json(JsonObject json, DeviceValue & dv) {
|
||||
json["visible"] = !dv.has_state(DeviceValueState::DV_WEB_EXCLUDE);
|
||||
}
|
||||
|
||||
// generate Prometheus metrics format from device values
|
||||
std::string EMSdevice::get_metrics_prometheus(const int8_t tag) {
|
||||
std::string result;
|
||||
std::unordered_map<std::string, bool> seen_metrics;
|
||||
|
||||
for (auto & dv : devicevalues_) {
|
||||
if (tag >= 0 && tag != dv.tag) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// only process number and boolean types for now
|
||||
if (dv.type != DeviceValueType::BOOL && dv.type != DeviceValueType::UINT8 && dv.type != DeviceValueType::INT8
|
||||
&& dv.type != DeviceValueType::UINT16 && dv.type != DeviceValueType::INT16 && dv.type != DeviceValueType::UINT24
|
||||
&& dv.type != DeviceValueType::UINT32 && dv.type != DeviceValueType::TIME) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool has_value = false;
|
||||
double metric_value = 0.0;
|
||||
|
||||
switch (dv.type) {
|
||||
case DeviceValueType::BOOL:
|
||||
if (Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) {
|
||||
has_value = true;
|
||||
metric_value = (bool)*(uint8_t *)(dv.value_p) ? 1.0 : 0.0;
|
||||
}
|
||||
break;
|
||||
case DeviceValueType::UINT8:
|
||||
if (Helpers::hasValue(*(uint8_t *)(dv.value_p))) {
|
||||
has_value = true;
|
||||
metric_value = *(uint8_t *)(dv.value_p);
|
||||
}
|
||||
break;
|
||||
case DeviceValueType::INT8:
|
||||
if (Helpers::hasValue(*(int8_t *)(dv.value_p))) {
|
||||
has_value = true;
|
||||
metric_value = *(int8_t *)(dv.value_p);
|
||||
}
|
||||
break;
|
||||
case DeviceValueType::UINT16:
|
||||
if (Helpers::hasValue(*(uint16_t *)(dv.value_p))) {
|
||||
has_value = true;
|
||||
metric_value = *(uint16_t *)(dv.value_p);
|
||||
}
|
||||
break;
|
||||
case DeviceValueType::INT16:
|
||||
if (Helpers::hasValue(*(int16_t *)(dv.value_p))) {
|
||||
has_value = true;
|
||||
metric_value = *(int16_t *)(dv.value_p);
|
||||
}
|
||||
break;
|
||||
case DeviceValueType::UINT24:
|
||||
case DeviceValueType::UINT32:
|
||||
case DeviceValueType::TIME:
|
||||
if (Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
|
||||
has_value = true;
|
||||
metric_value = *(uint32_t *)(dv.value_p);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!has_value) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string metric_name = dv.short_name;
|
||||
size_t last_dot = metric_name.find_last_of('.');
|
||||
if (last_dot != std::string::npos) {
|
||||
metric_name = metric_name.substr(last_dot + 1);
|
||||
}
|
||||
|
||||
for (char & c : metric_name) {
|
||||
if (!isalnum(c) && c != '_') {
|
||||
c = '_';
|
||||
}
|
||||
}
|
||||
|
||||
std::string full_metric_name = "emsesp_" + metric_name;
|
||||
|
||||
std::string circuit_label;
|
||||
if (dv.tag != DeviceValueTAG::TAG_NONE) {
|
||||
const char * circuit = tag_to_mqtt(dv.tag);
|
||||
if (circuit && strlen(circuit) > 0) {
|
||||
circuit_label = circuit;
|
||||
}
|
||||
}
|
||||
|
||||
auto fullname = dv.get_fullname();
|
||||
std::string help_text;
|
||||
if (!fullname.empty()) {
|
||||
help_text = fullname;
|
||||
} else {
|
||||
help_text = metric_name;
|
||||
}
|
||||
|
||||
std::string uom_str;
|
||||
if (dv.type == DeviceValueType::BOOL) {
|
||||
uom_str = "boolean";
|
||||
} else if (dv.uom != DeviceValueUOM::NONE) {
|
||||
uom_str = uom_to_string(dv.uom);
|
||||
}
|
||||
|
||||
std::string help_line = help_text;
|
||||
if (!uom_str.empty()) {
|
||||
help_line += ", " + uom_str;
|
||||
}
|
||||
|
||||
bool readable = dv.type != DeviceValueType::CMD && !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE);
|
||||
bool writeable = dv.has_cmd && !dv.has_state(DeviceValueState::DV_READONLY);
|
||||
bool visible = !dv.has_state(DeviceValueState::DV_WEB_EXCLUDE);
|
||||
|
||||
if (readable) {
|
||||
help_line += ", readable";
|
||||
}
|
||||
if (writeable) {
|
||||
help_line += ", writeable";
|
||||
}
|
||||
if (visible) {
|
||||
help_line += ", visible";
|
||||
}
|
||||
|
||||
std::string escaped_help;
|
||||
for (char c : help_line) {
|
||||
if (c == '\\') {
|
||||
escaped_help += "\\\\";
|
||||
} else if (c == '\n') {
|
||||
escaped_help += "\\n";
|
||||
} else {
|
||||
escaped_help += c;
|
||||
}
|
||||
}
|
||||
|
||||
if (seen_metrics.find(full_metric_name) == seen_metrics.end()) {
|
||||
result += "# HELP " + full_metric_name + " " + escaped_help + "\n";
|
||||
result += "# TYPE " + full_metric_name + " gauge\n";
|
||||
seen_metrics[full_metric_name] = true;
|
||||
}
|
||||
|
||||
result += full_metric_name;
|
||||
if (!circuit_label.empty()) {
|
||||
result += "{circuit=\"" + circuit_label + "\"}";
|
||||
}
|
||||
result += " ";
|
||||
|
||||
char val_str[30];
|
||||
double final_value = metric_value;
|
||||
|
||||
if (dv.numeric_operator != 0) {
|
||||
if (dv.numeric_operator > 0) {
|
||||
final_value = metric_value / dv.numeric_operator;
|
||||
} else {
|
||||
final_value = metric_value * (-dv.numeric_operator);
|
||||
}
|
||||
}
|
||||
|
||||
double rounded = (final_value >= 0) ? (double)((int64_t)(final_value + 0.5)) : (double)((int64_t)(final_value - 0.5));
|
||||
if (dv.type == DeviceValueType::BOOL || (final_value == rounded)) {
|
||||
snprintf(val_str, sizeof(val_str), "%.0f", final_value);
|
||||
} else {
|
||||
snprintf(val_str, sizeof(val_str), "%.2f", final_value);
|
||||
}
|
||||
result += val_str;
|
||||
result += "\n";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// mqtt publish all single values from one device (used for time schedule)
|
||||
void EMSdevice::publish_all_values() {
|
||||
for (const auto & dv : devicevalues_) {
|
||||
|
||||
@@ -251,6 +251,7 @@ class EMSdevice {
|
||||
std::string get_value_uom(const std::string & shortname) const;
|
||||
bool get_value_info(JsonObject root, const char * cmd, const int8_t id);
|
||||
void get_value_json(JsonObject output, DeviceValue & dv);
|
||||
std::string get_metrics_prometheus(const int8_t tag = -1);
|
||||
void get_dv_info(JsonObject json);
|
||||
|
||||
enum OUTPUT_TARGET : uint8_t { API_VERBOSE, API_SHORTNAMES, MQTT, CONSOLE };
|
||||
|
||||
@@ -86,6 +86,7 @@ MAKE_WORD(info)
|
||||
MAKE_WORD(settings)
|
||||
MAKE_WORD(value)
|
||||
MAKE_WORD(entities)
|
||||
MAKE_WORD(metrics)
|
||||
MAKE_WORD(coldshot)
|
||||
|
||||
// device types - lowercase, used in MQTT
|
||||
|
||||
@@ -63,6 +63,7 @@ MAKE_WORD_TRANSLATION(pool_device, "Pool Module", "Poolmodul", "", "Poolmodul",
|
||||
MAKE_WORD_TRANSLATION(info_cmd, "list all values (verbose)", "Liste aller Werte", "lijst van alle waardes", "lista alla värden", "wyświetl wszystkie wartości", "Viser alle verdier", "", "Tüm değerleri listele", "elenca tutti i valori", "zobraziť všetky hodnoty", "vypsat všechny hodnoty (podrobně)") // TODO translate
|
||||
MAKE_WORD_TRANSLATION(commands_cmd, "list all commands", "Liste aller Kommandos", "lijst van alle commando's", "lista alla kommandon", "wyświetl wszystkie komendy", "Viser alle kommandoer", "", "Tüm komutları listele", "elencaa tutti i comandi", "zobraziť všetky príkazy", "vypsat všechny příkazy") // TODO translate
|
||||
MAKE_WORD_TRANSLATION(entities_cmd, "list all entities", "Liste aller Entitäten", "lijst van alle entiteiten", "lista all entiteter", "wyświetl wszsytkie encje", "Viser alle enheter", "", "Tüm varlıkları listele", "elenca tutte le entità", "zobraziť všetky entity", "vypsat všechny entity") // TODO translate
|
||||
MAKE_WORD_TRANSLATION(metrics_cmd, "list all prometheus metrics", "Liste aller Prometheus Metriken", "lijst van alle Prometheus metriken", "lista alla Prometheus metriker", "wyświetl wszystkie Prometheus metryki", "Viser alle Prometheus metrikker", "", "Tüm Prometheus metriklerini listele", "elenca tutte le metriche Prometheus", "zobraziť všetky Prometheus metriky", "vypsat všechny Prometheus metriky") // TODO translate
|
||||
MAKE_WORD_TRANSLATION(send_cmd, "send a telegram", "Sende EMS-Telegramm", "stuur een telegram", "skicka ett telegram", "wyślij telegram", "send et telegram", "", "Bir telegram gönder", "invia un telegramma", "poslať telegram", "odeslat telegram") // TODO translate
|
||||
MAKE_WORD_TRANSLATION(read_cmd, "send read request", "", "", "skicka en läsförfrågan", "", "", "", "", "", "odoslať žiadosť o prečítanie", "") // TODO translate
|
||||
MAKE_WORD_TRANSLATION(setiovalue_cmd, "set I/O value", "Setze Werte E/A", "instellen standaardwaarde", "sätt ett I/O-värde", "ustaw wartość", "sett en io verdi", "", "Giriş/Çıkış değerlerini ayarla", "imposta valore io", "nastaviť hodnotu io", "nastavit hodnotu I/O") // TODO translate
|
||||
|
||||
@@ -288,6 +288,37 @@ void manual_test7() {
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/custom/test_ram", data));
|
||||
}
|
||||
|
||||
void manual_test8() {
|
||||
const char * response = call_url("/api/boiler/metrics");
|
||||
|
||||
TEST_ASSERT_NOT_NULL(response);
|
||||
TEST_ASSERT_TRUE(strlen(response) > 0);
|
||||
|
||||
TEST_ASSERT_TRUE(strstr(response, "# HELP") != nullptr);
|
||||
TEST_ASSERT_TRUE(strstr(response, "# TYPE") != nullptr);
|
||||
TEST_ASSERT_TRUE(strstr(response, "emsesp_") != nullptr);
|
||||
TEST_ASSERT_TRUE(strstr(response, " gauge") != nullptr);
|
||||
|
||||
TEST_ASSERT_TRUE(strstr(response, "emsesp_tapwateractive") != nullptr ||
|
||||
strstr(response, "emsesp_selflowtemp") != nullptr ||
|
||||
strstr(response, "emsesp_curflowtemp") != nullptr);
|
||||
}
|
||||
|
||||
void manual_test9() {
|
||||
const char * response = call_url("/api/thermostat/metrics");
|
||||
|
||||
TEST_ASSERT_NOT_NULL(response);
|
||||
TEST_ASSERT_TRUE(strlen(response) > 0);
|
||||
|
||||
TEST_ASSERT_TRUE(strstr(response, "# HELP") != nullptr);
|
||||
TEST_ASSERT_TRUE(strstr(response, "# TYPE") != nullptr);
|
||||
TEST_ASSERT_TRUE(strstr(response, "emsesp_") != nullptr);
|
||||
|
||||
if (strstr(response, "circuit=") != nullptr) {
|
||||
TEST_ASSERT_TRUE(strstr(response, "{circuit=") != nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void run_manual_tests() {
|
||||
RUN_TEST(manual_test1);
|
||||
RUN_TEST(manual_test2);
|
||||
@@ -296,6 +327,8 @@ void run_manual_tests() {
|
||||
RUN_TEST(manual_test5);
|
||||
RUN_TEST(manual_test6);
|
||||
RUN_TEST(manual_test7);
|
||||
RUN_TEST(manual_test8);
|
||||
RUN_TEST(manual_test9);
|
||||
}
|
||||
|
||||
const char * run_console_command(const char * command) {
|
||||
@@ -353,6 +386,7 @@ void create_tests() {
|
||||
capture("/api/boiler/values");
|
||||
capture("/api/boiler/info");
|
||||
// capture("/api/boiler/entities"); // skipping since payload is too large
|
||||
capture("/api/boiler/metrics");
|
||||
capture("/api/boiler/comfort");
|
||||
capture("/api/boiler/comfort/value");
|
||||
capture("/api/boiler/comfort/fullname");
|
||||
@@ -364,6 +398,7 @@ void create_tests() {
|
||||
// thermostat
|
||||
capture("/api/thermostat");
|
||||
capture("/api/thermostat/hc1/values");
|
||||
capture("/api/thermostat/metrics");
|
||||
capture("/api/thermostat/hc1/seltemp");
|
||||
capture("/api/thermostat/hc2/seltemp");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user