diff --git a/exporter/exporter.go b/exporter/exporter.go index 89e870e..553e051 100644 --- a/exporter/exporter.go +++ b/exporter/exporter.go @@ -77,7 +77,7 @@ func (k *Exporter) Describe(ch chan<- *prometheus.Desc) { } func (k *Exporter) Collect(ch chan<- prometheus.Metric) { - s := k.client.SystemService() + s := k.client.SystemService(nil) r, err := s.GetSysInfo() if err != nil { @@ -85,32 +85,56 @@ func (k *Exporter) Collect(ch chan<- prometheus.Metric) { 0) return } - alias := r.Alias ch <- prometheus.MustNewConstMetric(k.metricsRelayState, prometheus.GaugeValue, - float64(r.RelayState), alias) + float64(r.RelayState), r.Alias) ch <- prometheus.MustNewConstMetric(k.metricsOnTime, prometheus.CounterValue, - float64(r.OnTime), alias) + float64(r.OnTime), r.Alias) ch <- prometheus.MustNewConstMetric(k.metricsRssi, prometheus.GaugeValue, - float64(r.RSSI), alias) + float64(r.RSSI), r.Alias) + + aliases := map[string]string{} + emeterContexts := []*kasa.KasaRequestContext{ + nil, // a nil context, represent the single plug or the parent strip + } + // iterrate over every child plug in a power strip + for _, children := range r.Children { + aliases[children.ID] = children.Alias + emeterContexts = append(emeterContexts, &kasa.KasaRequestContext{ + ChildIDs: []string{children.ID}, + }) + + ch <- prometheus.MustNewConstMetric(k.metricsRelayState, prometheus.GaugeValue, + float64(children.State), children.Alias) + } if s.EmeterSupported(r) { - m := k.client.EmeterService() - r, err := m.GetRealtime() + for _, ctx := range emeterContexts { + m := k.client.EmeterService(ctx) + re, err := m.GetRealtime() - if err != nil { - ch <- prometheus.MustNewConstMetric(k.metricsUp, prometheus.GaugeValue, - 0) - return + alias := r.Alias + if ctx != nil { + alias = aliases[ctx.ChildIDs[0]] + } + + // TODO: only set the child up to 0 on error + if err != nil { + ch <- prometheus.MustNewConstMetric(k.metricsUp, prometheus.GaugeValue, + 0) + return + } + + ch <- prometheus.MustNewConstMetric(k.metricsCurrent, prometheus.GaugeValue, + float64(re.Current), alias) + ch <- prometheus.MustNewConstMetric(k.metricsVoltage, prometheus.GaugeValue, + float64(re.Voltage), alias) + ch <- prometheus.MustNewConstMetric(k.metricsPowerLoad, prometheus.GaugeValue, + float64(re.Power), alias) + ch <- prometheus.MustNewConstMetric(k.metricsPowerTotal, prometheus.CounterValue, + float64(re.Total), alias) } - ch <- prometheus.MustNewConstMetric(k.metricsCurrent, prometheus.GaugeValue, - float64(r.Current), alias) - ch <- prometheus.MustNewConstMetric(k.metricsVoltage, prometheus.GaugeValue, - float64(r.Voltage), alias) - ch <- prometheus.MustNewConstMetric(k.metricsPowerLoad, prometheus.GaugeValue, - float64(r.Power), alias) - ch <- prometheus.MustNewConstMetric(k.metricsPowerTotal, prometheus.CounterValue, - float64(r.Total), alias) + } ch <- prometheus.MustNewConstMetric(k.metricsUp, prometheus.GaugeValue, diff --git a/kasa/emeter.go b/kasa/emeter.go index 25d136a..090baaf 100644 --- a/kasa/emeter.go +++ b/kasa/emeter.go @@ -1,7 +1,8 @@ package kasa type KasaClientEmeterService struct { - c *KasaClient + c *KasaClient + ctx *KasaRequestContext } type GetRealtimeRequest struct { @@ -34,7 +35,7 @@ func (s *KasaClientEmeterService) GetRealtime() (*GetRealtimeResponse, error) { response := GetRealtimeResponse{ TotalWh: -1, } - err := s.c.RPC("emeter", "get_realtime", GetRealtimeRequest{}, &response) + err := s.c.RPC("emeter", "get_realtime", s.ctx, GetRealtimeRequest{}, &response) response.Normalize() diff --git a/kasa/kasa.go b/kasa/kasa.go index 99f5538..81afbe2 100644 --- a/kasa/kasa.go +++ b/kasa/kasa.go @@ -19,6 +19,10 @@ type KasaClientConfig struct { Host string } +type KasaRequestContext struct { + ChildIDs []string `json:"child_ids"` +} + type RPCResponse struct { ErrCode int `json:"err_code"` } @@ -129,14 +133,19 @@ func (c *KasaClient) Request(payload interface{}) ([]byte, error) { return buf, nil } -func (c *KasaClient) RPC(service string, cmd string, payload interface{}, out interface{}) error { - payload = map[string]interface{}{ +func (c *KasaClient) RPC(service string, cmd string, + ctx *KasaRequestContext, payload interface{}, out interface{}) error { + + body := map[string]interface{}{ service: map[string]interface{}{ cmd: payload, }, } + if ctx != nil { + body["context"] = ctx + } - response, err := c.Request(payload) + response, err := c.Request(body) if err != nil { return err } @@ -163,14 +172,16 @@ func (c *KasaClient) RPC(service string, cmd string, payload interface{}, out in } -func (c *KasaClient) SystemService() *KasaClientSystemService { +func (c *KasaClient) SystemService(ctx *KasaRequestContext) *KasaClientSystemService { return &KasaClientSystemService{ - c: c, + c: c, + ctx: ctx, } } -func (c *KasaClient) EmeterService() *KasaClientEmeterService { +func (c *KasaClient) EmeterService(ctx *KasaRequestContext) *KasaClientEmeterService { return &KasaClientEmeterService{ - c: c, + c: c, + ctx: ctx, } } diff --git a/kasa/system.go b/kasa/system.go index 21144d0..1d422f2 100644 --- a/kasa/system.go +++ b/kasa/system.go @@ -3,26 +3,34 @@ package kasa import "strings" type KasaClientSystemService struct { - c *KasaClient + c *KasaClient + ctx *KasaRequestContext } type GetSysInfoRequest struct { } +type SysInfoChildren struct { + ID string `mapstructure:"id"` + State int `mapstructure:"state"` + Alias string `mapstructure:"alias"` +} + type GetSysInfoResponse struct { - MAC string `mapstructure:"mac"` - Model string `mapstructure:"model"` - Alias string `mapstructure:"alias"` - Feature string `mapstructure:"feature"` - RelayState int `mapstructure:"relay_state"` - RSSI int `mapstructure:"rssi"` - LEDOff int `mapstructure:"led_off"` - OnTime int `mapstructure:"on_time"` + MAC string `mapstructure:"mac"` + Model string `mapstructure:"model"` + Alias string `mapstructure:"alias"` + Feature string `mapstructure:"feature"` + RelayState int `mapstructure:"relay_state"` + RSSI int `mapstructure:"rssi"` + LEDOff int `mapstructure:"led_off"` + OnTime int `mapstructure:"on_time"` + Children []SysInfoChildren `mapstructure:"children"` } func (s *KasaClientSystemService) GetSysInfo() (*GetSysInfoResponse, error) { var response GetSysInfoResponse - err := s.c.RPC("system", "get_sysinfo", GetSysInfoRequest{}, &response) + err := s.c.RPC("system", "get_sysinfo", s.ctx, GetSysInfoRequest{}, &response) return &response, err }