Plugins: allow chaining of input/output plugins (#7836)
This commit is contained in:
parent
370b15aaca
commit
cba97832db
16 changed files with 582 additions and 199 deletions
|
|
@ -19,11 +19,7 @@ javascript:
|
|||
{ enabled: false, vehicleSoc: 22, maxcurrent: 0, phases: 3, chargepower: 0 }
|
||||
]
|
||||
};
|
||||
function get() {
|
||||
console.log("state:", JSON.stringify(state));
|
||||
}
|
||||
function set() {
|
||||
console.log(param+":", val);
|
||||
function logState() {
|
||||
console.log("state:", JSON.stringify(state));
|
||||
}
|
||||
|
||||
|
|
@ -34,10 +30,25 @@ meters:
|
|||
source: js
|
||||
vm: shared
|
||||
script: |
|
||||
state.pvpower = 8000+500*Math.random();
|
||||
state.batterypower = state.gridpower > 0 ? 1000 * Math.random() : 0;
|
||||
state.gridpower = state.loadpoints[0].chargepower + state.loadpoints[1].chargepower + state.residualpower - state.batterypower - state.pvpower;
|
||||
state.gridpower = state.loadpoints[0].chargepower + state.loadpoints[1].chargepower + state.residualpower - batterypower - pvpower;
|
||||
state.gridpower;
|
||||
in:
|
||||
- name: pvpower
|
||||
type: float
|
||||
config:
|
||||
source: js
|
||||
vm: shared
|
||||
script: |
|
||||
state.pvpower = 8000+500*Math.random();
|
||||
state.pvpower
|
||||
- name: batterypower
|
||||
type: float
|
||||
config:
|
||||
source: js
|
||||
vm: shared
|
||||
script: |
|
||||
state.batterypower = state.gridpower > 0 ? 1000 * Math.random() : 0;
|
||||
state.batterypower
|
||||
|
||||
- name: pv
|
||||
type: custom
|
||||
|
|
@ -83,10 +94,18 @@ chargers:
|
|||
source: js
|
||||
vm: shared
|
||||
script: |
|
||||
set();
|
||||
logState();
|
||||
var lp = state.loadpoints[0];
|
||||
lp.enabled = val;
|
||||
if (lp.enabled) lp.chargepower = lp.maxcurrent * 230 * lp.phases; else lp.chargepower = 0;
|
||||
lp.enabled = enable;
|
||||
enable;
|
||||
out:
|
||||
- name: enable
|
||||
type: bool
|
||||
config:
|
||||
source: js
|
||||
vm: shared
|
||||
script: |
|
||||
if (enable) lp.chargepower = lp.maxcurrent * 230 * lp.phases; else lp.chargepower = 0;
|
||||
enabled:
|
||||
source: js
|
||||
vm: shared
|
||||
|
|
@ -101,9 +120,9 @@ chargers:
|
|||
source: js
|
||||
vm: shared
|
||||
script: |
|
||||
set();
|
||||
logState();
|
||||
var lp = state.loadpoints[0];
|
||||
lp.maxcurrent = val;
|
||||
lp.maxcurrent = maxcurrent;
|
||||
if (lp.enabled) lp.chargepower = lp.maxcurrent * 230 * lp.phases; else lp.chargepower = 0;
|
||||
|
||||
- name: charger_2
|
||||
|
|
@ -112,9 +131,9 @@ chargers:
|
|||
source: js
|
||||
vm: shared
|
||||
script: |
|
||||
set();
|
||||
logState();
|
||||
var lp = state.loadpoints[1];
|
||||
lp.enabled = val;
|
||||
lp.enabled = enable;
|
||||
if (lp.enabled) lp.chargepower = lp.maxcurrent * 230 * lp.phases; else lp.chargepower = 0;
|
||||
enabled:
|
||||
source: js
|
||||
|
|
@ -130,16 +149,16 @@ chargers:
|
|||
source: js
|
||||
vm: shared
|
||||
script: |
|
||||
set();
|
||||
logState();
|
||||
var lp = state.loadpoints[1];
|
||||
lp.maxcurrent = val;
|
||||
lp.maxcurrent = maxcurrent;
|
||||
if (lp.enabled) lp.chargepower = lp.maxcurrent * 230 * lp.phases; else lp.chargepower = 0;
|
||||
phases1p3p:
|
||||
source: js
|
||||
vm: shared
|
||||
script: |
|
||||
set();
|
||||
if (val === 1) lp.phases = 1; else lp.phases = 3;
|
||||
logState();
|
||||
if (phases === 1) lp.phases = 1; else lp.phases = 3;
|
||||
lp.phases;
|
||||
|
||||
vehicles:
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ func init() {
|
|||
}
|
||||
|
||||
// NewCalcFromConfig creates calc provider
|
||||
func NewCalcFromConfig(other map[string]interface{}) (IntProvider, error) {
|
||||
func NewCalcFromConfig(other map[string]interface{}) (Provider, error) {
|
||||
var cc struct {
|
||||
Add []Config
|
||||
Mul []Config
|
||||
|
|
|
|||
27
provider/combined.go
Normal file
27
provider/combined.go
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package provider
|
||||
|
||||
type combinedProvider struct {
|
||||
status func() (string, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.Add("combined", NewCombinedFromConfig)
|
||||
registry.Add("openwb", NewCombinedFromConfig)
|
||||
}
|
||||
|
||||
// NewCombinedFromConfig creates combined provider
|
||||
func NewCombinedFromConfig(other map[string]interface{}) (Provider, error) {
|
||||
status, err := NewOpenWBStatusProviderFromConfig(other)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
o := &combinedProvider{status: status}
|
||||
return o, nil
|
||||
}
|
||||
|
||||
func (o *combinedProvider) StringGetter() func() (string, error) {
|
||||
return func() (string, error) {
|
||||
return o.status()
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
// provider types
|
||||
type (
|
||||
Provider interface{}
|
||||
IntProvider interface {
|
||||
IntGetter() func() (int64, error)
|
||||
}
|
||||
|
|
@ -24,21 +25,24 @@ type (
|
|||
SetStringProvider interface {
|
||||
StringSetter(param string) func(string) error
|
||||
}
|
||||
SetFloatProvider interface {
|
||||
FloatSetter(param string) func(float64) error
|
||||
}
|
||||
SetBoolProvider interface {
|
||||
BoolSetter(param string) func(bool) error
|
||||
}
|
||||
)
|
||||
|
||||
type providerRegistry map[string]func(map[string]interface{}) (IntProvider, error)
|
||||
type providerRegistry map[string]func(map[string]interface{}) (Provider, error)
|
||||
|
||||
func (r providerRegistry) Add(name string, factory func(map[string]interface{}) (IntProvider, error)) {
|
||||
func (r providerRegistry) Add(name string, factory func(map[string]interface{}) (Provider, error)) {
|
||||
if _, exists := r[name]; exists {
|
||||
panic(fmt.Sprintf("cannot register duplicate plugin type: %s", name))
|
||||
}
|
||||
r[name] = factory
|
||||
}
|
||||
|
||||
func (r providerRegistry) Get(name string) (func(map[string]interface{}) (IntProvider, error), error) {
|
||||
func (r providerRegistry) Get(name string) (func(map[string]interface{}) (Provider, error), error) {
|
||||
factory, exists := r[name]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("invalid plugin source: %s", name)
|
||||
|
|
@ -46,7 +50,7 @@ func (r providerRegistry) Get(name string) (func(map[string]interface{}) (IntPro
|
|||
return factory, nil
|
||||
}
|
||||
|
||||
var registry providerRegistry = make(map[string]func(map[string]interface{}) (IntProvider, error))
|
||||
var registry providerRegistry = make(map[string]func(map[string]interface{}) (Provider, error))
|
||||
|
||||
// Config is the general provider config
|
||||
type Config struct {
|
||||
|
|
@ -55,122 +59,161 @@ type Config struct {
|
|||
}
|
||||
|
||||
// NewIntGetterFromConfig creates a IntGetter from config
|
||||
func NewIntGetterFromConfig(config Config) (res func() (int64, error), err error) {
|
||||
func NewIntGetterFromConfig(config Config) (func() (int64, error), error) {
|
||||
factory, err := registry.Get(config.Source)
|
||||
if err == nil {
|
||||
var provider IntProvider
|
||||
provider, err = factory(config.Other)
|
||||
|
||||
if err == nil {
|
||||
res = provider.IntGetter()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err == nil && res == nil {
|
||||
err = fmt.Errorf("invalid plugin source: %s", config.Source)
|
||||
provider, err := factory(config.Other)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
prov, ok := provider.(IntProvider)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid plugin source for type int: %s", config.Source)
|
||||
}
|
||||
|
||||
return prov.IntGetter(), nil
|
||||
}
|
||||
|
||||
// NewFloatGetterFromConfig creates a FloatGetter from config
|
||||
func NewFloatGetterFromConfig(config Config) (res func() (float64, error), err error) {
|
||||
func NewFloatGetterFromConfig(config Config) (func() (float64, error), error) {
|
||||
factory, err := registry.Get(config.Source)
|
||||
if err == nil {
|
||||
var provider IntProvider
|
||||
provider, err = factory(config.Other)
|
||||
|
||||
if prov, ok := provider.(FloatProvider); ok {
|
||||
res = prov.FloatGetter()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err == nil && res == nil {
|
||||
err = fmt.Errorf("invalid plugin source: %s", config.Source)
|
||||
provider, err := factory(config.Other)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
prov, ok := provider.(FloatProvider)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid plugin source for type float: %s", config.Source)
|
||||
}
|
||||
|
||||
return prov.FloatGetter(), nil
|
||||
}
|
||||
|
||||
// NewStringGetterFromConfig creates a StringGetter from config
|
||||
func NewStringGetterFromConfig(config Config) (res func() (string, error), err error) {
|
||||
switch typ := config.Source; typ {
|
||||
case "combined", "openwb":
|
||||
res, err = NewOpenWBStatusProviderFromConfig(config.Other)
|
||||
|
||||
default:
|
||||
var factory func(map[string]interface{}) (IntProvider, error)
|
||||
factory, err = registry.Get(typ)
|
||||
if err == nil {
|
||||
var provider IntProvider
|
||||
provider, err = factory(config.Other)
|
||||
|
||||
if prov, ok := provider.(StringProvider); ok {
|
||||
res = prov.StringGetter()
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil && res == nil {
|
||||
err = fmt.Errorf("invalid plugin source: %s", config.Source)
|
||||
}
|
||||
func NewStringGetterFromConfig(config Config) (func() (string, error), error) {
|
||||
factory, err := registry.Get(config.Source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
provider, err := factory(config.Other)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prov, ok := provider.(StringProvider)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid plugin source for type string: %s", config.Source)
|
||||
}
|
||||
|
||||
return prov.StringGetter(), nil
|
||||
}
|
||||
|
||||
// NewBoolGetterFromConfig creates a BoolGetter from config
|
||||
func NewBoolGetterFromConfig(config Config) (res func() (bool, error), err error) {
|
||||
func NewBoolGetterFromConfig(config Config) (func() (bool, error), error) {
|
||||
factory, err := registry.Get(config.Source)
|
||||
if err == nil {
|
||||
var provider IntProvider
|
||||
provider, err = factory(config.Other)
|
||||
|
||||
if prov, ok := provider.(BoolProvider); ok {
|
||||
res = prov.BoolGetter()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err == nil && res == nil {
|
||||
err = fmt.Errorf("invalid plugin source: %s", config.Source)
|
||||
provider, err := factory(config.Other)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
prov, ok := provider.(BoolProvider)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid plugin source for type bool: %s", config.Source)
|
||||
}
|
||||
|
||||
return prov.BoolGetter(), nil
|
||||
}
|
||||
|
||||
// NewIntSetterFromConfig creates a IntSetter from config
|
||||
func NewIntSetterFromConfig(param string, config Config) (res func(int64) error, err error) {
|
||||
func NewIntSetterFromConfig(param string, config Config) (func(int64) error, error) {
|
||||
factory, err := registry.Get(config.Source)
|
||||
if err == nil {
|
||||
var provider IntProvider
|
||||
provider, err = factory(config.Other)
|
||||
|
||||
if prov, ok := provider.(SetIntProvider); ok {
|
||||
res = prov.IntSetter(param)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err == nil && res == nil {
|
||||
err = fmt.Errorf("invalid plugin source: %s", config.Source)
|
||||
provider, err := factory(config.Other)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
prov, ok := provider.(SetIntProvider)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid plugin source for type int: %s", config.Source)
|
||||
}
|
||||
|
||||
return prov.IntSetter(param), nil
|
||||
}
|
||||
|
||||
// NewFloatSetterFromConfig creates a FloatSetter from config
|
||||
func NewFloatSetterFromConfig(param string, config Config) (func(float642 float64) error, error) {
|
||||
factory, err := registry.Get(config.Source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
provider, err := factory(config.Other)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prov, ok := provider.(SetFloatProvider)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid plugin source for type float: %s", config.Source)
|
||||
}
|
||||
|
||||
return prov.FloatSetter(param), nil
|
||||
}
|
||||
|
||||
// NewStringSetterFromConfig creates a StringSetter from config
|
||||
func NewStringSetterFromConfig(param string, config Config) (func(string) error, error) {
|
||||
factory, err := registry.Get(config.Source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
provider, err := factory(config.Other)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prov, ok := provider.(SetStringProvider)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid plugin source for type string: %s", config.Source)
|
||||
}
|
||||
|
||||
return prov.StringSetter(param), nil
|
||||
}
|
||||
|
||||
// NewBoolSetterFromConfig creates a BoolSetter from config
|
||||
func NewBoolSetterFromConfig(param string, config Config) (res func(bool) error, err error) {
|
||||
func NewBoolSetterFromConfig(param string, config Config) (func(bool) error, error) {
|
||||
factory, err := registry.Get(config.Source)
|
||||
if err == nil {
|
||||
var provider IntProvider
|
||||
provider, err = factory(config.Other)
|
||||
|
||||
if prov, ok := provider.(SetBoolProvider); ok {
|
||||
res = prov.BoolSetter(param)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err == nil && res == nil {
|
||||
err = fmt.Errorf("invalid plugin source: %s", config.Source)
|
||||
provider, err := factory(config.Other)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
prov, ok := provider.(SetBoolProvider)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid plugin source for type bool: %s", config.Source)
|
||||
}
|
||||
|
||||
return prov.BoolSetter(param), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ func init() {
|
|||
}
|
||||
|
||||
// NewConstFromConfig creates const provider
|
||||
func NewConstFromConfig(other map[string]interface{}) (IntProvider, error) {
|
||||
func NewConstFromConfig(other map[string]interface{}) (Provider, error) {
|
||||
var cc struct {
|
||||
Value string
|
||||
pipeline.Settings `mapstructure:",squash"`
|
||||
|
|
|
|||
148
provider/go.go
148
provider/go.go
|
|
@ -2,7 +2,6 @@ package provider
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/evcc-io/evcc/provider/golang"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
|
|
@ -13,6 +12,8 @@ import (
|
|||
type Go struct {
|
||||
vm *interp.Interpreter
|
||||
script string
|
||||
in []inputTransformation
|
||||
out []outputTransformation
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
@ -20,10 +21,12 @@ func init() {
|
|||
}
|
||||
|
||||
// NewGoProviderFromConfig creates a Go provider
|
||||
func NewGoProviderFromConfig(other map[string]interface{}) (IntProvider, error) {
|
||||
func NewGoProviderFromConfig(other map[string]interface{}) (Provider, error) {
|
||||
var cc struct {
|
||||
VM string
|
||||
Script string
|
||||
In []transformationConfig
|
||||
Out []transformationConfig
|
||||
}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
|
|
@ -35,9 +38,21 @@ func NewGoProviderFromConfig(other map[string]interface{}) (IntProvider, error)
|
|||
return nil, err
|
||||
}
|
||||
|
||||
in, err := configureInputs(cc.In)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out, err := configureOutputs(cc.Out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := &Go{
|
||||
vm: vm,
|
||||
script: cc.Script,
|
||||
in: in,
|
||||
out: out,
|
||||
}
|
||||
|
||||
return p, nil
|
||||
|
|
@ -45,92 +60,135 @@ func NewGoProviderFromConfig(other map[string]interface{}) (IntProvider, error)
|
|||
|
||||
// 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 func() (float64, error) {
|
||||
v, err := p.handleGetter()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return res, err
|
||||
vv, ok := v.(float64)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("not a float: %s", v)
|
||||
}
|
||||
|
||||
return vv, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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 func() (int64, error) {
|
||||
v, err := p.handleGetter()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return res, err
|
||||
vv, ok := v.(int64)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("not a int: %s", v)
|
||||
}
|
||||
|
||||
return vv, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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 func() (string, error) {
|
||||
v, err := p.handleGetter()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return res, err
|
||||
vv, ok := v.(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("not a string: %s", v)
|
||||
}
|
||||
|
||||
return vv, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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 func() (bool, error) {
|
||||
v, err := p.handleGetter()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return res, err
|
||||
vv, ok := v.(bool)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("not a bool: %s", v)
|
||||
}
|
||||
|
||||
return vv, nil
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
func (p *Go) handleGetter() (any, error) {
|
||||
if err := transformInputs(p.in, p.setParam); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.evaluate()
|
||||
}
|
||||
|
||||
func (p *Go) handleSetter(param string, val any) error {
|
||||
if err := p.setParam(param, val); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vv, err := p.evaluate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return transformOutputs(p.out, vv)
|
||||
}
|
||||
|
||||
func (p *Go) evaluate() (any, error) {
|
||||
v, err := p.vm.Eval(p.script)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return normalizeValue(v.Interface())
|
||||
}
|
||||
|
||||
func (p *Go) setParam(param string, val any) error {
|
||||
if str, ok := val.(string); ok {
|
||||
val = "\"" + str + "\""
|
||||
}
|
||||
|
||||
_, err := p.vm.Eval(fmt.Sprintf("%s := %v;", param, val))
|
||||
return err
|
||||
}
|
||||
|
||||
// IntSetter sends int request
|
||||
func (p *Go) IntSetter(param string) func(int64) error {
|
||||
return func(val int64) error {
|
||||
return p.paramAndEval(param, val)
|
||||
return p.handleSetter(param, val)
|
||||
}
|
||||
}
|
||||
|
||||
// FloatSetter sends float request
|
||||
func (p *Go) FloatSetter(param string) func(float64) error {
|
||||
return func(val float64) error {
|
||||
return p.handleSetter(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)
|
||||
return p.handleSetter(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)
|
||||
return p.handleSetter(param, val)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ type Auth struct {
|
|||
}
|
||||
|
||||
// NewHTTPProviderFromConfig creates a HTTP provider
|
||||
func NewHTTPProviderFromConfig(other map[string]interface{}) (IntProvider, error) {
|
||||
func NewHTTPProviderFromConfig(other map[string]interface{}) (Provider, error) {
|
||||
cc := struct {
|
||||
URI, Method string
|
||||
Headers map[string]string
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/evcc-io/evcc/provider/javascript"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/robertkrimen/otto"
|
||||
|
|
@ -10,17 +12,21 @@ import (
|
|||
type Javascript struct {
|
||||
vm *otto.Otto
|
||||
script string
|
||||
in []inputTransformation
|
||||
out []outputTransformation
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.Add("js", NewJavascriptProviderFromConfig)
|
||||
}
|
||||
|
||||
// NewJavascriptProviderFromConfig creates a HTTP provider
|
||||
func NewJavascriptProviderFromConfig(other map[string]interface{}) (IntProvider, error) {
|
||||
// NewJavascriptProviderFromConfig creates a Javascript provider
|
||||
func NewJavascriptProviderFromConfig(other map[string]interface{}) (Provider, error) {
|
||||
var cc struct {
|
||||
VM string
|
||||
Script string
|
||||
In []transformationConfig
|
||||
Out []transformationConfig
|
||||
}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
|
|
@ -32,9 +38,21 @@ func NewJavascriptProviderFromConfig(other map[string]interface{}) (IntProvider,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
in, err := configureInputs(cc.In)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out, err := configureOutputs(cc.Out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := &Javascript{
|
||||
vm: vm,
|
||||
script: cc.Script,
|
||||
in: in,
|
||||
out: out,
|
||||
}
|
||||
|
||||
return p, nil
|
||||
|
|
@ -42,92 +60,140 @@ func NewJavascriptProviderFromConfig(other map[string]interface{}) (IntProvider,
|
|||
|
||||
// FloatGetter parses float from request
|
||||
func (p *Javascript) FloatGetter() func() (float64, error) {
|
||||
return func() (res float64, err error) {
|
||||
v, err := p.vm.Eval(p.script)
|
||||
if err == nil {
|
||||
res, err = v.ToFloat()
|
||||
return func() (float64, error) {
|
||||
v, err := p.handleGetter()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return res, err
|
||||
vv, ok := v.(float64)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("not a float: %s", v)
|
||||
}
|
||||
|
||||
return vv, nil
|
||||
}
|
||||
}
|
||||
|
||||
// IntGetter parses int64 from request
|
||||
func (p *Javascript) IntGetter() func() (int64, error) {
|
||||
return func() (res int64, err error) {
|
||||
v, err := p.vm.Eval(p.script)
|
||||
if err == nil {
|
||||
res, err = v.ToInteger()
|
||||
return func() (int64, error) {
|
||||
v, err := p.handleGetter()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return res, err
|
||||
vv, ok := v.(int64)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("not a int: %s", v)
|
||||
}
|
||||
|
||||
return vv, nil
|
||||
}
|
||||
}
|
||||
|
||||
// StringGetter sends string request
|
||||
// StringGetter parses string from request
|
||||
func (p *Javascript) StringGetter() func() (string, error) {
|
||||
return func() (res string, err error) {
|
||||
v, err := p.vm.Eval(p.script)
|
||||
if err == nil {
|
||||
res, err = v.ToString()
|
||||
return func() (string, error) {
|
||||
v, err := p.handleGetter()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return res, err
|
||||
vv, ok := v.(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("not a string: %s", v)
|
||||
}
|
||||
|
||||
return vv, nil
|
||||
}
|
||||
}
|
||||
|
||||
// BoolGetter parses bool from request
|
||||
func (p *Javascript) BoolGetter() func() (bool, error) {
|
||||
return func() (res bool, err error) {
|
||||
v, err := p.vm.Eval(p.script)
|
||||
if err == nil {
|
||||
res, err = v.ToBoolean()
|
||||
return func() (bool, error) {
|
||||
v, err := p.handleGetter()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return res, err
|
||||
vv, ok := v.(bool)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("not a bool: %s", v)
|
||||
}
|
||||
|
||||
return vv, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Javascript) setParam(param string, val interface{}) error {
|
||||
err := p.vm.Set(param, val)
|
||||
if err == nil {
|
||||
err = p.vm.Set("param", param)
|
||||
func (p *Javascript) handleGetter() (any, error) {
|
||||
if err := transformInputs(p.in, p.setParam); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err == nil {
|
||||
err = p.vm.Set("val", val)
|
||||
|
||||
v, err := p.evaluate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return err
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (p *Javascript) handleSetter(param string, val any) error {
|
||||
if err := p.setParam(param, val); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v, err := p.evaluate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return transformOutputs(p.out, v)
|
||||
}
|
||||
|
||||
func (p *Javascript) evaluate() (any, error) {
|
||||
v, err := p.vm.Eval(p.script)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vv, err := v.Export()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return normalizeValue(vv)
|
||||
}
|
||||
|
||||
func (p *Javascript) setParam(param string, val any) error {
|
||||
return p.vm.Set(param, val)
|
||||
}
|
||||
|
||||
// IntSetter sends int request
|
||||
func (p *Javascript) IntSetter(param string) func(int64) error {
|
||||
return func(val int64) error {
|
||||
err := p.setParam(param, val)
|
||||
if err == nil {
|
||||
_, err = p.vm.Eval(p.script)
|
||||
}
|
||||
return err
|
||||
return p.handleSetter(param, val)
|
||||
}
|
||||
}
|
||||
|
||||
// FloatSetter sends float request
|
||||
func (p *Javascript) FloatSetter(param string) func(float64) error {
|
||||
return func(val float64) error {
|
||||
return p.handleSetter(param, val)
|
||||
}
|
||||
}
|
||||
|
||||
// StringSetter sends string request
|
||||
func (p *Javascript) StringSetter(param string) func(string) error {
|
||||
return func(val string) error {
|
||||
err := p.setParam(param, val)
|
||||
if err == nil {
|
||||
_, err = p.vm.Eval(p.script)
|
||||
}
|
||||
return err
|
||||
return p.handleSetter(param, val)
|
||||
}
|
||||
}
|
||||
|
||||
// BoolSetter sends bool request
|
||||
func (p *Javascript) BoolSetter(param string) func(bool) error {
|
||||
return func(val bool) error {
|
||||
err := p.setParam(param, val)
|
||||
if err == nil {
|
||||
_, err = p.vm.Eval(p.script)
|
||||
}
|
||||
return err
|
||||
return p.handleSetter(param, val)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ func init() {
|
|||
}
|
||||
|
||||
// NewModbusFromConfig creates Modbus plugin
|
||||
func NewModbusFromConfig(other map[string]interface{}) (IntProvider, error) {
|
||||
func NewModbusFromConfig(other map[string]interface{}) (Provider, error) {
|
||||
cc := struct {
|
||||
Model string
|
||||
modbus.Settings `mapstructure:",squash"`
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ func init() {
|
|||
}
|
||||
|
||||
// NewMqttFromConfig creates Mqtt provider
|
||||
func NewMqttFromConfig(other map[string]interface{}) (IntProvider, error) {
|
||||
func NewMqttFromConfig(other map[string]interface{}) (Provider, error) {
|
||||
cc := struct {
|
||||
mqtt.Config `mapstructure:",squash"`
|
||||
Topic, Payload string // Payload only applies to setters
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ func init() {
|
|||
registry.Add("prometheus", NewPrometheusFromConfig)
|
||||
}
|
||||
|
||||
func NewPrometheusFromConfig(other map[string]interface{}) (IntProvider, error) {
|
||||
func NewPrometheusFromConfig(other map[string]interface{}) (Provider, error) {
|
||||
cc := struct {
|
||||
Uri, Query string
|
||||
Timeout time.Duration
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ func init() {
|
|||
}
|
||||
|
||||
// NewScriptProviderFromConfig creates a script provider.
|
||||
func NewScriptProviderFromConfig(other map[string]interface{}) (IntProvider, error) {
|
||||
func NewScriptProviderFromConfig(other map[string]interface{}) (Provider, error) {
|
||||
cc := struct {
|
||||
Cmd string
|
||||
Timeout time.Duration
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ func init() {
|
|||
}
|
||||
|
||||
// NewSMAFromConfig creates SMA provider
|
||||
func NewSMAFromConfig(other map[string]interface{}) (IntProvider, error) {
|
||||
func NewSMAFromConfig(other map[string]interface{}) (Provider, error) {
|
||||
cc := struct {
|
||||
URI, Password, Interface string
|
||||
Serial uint32
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ func init() {
|
|||
}
|
||||
|
||||
// NewSocketProviderFromConfig creates a HTTP provider
|
||||
func NewSocketProviderFromConfig(other map[string]interface{}) (IntProvider, error) {
|
||||
func NewSocketProviderFromConfig(other map[string]interface{}) (Provider, error) {
|
||||
cc := struct {
|
||||
URI string
|
||||
Headers map[string]string
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ func TestSocketProvider(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
g := p.IntGetter()
|
||||
g := p.(IntProvider).IntGetter()
|
||||
i, err := g()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(1), i)
|
||||
|
|
|
|||
170
provider/transformation.go
Normal file
170
provider/transformation.go
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type transformationConfig struct {
|
||||
Name, Type string
|
||||
Config Config
|
||||
}
|
||||
|
||||
type inputTransformation struct {
|
||||
name string
|
||||
function func() (any, error)
|
||||
}
|
||||
|
||||
type outputTransformation struct {
|
||||
name string
|
||||
function func(any) error
|
||||
}
|
||||
|
||||
func configureInputs(inConfig []transformationConfig) ([]inputTransformation, error) {
|
||||
var in []inputTransformation
|
||||
|
||||
for _, cc := range inConfig {
|
||||
var f func() (any, error)
|
||||
|
||||
switch strings.ToLower(cc.Type) {
|
||||
case "bool":
|
||||
ff, err := NewBoolGetterFromConfig(cc.Config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", cc.Name, err)
|
||||
}
|
||||
f = func() (any, error) { return ff() }
|
||||
|
||||
case "int":
|
||||
ff, err := NewIntGetterFromConfig(cc.Config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", cc.Name, err)
|
||||
}
|
||||
f = func() (any, error) { return ff() }
|
||||
|
||||
case "float":
|
||||
ff, err := NewFloatGetterFromConfig(cc.Config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", cc.Name, err)
|
||||
}
|
||||
f = func() (any, error) { return ff() }
|
||||
|
||||
case "string":
|
||||
ff, err := NewStringGetterFromConfig(cc.Config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", cc.Name, err)
|
||||
}
|
||||
f = func() (any, error) { return ff() }
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("%s: Could not find converter for %s", cc.Name, cc.Type)
|
||||
}
|
||||
|
||||
in = append(in, inputTransformation{
|
||||
name: cc.Name,
|
||||
function: f,
|
||||
})
|
||||
}
|
||||
return in, nil
|
||||
}
|
||||
|
||||
func configureOutputs(outConfig []transformationConfig) ([]outputTransformation, error) {
|
||||
var out []outputTransformation
|
||||
|
||||
for _, cc := range outConfig {
|
||||
var f func(v any) error
|
||||
|
||||
switch strings.ToLower(cc.Type) {
|
||||
case "bool":
|
||||
ff, err := NewBoolSetterFromConfig(cc.Name, cc.Config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", cc.Name, err)
|
||||
}
|
||||
|
||||
f = func(v any) error {
|
||||
return ff(v.(bool))
|
||||
}
|
||||
|
||||
case "int":
|
||||
ff, err := NewIntSetterFromConfig(cc.Name, cc.Config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", cc.Name, err)
|
||||
}
|
||||
|
||||
f = func(v any) error {
|
||||
return ff(v.(int64))
|
||||
}
|
||||
|
||||
case "float":
|
||||
ff, err := NewFloatSetterFromConfig(cc.Name, cc.Config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", cc.Name, err)
|
||||
}
|
||||
|
||||
f = func(v any) error {
|
||||
return ff(v.(float64))
|
||||
}
|
||||
|
||||
case "string":
|
||||
ff, err := NewStringSetterFromConfig(cc.Name, cc.Config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", cc.Name, err)
|
||||
}
|
||||
|
||||
f = func(v any) error {
|
||||
return ff(v.(string))
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("%s: invalid type %s", cc.Name, cc.Type)
|
||||
}
|
||||
|
||||
out = append(out, outputTransformation{
|
||||
name: cc.Name,
|
||||
function: f,
|
||||
})
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func transformInputs(in []inputTransformation, set func(string, any) error) error {
|
||||
for _, cc := range in {
|
||||
val, err := cc.function()
|
||||
|
||||
if err == nil {
|
||||
err = set(cc.name, val)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", cc.name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func transformOutputs(out []outputTransformation, v any) error {
|
||||
for _, cc := range out {
|
||||
if err := cc.function(v); err != nil {
|
||||
return fmt.Errorf("%s: %w", cc.name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// normalizeValue transforms compatible plugin return types to ensure only supported ones are used
|
||||
func normalizeValue(val any) (any, error) {
|
||||
switch v := val.(type) {
|
||||
case int:
|
||||
return int64(v), nil
|
||||
case int32:
|
||||
return int64(v), nil
|
||||
case float32:
|
||||
return float64(v), nil
|
||||
case int64, float64, bool, string:
|
||||
return v, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("type not supported: %T", val)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue