mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 06:33:51 +00:00 
			
		
		
		
	Add StatsD component (#6642)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
		| @@ -385,6 +385,7 @@ esphome/components/st7701s/* @clydebarrow | ||||
| esphome/components/st7735/* @SenexCrenshaw | ||||
| esphome/components/st7789v/* @kbx81 | ||||
| esphome/components/st7920/* @marsjan155 | ||||
| esphome/components/statsd/* @Links2004 | ||||
| esphome/components/substitutions/* @esphome/core | ||||
| esphome/components/sun/* @OttoWinter | ||||
| esphome/components/sun_gtil2/* @Mat931 | ||||
|   | ||||
							
								
								
									
										65
									
								
								esphome/components/statsd/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								esphome/components/statsd/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, binary_sensor | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_PORT, | ||||
|     CONF_NAME, | ||||
|     CONF_SENSORS, | ||||
|     CONF_BINARY_SENSORS, | ||||
| ) | ||||
|  | ||||
| AUTO_LOAD = ["socket"] | ||||
| CODEOWNERS = ["@Links2004"] | ||||
| DEPENDENCIES = ["network"] | ||||
|  | ||||
| CONF_HOST = "host" | ||||
| CONF_PREFIX = "prefix" | ||||
|  | ||||
| statsd_component_ns = cg.esphome_ns.namespace("statsd") | ||||
| StatsdComponent = statsd_component_ns.class_("StatsdComponent", cg.PollingComponent) | ||||
|  | ||||
| CONFIG_SENSORS_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.Required(CONF_ID): cv.use_id(sensor.Sensor), | ||||
|         cv.Required(CONF_NAME): cv.string_strict, | ||||
|     } | ||||
| ) | ||||
|  | ||||
| CONFIG_BINARY_SENSORS_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.Required(CONF_ID): cv.use_id(binary_sensor.BinarySensor), | ||||
|         cv.Required(CONF_NAME): cv.string_strict, | ||||
|     } | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(StatsdComponent), | ||||
|         cv.Required(CONF_HOST): cv.string_strict, | ||||
|         cv.Optional(CONF_PORT, default=8125): cv.port, | ||||
|         cv.Optional(CONF_PREFIX, default=""): cv.string_strict, | ||||
|         cv.Optional(CONF_SENSORS): cv.ensure_list(CONFIG_SENSORS_SCHEMA), | ||||
|         cv.Optional(CONF_BINARY_SENSORS): cv.ensure_list(CONFIG_BINARY_SENSORS_SCHEMA), | ||||
|     } | ||||
| ).extend(cv.polling_component_schema("10s")) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     cg.add( | ||||
|         var.configure( | ||||
|             config.get(CONF_HOST), | ||||
|             config.get(CONF_PORT), | ||||
|             config.get(CONF_PREFIX), | ||||
|         ) | ||||
|     ) | ||||
|  | ||||
|     for sensor_cfg in config.get(CONF_SENSORS, []): | ||||
|         s = await cg.get_variable(sensor_cfg[CONF_ID]) | ||||
|         cg.add(var.register_sensor(sensor_cfg[CONF_NAME], s)) | ||||
|  | ||||
|     for sensor_cfg in config.get(CONF_BINARY_SENSORS, []): | ||||
|         s = await cg.get_variable(sensor_cfg[CONF_ID]) | ||||
|         cg.add(var.register_binary_sensor(sensor_cfg[CONF_NAME], s)) | ||||
							
								
								
									
										156
									
								
								esphome/components/statsd/statsd.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								esphome/components/statsd/statsd.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,156 @@ | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #include "statsd.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace statsd { | ||||
|  | ||||
| // send UDP packet if we reach 1Kb packed size | ||||
| // this is needed since statsD does not support fragmented UDP packets | ||||
| static const uint16_t SEND_THRESHOLD = 1024; | ||||
|  | ||||
| static const char *const TAG = "statsD"; | ||||
|  | ||||
| void StatsdComponent::setup() { | ||||
| #ifndef USE_ESP8266 | ||||
|   this->sock_ = esphome::socket::socket(AF_INET, SOCK_DGRAM, 0); | ||||
|  | ||||
|   struct sockaddr_in source; | ||||
|   source.sin_family = AF_INET; | ||||
|   source.sin_addr.s_addr = htonl(INADDR_ANY); | ||||
|   source.sin_port = htons(this->port_); | ||||
|   this->sock_->bind((struct sockaddr *) &source, sizeof(source)); | ||||
|  | ||||
|   this->destination_.sin_family = AF_INET; | ||||
|   this->destination_.sin_port = htons(this->port_); | ||||
|   this->destination_.sin_addr.s_addr = inet_addr(this->host_); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| StatsdComponent::~StatsdComponent() { | ||||
| #ifndef USE_ESP8266 | ||||
|   if (!this->sock_) { | ||||
|     return; | ||||
|   } | ||||
|   this->sock_->close(); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void StatsdComponent::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "statsD:"); | ||||
|   ESP_LOGCONFIG(TAG, "  host: %s", this->host_); | ||||
|   ESP_LOGCONFIG(TAG, "  port: %d", this->port_); | ||||
|   if (this->prefix_) { | ||||
|     ESP_LOGCONFIG(TAG, "  prefix: %s", this->prefix_); | ||||
|   } | ||||
|  | ||||
|   ESP_LOGCONFIG(TAG, "  metrics:"); | ||||
|   for (sensors_t s : this->sensors_) { | ||||
|     ESP_LOGCONFIG(TAG, "    - name: %s", s.name); | ||||
|     ESP_LOGCONFIG(TAG, "      type: %d", s.type); | ||||
|   } | ||||
| } | ||||
|  | ||||
| float StatsdComponent::get_setup_priority() const { return esphome::setup_priority::AFTER_WIFI; } | ||||
|  | ||||
| #ifdef USE_SENSOR | ||||
| void StatsdComponent::register_sensor(const char *name, esphome::sensor::Sensor *sensor) { | ||||
|   sensors_t s; | ||||
|   s.name = name; | ||||
|   s.sensor = sensor; | ||||
|   s.type = TYPE_SENSOR; | ||||
|   this->sensors_.push_back(s); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_BINARY_SENSOR | ||||
| void StatsdComponent::register_binary_sensor(const char *name, esphome::binary_sensor::BinarySensor *binary_sensor) { | ||||
|   sensors_t s; | ||||
|   s.name = name; | ||||
|   s.binary_sensor = binary_sensor; | ||||
|   s.type = TYPE_BINARY_SENSOR; | ||||
|   this->sensors_.push_back(s); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| void StatsdComponent::update() { | ||||
|   std::string out; | ||||
|   out.reserve(SEND_THRESHOLD); | ||||
|  | ||||
|   for (sensors_t s : this->sensors_) { | ||||
|     double val = 0; | ||||
|     switch (s.type) { | ||||
| #ifdef USE_SENSOR | ||||
|       case TYPE_SENSOR: | ||||
|         if (!s.sensor->has_state()) { | ||||
|           continue; | ||||
|         } | ||||
|         val = s.sensor->state; | ||||
|         break; | ||||
| #endif | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|       case TYPE_BINARY_SENSOR: | ||||
|         if (!s.binary_sensor->has_state()) { | ||||
|           continue; | ||||
|         } | ||||
|         // map bool to double | ||||
|         if (s.binary_sensor->state) { | ||||
|           val = 1; | ||||
|         } | ||||
|         break; | ||||
| #endif | ||||
|       default: | ||||
|         ESP_LOGE(TAG, "type not known, name: %s type: %d", s.name, s.type); | ||||
|         continue; | ||||
|     } | ||||
|  | ||||
|     // statsD gauge: | ||||
|     // https://github.com/statsd/statsd/blob/master/docs/metric_types.md | ||||
|     // This implies you can't explicitly set a gauge to a negative number without first setting it to zero. | ||||
|     if (val < 0) { | ||||
|       if (this->prefix_) { | ||||
|         out.append(str_sprintf("%s.", this->prefix_)); | ||||
|       } | ||||
|       out.append(str_sprintf("%s:0|g\n", s.name)); | ||||
|     } | ||||
|     if (this->prefix_) { | ||||
|       out.append(str_sprintf("%s.", this->prefix_)); | ||||
|     } | ||||
|     out.append(str_sprintf("%s:%f|g\n", s.name, val)); | ||||
|  | ||||
|     if (out.length() > SEND_THRESHOLD) { | ||||
|       this->send_(&out); | ||||
|       out.clear(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   this->send_(&out); | ||||
| } | ||||
|  | ||||
| void StatsdComponent::send_(std::string *out) { | ||||
|   if (out->empty()) { | ||||
|     return; | ||||
|   } | ||||
| #ifdef USE_ESP8266 | ||||
|   IPAddress ip; | ||||
|   ip.fromString(this->host_); | ||||
|  | ||||
|   this->sock_.beginPacket(ip, this->port_); | ||||
|   this->sock_.write((const uint8_t *) out->c_str(), out->length()); | ||||
|   this->sock_.endPacket(); | ||||
|  | ||||
| #else | ||||
|   if (!this->sock_) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   int n_bytes = this->sock_->sendto(out->c_str(), out->length(), 0, reinterpret_cast<sockaddr *>(&this->destination_), | ||||
|                                     sizeof(this->destination_)); | ||||
|   if (n_bytes != out->length()) { | ||||
|     ESP_LOGE(TAG, "Failed to send UDP packed (%d of %d)", n_bytes, out->length()); | ||||
|   } | ||||
| #endif | ||||
| } | ||||
|  | ||||
| }  // namespace statsd | ||||
| }  // namespace esphome | ||||
							
								
								
									
										86
									
								
								esphome/components/statsd/statsd.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								esphome/components/statsd/statsd.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <vector> | ||||
|  | ||||
| #include "esphome/core/defines.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/socket/socket.h" | ||||
| #include "esphome/components/network/ip_address.h" | ||||
|  | ||||
| #ifdef USE_SENSOR | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_BINARY_SENSOR | ||||
| #include "esphome/components/binary_sensor/binary_sensor.h" | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_LOGGER | ||||
| #include "esphome/components/logger/logger.h" | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_ESP8266 | ||||
| #include "WiFiUdp.h" | ||||
| #include "IPAddress.h" | ||||
| #endif | ||||
|  | ||||
| namespace esphome { | ||||
| namespace statsd { | ||||
|  | ||||
| using sensor_type_t = enum { TYPE_SENSOR, TYPE_BINARY_SENSOR }; | ||||
|  | ||||
| using sensors_t = struct { | ||||
|   const char *name; | ||||
|   sensor_type_t type; | ||||
|   union { | ||||
| #ifdef USE_SENSOR | ||||
|     esphome::sensor::Sensor *sensor; | ||||
| #endif | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|     esphome::binary_sensor::BinarySensor *binary_sensor; | ||||
| #endif | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| class StatsdComponent : public PollingComponent { | ||||
|  public: | ||||
|   ~StatsdComponent(); | ||||
|  | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   void update() override; | ||||
|   float get_setup_priority() const override; | ||||
|  | ||||
|   void configure(const char *host, uint16_t port, const char *prefix) { | ||||
|     this->host_ = host; | ||||
|     this->port_ = port; | ||||
|     this->prefix_ = prefix; | ||||
|   } | ||||
|  | ||||
| #ifdef USE_SENSOR | ||||
|   void register_sensor(const char *name, esphome::sensor::Sensor *sensor); | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   void register_binary_sensor(const char *name, esphome::binary_sensor::BinarySensor *binary_sensor); | ||||
| #endif | ||||
|  | ||||
|  private: | ||||
|   const char *host_; | ||||
|   const char *prefix_; | ||||
|   uint16_t port_; | ||||
|  | ||||
|   std::vector<sensors_t> sensors_; | ||||
|  | ||||
| #ifdef USE_ESP8266 | ||||
|   WiFiUDP sock_; | ||||
| #else | ||||
|   std::unique_ptr<esphome::socket::Socket> sock_; | ||||
|   struct sockaddr_in destination_; | ||||
| #endif | ||||
|  | ||||
|   void send_(std::string *out); | ||||
| }; | ||||
|  | ||||
| }  // namespace statsd | ||||
| }  // namespace esphome | ||||
							
								
								
									
										29
									
								
								tests/components/statsD/common.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								tests/components/statsD/common.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| wifi: | ||||
|   ssid: MySSID | ||||
|   password: password1 | ||||
|  | ||||
| statsd: | ||||
|   host: "192.168.1.1" | ||||
|   port: 8125 | ||||
|   prefix: esphome | ||||
|   update_interval: 60s | ||||
|   sensors: | ||||
|     id: s | ||||
|     name: sensors | ||||
|   binary_sensors: | ||||
|     id: bs | ||||
|     name: binary_sensors | ||||
|  | ||||
| sensor: | ||||
|   - platform: template | ||||
|     id: s | ||||
|     name: "42.1" | ||||
|     lambda: |- | ||||
|         return 42.1f; | ||||
|  | ||||
| binary_sensor: | ||||
|   - platform: template | ||||
|     id: bs | ||||
|     name: "On" | ||||
|     lambda: |- | ||||
|         return true; | ||||
							
								
								
									
										2
									
								
								tests/components/statsD/test.bk72xx-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/components/statsD/test.bk72xx-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| packages: | ||||
|   common: !include common.yaml | ||||
							
								
								
									
										2
									
								
								tests/components/statsD/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/components/statsD/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| packages: | ||||
|   common: !include common.yaml | ||||
							
								
								
									
										2
									
								
								tests/components/statsD/test.esp32-c3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/components/statsD/test.esp32-c3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| packages: | ||||
|   common: !include common.yaml | ||||
							
								
								
									
										2
									
								
								tests/components/statsD/test.esp32-c3-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/components/statsD/test.esp32-c3-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| packages: | ||||
|   common: !include common.yaml | ||||
							
								
								
									
										2
									
								
								tests/components/statsD/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/components/statsD/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| packages: | ||||
|   common: !include common.yaml | ||||
							
								
								
									
										2
									
								
								tests/components/statsD/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/components/statsD/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| packages: | ||||
|   common: !include common.yaml | ||||
							
								
								
									
										2
									
								
								tests/components/statsD/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/components/statsD/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| packages: | ||||
|   common: !include common.yaml | ||||
		Reference in New Issue
	
	Block a user