mirror of
https://github.com/esphome/esphome.git
synced 2024-11-25 00:18:11 +01:00
Add StatsD component (#6642)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
dc4e60526c
commit
b496233425
12 changed files with 351 additions and 0 deletions
|
@ -385,6 +385,7 @@ esphome/components/st7701s/* @clydebarrow
|
||||||
esphome/components/st7735/* @SenexCrenshaw
|
esphome/components/st7735/* @SenexCrenshaw
|
||||||
esphome/components/st7789v/* @kbx81
|
esphome/components/st7789v/* @kbx81
|
||||||
esphome/components/st7920/* @marsjan155
|
esphome/components/st7920/* @marsjan155
|
||||||
|
esphome/components/statsd/* @Links2004
|
||||||
esphome/components/substitutions/* @esphome/core
|
esphome/components/substitutions/* @esphome/core
|
||||||
esphome/components/sun/* @OttoWinter
|
esphome/components/sun/* @OttoWinter
|
||||||
esphome/components/sun_gtil2/* @Mat931
|
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
|
Loading…
Reference in a new issue