Merge pull request #1720 from evcc-io/feature/http-api
Remove http api GET methods in favour of /state
This commit is contained in:
commit
8baf2a3fab
4 changed files with 306 additions and 408 deletions
12
README.md
12
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/<id>/mode`: loadpoint charge mode (writable)
|
||||
- `/api/loadpoints/<id>/minsoc`: loadpoint minimum SoC (writable)
|
||||
- `/api/loadpoints/<id>/targetsoc`: loadpoint target SoC (writable)
|
||||
- `/api/loadpoints/<id>/mincurrent`: loadpoint minimum current (writable)
|
||||
- `/api/loadpoints/<id>/maxcurrent`: loadpoint maximum current (writable)
|
||||
- `/api/loadpoints/<id>/phases`: loadpoint enabled phases (writable)
|
||||
- `/api/loadpoints/<id>/mode`: set loadpoint charge mode
|
||||
- `/api/loadpoints/<id>/minsoc`: set loadpoint minimum SoC
|
||||
- `/api/loadpoints/<id>/targetsoc`: set loadpoint target SoC
|
||||
- `/api/loadpoints/<id>/mincurrent`: set loadpoint minimum current
|
||||
- `/api/loadpoints/<id>/maxcurrent`: set loadpoint maximum current
|
||||
- `/api/loadpoints/<id>/phases`: set loadpoint enabled phases
|
||||
|
||||
Note: to modify writable settings perform a `POST` request appending the value as path segment.
|
||||
|
||||
|
|
|
|||
412
server/http.go
412
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 {
|
||||
|
|
|
|||
288
server/http_handler.go
Normal file
288
server/http_handler.go
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue