evcc-io/cmd/metrics.go
2026-05-22 13:39:04 +02:00

113 lines
3 KiB
Go

package cmd
import (
"slices"
"strconv"
"strings"
"time"
"github.com/evcc-io/evcc/core/metrics"
"github.com/evcc-io/evcc/util/config"
"github.com/evcc-io/evcc/util/templates"
"github.com/spf13/cobra"
)
// metricsCmd represents the metrics command
var metricsCmd = &cobra.Command{
Use: "metrics",
Short: "Inspect stored energy metrics",
}
func init() {
rootCmd.AddCommand(metricsCmd)
}
// setupMetrics loads the config file and opens the database. Both metrics
// subcommands need the metric tables and the device configuration.
func setupMetrics(cmd *cobra.Command) {
if err := loadConfigFile(&conf, !cmd.Flag(flagIgnoreDatabase).Changed); err != nil {
log.FATAL.Fatal(err)
}
if err := configureDatabase(conf.Database); err != nil {
log.FATAL.Fatal(err)
}
}
// metricsGroupRank returns the canonical sort position of a metric group.
func metricsGroupRank(group string) int {
if i := slices.Index(metrics.GroupOrder, group); i >= 0 {
return i
}
return len(metrics.GroupOrder)
}
// metricsSortCanonical sorts entities by canonical group order, then name.
func metricsSortCanonical(entities []metrics.EntityInfo) {
slices.SortFunc(entities, func(a, b metrics.EntityInfo) int {
if d := metricsGroupRank(a.Group) - metricsGroupRank(b.Group); d != 0 {
return d
}
return strings.Compare(a.Name, b.Name)
})
}
// metricsFormatDate formats a date for display, empty for the zero time.
func metricsFormatDate(t time.Time) string {
if t.IsZero() {
return ""
}
return t.Local().Format("2006-01-02")
}
// metricsEntityTitle resolves human-readable titles for metric entities. Titles
// exist only for configured loadpoints and meters; virtual entities (home,
// forecast) have none. The returned function maps an entity to its title, or an
// empty string when no title is configured.
func metricsEntityTitle() func(group, name string) string {
// loadpoints are addressed as lp-<n>, numbered yaml-first then database,
// mirroring configureLoadpoints
loadpoints := make(map[string]string)
idx := 0
addLoadpoint := func(n config.Named) {
idx++
if t, ok := n.Property("title").(string); ok && t != "" {
loadpoints["lp-"+strconv.Itoa(idx)] = t
}
}
for _, lp := range conf.Loadpoints {
addLoadpoint(lp)
}
if devices, err := config.ConfigurationsByClass(templates.Loadpoint); err == nil {
for _, dev := range devices {
addLoadpoint(dev.Named())
}
}
// meter entities are addressed by their device ref; the title comes from the
// device configuration
meters := make(map[string]string)
for _, m := range conf.Meters {
if t, ok := m.Property("title").(string); ok && t != "" {
meters[m.Name] = t
}
}
if devices, err := config.ConfigurationsByClass(templates.Meter); err == nil {
for _, dev := range devices {
if dev.Title != "" {
meters[config.NameForID(dev.ID)] = dev.Title
}
}
}
return func(group, name string) string {
switch group {
case metrics.Loadpoint:
return loadpoints[name]
case metrics.Grid, metrics.PV, metrics.Battery, metrics.Meter:
return meters[name]
default:
return ""
}
}
}