This commit is contained in:
@@ -0,0 +1,514 @@
|
||||
package collector
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"lostak.dev/shelly-exporter/configuration"
|
||||
"lostak.dev/shelly-exporter/shelly"
|
||||
)
|
||||
|
||||
// These identifiers use the common Gen1 /status protocol. The generic gen1
|
||||
// identifier is available for compatible devices not explicitly listed here.
|
||||
var gen1Products = []string{
|
||||
"gen1",
|
||||
"plug", "plug_s",
|
||||
"shelly_1", "shelly_1pm", "shelly_1l", "shelly_2", "shelly_2_5", "shelly_4pro", "shelly_uni",
|
||||
"shelly_i3",
|
||||
"shelly_button1", "shelly_trv",
|
||||
"shelly_em", "shelly_3em",
|
||||
"shelly_bulb", "shelly_bulb_rgbw", "shelly_duo", "shelly_vintage", "shelly_rgbw2", "shelly_dimmer",
|
||||
"shelly_ht", "shelly_flood", "shelly_smoke", "shelly_door_window", "shelly_motion", "shelly_sense", "shelly_gas",
|
||||
}
|
||||
|
||||
func init() {
|
||||
for _, product := range gen1Products {
|
||||
if err := RegisterProduct(product, newGen1Collector); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type gen1Collector struct {
|
||||
base baseCollector
|
||||
client shelly.StatusClient
|
||||
descriptors map[string]*prometheus.Desc
|
||||
}
|
||||
|
||||
func newGen1Collector(config configuration.DeviceConfiguration, client shelly.StatusClient) (prometheus.Collector, error) {
|
||||
if client == nil {
|
||||
return nil, fmt.Errorf("client for device %q cannot be nil", config.Name)
|
||||
}
|
||||
c := &gen1Collector{
|
||||
base: newBaseCollector(config.Name, config.Product),
|
||||
client: client,
|
||||
descriptors: make(map[string]*prometheus.Desc),
|
||||
}
|
||||
c.registerDescriptors()
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *gen1Collector) registerDescriptors() {
|
||||
c.add("device_info", "Static Shelly device information.", "product", "mac", "firmware")
|
||||
c.add("wifi_info", "Shelly Wi-Fi network information.", "ssid", "ip")
|
||||
c.add("wifi_connected", "Whether the Wi-Fi station is connected (1) or not (0).")
|
||||
c.add("wifi_rssi_dbm", "Received Wi-Fi signal strength in dBm.")
|
||||
c.add("cloud_enabled", "Whether Shelly Cloud is enabled (1) or not (0).")
|
||||
c.add("cloud_connected", "Whether the device is connected to Shelly Cloud (1) or not (0).")
|
||||
c.add("mqtt_connected", "Whether the device is connected to MQTT (1) or not (0).")
|
||||
c.add("update_available", "Whether a firmware update is available (1) or not (0).")
|
||||
c.add("update_info", "Shelly firmware update information.", "status", "current_version", "new_version")
|
||||
c.add("status_serial", "Sequence number of the Shelly status response.")
|
||||
c.add("ram_size_bytes", "Total device RAM in bytes.")
|
||||
c.add("ram_free_bytes", "Free device RAM in bytes.")
|
||||
c.add("filesystem_size_bytes", "Total device filesystem size in bytes.")
|
||||
c.add("filesystem_free_bytes", "Free device filesystem space in bytes.")
|
||||
c.add("filesystem_mounted", "Whether the device data filesystem is mounted (1) or not (0).")
|
||||
c.add("uptime_seconds", "Device uptime in seconds.")
|
||||
|
||||
c.add("relay_on", "Whether the relay output is on (1) or off (0).", "index")
|
||||
c.add("relay_has_timer", "Whether a relay timer is active (1) or not (0).", "index")
|
||||
c.add("relay_timer_duration_seconds", "Configured duration of the active relay timer in seconds.", "index")
|
||||
c.add("relay_timer_remaining_seconds", "Remaining duration of the active relay timer in seconds.", "index")
|
||||
c.add("relay_overpower", "Whether relay overpower protection is active (1) or not (0).", "index")
|
||||
c.add("relay_valid", "Whether the relay status is valid (1) or not (0).", "index")
|
||||
|
||||
c.add("input_on", "Logical input state (1 for on, 0 for off).", "index")
|
||||
c.add("input_event_info", "Latest input event and event sequence.", "index", "event", "last_sequence")
|
||||
c.add("input_event_count_total", "Total input events since the device restarted.", "index")
|
||||
|
||||
c.add("meter_power_watts", "Current active power measured in watts.", "index")
|
||||
c.add("meter_valid", "Whether the meter reading is valid (1) or not (0).", "index")
|
||||
c.add("meter_timestamp_seconds", "Unix timestamp of the meter reading in seconds.", "index")
|
||||
c.add("meter_overpower", "Whether the meter reports an overpower condition (1) or not (0).", "index")
|
||||
c.add("meter_overpower_threshold_watts", "Configured meter overpower threshold in watts.", "index")
|
||||
c.add("meter_recent_energy_watt_minutes", "Recent per-minute energy reported in watt-minutes.", "index", "minute")
|
||||
c.add("meter_energy_watt_hours_total", "Total energy consumed in watt-hours, converted from the Shelly watt-minute total.", "index")
|
||||
|
||||
c.add("emeter_power_watts", "Current active power measured by an energy meter in watts.", "index")
|
||||
c.add("emeter_reactive_power_var", "Current reactive power measured by an energy meter in var.", "index")
|
||||
c.add("emeter_power_factor", "Power factor measured by an energy meter.", "index")
|
||||
c.add("emeter_current_amperes", "Current measured by an energy meter in amperes.", "index")
|
||||
c.add("emeter_voltage_volts", "RMS voltage measured by an energy meter in volts.", "index")
|
||||
c.add("emeter_valid", "Whether the energy meter reading is valid (1) or not (0).", "index")
|
||||
c.add("emeter_energy_watt_hours_total", "Total energy consumed as reported by the energy meter in watt-hours.", "index")
|
||||
c.add("emeter_returned_energy_watt_hours_total", "Total energy returned to the grid in watt-hours.", "index")
|
||||
c.add("total_power_watts", "Total active power across all device channels in watts.")
|
||||
|
||||
c.add("roller_info", "Roller operating state.", "index", "state", "stop_reason", "last_direction")
|
||||
c.add("roller_power_watts", "Current roller power consumption in watts.", "index")
|
||||
c.add("roller_valid", "Whether the roller power reading is valid (1) or not (0).", "index")
|
||||
c.add("roller_safety_switch", "Whether the roller safety switch is active (1) or not (0).", "index")
|
||||
c.add("roller_overtemperature", "Whether roller overtemperature protection is active (1) or not (0).", "index")
|
||||
c.add("roller_position_percent", "Current roller position in percent.", "index")
|
||||
c.add("roller_calibrating", "Whether roller calibration is running (1) or not (0).", "index")
|
||||
c.add("roller_positioning", "Whether roller positioning control is available (1) or not (0).", "index")
|
||||
|
||||
c.add("light_info", "Light operating mode.", "index", "mode")
|
||||
c.add("light_on", "Whether the light output is on (1) or off (0).", "index")
|
||||
c.add("light_has_timer", "Whether a light timer is active (1) or not (0).", "index")
|
||||
c.add("light_timer_remaining_seconds", "Remaining duration of the active light timer in seconds.", "index")
|
||||
c.add("light_brightness_percent", "Light brightness in percent.", "index")
|
||||
c.add("light_red", "Red channel value from 0 to 255.", "index")
|
||||
c.add("light_green", "Green channel value from 0 to 255.", "index")
|
||||
c.add("light_blue", "Blue channel value from 0 to 255.", "index")
|
||||
c.add("light_white", "White channel value from 0 to 255.", "index")
|
||||
c.add("light_gain_percent", "Color mode gain in percent.", "index")
|
||||
c.add("light_color_temperature_kelvin", "Configured white color temperature in kelvin.", "index")
|
||||
c.add("light_effect", "Selected light effect number.", "index")
|
||||
|
||||
c.add("temperature_celsius", "Device or sensor temperature in degrees Celsius.")
|
||||
c.add("temperature_valid", "Whether the temperature reading is valid (1) or not (0).")
|
||||
c.add("temperature_status_info", "Device temperature status.", "status")
|
||||
c.add("overtemperature", "Whether overtemperature protection is active (1) or not (0).")
|
||||
c.add("humidity_percent", "Relative humidity in percent.")
|
||||
c.add("humidity_valid", "Whether the humidity reading is valid (1) or not (0).")
|
||||
c.add("battery_percent", "Estimated remaining battery capacity in percent.")
|
||||
c.add("battery_voltage_volts", "Measured battery voltage in volts.")
|
||||
c.add("illuminance_lux", "Measured illuminance in lux.")
|
||||
c.add("illuminance_valid", "Whether the illuminance reading is valid (1) or not (0).")
|
||||
c.add("illuminance_info", "Classified illumination level.", "illumination")
|
||||
c.add("sensor_valid", "Whether the primary sensor is valid (1) or not (0).")
|
||||
c.add("sensor_error", "Product-specific sensor error code.")
|
||||
c.add("connect_retries", "Number of Wi-Fi connection retries during the current wake cycle.")
|
||||
c.add("motion", "Whether motion is detected (1) or not (0).")
|
||||
c.add("motion_active", "Whether motion detection is active (1) or not (0).")
|
||||
c.add("motion_timestamp_seconds", "Unix timestamp of the motion sensor reading.")
|
||||
c.add("vibration", "Whether vibration is detected (1) or not (0).")
|
||||
c.add("charger_connected", "Whether an external charger is connected (1) or not (0).")
|
||||
c.add("smoke", "Whether smoke is detected (1) or not (0).")
|
||||
c.add("flood", "Whether a flood condition is detected (1) or not (0).")
|
||||
c.add("rain_sensor_mode", "Whether rain sensor mode is enabled (1) or not (0).")
|
||||
c.add("door_window_info", "Door/window sensor state.", "state")
|
||||
c.add("tilt_degrees", "Door/window sensor tilt in degrees.")
|
||||
c.add("vibration_value", "Raw door/window vibration state; -1 means disabled.")
|
||||
c.add("vibration_time_seconds", "Door/window vibration state validity time in seconds.")
|
||||
c.add("adc_voltage_volts", "Voltage measured by an ADC input in volts.", "index")
|
||||
c.add("external_temperature_celsius", "Temperature measured by an external sensor in degrees Celsius.", "index", "hardware_id")
|
||||
c.add("external_humidity_percent", "Relative humidity measured by an external sensor in percent.", "index", "hardware_id")
|
||||
c.add("thermostat_valve_position_percent", "Thermostatic valve position in percent.", "index")
|
||||
c.add("thermostat_target_enabled", "Whether automatic target temperature control is enabled (1) or not (0).", "index")
|
||||
c.add("thermostat_target_temperature_celsius", "Thermostat target temperature in degrees Celsius.", "index")
|
||||
c.add("thermostat_temperature_celsius", "Temperature measured by the thermostat in degrees Celsius.", "index")
|
||||
c.add("thermostat_temperature_valid", "Whether the thermostat temperature reading is valid (1) or not (0).", "index")
|
||||
c.add("thermostat_schedule_enabled", "Whether the thermostat schedule is enabled (1) or not (0).", "index")
|
||||
c.add("thermostat_schedule_profile", "Selected thermostat schedule profile.", "index")
|
||||
c.add("thermostat_boost_minutes", "Remaining or configured thermostat boost duration in minutes.", "index")
|
||||
c.add("thermostat_window_open", "Whether the thermostat reports an open window (1) or not (0).", "index")
|
||||
c.add("thermostat_calibrated", "Whether the thermostatic valve is calibrated (1) or not (0).")
|
||||
|
||||
c.add("gas_concentration_ppm", "Measured combustible gas concentration in parts per million.")
|
||||
c.add("gas_concentration_valid", "Whether the gas concentration reading is valid (1) or not (0).")
|
||||
c.add("gas_sensor_info", "Gas sensor operating and alarm state.", "sensor_state", "self_test_state", "alarm_state")
|
||||
c.add("gas_valve_info", "Gas valve addon state.", "index", "state")
|
||||
}
|
||||
|
||||
func (c *gen1Collector) add(name, help string, extraLabels ...string) {
|
||||
labels := append([]string{"device"}, extraLabels...)
|
||||
c.descriptors[name] = prometheus.NewDesc("shelly_"+name, help, labels, nil)
|
||||
}
|
||||
|
||||
func (c *gen1Collector) Describe(channel chan<- *prometheus.Desc) {
|
||||
c.base.describe(channel)
|
||||
for _, descriptor := range c.descriptors {
|
||||
channel <- descriptor
|
||||
}
|
||||
}
|
||||
|
||||
func (c *gen1Collector) Collect(channel chan<- prometheus.Metric) {
|
||||
started := time.Now()
|
||||
var status gen1Status
|
||||
err := c.client.GetStatus(context.Background(), &status)
|
||||
c.base.collect(channel, err == nil, time.Since(started).Seconds())
|
||||
if err != nil {
|
||||
log.Warnf("Cannot collect status from Shelly device %q: %v", c.base.device, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.collectCommon(channel, &status)
|
||||
c.collectRelays(channel, status.Relays)
|
||||
inputs := status.Inputs
|
||||
if len(inputs) == 0 && status.Input != nil {
|
||||
inputs = []gen1Input{{Input: status.Input}}
|
||||
}
|
||||
c.collectInputs(channel, inputs)
|
||||
c.collectMeters(channel, status.Meters)
|
||||
c.collectEMeters(channel, status.EMeters)
|
||||
c.collectRollers(channel, status.Rollers)
|
||||
c.collectLights(channel, status.Lights)
|
||||
c.collectThermostats(channel, &status)
|
||||
c.collectSensors(channel, &status)
|
||||
}
|
||||
|
||||
func (c *gen1Collector) collectCommon(channel chan<- prometheus.Metric, status *gen1Status) {
|
||||
firmware := ""
|
||||
if status.Update != nil {
|
||||
firmware = status.Update.OldVersion
|
||||
}
|
||||
c.gauge(channel, "device_info", 1, c.base.product, status.MAC, firmware)
|
||||
|
||||
if status.WiFiStation != nil {
|
||||
c.gauge(channel, "wifi_info", 1, status.WiFiStation.SSID, status.WiFiStation.IP)
|
||||
c.gauge(channel, "wifi_connected", boolFloat(status.WiFiStation.Connected))
|
||||
c.gauge(channel, "wifi_rssi_dbm", float64(status.WiFiStation.RSSI))
|
||||
}
|
||||
if status.Cloud != nil {
|
||||
c.gauge(channel, "cloud_enabled", boolFloat(status.Cloud.Enabled))
|
||||
c.gauge(channel, "cloud_connected", boolFloat(status.Cloud.Connected))
|
||||
}
|
||||
if status.MQTT != nil {
|
||||
c.gauge(channel, "mqtt_connected", boolFloat(status.MQTT.Connected))
|
||||
}
|
||||
if status.HasUpdate != nil || status.Update != nil {
|
||||
available := status.HasUpdate != nil && *status.HasUpdate
|
||||
if status.Update != nil {
|
||||
available = available || status.Update.HasUpdate
|
||||
}
|
||||
c.gauge(channel, "update_available", boolFloat(available))
|
||||
}
|
||||
if status.Update != nil {
|
||||
c.gauge(channel, "update_info", 1, status.Update.Status, status.Update.OldVersion, status.Update.NewVersion)
|
||||
}
|
||||
if status.Serial != nil {
|
||||
c.gauge(channel, "status_serial", float64(*status.Serial))
|
||||
}
|
||||
if status.RAMTotal != nil {
|
||||
c.gauge(channel, "ram_size_bytes", float64(*status.RAMTotal))
|
||||
}
|
||||
if status.RAMFree != nil {
|
||||
c.gauge(channel, "ram_free_bytes", float64(*status.RAMFree))
|
||||
}
|
||||
if status.FSSize != nil {
|
||||
c.gauge(channel, "filesystem_size_bytes", float64(*status.FSSize))
|
||||
}
|
||||
if status.FSFree != nil {
|
||||
c.gauge(channel, "filesystem_free_bytes", float64(*status.FSFree))
|
||||
}
|
||||
if status.FSMounted != nil {
|
||||
c.gauge(channel, "filesystem_mounted", boolFloat(*status.FSMounted))
|
||||
}
|
||||
if status.Uptime != nil {
|
||||
c.gauge(channel, "uptime_seconds", *status.Uptime)
|
||||
}
|
||||
if status.TotalPower != nil {
|
||||
c.gauge(channel, "total_power_watts", *status.TotalPower)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *gen1Collector) collectRelays(channel chan<- prometheus.Metric, relays []gen1Relay) {
|
||||
for index, relay := range relays {
|
||||
label := strconv.Itoa(index)
|
||||
c.optionalBool(channel, "relay_on", relay.On, label)
|
||||
c.optionalBool(channel, "relay_has_timer", relay.HasTimer, label)
|
||||
c.optionalGauge(channel, "relay_timer_duration_seconds", relay.TimerDuration, label)
|
||||
c.optionalGauge(channel, "relay_timer_remaining_seconds", relay.TimerRemaining, label)
|
||||
c.optionalBool(channel, "relay_overpower", relay.Overpower, label)
|
||||
c.optionalBool(channel, "relay_valid", relay.Valid, label)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *gen1Collector) collectInputs(channel chan<- prometheus.Metric, inputs []gen1Input) {
|
||||
for index, input := range inputs {
|
||||
label := strconv.Itoa(index)
|
||||
c.optionalGauge(channel, "input_on", input.Input, label)
|
||||
if input.Event != "" || input.LastSequence != "" {
|
||||
c.gauge(channel, "input_event_info", 1, label, input.Event, input.LastSequence)
|
||||
}
|
||||
if input.EventCount != nil && *input.EventCount >= 0 {
|
||||
c.counter(channel, "input_event_count_total", *input.EventCount, label)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *gen1Collector) collectMeters(channel chan<- prometheus.Metric, meters []gen1Meter) {
|
||||
for index, meter := range meters {
|
||||
label := strconv.Itoa(index)
|
||||
c.optionalGauge(channel, "meter_power_watts", meter.Power, label)
|
||||
c.optionalBool(channel, "meter_valid", meter.Valid, label)
|
||||
c.optionalGauge(channel, "meter_timestamp_seconds", meter.Timestamp, label)
|
||||
if meter.Overpower != nil {
|
||||
c.optionalGauge(channel, "meter_overpower_threshold_watts", meter.Overpower.Number, label)
|
||||
c.optionalBool(channel, "meter_overpower", meter.Overpower.Bool, label)
|
||||
}
|
||||
if meter.Total != nil && *meter.Total >= 0 {
|
||||
c.counter(channel, "meter_energy_watt_hours_total", *meter.Total/60, label)
|
||||
}
|
||||
for minute, recentEnergy := range meter.Counters {
|
||||
c.gauge(channel, "meter_recent_energy_watt_minutes", recentEnergy, label, strconv.Itoa(minute))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *gen1Collector) collectEMeters(channel chan<- prometheus.Metric, meters []gen1EMeter) {
|
||||
for index, meter := range meters {
|
||||
label := strconv.Itoa(index)
|
||||
c.optionalGauge(channel, "emeter_power_watts", meter.Power, label)
|
||||
c.optionalGauge(channel, "emeter_reactive_power_var", meter.ReactivePower, label)
|
||||
c.optionalGauge(channel, "emeter_power_factor", meter.PowerFactor, label)
|
||||
c.optionalGauge(channel, "emeter_current_amperes", meter.Current, label)
|
||||
c.optionalGauge(channel, "emeter_voltage_volts", meter.Voltage, label)
|
||||
c.optionalBool(channel, "emeter_valid", meter.Valid, label)
|
||||
if meter.Total != nil && *meter.Total >= 0 {
|
||||
c.counter(channel, "emeter_energy_watt_hours_total", *meter.Total, label)
|
||||
}
|
||||
if meter.TotalReturned != nil && *meter.TotalReturned >= 0 {
|
||||
c.counter(channel, "emeter_returned_energy_watt_hours_total", *meter.TotalReturned, label)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *gen1Collector) collectRollers(channel chan<- prometheus.Metric, rollers []gen1Roller) {
|
||||
for index, roller := range rollers {
|
||||
label := strconv.Itoa(index)
|
||||
if roller.State != "" || roller.StopReason != "" || roller.LastDirection != "" {
|
||||
c.gauge(channel, "roller_info", 1, label, roller.State, roller.StopReason, roller.LastDirection)
|
||||
}
|
||||
c.optionalGauge(channel, "roller_power_watts", roller.Power, label)
|
||||
c.optionalBool(channel, "roller_valid", roller.Valid, label)
|
||||
c.optionalBool(channel, "roller_safety_switch", roller.SafetySwitch, label)
|
||||
c.optionalBool(channel, "roller_overtemperature", roller.Overtemperature, label)
|
||||
c.optionalGauge(channel, "roller_position_percent", roller.CurrentPosition, label)
|
||||
c.optionalBool(channel, "roller_calibrating", roller.Calibrating, label)
|
||||
c.optionalBool(channel, "roller_positioning", roller.Positioning, label)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *gen1Collector) collectLights(channel chan<- prometheus.Metric, lights []gen1Light) {
|
||||
for index, light := range lights {
|
||||
label := strconv.Itoa(index)
|
||||
if light.Mode != "" {
|
||||
c.gauge(channel, "light_info", 1, label, light.Mode)
|
||||
}
|
||||
c.optionalBool(channel, "light_on", light.On, label)
|
||||
c.optionalBool(channel, "light_has_timer", light.HasTimer, label)
|
||||
c.optionalGauge(channel, "light_timer_remaining_seconds", light.TimerRemaining, label)
|
||||
c.optionalGauge(channel, "light_brightness_percent", light.Brightness, label)
|
||||
c.optionalGauge(channel, "light_red", light.Red, label)
|
||||
c.optionalGauge(channel, "light_green", light.Green, label)
|
||||
c.optionalGauge(channel, "light_blue", light.Blue, label)
|
||||
c.optionalGauge(channel, "light_white", light.White, label)
|
||||
c.optionalGauge(channel, "light_gain_percent", light.Gain, label)
|
||||
c.optionalGauge(channel, "light_color_temperature_kelvin", light.Temperature, label)
|
||||
c.optionalGauge(channel, "light_effect", light.Effect, label)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *gen1Collector) collectThermostats(channel chan<- prometheus.Metric, status *gen1Status) {
|
||||
if len(status.Thermostats) > 0 {
|
||||
c.optionalBool(channel, "thermostat_calibrated", status.Calibrated)
|
||||
}
|
||||
for index, thermostat := range status.Thermostats {
|
||||
label := strconv.Itoa(index)
|
||||
c.optionalGauge(channel, "thermostat_valve_position_percent", thermostat.Position, label)
|
||||
if thermostat.Target != nil {
|
||||
c.optionalBool(channel, "thermostat_target_enabled", thermostat.Target.Enabled, label)
|
||||
if thermostat.Target.Units == "C" {
|
||||
c.optionalGauge(channel, "thermostat_target_temperature_celsius", thermostat.Target.Value, label)
|
||||
}
|
||||
}
|
||||
if thermostat.Temperature != nil {
|
||||
temperature := thermostat.Temperature.C
|
||||
if temperature == nil && thermostat.Temperature.Units == "C" {
|
||||
temperature = thermostat.Temperature.Value
|
||||
}
|
||||
c.optionalGauge(channel, "thermostat_temperature_celsius", temperature, label)
|
||||
c.optionalBool(channel, "thermostat_temperature_valid", thermostat.Temperature.Valid, label)
|
||||
}
|
||||
c.optionalBool(channel, "thermostat_schedule_enabled", thermostat.Schedule, label)
|
||||
c.optionalGauge(channel, "thermostat_schedule_profile", thermostat.ScheduleProfile, label)
|
||||
c.optionalGauge(channel, "thermostat_boost_minutes", thermostat.BoostMinutes, label)
|
||||
c.optionalBool(channel, "thermostat_window_open", thermostat.WindowOpen, label)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *gen1Collector) collectSensors(channel chan<- prometheus.Metric, status *gen1Status) {
|
||||
temperature := status.Temperature
|
||||
if temperature == nil && status.TemperatureSensor != nil {
|
||||
temperature = status.TemperatureSensor.C
|
||||
if temperature == nil && status.TemperatureSensor.Units == "C" {
|
||||
temperature = status.TemperatureSensor.Value
|
||||
}
|
||||
}
|
||||
c.optionalGauge(channel, "temperature_celsius", temperature)
|
||||
if status.TemperatureSensor != nil {
|
||||
c.optionalBool(channel, "temperature_valid", status.TemperatureSensor.Valid)
|
||||
}
|
||||
if status.TemperatureStatus != "" {
|
||||
c.gauge(channel, "temperature_status_info", 1, status.TemperatureStatus)
|
||||
}
|
||||
c.optionalBool(channel, "overtemperature", status.Overtemperature)
|
||||
if status.Humidity != nil {
|
||||
c.optionalGauge(channel, "humidity_percent", status.Humidity.Value)
|
||||
c.optionalBool(channel, "humidity_valid", status.Humidity.Valid)
|
||||
}
|
||||
if status.Battery != nil {
|
||||
c.optionalGauge(channel, "battery_percent", status.Battery.Value)
|
||||
c.optionalGauge(channel, "battery_voltage_volts", status.Battery.Voltage)
|
||||
}
|
||||
if status.Lux != nil {
|
||||
c.optionalGauge(channel, "illuminance_lux", status.Lux.Value)
|
||||
c.optionalBool(channel, "illuminance_valid", status.Lux.Valid)
|
||||
if status.Lux.Illumination != "" {
|
||||
c.gauge(channel, "illuminance_info", 1, status.Lux.Illumination)
|
||||
}
|
||||
}
|
||||
c.optionalGauge(channel, "sensor_error", status.SensorError)
|
||||
c.optionalGauge(channel, "connect_retries", status.ConnectRetries)
|
||||
c.optionalBool(channel, "charger_connected", status.Charger)
|
||||
c.optionalBool(channel, "smoke", status.Smoke)
|
||||
c.optionalBool(channel, "flood", status.Flood)
|
||||
c.optionalBool(channel, "rain_sensor_mode", status.RainSensor)
|
||||
|
||||
motion := status.Motion
|
||||
sensorValid := status.Valid
|
||||
if status.Sensor != nil {
|
||||
if status.Sensor.Motion != nil {
|
||||
motion = status.Sensor.Motion
|
||||
}
|
||||
if status.Sensor.Valid != nil {
|
||||
sensorValid = status.Sensor.Valid
|
||||
}
|
||||
c.optionalBool(channel, "motion_active", status.Sensor.Active)
|
||||
c.optionalGauge(channel, "motion_timestamp_seconds", status.Sensor.Timestamp)
|
||||
c.optionalBool(channel, "vibration", status.Sensor.Vibration)
|
||||
if status.Sensor.State != "" {
|
||||
c.gauge(channel, "door_window_info", 1, status.Sensor.State)
|
||||
}
|
||||
}
|
||||
c.optionalBool(channel, "motion", motion)
|
||||
c.optionalBool(channel, "sensor_valid", sensorValid)
|
||||
if status.Acceleration != nil {
|
||||
c.optionalGauge(channel, "tilt_degrees", status.Acceleration.Tilt)
|
||||
c.optionalGauge(channel, "vibration_value", status.Acceleration.Vibration)
|
||||
c.optionalGauge(channel, "vibration_time_seconds", status.Acceleration.VibrationTime)
|
||||
}
|
||||
|
||||
for index, adc := range status.ADCs {
|
||||
c.optionalGauge(channel, "adc_voltage_volts", adc.Voltage, strconv.Itoa(index))
|
||||
}
|
||||
for _, index := range sortedKeys(status.ExternalTemperature) {
|
||||
sensor := status.ExternalTemperature[index]
|
||||
c.optionalGauge(channel, "external_temperature_celsius", sensor.C, index, sensor.HardwareID)
|
||||
}
|
||||
for _, index := range sortedKeys(status.ExternalHumidity) {
|
||||
sensor := status.ExternalHumidity[index]
|
||||
c.optionalGauge(channel, "external_humidity_percent", sensor.Humidity, index, sensor.HardwareID)
|
||||
}
|
||||
|
||||
if status.GasSensor != nil {
|
||||
c.gauge(channel, "gas_sensor_info", 1, status.GasSensor.SensorState, status.GasSensor.SelfTestState, status.GasSensor.AlarmState)
|
||||
}
|
||||
if status.Concentration != nil {
|
||||
c.optionalGauge(channel, "gas_concentration_ppm", status.Concentration.PPM)
|
||||
c.optionalBool(channel, "gas_concentration_valid", status.Concentration.Valid)
|
||||
}
|
||||
for index, valve := range status.Valves {
|
||||
c.gauge(channel, "gas_valve_info", 1, strconv.Itoa(index), valve.State)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *gen1Collector) gauge(channel chan<- prometheus.Metric, name string, value float64, labels ...string) {
|
||||
c.emit(channel, name, prometheus.GaugeValue, value, labels...)
|
||||
}
|
||||
|
||||
func (c *gen1Collector) counter(channel chan<- prometheus.Metric, name string, value float64, labels ...string) {
|
||||
c.emit(channel, name, prometheus.CounterValue, value, labels...)
|
||||
}
|
||||
|
||||
func (c *gen1Collector) emit(channel chan<- prometheus.Metric, name string, valueType prometheus.ValueType, value float64, labels ...string) {
|
||||
allLabels := append([]string{c.base.device}, labels...)
|
||||
channel <- prometheus.MustNewConstMetric(c.descriptors[name], valueType, value, allLabels...)
|
||||
}
|
||||
|
||||
func (c *gen1Collector) optionalGauge(channel chan<- prometheus.Metric, name string, value *float64, labels ...string) {
|
||||
if value != nil {
|
||||
c.gauge(channel, name, *value, labels...)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *gen1Collector) optionalBool(channel chan<- prometheus.Metric, name string, value *bool, labels ...string) {
|
||||
if value != nil {
|
||||
c.gauge(channel, name, boolFloat(*value), labels...)
|
||||
}
|
||||
}
|
||||
|
||||
func boolFloat(value bool) float64 {
|
||||
if value {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func sortedKeys[T any](values map[string]T) []string {
|
||||
keys := make([]string, 0, len(values))
|
||||
for key := range values {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
Reference in New Issue
Block a user