Repository: evcc-io/evcc (go)

Issue:
Improve SMA EV Charger template
Problem gelöst:
- in der Doku stand: "neuen Benutzer in der wallbox anlegen, der initiale admin geht nicht" (was ja auch stimmt)
- das hatte für mich impliziert: neuen Benutzer vom Typ "Benutzer" anlegen, was falsch war (der SMA-EV-charger-user mit Recht "Benutzer" sieht nur ein subset, und eben nicht "Parameter.Inverter.AcALim")
- WICHTIG: der neue EV-charger-Benutzer für EVCC muss die Rechte "Administrator" haben

_Originally posted by @pwtanagit in https://github.com/evcc-io/evcc/discussions/5893#discussioncomment-5017204_
      

Pull request:
Notice EV-charger user must have Admin rights.
fix #6294

Patch:
diff --git a/cmd/root.go b/cmd/root.go
index c4ed0023..8d5f85e7 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -21,6 +21,9 @@ import (
 	"github.com/evcc-io/evcc/util/pipe"
 	"github.com/evcc-io/evcc/util/sponsor"
 	"github.com/evcc-io/evcc/util/telemetry"
+	"github.com/fatih/structs"
+	"github.com/jeremywohl/flatten"
+	"golang.org/x/exp/maps"
 
 	_ "github.com/joho/godotenv/autoload"
 	"github.com/prometheus/client_golang/prometheus/promhttp"
@@ -89,6 +92,12 @@ func initConfig() {
 	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
 	viper.AutomaticEnv() // read in environment variables that match
 
+	// register all known config keys
+	flat, _ := flatten.Flatten(structs.Map(conf), "", flatten.DotStyle)
+	for _, v := range maps.Keys(flat) {
+		_ = viper.BindEnv(v)
+	}
+
 	// print version
 	util.LogLevel("info", nil)
 	log.INFO.Printf("evcc %s", server.FormattedVersion())
diff --git a/core/loadpoint.go b/core/loadpoint.go
index 8e86fbe6..5eed52e4 100644
--- a/core/loadpoint.go
+++ b/core/loadpoint.go
@@ -103,6 +103,7 @@ type Loadpoint struct {
 	Mode       api.ChargeMode `mapstructure:"mode"` // Charge mode, guarded by mutex
 
 	Title_            string   `mapstructure:"title"`    // UI title
+	Priority_         int      `mapstructure:"priority"` // Priority
 	ConfiguredPhases  int      `mapstructure:"phases"`   // Charger configured phase mode 0/1/3
 	ChargerRef        string   `mapstructure:"charger"`  // Charger reference
 	VehicleRef        string   `mapstructure:"vehicle"`  // Vehicle reference
diff --git a/core/loadpoint/api.go b/core/loadpoint/api.go
index 29518939..20fea55c 100644
--- a/core/loadpoint/api.go
+++ b/core/loadpoint/api.go
@@ -6,6 +6,8 @@ import (
 	"github.com/evcc-io/evcc/api"
 )
 
+//go:generate mockgen -package loadpoint -destination mock.go -mock_names API=MockAPI github.com/evcc-io/evcc/core/loadpoint API
+
 // Controller gives access to loadpoint
 type Controller interface {
 	LoadpointControl(API)
@@ -15,6 +17,7 @@ type Controller interface {
 type API interface {
 	// Title returns the defined loadpoint title
 	Title() string
+	Priority() int
 
 	//
 	// status
@@ -76,6 +79,8 @@ type API interface {
 	HasChargeMeter() bool
 	// GetChargePower returns the current charging power
 	GetChargePower() float64
+	// GetChargePowerFlexibility returns the flexible amount of current charging power
+	GetChargePowerFlexibility() float64
 	// GetMinCurrent returns the min charging current
 	GetMinCurrent() float64
 	// SetMinCurrent sets the min charging current
diff --git a/core/loadpoint/mock.go b/core/loadpoint/mock.go
new file mode 100644
index 00000000..991780e1
--- /dev/null
+++ b/core/loadpoint/mock.go
@@ -0,0 +1,520 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: github.com/evcc-io/evcc/core/loadpoint (interfaces: API)
+
+// Package loadpoint is a generated GoMock package.
+package loadpoint
+
+import (
+	reflect "reflect"
+	time "time"
+
+	api "github.com/evcc-io/evcc/api"
+	gomock "github.com/golang/mock/gomock"
+)
+
+// MockAPI is a mock of API interface.
+type MockAPI struct {
+	ctrl     *gomock.Controller
+	recorder *MockAPIMockRecorder
+}
+
+// MockAPIMockRecorder is the mock recorder for MockAPI.
+type MockAPIMockRecorder struct {
+	mock *MockAPI
+}
+
+// NewMockAPI creates a new mock instance.
+func NewMockAPI(ctrl *gomock.Controller) *MockAPI {
+	mock := &MockAPI{ctrl: ctrl}
+	mock.recorder = &MockAPIMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockAPI) EXPECT() *MockAPIMockRecorder {
+	return m.recorder
+}
+
+// GetChargePower mocks base method.
+func (m *MockAPI) GetChargePower() float64 {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetChargePower")
+	ret0, _ := ret[0].(float64)
+	return ret0
+}
+
+// GetChargePower indicates an expected call of GetChargePower.
+func (mr *MockAPIMockRecorder) GetChargePower() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChargePower", reflect.TypeOf((*MockAPI)(nil).GetChargePower))
+}
+
+// GetChargePowerFlexibility mocks base method.
+func (m *MockAPI) GetChargePowerFlexibility() float64 {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetChargePowerFlexibility")
+	ret0, _ := ret[0].(float64)
+	return ret0
+}
+
+// GetChargePowerFlexibility indicates an expected call of GetChargePowerFlexibility.
+func (mr *MockAPIMockRecorder) GetChargePowerFlexibility() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChargePowerFlexibility", reflect.TypeOf((*MockAPI)(nil).GetChargePowerFlexibility))
+}
+
+// GetDisableThreshold mocks base method.
+func (m *MockAPI) GetDisableThreshold() float64 {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetDisableThreshold")
+	ret0, _ := ret[0].(float64)
+	return ret0
+}
+
+// GetDisableThreshold indicates an expected call of GetDisableThreshold.
+func (mr *MockAPIMockRecorder) GetDisableThreshold() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDisableThreshold", reflect.TypeOf((*MockAPI)(nil).GetDisableThreshold))
+}
+
+// GetEnableThreshold mocks base method.
+func (m *MockAPI) GetEnableThreshold() float64 {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetEnableThreshold")
+	ret0, _ := ret[0].(float64)
+	return ret0
+}
+
+// GetEnableThreshold indicates an expected call of GetEnableThreshold.
+func (mr *MockAPIMockRecorder) GetEnableThreshold() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEnableThreshold", reflect.TypeOf((*MockAPI)(nil).GetEnableThreshold))
+}
+
+// GetMaxCurrent mocks base method.
+func (m *MockAPI) GetMaxCurrent() float64 {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetMaxCurrent")
+	ret0, _ := ret[0].(float64)
+	return ret0
+}
+
+// GetMaxCurrent indicates an expected call of GetMaxCurrent.
+func (mr *MockAPIMockRecorder) GetMaxCurrent() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMaxCurrent", reflect.TypeOf((*MockAPI)(nil).GetMaxCurrent))
+}
+
+// GetMaxPower mocks base method.
+func (m *MockAPI) GetMaxPower() float64 {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetMaxPower")
+	ret0, _ := ret[0].(float64)
+	return ret0
+}
+
+// GetMaxPower indicates an expected call of GetMaxPower.
+func (mr *MockAPIMockRecorder) GetMaxPower() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMaxPower", reflect.TypeOf((*MockAPI)(nil).GetMaxPower))
+}
+
+// GetMinCurrent mocks base method.
+func (m *MockAPI) GetMinCurrent() float64 {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetMinCurrent")
+	ret0, _ := ret[0].(float64)
+	return ret0
+}
+
+// GetMinCurrent indicates an expected call of GetMinCurrent.
+func (mr *MockAPIMockRecorder) GetMinCurrent() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMinCurrent", reflect.TypeOf((*MockAPI)(nil).GetMinCurrent))
+}
+
+// GetMinPower mocks base method.
+func (m *MockAPI) GetMinPower() float64 {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetMinPower")
+	ret0, _ := ret[0].(float64)
+	return ret0
+}
+
+// GetMinPower indicates an expected call of GetMinPower.
+func (mr *MockAPIMockRecorder) GetMinPower() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMinPower", reflect.TypeOf((*MockAPI)(nil).GetMinPower))
+}
+
+// GetMinSoc mocks base method.
+func (m *MockAPI) GetMinSoc() int {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetMinSoc")
+	ret0, _ := ret[0].(int)
+	return ret0
+}
+
+// GetMinSoc indicates an expected call of GetMinSoc.
+func (mr *MockAPIMockRecorder) GetMinSoc() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMinSoc", reflect.TypeOf((*MockAPI)(nil).GetMinSoc))
+}
+
+// GetMode mocks base method.
+func (m *MockAPI) GetMode() api.ChargeMode {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetMode")
+	ret0, _ := ret[0].(api.ChargeMode)
+	return ret0
+}
+
+// GetMode indicates an expected call of GetMode.
+func (mr *MockAPIMockRecorder) GetMode() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMode", reflect.TypeOf((*MockAPI)(nil).GetMode))
+}
+
+// GetPhases mocks base method.
+func (m *MockAPI) GetPhases() int {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetPhases")
+	ret0, _ := ret[0].(int)
+	return ret0
+}
+
+// GetPhases indicates an expected call of GetPhases.
+func (mr *MockAPIMockRecorder) GetPhases() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPhases", reflect.TypeOf((*MockAPI)(nil).GetPhases))
+}
+
+// GetPlan mocks base method.
+func (m *MockAPI) GetPlan(arg0 time.Time, arg1 float64) (time.Duration, api.Rates, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetPlan", arg0, arg1)
+	ret0, _ := ret[0].(time.Duration)
+	ret1, _ := ret[1].(api.Rates)
+	ret2, _ := ret[2].(error)
+	return ret0, ret1, ret2
+}
+
+// GetPlan indicates an expected call of GetPlan.
+func (mr *MockAPIMockRecorder) GetPlan(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPlan", reflect.TypeOf((*MockAPI)(nil).GetPlan), arg0, arg1)
+}
+
+// GetPlannerUnit mocks base method.
+func (m *MockAPI) GetPlannerUnit() string {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetPlannerUnit")
+	ret0, _ := ret[0].(string)
+	return ret0
+}
+
+// GetPlannerUnit indicates an expected call of GetPlannerUnit.
+func (mr *MockAPIMockRecorder) GetPlannerUnit() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPlannerUnit", reflect.TypeOf((*MockAPI)(nil).GetPlannerUnit))
+}
+
+// GetRemainingDuration mocks base method.
+func (m *MockAPI) GetRemainingDuration() time.Duration {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetRemainingDuration")
+	ret0, _ := ret[0].(time.Duration)
+	return ret0
+}
+
+// GetRemainingDuration indicates an expected call of GetRemainingDuration.
+func (mr *MockAPIMockRecorder) GetRemainingDuration() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRemainingDuration", reflect.TypeOf((*MockAPI)(nil).GetRemainingDuration))
+}
+
+// GetRemainingEnergy mocks base method.
+func (m *MockAPI) GetRemainingEnergy() float64 {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetRemainingEnergy")
+	ret0, _ := ret[0].(float64)
+	return ret0
+}
+
+// GetRemainingEnergy indicates an expected call of GetRemainingEnergy.
+func (mr *MockAPIMockRecorder) GetRemainingEnergy() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRemainingEnergy", reflect.TypeOf((*MockAPI)(nil).GetRemainingEnergy))
+}
+
+// GetStatus mocks base method.
+func (m *MockAPI) GetStatus() api.ChargeStatus {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetStatus")
+	ret0, _ := ret[0].(api.ChargeStatus)
+	return ret0
+}
+
+// GetStatus indicates an expected call of GetStatus.
+func (mr *MockAPIMockRecorder) GetStatus() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStatus", reflect.TypeOf((*MockAPI)(nil).GetStatus))
+}
+
+// GetTargetEnergy mocks base method.
+func (m *MockAPI) GetTargetEnergy() float64 {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetTargetEnergy")
+	ret0, _ := ret[0].(float64)
+	return ret0
+}
+
+// GetTargetEnergy indicates an expected call of GetTargetEnergy.
+func (mr *MockAPIMockRecorder) GetTargetEnergy() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTargetEnergy", reflect.TypeOf((*MockAPI)(nil).GetTargetEnergy))
+}
+
+// GetTargetSoc mocks base method.
+func (m *MockAPI) GetTargetSoc() int {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetTargetSoc")
+	ret0, _ := ret[0].(int)
+	return ret0
+}
+
+// GetTargetSoc indicates an expected call of GetTargetSoc.
+func (mr *MockAPIMockRecorder) GetTargetSoc() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTargetSoc", reflect.TypeOf((*MockAPI)(nil).GetTargetSoc))
+}
+
+// GetTargetTime mocks base method.
+func (m *MockAPI) GetTargetTime() time.Time {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetTargetTime")
+	ret0, _ := ret[0].(time.Time)
+	return ret0
+}
+
+// GetTargetTime indicates an expected call of GetTargetTime.
+func (mr *MockAPIMockRecorder) GetTargetTime() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTargetTime", reflect.TypeOf((*MockAPI)(nil).GetTargetTime))
+}
+
+// GetVehicle mocks base method.
+func (m *MockAPI) GetVehicle() api.Vehicle {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetVehicle")
+	ret0, _ := ret[0].(api.Vehicle)
+	return ret0
+}
+
+// GetVehicle indicates an expected call of GetVehicle.
+func (mr *MockAPIMockRecorder) GetVehicle() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVehicle", reflect.TypeOf((*MockAPI)(nil).GetVehicle))
+}
+
+// HasChargeMeter mocks base method.
+func (m *MockAPI) HasChargeMeter() bool {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "HasChargeMeter")
+	ret0, _ := ret[0].(bool)
+	return ret0
+}
+
+// HasChargeMeter indicates an expected call of HasChargeMeter.
+func (mr *MockAPIMockRecorder) HasChargeMeter() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasChargeMeter", reflect.TypeOf((*MockAPI)(nil).HasChargeMeter))
+}
+
+// Priority mocks base method.
+func (m *MockAPI) Priority() int {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Priority")
+	ret0, _ := ret[0].(int)
+	return ret0
+}
+
+// Priority indicates an expected call of Priority.
+func (mr *MockAPIMockRecorder) Priority() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Priority", reflect.TypeOf((*MockAPI)(nil).Priority))
+}
+
+// RemoteControl mocks base method.
+func (m *MockAPI) RemoteControl(arg0 string, arg1 RemoteDemand) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "RemoteControl", arg0, arg1)
+}
+
+// RemoteControl indicates an expected call of RemoteControl.
+func (mr *MockAPIMockRecorder) RemoteControl(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoteControl", reflect.TypeOf((*MockAPI)(nil).RemoteControl), arg0, arg1)
+}
+
+// SetDisableThreshold mocks base method.
+func (m *MockAPI) SetDisableThreshold(arg0 float64) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "SetDisableThreshold", arg0)
+}
+
+// SetDisableThreshold indicates an expected call of SetDisableThreshold.
+func (mr *MockAPIMockRecorder) SetDisableThreshold(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDisableThreshold", reflect.TypeOf((*MockAPI)(nil).SetDisableThreshold), arg0)
+}
+
+// SetEnableThreshold mocks base method.
+func (m *MockAPI) SetEnableThreshold(arg0 float64) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "SetEnableThreshold", arg0)
+}
+
+// SetEnableThreshold indicates an expected call of SetEnableThreshold.
+func (mr *MockAPIMockRecorder) SetEnableThreshold(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetEnableThreshold", reflect.TypeOf((*MockAPI)(nil).SetEnableThreshold), arg0)
+}
+
+// SetMaxCurrent mocks base method.
+func (m *MockAPI) SetMaxCurrent(arg0 float64) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "SetMaxCurrent", arg0)
+}
+
+// SetMaxCurrent indicates an expected call of SetMaxCurrent.
+func (mr *MockAPIMockRecorder) SetMaxCurrent(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMaxCurrent", reflect.TypeOf((*MockAPI)(nil).SetMaxCurrent), arg0)
+}
+
+// SetMinCurrent mocks base method.
+func (m *MockAPI) SetMinCurrent(arg0 float64) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "SetMinCurrent", arg0)
+}
+
+// SetMinCurrent indicates an expected call of SetMinCurrent.
+func (mr *MockAPIMockRecorder) SetMinCurrent(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMinCurrent", reflect.TypeOf((*MockAPI)(nil).SetMinCurrent), arg0)
+}
+
+// SetMinSoc mocks base method.
+func (m *MockAPI) SetMinSoc(arg0 int) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "SetMinSoc", arg0)
+}
+
+// SetMinSoc indicates an expected call of SetMinSoc.
+func (mr *MockAPIMockRecorder) SetMinSoc(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMinSoc", reflect.TypeOf((*MockAPI)(nil).SetMinSoc), arg0)
+}
+
+// SetMode mocks base method.
+func (m *MockAPI) SetMode(arg0 api.ChargeMode) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "SetMode", arg0)
+}
+
+// SetMode indicates an expected call of SetMode.
+func (mr *MockAPIMockRecorder) SetMode(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMode", reflect.TypeOf((*MockAPI)(nil).SetMode), arg0)
+}
+
+// SetPhases mocks base method.
+func (m *MockAPI) SetPhases(arg0 int) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SetPhases", arg0)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// SetPhases indicates an expected call of SetPhases.
+func (mr *MockAPIMockRecorder) SetPhases(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPhases", reflect.TypeOf((*MockAPI)(nil).SetPhases), arg0)
+}
+
+// SetTargetEnergy mocks base method.
+func (m *MockAPI) SetTargetEnergy(arg0 float64) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "SetTargetEnergy", arg0)
+}
+
+// SetTargetEnergy indicates an expected call of SetTargetEnergy.
+func (mr *MockAPIMockRecorder) SetTargetEnergy(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTargetEnergy", reflect.TypeOf((*MockAPI)(nil).SetTargetEnergy), arg0)
+}
+
+// SetTargetSoc mocks base method.
+func (m *MockAPI) SetTargetSoc(arg0 int) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "SetTargetSoc", arg0)
+}
+
+// SetTargetSoc indicates an expected call of SetTargetSoc.
+func (mr *MockAPIMockRecorder) SetTargetSoc(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTargetSoc", reflect.TypeOf((*MockAPI)(nil).SetTargetSoc), arg0)
+}
+
+// SetTargetTime mocks base method.
+func (m *MockAPI) SetTargetTime(arg0 time.Time) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SetTargetTime", arg0)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// SetTargetTime indicates an expected call of SetTargetTime.
+func (mr *MockAPIMockRecorder) SetTargetTime(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTargetTime", reflect.TypeOf((*MockAPI)(nil).SetTargetTime), arg0)
+}
+
+// SetVehicle mocks base method.
+func (m *MockAPI) SetVehicle(arg0 api.Vehicle) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "SetVehicle", arg0)
+}
+
+// SetVehicle indicates an expected call of SetVehicle.
+func (mr *MockAPIMockRecorder) SetVehicle(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetVehicle", reflect.TypeOf((*MockAPI)(nil).SetVehicle), arg0)
+}
+
+// StartVehicleDetection mocks base method.
+func (m *MockAPI) StartVehicleDetection() {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "StartVehicleDetection")
+}
+
+// StartVehicleDetection indicates an expected call of StartVehicleDetection.
+func (mr *MockAPIMockRecorder) StartVehicleDetection() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartVehicleDetection", reflect.TypeOf((*MockAPI)(nil).StartVehicleDetection))
+}
+
+// Title mocks base method.
+func (m *MockAPI) Title() string {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Title")
+	ret0, _ := ret[0].(string)
+	return ret0
+}
+
+// Title indicates an expected call of Title.
+func (mr *MockAPIMockRecorder) Title() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Title", reflect.TypeOf((*MockAPI)(nil).Title))
+}
diff --git a/core/loadpoint_api.go b/core/loadpoint_api.go
index 04a15c53..2a32e5c1 100644
--- a/core/loadpoint_api.go
+++ b/core/loadpoint_api.go
@@ -3,6 +3,7 @@ package core
 import (
 	"errors"
 	"fmt"
+	"math"
 	"time"
 
 	"github.com/evcc-io/evcc/api"
@@ -17,6 +18,11 @@ func (lp *Loadpoint) Title() string {
 	return lp.Title_
 }
 
+// Priority returns the loadpoint priority
+func (lp *Loadpoint) Priority() int {
+	return lp.Priority_
+}
+
 // GetStatus returns the charging status
 func (lp *Loadpoint) GetStatus() api.ChargeStatus {
 	lp.Lock()
@@ -285,6 +291,22 @@ func (lp *Loadpoint) GetChargePower() float64 {
 	return lp.chargePower
 }
 
+// GetChargePowerFlexibility returns the flexible amount of current charging power
+func (lp *Loadpoint) GetChargePowerFlexibility() float64 {
+	// no locking
+	mode := lp.GetMode()
+	if mode == api.ModeNow || !lp.charging() || lp.minSocNotReached() {
+		return 0
+	}
+
+	if mode == api.ModePV {
+		return lp.GetChargePower()
+	}
+
+	// MinPV mode
+	return math.Max(0, lp.GetChargePower()-lp.GetMinPower())
+}
+
 // GetMinCurrent returns the min loadpoint current
 func (lp *Loadpoint) GetMinCurrent() float64 {
 	lp.Lock()
diff --git a/core/prioritizer/prioritizer.go b/core/prioritizer/prioritizer.go
new file mode 100644
index 00000000..334dac20
--- /dev/null
+++ b/core/prioritizer/prioritizer.go
@@ -0,0 +1,34 @@
+package prioritizer
+
+import (
+	"github.com/evcc-io/evcc/core/loadpoint"
+)
+
+type Prioritizer struct {
+	demand map[loadpoint.API]float64
+}
+
+func New() *Prioritizer {
+	return &Prioritizer{
+		demand: make(map[loadpoint.API]float64),
+	}
+}
+
+func (p *Prioritizer) UpdateChargePowerFlexibility(lp loadpoint.API) {
+	if power := lp.GetChargePowerFlexibility(); power >= 0 {
+		p.demand[lp] = power
+	}
+}
+
+func (p *Prioritizer) GetChargePowerFlexibility(lp loadpoint.API) float64 {
+	prio := lp.Priority()
+
+	var reduceBy float64
+	for lp, power := range p.demand {
+		if lp.Priority() < prio {
+			reduceBy += power
+		}
+	}
+
+	return reduceBy
+}
diff --git a/core/site.go b/core/site.go
index 95ba444a..dac066e3 100644
--- a/core/site.go
+++ b/core/site.go
@@ -14,6 +14,7 @@ import (
 	"github.com/evcc-io/evcc/core/db"
 	"github.com/evcc-io/evcc/core/loadpoint"
 	"github.com/evcc-io/evcc/core/planner"
+	"github.com/evcc-io/evcc/core/prioritizer"
 	"github.com/evcc-io/evcc/push"
 	serverdb "github.com/evcc-io/evcc/server/db"
 	"github.com/evcc-io/evcc/tariff"
@@ -25,6 +26,7 @@ const standbyPower = 10 // consider less than 10W as charger in standby
 
 // Updater abstracts the Loadpoint implementation for testing
 type Updater interface {
+	loadpoint.API
 	Update(availablePower float64, batteryBuffered bool)
 }
 
@@ -68,6 +70,7 @@ type Site struct {
 	tariffs     tariff.Tariffs           // Tariff
 	loadpoints  []*Loadpoint             // Loadpoints
 	coordinator *coordinator.Coordinator // Vehicles
+	prioritizer *prioritizer.Prioritizer // Power budgets
 	savings     *Savings                 // Savings
 
 	// cached state
@@ -108,6 +111,7 @@ func NewSiteFromConfig(
 	site.loadpoints = loadpoints
 	site.tariffs = tariffs
 	site.coordinator = coordinator.New(log, vehicles)
+	site.prioritizer = prioritizer.New()
 	site.savings = NewSavings(tariffs)
 
 	// upload telemetry on shutdown
@@ -485,7 +489,7 @@ func (site *Site) updateMeters() error {
 
 // sitePower returns the net power exported by the site minus a residual margin.
 // negative values mean grid: export, battery: charging
-func (site *Site) sitePower(totalChargePower float64) (float64, error) {
+func (site *Site) sitePower(totalChargePower, flexiblePower float64) (float64, error) {
 	if err := site.updateMeters(); err != nil {
 		return 0, err
 	}
@@ -537,6 +541,12 @@ func (site *Site) sitePower(totalChargePower float64) (float64, error) {
 	site.publish("auxPower", auxPower)
 	sitePower -= auxPower
 
+	// handle priority
+	if flexiblePower > 0 {
+		site.log.DEBUG.Printf("giving loadpoint priority for additional: %.0fW", flexiblePower)
+		sitePower -= flexiblePower
+	}
+
 	site.log.DEBUG.Printf("site power: %.0fW", sitePower)
 
 	return sitePower, nil
@@ -609,9 +619,17 @@ func (site *Site) update(lp Updater) {
 	for _, lp := range site.loadpoints {
 		lp.UpdateChargePower()
 		totalChargePower += lp.GetChargePower()
+
+		site.prioritizer.UpdateChargePowerFlexibility(lp)
+	}
+
+	// prioritize if possible
+	var flexiblePower float64
+	if lp.GetMode() == api.ModePV {
+		flexiblePower = site.prioritizer.GetChargePowerFlexibility(lp)
 	}
 
-	if sitePower, err := site.sitePower(totalChargePower); err == nil {
+	if sitePower, err := site.sitePower(totalChargePower, flexiblePower); err == nil {
 		lp.Update(sitePower, site.batteryBuffered)
 
 		// ignore negative pvPower values as that means it is not an energy source but consumption
diff --git a/templates/definition/charger/smaevcharger.yaml b/templates/definition/charger/smaevcharger.yaml
index b1a0cb83..70a411b2 100644
--- a/templates/definition/charger/smaevcharger.yaml
+++ b/templates/definition/charger/smaevcharger.yaml
@@ -7,8 +7,8 @@ capabilities: ["mA"]
 requirements:
   evcc: ["sponsorship"]
   description:
-    de: Der EV Charger muss sich im Modus "Fast" befinden.
-    en: The charger must be switched to "Fast" charging mode.
+    de: Der EV Charger muss sich im Modus "Fast" befinden und der Benutzer muss die Rechte "Administrator" haben.
+    en: The charger must be switched to "Fast" charging mode and the user must have "Administrator" rights.
 params:
   - name: host
     required: true
diff --git a/templates/docs/charger/smaevcharger_0.yaml b/templates/docs/charger/smaevcharger_0.yaml
index e18c566c..65ac0c9b 100644
--- a/templates/docs/charger/smaevcharger_0.yaml
+++ b/templates/docs/charger/smaevcharger_0.yaml
@@ -4,7 +4,7 @@ product:
 capabilities: ["mA"]
 requirements: ["sponsorship"]
 description: |
-  Der EV Charger muss sich im Modus "Fast" befinden.
+  Der EV Charger muss sich im Modus "Fast" befinden und der Benutzer muss die Rechte "Administrator" haben.
 render:
   - default: |
       type: template


Test patch:
diff --git a/core/prioritizer/prioritizer_test.go b/core/prioritizer/prioritizer_test.go
new file mode 100644
index 00000000..f3d322c6
--- /dev/null
+++ b/core/prioritizer/prioritizer_test.go
@@ -0,0 +1,36 @@
+package prioritizer
+
+import (
+	"testing"
+
+	"github.com/evcc-io/evcc/core/loadpoint"
+	"github.com/golang/mock/gomock"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestPrioritzer(t *testing.T) {
+	ctrl := gomock.NewController(t)
+
+	p := New()
+
+	lo := loadpoint.NewMockAPI(ctrl)
+	lo.EXPECT().Priority().Return(0).AnyTimes()
+
+	hi := loadpoint.NewMockAPI(ctrl)
+	hi.EXPECT().Priority().Return(1).AnyTimes()
+
+	// no additional power available
+	lo.EXPECT().GetChargePowerFlexibility().Return(300.0)
+	p.UpdateChargePowerFlexibility(lo)
+	assert.Equal(t, 0.0, p.GetChargePowerFlexibility(lo))
+
+	// additional power available
+	hi.EXPECT().GetChargePowerFlexibility().Return(1e3)
+	p.UpdateChargePowerFlexibility(hi)
+	assert.Equal(t, 300.0, p.GetChargePowerFlexibility(hi))
+
+	// additional power removed
+	lo.EXPECT().GetChargePowerFlexibility().Return(0.0)
+	p.UpdateChargePowerFlexibility(lo)
+	assert.Equal(t, 0.0, p.GetChargePowerFlexibility(hi))
+}