Skip to content

Commit

Permalink
feat: add deviceId to all metrics, add kasa_metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
fffonion committed Dec 7, 2020
1 parent dd56d46 commit 2437246
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 27 deletions.
46 changes: 35 additions & 11 deletions exporter/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ package exporter
import (
"github.com/fffonion/tplink-plug-exporter/kasa"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/log"
)

type Exporter struct {
target string
client *kasa.KasaClient

metricsUp,
metricsMetadata,
metricsRelayState,
metricsOnTime,
metricsRssi,
Expand All @@ -26,7 +28,7 @@ type ExporterTarget struct {
func NewExporter(t *ExporterTarget) *Exporter {
var (
constLabels = prometheus.Labels{}
labelNames = []string{"alias"}
labelNames = []string{"alias", "id"}
)

e := &Exporter{
Expand All @@ -38,6 +40,14 @@ func NewExporter(t *ExporterTarget) *Exporter {
"Device online.",
nil, constLabels,
),

metricsMetadata: prometheus.NewDesc("kasa_metadata",
"Device metadata.",
[]string{
"alias", "hw_ver", "sw_ver", "model", "feature",
}, constLabels,
),

metricsRelayState: prometheus.NewDesc("kasa_relay_state",
"Relay state (switch on/off).",
labelNames, constLabels,
Expand All @@ -48,6 +58,7 @@ func NewExporter(t *ExporterTarget) *Exporter {
metricsRssi: prometheus.NewDesc("kasa_rssi",
"Wifi received signal strength indicator.",
labelNames, constLabels),

metricsCurrent: prometheus.NewDesc("kasa_current",
"Current flowing through device in Ampere.",
labelNames, constLabels),
Expand Down Expand Up @@ -83,20 +94,26 @@ func (k *Exporter) Collect(ch chan<- prometheus.Metric) {
if err != nil {
ch <- prometheus.MustNewConstMetric(k.metricsUp, prometheus.GaugeValue,
0)
log.Errorln("error collecting", k.target, ":", err)
return
}

// "alias", "hw_ver", "sw_ver", "model", "feature",
ch <- prometheus.MustNewConstMetric(k.metricsMetadata, prometheus.GaugeValue,
1, r.Alias, r.HardwareVersion, r.SoftwareVersion, r.Model, r.Feature)

ch <- prometheus.MustNewConstMetric(k.metricsRelayState, prometheus.GaugeValue,
float64(r.RelayState), r.Alias)
float64(r.RelayState), r.Alias, r.DeviceID)
ch <- prometheus.MustNewConstMetric(k.metricsOnTime, prometheus.CounterValue,
float64(r.OnTime), r.Alias)
float64(r.OnTime), r.Alias, r.DeviceID)
ch <- prometheus.MustNewConstMetric(k.metricsRssi, prometheus.GaugeValue,
float64(r.RSSI), r.Alias)
float64(r.RSSI), r.Alias, r.DeviceID)

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
Expand All @@ -105,34 +122,41 @@ func (k *Exporter) Collect(ch chan<- prometheus.Metric) {
})

ch <- prometheus.MustNewConstMetric(k.metricsRelayState, prometheus.GaugeValue,
float64(children.State), children.Alias)
float64(children.State), children.Alias, children.ID)

ch <- prometheus.MustNewConstMetric(k.metricsOnTime, prometheus.CounterValue,
float64(children.OnTime), children.Alias, children.ID)
}

if s.EmeterSupported(r) {
for _, ctx := range emeterContexts {
m := k.client.EmeterService(ctx)
re, err := m.GetRealtime()

alias := r.Alias
labels := []string{r.Alias, r.DeviceID}
// if this is a child plug in a powerstrip, set the alias and ID
if ctx != nil {
alias = aliases[ctx.ChildIDs[0]]
id := ctx.ChildIDs[0]
labels[0] = aliases[id]
labels[1] = id
}

// TODO: only set the child up to 0 on error
if err != nil {
ch <- prometheus.MustNewConstMetric(k.metricsUp, prometheus.GaugeValue,
0)
log.Errorln("error collecting", k.target, ":", err)
return
}

ch <- prometheus.MustNewConstMetric(k.metricsCurrent, prometheus.GaugeValue,
float64(re.Current), alias)
float64(re.Current), labels...)
ch <- prometheus.MustNewConstMetric(k.metricsVoltage, prometheus.GaugeValue,
float64(re.Voltage), alias)
float64(re.Voltage), labels...)
ch <- prometheus.MustNewConstMetric(k.metricsPowerLoad, prometheus.GaugeValue,
float64(re.Power), alias)
float64(re.Power), labels...)
ch <- prometheus.MustNewConstMetric(k.metricsPowerTotal, prometheus.CounterValue,
float64(re.Total), alias)
float64(re.Total), labels...)
}

}
Expand Down
10 changes: 6 additions & 4 deletions kasa/kasa.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ func (c *KasaClient) Request(payload interface{}) ([]byte, error) {
func (c *KasaClient) RPC(service string, cmd string,
ctx *KasaRequestContext, payload interface{}, out interface{}) error {

errmsgPrefix := fmt.Sprintf("%s.%s", service, cmd)

body := map[string]interface{}{
service: map[string]interface{}{
cmd: payload,
Expand All @@ -147,23 +149,23 @@ func (c *KasaClient) RPC(service string, cmd string,

response, err := c.Request(body)
if err != nil {
return err
return fmt.Errorf("%s: %v", errmsgPrefix, err)
}

var outMarshal map[string]map[string]map[string]interface{}

err = json.Unmarshal(response, &outMarshal)
if err != nil {
return err
return fmt.Errorf("%s: %v", errmsgPrefix, err)
}

if outMarshal[service] == nil || outMarshal[service][cmd] == nil {
return fmt.Errorf("malformed response: %v", outMarshal)
return fmt.Errorf("%s: malformed response: %v", errmsgPrefix, outMarshal)
}
var r RPCResponse
mapstructure.Decode(outMarshal[service][cmd], &r)
if r.ErrCode != 0 {
return fmt.Errorf("rpc error: %v", outMarshal)
return fmt.Errorf("%s: rpc error: %v", errmsgPrefix, outMarshal)
}

mapstructure.Decode(outMarshal[service][cmd], &out)
Expand Down
28 changes: 16 additions & 12 deletions kasa/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,25 @@ type GetSysInfoRequest struct {
}

type SysInfoChildren struct {
ID string `mapstructure:"id"`
State int `mapstructure:"state"`
Alias string `mapstructure:"alias"`
ID string `mapstructure:"id"`
State int `mapstructure:"state"`
Alias string `mapstructure:"alias"`
OnTime int `mapstructure:"on_time"`
}

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"`
Children []SysInfoChildren `mapstructure:"children"`
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"`
DeviceID string `mapstructure:"deviceId"`
SoftwareVersion string `mapstructure:"sw_ver"`
HardwareVersion string `mapstructure:"hw_ver"`
Children []SysInfoChildren `mapstructure:"children"`
}

func (s *KasaClientSystemService) GetSysInfo() (*GetSysInfoResponse, error) {
Expand Down

0 comments on commit 2437246

Please sign in to comment.