Messaging: send warnings and errors to ui (#6351)

This commit is contained in:
andig 2023-02-21 21:30:39 +01:00 • committed by GitHub
parent d35170955e
commit ff7ae36967
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 71 additions and 34 deletions

View file

@ -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 {

View file

@ -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()}

View file

@ -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) {

View file

@ -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

View file

@ -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)

View file

@ -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)