Curtailment devices: new device class for feed-in limitation (BC) (#32416)
This commit is contained in:
parent
7c0ddf1828
commit
8661c66fc7
40 changed files with 819 additions and 149 deletions
|
|
@ -18,6 +18,7 @@ import (
|
|||
"github.com/evcc-io/evcc/core/circuit"
|
||||
"github.com/evcc-io/evcc/core/keys"
|
||||
"github.com/evcc-io/evcc/core/site"
|
||||
"github.com/evcc-io/evcc/curtailer"
|
||||
"github.com/evcc-io/evcc/hems"
|
||||
hemsapi "github.com/evcc-io/evcc/hems/hems"
|
||||
"github.com/evcc-io/evcc/messenger"
|
||||
|
|
@ -81,6 +82,9 @@ func devicesConfigHandler(w http.ResponseWriter, r *http.Request) {
|
|||
case templates.Circuit:
|
||||
res, err = devicesConfig(class, config.Circuits(), hidePrivate)
|
||||
|
||||
case templates.Curtailer:
|
||||
res, err = devicesConfig(class, config.Curtailers(), hidePrivate)
|
||||
|
||||
case templates.Hems:
|
||||
res, err = devicesConfig(class, config.Hems(), hidePrivate)
|
||||
|
||||
|
|
@ -210,6 +214,9 @@ func deviceConfigHandler(w http.ResponseWriter, r *http.Request) {
|
|||
case templates.Circuit:
|
||||
res, err = deviceConfig(class, id, config.Circuits(), hidePrivate)
|
||||
|
||||
case templates.Curtailer:
|
||||
res, err = deviceConfig(class, id, config.Curtailers(), hidePrivate)
|
||||
|
||||
case templates.Hems:
|
||||
res, err = deviceConfig(class, id, config.Hems(), hidePrivate)
|
||||
|
||||
|
|
@ -275,6 +282,9 @@ func deviceStatusHandler(w http.ResponseWriter, r *http.Request) {
|
|||
case templates.Circuit:
|
||||
instance, err = deviceStatus(name, config.Circuits())
|
||||
|
||||
case templates.Curtailer:
|
||||
instance, err = deviceStatus(name, config.Curtailers())
|
||||
|
||||
case templates.Hems:
|
||||
err = api.ErrNotAvailable
|
||||
|
||||
|
|
@ -375,6 +385,9 @@ func newDeviceHandler(site site.API, authObject auth.Auth) http.HandlerFunc {
|
|||
case templates.Circuit:
|
||||
conf, err = newDevice(ctx, class, req, circuit.NewFromConfig, config.Circuits(), force)
|
||||
|
||||
case templates.Curtailer:
|
||||
conf, err = newDevice(ctx, class, req, curtailer.NewFromConfig, config.Curtailers(), force)
|
||||
|
||||
case templates.Hems:
|
||||
if existing, _ := config.ConfigurationByClass(templates.Hems); existing != nil {
|
||||
err = errors.New("hems already configured")
|
||||
|
|
@ -477,6 +490,9 @@ func updateDeviceHandler(site site.API, authObject auth.Auth) http.HandlerFunc {
|
|||
case templates.Circuit:
|
||||
err = updateDevice(ctx, id, class, req, circuit.NewFromConfig, config.Circuits(), force)
|
||||
|
||||
case templates.Curtailer:
|
||||
err = updateDevice(ctx, id, class, req, curtailer.NewFromConfig, config.Curtailers(), force)
|
||||
|
||||
case templates.Hems:
|
||||
err = updateDevice(ctx, id, class, req, newHemsFactory(site), config.Hems(), force)
|
||||
|
||||
|
|
@ -655,6 +671,12 @@ func deleteDeviceHandler(site site.API) func(w http.ResponseWriter, r *http.Requ
|
|||
}
|
||||
}
|
||||
|
||||
case templates.Curtailer:
|
||||
err = deleteDevice(id, config.Curtailers())
|
||||
|
||||
// cleanup references
|
||||
cleanupSiteMeterRef(config.NameForID(id), site.GetCurtailerRefs, site.SetCurtailerRefs)
|
||||
|
||||
case templates.Hems:
|
||||
err = deleteDevice(id, config.Hems())
|
||||
|
||||
|
|
@ -759,6 +781,9 @@ func testConfigHandler(site site.API, authObject auth.Auth) http.HandlerFunc {
|
|||
case templates.Circuit:
|
||||
instance, err = testConfig(ctx, id, class, req, circuit.NewFromConfig, config.Circuits())
|
||||
|
||||
case templates.Curtailer:
|
||||
instance, err = testConfig(ctx, id, class, req, curtailer.NewFromConfig, config.Curtailers())
|
||||
|
||||
case templates.Hems:
|
||||
instance, err = testConfig(ctx, id, class, req, newHemsFactory(site), config.Hems())
|
||||
|
||||
|
|
|
|||
|
|
@ -446,9 +446,15 @@ func testInstance(ctx context.Context, instance any) map[string]testResult {
|
|||
|
||||
wg.Go(func() {
|
||||
if dev, ok := api.Cap[api.Curtailer](instance); ok {
|
||||
makeResult("curtailable", true, nil)
|
||||
// only reported while actually curtailing
|
||||
if val, err := dev.CurtailedPercent(); err != nil || val < 100 {
|
||||
if _, isMeter := instance.(api.Meter); isMeter {
|
||||
// curtailment as meter capability, limit only reported while actually curtailing
|
||||
makeResult("curtailable", true, nil)
|
||||
if val, err := dev.CurtailedPercent(); err != nil || val < 100 {
|
||||
makeResult("curtailed", val, err)
|
||||
}
|
||||
} else {
|
||||
// dedicated curtailment device, always report the limit
|
||||
val, err := dev.CurtailedPercent()
|
||||
makeResult("curtailed", val, err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,11 @@ func siteHandler(site site.API) http.HandlerFunc {
|
|||
Aux []string `json:"aux"`
|
||||
Ext []string `json:"ext"`
|
||||
Consumer []string `json:"consumer"`
|
||||
Curtail []string `json:"curtail"`
|
||||
}{
|
||||
Title: site.GetTitle(),
|
||||
Grid: site.GetGridMeterRef(),
|
||||
Curtail: site.GetCurtailerRefs(),
|
||||
PV: site.GetPVMeterRefs(),
|
||||
Battery: site.GetBatteryMeterRefs(),
|
||||
Aux: site.GetAuxMeterRefs(),
|
||||
|
|
@ -54,6 +56,7 @@ func updateSiteHandler(site site.API) http.HandlerFunc {
|
|||
Aux *[]string
|
||||
Ext *[]string
|
||||
Consumer *[]string
|
||||
Curtail *[]string
|
||||
}
|
||||
|
||||
if err := jsonDecoder(r.Body).Decode(&payload); err != nil {
|
||||
|
|
@ -119,6 +122,18 @@ func updateSiteHandler(site site.API) http.HandlerFunc {
|
|||
setConfigDirty()
|
||||
}
|
||||
|
||||
if payload.Curtail != nil {
|
||||
for _, m := range *payload.Curtail {
|
||||
if _, err := config.Curtailers().ByName(m); err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
site.SetCurtailerRefs(*payload.Curtail)
|
||||
setConfigDirty()
|
||||
}
|
||||
|
||||
// persist immediately to keep meter refs consistent with device config on unclean shutdown
|
||||
if err := settings.Persist(); err != nil {
|
||||
jsonError(w, http.StatusInternalServerError, err)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue