diff --git a/cmd/config.go b/cmd/config.go index a6ca22710..560051cb6 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -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 diff --git a/cmd/demo.go b/cmd/demo.go index 29e09d44e..3e42b404b 100644 --- a/cmd/demo.go +++ b/cmd/demo.go @@ -8,24 +8,60 @@ import ( 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 - script: "1200" + vm: shared + script: state.gridpower = state.pvpower + state.chargepower + state.residualpower + state.batterypower; + - name: pv type: default power: type: js - script: "6400" + vm: shared + script: state.pvpower += 100*Math.random(); + - name: battery type: default power: type: js - script: "200" + 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: @@ -33,16 +69,28 @@ chargers: type: default enable: type: js - script: "true" + vm: shared + script: | + set(); + state.enabled = val; + if (state.enabled) state.chargepower = state.maxcurrent * 230; else state.chargepower = 0; enabled: type: js - script: "true" + vm: shared + script: | + state.enabled; status: type: js - script: "'B'" + vm: shared + script: | + if (state.enabled) "C"; else "B"; maxcurrent: type: js - script: "6" + vm: shared + script: | + set(); + state.maxcurrent = val; + if (state.enabled) state.chargepower = state.maxcurrent * 230; vehicles: - name: demo @@ -50,7 +98,13 @@ vehicles: type: default charge: type: js - script: "63" + 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 @@ -62,6 +116,8 @@ site: loadpoints: - title: Carport charger: demo + meters: + charge: charge vehicle: demo ` diff --git a/cmd/root.go b/cmd/root.go index 4f7c2c79e..1d0ce7867 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -158,6 +158,9 @@ func run(cmd *cobra.Command, args []string) { configureMQTT(conf.Mqtt) } + // setup javascript VMs + configureJavascript(conf.Javascript) + // start broadcasting values tee := &Tee{} diff --git a/cmd/setup.go b/cmd/setup.go index 9d3a3f5c4..834264324 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -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) diff --git a/provider/javascript.go b/provider/javascript.go index b877733a8..4426a2dc9 100644 --- a/provider/javascript.go +++ b/provider/javascript.go @@ -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) } diff --git a/provider/javascript/init.go b/provider/javascript/init.go new file mode 100644 index 000000000..0c644d9e1 --- /dev/null +++ b/provider/javascript/init.go @@ -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 +}