diff --git a/README.md b/README.md index 5fc606384..07a3b302d 100644 --- a/README.md +++ b/README.md @@ -604,12 +604,12 @@ EVCC provides a REST and MQTT APIs. Loadpoint ids for REST API are starting at `0: - `/api/state`: EVCC state (static configuration and dynamic state) -- `/api/loadpoints//mode`: loadpoint charge mode (writable) -- `/api/loadpoints//minsoc`: loadpoint minimum SoC (writable) -- `/api/loadpoints//targetsoc`: loadpoint target SoC (writable) -- `/api/loadpoints//mincurrent`: loadpoint minimum current (writable) -- `/api/loadpoints//maxcurrent`: loadpoint maximum current (writable) -- `/api/loadpoints//phases`: loadpoint enabled phases (writable) +- `/api/loadpoints//mode`: set loadpoint charge mode +- `/api/loadpoints//minsoc`: set loadpoint minimum SoC +- `/api/loadpoints//targetsoc`: set loadpoint target SoC +- `/api/loadpoints//mincurrent`: set loadpoint minimum current +- `/api/loadpoints//maxcurrent`: set loadpoint maximum current +- `/api/loadpoints//phases`: set loadpoint enabled phases Note: to modify writable settings perform a `POST` request appending the value as path segment. diff --git a/server/http.go b/server/http.go index d78f6ed39..e9a68bc5f 100644 --- a/server/http.go +++ b/server/http.go @@ -1,20 +1,13 @@ package server import ( - "encoding/json" "fmt" - "html/template" "io/fs" "net/http" - "os" - "strconv" "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/evcc-io/evcc/util/test" "github.com/gorilla/handlers" "github.com/gorilla/mux" ) @@ -22,34 +15,6 @@ import ( // Assets is the embedded assets file system var Assets fs.FS -type errorJSON struct { - Error string `json:"error"` -} - -type chargeModeJSON struct { - Mode api.ChargeMode `json:"mode"` -} - -type targetSoCJSON struct { - TargetSoC int `json:"targetSoC"` -} - -type minSoCJSON struct { - MinSoC int `json:"minSoC"` -} - -type minCurrentJSON struct { - MinCurrent float64 `json:"minCurrent"` -} - -type maxCurrentJSON struct { - MaxCurrent float64 `json:"maxCurrent"` -} - -type phasesJSON struct { - Phases int `json:"phases"` -} - type route struct { Methods []string Pattern string @@ -70,354 +35,6 @@ func routeLogger(inner http.Handler) http.HandlerFunc { } } -func indexHandler(site site.API) http.HandlerFunc { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/html; charset=UTF-8") - - indexTemplate, err := fs.ReadFile(Assets, "index.html") - if err != nil { - log.FATAL.Print("httpd: failed to load embedded template:", err.Error()) - log.FATAL.Fatal("Make sure templates are included using the `release` build tag or use `make build`") - } - - t, err := template.New("evcc").Delims("[[", "]]").Parse(string(indexTemplate)) - if err != nil { - log.FATAL.Fatal("httpd: failed to create main page template:", err.Error()) - } - - if err := t.Execute(w, map[string]interface{}{ - "Version": Version, - "Commit": Commit, - "Configured": len(site.LoadPoints()), - }); err != nil { - log.ERROR.Println("httpd: failed to render main page:", err.Error()) - } - }) -} - -// jsonHandler is a middleware that decorates responses with JSON and CORS headers -func jsonHandler(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - h.ServeHTTP(w, r) - }) -} - -func jsonResponse(w http.ResponseWriter, r *http.Request, content interface{}) { - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(content); err != nil { - log.ERROR.Printf("httpd: failed to encode JSON: %v", err) - } -} - -// HealthHandler returns current charge mode -func HealthHandler(site site.API) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if !site.Healthy() { - w.WriteHeader(http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) - fmt.Fprintln(w, "OK") - } -} - -// TemplatesHandler returns current charge mode -func TemplatesHandler() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - class, ok := vars["class"] - if !ok { - w.WriteHeader(http.StatusBadRequest) - return - } - - type template = struct { - Name string `json:"name"` - Sample string `json:"template"` - } - - res := make([]template, 0) - for _, conf := range test.ConfigTemplates(class) { - typedSample := fmt.Sprintf("type: %s\n%s", conf.Type, conf.Sample) - t := template{ - Name: conf.Name, - Sample: typedSample, - } - res = append(res, t) - } - - jsonResponse(w, r, res) - } -} - -// StateHandler returns current charge mode -func StateHandler(cache *util.Cache) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - res := cache.State() - for _, k := range []string{"availableVersion", "releaseNotes"} { - delete(res, k) - } - jsonResponse(w, r, res) - } -} - -// CurrentChargeModeHandler returns current charge mode -func CurrentChargeModeHandler(lp loadpoint.API) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - res := chargeModeJSON{Mode: lp.GetMode()} - jsonResponse(w, r, res) - } -} - -// ChargeModeHandler updates charge mode -func ChargeModeHandler(lp loadpoint.API) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - - mode, err := api.ChargeModeString(vars["value"]) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - jsonResponse(w, r, errorJSON{Error: err.Error()}) - return - } - - lp.SetMode(mode) - - res := chargeModeJSON{Mode: lp.GetMode()} - jsonResponse(w, r, res) - } -} - -// CurrentTargetSoCHandler returns current target soc -func CurrentTargetSoCHandler(lp loadpoint.API) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - res := targetSoCJSON{TargetSoC: lp.GetTargetSoC()} - jsonResponse(w, r, res) - } -} - -// TargetSoCHandler updates target soc -func TargetSoCHandler(lp loadpoint.API) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - - soc, err := strconv.ParseInt(vars["value"], 10, 32) - if err == nil { - err = lp.SetTargetSoC(int(soc)) - } - - if err != nil { - w.WriteHeader(http.StatusBadRequest) - jsonResponse(w, r, errorJSON{Error: err.Error()}) - return - } - - res := targetSoCJSON{TargetSoC: lp.GetTargetSoC()} - jsonResponse(w, r, res) - } -} - -// CurrentMinSoCHandler returns current minimum soc -func CurrentMinSoCHandler(lp loadpoint.API) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - res := minSoCJSON{MinSoC: lp.GetMinSoC()} - jsonResponse(w, r, res) - } -} - -// MinSoCHandler updates minimum soc -func MinSoCHandler(lp loadpoint.API) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - - soc, err := strconv.ParseInt(vars["value"], 10, 32) - if err == nil { - err = lp.SetMinSoC(int(soc)) - } - - if err != nil { - w.WriteHeader(http.StatusBadRequest) - jsonResponse(w, r, errorJSON{Error: err.Error()}) - return - } - - res := minSoCJSON{MinSoC: lp.GetMinSoC()} - jsonResponse(w, r, res) - } -} - -// CurrentMinCurrentHandler returns current minimum current -func CurrentMinCurrentHandler(lp loadpoint.API) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - res := minCurrentJSON{MinCurrent: lp.GetMinCurrent()} - jsonResponse(w, r, res) - } -} - -// MinCurrentHandler updates minimum current -func MinCurrentHandler(lp loadpoint.API) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - - current, err := strconv.ParseFloat(vars["value"], 64) - if err == nil { - lp.SetMinCurrent(current) - } else { - w.WriteHeader(http.StatusBadRequest) - jsonResponse(w, r, errorJSON{Error: err.Error()}) - return - } - - res := minCurrentJSON{MinCurrent: lp.GetMinCurrent()} - jsonResponse(w, r, res) - } -} - -// CurrentMaxCurrentHandler returns current maximum current -func CurrentMaxCurrentHandler(lp loadpoint.API) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - res := maxCurrentJSON{MaxCurrent: lp.GetMaxCurrent()} - jsonResponse(w, r, res) - } -} - -// MaxCurrentHandler updates maximum current -func MaxCurrentHandler(lp loadpoint.API) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - - current, err := strconv.ParseFloat(vars["value"], 64) - if err == nil { - lp.SetMaxCurrent(current) - } else { - w.WriteHeader(http.StatusBadRequest) - jsonResponse(w, r, errorJSON{Error: err.Error()}) - return - } - - res := maxCurrentJSON{MaxCurrent: lp.GetMaxCurrent()} - jsonResponse(w, r, res) - } -} - -// CurrentPhasesHandler returns current minimum soc -func CurrentPhasesHandler(lp loadpoint.API) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - res := phasesJSON{Phases: lp.GetPhases()} - jsonResponse(w, r, res) - } -} - -// PhasesHandler updates minimum soc -func PhasesHandler(lp loadpoint.API) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - - phases, err := strconv.ParseInt(vars["value"], 10, 32) - if err == nil { - err = lp.SetPhases(int(phases)) - } - - if err != nil { - w.WriteHeader(http.StatusBadRequest) - jsonResponse(w, r, errorJSON{Error: err.Error()}) - return - } - - res := phasesJSON{Phases: lp.GetPhases()} - jsonResponse(w, r, res) - } -} - -// RemoteDemandHandler updates minimum soc -func RemoteDemandHandler(lp loadpoint.API) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - - demandS, ok := vars["demand"] - - var source string - if ok { - source, ok = vars["source"] - } - - demand, err := loadpoint.RemoteDemandString(demandS) - - if !ok || err != nil { - w.WriteHeader(http.StatusBadRequest) - jsonResponse(w, r, errorJSON{Error: err.Error()}) - return - } - - lp.RemoteControl(source, demand) - - res := struct { - Demand loadpoint.RemoteDemand `json:"demand"` - Source string `json:"source"` - }{ - Source: source, - Demand: demand, - } - - jsonResponse(w, r, res) - } -} - -func timezone() *time.Location { - tz := os.Getenv("TZ") - if tz == "" { - tz = "Local" - } - - loc, _ := time.LoadLocation(tz) - return loc -} - -// TargetChargeHandler updates target soc -func TargetChargeHandler(loadpoint loadpoint.API) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - - socS, ok := vars["soc"] - socV, err := strconv.ParseInt(socS, 10, 32) - - if !ok || err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } - - timeS, ok := vars["time"] - timeV, err := time.ParseInLocation("2006-01-02T15:04:05", timeS, timezone()) - - if !ok || err != nil { - w.WriteHeader(http.StatusBadRequest) - jsonResponse(w, r, errorJSON{Error: err.Error()}) - return - } - - loadpoint.SetTargetCharge(timeV, int(socV)) - - res := struct { - SoC int64 `json:"soc"` - Time time.Time `json:"time"` - }{ - SoC: socV, - Time: timeV, - } - - jsonResponse(w, r, res) - } -} - -// SocketHandler attaches websocket handler to uri -func SocketHandler(hub *SocketHub) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ServeWebsocket(hub, w, r) - } -} - // HTTPd wraps an http.Server and adds the root router type HTTPd struct { *http.Server @@ -426,15 +43,14 @@ type HTTPd struct { // NewHTTPd creates HTTP server with configured routes for loadpoint func NewHTTPd(url string, site site.API, hub *SocketHub, cache *util.Cache) *HTTPd { routes := map[string]route{ - "health": {[]string{"GET"}, "/health", HealthHandler(site)}, - "state": {[]string{"GET"}, "/state", StateHandler(cache)}, - "templates": {[]string{"GET"}, "/config/templates/{class:[a-z]+}", TemplatesHandler()}, + "health": {[]string{"GET"}, "/health", healthHandler(site)}, + "state": {[]string{"GET"}, "/state", stateHandler(cache)}, } router := mux.NewRouter().StrictSlash(true) // websocket - router.HandleFunc("/ws", SocketHandler(hub)) + router.HandleFunc("/ws", socketHandler(hub)) // static - individual handlers per root and folders static := router.PathPrefix("/").Subrouter() @@ -465,20 +81,14 @@ func NewHTTPd(url string, site site.API, hub *SocketHub, cache *util.Cache) *HTT lpAPI := api.PathPrefix(fmt.Sprintf("/loadpoints/%d", id)).Subrouter() routes := map[string]route{ - "getmode": {[]string{"GET"}, "/mode", CurrentChargeModeHandler(lp)}, - "setmode": {[]string{"POST", "OPTIONS"}, "/mode/{value:[a-z]+}", ChargeModeHandler(lp)}, - "gettargetsoc": {[]string{"GET"}, "/targetsoc", CurrentTargetSoCHandler(lp)}, - "settargetsoc": {[]string{"POST", "OPTIONS"}, "/targetsoc/{value:[0-9]+}", TargetSoCHandler(lp)}, - "getminsoc": {[]string{"GET"}, "/minsoc", CurrentMinSoCHandler(lp)}, - "setminsoc": {[]string{"POST", "OPTIONS"}, "/minsoc/{value:[0-9]+}", MinSoCHandler(lp)}, - "getmincurrent": {[]string{"GET"}, "/mincurrent", CurrentMinCurrentHandler(lp)}, - "setmincurrent": {[]string{"POST", "OPTIONS"}, "/mincurrent/{value:[0-9]+}", MinCurrentHandler(lp)}, - "getmaxcurrent": {[]string{"GET"}, "/maxcurrent", CurrentMaxCurrentHandler(lp)}, - "setmaxcurrent": {[]string{"POST", "OPTIONS"}, "/maxcurrent/{value:[0-9]+}", MaxCurrentHandler(lp)}, - "getphases": {[]string{"GET"}, "/phases", CurrentPhasesHandler(lp)}, - "setphases": {[]string{"POST", "OPTIONS"}, "/phases/{value:[0-9]+}", PhasesHandler(lp)}, - "settargetcharge": {[]string{"POST", "OPTIONS"}, "/targetcharge/{soc:[0-9]+}/{time:[0-9TZ:-]+}", TargetChargeHandler(lp)}, - "remotedemand": {[]string{"POST", "OPTIONS"}, "/remotedemand/{demand:[a-z]+}/{source::[0-9a-zA-Z_-]+}", RemoteDemandHandler(lp)}, + "mode": {[]string{"POST", "OPTIONS"}, "/mode/{value:[a-z]+}", chargeModeHandler(lp)}, + "targetsoc": {[]string{"POST", "OPTIONS"}, "/targetsoc/{value:[0-9]+}", targetSoCHandler(lp)}, + "minsoc": {[]string{"POST", "OPTIONS"}, "/minsoc/{value:[0-9]+}", minSoCHandler(lp)}, + "mincurrent": {[]string{"POST", "OPTIONS"}, "/mincurrent/{value:[0-9]+}", minCurrentHandler(lp)}, + "maxcurrent": {[]string{"POST", "OPTIONS"}, "/maxcurrent/{value:[0-9]+}", maxCurrentHandler(lp)}, + "phases": {[]string{"POST", "OPTIONS"}, "/phases/{value:[0-9]+}", phasesHandler(lp)}, + "targetcharge": {[]string{"POST", "OPTIONS"}, "/targetcharge/{soc:[0-9]+}/{time:[0-9TZ:-]+}", targetChargeHandler(lp)}, + "remotedemand": {[]string{"POST", "OPTIONS"}, "/remotedemand/{demand:[a-z]+}/{source::[0-9a-zA-Z_-]+}", remoteDemandHandler(lp)}, } for _, r := range routes { diff --git a/server/http_handler.go b/server/http_handler.go new file mode 100644 index 000000000..217483668 --- /dev/null +++ b/server/http_handler.go @@ -0,0 +1,288 @@ +package server + +import ( + "encoding/json" + "fmt" + "io/fs" + "net/http" + "os" + "strconv" + "text/template" + "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" +) + +const ( + result = "result" + failure = "error" +) + +func indexHandler(site site.API) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html; charset=UTF-8") + + indexTemplate, err := fs.ReadFile(Assets, "index.html") + if err != nil { + log.FATAL.Print("httpd: failed to load embedded template:", err.Error()) + log.FATAL.Fatal("Make sure templates are included using the `release` build tag or use `make build`") + } + + t, err := template.New("evcc").Delims("[[", "]]").Parse(string(indexTemplate)) + if err != nil { + log.FATAL.Fatal("httpd: failed to create main page template:", err.Error()) + } + + if err := t.Execute(w, map[string]interface{}{ + "Version": Version, + "Commit": Commit, + "Configured": len(site.LoadPoints()), + }); err != nil { + log.ERROR.Println("httpd: failed to render main page:", err.Error()) + } + }) +} + +// jsonHandler is a middleware that decorates responses with JSON and CORS headers +func jsonHandler(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + h.ServeHTTP(w, r) + }) +} + +func jsonResponse(w http.ResponseWriter, r *http.Request, content interface{}) { + w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(content); err != nil { + log.ERROR.Printf("httpd: failed to encode JSON: %v", err) + } +} + +// healthHandler returns current charge mode +func healthHandler(site site.API) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !site.Healthy() { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + fmt.Fprintln(w, "OK") + } +} + +// stateHandler returns current charge mode +func stateHandler(cache *util.Cache) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + res := cache.State() + for _, k := range []string{"availableVersion", "releaseNotes"} { + delete(res, k) + } + jsonResponse(w, r, res) + } +} + +// chargeModeHandler updates charge mode +func chargeModeHandler(lp loadpoint.API) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + mode, err := api.ChargeModeString(vars["value"]) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + jsonResponse(w, r, map[string]interface{}{failure: err.Error()}) + return + } + + lp.SetMode(mode) + + jsonResponse(w, r, map[string]interface{}{result: lp.GetMode()}) + } +} + +// targetSoCHandler updates target soc +func targetSoCHandler(lp loadpoint.API) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + soc, err := strconv.ParseInt(vars["value"], 10, 32) + if err == nil { + err = lp.SetTargetSoC(int(soc)) + } + + if err != nil { + w.WriteHeader(http.StatusBadRequest) + jsonResponse(w, r, map[string]interface{}{failure: err.Error()}) + return + } + + jsonResponse(w, r, map[string]interface{}{result: lp.GetTargetSoC()}) + } +} + +// minSoCHandler updates minimum soc +func minSoCHandler(lp loadpoint.API) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + soc, err := strconv.ParseInt(vars["value"], 10, 32) + if err == nil { + err = lp.SetMinSoC(int(soc)) + } + + if err != nil { + w.WriteHeader(http.StatusBadRequest) + jsonResponse(w, r, map[string]interface{}{failure: err.Error()}) + return + } + + jsonResponse(w, r, map[string]interface{}{result: lp.GetMinSoC()}) + } +} + +// minCurrentHandler updates minimum current +func minCurrentHandler(lp loadpoint.API) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + current, err := strconv.ParseFloat(vars["value"], 64) + if err == nil { + lp.SetMinCurrent(current) + } else { + w.WriteHeader(http.StatusBadRequest) + jsonResponse(w, r, map[string]interface{}{failure: err.Error()}) + return + } + + jsonResponse(w, r, map[string]interface{}{result: lp.GetMinCurrent()}) + } +} + +// maxCurrentHandler updates maximum current +func maxCurrentHandler(lp loadpoint.API) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + current, err := strconv.ParseFloat(vars["value"], 64) + if err == nil { + lp.SetMaxCurrent(current) + } else { + w.WriteHeader(http.StatusBadRequest) + jsonResponse(w, r, map[string]interface{}{failure: err.Error()}) + return + } + + jsonResponse(w, r, map[string]interface{}{result: lp.GetMaxCurrent()}) + } +} + +// phasesHandler updates minimum soc +func phasesHandler(lp loadpoint.API) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + phases, err := strconv.ParseInt(vars["value"], 10, 32) + if err == nil { + err = lp.SetPhases(int(phases)) + } + + if err != nil { + w.WriteHeader(http.StatusBadRequest) + jsonResponse(w, r, map[string]interface{}{failure: err.Error()}) + return + } + + jsonResponse(w, r, map[string]interface{}{result: lp.GetPhases()}) + } +} + +// remoteDemandHandler updates minimum soc +func remoteDemandHandler(lp loadpoint.API) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + demandS, ok := vars["demand"] + + var source string + if ok { + source, ok = vars["source"] + } + + demand, err := loadpoint.RemoteDemandString(demandS) + + if !ok || err != nil { + w.WriteHeader(http.StatusBadRequest) + jsonResponse(w, r, map[string]interface{}{failure: err.Error()}) + return + } + + lp.RemoteControl(source, demand) + + res := struct { + Demand loadpoint.RemoteDemand `json:"demand"` + Source string `json:"source"` + }{ + Source: source, + Demand: demand, + } + + jsonResponse(w, r, res) + } +} + +func timezone() *time.Location { + tz := os.Getenv("TZ") + if tz == "" { + tz = "Local" + } + + loc, _ := time.LoadLocation(tz) + return loc +} + +// targetChargeHandler updates target soc +func targetChargeHandler(loadpoint loadpoint.API) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + socS, ok := vars["soc"] + socV, err := strconv.ParseInt(socS, 10, 32) + + if !ok || err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + timeS, ok := vars["time"] + timeV, err := time.ParseInLocation("2006-01-02T15:04:05", timeS, timezone()) + + if !ok || err != nil { + w.WriteHeader(http.StatusBadRequest) + jsonResponse(w, r, map[string]interface{}{failure: err.Error()}) + return + } + + loadpoint.SetTargetCharge(timeV, int(socV)) + + res := struct { + SoC int64 `json:"soc"` + Time time.Time `json:"time"` + }{ + SoC: socV, + Time: timeV, + } + + jsonResponse(w, r, res) + } +} + +// socketHandler attaches websocket handler to uri +func socketHandler(hub *SocketHub) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ServeWebsocket(hub, w, r) + } +} diff --git a/server/uds.go b/server/uds.go index 5d50c97d9..43e30d64a 100644 --- a/server/uds.go +++ b/server/uds.go @@ -38,7 +38,7 @@ func HealthListener(site site.API) { mux := http.NewServeMux() httpd := http.Server{Handler: mux} - mux.HandleFunc("/health", HealthHandler(site)) + mux.HandleFunc("/health", healthHandler(site)) _ = httpd.Serve(l) }