Merge pull request #575 from andig/feature/gokrazy-config

Switch into demo mode when config file not available
This commit is contained in:
andig 2021-01-04 16:26:04 +01:00 • committed by GitHub
commit 6da99eb8dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 257 additions and 12 deletions

View file

@ -24,7 +24,10 @@ func runCharger(cmd *cobra.Command, args []string) {
log.INFO.Printf("evcc %s (%s)", server.Version, server.Commit)
// load config
conf := loadConfigFile(cfgFile)
conf, err := loadConfigFile(cfgFile)
if err != nil {
log.FATAL.Fatal(err)
}
// setup mqtt
if conf.Mqtt.Broker != "" {

View file

@ -21,6 +21,7 @@ type config struct {
Levels map[string]string
Interval time.Duration
Mqtt mqttConfig
Javascript map[string]interface{}
Influx server.InfluxConfig
HEMS typedConfig
Messaging messagingConfig

140
cmd/demo.go Normal file
View file

@ -0,0 +1,140 @@
package cmd
import (
"github.com/spf13/viper"
"gopkg.in/yaml.v3"
)
const demoYaml = `
log: info
javascript:
- vm: shared
script: |
state = {
residualpower: 200,
pvpower: -3000,
batterypower: 200,
gridpower: -2000,
chargepower: 0,
maxcurrent: 0,
battery: 63, // car
};
function get() {
console.log("state:", JSON.stringify(state));
}
function set() {
console.log(param+":", val);
console.log("state:", JSON.stringify(state));
}
meters:
- name: grid
type: default
power:
type: js
vm: shared
script: state.gridpower = state.pvpower + state.chargepower + state.residualpower + state.batterypower;
- name: pv
type: default
power:
type: js
vm: shared
script: state.pvpower += 100*Math.random();
- name: battery
type: default
power:
type: js
vm: shared
script: state.batterypower;
soc:
type: js
vm: shared
script: "30"
- name: charge
type: default
power:
type: js
vm: shared
script: state.chargepower;
soc:
type: js
vm: shared
script: "30"
chargers:
- name: demo
type: default
enable:
type: js
vm: shared
script: |
set();
state.enabled = val;
if (state.enabled) state.chargepower = state.maxcurrent * 230; else state.chargepower = 0;
enabled:
type: js
vm: shared
script: |
state.enabled;
status:
type: js
vm: shared
script: |
if (state.enabled) "C"; else "B";
maxcurrent:
type: js
vm: shared
script: |
set();
state.maxcurrent = val;
if (state.enabled) state.chargepower = state.maxcurrent * 230;
vehicles:
- name: demo
title: e-Golf
type: default
charge:
type: js
vm: shared
script: |
if (state.chargepower > 0) state.battery++; else state.battery--;
if (state.battery < 15) state.battery = 15;
if (state.battery > 100) state.battery = 100;
state.battery;
cache: 1s
site:
title: Demo
meters:
grid: grid
pv: pv
battery: battery
loadpoints:
- title: Carport
charger: demo
meters:
charge: charge
vehicle: demo
`
func demoConfig() (conf config) {
demo := map[string]interface{}{}
if err := yaml.Unmarshal([]byte(demoYaml), &demo); err != nil {
log.FATAL.Fatalf("failed decoding demo config: %+v", err)
}
for k, v := range demo {
viper.Set(k, v)
}
if err := viper.UnmarshalExact(&conf); err != nil {
log.FATAL.Fatalf("failed loading demo config: %v", err)
}
return conf
}

View file

@ -26,7 +26,10 @@ func runDump(cmd *cobra.Command, args []string) {
log.INFO.Printf("evcc %s (%s)", server.Version, server.Commit)
// load config
conf := loadConfigFile(cfgFile)
conf, err := loadConfigFile(cfgFile)
if err != nil {
log.FATAL.Fatal(err)
}
// setup mqtt
if conf.Mqtt.Broker != "" {

View file

@ -24,7 +24,10 @@ func runMeter(cmd *cobra.Command, args []string) {
log.INFO.Printf("evcc %s (%s)", server.Version, server.Commit)
// load config
conf := loadConfigFile(cfgFile)
conf, err := loadConfigFile(cfgFile)
if err != nil {
log.FATAL.Fatal(err)
}
// setup mqtt
if conf.Mqtt.Broker != "" {

View file

@ -142,7 +142,12 @@ func run(cmd *cobra.Command, args []string) {
log.INFO.Printf("evcc %s (%s)", server.Version, server.Commit)
// load config and re-configure logging after reading config file
conf := loadConfigFile(cfgFile)
conf, err := loadConfigFile(cfgFile)
if err != nil {
log.ERROR.Println("missing evcc config - switching into demo mode")
conf = demoConfig()
}
util.LogLevel(viper.GetString("log"), viper.GetStringMapString("levels"))
uri := viper.GetString("uri")
@ -153,6 +158,9 @@ func run(cmd *cobra.Command, args []string) {
configureMQTT(conf.Mqtt)
}
// setup javascript VMs
configureJavascript(conf.Javascript)
// start broadcasting values
tee := &Tee{}

View file

@ -9,6 +9,7 @@ import (
"github.com/andig/evcc/core"
"github.com/andig/evcc/hems"
"github.com/andig/evcc/provider/javascript"
"github.com/andig/evcc/provider/mqtt"
"github.com/andig/evcc/push"
"github.com/andig/evcc/server"
@ -57,6 +58,13 @@ func configureMQTT(conf mqttConfig) {
}
}
// setup javascript
func configureJavascript(conf map[string]interface{}) {
if err := javascript.Configure(conf); err != nil {
log.FATAL.Fatalf("failed configuring javascript: %v", err)
}
}
// setup HEMS
func configureHEMS(conf typedConfig, site *core.Site, cache *util.Cache, httpd *server.HTTPd) hems.HEMS {
hems, err := hems.NewFromConfig(conf.Type, conf.Other, site, cache, httpd)
@ -131,15 +139,15 @@ func configureLoadPoints(conf config, cp *ConfigProvider) (loadPoints []*core.Lo
return loadPoints, nil
}
func loadConfigFile(cfgFile string) (conf config) {
func loadConfigFile(cfgFile string) (conf config, err error) {
if cfgFile != "" {
log.INFO.Println("using config file", cfgFile)
if err := viper.UnmarshalExact(&conf); err != nil {
log.FATAL.Fatalf("failed parsing config file %s: %v", cfgFile, err)
}
} else {
log.FATAL.Fatal("missing evcc config")
err = errors.New("missing evcc config")
}
return conf
return conf, err
}

View file

@ -24,7 +24,10 @@ func runVehicle(cmd *cobra.Command, args []string) {
log.INFO.Printf("evcc %s (%s)", server.Version, server.Commit)
// load config
conf := loadConfigFile(cfgFile)
conf, err := loadConfigFile(cfgFile)
if err != nil {
log.FATAL.Fatal(err)
}
// setup mqtt
if conf.Mqtt.Broker != "" {

1
go.mod
View file

@ -57,6 +57,7 @@ require (
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c
github.com/volkszaehler/mbmd v0.0.0-20201205173745-5106cb0b334e
golang.org/x/net v0.0.0-20200904194848-62affa334b73
golang.org/x/sys v0.0.0-20201231184435-2d18734c6014 // indirect
gopkg.in/ini.v1 v1.62.0
gopkg.in/sourcemap.v1 v1.0.5 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776

2
go.sum
View file

@ -435,6 +435,8 @@ golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1 h1:sIky/MyNRSHTrdxfsiUSS4WIA
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201231184435-2d18734c6014 h1:joucsQqXmyBVxViHCPFjG3hx8JzIFSaym3l3MM/Jsdg=
golang.org/x/sys v0.0.0-20201231184435-2d18734c6014/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=

View file

@ -1,6 +1,9 @@
package provider
import (
"strings"
"github.com/andig/evcc/provider/javascript"
"github.com/andig/evcc/util"
"github.com/robertkrimen/otto"
)
@ -19,6 +22,7 @@ func init() {
// NewJavascriptProviderFromConfig creates a HTTP provider
func NewJavascriptProviderFromConfig(other map[string]interface{}) (IntProvider, error) {
cc := struct {
VM string
Script string
}{}
@ -28,9 +32,11 @@ func NewJavascriptProviderFromConfig(other map[string]interface{}) (IntProvider,
log := util.NewLogger("js")
vm := javascript.RegisteredVM(strings.ToLower(cc.VM))
p := &Javascript{
log: log,
vm: otto.New(),
vm: vm,
script: cc.Script,
}
@ -85,10 +91,21 @@ func (p *Javascript) BoolGetter() func() (bool, error) {
}
}
func (p *Javascript) setParam(param string, val interface{}) error {
err := p.vm.Set(param, val)
if err == nil {
err = p.vm.Set("param", param)
}
if err == nil {
err = p.vm.Set("val", val)
}
return err
}
// IntSetter sends int request
func (p *Javascript) IntSetter(param string) func(int64) error {
return func(val int64) error {
err := p.vm.Set(param, val)
err := p.setParam(param, val)
if err == nil {
_, err = p.vm.Eval(p.script)
}
@ -99,7 +116,7 @@ func (p *Javascript) IntSetter(param string) func(int64) error {
// StringSetter sends string request
func (p *Javascript) StringSetter(param string) func(string) error {
return func(val string) error {
err := p.vm.Set(param, val)
err := p.setParam(param, val)
if err == nil {
_, err = p.vm.Eval(p.script)
}
@ -110,7 +127,7 @@ func (p *Javascript) StringSetter(param string) func(string) error {
// BoolSetter sends bool request
func (p *Javascript) BoolSetter(param string) func(bool) error {
return func(val bool) error {
err := p.vm.Set(param, val)
err := p.setParam(param, val)
if err == nil {
_, err = p.vm.Eval(p.script)
}

View file

@ -0,0 +1,56 @@
package javascript
import (
"github.com/andig/evcc/util"
"github.com/robertkrimen/otto"
)
// Configure initializes JS VMs
func Configure(other map[string]interface{}) error {
cc := []struct {
VM string
Script string
}{}
if err := util.DecodeOther(other, &cc); err != nil {
return err
}
// init all VMs that require it
for _, conf := range cc {
if conf.Script == "" {
continue
}
if _, ok := registry[conf.VM]; !ok {
vm := otto.New()
_, err := vm.Run(conf.Script)
if err != nil {
return err
}
registry[conf.VM] = vm
}
}
return nil
}
var registry = make(map[string]*otto.Otto)
// RegisteredVM returns a JS VM. If name is not empty, it will return a shared instance.
func RegisteredVM(name string) *otto.Otto {
vm, ok := registry[name]
// create new VM
if !ok {
vm = otto.New()
if name != "" {
registry[name] = vm
}
}
return vm
}