Add schedule for planner (#16091)

This commit is contained in:
Maschga 2024-12-01 12:53:11 +01:00 • committed by GitHub
parent 35c9452d76
commit a35a705e48
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 1771 additions and 421 deletions

View file

@ -138,10 +138,11 @@ func (s *HTTPd) RegisterSiteHandlers(site site.API, valueChan chan<- util.Param)
// vehicle api
vehicles := map[string]route{
"minsoc": {"POST", "/vehicles/{name:[a-zA-Z0-9_.:-]+}/minsoc/{value:[0-9]+}", minSocHandler(site)},
"limitsoc": {"POST", "/vehicles/{name:[a-zA-Z0-9_.:-]+}/limitsoc/{value:[0-9]+}", limitSocHandler(site)},
"plan": {"POST", "/vehicles/{name:[a-zA-Z0-9_.:-]+}/plan/soc/{value:[0-9]+}/{time:[0-9TZ:.+-]+}", planSocHandler(site)},
"plan2": {"DELETE", "/vehicles/{name:[a-zA-Z0-9_.:-]+}/plan/soc", planSocRemoveHandler(site)},
"minsoc": {"POST", "/vehicles/{name:[a-zA-Z0-9_.:-]+}/minsoc/{value:[0-9]+}", minSocHandler(site)},
"limitsoc": {"POST", "/vehicles/{name:[a-zA-Z0-9_.:-]+}/limitsoc/{value:[0-9]+}", limitSocHandler(site)},
"plan": {"POST", "/vehicles/{name:[a-zA-Z0-9_.:-]+}/plan/soc/{value:[0-9]+}/{time:[0-9TZ:.+-]+}", planSocHandler(site)},
"plan2": {"DELETE", "/vehicles/{name:[a-zA-Z0-9_.:-]+}/plan/soc", planSocRemoveHandler(site)},
"repeatingPlans": {"POST", "/vehicles/{name:[a-zA-Z0-9_.:-]+}/plan/repeating", addRepeatingPlansHandler(site)},
// config ui
// "mode": {"POST", "/mode/{value:[a-z]+}", chargeModeHandler(v)},
@ -159,28 +160,29 @@ func (s *HTTPd) RegisterSiteHandlers(site site.API, valueChan chan<- util.Param)
api := api.PathPrefix(fmt.Sprintf("/loadpoints/%d", id+1)).Subrouter()
routes := map[string]route{
"mode": {"POST", "/mode/{value:[a-z]+}", handler(eapi.ChargeModeString, pass(lp.SetMode), lp.GetMode)},
"limitsoc": {"POST", "/limitsoc/{value:[0-9]+}", intHandler(pass(lp.SetLimitSoc), lp.GetLimitSoc)},
"limitenergy": {"POST", "/limitenergy/{value:[0-9.]+}", floatHandler(pass(lp.SetLimitEnergy), lp.GetLimitEnergy)},
"mincurrent": {"POST", "/mincurrent/{value:[0-9.]+}", floatHandler(lp.SetMinCurrent, lp.GetMinCurrent)},
"maxcurrent": {"POST", "/maxcurrent/{value:[0-9.]+}", floatHandler(lp.SetMaxCurrent, lp.GetMaxCurrent)},
"phases": {"POST", "/phases/{value:[0-9]+}", intHandler(lp.SetPhases, lp.GetPhases)},
"plan": {"GET", "/plan", planHandler(lp)},
"planpreview": {"GET", "/plan/preview/{type:(?:soc|energy)}/{value:[0-9.]+}/{time:[0-9TZ:.+-]+}", planPreviewHandler(lp)},
"planenergy": {"POST", "/plan/energy/{value:[0-9.]+}/{time:[0-9TZ:.+-]+}", planEnergyHandler(lp)},
"planenergy2": {"DELETE", "/plan/energy", planRemoveHandler(lp)},
"vehicle": {"POST", "/vehicle/{name:[a-zA-Z0-9_.:-]+}", vehicleSelectHandler(site, lp)},
"vehicle2": {"DELETE", "/vehicle", vehicleRemoveHandler(lp)},
"vehicleDetect": {"PATCH", "/vehicle", vehicleDetectHandler(lp)},
"remotedemand": {"POST", "/remotedemand/{demand:[a-z]+}/{source:[0-9a-zA-Z_-]+}", remoteDemandHandler(lp)},
"enableThreshold": {"POST", "/enable/threshold/{value:-?[0-9.]+}", floatHandler(pass(lp.SetEnableThreshold), lp.GetEnableThreshold)},
"enableDelay": {"POST", "/enable/delay/{value:[0-9]+}", durationHandler(pass(lp.SetEnableDelay), lp.GetEnableDelay)},
"disableThreshold": {"POST", "/disable/threshold/{value:-?[0-9.]+}", floatHandler(pass(lp.SetDisableThreshold), lp.GetDisableThreshold)},
"disableDelay": {"POST", "/disable/delay/{value:[0-9]+}", durationHandler(pass(lp.SetDisableDelay), lp.GetDisableDelay)},
"smartCost": {"POST", "/smartcostlimit/{value:-?[0-9.]+}", floatPtrHandler(pass(lp.SetSmartCostLimit), lp.GetSmartCostLimit)},
"smartCostDelete": {"DELETE", "/smartcostlimit", floatPtrHandler(pass(lp.SetSmartCostLimit), lp.GetSmartCostLimit)},
"priority": {"POST", "/priority/{value:[0-9]+}", intHandler(pass(lp.SetPriority), lp.GetPriority)},
"batteryBoost": {"POST", "/batteryboost/{value:[01truefalse]}", boolHandler(lp.SetBatteryBoost, lp.GetBatteryBoost)},
"mode": {"POST", "/mode/{value:[a-z]+}", handler(eapi.ChargeModeString, pass(lp.SetMode), lp.GetMode)},
"limitsoc": {"POST", "/limitsoc/{value:[0-9]+}", intHandler(pass(lp.SetLimitSoc), lp.GetLimitSoc)},
"limitenergy": {"POST", "/limitenergy/{value:[0-9.]+}", floatHandler(pass(lp.SetLimitEnergy), lp.GetLimitEnergy)},
"mincurrent": {"POST", "/mincurrent/{value:[0-9.]+}", floatHandler(lp.SetMinCurrent, lp.GetMinCurrent)},
"maxcurrent": {"POST", "/maxcurrent/{value:[0-9.]+}", floatHandler(lp.SetMaxCurrent, lp.GetMaxCurrent)},
"phases": {"POST", "/phases/{value:[0-9]+}", intHandler(lp.SetPhases, lp.GetPhases)},
"plan": {"GET", "/plan", planHandler(lp)},
"staticPlanPreview": {"GET", "/plan/static/preview/{type:(?:soc|energy)}/{value:[0-9.]+}/{time:[0-9TZ:.+-]+}", staticPlanPreviewHandler(lp)},
"repeatingPlanPreview": {"GET", "/plan/repeating/preview/{soc:[0-9]+}/{weekdays:[0-6,]+}/{time:[0-2][0-9]:[0-5][0-9]}/{tz:[a-zA-Z0-9_./:-]+}", repeatingPlanPreviewHandler(lp)},
"planenergy": {"POST", "/plan/energy/{value:[0-9.]+}/{time:[0-9TZ:.+-]+}", planEnergyHandler(lp)},
"planenergy2": {"DELETE", "/plan/energy", planRemoveHandler(lp)},
"vehicle": {"POST", "/vehicle/{name:[a-zA-Z0-9_.:-]+}", vehicleSelectHandler(site, lp)},
"vehicle2": {"DELETE", "/vehicle", vehicleRemoveHandler(lp)},
"vehicleDetect": {"PATCH", "/vehicle", vehicleDetectHandler(lp)},
"remotedemand": {"POST", "/remotedemand/{demand:[a-z]+}/{source:[0-9a-zA-Z_-]+}", remoteDemandHandler(lp)},
"enableThreshold": {"POST", "/enable/threshold/{value:-?[0-9.]+}", floatHandler(pass(lp.SetEnableThreshold), lp.GetEnableThreshold)},
"enableDelay": {"POST", "/enable/delay/{value:[0-9]+}", durationHandler(pass(lp.SetEnableDelay), lp.GetEnableDelay)},
"disableThreshold": {"POST", "/disable/threshold/{value:-?[0-9.]+}", floatHandler(pass(lp.SetDisableThreshold), lp.GetDisableThreshold)},
"disableDelay": {"POST", "/disable/delay/{value:[0-9]+}", durationHandler(pass(lp.SetDisableDelay), lp.GetDisableDelay)},
"smartCost": {"POST", "/smartcostlimit/{value:-?[0-9.]+}", floatPtrHandler(pass(lp.SetSmartCostLimit), lp.GetSmartCostLimit)},
"smartCostDelete": {"DELETE", "/smartcostlimit", floatPtrHandler(pass(lp.SetSmartCostLimit), lp.GetSmartCostLimit)},
"priority": {"POST", "/priority/{value:[0-9]+}", intHandler(pass(lp.SetPriority), lp.GetPriority)},
"batteryBoost": {"POST", "/batteryboost/{value:[01truefalse]}", boolHandler(lp.SetBatteryBoost, lp.GetBatteryBoost)},
}
for _, r := range routes {

View file

@ -5,11 +5,13 @@ import (
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/core/loadpoint"
"github.com/evcc-io/evcc/core/site"
"github.com/evcc-io/evcc/util"
"github.com/gorilla/mux"
)
@ -44,6 +46,7 @@ func planHandler(lp loadpoint.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
maxPower := lp.EffectiveMaxPower()
planTime := lp.EffectivePlanTime()
id := lp.EffectivePlanId()
goal, _ := lp.GetPlanGoal()
requiredDuration := lp.GetPlanRequiredDuration(goal, maxPower)
@ -54,11 +57,13 @@ func planHandler(lp loadpoint.API) http.HandlerFunc {
}
res := struct {
PlanId int `json:"planId"`
PlanTime time.Time `json:"planTime"`
Duration int64 `json:"duration"`
Plan api.Rates `json:"plan"`
Power float64 `json:"power"`
}{
PlanId: id,
PlanTime: planTime,
Duration: int64(requiredDuration.Seconds()),
Plan: plan,
@ -69,8 +74,8 @@ func planHandler(lp loadpoint.API) http.HandlerFunc {
}
}
// planPreviewHandler returns a plan preview for given parameters
func planPreviewHandler(lp loadpoint.API) http.HandlerFunc {
// staticPlanPreviewHandler returns a plan preview for given parameters
func staticPlanPreviewHandler(lp loadpoint.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
@ -126,6 +131,59 @@ func planPreviewHandler(lp loadpoint.API) http.HandlerFunc {
}
}
func repeatingPlanPreviewHandler(lp loadpoint.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
hourMinute := vars["time"]
tz := vars["tz"]
var weekdays []int
for _, weekdayStr := range strings.Split(vars["weekdays"], ",") {
weekday, err := strconv.Atoi(weekdayStr)
if err != nil {
jsonError(w, http.StatusBadRequest, fmt.Errorf("invalid weekdays format"))
return
}
weekdays = append(weekdays, weekday)
}
soc, err := strconv.ParseFloat(vars["soc"], 64)
if err != nil {
jsonError(w, http.StatusBadRequest, err)
return
}
planTime, err := util.GetNextOccurrence(weekdays, hourMinute, tz)
if err != nil {
jsonError(w, http.StatusBadRequest, err)
return
}
maxPower := lp.EffectiveMaxPower()
requiredDuration := lp.GetPlanRequiredDuration(soc, maxPower)
plan, err := lp.GetPlan(planTime, requiredDuration)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
res := struct {
PlanTime time.Time `json:"planTime"`
Duration int64 `json:"duration"`
Plan api.Rates `json:"plan"`
Power float64 `json:"power"`
}{
PlanTime: planTime,
Duration: int64(requiredDuration.Seconds()),
Plan: plan,
Power: maxPower,
}
jsonResult(w, res)
}
}
// planEnergyHandler updates plan energy and time
func planEnergyHandler(lp loadpoint.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {

View file

@ -1,10 +1,12 @@
package server
import (
"encoding/json"
"net/http"
"strconv"
"time"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/core/site"
"github.com/gorilla/mux"
)
@ -109,6 +111,36 @@ func planSocHandler(site site.API) http.HandlerFunc {
}
}
// addRepeatingPlansHandler handles any information regarding weekday, hour, minute, soc and isActive
func addRepeatingPlansHandler(site site.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
v, err := site.Vehicles().ByName(vars["name"])
if err != nil {
jsonError(w, http.StatusBadRequest, err)
return
}
var plansWrapper struct {
RepeatingPlans []api.RepeatingPlanStruct `json:"plans"`
}
err = json.NewDecoder(r.Body).Decode(&plansWrapper)
if err != nil {
jsonError(w, http.StatusBadRequest, err)
return
}
if err := v.SetRepeatingPlans(plansWrapper.RepeatingPlans); err != nil {
jsonError(w, http.StatusBadRequest, err)
return
}
jsonResult(w, plansWrapper)
}
}
// planSocRemoveHandler removes plan soc and time
func planSocRemoveHandler(site site.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {