Planner: drop deprecated publishing keys #2 (BC) (#26540)

This commit is contained in:
Ingo 2026-02-01 19:38:19 +01:00 • committed by GitHub
parent 8d350a7a65
commit e1accfeb9d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 135 additions and 142 deletions

View file

@ -1,17 +1,45 @@
package api
import "time"
import (
"encoding/json"
"time"
)
type RepeatingPlan struct {
Weekdays []int `json:"weekdays"` // 0-6 (Sunday-Saturday)
Time string `json:"time"` // HH:MM
Tz string `json:"tz"` // timezone in IANA format
Soc int `json:"soc"` // target soc
Active bool `json:"active"` // active flag
Precondition int64 `json:"-" todo:"..."` // TODO deprecated
Weekdays []int `json:"weekdays"` // 0-6 (Sunday-Saturday)
Time string `json:"time"` // HH:MM
Tz string `json:"tz"` // timezone in IANA format
Soc int `json:"soc"` // target soc
Active bool `json:"active"` // active flag
}
type PlanStrategy struct {
Continuous bool `json:"continuous"` // force continuous planning
Precondition time.Duration `json:"precondition"` // precondition duration in seconds
}
type planStrategy struct {
Continuous bool `json:"continuous"` // force continuous planning
Precondition int64 `json:"precondition"` // precondition duration in seconds
}
func (ps PlanStrategy) MarshalJSON() ([]byte, error) {
return json.Marshal(planStrategy{
Continuous: ps.Continuous,
Precondition: int64(ps.Precondition.Seconds()),
})
}
func (ps *PlanStrategy) UnmarshalJSON(data []byte) error {
var res planStrategy
if err := json.Unmarshal(data, &res); err != nil {
return err
}
*ps = PlanStrategy{
Continuous: res.Continuous,
Precondition: time.Duration(res.Precondition) * time.Second,
}
return nil
}

View file

