Skip to content

Conversation

@andig
Copy link
Member

@andig andig commented Jan 18, 2026

TODO

  • rename push to messenger @naltatis?
  • figure out how to remove the "old" ui-based messengers: indicate yaml configuration to ui @naltatis
  • add templates (can be separate PR) @Maschga
  • check stringlist type UI support @naltatis > type: list exists
  • add e2e tests for basic configuration (evcc.yaml & ui) @Maschga

Out of scope

  • add more templates per shoutrrr type (see email)

/cc @Maschga

@evcc-bot evcc-bot added enhancement New feature or request ux User experience/ interface labels Jan 18, 2026
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 4 issues, and left some high level feedback:

  • In the configureMessengers function, the goroutine over configurable closes over the loop variable conf and later uses &conf in config.NewConfigurableDevice(&conf, instance), which can lead to all goroutines referencing the last element; take a copy of conf inside the loop before starting the goroutine (e.g., conf := conf) and use that copy instead.
  • There are a couple of inconsistent/incorrect log and error messages (e.g., "cannot create messager %d", log.WARN.Printf("create meter %d: %v", ...)) that should be aligned with the new messenger terminology and fixed typos to avoid confusion during debugging.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the `configureMessengers` function, the goroutine over `configurable` closes over the loop variable `conf` and later uses `&conf` in `config.NewConfigurableDevice(&conf, instance)`, which can lead to all goroutines referencing the last element; take a copy of `conf` inside the loop before starting the goroutine (e.g., `conf := conf`) and use that copy instead.
- There are a couple of inconsistent/incorrect log and error messages (e.g., `"cannot create messager %d"`, `log.WARN.Printf("create meter %d: %v", ...)`) that should be aligned with the new messenger terminology and fixed typos to avoid confusion during debugging.

## Individual Comments

