From df26ace0f172d1bad7f019fc4d095f3a7be188f9 Mon Sep 17 00:00:00 2001 From: Jordan Zucker Date: Tue, 14 Jan 2025 19:56:22 -0800 Subject: [PATCH] [prometheus] Select, media_player, and number prometheus metrics (#7895) --- .../prometheus/prometheus_handler.cpp | 168 ++++++++++++++++++ .../prometheus/prometheus_handler.h | 24 +++ tests/components/prometheus/common.yaml | 20 +++ .../components/prometheus/test.esp32-ard.yaml | 33 ++++ 4 files changed, 245 insertions(+) diff --git a/esphome/components/prometheus/prometheus_handler.cpp b/esphome/components/prometheus/prometheus_handler.cpp index 5d1861202a..2d39d8ef3f 100644 --- a/esphome/components/prometheus/prometheus_handler.cpp +++ b/esphome/components/prometheus/prometheus_handler.cpp @@ -59,6 +59,24 @@ void PrometheusHandler::handleRequest(AsyncWebServerRequest *req) { this->text_sensor_row_(stream, obj, area, node, friendly_name); #endif +#ifdef USE_NUMBER + this->number_type_(stream); + for (auto *obj : App.get_numbers()) + this->number_row_(stream, obj, area, node, friendly_name); +#endif + +#ifdef USE_SELECT + this->select_type_(stream); + for (auto *obj : App.get_selects()) + this->select_row_(stream, obj, area, node, friendly_name); +#endif + +#ifdef USE_MEDIA_PLAYER + this->media_player_type_(stream); + for (auto *obj : App.get_media_players()) + this->media_player_row_(stream, obj, area, node, friendly_name); +#endif + req->send(stream); } @@ -511,6 +529,156 @@ void PrometheusHandler::text_sensor_row_(AsyncResponseStream *stream, text_senso } #endif +// Type-specific implementation +#ifdef USE_NUMBER +void PrometheusHandler::number_type_(AsyncResponseStream *stream) { + stream->print(F("#TYPE esphome_number_value gauge\n")); + stream->print(F("#TYPE esphome_number_failed gauge\n")); +} +void PrometheusHandler::number_row_(AsyncResponseStream *stream, number::Number *obj, std::string &area, + std::string &node, std::string &friendly_name) { + if (obj->is_internal() && !this->include_internal_) + return; + if (!std::isnan(obj->state)) { + // We have a valid value, output this value + stream->print(F("esphome_number_failed{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} 0\n")); + // Data itself + stream->print(F("esphome_number_value{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} ")); + stream->print(obj->state); + stream->print(F("\n")); + } else { + // Invalid state + stream->print(F("esphome_number_failed{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} 1\n")); + } +} +#endif + +#ifdef USE_SELECT +void PrometheusHandler::select_type_(AsyncResponseStream *stream) { + stream->print(F("#TYPE esphome_select_value gauge\n")); + stream->print(F("#TYPE esphome_select_failed gauge\n")); +} +void PrometheusHandler::select_row_(AsyncResponseStream *stream, select::Select *obj, std::string &area, + std::string &node, std::string &friendly_name) { + if (obj->is_internal() && !this->include_internal_) + return; + if (obj->has_state()) { + // We have a valid value, output this value + stream->print(F("esphome_select_failed{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} 0\n")); + // Data itself + stream->print(F("esphome_select_value{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\",value=\"")); + stream->print(obj->state.c_str()); + stream->print(F("\"} ")); + stream->print(F("1.0")); + stream->print(F("\n")); + } else { + // Invalid state + stream->print(F("esphome_select_failed{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} 1\n")); + } +} +#endif + +#ifdef USE_MEDIA_PLAYER +void PrometheusHandler::media_player_type_(AsyncResponseStream *stream) { + stream->print(F("#TYPE esphome_media_player_state_value gauge\n")); + stream->print(F("#TYPE esphome_media_player_volume gauge\n")); + stream->print(F("#TYPE esphome_media_player_is_muted gauge\n")); + stream->print(F("#TYPE esphome_media_player_failed gauge\n")); +} +void PrometheusHandler::media_player_row_(AsyncResponseStream *stream, media_player::MediaPlayer *obj, + std::string &area, std::string &node, std::string &friendly_name) { + if (obj->is_internal() && !this->include_internal_) + return; + stream->print(F("esphome_media_player_failed{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} 0\n")); + // Data itself + stream->print(F("esphome_media_player_state_value{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\",value=\"")); + stream->print(media_player::media_player_state_to_string(obj->state)); + stream->print(F("\"} ")); + stream->print(F("1.0")); + stream->print(F("\n")); + stream->print(F("esphome_media_player_volume{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} ")); + stream->print(obj->volume); + stream->print(F("\n")); + stream->print(F("esphome_media_player_is_muted{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} ")); + if (obj->is_muted()) { + stream->print(F("1.0")); + } else { + stream->print(F("0.0")); + } + stream->print(F("\n")); +} +#endif + } // namespace prometheus } // namespace esphome #endif diff --git a/esphome/components/prometheus/prometheus_handler.h b/esphome/components/prometheus/prometheus_handler.h index 5d08aca63a..41a06537ed 100644 --- a/esphome/components/prometheus/prometheus_handler.h +++ b/esphome/components/prometheus/prometheus_handler.h @@ -128,6 +128,30 @@ class PrometheusHandler : public AsyncWebHandler, public Component { std::string &friendly_name); #endif +#ifdef USE_NUMBER + /// Return the type for prometheus + void number_type_(AsyncResponseStream *stream); + /// Return the sensor state as prometheus data point + void number_row_(AsyncResponseStream *stream, number::Number *obj, std::string &area, std::string &node, + std::string &friendly_name); +#endif + +#ifdef USE_SELECT + /// Return the type for prometheus + void select_type_(AsyncResponseStream *stream); + /// Return the select state as prometheus data point + void select_row_(AsyncResponseStream *stream, select::Select *obj, std::string &area, std::string &node, + std::string &friendly_name); +#endif + +#ifdef USE_MEDIA_PLAYER + /// Return the type for prometheus + void media_player_type_(AsyncResponseStream *stream); + /// Return the select state as prometheus data point + void media_player_row_(AsyncResponseStream *stream, media_player::MediaPlayer *obj, std::string &area, + std::string &node, std::string &friendly_name); +#endif + web_server_base::WebServerBase *base_; bool include_internal_{false}; std::map relabel_map_id_; diff --git a/tests/components/prometheus/common.yaml b/tests/components/prometheus/common.yaml index 68ef2a2f58..1b87c1d6c1 100644 --- a/tests/components/prometheus/common.yaml +++ b/tests/components/prometheus/common.yaml @@ -78,6 +78,26 @@ lock: } optimistic: true +select: + - platform: template + id: template_select1 + name: "Template select" + optimistic: true + options: + - one + - two + - three + initial_option: two + +number: + - platform: template + id: template_number1 + name: "Template number" + optimistic: true + min_value: 0 + max_value: 100 + step: 1 + prometheus: include_internal: true relabel: diff --git a/tests/components/prometheus/test.esp32-ard.yaml b/tests/components/prometheus/test.esp32-ard.yaml index dade44d145..3045a6db13 100644 --- a/tests/components/prometheus/test.esp32-ard.yaml +++ b/tests/components/prometheus/test.esp32-ard.yaml @@ -1 +1,34 @@ <<: !include common.yaml + +i2s_audio: + i2s_lrclk_pin: 1 + i2s_bclk_pin: 2 + i2s_mclk_pin: 3 + +media_player: + - platform: i2s_audio + name: "Media Player" + dac_type: external + i2s_dout_pin: 18 + mute_pin: 19 + on_state: + - media_player.play: + - media_player.play_media: http://localhost/media.mp3 + - media_player.play_media: !lambda 'return "http://localhost/media.mp3";' + on_idle: + - media_player.pause: + on_play: + - media_player.stop: + on_pause: + - media_player.toggle: + - wait_until: + media_player.is_idle: + - wait_until: + media_player.is_playing: + - wait_until: + media_player.is_announcing: + - wait_until: + media_player.is_paused: + - media_player.volume_up: + - media_player.volume_down: + - media_player.volume_set: 50%