Refactor to support Wallbe dip 10 enable/disable

This commit is contained in:
andig 2020-03-18 16:16:35 +01:00
parent 3c503b333d
commit da74ce17fd
48 changed files with 1274 additions and 1393 deletions

View file

@ -32,11 +32,12 @@ type mqttConfig struct {
}
type providerConfig struct {
Type string
Topic string
Cmd string
Timeout time.Duration
Cache time.Duration
Type string
Topic string
Cmd string
Multiplier float64
Timeout time.Duration
Cache time.Duration
}
type meterConfig struct {
@ -54,11 +55,10 @@ type chargerConfig struct {
URI string
// composite charger
Status *providerConfig // Charger
ActualCurrent *providerConfig // Charger
MaxCurrent *providerConfig // ChargeController
Enable *providerConfig // Charger
Enabled *providerConfig // Charger
Status *providerConfig // Charger
MaxCurrent *providerConfig // ChargeController
Enable *providerConfig // Charger
Enabled *providerConfig // Charger
}
type socConfig struct {
@ -80,6 +80,8 @@ type loadPointConfig struct {
Phases int64
MinCurrent int64
MaxCurrent int64
Steepness int64
GuardDuration time.Duration
Voltage float64
ResidualPower float64
}

View file

@ -49,37 +49,16 @@ func boolGetter(pc *providerConfig) (res provider.BoolGetter) {
return
}
func intProvider(pc *providerConfig) (res provider.IntGetter) {
switch pc.Type {
case "mqtt":
if pc.Timeout == 0 {
pc.Timeout = mqttTimeout
}
res = mq.IntGetter(pc.Topic, pc.Timeout)
case "script":
if pc.Timeout == 0 {
pc.Timeout = execTimeout
}
exec := provider.NewScriptProvider(pc.Timeout)
res = exec.IntGetter(pc.Cmd)
default:
log.FATAL.Printf("invalid provider type %s", pc.Type)
}
if pc.Cache > 0 {
res = provider.NewCacheGetter(res, pc.Cache).IntGetter
}
return
}
func floatGetter(pc *providerConfig) (res provider.FloatGetter) {
switch pc.Type {
case "mqtt":
if pc.Timeout == 0 {
pc.Timeout = mqttTimeout
}
res = mq.FloatGetter(pc.Topic, pc.Timeout)
if pc.Multiplier == 0 {
pc.Multiplier = 1
}
res = mq.FloatGetter(pc.Topic, pc.Multiplier, pc.Timeout)
case "script":
if pc.Timeout == 0 {
pc.Timeout = execTimeout

View file

@ -21,32 +21,22 @@ meters:
if err := viper.ReadConfig(bytes.NewBuffer([]byte(yaml))); err != nil {
t.Error(err)
}
// var conf Config
// if err := viper.UnmarshalExact(&conf); err != nil {
// t.Error(err)
// }
// _ = configureMeters(conf)
}
func TestChargerConfig(t *testing.T) {
yaml := `
chargers:
- name: test
type: configurable
status:
status:
type: script
cmd: script
actualCurrent:
enable:
type: script
cmd: script
enable:
enabled:
type: script
cmd: script
enabled:
type: script
cmd: script
maxCurrent:
maxCurrent:
type: script
cmd: script
- name: wallbe
@ -57,11 +47,4 @@ chargers:
if err := viper.ReadConfig(bytes.NewBuffer([]byte(yaml))); err != nil {
t.Error(err)
}
// var conf Config
// if err := viper.UnmarshalExact(&conf); err != nil {
// t.Error(err)
// }
// _ = configureChargers(conf)
}

View file

@ -1,13 +1,13 @@
package cmd
import (
"context"
"fmt"
"os"
"time"
"github.com/andig/evcc/api"
"github.com/andig/evcc/core"
"github.com/andig/evcc/push"
"github.com/andig/evcc/server"
"github.com/spf13/cobra"
@ -115,17 +115,10 @@ func Execute() {
}
func configureLogging(level string) {
// f, err := os.OpenFile("evcc.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
// if err != nil {
// log.FATAL.Fatal(err)
// }
api.OutThreshold = api.LogLevelToThreshold(level)
api.LogThreshold = api.OutThreshold
api.Loggers(func(name string, logger *api.Logger) {
logger.SetStdoutThreshold(api.OutThreshold)
// logger.SetLogThreshold(api.OutThreshold)
// logger.SetLogOutput(f)
})
}
@ -143,15 +136,6 @@ func checkVersion() {
}
}
// cycle executes loadpoint update and publishes all paramters
func cycle(lp *core.LoadPoint, tick time.Duration, updateChan chan core.Param) {
lp.Update()
ctx, cancel := context.WithTimeout(context.Background(), tick)
lp.Publish(ctx, updateChan)
cancel()
}
func run(cmd *cobra.Command, args []string) {
level, _ := cmd.PersistentFlags().GetString("log")
configureLogging(level)
@ -174,35 +158,42 @@ func run(cmd *cobra.Command, args []string) {
uri := viper.GetString("uri")
log.INFO.Println("listening at", uri)
loadPoints := loadConfig(conf)
// setup messaging
// if conf.Pushover.App != "" {
// po := server.NewMessenger(conf.Pushover.App, conf.Pushover.Recipients)
// po.Send("Wallbe", "Charge started", "Charging started in PV mode")
// }
notificationChan := make(chan push.Event, 1)
notificationHub := &push.Hub{}
if conf.Pushover.App != "" {
notificationHub.PushOver = push.NewMessenger(conf.Pushover.App, conf.Pushover.Recipients)
}
loadPoints := loadConfig(conf, notificationChan)
go notificationHub.Run(notificationChan)
// create webserver
hub := server.NewSocketHub()
httpd := server.NewHttpd(uri, conf.Menu, loadPoints[0], hub)
socketHub := server.NewSocketHub()
httpd := server.NewHttpd(uri, conf.Menu, loadPoints[0], socketHub)
// start broadcasting values
updateChan := make(chan core.Param)
go hub.Run(updateChan)
uiChan := make(chan core.Param)
triggerChan := make(chan struct{})
go socketHub.Run(uiChan, triggerChan)
// start all loadpoints
for _, lp := range loadPoints {
lp.Dump()
// update loop
go func(lp *core.LoadPoint) {
tick := conf.Interval
cycle(lp, tick, updateChan)
for range time.Tick(tick) {
cycle(lp, tick, updateChan)
}
}(lp)
lp.Prepare(uiChan, notificationChan)
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())
}

View file

@ -6,9 +6,11 @@ import (
"time"
"github.com/andig/evcc/api"
"github.com/andig/evcc/charger"
"github.com/andig/evcc/core"
"github.com/andig/evcc/core/wrapper"
"github.com/andig/evcc/provider"
"github.com/andig/evcc/push"
"github.com/spf13/viper"
)
@ -27,10 +29,9 @@ func clientID() string {
func configureMeters(conf config) (meters map[string]api.Meter) {
meters = make(map[string]api.Meter)
for _, mc := range conf.Meters {
m := core.NewMeter(
floatGetter(mc.Power),
)
m := core.NewMeter(floatGetter(mc.Power))
// decorate Meter with MeterEnergy
if mc.Energy != nil {
m = &wrapper.CompositeMeter{
Meter: m,
@ -49,25 +50,16 @@ func configureChargers(conf config) (chargers map[string]api.Charger) {
switch cc.Type {
case "wallbe":
c = core.NewWallbe(cc.URI)
c = charger.NewWallbe(cc.URI)
case "default", "configurable":
c = core.NewCharger(
c = charger.NewCharger(
stringGetter(cc.Status),
intProvider(cc.ActualCurrent),
boolGetter(cc.Enabled),
boolSetter("enable", cc.Enable),
intSetter("current", cc.MaxCurrent),
)
// if chargecontroller specified build composite charger
if cc.MaxCurrent != nil {
c = &wrapper.CompositeCharger{
Charger: c,
ChargeController: core.NewChargeController(
intSetter("current", cc.MaxCurrent),
),
}
}
default:
log.FATAL.Fatalf("invalid charger type '%s'", cc.Type)
}
@ -80,39 +72,46 @@ func configureChargers(conf config) (chargers map[string]api.Charger) {
func configureSoCs(conf config) (socs map[string]api.SoC) {
socs = make(map[string]api.SoC)
for _, sc := range conf.SoCs {
soc := core.NewSoC(
sc.Capacity,
sc.Title,
floatGetter(sc.Charge),
)
soc := core.NewSoC(sc.Capacity, sc.Title, floatGetter(sc.Charge))
socs[sc.Name] = soc
}
return
}
func configureLoadPoint(lp *core.LoadPoint, lpc loadPointConfig) {
// TODO rewrite based on https://github.com/spf13/viper/pull/863
func configureLoadPoint(lp *core.LoadPoint, lpc loadPointConfig, subv *viper.Viper) {
// for _, key := range []string{"charger", "gridmeter", "pvmeter", "chargemeter", "soc"} {
// delete(kv, key)
// }
// config := &mapstructure.DecoderConfig{
// WeaklyTypedInput: true,
// DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
// Result: lp,
// }
// decoder, err := mapstructure.NewDecoder(config)
// if err != nil {
// log.FATAL.Fatalf("configuring loadpoints failed: %v", err)
// }
// if err := decoder.Decode(kv); err != nil {
// log.FATAL.Fatalf("configuring loadpoints failed: %v", err)
// }
// we can ignore the error here as UnmarshalExact has been called before
_ = subv.UnmarshalExact(lp)
if lpc.Mode != "" {
lp.Synced().SetMode(api.ChargeMode(lpc.Mode))
}
if lpc.MinCurrent > 0 {
lp.MinCurrent = lpc.MinCurrent
}
if lpc.MaxCurrent > 0 {
lp.MaxCurrent = lpc.MaxCurrent
}
if lpc.Voltage > 0 {
lp.Voltage = lpc.Voltage
}
if lpc.Phases > 0 {
lp.Phases = lpc.Phases
}
if lpc.ResidualPower != 0 {
lp.ResidualPower = lpc.ResidualPower
// workaround for golangs yaml off=0 conversion
if lpc.Mode == "0" {
lpc.Mode = api.ModeOff
}
lp.Mode = lpc.Mode // don't use SetMode here as that will block on channel send
}
}
func loadConfig(conf config) (loadPoints []*core.LoadPoint) {
func loadConfig(conf config, eventsChan chan push.Event) (loadPoints []*core.LoadPoint) {
if viper.Get("mqtt") != nil {
mq = provider.NewMqttClient(conf.Mqtt.Broker, conf.Mqtt.User, conf.Mqtt.Password, clientID(), 1)
}
@ -121,15 +120,18 @@ func loadConfig(conf config) (loadPoints []*core.LoadPoint) {
chargers := configureChargers(conf)
socs := configureSoCs(conf)
for _, lpc := range conf.LoadPoints {
charger, ok := chargers[lpc.Charger]
if !ok {
for idx, lpc := range conf.LoadPoints {
// configure loadpoint
lp := core.NewLoadPoint()
subv := viper.SubSlice("loadpoints")[idx]
configureLoadPoint(lp, lpc, subv)
// assign charger
if charger, ok := chargers[lpc.Charger]; ok {
lp.Charger = charger
} else {
log.FATAL.Fatalf("invalid charger '%s'", lpc.Charger)
}
lp := core.NewLoadPoint(
lpc.Name,
charger,
)
// assign meters
for _, m := range []struct {
@ -158,12 +160,6 @@ func loadConfig(conf config) (loadPoints []*core.LoadPoint) {
}
}
// assign remaing config
configureLoadPoint(lp, lpc)
// add loadpoint helper objects
lp.Prepare()
loadPoints = append(loadPoints, lp)
}