### Comment 1
<location> `cmd/setup.go:857` </location>
<code_context>
+		return messageChan, err
+	}
+
+	for _, conf := range configurable {
+		eg.Go(func() error {
+			cc := conf.Named()
</code_context>

<issue_to_address>
**issue (bug_risk):** Loop variable `conf` is captured by the goroutine, which can lead to all goroutines using the last `conf` value.

Because the `for _, conf := range` loop variable is reused, the closure passed to `eg.Go` may see only the final `conf` value, and `&conf` will not point to a per-iteration instance. Shadow `conf` inside the loop so each goroutine captures its own copy:

```go
for _, conf := range configurable {
    conf := conf // per-iteration copy
    eg.Go(func() error {
        cc := conf.Named()
        if e := config.Messengers().Add(config.NewConfigurableDevice(&conf, instance)); e != nil && err == nil {
            err = &DeviceError{cc.Name, e}
        }
        return err
    })
}
```
</issue_to_address>

### Comment 2
<location> `cmd/setup.go:831-833` </location>
<code_context>
+		// 	continue
+		// }
+
+		if err := nameValid(cc.Name); err != nil {
+			log.WARN.Printf("create meter %d: %v", i+1, err)
+		}
+
</code_context>

<issue_to_address>
**nitpick (typo):** Log message refers to "meter" instead of "messenger", which is misleading.

Since this runs in the messenger setup path, the log text should match to avoid confusion during debugging. Suggest updating the message to say "messenger" instead of "meter", e.g.:

```go
log.WARN.Printf("create messenger %d: %v", i+1, err)
```

```suggestion
		if err := nameValid(cc.Name); err != nil {
			log.WARN.Printf("create messenger %d: %v", i+1, err)
		}
```
</issue_to_address>

### Comment 3
<location> `cmd/setup.go:823` </location>
<code_context>
+			Other: cc.Other,
+		}
+
+		if cc.Name == "" {
+			return messageChan, fmt.Errorf("cannot create messager %d: missing name", i+1)
+		}
+
</code_context>

<issue_to_address>
**nitpick (typo):** Error message has a minor typo ("messager" vs "messenger").

Please align this with existing terminology by changing the message to: `cannot create messenger %d: missing name`.

```suggestion
			return messageChan, fmt.Errorf("cannot create messenger %d: missing name", i+1)
```
</issue_to_address>

### Comment 4
<location> `cmd/setup.go:814` </location>
<code_context>

+	var eg errgroup.Group
+
+	for i, cc := range conf.Services {
+		// add name
+		cc := config.Named{
</code_context>

<issue_to_address>
**issue (complexity):** Consider extracting a shared helper for creating and registering messengers and tightening the goroutine usage to remove duplication and a closure-capture bug.

You can reduce complexity and also fix a subtle bug by extracting a shared helper and tightening the concurrency/error-handling.

**1. Extract shared “create + register messenger” helper**

Both loops build a `config.Named`, create a logger, construct a messenger via `push.NewFromConfig`, and register it. The only difference is whether `Other` needs `customDevice` and which `Add` method is used.

You can centralize this:

```go
func initMessengerDevice(
    ctx context.Context,
    named config.Named,
    rawOther map[string]any,
    configurable *config.Configurable, // nil for static
) error {
    // decode custom device if needed
    props := rawOther
    var err error
    if configurable != nil {
        props, err = customDevice(named.Other)
        if err != nil {
            return &DeviceError{named.Name, fmt.Errorf("cannot decode custom messenger '%s': %w", named.Name, err)}
        }
    }

    // create instance
    instance, err := push.NewFromConfig(ctx, named.Type, props)
    if err != nil {
        return &DeviceError{named.Name, fmt.Errorf("cannot create messenger '%s': %w", named.Name, err)}
    }

    // register
    if configurable == nil {
        if err := config.Messengers().Add(config.NewStaticDevice(named, instance)); err != nil {
            return &DeviceError{named.Name, err}
        }
    } else {
        if err := config.Messengers().Add(config.NewConfigurableDevice(configurable, instance)); err != nil {
            return &DeviceError{named.Name, err}
        }
    }

    return nil
}
```

Then both loops collapse to simple calls with much less duplication and branching:

```go
var eg errgroup.Group

for i, svc := range conf.Services {
    named := config.Named{
        Name:  fmt.Sprintf("push-%d", i+1),
        Type:  svc.Type,
        Other: svc.Other,
    }

    if named.Name == "" {
        return messageChan, fmt.Errorf("cannot create messenger %d: missing name", i+1)
    }
    if err := nameValid(named.Name); err != nil {
        log.WARN.Printf("create messenger %d: %v", i+1, err)
    }

    named := named // avoid closure capture issues if you add fields later
    eg.Go(func() error {
        ctx := util.WithLogger(context.TODO(), util.NewLogger(named.Name))
        return initMessengerDevice(ctx, named, named.Other, nil)
    })
}

configurable, err := config.ConfigurationsByClass(templates.Messenger)
if err != nil {
    return messageChan, err
}

for _, cfg := range configurable {
    cfg := cfg // FIX: avoid capturing loop variable by reference
    named := cfg.Named()

    eg.Go(func() error {
        ctx := util.WithLogger(context.TODO(), util.NewLogger(named.Name))
        return initMessengerDevice(ctx, named, named.Other, &cfg)
    })
}

if err := eg.Wait(); err != nil {
    return messageChan, &ClassError{ClassMessenger, err}
}
```

Benefits:

- Removes the duplicated “decode / create / add / wrap error” logic.
- Simplifies error handling: each failure path returns immediately from the helper, callers only see a single `error`.
- Fixes the closure-capture bug in the second loop (`conf` was being captured and then used via `&conf` and `conf.Named()` inside the goroutine).
- Clarifies naming (`svc`, `cfg`, `named`) and avoids shadowing (`conf` and `cc` reuse).
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@naltatis
Copy link
Member

figure out how to remove the "old" ui-based messengers: one-time device migration?

Suggestion: Lets not do auto-migrate at all. If yaml based config exists (via evcc.yaml or ui) we'll simply use it. We only unlock new form based configuration if messaging is not configured yet. I'm doing the same thing in the tariffs branch right now. If we detect yaml based config and(!) device-based config we'll simply fail on boot. but this could only happen if a users tries to add messaging configuration to evcc.yaml even though he already has ui-configured it.

In short config ui will have two ways to configure messaging:

  1. via yaml editor (legacy, only visible if yaml config already exists)
  2. via forms/devices (new default, disabled if yaml config exists)

This way we don't have to account for one-time logics (when, re-run, rollback, ...). Drawback is, that the user has to reenter the values in UI when he wants to go to the new model. But he retains control and is not forced.

@naltatis
Copy link
Member

rename push to messenger @naltatis?

Was not aware that it was named push in code. The config and publish key is messaging. So +1 for the rename.

Note: In UI we're calling this feature "Notifications" / "Benachrichtigungen" which better describes the use-case than the actual tech. But this does not have to match our internal naming. Renaming the keys will do more harm than good 😄.

@andig
Copy link
Member Author

andig commented Jan 19, 2026

via yaml editor (legacy, only visible if yaml config already exists)
via forms/devices (new default, disabled if yaml config exists)

In first case these will still be devices internally from now on. It's up to the UI then to choose what type of config interface to offer?

@naltatis
Copy link
Member

naltatis commented Jan 19, 2026

It's up to the UI then to choose what type of config interface to offer?

Yes, UI will stay as is: yaml-config via UI editor

@naltatis
Copy link
Member

@Maschga we currently dont have e2e tests that verify that messaging configuration is properly read. You've added a lot of tests in the #25768 branch. Can you add basic tests (is configured) here as well?

@Maschga
Copy link
Collaborator

Maschga commented Jan 19, 2026

Can we also split the parameters in the templates here as in #25768?
I described at the top of the PR description which messaging services were split and how:

🧩 migration: split email-uri into host-, port-, user-, password-, from- and to-keys
🧩 migration: split ntfy-uri into host- and topics-keys

Or isn't that the goal of this PR?

Can you add basic tests (is configured) here as well?

Will have a try and create a PR for this. 👍

@andig
Copy link
Member Author

andig commented Jan 19, 2026

Can we also split the parameters in the templates here as in #25768?

Sure, feel free! We can also do that as follow-up, doesn't need to be here. As you like, I just did a quick AI attempt.

@andig
Copy link
Member Author

andig commented Jan 19, 2026

Can also remove the templates here as you prefer. No need to introduce conflicts.

@Maschga
Copy link
Collaborator

Maschga commented Jan 19, 2026

I appreciate your efforts. Let's keep the templates. 👍

@Maschga Maschga mentioned this pull request Jan 19, 2026
@Maschga
Copy link
Collaborator

Maschga commented Jan 20, 2026

add e2e tests for basic configuration (evcc.yaml & ui)

Tests are done. :)

@andig
Copy link
Member Author

andig commented Jan 22, 2026

Was fehlt denn hier noch- das eigentlich UI? Sollen wir darauf warten?

@Maschga
Copy link
Collaborator

Maschga commented Jan 22, 2026

I can branch here and create a PR for the UI.

@github-actions github-actions bot added the stale Outdated and ready to close label Jan 29, 2026
@naltatis naltatis removed the stale Outdated and ready to close label Jan 30, 2026
@github-actions github-actions bot added the stale Outdated and ready to close label Feb 6, 2026
@andig
Copy link
Member Author

andig commented Feb 8, 2026

@Maschga can I close this one?

@andig andig closed this Feb 8, 2026
@Maschga
Copy link
Collaborator

Maschga commented Feb 8, 2026

Yes. :-)
Can be closed in favor of #26946.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request stale Outdated and ready to close ux User experience/ interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants