Skip to content

Commit

Permalink
refactoring and add shellypro em support
Browse files Browse the repository at this point in the history
Signed-off-by: Markus Blaschke <[email protected]>
  • Loading branch information
mblaschke committed Oct 7, 2023
1 parent 3de623b commit 399debb
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 53 deletions.
2 changes: 1 addition & 1 deletion config/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type (
}

ServiceDiscovery struct {
Timeout time.Duration `long:"shelly.servicediscovery.timeout" env:"SHELLY_SERVICEDISCOVERY_TIMEOUT" description:"mDNS discovery response timeout" default:"5s"`
Timeout time.Duration `long:"shelly.servicediscovery.timeout" env:"SHELLY_SERVICEDISCOVERY_TIMEOUT" description:"mDNS discovery response timeout" default:"20s"`
Refresh time.Duration `long:"shelly.servicediscovery.refresh" env:"SHELLY_SERVICEDISCOVERY_REFRESH" description:"mDNS discovery refresh time" default:"15m"`
}
}
Expand Down
11 changes: 10 additions & 1 deletion discovery/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func (d *serviceDiscovery) init() {
func (d *serviceDiscovery) Run(timeout time.Duration) {
wg := sync.WaitGroup{}
// Make a channel for results and start listening
entriesCh := make(chan *mdns.ServiceEntry, 4)
entriesCh := make(chan *mdns.ServiceEntry, 15)

wg.Add(1)
go func() {
Expand All @@ -77,7 +77,16 @@ func (d *serviceDiscovery) Run(timeout time.Duration) {
Address: entry.AddrV4.String(),
Type: TargetTypeShellyPlus,
})
case strings.HasPrefix(strings.ToLower(entry.Name), "shellypro"):
d.logger.Debugf(`found %v [%v] via mDNS servicediscovery`, entry.Name, entry.AddrV4.String())
targetList = append(targetList, DiscoveryTarget{
Hostname: entry.Name,
Port: entry.Port,
Address: entry.AddrV4.String(),
Type: TargetTypeShellyPlus,
})
}

}

d.lock.Lock()
Expand Down
79 changes: 74 additions & 5 deletions shellyplug/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,44 @@ import (
"github.com/prometheus/client_golang/prometheus"
)

type (
shellyPlugMetrics struct {
info *prometheus.GaugeVec
temp *prometheus.GaugeVec
overTemp *prometheus.GaugeVec
wifiRssi *prometheus.GaugeVec
updateNeeded *prometheus.GaugeVec
restartRequired *prometheus.GaugeVec

cloudEnabled *prometheus.GaugeVec
cloudConnected *prometheus.GaugeVec

switchOn *prometheus.GaugeVec
switchOverpower *prometheus.GaugeVec
switchTimer *prometheus.GaugeVec

powerCurrent *prometheus.GaugeVec
powerApparentCurrent *prometheus.GaugeVec
powerTotal *prometheus.GaugeVec
powerLimit *prometheus.GaugeVec
powerFactor *prometheus.GaugeVec
powerFrequency *prometheus.GaugeVec
powerVoltage *prometheus.GaugeVec

sysUnixtime *prometheus.GaugeVec
sysUptime *prometheus.GaugeVec
sysMemTotal *prometheus.GaugeVec
sysMemFree *prometheus.GaugeVec
sysFsSize *prometheus.GaugeVec
sysFsFree *prometheus.GaugeVec
}
)

func (sp *ShellyPlug) initMetrics() {
commonLabels := []string{"target", "mac", "plugName"}
tempLabels := append(commonLabels, "sensorID", "sensorName")
switchLabels := append(commonLabels, "switchID", "switchName")
powerLabels := append(commonLabels, "switchID", "switchName")
tempLabels := append(commonLabels, "id", "name")
switchLabels := append(commonLabels, "id", "name")
powerLabels := append(commonLabels, "id", "name")

// ##########################################
// Info
Expand Down Expand Up @@ -113,7 +146,7 @@ func (sp *ShellyPlug) initMetrics() {
Name: "shellyplug_switch_on",
Help: "ShellyPlug switch on status",
},
append(switchLabels, "switchSource"),
append(switchLabels, "source"),
)
sp.registry.MustRegister(sp.prometheus.switchOn)

