288 lines
7 KiB
Go
288 lines
7 KiB
Go
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)
|
|
}
|
|
}
|