Meter (SunSpec): read max AC power from nameplate (#32732)

This commit is contained in:
andig 2026-08-20 20:14:13 +02:00 • committed by GitHub
parent fb35c0905f
commit e2c59fc6e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 91 additions and 24 deletions

View file

@ -685,7 +685,9 @@ func (site *Site) updatePvMeters() {
}
if m, ok := api.Cap[api.MaxACPowerGetter](meter); ok {
if dc := power - m.MaxACPower(); dc > 0 && power > 0 {
// unknown rating (0) must not turn the entire production into excess DC
if maxACPower := m.MaxACPower(); maxACPower > 0 && power > maxACPower {
dc := power - maxACPower
mm[i].ExcessDCPower = dc
site.log.DEBUG.Printf("pv %d excess DC: %.0fW", i+1, dc)
}

View file

@ -24,7 +24,7 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.M
measurement.Curtailer `mapstructure:",squash"` // optional
// pv
pvMaxACPower `mapstructure:",squash"`
pvMaxACPowerCtx `mapstructure:",squash"`
// battery
batteryCapacityCtx `mapstructure:",squash"`
@ -127,7 +127,7 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.M
implement.May(m, implement.PhaseCurrents(currentsG))
implement.May(m, implement.PhaseVoltages(voltagesG))
implement.May(m, implement.PhasePowers(powersG))
implement.May(m, implement.MaxACPowerGetter(cc.pvMaxACPower.Decorator()))
implement.May(m, implement.MaxACPowerGetter(cc.pvMaxACPowerCtx.Decorator(ctx)))
return m, nil
}

View file

@ -22,6 +22,27 @@ func TestPV(t *testing.T) {
_, ok := api.Cap[api.MaxACPowerGetter](m)
assert.True(t, ok, "MaxACPowerGetter")
}
func TestPVMaxACPowerPlugin(t *testing.T) {
newMeter := func(maxacpower any) api.Meter {
m, err := NewConfigurableFromConfig(t.Context(), map[string]any{
"power": map[string]any{"source": "const", "value": 1000},
"maxacpower": maxacpower,
})
require.NoError(t, err)
return m
}
m := newMeter(map[string]any{"source": "const", "value": 5000})
mp, ok := api.Cap[api.MaxACPowerGetter](m)
require.True(t, ok, "MaxACPowerGetter")
assert.Equal(t, 5000.0, mp.MaxACPower())
// device without rating must not be decorated
m = newMeter(map[string]any{"source": "error", "error": "ErrNotAvailable"})
_, ok = api.Cap[api.MaxACPowerGetter](m)
assert.False(t, ok, "MaxACPowerGetter")
}
func TestBattery(t *testing.T) {
m, err := NewConfigurableFromConfig(t.Context(), map[string]any{
"power": map[string]any{

View file

@ -43,11 +43,11 @@ func resolveFloat(ctx context.Context, v any) (func() float64, error) {
case nil:
return nil, nil
case int:
return staticCapacity(float64(v)), nil
return staticFloat(float64(v)), nil
case int64:
return staticCapacity(float64(v)), nil
return staticFloat(float64(v)), nil
case float64:
return staticCapacity(v), nil
return staticFloat(v), nil
default:
var cfg plugin.Config
if err := util.DecodeOther(v, &cfg); err != nil {
@ -67,7 +67,7 @@ func resolveFloat(ctx context.Context, v any) (func() float64, error) {
}
}
func staticCapacity(f float64) func() float64 {
func staticFloat(f float64) func() float64 {
if f == 0 {
return nil
}

View file

@ -1,5 +1,11 @@
package meter
import (
"context"
"github.com/evcc-io/evcc/util"
)
type pvMaxACPower struct {
MaxACPower float64
}
@ -8,10 +14,23 @@ type pvMaxACPower struct {
// Decorator returns the max AC power decorator
func (m *pvMaxACPower) Decorator() func() float64 {
if m.MaxACPower == 0 {
return nil
return staticFloat(m.MaxACPower)
}
return func() float64 {
return m.MaxACPower
type pvMaxACPowerCtx struct {
MaxACPower any // static W value or float plugin
}
// var _ api.MaxACPowerGetter = (*pvMaxACPowerCtx)(nil)
// Decorator returns the max AC power decorator. MaxACPower may be a static value or a
// float plugin like the SunSpec nameplate rating. Since the rating is constant, the
// plugin is read once. Unavailable or zero means not configured.
func (m *pvMaxACPowerCtx) Decorator(ctx context.Context) func() float64 {
get, err := resolveFloat(ctx, m.MaxACPower)
if err != nil {
// not all devices expose the rating- must not fail configuration
util.NewLogger("meter").WARN.Printf("maxacpower: %v", err)
}
return staticFloat(floatOr0(get))
}

View file

@ -17,8 +17,7 @@ params:
choice: ["tcpip"]
id: 1
- name: maxacpower
service: modbus/read?address=40227&type=holding&encoding=uint16&scale=1&{modbus}
usages: ["pv"]
deprecated: true # read from nameplate
render: |
type: custom
# sunspec model 701 (DER AC Measurement)
@ -96,5 +95,5 @@ render: |
percent = int(limit)
}
percent
maxacpower: {{ .maxacpower }}
{{- include "sunspec-maxacpower" . }}
{{- end }}

View file

@ -16,6 +16,7 @@ params:
- name: port
default: 502
- name: maxacpower
deprecated: true # read from nameplate
render: |
type: custom
# Fronius Argeno: AC output via SunSpec inverter model 113 (float, primary)
@ -38,4 +39,4 @@ render: |
- 113:WH
- 103:WH
scale: 0.001
maxacpower: {{ .maxacpower }} # W
{{- include "sunspec-maxacpower-tcp" . }}

View file

@ -31,6 +31,7 @@ params:
generic: Integer
deprecated: true
- name: maxacpower
deprecated: true # read from nameplate
- preset: battery-params
- name: maxchargerate
render: |
@ -167,7 +168,7 @@ render: |
id: 1
value: 160:2:DCWH # mppt 2
scale: 0.001
maxacpower: {{ .maxacpower }} # W
{{- include "sunspec-maxacpower-tcp" . }}
curtail:
source: sequence
set:

View file

@ -18,6 +18,7 @@ params:
de: "Zähleradresse von Primär- oder Sekundärzählern. Auf der Weboberfläche des Wechselrichters kann nur die Adresse des ersten Zählers (z.B. 200) eingestellt werden. Zusätzliche Zähler erhalten eine aufsteigende Nummer (z.B: 201)."
usages: ["grid"]
- name: maxacpower
deprecated: true # read from nameplate
- name: maxchargerate
- preset: battery-params
render: |
@ -163,7 +164,7 @@ render: |
id: 1
value: 160:3:DCWH # mppt 3
scale: 0.001
maxacpower: {{ .maxacpower }} # W
{{- include "sunspec-maxacpower-tcp" . }}
{{- end }}
{{- if eq .usage "battery" }}
power:

View file

@ -35,7 +35,7 @@ params:
default: little
advanced: true
- name: maxacpower
service: modbus/read?address=531&type=holding&encoding=uint16&{modbus}
deprecated: true # read from nameplate
- preset: battery-params
- name: capacity
service: modbus/read?address=1068&type=holding&encoding=float32s&scale=0.001&{modbus}
@ -74,7 +74,7 @@ render: |
type: holding
encoding: {{ if (eq .endianness "big") }}float32{{ else }}float32s{{ end }}
scale: 0.001 # Wh -> kWh
maxacpower: {{ .maxacpower }} # W
{{- include "sunspec-maxacpower" . }}
# feed-in curtailment via active power limit (SunSpec model 123, WMaxLimPct @ 40217).
# written as a single register (fn 06); the Plenticore rejects write multiple (fn 16) here.
# WMaxLim_Ena (register 40221) is enabled by default and left to the inverter

View file

@ -109,7 +109,7 @@ render: |
address: 0xE174 # Battery 1 Instantaneous Power
type: holding
decode: float32nans
maxacpower: {{ .maxacpower }} # W
{{- include "sunspec-maxacpower" . }}
curtail:
source: sequence
set:

View file

@ -149,4 +149,5 @@ render: |
percent = 100
}
percent
{{- include "sunspec-maxacpower" . }}
{{- end }}

View file

@ -11,6 +11,7 @@ params:
- name: modbus
choice: ["tcpip", "rs485"]
- name: maxacpower
deprecated: true # read from nameplate
render: |
type: custom
# sunspec model 203 (int+sf)/ 213 (float) meter
@ -88,4 +89,4 @@ render: |
percent = int(limit)
}
percent
maxacpower: {{ .maxacpower }} # W
{{- include "sunspec-maxacpower" . }}

View file

@ -11,6 +11,7 @@ params:
- name: modbus
choice: ["tcpip", "rs485"]
- name: maxacpower
deprecated: true # read from nameplate
- preset: battery-params
render: |
type: custom
@ -106,7 +107,7 @@ render: |
{{- include "modbus" . | indent 6 }}
value: 160:2:DCWH # mppt 2
scale: 0.001
maxacpower: {{ .maxacpower }} # W
{{- include "sunspec-maxacpower" . }}
{{- end }}
{{- if eq .usage "battery" }}
power:

View file

@ -87,3 +87,4 @@ render: |
percent = int(limit)
}
percent
{{- include "sunspec-maxacpower" . }}

View file

@ -153,6 +153,7 @@ render: |
- 103:WH
- 113:WH
scale: 0.001
{{- include "sunspec-maxacpower" . }}
{{- end }}
{{- if eq .usage "battery" }}
power:

View file

@ -0,0 +1,18 @@
{{ define "sunspec-maxacpower" }}
maxacpower: # nameplate rating
source: sunspec
{{- include "modbus" . | indent 2 }}
value:
- 120:WRtg
- 702:WMaxRtg
{{- end }}
{{ define "sunspec-maxacpower-tcp" }}
maxacpower: # nameplate rating
source: sunspec
uri: {{ joinHostPort .host .port }}
id: 1
value:
- 120:WRtg
- 702:WMaxRtg
{{- end }}