@ -121,8 +121,7 @@ export default defineComponent({
effectiveLimitSoc: Number,
effectivePlanSoc: Number,
effectivePlanTime: String,
effectivePlanPrecondition: Number,
effectivePlanContinuous: Boolean,
effectivePlanStrategy: Object as PropType<PlanStrategy>,
id: [String, Number],
limitEnergy: Number,
mode: String,

View file

@ -62,7 +62,6 @@ export default defineComponent({
weekdays: DEFAULT_WEEKDAYS,
time: DEFAULT_TARGET_TIME,
soc: DEFAULT_TARGET_SOC,
precondition: 0,
active: false,
tz: this.timezone(),
};

View file

@ -56,7 +56,9 @@
</div>
</h5>
<ChargingPlanStrategy
v-bind="chargingPlanStrategyProps"
:id="id"
:precondition="effectivePlanStrategy?.precondition"
:continuous="effectivePlanStrategy?.continuous"
:disabled="strategyDisabled"
:show="strategyOpen"
@update="updatePlanStrategy"
@ -107,8 +109,7 @@ export default defineComponent({
effectiveLimitSoc: Number,
effectivePlanTime: String,
effectivePlanSoc: Number,
effectivePlanPrecondition: Number,
effectivePlanContinuous: Boolean,
effectivePlanStrategy: Object as PropType<PlanStrategy>,
planEnergy: Number,
limitEnergy: Number,
socBasedPlanning: Boolean,
@ -159,13 +160,6 @@ export default defineComponent({
? { duration, plan, power, rates, targetTime, currency, smartCostType }
: null;
},
chargingPlanStrategyProps(): any {
return {
id: this.id,
precondition: this.effectivePlanPrecondition,
continuous: this.effectivePlanContinuous,
};
},
alreadyReached(): boolean {
return this.plan.duration === 0;
},
@ -186,11 +180,11 @@ export default defineComponent({
this.updatePlanDebounced();
}
},
effectivePlanPrecondition() {
this.updatePlanDebounced();
},
effectivePlanContinuous() {
this.updatePlanDebounced();
effectivePlanStrategy: {
deep: true,
handler() {
this.updatePlanDebounced();
},
},
staticPlan: {
deep: true,

View file

@ -51,8 +51,7 @@ const baseState = {
planActive: false,
planEnergy: 0,
planOverrun: 0,
planPrecondition: 0,
planContinuous: false,
planStrategy: { continuous: false, precondition: 0 },
planProjectedEnd: undefined,
planProjectedStart: undefined,
planTime: undefined,
@ -83,8 +82,10 @@ const baseState = {
capacity: 8,
features: ["Offline"],
repeatingPlans: [],
planPrecondition: 0,
planContinuous: false,
planStrategy: {
continuous: false,
precondition: 0,
},
},
{
name: "vehicle_4",
@ -93,8 +94,10 @@ const baseState = {
capacity: 80,
features: ["Offline"],
repeatingPlans: [],
planPrecondition: 0,
planContinuous: false,
planStrategy: {
continuous: true,
precondition: 0,
},
},
],
smartCostType: SMART_COST_TYPE.PRICE_FORECAST,

View file

@ -127,6 +127,7 @@ import type {
Forecast,
SMART_COST_TYPE,
} from "@/types/evcc";
import type { PlanStrategy } from "@/components/ChargingPlans/types";
export default defineComponent({
name: "Loadpoint",
@ -189,8 +190,7 @@ export default defineComponent({
planTime: String as PropType<string | null>,
effectivePlanTime: String as PropType<string | null>,
effectivePlanSoc: Number,
effectivePlanPrecondition: Number,
effectivePlanContinuous: Boolean,
effectivePlanStrategy: Object as PropType<PlanStrategy>,
vehicleProviderLoggedIn: Boolean,
vehicleProviderLoginPath: String,
vehicleProviderLogoutPath: String,

View file

@ -48,8 +48,10 @@ const baseArgs = {
capacity: 8,
features: ["Offline"],
repeatingPlans: [],
planPrecondition: 0,
planContinuous: false,
planStrategy: {
continuous: false,
precondition: 0,
},
},
{
name: "vehicle_4",
@ -58,8 +60,10 @@ const baseArgs = {
capacity: 80,
features: ["Offline"],
repeatingPlans: [],
planPrecondition: 0,
planContinuous: false,
planStrategy: {
continuous: true,
precondition: 0,
},
},
{
name: "vehicle_5",
@ -68,8 +72,10 @@ const baseArgs = {
capacity: 0.46,
features: ["Offline"],
repeatingPlans: [],
planPrecondition: 0,
planContinuous: false,
planStrategy: {
continuous: false,
precondition: 0,
},
},
],
smartCostType: SMART_COST_TYPE.PRICE_FORECAST,

View file

@ -10,8 +10,10 @@ const baseState = {
features: [],
name: "",
repeatingPlans: [],
planPrecondition: 0,
planContinuous: false,
planStrategy: {
continuous: false,
precondition: 0,
},
},
enabled: false,
connected: true,

View file

@ -80,6 +80,7 @@ import LimitEnergySelect from "./LimitEnergySelect.vue";
import { distanceUnit, distanceValue } from "@/units.ts";
import { defineComponent, type PropType } from "vue";
import { CHARGE_MODE, type Forecast, type Vehicle } from "@/types/evcc";
import type { PlanStrategy } from "@/components/ChargingPlans/types";
export default defineComponent({
name: "Vehicle",
@ -103,8 +104,7 @@ export default defineComponent({
effectiveLimitSoc: Number,
effectivePlanSoc: Number,
effectivePlanTime: String,
effectivePlanPrecondition: Number,
effectivePlanContinuous: Boolean,
effectivePlanStrategy: Object as PropType<PlanStrategy>,
batteryBoostActive: Boolean,
enabled: Boolean,
heating: Boolean,

View file

@ -1,4 +1,4 @@
import type { StaticPlan, RepeatingPlan } from "../components/ChargingPlans/types";
import type { StaticPlan, RepeatingPlan, PlanStrategy } from "../components/ChargingPlans/types";
import type { ForecastSlot, SolarDetails } from "../components/Forecast/types";
// react-native-webview
@ -191,7 +191,7 @@ export interface ConfigLoadpoint {
smartCostLimit: number | null;
planEnergy?: number;
planTime?: string;
planPrecondition?: number;
planStrategy?: PlanStrategy;
limitEnergy?: number;
limitSoc?: number;
circuit?: string;
@ -246,8 +246,7 @@ export interface Loadpoint {
effectivePlanId: number;
effectivePlanSoc: number;
effectivePlanTime: string | null;
effectivePlanPrecondition: number;
effectivePlanContinuous: boolean;
effectivePlanStrategy: PlanStrategy;
effectivePriority: number;
enableDelay: number;
enableThreshold: number;
@ -265,8 +264,7 @@ export interface Loadpoint {
planActive: boolean;
planEnergy: number;
planOverrun: number;
planPrecondition: number;
planContinuous: boolean;
planStrategy: PlanStrategy;
planProjectedEnd: string | null;
planProjectedStart: string | null;
planTime: string | null;
@ -504,8 +502,7 @@ export interface Vehicle {
limitSoc?: number;
plan?: StaticPlan;
repeatingPlans: RepeatingPlan[] | null;
planPrecondition: number;
planContinuous: boolean;
planStrategy: PlanStrategy;
title: string;
features?: string[];
capacity?: number;

View file

@ -58,10 +58,8 @@ const (
EffectiveMinCurrent = "effectiveMinCurrent" // effective min current
EffectiveMaxCurrent = "effectiveMaxCurrent" // effective max current
EffectiveLimitSoc = "effectiveLimitSoc" // effective limit soc
EffectivePlanStrategy = "effectivePlanStrategy" // effective plan strategy (deprecated, use individual fields)
EffectivePlanPrecondition = "effectivePlanPrecondition" // effective plan precondition duration
EffectivePlanContinuous = "effectivePlanContinuous" // effective plan continuous planning
EffectiveLimitSoc = "effectiveLimitSoc" // effective limit soc
EffectivePlanStrategy = "effectivePlanStrategy" // effective plan strategy
// measurements
ChargePower = "chargePower" // charge power
@ -86,8 +84,6 @@ const (
PlanProjectedEnd = "planProjectedEnd" // charge plan ends (end of last slot)
PlanOverrun = "planOverrun" // charge plan goal not reachable in time
PlanStrategy = "planStrategy" // charge plan strategy (precondition, continuous)
PlanPrecondition = "planPrecondition" // charge plan precondition duration
PlanContinuous = "planContinuous" // charge plan continuous planning
// repeating plans
RepeatingPlans = "repeatingPlans" // key to access all repeating plans in db

View file

@ -690,8 +690,7 @@ func (lp *Loadpoint) Prepare(site site.API, uiChan chan<- util.Param, pushChan c
// restored settings
lp.publish(keys.PlanTime, lp.planTime)
lp.publish(keys.PlanEnergy, lp.planEnergy)
lp.publish(keys.PlanPrecondition, int64(lp.planStrategy.Precondition.Seconds()))
lp.publish(keys.PlanContinuous, lp.planStrategy.Continuous)
lp.publish(keys.PlanStrategy, lp.planStrategy)
lp.publish(keys.LimitSoc, lp.limitSoc)
lp.publish(keys.LimitEnergy, lp.limitEnergy)

View file

@ -405,8 +405,9 @@ func (lp *Loadpoint) setPlanStrategy(strategy api.PlanStrategy) error {
}
lp.planStrategy = strategy
lp.publish(keys.PlanPrecondition, int64(strategy.Precondition.Seconds()))
lp.publish(keys.PlanContinuous, strategy.Continuous)
lp.publish(keys.PlanStrategy, strategy)
lp.publish(keys.EffectivePlanStrategy, lp.getEffectivePlanStrategy())
lp.requestUpdate()

View file

@ -12,16 +12,14 @@ import (
// PublishEffectiveValues publishes all effective values
func (lp *Loadpoint) PublishEffectiveValues() {
strategy := lp.EffectivePlanStrategy()
lp.publish(keys.EffectivePriority, lp.EffectivePriority())
lp.publish(keys.EffectivePlanId, lp.EffectivePlanId())
lp.publish(keys.EffectivePlanTime, lp.EffectivePlanTime())
lp.publish(keys.EffectivePlanSoc, lp.EffectivePlanSoc())
lp.publish(keys.EffectivePlanStrategy, lp.EffectivePlanStrategy())
lp.publish(keys.EffectiveMinCurrent, lp.effectiveMinCurrent())
lp.publish(keys.EffectiveMaxCurrent, lp.effectiveMaxCurrent())
lp.publish(keys.EffectiveLimitSoc, lp.EffectiveLimitSoc())
lp.publish(keys.EffectivePlanPrecondition, int64(strategy.Precondition.Seconds()))
lp.publish(keys.EffectivePlanContinuous, strategy.Continuous)
}
// EffectivePriority returns the effective priority

View file

@ -13,27 +13,24 @@ import (
)
type planStruct struct {
Soc int `json:"soc"`
Continuous bool `json:"continuous"`
Precondition int64 `json:"precondition"`
Time time.Time `json:"time"`
Soc int `json:"soc"`
Time time.Time `json:"time"`
}
type vehicleStruct struct {
Title string `json:"title"`
Icon string `json:"icon,omitempty"`
Capacity float64 `json:"capacity,omitempty"`
Phases int `json:"phases,omitempty"`
MinSoc int `json:"minSoc,omitempty"`
LimitSoc int `json:"limitSoc,omitempty"`
MinCurrent float64 `json:"minCurrent,omitempty"`
MaxCurrent float64 `json:"maxCurrent,omitempty"`
Priority int `json:"priority,omitempty"`
Features []string `json:"features,omitempty"`
Plan *planStruct `json:"plan,omitempty"`
RepeatingPlans []api.RepeatingPlan `json:"repeatingPlans"`
PlanPrecondition int64 `json:"planPrecondition"`
PlanContinuous bool `json:"planContinuous"`
Title string `json:"title"`
Icon string `json:"icon,omitempty"`
Capacity float64 `json:"capacity,omitempty"`
Phases int `json:"phases,omitempty"`
MinSoc int `json:"minSoc,omitempty"`
LimitSoc int `json:"limitSoc,omitempty"`
MinCurrent float64 `json:"minCurrent,omitempty"`
MaxCurrent float64 `json:"maxCurrent,omitempty"`
Priority int `json:"priority,omitempty"`
Features []string `json:"features,omitempty"`
Plan *planStruct `json:"plan,omitempty"`
RepeatingPlans []api.RepeatingPlan `json:"repeatingPlans"`
PlanStrategy api.PlanStrategy `json:"planStrategy"`
}
// publishVehicles returns a list of vehicle titles
@ -48,32 +45,34 @@ func (site *Site) publishVehicles() {
}
ac := instance.OnIdentified()
strategy := v.GetPlanStrategy()
var plan *planStruct
if time, soc := v.GetPlanSoc(); !time.IsZero() {
plan = &planStruct{Soc: soc, Precondition: int64(strategy.Precondition.Seconds()), Time: time}
plan = &planStruct{
Soc: soc,
Time: time,
}
}
res[v.Name()] = vehicleStruct{
Title: instance.GetTitle(),
Icon: instance.Icon(),
Capacity: instance.Capacity(),
Phases: instance.Phases(),
MinSoc: v.GetMinSoc(),
LimitSoc: v.GetLimitSoc(),
MinCurrent: ac.MinCurrent,
MaxCurrent: ac.MaxCurrent,
Priority: ac.Priority,
Features: lo.Map(instance.Features(), func(f api.Feature, _ int) string { return f.String() }),
Plan: plan,
RepeatingPlans: v.GetRepeatingPlans(),
PlanPrecondition: int64(strategy.Precondition.Seconds()),
PlanContinuous: strategy.Continuous,
Title: instance.GetTitle(),
Icon: instance.Icon(),
Capacity: instance.Capacity(),
Phases: instance.Phases(),
MinSoc: v.GetMinSoc(),
LimitSoc: v.GetLimitSoc(),
MinCurrent: ac.MinCurrent,
MaxCurrent: ac.MaxCurrent,
Priority: ac.Priority,
Features: lo.Map(instance.Features(), func(f api.Feature, _ int) string { return f.String() }),
Plan: plan,
RepeatingPlans: v.GetRepeatingPlans(),
PlanStrategy: v.GetPlanStrategy(),
}
// publish effective plan strategy immediately for soc-based planning
if lp := site.coordinator.Owner(instance); lp != nil {
go lp.PublishEffectiveValues()
lp.PublishEffectiveValues()
}
}

View file

@ -169,7 +169,6 @@ func (v *adapter) SetPlanStrategy(planStrategy api.PlanStrategy) error {
return err
}
v.log.DEBUG.Printf("update plan strategy for vehicle %s (precondition: %vs, continuous: %v)", v.name, planStrategy.Continuous, planStrategy.Precondition)
v.publish()
return nil

View file

@ -26,10 +26,6 @@ type PlanPreviewResponse struct {
Duration int64 `json:"duration"`
Plan api.Rates `json:"plan"`
Power float64 `json:"power"`
// TODO remove
Continuous bool `json:"continuous"`
Precondition int64 `json:"precondition"`
}
// planHandler returns the current plan
@ -100,10 +96,6 @@ func staticPlanPreviewHandler(lp loadpoint.API) http.HandlerFunc {
Duration: int64(requiredDuration.Seconds()),
Plan: plan,
Power: maxPower,
// TODO remove
Continuous: strategy.Continuous,
Precondition: int64(strategy.Precondition.Seconds()),
}
jsonWrite(w, res)
@ -206,7 +198,7 @@ func planStrategyHandler(lp loadpoint.API) http.HandlerFunc {
return
}
res := planStrategyPayloadFromApi(lp.GetPlanStrategy())
res := lp.GetPlanStrategy()
jsonWrite(w, res)
}

View file

@ -112,14 +112,12 @@ func planSocHandler(site site.API) http.HandlerFunc {
}
func planStrategyHandlerSetter(r *http.Request, set func(api.PlanStrategy) error) error {
var planStrategy planStrategyPayload
if err := json.NewDecoder(r.Body).Decode(&planStrategy); err != nil {
var res api.PlanStrategy
if err := json.NewDecoder(r.Body).Decode(&res); err != nil {
return err
}
return set(api.PlanStrategy{
Continuous: planStrategy.Continuous,
Precondition: time.Duration(planStrategy.Precondition) * time.Second,
})
return set(res)
}
// updatePlanStrategyHandler updates plan strategy
@ -137,7 +135,7 @@ func updatePlanStrategyHandler(site site.API) http.HandlerFunc {
return
}
res := planStrategyPayloadFromApi(v.GetPlanStrategy())
res := v.GetPlanStrategy()
jsonWrite(w, res)
}

View file

@ -68,15 +68,12 @@ func durationSetter(set func(time.Duration) error) func(string) error {
func planStrategySetter(set func(api.PlanStrategy) error) func(string) error {
return func(payload string) error {
var planStrategy planStrategyPayload
if err := json.Unmarshal([]byte(payload), &planStrategy); err != nil {
var res api.PlanStrategy
if err := json.Unmarshal([]byte(payload), &res); err != nil {
return err
}
return set(api.PlanStrategy{
Continuous: planStrategy.Continuous,
Precondition: time.Duration(planStrategy.Precondition) * time.Second,
})
return set(res)
}
}

View file

@ -2,22 +2,8 @@ package server
import (
"time"
"github.com/evcc-io/evcc/api"
)
type planStrategyPayload struct {
Continuous bool `json:"continuous"`
Precondition int64 `json:"precondition"`
}
func planStrategyPayloadFromApi(ps api.PlanStrategy) planStrategyPayload {
return planStrategyPayload{
Continuous: ps.Continuous,
Precondition: int64(ps.Precondition.Seconds()),
}
}
type planGoal[T any] struct {
Time time.Time `json:"time"`
Value T `json:"value"`

View file

@ -24,8 +24,8 @@ func (d *Duration) Duration() time.Duration {
return time.Duration(*d)
}
func (d *Duration) UnmarshalJSON(b []byte) error {
val, err := iso8601.ParseDuration(string(b))
func (d *Duration) UnmarshalJSON(data []byte) error {
val, err := iso8601.ParseDuration(string(data))
if err != nil {
return err
}

View file

@ -15,8 +15,8 @@ type Timestamp struct {
// Layout is the time.Parse compliant parsing string for use when parsing Shortened RFC-3339 compliant timestamps.
const Layout = "2006-01-02T15:04Z"
func (ct *Timestamp) UnmarshalJSON(b []byte) (err error) {
s := strings.Trim(string(b), "\"")
func (ct *Timestamp) UnmarshalJSON(data []byte) (err error) {
s := strings.Trim(string(data), "\"")
if s == "null" {
ct.Time = time.Time{}
return

View file

@ -14,9 +14,9 @@ type Duration struct {
}
// UnmarshalJSON implements json.Unmarshaler
func (d *Duration) UnmarshalJSON(b []byte) error {
func (d *Duration) UnmarshalJSON(data []byte) error {
var v any
if err := json.Unmarshal(b, &v); err != nil {
if err := json.Unmarshal(data, &v); err != nil {
return err
}