Add javascript and decoding support to the http plugin (#1944)
This allows to run javascript code on the data returned by the HTTP call, optionally processed via jq and decoded, e.g. hex values into ints and floats
This commit is contained in:
parent
aeb67da85f
commit
0b248d55cb
1 changed files with 124 additions and 1 deletions
125
provider/http.go
125
provider/http.go
|
|
@ -1,6 +1,7 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
|
|
@ -9,12 +10,15 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/evcc-io/evcc/provider/javascript"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/jq"
|
||||
"github.com/evcc-io/evcc/util/request"
|
||||
"github.com/evcc-io/evcc/util/transport"
|
||||
"github.com/itchyny/gojq"
|
||||
"github.com/jpfielding/go-http-digest/pkg/digest"
|
||||
"github.com/robertkrimen/otto"
|
||||
"github.com/volkszaehler/mbmd/meters/rs485"
|
||||
)
|
||||
|
||||
// HTTP implements HTTP request provider
|
||||
|
|
@ -25,6 +29,10 @@ type HTTP struct {
|
|||
body string
|
||||
re *regexp.Regexp
|
||||
jq *gojq.Query
|
||||
unpack string
|
||||
decode string
|
||||
vm *otto.Otto
|
||||
script string
|
||||
scale float64
|
||||
cache time.Duration
|
||||
updated time.Time
|
||||
|
|
@ -49,6 +57,10 @@ func NewHTTPProviderFromConfig(other map[string]interface{}) (IntProvider, error
|
|||
Body string
|
||||
Regex string
|
||||
Jq string
|
||||
Unpack string
|
||||
Decode string
|
||||
VM string
|
||||
Script string
|
||||
Scale float64
|
||||
Insecure bool
|
||||
Auth Auth
|
||||
|
|
@ -83,6 +95,18 @@ func NewHTTPProviderFromConfig(other map[string]interface{}) (IntProvider, error
|
|||
_, err = http.WithJq(cc.Jq)
|
||||
}
|
||||
|
||||
if err == nil && cc.Unpack != "" {
|
||||
_, err = http.WithUnpack(cc.Unpack)
|
||||
}
|
||||
|
||||
if err == nil && cc.Decode != "" {
|
||||
_, err = http.WithDecode(cc.Decode)
|
||||
}
|
||||
|
||||
if err == nil && cc.Script != "" {
|
||||
_, err = http.WithScript(cc.VM, cc.Script)
|
||||
}
|
||||
|
||||
if err == nil && cc.Auth.Type != "" {
|
||||
_, err = http.WithAuth(cc.Auth.Type, cc.Auth.User, cc.Auth.Password)
|
||||
}
|
||||
|
|
@ -149,6 +173,30 @@ func (p *HTTP) WithJq(jq string) (*HTTP, error) {
|
|||
return p, nil
|
||||
}
|
||||
|
||||
// WithUnpack adds data unpacking
|
||||
func (p *HTTP) WithUnpack(unpack string) (*HTTP, error) {
|
||||
p.unpack = strings.ToLower(unpack)
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// WithDecode adds data decoding
|
||||
func (p *HTTP) WithDecode(decode string) (*HTTP, error) {
|
||||
p.decode = strings.ToLower(decode)
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// WithScript adds a javascript script to process the response
|
||||
func (p *HTTP) WithScript(vm, script string) (*HTTP, error) {
|
||||
regvm := javascript.RegisteredVM(strings.ToLower(vm))
|
||||
|
||||
p.vm = regvm
|
||||
p.script = script
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// WithAuth adds authorized transport
|
||||
func (p *HTTP) WithAuth(typ, user, password string) (*HTTP, error) {
|
||||
switch strings.ToLower(typ) {
|
||||
|
|
@ -184,6 +232,48 @@ func (p *HTTP) request(body ...string) ([]byte, error) {
|
|||
return p.val, p.err
|
||||
}
|
||||
|
||||
func (p *HTTP) unpackValue(value []byte) (string, error) {
|
||||
switch p.unpack {
|
||||
case "hex":
|
||||
b, err := hex.DecodeString(string(value))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("invalid unpack: %s", p.unpack)
|
||||
}
|
||||
|
||||
// decode a hex string to a proper value
|
||||
// TODO reuse similar code from Modbus
|
||||
func (p *HTTP) decodeValue(value []byte) (interface{}, error) {
|
||||
switch p.decode {
|
||||
case "float32", "ieee754":
|
||||
return rs485.RTUIeee754ToFloat64(value), nil
|
||||
case "float32s", "ieee754s":
|
||||
return rs485.RTUIeee754ToFloat64Swapped(value), nil
|
||||
case "float64":
|
||||
return rs485.RTUUint64ToFloat64(value), nil
|
||||
case "uint16":
|
||||
return rs485.RTUUint16ToFloat64(value), nil
|
||||
case "uint32":
|
||||
return rs485.RTUUint32ToFloat64(value), nil
|
||||
case "uint32s":
|
||||
return rs485.RTUUint32ToFloat64Swapped(value), nil
|
||||
case "uint64":
|
||||
return rs485.RTUUint64ToFloat64(value), nil
|
||||
case "int16":
|
||||
return rs485.RTUInt16ToFloat64(value), nil
|
||||
case "int32":
|
||||
return rs485.RTUInt32ToFloat64(value), nil
|
||||
case "int32s":
|
||||
return rs485.RTUInt32ToFloat64Swapped(value), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid decoding: %s", p.decode)
|
||||
}
|
||||
|
||||
// FloatGetter parses float from request
|
||||
func (p *HTTP) FloatGetter() func() (float64, error) {
|
||||
g := p.StringGetter()
|
||||
|
|
@ -230,7 +320,40 @@ func (p *HTTP) StringGetter() func() (string, error) {
|
|||
|
||||
if p.jq != nil {
|
||||
v, err := jq.Query(p.jq, b)
|
||||
return fmt.Sprintf("%v", v), err
|
||||
if err != nil {
|
||||
return string(b), err
|
||||
}
|
||||
b = []byte(fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
if p.unpack != "" {
|
||||
v, err := p.unpackValue(b)
|
||||
if err != nil {
|
||||
return string(b), err
|
||||
}
|
||||
b = []byte(fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
if p.decode != "" {
|
||||
v, err := p.decodeValue(b)
|
||||
if err != nil {
|
||||
return string(b), err
|
||||
}
|
||||
b = []byte(fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
if p.vm != nil {
|
||||
err := p.vm.Set("val", string(b))
|
||||
if err != nil {
|
||||
return string(b), err
|
||||
}
|
||||
|
||||
v, err := p.vm.Eval(p.script)
|
||||
if err != nil {
|
||||
return string(b), err
|
||||
}
|
||||
|
||||
return v.ToString()
|
||||
}
|
||||
|
||||
return string(b), err
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue