Add smart feed-in priority (#21813)

This commit is contained in:
andig 2025-06-27 09:03:46 +02:00 • committed by GitHub
parent 6ba804b106
commit 42ed7da2e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
63 changed files with 1645 additions and 1290 deletions

View file

@ -42,8 +42,12 @@ const (
// smart charging
SmartCostActive = "smartCostActive" // smart cost active
SmartCostLimit = "smartCostLimit" // smart cost limit
SmartCostNextStart = "smartCostNextStart" // smart cost next start
SmartCostLimit = "smartCostLimit" // smart cost limit, fast charge when costs are below
SmartCostNextStart = "smartCostNextStart" // smart cost next start, time of next fast charging
SmartFeedInPriorityActive = "smartFeedInPriorityActive" // smart feed-in priority active
SmartFeedInPriorityLimit = "smartFeedInPriorityLimit" // smart feed-in priority limit, pause self-consumption when feed-in rates are above
SmartFeedInPriorityNextStart = "smartFeedInPriorityNextStart" // smart feed-in priority next start, time of next pause
// effective values
EffectivePriority = "effectivePriority" // effective priority

View file

@ -56,4 +56,8 @@ const (
// external battery control
BatteryModeExternal = "batteryModeExternal"
// smart charging
SmartCostAvailable = "smartCostAvailable" // smart cost available
SmartFeedInPriorityAvailable = "smartFeedInPriorityAvailable" // smart feed-in priority available
)

View file

@ -107,15 +107,16 @@ type Loadpoint struct {
MinCurrent_ float64 `mapstructure:"minCurrent"` // ignored, present for compatibility
MaxCurrent_ float64 `mapstructure:"maxCurrent"` // ignored, present for compatibility
title string // UI title
priority int // Priority
minCurrent float64 // PV mode: start current Min+PV mode: min current
maxCurrent float64 // Max allowed current. Physically ensured by the charger
phasesConfigured int // Charger configured phase mode 0/1/3
limitSoc int // Session limit for soc
limitEnergy float64 // Session limit for energy
smartCostLimit *float64 // always charge if cost is below this value
batteryBoost int // battery boost state
title string // UI title
priority int // Priority
minCurrent float64 // PV mode: start current Min+PV mode: min current
maxCurrent float64 // Max allowed current. Physically ensured by the charger
phasesConfigured int // Charger configured phase mode 0/1/3
limitSoc int // Session limit for soc
limitEnergy float64 // Session limit for energy
smartCostLimit *float64 // always charge if consumption cost is below this value
smartFeedInPriorityLimit *float64 // prevent charging if feed-in cost is above this value
batteryBoost int // battery boost state
mode api.ChargeMode
enabled bool // Charger enabled state
@ -332,6 +333,9 @@ func (lp *Loadpoint) restoreSettings() {
if v, err := lp.settings.Float(keys.SmartCostLimit); err == nil {
lp.SetSmartCostLimit(&v)
}
if v, err := lp.settings.Float(keys.SmartFeedInPriorityLimit); err == nil {
lp.SetSmartFeedInPriorityLimit(&v)
}
var thresholds loadpoint.ThresholdsConfig
if err := lp.settings.Json(keys.Thresholds, &thresholds); err == nil {
@ -632,6 +636,7 @@ func (lp *Loadpoint) Prepare(site site.API, uiChan chan<- util.Param, pushChan c
lp.publish(keys.ChargerSinglePhase, lp.getChargerPhysicalPhases() == 1)
lp.publish(keys.PhasesActive, lp.ActivePhases())
lp.publish(keys.SmartCostLimit, lp.smartCostLimit)
lp.publish(keys.SmartFeedInPriorityLimit, lp.smartFeedInPriorityLimit)
lp.publishTimer(phaseTimer, 0, timerInactive)
lp.publishTimer(pvTimer, 0, timerInactive)
@ -1781,17 +1786,16 @@ func (lp *Loadpoint) phaseSwitchCompleted() bool {
}
// Update is the main control function. It reevaluates meters and charger state
func (lp *Loadpoint) Update(sitePower, batteryBoostPower float64, rates api.Rates, batteryBuffered, batteryStart bool, greenShare float64, effPrice, effCo2 *float64) {
func (lp *Loadpoint) Update(sitePower, batteryBoostPower float64, consumption, feedin api.Rates, batteryBuffered, batteryStart bool, greenShare float64, effPrice, effCo2 *float64) {
// smart cost
smartCostActive := lp.smartCostActive(rates)
smartCostActive, smartCostNextStart := lp.checkSmartLimit(lp.GetSmartCostLimit(), consumption, true)
lp.publish(keys.SmartCostActive, smartCostActive)
var smartCostNextStart time.Time
if !smartCostActive {
smartCostNextStart = lp.smartCostNextStart(rates)
}
lp.publish(keys.SmartCostNextStart, smartCostNextStart)
smartFeedInPriorityActive, smartFeedInPriorityNextStart := lp.checkSmartLimit(lp.GetSmartFeedInPriorityLimit(), feedin, false)
lp.publish(keys.SmartFeedInPriorityActive, smartFeedInPriorityActive)
lp.publish(keys.SmartFeedInPriorityNextStart, smartFeedInPriorityNextStart)
// long-running tasks
lp.processTasks()
@ -1902,14 +1906,30 @@ func (lp *Loadpoint) Update(sitePower, batteryBoostPower float64, rates api.Rate
case mode == api.ModeMinPV || mode == api.ModePV:
// cheap tariff
if smartCostActive {
rate, _ := rates.At(time.Now())
lp.log.DEBUG.Printf("smart cost active: %.2f", rate.Value)
rate, _ := consumption.At(time.Now())
lp.log.DEBUG.Printf("smart consumption active: %.2f", rate.Value)
err = lp.fastCharging()
lp.resetPhaseTimer()
lp.elapsePVTimer() // let PV mode disable immediately afterwards
break
}
// attractive feedin
if smartFeedInPriorityActive {
rate, _ := feedin.At(time.Now())
lp.log.DEBUG.Printf("smart feed-in active: %.2f", rate.Value)
var targetCurrent float64
if mode == api.ModeMinPV {
targetCurrent = lp.GetMinCurrent()
}
err = lp.setLimit(targetCurrent)
lp.resetPhaseTimer()
lp.elapsePVTimer() // let PV mode disable immediately afterwards
break
}
targetCurrent := lp.pvMaxCurrent(mode, sitePower, batteryBoostPower, batteryBuffered, batteryStart)
if targetCurrent == 0 && lp.vehicleClimateActive() {

View file

@ -167,10 +167,14 @@ type API interface {
// smart grid charging
//
// GetSmartChargingActive determines if smart charging is active
// GetSmartCostLimit return the smart cost limit
GetSmartCostLimit() *float64
// SetSmartCostLimit sets the smart cost limit
SetSmartCostLimit(limit *float64)
// GetSmartFeedInPriorityLimit return the smart feed-in limit
GetSmartFeedInPriorityLimit() *float64
// SetSmartFeedInPriorityLimit sets the smart feed-in limit
SetSmartFeedInPriorityLimit(limit *float64)
//
// power and energy

View file

@ -17,18 +17,19 @@ type StaticConfig struct {
type DynamicConfig struct {
// dynamic config
Title string `json:"title"`
DefaultMode string `json:"defaultMode"`
Priority int `json:"priority"`
PhasesConfigured int `json:"phasesConfigured"`
MinCurrent float64 `json:"minCurrent"`
MaxCurrent float64 `json:"maxCurrent"`
SmartCostLimit *float64 `json:"smartCostLimit"`
PlanEnergy float64 `json:"planEnergy"`
PlanTime time.Time `json:"planTime"`
PlanPrecondition int64 `json:"planPrecondition"`
LimitEnergy float64 `json:"limitEnergy"`
LimitSoc int `json:"limitSoc"`
Title string `json:"title"`
DefaultMode string `json:"defaultMode"`
Priority int `json:"priority"`
PhasesConfigured int `json:"phasesConfigured"`
MinCurrent float64 `json:"minCurrent"`
MaxCurrent float64 `json:"maxCurrent"`
SmartCostLimit *float64 `json:"smartCostLimit"`
SmartFeedInPriorityLimit *float64 `json:"smartFeedInPriorityLimit"`
PlanEnergy float64 `json:"planEnergy"`
PlanTime time.Time `json:"planTime"`
PlanPrecondition int64 `json:"planPrecondition"`
LimitEnergy float64 `json:"limitEnergy"`
LimitSoc int `json:"limitSoc"`
Thresholds ThresholdsConfig `json:"thresholds"`
Soc SocConfig `json:"soc"`
@ -56,6 +57,7 @@ func (payload DynamicConfig) Apply(lp API) error {
lp.SetTitle(payload.Title)
lp.SetPriority(payload.Priority)
lp.SetSmartCostLimit(payload.SmartCostLimit)
lp.SetSmartFeedInPriorityLimit(payload.SmartFeedInPriorityLimit)
lp.SetThresholds(payload.Thresholds)
lp.SetPlanEnergy(payload.PlanTime, time.Duration(payload.PlanPrecondition)*time.Second, payload.PlanEnergy)
lp.SetLimitEnergy(payload.LimitEnergy)

View file

@ -548,6 +548,20 @@ func (mr *MockAPIMockRecorder) GetSmartCostLimit() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSmartCostLimit", reflect.TypeOf((*MockAPI)(nil).GetSmartCostLimit))
}
// GetSmartFeedInPriorityLimit mocks base method.
func (m *MockAPI) GetSmartFeedInPriorityLimit() *float64 {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSmartFeedInPriorityLimit")
ret0, _ := ret[0].(*float64)
return ret0
}
// GetSmartFeedInPriorityLimit indicates an expected call of GetSmartFeedInPriorityLimit.
func (mr *MockAPIMockRecorder) GetSmartFeedInPriorityLimit() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSmartFeedInPriorityLimit", reflect.TypeOf((*MockAPI)(nil).GetSmartFeedInPriorityLimit))
}
// GetSocConfig mocks base method.
func (m *MockAPI) GetSocConfig() SocConfig {
m.ctrl.T.Helper()
@ -908,6 +922,18 @@ func (mr *MockAPIMockRecorder) SetSmartCostLimit(limit any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSmartCostLimit", reflect.TypeOf((*MockAPI)(nil).SetSmartCostLimit), limit)
}
// SetSmartFeedInPriorityLimit mocks base method.
func (m *MockAPI) SetSmartFeedInPriorityLimit(limit *float64) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetSmartFeedInPriorityLimit", limit)
}
// SetSmartFeedInPriorityLimit indicates an expected call of SetSmartFeedInPriorityLimit.
func (mr *MockAPIMockRecorder) SetSmartFeedInPriorityLimit(limit any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSmartFeedInPriorityLimit", reflect.TypeOf((*MockAPI)(nil).SetSmartFeedInPriorityLimit), limit)
}
// SetSocConfig mocks base method.
func (m *MockAPI) SetSocConfig(soc SocConfig) {
m.ctrl.T.Helper()

View file

@ -608,7 +608,8 @@ func (lp *Loadpoint) GetChargePower() float64 {
// GetChargePowerFlexibility returns the flexible amount of current charging power
func (lp *Loadpoint) GetChargePowerFlexibility(rates api.Rates) float64 {
mode := lp.GetMode()
if mode == api.ModeNow || !lp.charging() || lp.minSocNotReached() || lp.smartCostActive(rates) {
if mode == api.ModeNow || !lp.charging() || lp.minSocNotReached() ||
lp.smartLimitActive(lp.GetSmartCostLimit(), rates, true) {
return 0
}
@ -807,6 +808,28 @@ func (lp *Loadpoint) SetSmartCostLimit(val *float64) {
}
}
// GetSmartFeedInPriorityLimit gets the smart feed-in limit
func (lp *Loadpoint) GetSmartFeedInPriorityLimit() *float64 {
lp.RLock()
defer lp.RUnlock()
return lp.smartFeedInPriorityLimit
}
// SetSmartFeedInPriorityLimit sets the smart cost feed-in
func (lp *Loadpoint) SetSmartFeedInPriorityLimit(val *float64) {
lp.Lock()
defer lp.Unlock()
lp.log.DEBUG.Println("set smart feed-in limit:", printPtr("%.1f", val))
if !ptrValueEqual(lp.smartFeedInPriorityLimit, val) {
lp.smartFeedInPriorityLimit = val
lp.settings.SetFloatPtr(keys.SmartFeedInPriorityLimit, val)
lp.publish(keys.SmartFeedInPriorityLimit, val)
}
}
// GetCircuit returns the assigned circuit
func (lp *Loadpoint) GetCircuit() api.Circuit {
lp.RLock()

View file

@ -6,22 +6,40 @@ import (
"github.com/evcc-io/evcc/api"
)
func (lp *Loadpoint) smartCostActive(rates api.Rates) bool {
rate, err := rates.At(time.Now())
limit := lp.GetSmartCostLimit()
return err == nil && limit != nil && rate.Value <= *limit
// checkSmartLimit checks if current rate meets smart limit and returns next start time if not active.
// checkBelow: true for rate <= limit, false for rate >= limit
func (lp *Loadpoint) checkSmartLimit(limit *float64, rates api.Rates, checkBelow bool) (bool, time.Time) {
var nextStart time.Time
active := lp.smartLimitActive(limit, rates, checkBelow)
if !active {
nextStart = lp.smartLimitNextStart(limit, rates, checkBelow)
}
return active, nextStart
}
// smartCostNextStart returns the next start time for a smart cost rate below the limit
func (lp *Loadpoint) smartCostNextStart(rates api.Rates) time.Time {
limit := lp.GetSmartCostLimit()
func (lp *Loadpoint) smartLimitActive(limit *float64, rates api.Rates, checkBelow bool) bool {
rate, err := rates.At(time.Now())
if err != nil || limit == nil {
return false
}
if checkBelow {
return rate.Value <= *limit
}
return rate.Value >= *limit
}
// smartLimitNextStart returns the next start time when the smart limit condition will be met
func (lp *Loadpoint) smartLimitNextStart(limit *float64, rates api.Rates, checkBelow bool) time.Time {
if limit == nil || rates == nil {
return time.Time{}
}
now := time.Now()
for _, slot := range rates {
if slot.Start.After(now) && slot.Value <= *limit {
if slot.Start.After(now) && (checkBelow && slot.Value <= *limit || !checkBelow && slot.Value >= *limit) {
return slot.Start
}
}

View file

@ -183,7 +183,7 @@ func TestUpdatePowerZero(t *testing.T) {
}
lp.mode = tc.mode
lp.Update(0, 0, nil, false, false, 0, nil, nil) // false,sitePower false,0
lp.Update(0, 0, nil, nil, false, false, 0, nil, nil) // false,sitePower false,0
ctrl.Finish()
}
@ -428,7 +428,7 @@ func TestDisableAndEnableAtTargetSoc(t *testing.T) {
charger.EXPECT().Status().Return(api.StatusC, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().MaxCurrent(int64(maxA)).Return(nil)
lp.Update(500, 0, nil, false, false, 0, nil, nil)
lp.Update(500, 0, nil, nil, false, false, 0, nil, nil)
ctrl.Finish()
t.Log("charging above target - soc deactivates charger")
@ -437,7 +437,7 @@ func TestDisableAndEnableAtTargetSoc(t *testing.T) {
charger.EXPECT().Status().Return(api.StatusC, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Enable(false).Return(nil)
lp.Update(500, 0, nil, false, false, 0, nil, nil)
lp.Update(500, 0, nil, nil, false, false, 0, nil, nil)
ctrl.Finish()
t.Log("deactivated charger changes status to B")
@ -445,14 +445,14 @@ func TestDisableAndEnableAtTargetSoc(t *testing.T) {
vehicle.EXPECT().Soc().Return(95.0, nil)
charger.EXPECT().Status().Return(api.StatusB, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
lp.Update(-500, 0, nil, false, false, 0, nil, nil)
lp.Update(-500, 0, nil, nil, false, false, 0, nil, nil)
ctrl.Finish()
t.Log("soc has risen below target - soc update prevented by timer")
clock.Add(5 * time.Minute)
charger.EXPECT().Status().Return(api.StatusB, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
lp.Update(-500, 0, nil, false, false, 0, nil, nil)
lp.Update(-500, 0, nil, nil, false, false, 0, nil, nil)
ctrl.Finish()
t.Log("soc has fallen below target - soc update timer expired")
@ -462,7 +462,7 @@ func TestDisableAndEnableAtTargetSoc(t *testing.T) {
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().MaxCurrent(int64(maxA)).Return(nil)
charger.EXPECT().Enable(true).Return(nil)
lp.Update(-500, 0, nil, false, false, 0, nil, nil)
lp.Update(-500, 0, nil, nil, false, false, 0, nil, nil)
ctrl.Finish()
}
@ -497,14 +497,14 @@ func TestSetModeAndSocAtDisconnect(t *testing.T) {
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusC, nil)
charger.EXPECT().MaxCurrent(int64(maxA)).Return(nil)
lp.Update(500, 0, nil, false, false, 0, nil, nil)
lp.Update(500, 0, nil, nil, false, false, 0, nil, nil)
t.Log("switch off when disconnected")
clock.Add(5 * time.Minute)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusA, nil)
charger.EXPECT().Enable(false).Return(nil)
lp.Update(-300, 0, nil, false, false, 0, nil, nil)
lp.Update(-300, 0, nil, nil, false, false, 0, nil, nil)
if mode := lp.GetMode(); mode != api.ModeOff {
t.Error("unexpected mode", mode)
@ -566,14 +566,14 @@ func TestChargedEnergyAtDisconnect(t *testing.T) {
rater.EXPECT().ChargedEnergy().Return(0.0, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusC, nil)
lp.Update(-1, 0, nil, false, false, 0, nil, nil)
lp.Update(-1, 0, nil, nil, false, false, 0, nil, nil)
t.Log("at 1:00h charging at 5 kWh")
clock.Add(time.Hour)
rater.EXPECT().ChargedEnergy().Return(5.0, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusC, nil)
lp.Update(-1, 0, nil, false, false, 0, nil, nil)
lp.Update(-1, 0, nil, nil, false, false, 0, nil, nil)
expectCache("chargedEnergy", 5000.0)
t.Log("at 1:00h stop charging at 5 kWh")
@ -581,7 +581,7 @@ func TestChargedEnergyAtDisconnect(t *testing.T) {
rater.EXPECT().ChargedEnergy().Return(5.0, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusB, nil)
lp.Update(-1, 0, nil, false, false, 0, nil, nil)
lp.Update(-1, 0, nil, nil, false, false, 0, nil, nil)
expectCache("chargedEnergy", 5000.0)
t.Log("at 1:00h restart charging at 5 kWh")
@ -589,7 +589,7 @@ func TestChargedEnergyAtDisconnect(t *testing.T) {
rater.EXPECT().ChargedEnergy().Return(5.0, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusC, nil)
lp.Update(-1, 0, nil, false, false, 0, nil, nil)
lp.Update(-1, 0, nil, nil, false, false, 0, nil, nil)
expectCache("chargedEnergy", 5000.0)
t.Log("at 1:30h continue charging at 7.5 kWh")
@ -597,7 +597,7 @@ func TestChargedEnergyAtDisconnect(t *testing.T) {
rater.EXPECT().ChargedEnergy().Return(7.5, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusC, nil)
lp.Update(-1, 0, nil, false, false, 0, nil, nil)
lp.Update(-1, 0, nil, nil, false, false, 0, nil, nil)
expectCache("chargedEnergy", 7500.0)
t.Log("at 2:00h stop charging at 10 kWh")
@ -605,7 +605,7 @@ func TestChargedEnergyAtDisconnect(t *testing.T) {
rater.EXPECT().ChargedEnergy().Return(10.0, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusB, nil)
lp.Update(-1, 0, nil, false, false, 0, nil, nil)
lp.Update(-1, 0, nil, nil, false, false, 0, nil, nil)
expectCache("chargedEnergy", 10000.0)
ctrl.Finish()

View file

@ -269,7 +269,7 @@ func TestReconnectVehicle(t *testing.T) {
// vehicle not updated yet
vehicle.MockChargeState.EXPECT().Status().Return(api.StatusA, nil)
lp.Update(0, 0, nil, false, false, 0, nil, nil)
lp.Update(0, 0, nil, nil, false, false, 0, nil, nil)
ctrl.Finish()
// detection started
@ -283,7 +283,7 @@ func TestReconnectVehicle(t *testing.T) {
// vehicle not updated yet
vehicle.MockChargeState.EXPECT().Status().Return(api.StatusB, nil)
lp.Update(0, 0, nil, false, false, 0, nil, nil)
lp.Update(0, 0, nil, nil, false, false, 0, nil, nil)
ctrl.Finish()
// vehicle detected

View file

@ -42,7 +42,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(sitePower, batteryBoostPower float64, rates api.Rates, batteryBuffered, batteryStart bool, greenShare float64, effectivePrice, effectiveCo2 *float64)
Update(sitePower, batteryBoostPower float64, consumption, feedin api.Rates, batteryBuffered, batteryStart bool, greenShare float64, effectivePrice, effectiveCo2 *float64)
}
// measurement is used as slice element for publishing structured data
@ -871,13 +871,18 @@ func (site *Site) update(lp updater) {
site.log.DEBUG.Println("----")
// smart cost and battery mode handling
rates, err := site.plannerRates()
consumption, err := site.tariffRates(api.TariffUsagePlanner)
if err != nil {
site.log.WARN.Println("planner:", err)
}
feedin, err := site.tariffRates(api.TariffUsageFeedIn)
if err != nil {
site.log.WARN.Println("feed-in:", err)
}
// update loadpoints
totalChargePower := site.updateLoadpoints(rates)
totalChargePower := site.updateLoadpoints(consumption)
// update all circuits' power and currents
if site.circuit != nil {
@ -894,13 +899,13 @@ func (site *Site) update(lp updater) {
flexiblePower = site.prioritizer.GetChargePowerFlexibility(lp)
}
rate, err := rates.At(time.Now())
if rates != nil && err != nil {
rate, err := consumption.At(time.Now())
if consumption != nil && err != nil {
msg := fmt.Sprintf("no matching rate for: %s", time.Now().Format(time.RFC3339))
if len(rates) > 0 {
msg += fmt.Sprintf(", %d rates (%s to %s)", len(rates),
rates[0].Start.Local().Format(time.RFC3339),
rates[len(rates)-1].End.Local().Format(time.RFC3339),
if len(consumption) > 0 {
msg += fmt.Sprintf(", %d consumption rates (%s to %s)", len(consumption),
consumption[0].Start.Local().Format(time.RFC3339),
consumption[len(consumption)-1].End.Local().Format(time.RFC3339),
)
}
@ -923,8 +928,9 @@ func (site *Site) update(lp updater) {
greenShareHome := site.greenShare(0, homePower)
greenShareLoadpoints := site.greenShare(nonChargePower, nonChargePower+totalChargePower)
// TODO
lp.Update(
sitePower, max(0, site.batteryPower), rates, batteryBuffered, batteryStart,
sitePower, max(0, site.batteryPower), consumption, feedin, batteryBuffered, batteryStart,
greenShareLoadpoints, site.effectivePrice(greenShareLoadpoints), site.effectiveCo2(greenShareLoadpoints),
)
@ -962,6 +968,8 @@ func (site *Site) prepare() {
site.publish(keys.BatteryMode, site.batteryMode)
site.publish(keys.BatteryDischargeControl, site.batteryDischargeControl)
site.publish(keys.ResidualPower, site.GetResidualPower())
site.publish(keys.SmartCostAvailable, site.isDynamicTariff(api.TariffUsagePlanner))
site.publish(keys.SmartFeedInPriorityAvailable, site.isDynamicTariff(api.TariffUsageFeedIn))
site.publish(keys.Currency, site.tariffs.Currency)
if tariff := site.GetTariff(api.TariffUsagePlanner); tariff != nil {

View file

@ -106,8 +106,8 @@ func (site *Site) applyBatteryMode(mode api.BatteryMode) error {
return nil
}
func (site *Site) plannerRates() (api.Rates, error) {
tariff := site.GetTariff(api.TariffUsagePlanner)
func (site *Site) tariffRates(usage api.TariffUsage) (api.Rates, error) {
tariff := site.GetTariff(usage)
if tariff == nil || tariff.Type() == api.TariffTypePriceStatic {
return nil, nil
}

View file

@ -167,3 +167,8 @@ func (site *Site) solarDetails(solar timeseries) solarDetails {
return res
}
func (site *Site) isDynamicTariff(usage api.TariffUsage) bool {
tariff := site.GetTariff(usage)
return tariff != nil && tariff.Type() != api.TariffTypePriceStatic
}