Improve network configuration (#3155)

This commit is contained in:
andig 2022-04-14 13:24:46 +02:00 • committed by GitHub
parent 741cfce027
commit 1a4da3f7ca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 105 additions and 70 deletions

View file

@ -38,8 +38,7 @@ func runCharger(cmd *cobra.Command, args []string) {
log.INFO.Printf("evcc %s", server.FormattedVersion())
// load config
conf, err := loadConfigFile(cfgFile)
if err != nil {
if err := loadConfigFile(cfgFile, &conf); err != nil {
log.FATAL.Fatal(err)
}

View file

@ -2,7 +2,9 @@ package cmd
import (
"fmt"
"net"
"net/http"
"strconv"
"strings"
"time"
@ -18,10 +20,12 @@ import (
"github.com/evcc-io/evcc/vehicle"
"github.com/evcc-io/evcc/vehicle/wrapper"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
)
type config struct {
URI string
URI interface{} // TODO deprecated
Network networkConfig
Log string
SponsorToken string
Metrics bool
@ -42,6 +46,20 @@ type config struct {
LoadPoints []map[string]interface{}
}
type networkConfig struct {
Schema string
Host string
Port int
}
func (c networkConfig) HostPort() string {
return net.JoinHostPort(c.Host, strconv.Itoa(c.Port))
}
func (c networkConfig) URI() string {
return fmt.Sprintf("%s://%s", c.Schema, c.HostPort())
}
type mqttConfig struct {
mqtt.Config `mapstructure:",squash"`
Topic string
@ -218,9 +236,7 @@ func (cp *ConfigProvider) configureVehicles(conf config) error {
}
// webControl handles routing for devices. For now only api.ProviderLogin related routes
func (cp *ConfigProvider) webControl(httpd *server.HTTPd, paramC chan<- util.Param) {
router := httpd.Router()
func (cp *ConfigProvider) webControl(conf networkConfig, router *mux.Router, paramC chan<- util.Param) {
auth := router.PathPrefix("/oauth").Subrouter()
auth.Use(handlers.CompressHandler)
auth.Use(handlers.CORS(
@ -233,9 +249,8 @@ func (cp *ConfigProvider) webControl(httpd *server.HTTPd, paramC chan<- util.Par
// initialize
cp.auth = util.NewAuthCollection(paramC)
// TODO make evccURI configurable, add warnings for any network/ localhost
evccURI := fmt.Sprintf("http://%s", httpd.Addr)
baseAuthURI := fmt.Sprintf("%s/oauth", evccURI)
baseURI := conf.URI()
baseAuthURI := fmt.Sprintf("%s/oauth", baseURI)
var id int
for _, v := range cp.vehicles {
@ -245,14 +260,10 @@ func (cp *ConfigProvider) webControl(httpd *server.HTTPd, paramC chan<- util.Par
basePath := fmt.Sprintf("vehicles/%d", id)
callbackURI := fmt.Sprintf("%s/%s/callback", baseAuthURI, basePath)
// replace interface designator with address
// TODO fix when evccURI becomes configurable
callbackURI = strings.ReplaceAll(callbackURI, "0.0.0.0", "localhost")
// register vehicle
ap := cp.auth.Register(fmt.Sprintf("oauth/%s", basePath), v.Title())
provider.SetCallbackParams(evccURI, callbackURI, ap.Handler())
provider.SetCallbackParams(baseURI, callbackURI, ap.Handler())
auth.
Methods(http.MethodPost).

View file

@ -1,7 +1,18 @@
uri: 0.0.0.0:7070 # uri for ui
interval: 10s # control cycle interval
# open evcc at http://evcc.local:7070
network:
schema: http
host: evcc.local # .local suffix announces the hostname on MDNS
port: 7070
log: info
levels:
cache: error
interval: 10s # control cycle interval
{{- if ne (len .SponsorToken) 0 }}
sponsortoken: {{ .SponsorToken }}
{{- end}}
{{- if ne (len .Meters) 0 }}
meters:
@ -74,7 +85,3 @@ hems:
eebus:
{{ .EEBUS | indent 2 }}
{{- end }}
{{- if ne (len .SponsorToken) 0 }}
sponsortoken: {{ .SponsorToken }}
{{- end}}

View file

@ -2,7 +2,6 @@ package cmd
import (
_ "embed" // for yaml
"fmt"
"github.com/spf13/viper"
"gopkg.in/yaml.v3"
@ -11,7 +10,7 @@ import (
//go:embed demo.yaml
var demoYaml string
func demoConfig() (conf config) {
func demoConfig(conf *config) {
demo := map[string]interface{}{}
if err := yaml.Unmarshal([]byte(demoYaml), &demo); err != nil {
@ -22,12 +21,9 @@ func demoConfig() (conf config) {
viper.Set(k, v)
}
// demo port
viper.Set("uri", fmt.Sprintf("0.0.0.0:%d", defaultPort))
if err := viper.UnmarshalExact(&conf); err != nil {
log.FATAL.Fatalf("failed loading demo config: %v", err)
}
return conf
conf.Network.Port = defaultPort
}

View file

@ -1,3 +1,6 @@
network:
port: 7070
log: info
interval: 3s

View file

@ -26,8 +26,7 @@ func runDump(cmd *cobra.Command, args []string) {
log.INFO.Printf("evcc %s", server.FormattedVersion())
// load config
conf, err := loadConfigFile(cfgFile)
if err != nil {
if err := loadConfigFile(cfgFile, &conf); err != nil {
log.FATAL.Fatal(err)
}

View file

@ -29,8 +29,7 @@ func runMeter(cmd *cobra.Command, args []string) {
log.INFO.Printf("evcc %s", server.FormattedVersion())
// load config
conf, err := loadConfigFile(cfgFile)
if err != nil {
if err := loadConfigFile(cfgFile, &conf); err != nil {
log.FATAL.Fatal(err)
}

View file

@ -2,12 +2,11 @@ package cmd
import (
"fmt"
"net"
"net/http"
_ "net/http/pprof" // pprof handler
"os"
"os/signal"
"strconv"
"strings"
"sync"
"syscall"
"time"
@ -34,6 +33,14 @@ var (
ignoreMqtt = []string{"auth", "releaseNotes"} // excessive size may crash certain brokers
)
var conf = config{
Network: networkConfig{
Schema: "http",
Host: "evcc.local",
Port: 7070,
},
}
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "evcc",
@ -72,8 +79,10 @@ func init() {
cobra.OnInitialize(initConfig)
configureCommand(rootCmd)
rootCmd.PersistentFlags().StringP("uri", "u", "0.0.0.0:7070", "Listen address")
bind(rootCmd, "uri")
rootCmd.PersistentFlags().IntP("port", "p", 7070, "Listen port")
if err := viper.BindPFlag("network.port", rootCmd.PersistentFlags().Lookup("port")); err != nil {
panic(err)
}
rootCmd.PersistentFlags().DurationP("interval", "i", 10*time.Second, "Update interval")
bind(rootCmd, "interval")
@ -134,16 +143,23 @@ func run(cmd *cobra.Command, args []string) {
log.INFO.Printf("evcc %s", server.FormattedVersion())
// load config and re-configure logging after reading config file
conf, err := loadConfigFile(cfgFile)
if err != nil {
if err := loadConfigFile(cfgFile, &conf); err != nil {
log.ERROR.Println("missing evcc config - switching into demo mode")
conf = demoConfig()
demoConfig(&conf)
}
util.LogLevel(viper.GetString("log"), viper.GetStringMapString("levels"))
uri := viper.GetString("uri")
log.INFO.Println("listening at", uri)
// network config
if viper.GetString("uri") != "" {
log.ERROR.Println("`uri` is deprecated and will be ignored. Use `network` instead.")
}
if cmd.PersistentFlags().Lookup("port").Changed {
conf.Network.Port = viper.GetInt("network.port")
}
log.INFO.Printf("listening at :%d", conf.Network.Port)
// setup environment
if err := configureEnvironment(conf); err != nil {
@ -183,16 +199,15 @@ func run(cmd *cobra.Command, args []string) {
// create webserver
socketHub := server.NewSocketHub()
httpd := server.NewHTTPd(uri, site, socketHub, cache)
httpd := server.NewHTTPd(fmt.Sprintf(":%d", conf.Network.Port), site, socketHub, cache)
// announce webserver on mDNS
if _, port, err := net.SplitHostPort(uri); err == nil {
if portInt, err := strconv.Atoi(port); err == nil {
if zc, err := zeroconf.RegisterProxy("evcc Website", "_http._tcp", "local.", portInt, "evcc", nil, []string{}, nil); err == nil {
shutdown.Register(zc.Shutdown)
} else {
log.ERROR.Printf("mDNS announcement: %s", err)
}
if strings.HasSuffix(conf.Network.Host, ".local") {
host := strings.TrimSuffix(conf.Network.Host, ".local")
if zc, err := zeroconf.RegisterProxy("EV Charge Controller", "_http._tcp", "local.", conf.Network.Port, host, nil, []string{}, nil); err == nil {
shutdown.Register(zc.Shutdown)
} else {
log.ERROR.Printf("mDNS announcement: %s", err)
}
}
@ -225,7 +240,7 @@ func run(cmd *cobra.Command, args []string) {
}
// allow web access for vehicles
cp.webControl(httpd, valueChan)
cp.webControl(conf.Network, httpd.Router(), valueChan)
// version check
go updater.Run(log, httpd, tee, valueChan)

View file

@ -31,7 +31,7 @@ func init() {
var cp = new(ConfigProvider)
func loadConfigFile(cfgFile string) (conf config, err error) {
func loadConfigFile(cfgFile string, conf *config) (err error) {
if cfgFile != "" {
log.INFO.Println("using config file", cfgFile)
if err := viper.UnmarshalExact(&conf); err != nil {
@ -41,7 +41,7 @@ func loadConfigFile(cfgFile string) (conf config, err error) {
err = errors.New("missing evcc config")
}
return conf, err
return err
}
func configureEnvironment(conf config) (err error) {

View file

@ -29,8 +29,7 @@ func runToken(cmd *cobra.Command, args []string) {
log.INFO.Printf("evcc %s", server.FormattedVersion())
// load config
conf, err := loadConfigFile(cfgFile)
if err != nil {
if err := loadConfigFile(cfgFile, &conf); err != nil {
log.FATAL.Fatal(err)
}
@ -55,6 +54,7 @@ func runToken(cmd *cobra.Command, args []string) {
}
var token *oauth2.Token
var err error
switch strings.ToLower(vehicleConf.Type) {
case "tesla":

View file

@ -7,7 +7,6 @@ import (
"errors"
"fmt"
"io"
"net"
"net/http"
"strings"
"sync"
@ -133,13 +132,8 @@ func tronityToken(conf config, vehicleConf qualifiedConfig) (*oauth2.Token, erro
}
if oc.RedirectURL = cc.RedirectURI; oc.RedirectURL == "" {
_, port, err := net.SplitHostPort(conf.URI)
if err != nil {
return nil, err
}
oc.RedirectURL = fmt.Sprintf("http://%s/auth/tronity", net.JoinHostPort("localhost", port))
oc.RedirectURL = fmt.Sprintf("%s/auth/tronity", conf.Network.URI())
}
return tronityAuthorize(conf.URI, oc)
return tronityAuthorize(conf.Network.HostPort(), oc)
}

View file

@ -32,8 +32,7 @@ func runVehicle(cmd *cobra.Command, args []string) {
log.INFO.Printf("evcc %s", server.FormattedVersion())
// load config
conf, err := loadConfigFile(cfgFile)
if err != nil {
if err := loadConfigFile(cfgFile, &conf); err != nil {
log.FATAL.Fatal(err)
}

View file

@ -172,7 +172,7 @@ func (site *Site) DumpConfig() {
if len(site.pvMeters) > 0 {
for i, pv := range site.pvMeters {
site.log.INFO.Println(meterCapabilities(fmt.Sprintf("pv %d", i), pv))
site.log.INFO.Println(meterCapabilities(fmt.Sprintf("pv %d", i+1), pv))
}
}
@ -180,7 +180,7 @@ func (site *Site) DumpConfig() {
for i, battery := range site.batteryMeters {
_, ok := battery.(api.Battery)
site.log.INFO.Println(
meterCapabilities(fmt.Sprintf("battery %d", i), battery),
meterCapabilities(fmt.Sprintf("battery %d", i+1), battery),
fmt.Sprintf("soc %s", presence[ok]),
)
}
@ -217,7 +217,7 @@ func (site *Site) DumpConfig() {
_, status := v.(api.ChargeState)
_, climate := v.(api.VehicleClimater)
lp.log.INFO.Printf(" vehicle %d: range %s finish %s status %s climate %s",
i, presence[rng], presence[finish], presence[status], presence[climate],
i+1, presence[rng], presence[finish], presence[status], presence[climate],
)
}
}

View file

@ -1,4 +1,15 @@
uri: 0.0.0.0:7070 # uri for ui
network:
# schema is the HTTP schema
# setting to `https` does not enable https, it only changes the way URLs are generated
schema: http
# host is the hostname or IP address
# if the host name contains a `.local` suffix, the name will be announced on MDNS
# docker: MDNS announcements don't work. host must be set to the docker host's name.
host: evcc.local
# port is the listening port for UI and api
# evcc will listen on all available interfaces
port: 7070
interval: 10s # control cycle interval
# sponsor token enables optional features (request at https://cloud.evcc.io)

View file

@ -41,7 +41,7 @@ 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 {
func NewHTTPd(addr 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)},
@ -98,7 +98,7 @@ func NewHTTPd(url string, site site.API, hub *SocketHub, cache *util.Cache) *HTT
srv := &HTTPd{
Server: &http.Server{
Addr: url,
Addr: addr,
Handler: router,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,

View file

@ -13,6 +13,7 @@ import (
// Mercedes is an api.Vehicle implementation for Mercedes cars
type Mercedes struct {
*embed
api.ProviderLogin
*mercedes.Provider
}
@ -63,8 +64,9 @@ func NewMercedesFromConfig(other map[string]interface{}) (api.Vehicle, error) {
api := mercedes.NewAPI(log, identity, cc.Sandbox)
v := &Mercedes{
embed: &cc.embed,
Provider: mercedes.NewProvider(api, strings.ToUpper(cc.VIN), cc.Cache),
embed: &cc.embed,
Provider: mercedes.NewProvider(api, strings.ToUpper(cc.VIN), cc.Cache),
ProviderLogin: identity, // expose the OAuth2 login
}
return v, nil