diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index 626529fca..305f92ea2 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -9,7 +9,7 @@ For more details go to [emsesp.org](https://emsesp.org/). - comfortpoint for BC400 [#2935](https://github.com/emsesp/EMS-ESP32/issues/2935) - customize device brand [#2784](https://github.com/emsesp/EMS-ESP32/issues/2784) - set model for ems-esp devices temperature, analog, etc. [#2958](https://github.com/emsesp/EMS-ESP32/discussions/2958) -- prometheus metrics for temperaturesensors [#2962](https://github.com/emsesp/EMS-ESP32/issues/2962) +- prometheus metrics for temperature/analog/scheduler/custom [#2962](https://github.com/emsesp/EMS-ESP32/issues/2962) ## Fixed diff --git a/src/core/analogsensor.cpp b/src/core/analogsensor.cpp index 84b803679..4147e5c34 100644 --- a/src/core/analogsensor.cpp +++ b/src/core/analogsensor.cpp @@ -852,6 +852,15 @@ bool AnalogSensor::get_value_info(JsonObject output, const char * cmd, const int return true; } + if (!strcmp(cmd, F_(metrics))) { + std::string metrics = get_metrics_prometheus(); + if (!metrics.empty()) { + output["api_data"] = metrics; + return true; + } + return false; + } + // this is for a specific sensor, return the value const char * attribute_s = Command::get_attribute(cmd); @@ -866,6 +875,35 @@ bool AnalogSensor::get_value_info(JsonObject output, const char * cmd, const int return false; // not found } +// generate Prometheus metrics format from analog values +std::string AnalogSensor::get_metrics_prometheus() { + std::string result; + result.reserve(sensors_.size() * 140); + char val[10]; + for (auto & sensor : sensors_) { + result += (std::string) "# HELP emsesp_" + sensor.name() + " " + sensor.name(); + if (sensor.type() != AnalogType::DIGITAL_OUT && sensor.type() != AnalogType::DIGITAL_IN) { + result += (std::string) ", " + EMSdevice::uom_to_string(sensor.uom()); + } else { + result += (std::string) ", boolean"; + } + result += (std::string) ", readable, visible"; + if (sensor.type() == AnalogType::COUNTER || sensor.type() == AnalogType::RGB || sensor.type() == AnalogType::PULSE + || (sensor.type() >= AnalogType::DIGITAL_OUT && sensor.type() <= AnalogType::PWM_2) + || (sensor.type() >= AnalogType::CNT_0 && sensor.type() <= AnalogType::CNT_2)) { + result += (std::string) ", writable"; + } + result += (std::string) "\n# TYPE emsesp_" + sensor.name() + " gauge\n"; + result += (std::string) "emsesp_" + sensor.name() + " "; + if (sensor.type() != AnalogType::DIGITAL_OUT && sensor.type() != AnalogType::DIGITAL_IN) { + result += (std::string) Helpers::render_value(val, sensor.value(), 2) + "\n"; + } else { + result += (std::string) (sensor.value() == 0 ? "0\n" : "1\n"); + } + } + return result; +} + // note we don't add the device and state classes here, as we do in the custom entity service void AnalogSensor::get_value_json(JsonObject output, const Sensor & sensor) { output["name"] = (const char *)sensor.name(); diff --git a/src/core/analogsensor.h b/src/core/analogsensor.h index f466adb75..65e200662 100644 --- a/src/core/analogsensor.h +++ b/src/core/analogsensor.h @@ -177,6 +177,7 @@ class AnalogSensor { bool update(uint8_t gpio, const char * name, double offset, double factor, uint8_t uom, int8_t type, bool deleted, bool is_system); bool get_value_info(JsonObject output, const char * cmd, const int8_t id = -1); void store_counters(); + std::string get_metrics_prometheus(); static std::vector exclude_types() { return exclude_types_; } diff --git a/src/emsesp_version.h b/src/emsesp_version.h index 3945693dd..73961265b 100644 --- a/src/emsesp_version.h +++ b/src/emsesp_version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.8.2-dev.6" +#define EMSESP_APP_VERSION "3.8.2-dev.7" diff --git a/src/web/WebCustomEntityService.cpp b/src/web/WebCustomEntityService.cpp index 3e4356318..9de8f3da8 100644 --- a/src/web/WebCustomEntityService.cpp +++ b/src/web/WebCustomEntityService.cpp @@ -343,6 +343,15 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd) return true; } + if (!strcmp(cmd, F_(metrics))) { + std::string metrics = get_metrics_prometheus(); + if (!metrics.empty()) { + output["api_data"] = metrics; + return true; + } + return false; + } + // specific value info const char * attribute_s = Command::get_attribute(cmd); for (auto const & entity : *customEntityItems_) { @@ -354,6 +363,54 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd) return false; // not found } +// generate Prometheus metrics format from custom entities +std::string WebCustomEntityService::get_metrics_prometheus() { + std::string result; + result.reserve(customEntityItems_->size() * 140); + char val[10]; + for (CustomEntityItem & entity : *customEntityItems_) { + if (entity.hide || entity.name[0] == '\0') { + continue; + } + result += (std::string) "# HELP emsesp_" + entity.name + " " + entity.name; + if (entity.uom != 0) { + result += (std::string) ", " + EMSdevice::uom_to_string(entity.uom); + } + result += (std::string) ", readable, visible" + (entity.writeable ? ", writable\n" : "\n"); + result += (std::string) "# TYPE emsesp_" + entity.name + " gauge\n"; + result += (std::string) "emsesp_" + entity.name + " "; + switch (entity.value_type) { + case DeviceValueType::BOOL: + result += (std::string)(entity.value == 0 ? "0" : "1"); + break; + case DeviceValueType::INT8: + result += (std::string)Helpers::render_value(val, entity.factor * (int8_t)entity.value, 2); + break; + case DeviceValueType::UINT8: + result += (std::string)Helpers::render_value(val, entity.factor * (uint8_t)entity.value, 2); + break; + case DeviceValueType::INT16: + result += (std::string)Helpers::render_value(val, entity.factor * (int16_t)entity.value, 2); + break; + case DeviceValueType::UINT16: + result += (std::string)Helpers::render_value(val, entity.factor * (uint16_t)entity.value, 2); + break; + case DeviceValueType::UINT24: + case DeviceValueType::TIME: + case DeviceValueType::UINT32: + result += (std::string)Helpers::render_value(val, entity.factor * entity.value, 2); + break; + default: + if (entity.data.length() > 0) { + result += entity.data; + } + break; + } + result += (std::string) "\n"; + } + return result; +} + // build the json for specific entity void WebCustomEntityService::get_value_json(JsonObject output, CustomEntityItem const & entity) { output["name"] = (const char *)entity.name; diff --git a/src/web/WebCustomEntityService.h b/src/web/WebCustomEntityService.h index d96254846..4cfec1ac4 100644 --- a/src/web/WebCustomEntityService.h +++ b/src/web/WebCustomEntityService.h @@ -68,6 +68,8 @@ class WebCustomEntityService : public StatefulService { void show_values(JsonObject output); void generate_value_web(JsonObject output, const bool is_dashboard = false); + std::string get_metrics_prometheus(); + uint8_t count_entities(); void ha_reset() { ha_configdone_ = false; diff --git a/src/web/WebSchedulerService.cpp b/src/web/WebSchedulerService.cpp index b92930357..e708c94e7 100644 --- a/src/web/WebSchedulerService.cpp +++ b/src/web/WebSchedulerService.cpp @@ -102,7 +102,7 @@ StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webSchedu if (webScheduler.scheduleItems.back().name[0] != '\0') { char key[sizeof(webScheduler.scheduleItems.back().name) + 2]; snprintf(key, sizeof(key), "s:%s", webScheduler.scheduleItems.back().name); - if (EMSESP::nvs_.isKey(key)) { + if (EMSESP::nvs_.isKey(key) && webScheduler.scheduleItems.back().flags != SCHEDULEFLAG_SCHEDULE_IMMEDIATE) { webScheduler.scheduleItems.back().active = EMSESP::nvs_.getBool(key); } Command::add( @@ -138,20 +138,11 @@ bool WebSchedulerService::command_setvalue(const char * value, const int8_t id, publish(); } // save new state to nvs #2946 - char key[sizeof(scheduleItem.name) + 2]; - snprintf(key, sizeof(key), "s:%s", scheduleItem.name); - EMSESP::nvs_.putBool(key, scheduleItem.active); - /* save to filesystem - EMSESP::webSchedulerService.update([&](WebScheduler & webSchedule) { - for (auto si : webSchedule.scheduleItems) { - if (!strcmp(si.name, scheduleItem.name)) { - si.active = scheduleItem.active; - break; - } - } - return StateUpdateResult::CHANGED; - }); - */ + if (scheduleItem.flags != SCHEDULEFLAG_SCHEDULE_IMMEDIATE) { + char key[sizeof(scheduleItem.name) + 2]; + snprintf(key, sizeof(key), "s:%s", scheduleItem.name); + EMSESP::nvs_.putBool(key, scheduleItem.active); + } return true; } } @@ -184,6 +175,15 @@ bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) { return true; } + if (!strcmp(cmd, F_(metrics))) { + std::string metrics = get_metrics_prometheus(); + if (!metrics.empty()) { + output["api_data"] = metrics; + return true; + } + return false; + } + const char * attribute_s = Command::get_attribute(cmd); for (const ScheduleItem & scheduleItem : *scheduleItems_) { if (Helpers::toLower(scheduleItem.name) == cmd) { @@ -195,6 +195,21 @@ bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) { return false; // not found } +// generate Prometheus metrics format from scheduler values +std::string WebSchedulerService::get_metrics_prometheus() { + std::string result; + result.reserve(scheduleItems_->size() * 140); + for (const ScheduleItem & scheduleItem : *scheduleItems_) { + if (scheduleItem.name[0] == '\0') { + continue; + } + result += (std::string) "# HELP emsesp_" + scheduleItem.name + " " + scheduleItem.name + ", boolean, readable, visible, writable\n"; + result += (std::string) "# TYPE emsesp_" + scheduleItem.name + " gauge\n"; + result += (std::string) "emsesp_" + scheduleItem.name + " " + (scheduleItem.active ? "1\n" : "0\n"); + } + return result; +} + // build the json for specific entity void WebSchedulerService::get_value_json(JsonObject output, const ScheduleItem & scheduleItem) { output["name"] = (const char *)scheduleItem.name; @@ -483,6 +498,10 @@ void WebSchedulerService::loop() { if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_IMMEDIATE) { command(scheduleItem.name, scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str())); scheduleItem.active = false; + publish_single(scheduleItem.name, false); + if (EMSESP::mqtt_.get_publish_onchange(0)) { + publish(); + } } } diff --git a/src/web/WebSchedulerService.h b/src/web/WebSchedulerService.h index 8a1bbbf55..0d1ad2fa6 100644 --- a/src/web/WebSchedulerService.h +++ b/src/web/WebSchedulerService.h @@ -88,6 +88,8 @@ class WebSchedulerService : public StatefulService { uint8_t count_entities(bool cmd_only = false); bool onChange(const char * cmd); + std::string get_metrics_prometheus(); + std::string raw_value; std::string computed_value;