From ff7ae369678d55679e3869bc2e03dfee0ea9921a Mon Sep 17 00:00:00 2001 From: andig Date: Tue, 21 Feb 2023 21:30:39 +0100 Subject: [PATCH] Messaging: send warnings and errors to ui (#6351) --- cmd/helper.go | 29 +++++++++++++++++++++++++++++ cmd/root.go | 21 ++++++++++++--------- cmd/setup.go | 19 ++++++++++--------- evcc.dist.yaml | 4 ++++ push/hub.go | 26 +++++++++++++------------- util/log.go | 6 +++--- 6 files changed, 71 insertions(+), 34 deletions(-) diff --git a/cmd/helper.go b/cmd/helper.go index 95f0f0758..1cecc9edf 100644 --- a/cmd/helper.go +++ b/cmd/helper.go @@ -11,8 +11,10 @@ import ( "strings" "github.com/evcc-io/evcc/cmd/shutdown" + "github.com/evcc-io/evcc/push" "github.com/evcc-io/evcc/util" "github.com/spf13/viper" + "golang.org/x/exp/slices" ) // parseLogLevels parses --log area:level[,...] switch into levels per log area @@ -32,6 +34,33 @@ func parseLogLevels() { util.LogLevel(level, levels) } +// pushErrorEvents forwards error events as push messages +func pushErrorEvents(events chan<- push.Event, valueChan <-chan util.Param) { + cache := make(map[string]interface{}) + + for p := range valueChan { + if !slices.Contains([]string{"warn", "error", "fatal"}, p.Key) { + continue + } + + // deduplicate + if v, ok := cache[p.Key]; ok && v == p.Val { + continue + } + cache[p.Key] = p.Val + + // simplify for users + if p.Key == "fatal" { + p.Key = "error" + } + + events <- push.Event{ + Event: p.Key, + Loadpoint: p.Loadpoint, + } + } +} + // unwrap converts a wrapped error into slice of strings func unwrap(err error) (res []string) { for err != nil { diff --git a/cmd/root.go b/cmd/root.go index 8d5f85e7d..65cc3e6b4 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -135,7 +135,7 @@ func runRoot(cmd *cobra.Command, args []string) { // value cache cache := util.NewCache() - go cache.Run(pipe.NewDropper(ignoreErrors...).Pipe(tee.Attach())) + go cache.Run(tee.Attach()) // create web server socketHub := server.NewSocketHub() @@ -155,12 +155,21 @@ func runRoot(cmd *cobra.Command, args []string) { go socketHub.Run(tee.Attach(), cache) // setup values channel - valueChan := make(chan util.Param) + valueChan := make(chan util.Param, 1) go tee.Run(valueChan) + // setup events channel + eventChan := make(chan push.Event, 1) + go pushErrorEvents(eventChan, tee.Attach()) + // capture log messages for UI util.CaptureLogs(valueChan) + // setup messaging + if err == nil { + err = configureMessengers(conf.Messaging, eventChan, valueChan, cache) + } + // setup environment if err == nil { err = configureEnvironment(cmd, conf) @@ -211,12 +220,6 @@ func runRoot(cmd *cobra.Command, args []string) { err = configureHEMS(conf.HEMS, site, httpd) } - // setup messaging - var pushChan chan push.Event - if err == nil { - pushChan, err = configureMessengers(conf.Messaging, valueChan, cache) - } - // run shutdown functions on stop var once sync.Once stopC := make(chan struct{}) @@ -252,7 +255,7 @@ func runRoot(cmd *cobra.Command, args []string) { // set channels site.DumpConfig() - site.Prepare(valueChan, pushChan) + site.Prepare(valueChan, eventChan) // show and check version valueChan <- util.Param{Key: "version", Val: server.FormattedVersion()} diff --git a/cmd/setup.go b/cmd/setup.go index 3764e0913..e0b3030be 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -203,25 +203,26 @@ func configureEEBus(conf map[string]interface{}) error { } // setup messaging -func configureMessengers(conf messagingConfig, valueChan chan util.Param, cache *util.Cache) (chan push.Event, error) { - messageChan := make(chan push.Event, 1) +func configureMessengers(conf messagingConfig, eventChan <-chan push.Event, valueChan chan util.Param, cache *util.Cache) error { + pushHub, err := push.NewHub(conf.Events, cache) + + if pushHub != nil { + go pushHub.Run(eventChan, valueChan) + } - messageHub, err := push.NewHub(conf.Events, cache) if err != nil { - return messageChan, fmt.Errorf("failed configuring push services: %w", err) + return fmt.Errorf("failed configuring push services: %w", err) } for _, service := range conf.Services { impl, err := push.NewFromConfig(service.Type, service.Other) if err != nil { - return messageChan, fmt.Errorf("failed configuring push service %s: %w", service.Type, err) + return fmt.Errorf("failed configuring push service %s: %w", service.Type, err) } - messageHub.Add(impl) + pushHub.Add(impl) } - go messageHub.Run(messageChan, valueChan) - - return messageChan, nil + return nil } func configureTariffs(conf tariffConfig) (tariff.Tariffs, error) { diff --git a/evcc.dist.yaml b/evcc.dist.yaml index ab8cdeceb..8c553b6cf 100644 --- a/evcc.dist.yaml +++ b/evcc.dist.yaml @@ -231,6 +231,10 @@ messaging: guest: # vehicle could not be identified title: Unknown vehicle msg: Unknown vehicle, guest connected? + error: # errors (deduplicated) + title: "Error: {{ .error }}" + warn: # warnings (deduplicated) + title: "Warning: {{ .warn }}" services: # - type: pushover # app: # app id diff --git a/push/hub.go b/push/hub.go index 07c8697d0..a1b84d9cc 100644 --- a/push/hub.go +++ b/push/hub.go @@ -27,23 +27,23 @@ type Hub struct { cache *util.Cache } -// NewHub creates push hub with definitions and receiver +// NewHub creates push hub with definitions and receiver. The hub is always created, even on error. func NewHub(cc map[string]EventTemplateConfig, cache *util.Cache) (*Hub, error) { - // instantiate all event templates - for k, v := range cc { - if _, err := template.New("out").Funcs(template.FuncMap(sprig.FuncMap())).Parse(v.Title); err != nil { - return nil, fmt.Errorf("invalid event title: %s (%w)", k, err) - } - if _, err := template.New("out").Funcs(template.FuncMap(sprig.FuncMap())).Parse(v.Msg); err != nil { - return nil, fmt.Errorf("invalid event message: %s (%w)", k, err) - } - } - h := &Hub{ definitions: cc, cache: cache, } + // instantiate all event templates + for k, v := range cc { + if _, err := template.New("out").Funcs(template.FuncMap(sprig.FuncMap())).Parse(v.Title); err != nil { + return h, fmt.Errorf("invalid event title: %s (%w)", k, err) + } + if _, err := template.New("out").Funcs(template.FuncMap(sprig.FuncMap())).Parse(v.Msg); err != nil { + return h, fmt.Errorf("invalid event message: %s (%w)", k, err) + } + } + return h, nil } @@ -72,7 +72,7 @@ func (h *Hub) apply(ev Event, tmpl string) (string, error) { } // Run is the Hub's main publishing loop -func (h *Hub) Run(events <-chan Event, valueChan chan util.Param) { +func (h *Hub) Run(events <-chan Event, valueChan chan<- util.Param) { log := util.NewLogger("push") for ev := range events { @@ -103,7 +103,7 @@ func (h *Hub) Run(events <-chan Event, valueChan chan util.Param) { } for _, sender := range h.sender { - if strings.TrimSpace(msg) != "" { + if strings.TrimSpace(title)+strings.TrimSpace(msg) != "" { go sender.Send(title, msg) } else { log.DEBUG.Printf("did not send empty message template for %s: %v", ev.Event, err) diff --git a/util/log.go b/util/log.go index 979f27ec6..a17802d23 100644 --- a/util/log.go +++ b/util/log.go @@ -146,12 +146,12 @@ func (w *uiWriter) Write(p []byte) (n int, err error) { // CaptureLogs appends uiWriter to relevant log levels for // loggers created before uiChan is initialized -func CaptureLogs(c chan<- Param) { +func CaptureLogs(valueChan chan<- Param) { if uiChan != nil { - return + panic("log capturing already initialized") } - uiChan = c + uiChan = valueChan for _, l := range loggers { captureLogger(l)