Simplify parameter distribution (#148)

This commit is contained in:
andig 2020-05-14 15:19:50 +02:00 • committed by GitHub
parent 40ea628341
commit f45abfe095
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 85 additions and 73 deletions

View file

@ -128,25 +128,13 @@ func checkVersion() {
}
}
var teeIsChained bool // controles piping of first channel in teed chain
// tee splits a tee channel from an input channel and starts a goroutine
// for duplicateing channel values to replacement input and tee channel
func tee(in chan core.Param) (chan core.Param, <-chan core.Param) {
gen := make(chan core.Param)
tee := make(chan core.Param)
go func(teeIsChained bool) {
for i := range gen {
if teeIsChained {
in <- i
}
tee <- i
// handle UI update requests
func handleUI(triggerChan <-chan struct{}, loadPoints []*core.LoadPoint) {
for range triggerChan {
for _, lp := range loadPoints {
lp.Update()
}
}(teeIsChained)
teeIsChained = true
return gen, tee
}
}
func run(cmd *cobra.Command, args []string) {
@ -174,8 +162,7 @@ func run(cmd *cobra.Command, args []string) {
loadPoints := loadConfig(conf, notificationChan)
// start broadcasting values
valueChan := make(chan core.Param)
triggerChan := make(chan struct{})
tee := &Tee{}
// setup influx
if viper.Get("influx") != nil {
@ -187,28 +174,32 @@ func run(cmd *cobra.Command, args []string) {
conf.Influx.Password,
)
var teeChan <-chan core.Param
valueChan, teeChan = tee(valueChan)
// eliminate duplicate values
dedupe := server.NewDeduplicator(30*time.Minute, "socCharge")
teeChan = dedupe.Pipe(teeChan)
pipeChan := dedupe.Pipe(tee.Attach())
// reduce number of values written to influx
limiter := server.NewLimiter(5 * time.Second)
teeChan = limiter.Pipe(teeChan)
pipeChan = limiter.Pipe(pipeChan)
go influx.Run(teeChan)
go influx.Run(pipeChan)
}
// create webserver
socketHub := server.NewSocketHub()
httpd := server.NewHTTPd(uri, conf.Menu, loadPoints[0], socketHub)
var teeChan <-chan core.Param
valueChan, teeChan = tee(valueChan)
triggerChan := make(chan struct{})
go socketHub.Run(teeChan, triggerChan)
// handle UI update requests whenever browser connects
go handleUI(triggerChan, loadPoints)
// publish to UI
go socketHub.Run(tee.Attach(), triggerChan)
// setup values channel
valueChan := make(chan util.Param)
go tee.Run(valueChan)
// start all loadpoints
for _, lp := range loadPoints {
@ -217,14 +208,5 @@ func run(cmd *cobra.Command, args []string) {
go lp.Run(conf.Interval)
}
// handle UI update requests whenever browser connects
go func() {
for range triggerChan {
for _, lp := range loadPoints {
lp.Update()
}
}
}()
log.FATAL.Println(httpd.ListenAndServe())
}

29
cmd/tee.go Normal file
View file

@ -0,0 +1,29 @@
package cmd
import "github.com/andig/evcc/util"
// Tee distributed parameters to subscribers
type Tee struct {
recv []chan<- util.Param
}
// Attach creates a new receiver channel and attaches it to the tee
func (t *Tee) Attach() <-chan util.Param {
out := make(chan util.Param)
t.Add(out)
return out
}
// Add attaches a receiver channel to the tee
func (t *Tee) Add(out chan<- util.Param) {
t.recv = append(t.recv, out)
}
// Run starts parameter distribution
func (t *Tee) Run(in <-chan util.Param) {
for msg := range in {
for _, recv := range t.recv {
recv <- msg
}
}
}

View file

@ -14,13 +14,6 @@ func (n *nilVal) String() string {
return "—"
}
// Param is the broadcast channel data type
type Param struct {
LoadPoint string
Key string
Val interface{}
}
// Configuration is the loadpoint feature structure
type Configuration struct {
Mode string `json:"mode"`

View file

@ -50,7 +50,7 @@ type Config struct {
Enable, Disable ThresholdConfig
}
// ThresholdConfig defines enable/disable hysteresis paramters
// ThresholdConfig defines enable/disable hysteresis parameters
type ThresholdConfig struct {
Delay time.Duration
Threshold float64
@ -72,7 +72,7 @@ type LoadPoint struct {
bus evbus.Bus // event bus
triggerChan chan struct{} // API updates
notificationChan chan<- push.Event // notifications
uiChan chan<- Param // client push messages
uiChan chan<- util.Param // client push messages
Config `mapstructure:",squash"` // exposed public configuration
ChargerHandler `mapstructure:",squash"` // handle charger state and current
@ -169,7 +169,7 @@ func (lp *LoadPoint) notify(event string, attributes map[string]interface{}) {
// publish sends values to UI and databases
func (lp *LoadPoint) publish(key string, val interface{}) {
lp.uiChan <- Param{
lp.uiChan <- util.Param{
LoadPoint: lp.Name,
Key: key,
Val: val,
@ -228,7 +228,7 @@ func (lp *LoadPoint) evChargeCurrentHandler(current int64) {
}
// Prepare loadpoint configuration by adding missing helper elements
func (lp *LoadPoint) Prepare(uiChan chan<- Param, notificationChan chan<- push.Event) {
func (lp *LoadPoint) Prepare(uiChan chan<- util.Param, notificationChan chan<- push.Event) {
lp.notificationChan = notificationChan
lp.uiChan = uiChan

View file

@ -10,6 +10,7 @@ import (
"github.com/andig/evcc/mock"
"github.com/andig/evcc/provider"
"github.com/andig/evcc/push"
"github.com/andig/evcc/util"
"github.com/benbjohnson/clock"
"github.com/golang/mock/gomock"
)
@ -66,7 +67,7 @@ func newLoadPoint(charger api.Charger, pv, gm, cm api.Meter) *LoadPoint {
lp.chargeMeter = cm
}
uiChan := make(chan Param)
uiChan := make(chan util.Param)
notificationChan := make(chan push.Event)
lp.Prepare(uiChan, notificationChan)

View file

@ -4,7 +4,6 @@ import (
"sync"
"time"
"github.com/andig/evcc/core"
"github.com/andig/evcc/util"
influxdb "github.com/influxdata/influxdb1-client/v2"
)
@ -131,7 +130,7 @@ func (m *Influx) asyncWriter(exit <-chan struct{}) <-chan struct{} {
}
// Run Influx publisher
func (m *Influx) Run(in <-chan core.Param) {
func (m *Influx) Run(in <-chan util.Param) {
exit := make(chan struct{}) // exit signals to stop writer
done := m.asyncWriter(exit) // done signals writer stopped

View file

@ -3,13 +3,13 @@ package server
import (
"time"
"github.com/andig/evcc/core"
"github.com/andig/evcc/util"
"github.com/benbjohnson/clock"
)
// Piper is the interface that data flow plugins must implement
type Piper interface {
Pipe(in <-chan core.Param) <-chan core.Param
Pipe(in <-chan util.Param) <-chan util.Param
}
type cacheItem struct {
@ -41,7 +41,7 @@ func NewDeduplicator(interval time.Duration, filter ...string) Piper {
return l
}
func (l *Deduplicator) pipe(in <-chan core.Param, out chan<- core.Param) {
func (l *Deduplicator) pipe(in <-chan util.Param, out chan<- util.Param) {
for p := range in {
// use loadpoint + param.Key as lookup key to value cache
key := p.LoadPoint + "." + p.Key
@ -58,8 +58,8 @@ func (l *Deduplicator) pipe(in <-chan core.Param, out chan<- core.Param) {
}
// Pipe creates a new filtered output channel for given input channel
func (l *Deduplicator) Pipe(in <-chan core.Param) <-chan core.Param {
out := make(chan core.Param)
func (l *Deduplicator) Pipe(in <-chan util.Param) <-chan util.Param {
out := make(chan util.Param)
go l.pipe(in, out)
return out
}
@ -82,7 +82,7 @@ func NewLimiter(interval time.Duration) Piper {
return l
}
func (l *Limiter) pipe(in <-chan core.Param, out chan<- core.Param) {
func (l *Limiter) pipe(in <-chan util.Param, out chan<- util.Param) {
for p := range in {
// use loadpoint + param.Key as lookup key to value cache
key := p.LoadPoint + "." + p.Key
@ -97,8 +97,8 @@ func (l *Limiter) pipe(in <-chan core.Param, out chan<- core.Param) {
}
// Pipe creates a new filtered output channel for given input channel
func (l *Limiter) Pipe(in <-chan core.Param) <-chan core.Param {
out := make(chan core.Param)
func (l *Limiter) Pipe(in <-chan util.Param) <-chan util.Param {
out := make(chan util.Param)
go l.pipe(in, out)
return out
}

View file

@ -5,7 +5,7 @@ import (
"testing"
"time"
"github.com/andig/evcc/core"
"github.com/andig/evcc/util"
"github.com/benbjohnson/clock"
)
@ -14,10 +14,10 @@ func TestLimiter(t *testing.T) {
clck := clock.NewMock()
l.clock = clck
in := make(chan core.Param)
in := make(chan util.Param)
out := l.Pipe(in)
p := core.Param{Key: "k", Val: 1}
p := util.Param{Key: "k", Val: 1}
in <- p
if o := <-out; o.Key != p.Key || o.Val != p.Val {
@ -57,17 +57,17 @@ func TestDeduplicator(t *testing.T) {
clck := clock.NewMock()
l.clock = clck
in := make(chan core.Param)
in := make(chan util.Param)
out := l.Pipe(in)
p := core.Param{Key: "k", Val: 1}
p := util.Param{Key: "k", Val: 1}
in <- p
if o := <-out; o.Key != p.Key || o.Val != p.Val {
t.Errorf("unexpected param %v", o)
}
p = core.Param{Key: "k", Val: 2}
p = util.Param{Key: "k", Val: 2}
in <- p
if o := <-out; o.Key != p.Key || o.Val != p.Val {
@ -75,21 +75,21 @@ func TestDeduplicator(t *testing.T) {
}
// allow nils
p = core.Param{Key: "k", Val: nil}
p = util.Param{Key: "k", Val: nil}
in <- p
if o := <-out; o.Key != p.Key || o.Val != p.Val {
t.Errorf("unexpected param %v", o)
}
p = core.Param{Key: "filtered", Val: 3}
p = util.Param{Key: "filtered", Val: 3}
in <- p
if o := <-out; o.Key != p.Key || o.Val != p.Val {
t.Errorf("unexpected param %v", o)
}
p = core.Param{Key: "filtered", Val: 4}
p = util.Param{Key: "filtered", Val: 4}
in <- p
if o := <-out; o.Key != p.Key || o.Val != p.Val {
@ -115,7 +115,7 @@ func TestDeduplicator(t *testing.T) {
}
// allow nils
p = core.Param{Key: "filtered", Val: nil}
p = util.Param{Key: "filtered", Val: nil}
in <- p
if o := <-out; o.Key != p.Key || o.Val != p.Val {

View file

@ -5,7 +5,7 @@ import (
"net/http"
"time"
"github.com/andig/evcc/core"
"github.com/andig/evcc/util"
"github.com/gorilla/websocket"
)
@ -98,7 +98,7 @@ func encode(v interface{}) (string, error) {
return s, nil
}
func (h *SocketHub) broadcast(i core.Param) {
func (h *SocketHub) broadcast(i util.Param) {
if len(h.clients) > 0 {
val, err := encode(i.Val)
if err != nil {
@ -118,7 +118,7 @@ func (h *SocketHub) broadcast(i core.Param) {
}
// Run starts data and status distribution
func (h *SocketHub) Run(in <-chan core.Param, triggerChan chan<- struct{}) {
func (h *SocketHub) Run(in <-chan util.Param, triggerChan chan<- struct{}) {
for {
select {
case client := <-h.register:

8
util/param.go Normal file
View file

@ -0,0 +1,8 @@
package util
// Param is the broadcast channel data type
type Param struct {
LoadPoint string
Key string
Val interface{}
}