Skip to content

Commit

Permalink
Add Go plugin (evcc-io#6672)
Browse files Browse the repository at this point in the history
  • Loading branch information
tisoft authored Apr 8, 2023
1 parent 4f4edba commit c14c873
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 0 deletions.
6 changes: 6 additions & 0 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ type config struct {
Mqtt mqttConfig
ModbusProxy []proxyConfig
Javascript []javascriptConfig
Go []goConfig
Influx server.InfluxConfig
EEBus map[string]interface{}
HEMS typedConfig
Expand All @@ -84,6 +85,11 @@ type javascriptConfig struct {
Script string
}

type goConfig struct {
VM string
Script string
}

type proxyConfig struct {
Port int
ReadOnly bool
Expand Down
16 changes: 16 additions & 0 deletions cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"errors"
"fmt"
"github.com/evcc-io/evcc/provider/golang"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -96,6 +97,11 @@ func configureEnvironment(cmd *cobra.Command, conf config) (err error) {
err = configureJavascript(conf.Javascript)
}

// setup go VMs
if err == nil {
err = configureGo(conf.Go)
}

// setup EEBus server
if err == nil && conf.EEBus != nil {
err = configureEEBus(conf.EEBus)
Expand Down Expand Up @@ -163,6 +169,16 @@ func configureJavascript(conf []javascriptConfig) error {
return nil
}

// setup go
func configureGo(conf []goConfig) error {
for _, cc := range conf {
if _, err := golang.RegisteredVM(cc.VM, cc.Script); err != nil {
return fmt.Errorf("failed configuring go: %w", err)
}
}
return nil
}

// setup HEMS
func configureHEMS(conf typedConfig, site *core.Site, httpd *server.HTTPd) error {
hems, err := hems.NewFromConfig(conf.Type, conf.Other, site, httpd)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ require (
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.15.0
github.com/stretchr/testify v1.8.2
github.com/traefik/yaegi v0.14.3
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c
github.com/volkszaehler/mbmd v0.0.0-20230312113724-f6764040a78e
github.com/writeas/go-strip-markdown v2.0.1+incompatible
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,8 @@ github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cb
github.com/teivah/onecontext v1.3.0 h1:tbikMhAlo6VhAuEGCvhc8HlTnpX4xTNPTOseWuhO1J0=
github.com/teivah/onecontext v1.3.0/go.mod h1:hoW1nmdPVK/0jrvGtcx8sCKYs2PiS4z0zzfdeuEVyb0=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/traefik/yaegi v0.14.3 h1:LqA0k8DKwvRMc+msfQjNusphHJc+r6WC5tZU5TmUFOM=
github.com/traefik/yaegi v0.14.3/go.mod h1:AVRxhaI2G+nUsaM1zyktzwXn69G3t/AuTDrCiTds9p0=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c h1:u6SKchux2yDvFQnDHS3lPnIRmfVJ5Sxy3ao2SIdysLQ=
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM=
Expand Down
136 changes: 136 additions & 0 deletions provider/go.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package provider

import (
"fmt"
"reflect"

"github.com/evcc-io/evcc/provider/golang"
"github.com/evcc-io/evcc/util"
"github.com/traefik/yaegi/interp"
)

// Go implements Go request provider
type Go struct {
vm *interp.Interpreter
script string
}

func init() {
registry.Add("go", NewGoProviderFromConfig)
}

// NewGoProviderFromConfig creates a Go provider
func NewGoProviderFromConfig(other map[string]interface{}) (IntProvider, error) {
var cc struct {
VM string
Script string
}

if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}

vm, err := golang.RegisteredVM(cc.VM, "")
if err != nil {
return nil, err
}

p := &Go{
vm: vm,
script: cc.Script,
}

return p, nil
}

// FloatGetter parses float from request
func (p *Go) FloatGetter() func() (float64, error) {
return func() (res float64, err error) {
v, err := p.vm.Eval(p.script)
if err == nil {
if typ := reflect.TypeOf(res); v.CanConvert(typ) {
res = v.Convert(typ).Float()
} else {
err = fmt.Errorf("not a float: %v", v)
}
}

return res, err
}
}

// IntGetter parses int64 from request
func (p *Go) IntGetter() func() (int64, error) {
return func() (res int64, err error) {
v, err := p.vm.Eval(p.script)
if err == nil {
if typ := reflect.TypeOf(res); v.CanConvert(typ) {
res = v.Convert(typ).Int()
} else {
err = fmt.Errorf("not an int: %v", v)
}
}

return res, err
}
}

// StringGetter parses string from request
func (p *Go) StringGetter() func() (string, error) {
return func() (res string, err error) {
v, err := p.vm.Eval(p.script)
if err == nil {
if typ := reflect.TypeOf(res); v.CanConvert(typ) {
res = v.Convert(typ).String()
} else {
err = fmt.Errorf("not a string: %v", v)
}
}

return res, err
}
}

// BoolGetter parses bool from request
func (p *Go) BoolGetter() func() (bool, error) {
return func() (res bool, err error) {
v, err := p.vm.Eval(p.script)
if err == nil {
if typ := reflect.TypeOf(res); v.CanConvert(typ) {
res = v.Convert(typ).Bool()
} else {
err = fmt.Errorf("not a boolean: %v", v)
}
}

return res, err
}
}

func (p *Go) paramAndEval(param string, val any) error {
_, err := p.vm.Eval(fmt.Sprintf("%s := %v;", param, val))
if err == nil {
_, err = p.vm.Eval(p.script)
}
return err
}

func (p *Go) IntSetter(param string) func(int64) error {
return func(val int64) error {
return p.paramAndEval(param, val)
}
}

// StringSetter sends string request
func (p *Go) StringSetter(param string) func(string) error {
return func(val string) error {
return p.paramAndEval(param, val)
}
}

// BoolSetter sends bool request
func (p *Go) BoolSetter(param string) func(bool) error {
return func(val bool) error {
return p.paramAndEval(param, val)
}
}
43 changes: 43 additions & 0 deletions provider/golang/registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package golang

import (
"github.com/traefik/yaegi/interp"
"github.com/traefik/yaegi/stdlib"
"strings"
"sync"
)

var (
mu sync.Mutex
registry = make(map[string]*interp.Interpreter)
)

// RegisteredVM returns a JS VM. If name is not empty, it will return a shared instance.
func RegisteredVM(name, init string) (*interp.Interpreter, error) {
mu.Lock()
defer mu.Unlock()

name = strings.ToLower(name)
vm, ok := registry[name]

// create new VM
if !ok {
vm = interp.New(interp.Options{})

if err := vm.Use(stdlib.Symbols); err != nil {
return nil, err
}

if init != "" {
if _, err := vm.Eval(init); err != nil {
return nil, err
}
}

if name != "" {
registry[name] = vm
}
}

return vm, nil
}

0 comments on commit c14c873

Please sign in to comment.