Expand Down Expand Up @@ -141,12 +174,21 @@ func (sp *ShellyPlug) initMetrics() {
sp.prometheus.powerCurrent = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "shellyplug_power_current",
Help: "ShellyPlug current power usage in watts",
Help: "ShellyPlug current power current in watts",
},
powerLabels,
)
sp.registry.MustRegister(sp.prometheus.powerCurrent)

sp.prometheus.powerApparentCurrent = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "shellyplug_power_apparentcurrent",
Help: "ShellyPlug current power apparent current in VA",
},
powerLabels,
)
sp.registry.MustRegister(sp.prometheus.powerApparentCurrent)

sp.prometheus.powerTotal = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "shellyplug_power_total",
Expand All @@ -165,6 +207,33 @@ func (sp *ShellyPlug) initMetrics() {
)
sp.registry.MustRegister(sp.prometheus.powerLimit)

sp.prometheus.powerFactor = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "shellyplug_power_factor",
Help: "ShellyPlug configured power factor",
},
powerLabels,
)
sp.registry.MustRegister(sp.prometheus.powerFactor)

sp.prometheus.powerFrequency = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "shellyplug_power_frequency",
Help: "ShellyPlug configured power frequency in Hz",
},
powerLabels,
)
sp.registry.MustRegister(sp.prometheus.powerFrequency)

sp.prometheus.powerVoltage = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "shellyplug_power_voltage",
Help: "ShellyPlug configured power voltage",
},
powerLabels,
)
sp.registry.MustRegister(sp.prometheus.powerVoltage)

// ##########################################
// System

Expand Down
20 changes: 10 additions & 10 deletions shellyplug/prober.gen1.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package shellyplug

import (
"strconv"
"fmt"

"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -29,8 +29,8 @@ func (sp *ShellyPlug) collectFromTargetGen1(target discovery.DiscoveryTarget, lo
infoLabels["plugModel"] = result.Device.Type

powerLimitLabels := copyLabelMap(targetLabels)
powerLimitLabels["switchID"] = ""
powerLimitLabels["switchName"] = ""
powerLimitLabels["id"] = "meter:0"
powerLimitLabels["name"] = ""
sp.prometheus.powerLimit.With(powerLimitLabels).Set(result.MaxPower)
} else {
logger.Errorf(`failed to fetch settings: %v`, err)
Expand All @@ -50,8 +50,8 @@ func (sp *ShellyPlug) collectFromTargetGen1(target discovery.DiscoveryTarget, lo
sp.prometheus.sysFsFree.With(targetLabels).Set(float64(result.FsFree))

tempLabels := copyLabelMap(targetLabels)
tempLabels["sensorID"] = ""
tempLabels["sensorName"] = "system"
tempLabels["id"] = "sensor:0"
tempLabels["name"] = "system"
sp.prometheus.temp.With(tempLabels).Set(result.Temperature)
sp.prometheus.overTemp.With(tempLabels).Set(boolToFloat64(result.Overtemperature))

Expand All @@ -65,8 +65,8 @@ func (sp *ShellyPlug) collectFromTargetGen1(target discovery.DiscoveryTarget, lo

for relayID, powerUsage := range result.Meters {
powerUsageLabels := copyLabelMap(targetLabels)
powerUsageLabels["switchID"] = strconv.Itoa(relayID)
powerUsageLabels["switchName"] = targetLabels["plugName"]
powerUsageLabels["id"] = fmt.Sprintf("meter:%d", relayID)
powerUsageLabels["name"] = targetLabels["plugName"]

sp.prometheus.powerCurrent.With(powerUsageLabels).Set(powerUsage.Power)
// total is provided as watt/minutes, we want watt/hours
Expand All @@ -75,11 +75,11 @@ func (sp *ShellyPlug) collectFromTargetGen1(target discovery.DiscoveryTarget, lo

for relayID, relay := range result.Relays {
switchLabels := copyLabelMap(targetLabels)
switchLabels["switchID"] = strconv.Itoa(relayID)
switchLabels["switchName"] = targetLabels["plugName"]
switchLabels["id"] = fmt.Sprintf("relay:%d", relayID)
switchLabels["name"] = targetLabels["plugName"]

switchOnLabels := copyLabelMap(switchLabels)
switchOnLabels["switchSource"] = relay.Source
switchOnLabels["source"] = relay.Source

sp.prometheus.switchOn.With(switchOnLabels).Set(boolToFloat64(relay.Ison))
sp.prometheus.switchOverpower.With(switchLabels).Set(boolToFloat64(relay.Overpower))
Expand Down
66 changes: 56 additions & 10 deletions shellyplug/prober.gen2.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package shellyplug

import (
"encoding/json"
"strconv"
"fmt"
"strings"

"github.com/prometheus/client_golang/prometheus"
Expand Down Expand Up @@ -59,20 +59,66 @@ func (sp *ShellyPlug) collectFromTargetGen2(target discovery.DiscoveryTarget, lo
if configData, err := decodeShellyConfigValueToItem(configValue); err == nil {
if result, err := shellyProber.GetSwitchStatus(configData.Id); err == nil {
switchLabels := copyLabelMap(targetLabels)
switchLabels["switchID"] = strconv.Itoa(configData.Id)
switchLabels["switchName"] = configData.Name
switchLabels["id"] = fmt.Sprintf("switch:%d", configData.Id)
switchLabels["name"] = configData.Name

switchOnLabels := copyLabelMap(switchLabels)
switchOnLabels["switchSource"] = result.Source
switchOnLabels["source"] = result.Source

sp.prometheus.switchOn.With(switchOnLabels).Set(boolToFloat64(result.Output))

powerUsageLabels := copyLabelMap(targetLabels)
powerUsageLabels["switchID"] = strconv.Itoa(configData.Id)
powerUsageLabels["switchName"] = configData.Name
powerUsageLabels["id"] = fmt.Sprintf("switch:%d", configData.Id)
powerUsageLabels["name"] = configData.Name
sp.prometheus.powerCurrent.With(powerUsageLabels).Set(result.Current)
// total is provided as watt/minutes, we want watt/hours
sp.prometheus.powerTotal.With(powerUsageLabels).Set(result.Apower / 60)
sp.prometheus.powerTotal.With(powerUsageLabels).Set(result.Apower)
} else {
logger.Errorf(`failed to decode switchStatus: %v`, err)
}
}
// em
case strings.HasPrefix(configName, "em:"):
if configData, err := decodeShellyConfigValueToItem(configValue); err == nil {
if result, err := shellyProber.GetEmStatus(configData.Id); err == nil {
// phase A
powerUsageLabels := copyLabelMap(targetLabels)
powerUsageLabels["id"] = fmt.Sprintf("em:%d:A", configData.Id)
powerUsageLabels["name"] = configData.Name
sp.prometheus.powerCurrent.With(powerUsageLabels).Set(result.ACurrent)
sp.prometheus.powerApparentCurrent.With(powerUsageLabels).Set(result.AAprtPower)
sp.prometheus.powerTotal.With(powerUsageLabels).Set(result.AActPower)
sp.prometheus.powerFactor.With(powerUsageLabels).Set(result.APf)
sp.prometheus.powerFrequency.With(powerUsageLabels).Set(result.AFreq)
sp.prometheus.powerVoltage.With(powerUsageLabels).Set(result.AVoltage)

// phase B
powerUsageLabels = copyLabelMap(targetLabels)
powerUsageLabels["id"] = fmt.Sprintf("em:%d:B", configData.Id)
powerUsageLabels["name"] = configData.Name
sp.prometheus.powerCurrent.With(powerUsageLabels).Set(result.BCurrent)
sp.prometheus.powerApparentCurrent.With(powerUsageLabels).Set(result.BAprtPower)
sp.prometheus.powerTotal.With(powerUsageLabels).Set(result.BActPower)
sp.prometheus.powerFactor.With(powerUsageLabels).Set(result.BPf)
sp.prometheus.powerFrequency.With(powerUsageLabels).Set(result.BFreq)
sp.prometheus.powerVoltage.With(powerUsageLabels).Set(result.BVoltage)

// phase C
powerUsageLabels = copyLabelMap(targetLabels)
powerUsageLabels["id"] = fmt.Sprintf("em:%d:C", configData.Id)
powerUsageLabels["name"] = configData.Name
sp.prometheus.powerCurrent.With(powerUsageLabels).Set(result.CCurrent)
sp.prometheus.powerApparentCurrent.With(powerUsageLabels).Set(result.CAprtPower)
sp.prometheus.powerTotal.With(powerUsageLabels).Set(result.CActPower)
sp.prometheus.powerFactor.With(powerUsageLabels).Set(result.CPf)
sp.prometheus.powerFrequency.With(powerUsageLabels).Set(result.CFreq)
sp.prometheus.powerVoltage.With(powerUsageLabels).Set(result.CVoltage)

// phase C
powerUsageLabels = copyLabelMap(targetLabels)
powerUsageLabels["id"] = "em:total"
powerUsageLabels["name"] = configData.Name
sp.prometheus.powerCurrent.With(powerUsageLabels).Set(result.TotalCurrent)
sp.prometheus.powerTotal.With(powerUsageLabels).Set(result.TotalActPower)
} else {
logger.Errorf(`failed to decode switchStatus: %v`, err)
}
Expand All @@ -82,8 +128,8 @@ func (sp *ShellyPlug) collectFromTargetGen2(target discovery.DiscoveryTarget, lo
if configData, err := decodeShellyConfigValueToItem(configValue); err == nil {
if result, err := shellyProber.GetTemperatureStatus(configData.Id); err == nil {
tempLabels := copyLabelMap(targetLabels)
tempLabels["sensorID"] = strconv.Itoa(configData.Id)
tempLabels["sensorName"] = configData.Name
tempLabels["id"] = fmt.Sprintf("sensor:%d", configData.Id)
tempLabels["name"] = configData.Name

sp.prometheus.temp.With(tempLabels).Set(result.TC)
} else {
Expand Down
27 changes: 1 addition & 26 deletions shellyplug/prober.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,32 +28,7 @@ type (
lock sync.RWMutex
}

prometheus struct {
info *prometheus.GaugeVec
temp *prometheus.GaugeVec
overTemp *prometheus.GaugeVec
wifiRssi *prometheus.GaugeVec
updateNeeded *prometheus.GaugeVec
restartRequired *prometheus.GaugeVec

cloudEnabled *prometheus.GaugeVec
cloudConnected *prometheus.GaugeVec

switchOn *prometheus.GaugeVec
switchOverpower *prometheus.GaugeVec
switchTimer *prometheus.GaugeVec

powerCurrent *prometheus.GaugeVec
powerTotal *prometheus.GaugeVec
powerLimit *prometheus.GaugeVec

sysUnixtime *prometheus.GaugeVec
sysUptime *prometheus.GaugeVec
sysMemTotal *prometheus.GaugeVec
sysMemFree *prometheus.GaugeVec
sysFsSize *prometheus.GaugeVec
sysFsFree *prometheus.GaugeVec
}
prometheus shellyPlugMetrics
}
)

Expand Down
33 changes: 33 additions & 0 deletions shellyprober/gen2.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,33 @@ type (
TF float64 `json:"tF"`
} `json:"temperature"`
}

ShellyProberGen2ResultEm struct {
ID int `json:"id"`
ACurrent float64 `json:"a_current"`
AVoltage float64 `json:"a_voltage"`
AActPower float64 `json:"a_act_power"`
AAprtPower float64 `json:"a_aprt_power"`
APf float64 `json:"a_pf"`
AFreq float64 `json:"a_freq"`
BCurrent float64 `json:"b_current"`
BVoltage float64 `json:"b_voltage"`
BActPower float64 `json:"b_act_power"`
BAprtPower float64 `json:"b_aprt_power"`
BPf float64 `json:"b_pf"`
BFreq float64 `json:"b_freq"`
CCurrent float64 `json:"c_current"`
CVoltage float64 `json:"c_voltage"`
CActPower float64 `json:"c_act_power"`
CAprtPower float64 `json:"c_aprt_power"`
CPf float64 `json:"c_pf"`
CFreq float64 `json:"c_freq"`
NCurrent any `json:"n_current"`
TotalCurrent float64 `json:"total_current"`
TotalActPower float64 `json:"total_act_power"`
TotalAprtPower float64 `json:"total_aprt_power"`
UserCalibratedPhase []any `json:"user_calibrated_phase"`
}
)

func (sp *ShellyProberGen2) fetch(url string, target interface{}) error {
Expand Down Expand Up @@ -126,3 +153,9 @@ func (sp *ShellyProberGen2) GetSwitchStatus(id int) (ShellyProberGen2ResultSwitc
err := sp.fetch(fmt.Sprintf("/rpc/Switch.GetStatus?id=%d", id), &result)
return result, err
}

func (sp *ShellyProberGen2) GetEmStatus(id int) (ShellyProberGen2ResultEm, error) {
result := ShellyProberGen2ResultEm{}
err := sp.fetch(fmt.Sprintf("/rpc/Em.GetStatus?id=%d", id), &result)
return result, err
}

0 comments on commit 399debb

Please sign in to comment.