diff --git a/charger/openwb.go b/charger/openwb.go index 2da6cd7c69..62d1db99c2 100644 --- a/charger/openwb.go +++ b/charger/openwb.go @@ -2,6 +2,7 @@ package charger import ( "fmt" + "strconv" "strings" "time" @@ -62,27 +63,34 @@ func NewOpenWB(log *util.Logger, mqttconf mqtt.Config, id int, topic string, p1p } // timeout handler - to := provider.NewTimeoutHandler(provider.NewMqtt(log, client, fmt.Sprintf("%s/system/%s", topic, openwb.TimestampTopic), timeout).StringGetter()) - - boolG := func(topic string) func() (bool, error) { - g := provider.NewMqtt(log, client, topic, 0).BoolGetter() - return to.BoolGetter(g) + h, err := provider.NewMqtt(log, client, fmt.Sprintf("%s/system/%s", topic, openwb.TimestampTopic), timeout).StringGetter() + if err != nil { + return nil, err } + to := provider.NewTimeoutHandler(h) - floatG := func(topic string) func() (float64, error) { - g := provider.NewMqtt(log, client, topic, 0).FloatGetter() - return to.FloatGetter(g) + mq := func(subtopic string) *provider.Mqtt { + return provider.NewMqtt(log, client, fmt.Sprintf("%s/lp/%d/%s", topic, id, subtopic), 0) } // check if loadpoint configured - configured := boolG(fmt.Sprintf("%s/lp/%d/%s", topic, id, openwb.ConfiguredTopic)) + configured, err := to.BoolGetter(mq(openwb.ConfiguredTopic)) + if err != nil { + return nil, err + } if isConfigured, err := configured(); err != nil || !isConfigured { return nil, fmt.Errorf("loadpoint %d is not configured", id) } // adapt plugged/charging to status - pluggedG := boolG(fmt.Sprintf("%s/lp/%d/%s", topic, id, openwb.PluggedTopic)) - chargingG := boolG(fmt.Sprintf("%s/lp/%d/%s", topic, id, openwb.ChargingTopic)) + pluggedG, err := to.BoolGetter(mq(openwb.PluggedTopic)) + if err != nil { + return nil, err + } + chargingG, err := to.BoolGetter(mq(openwb.ChargingTopic)) + if err != nil { + return nil, err + } statusG := provider.NewOpenWBStatusProvider(pluggedG, chargingG).StringGetter // setters @@ -91,27 +99,45 @@ func NewOpenWB(log *util.Logger, mqttconf mqtt.Config, id int, topic string, p1p // TODO remove after https://github.com/snaptec/openWB/issues/1757 currentTopic = "Lp2" + openwb.SlaveChargeCurrentTopic } - currentS := provider.NewMqtt(log, client, fmt.Sprintf("%s/set/isss/%s", topic, currentTopic), + currentS, err := provider.NewMqtt(log, client, fmt.Sprintf("%s/set/isss/%s", topic, currentTopic), timeout).WithRetained().IntSetter("current") + if err != nil { + return nil, err + } cpTopic := openwb.SlaveCPInterruptTopic if id == 2 { // TODO remove after https://github.com/snaptec/openWB/issues/1757 cpTopic = strings.TrimSuffix(cpTopic, "1") + "2" } - wakeupS := provider.NewMqtt(log, client, fmt.Sprintf("%s/set/isss/%s", topic, cpTopic), + wakeupS, err := provider.NewMqtt(log, client, fmt.Sprintf("%s/set/isss/%s", topic, cpTopic), timeout).WithRetained().IntSetter("cp") + if err != nil { + return nil, err + } - authS := provider.NewMqtt(log, client, fmt.Sprintf("%s/set/chargepoint/%d/set/%s", topic, id, openwb.RfidTopic), + authS, err := provider.NewMqtt(log, client, fmt.Sprintf("%s/set/chargepoint/%d/set/%s", topic, id, openwb.RfidTopic), timeout).WithRetained().StringSetter("rfid") + if err != nil { + return nil, err + } // meter getters - currentPowerG := floatG(fmt.Sprintf("%s/lp/%d/%s", topic, id, openwb.ChargePowerTopic)) - totalEnergyG := floatG(fmt.Sprintf("%s/lp/%d/%s", topic, id, openwb.ChargeTotalEnergyTopic)) + currentPowerG, err := to.FloatGetter(mq(openwb.ChargePowerTopic)) + if err != nil { + return nil, err + } + totalEnergyG, err := to.FloatGetter(mq(openwb.ChargeTotalEnergyTopic)) + if err != nil { + return nil, err + } var currentsG []func() (float64, error) for i := 1; i <= 3; i++ { - current := floatG(fmt.Sprintf("%s/lp/%d/%s%d", topic, id, openwb.CurrentTopic, i)) + current, err := to.FloatGetter(mq(openwb.CurrentTopic + strconv.Itoa(i))) + if err != nil { + return nil, err + } currentsG = append(currentsG, current) } @@ -126,10 +152,13 @@ func NewOpenWB(log *util.Logger, mqttconf mqtt.Config, id int, topic string, p1p } // heartbeat - go func() { - heartbeatS := provider.NewMqtt(log, client, fmt.Sprintf("%s/set/isss/%s", topic, openwb.SlaveHeartbeatTopic), - timeout).WithRetained().IntSetter("heartbeat") + heartbeatS, err := provider.NewMqtt(log, client, fmt.Sprintf("%s/set/isss/%s", topic, openwb.SlaveHeartbeatTopic), + timeout).WithRetained().IntSetter("heartbeat") + if err != nil { + return nil, err + } + go func() { for range time.Tick(openwb.HeartbeatInterval) { if err := heartbeatS(1); err != nil { log.ERROR.Printf("heartbeat: %v", err) @@ -146,8 +175,11 @@ func NewOpenWB(log *util.Logger, mqttconf mqtt.Config, id int, topic string, p1p // TODO remove after https://github.com/snaptec/openWB/issues/1757 phasesTopic += "Lp2" } - phasesS := provider.NewMqtt(log, client, fmt.Sprintf("%s/set/isss/%s", topic, phasesTopic), + phasesS, err := provider.NewMqtt(log, client, fmt.Sprintf("%s/set/isss/%s", topic, phasesTopic), timeout).WithRetained().IntSetter("phases") + if err != nil { + return nil, err + } phases = func(phases int) error { return phasesS(int64(phases)) @@ -156,7 +188,10 @@ func NewOpenWB(log *util.Logger, mqttconf mqtt.Config, id int, topic string, p1p var soc func() (float64, error) if dc { - soc = floatG(fmt.Sprintf("%s/lp/%d/%s", topic, id, openwb.VehicleSocTopic)) + soc, err = to.FloatGetter(mq(openwb.VehicleSocTopic)) + if err != nil { + return nil, err + } } return decorateOpenWB(c, phases, soc), nil diff --git a/charger/warp2.go b/charger/warp2.go index 29a0f62150..cd405eb090 100644 --- a/charger/warp2.go +++ b/charger/warp2.go @@ -25,11 +25,10 @@ type Warp2 struct { meterDetailsG func() (string, error) chargeG func() (string, error) userconfigG func() (string, error) + emStateG func() (string, error) maxcurrentS func(int64) error - // emConfigG func() (string, error) - emStateG func() (string, error) - phasesS func(int64) error - current int64 + phasesS func(int64) error + current int64 } func init() { @@ -103,33 +102,60 @@ func NewWarp2(mqttconf mqtt.Config, topic, emTopic string, timeout time.Duration } // timeout handler - to := provider.NewTimeoutHandler(provider.NewMqtt(log, client, - fmt.Sprintf("%s/evse/low_level_state", topic), timeout, - ).StringGetter()) + h, err := provider.NewMqtt(log, client, fmt.Sprintf("%s/evse/low_level_state", topic), timeout).StringGetter() + if err != nil { + return nil, err + } + to := provider.NewTimeoutHandler(h) - stringG := func(topic string) func() (string, error) { - g := provider.NewMqtt(log, client, topic, 0).StringGetter() - return to.StringGetter(g) + mq := func(s string, args ...any) *provider.Mqtt { + return provider.NewMqtt(log, client, fmt.Sprintf(s, args...), 0) } - wb.maxcurrentG = stringG(fmt.Sprintf("%s/evse/external_current", topic)) - wb.statusG = stringG(fmt.Sprintf("%s/evse/state", topic)) - wb.meterG = stringG(fmt.Sprintf("%s/meter/values", topic)) - wb.meterDetailsG = stringG(fmt.Sprintf("%s/meter/all_values", topic)) - wb.chargeG = stringG(fmt.Sprintf("%s/charge_tracker/current_charge", topic)) - wb.userconfigG = stringG(fmt.Sprintf("%s/users/config", topic)) + wb.maxcurrentG, err = to.StringGetter(mq("%s/evse/external_current", topic)) + if err != nil { + return nil, err + } + wb.statusG, err = to.StringGetter(mq("%s/evse/state", topic)) + if err != nil { + return nil, err + } + wb.meterG, err = to.StringGetter(mq("%s/meter/values", topic)) + if err != nil { + return nil, err + } + wb.meterDetailsG, err = to.StringGetter(mq("%s/meter/all_values", topic)) + if err != nil { + return nil, err + } + wb.chargeG, err = to.StringGetter(mq("%s/charge_tracker/current_charge", topic)) + if err != nil { + return nil, err + } + wb.userconfigG, err = to.StringGetter(mq("%s/users/config", topic)) + if err != nil { + return nil, err + } - wb.maxcurrentS = provider.NewMqtt(log, client, + wb.maxcurrentS, err = provider.NewMqtt(log, client, fmt.Sprintf("%s/evse/external_current_update", topic), 0). WithPayload(`{ "current": ${maxcurrent} }`). IntSetter("maxcurrent") + if err != nil { + return nil, err + } - // wb.emConfigG = stringG(fmt.Sprintf("%s/energy_manager/config", emTopic)) - wb.emStateG = stringG(fmt.Sprintf("%s/energy_manager/state", emTopic)) - wb.phasesS = provider.NewMqtt(log, client, + wb.emStateG, err = to.StringGetter(mq("%s/energy_manager/state", emTopic)) + if err != nil { + return nil, err + } + wb.phasesS, err = provider.NewMqtt(log, client, fmt.Sprintf("%s/energy_manager/external_control_update", emTopic), 0). WithPayload(`{ "phases_wanted": ${phases} }`). IntSetter("phases") + if err != nil { + return nil, err + } return wb, nil } @@ -141,9 +167,11 @@ func (wb *Warp2) hasFeature(root, feature string) bool { topic := fmt.Sprintf("%s/info/features", root) - if data, err := provider.NewMqtt(wb.log, wb.client, topic, 0).StringGetter()(); err == nil { - if err := json.Unmarshal([]byte(data), &wb.features); err == nil { - return slices.Contains(wb.features, feature) + if dataG, err := provider.NewMqtt(wb.log, wb.client, topic, 0).StringGetter(); err == nil { + if data, err := dataG(); err == nil { + if err := json.Unmarshal([]byte(data), &wb.features); err == nil { + return slices.Contains(wb.features, feature) + } } } @@ -289,17 +317,6 @@ func (wb *Warp2) identify() (string, error) { return res.AuthorizationInfo.TagId, err } -// func (wb *Warp2) emConfig() (warp.EmConfig, error) { -// var res warp.EmConfig - -// s, err := wb.emConfigG() -// if err == nil { -// err = json.Unmarshal([]byte(s), &res) -// } - -// return res, err -// } - func (wb *Warp2) emState() (warp.EmState, error) { var res warp.EmState diff --git a/meter/openwb.go b/meter/openwb.go index 7ed61d54b2..3f62347779 100644 --- a/meter/openwb.go +++ b/meter/openwb.go @@ -42,18 +42,14 @@ func NewOpenWBFromConfig(other map[string]interface{}) (api.Meter, error) { } // timeout handler - to := provider.NewTimeoutHandler(provider.NewMqtt(log, client, - fmt.Sprintf("%s/system/%s", cc.Topic, openwb.TimestampTopic), cc.Timeout, - ).StringGetter()) - - boolG := func(topic string) func() (bool, error) { - g := provider.NewMqtt(log, client, topic, 0).BoolGetter() - return to.BoolGetter(g) + h, err := provider.NewMqtt(log, client, fmt.Sprintf("%s/system/%s", cc.Topic, openwb.TimestampTopic), cc.Timeout).StringGetter() + if err != nil { + return nil, err } + to := provider.NewTimeoutHandler(h) - floatG := func(topic string) func() (float64, error) { - g := provider.NewMqtt(log, client, topic, 0).FloatGetter() - return to.FloatGetter(g) + mq := func(s string, args ...any) *provider.Mqtt { + return provider.NewMqtt(log, client, fmt.Sprintf(s, args...), 0) } var power func() (float64, error) @@ -63,18 +59,27 @@ func NewOpenWBFromConfig(other map[string]interface{}) (api.Meter, error) { switch strings.ToLower(cc.Usage) { case "grid": - power = floatG(fmt.Sprintf("%s/evu/%s", cc.Topic, openwb.PowerTopic)) + power, err = to.FloatGetter(mq("%s/evu/%s", cc.Topic, openwb.PowerTopic)) + if err != nil { + return nil, err + } var curr []func() (float64, error) for i := 1; i <= 3; i++ { - current := floatG(fmt.Sprintf("%s/evu/%s%d", cc.Topic, openwb.CurrentTopic, i)) + current, err := to.FloatGetter(mq("%s/evu/%s%d", cc.Topic, openwb.CurrentTopic, i)) + if err != nil { + return nil, err + } curr = append(curr, current) } currents = collectPhaseProviders(curr) case "pv": - configuredG := boolG(fmt.Sprintf("%s/pv/1/%s", cc.Topic, openwb.PvConfigured)) // first pv + configuredG, err := to.BoolGetter(mq("%s/pv/1/%s", cc.Topic, openwb.PvConfigured)) // first pv + if err != nil { + return nil, err + } configured, err := configuredG() if err != nil { return nil, err @@ -84,14 +89,20 @@ func NewOpenWBFromConfig(other map[string]interface{}) (api.Meter, error) { return nil, errors.New("pv not available") } - g := floatG(fmt.Sprintf("%s/pv/%s", cc.Topic, openwb.PowerTopic)) + g, err := to.FloatGetter(mq("%s/pv/%s", cc.Topic, openwb.PowerTopic)) + if err != nil { + return nil, err + } power = func() (float64, error) { f, err := g() return -f, err } case "battery": - configuredG := boolG(fmt.Sprintf("%s/housebattery/%s", cc.Topic, openwb.BatteryConfigured)) + configuredG, err := to.BoolGetter(mq("%s/housebattery/%s", cc.Topic, openwb.BatteryConfigured)) + if err != nil { + return nil, err + } configured, err := configuredG() if err != nil { return nil, err @@ -101,12 +112,20 @@ func NewOpenWBFromConfig(other map[string]interface{}) (api.Meter, error) { return nil, errors.New("battery not available") } - inner := floatG(fmt.Sprintf("%s/housebattery/%s", cc.Topic, openwb.PowerTopic)) + inner, err := to.FloatGetter(mq("%s/housebattery/%s", cc.Topic, openwb.PowerTopic)) + if err != nil { + return nil, err + } power = func() (float64, error) { f, err := inner() return -f, err } - soc = floatG(fmt.Sprintf("%s/housebattery/%s", cc.Topic, openwb.SocTopic)) + + soc, err = to.FloatGetter(mq("%s/housebattery/%s", cc.Topic, openwb.SocTopic)) + if err != nil { + return nil, err + } + capacity = cc.capacity.Decorator() default: diff --git a/provider/calc.go b/provider/calc.go index 6fd81c7865..abf8e8f460 100644 --- a/provider/calc.go +++ b/provider/calc.go @@ -72,22 +72,28 @@ func NewCalcFromConfig(other map[string]interface{}) (Provider, error) { return o, nil } -func (o *calcProvider) IntGetter() func() (int64, error) { +var _ IntProvider = (*calcProvider)(nil) + +func (o *calcProvider) IntGetter() (func() (int64, error), error) { return func() (int64, error) { f, err := o.floatGetter() return int64(f), err - } + }, nil } -func (o *calcProvider) StringGetter() func() (string, error) { +var _ StringProvider = (*calcProvider)(nil) + +func (o *calcProvider) StringGetter() (func() (string, error), error) { return func() (string, error) { f, err := o.floatGetter() return fmt.Sprintf("%c", int(f)), err - } + }, nil } -func (o *calcProvider) FloatGetter() func() (float64, error) { - return o.floatGetter +var _ FloatProvider = (*calcProvider)(nil) + +func (o *calcProvider) FloatGetter() (func() (float64, error), error) { + return o.floatGetter, nil } func (o *calcProvider) floatGetter() (float64, error) { diff --git a/provider/combined.go b/provider/combined.go index 46301c4fe3..52abbc8389 100644 --- a/provider/combined.go +++ b/provider/combined.go @@ -20,8 +20,10 @@ func NewCombinedFromConfig(other map[string]interface{}) (Provider, error) { return o, nil } -func (o *combinedProvider) StringGetter() func() (string, error) { +var _ StringProvider = (*combinedProvider)(nil) + +func (o *combinedProvider) StringGetter() (func() (string, error), error) { return func() (string, error) { return o.status() - } + }, nil } diff --git a/provider/config.go b/provider/config.go index 6457d71b67..da01fca1dd 100644 --- a/provider/config.go +++ b/provider/config.go @@ -8,28 +8,28 @@ import ( type ( Provider interface{} IntProvider interface { - IntGetter() func() (int64, error) + IntGetter() (func() (int64, error), error) } StringProvider interface { - StringGetter() func() (string, error) + StringGetter() (func() (string, error), error) } FloatProvider interface { - FloatGetter() func() (float64, error) + FloatGetter() (func() (float64, error), error) } BoolProvider interface { - BoolGetter() func() (bool, error) + BoolGetter() (func() (bool, error), error) } SetIntProvider interface { - IntSetter(param string) func(int64) error + IntSetter(param string) (func(int64) error, error) } SetStringProvider interface { - StringSetter(param string) func(string) error + StringSetter(param string) (func(string) error, error) } SetFloatProvider interface { - FloatSetter(param string) func(float64) error + FloatSetter(param string) (func(float64) error, error) } SetBoolProvider interface { - BoolSetter(param string) func(bool) error + BoolSetter(param string) (func(bool) error, error) } ) @@ -75,7 +75,7 @@ func NewIntGetterFromConfig(config Config) (func() (int64, error), error) { return nil, fmt.Errorf("invalid plugin source for type int: %s", config.Source) } - return prov.IntGetter(), nil + return prov.IntGetter() } // NewFloatGetterFromConfig creates a FloatGetter from config @@ -95,7 +95,7 @@ func NewFloatGetterFromConfig(config Config) (func() (float64, error), error) { return nil, fmt.Errorf("invalid plugin source for type float: %s", config.Source) } - return prov.FloatGetter(), nil + return prov.FloatGetter() } // NewStringGetterFromConfig creates a StringGetter from config @@ -115,7 +115,7 @@ func NewStringGetterFromConfig(config Config) (func() (string, error), error) { return nil, fmt.Errorf("invalid plugin source for type string: %s", config.Source) } - return prov.StringGetter(), nil + return prov.StringGetter() } // NewBoolGetterFromConfig creates a BoolGetter from config @@ -135,7 +135,7 @@ func NewBoolGetterFromConfig(config Config) (func() (bool, error), error) { return nil, fmt.Errorf("invalid plugin source for type bool: %s", config.Source) } - return prov.BoolGetter(), nil + return prov.BoolGetter() } // NewIntSetterFromConfig creates a IntSetter from config @@ -155,7 +155,7 @@ func NewIntSetterFromConfig(param string, config Config) (func(int64) error, err return nil, fmt.Errorf("invalid plugin source for type int: %s", config.Source) } - return prov.IntSetter(param), nil + return prov.IntSetter(param) } // NewFloatSetterFromConfig creates a FloatSetter from config @@ -175,7 +175,7 @@ func NewFloatSetterFromConfig(param string, config Config) (func(float642 float6 return nil, fmt.Errorf("invalid plugin source for type float: %s", config.Source) } - return prov.FloatSetter(param), nil + return prov.FloatSetter(param) } // NewStringSetterFromConfig creates a StringSetter from config @@ -195,7 +195,7 @@ func NewStringSetterFromConfig(param string, config Config) (func(string) error, return nil, fmt.Errorf("invalid plugin source for type string: %s", config.Source) } - return prov.StringSetter(param), nil + return prov.StringSetter(param) } // NewBoolSetterFromConfig creates a BoolSetter from config @@ -215,5 +215,5 @@ func NewBoolSetterFromConfig(param string, config Config) (func(bool) error, err return nil, fmt.Errorf("invalid plugin source for type bool: %s", config.Source) } - return prov.BoolSetter(param), nil + return prov.BoolSetter(param) } diff --git a/provider/const.go b/provider/const.go index 6472eeb36b..69bf6bc7fd 100644 --- a/provider/const.go +++ b/provider/const.go @@ -43,22 +43,28 @@ func NewConstFromConfig(other map[string]interface{}) (Provider, error) { return o, nil } -func (o *constProvider) StringGetter() func() (string, error) { +var _ StringProvider = (*constProvider)(nil) + +func (o *constProvider) StringGetter() (func() (string, error), error) { return func() (string, error) { return o.str, nil - } + }, nil } -func (o *constProvider) IntGetter() func() (int64, error) { +var _ IntProvider = (*constProvider)(nil) + +func (o *constProvider) IntGetter() (func() (int64, error), error) { val, err := strconv.ParseInt(o.str, 10, 64) return func() (int64, error) { return val, err - } + }, err } -func (o *constProvider) FloatGetter() func() (float64, error) { +var _ FloatProvider = (*constProvider)(nil) + +func (o *constProvider) FloatGetter() (func() (float64, error), error) { val, err := strconv.ParseFloat(o.str, 64) return func() (float64, error) { return val, err - } + }, err } diff --git a/provider/float_to_int.go b/provider/float_to_int.go index 8e4af3dff3..b87f9dfc40 100644 --- a/provider/float_to_int.go +++ b/provider/float_to_int.go @@ -1,13 +1,11 @@ package provider import ( - "fmt" - "github.com/evcc-io/evcc/util" ) type floatToIntProvider struct { - set func(int64) error + Set Config } func init() { @@ -16,32 +14,21 @@ func init() { // NewFloatToIntFromConfig creates type conversion provider func NewFloatToIntFromConfig(other map[string]interface{}) (Provider, error) { - var cc struct { - Param string - Set Config - } + var cc floatToIntProvider if err := util.DecodeOther(other, &cc); err != nil { return nil, err } - // TODO late init - set, err := NewIntSetterFromConfig(cc.Param, cc.Set) - if err != nil { - return nil, fmt.Errorf("set: %w", err) - } - - o := &floatToIntProvider{ - set: set, - } - - return o, nil + return &cc, nil } var _ SetFloatProvider = (*floatToIntProvider)(nil) -func (o *floatToIntProvider) FloatSetter(param string) func(float64) error { +func (o *floatToIntProvider) FloatSetter(param string) (func(float64) error, error) { + set, err := NewIntSetterFromConfig(param, o.Set) + return func(val float64) error { - return o.set(int64(val)) - } + return set(int64(val)) + }, err } diff --git a/provider/go.go b/provider/go.go index 15b338e38e..fc4ca78c8a 100644 --- a/provider/go.go +++ b/provider/go.go @@ -58,8 +58,10 @@ func NewGoProviderFromConfig(other map[string]interface{}) (Provider, error) { return p, nil } +var _ FloatProvider = (*Go)(nil) + // FloatGetter parses float from request -func (p *Go) FloatGetter() func() (float64, error) { +func (p *Go) FloatGetter() (func() (float64, error), error) { return func() (float64, error) { v, err := p.handleGetter() if err != nil { @@ -72,11 +74,13 @@ func (p *Go) FloatGetter() func() (float64, error) { } return vv, nil - } + }, nil } +var _ IntProvider = (*Go)(nil) + // IntGetter parses int64 from request -func (p *Go) IntGetter() func() (int64, error) { +func (p *Go) IntGetter() (func() (int64, error), error) { return func() (int64, error) { v, err := p.handleGetter() if err != nil { @@ -89,11 +93,13 @@ func (p *Go) IntGetter() func() (int64, error) { } return vv, nil - } + }, nil } +var _ StringProvider = (*Go)(nil) + // StringGetter parses string from request -func (p *Go) StringGetter() func() (string, error) { +func (p *Go) StringGetter() (func() (string, error), error) { return func() (string, error) { v, err := p.handleGetter() if err != nil { @@ -106,11 +112,13 @@ func (p *Go) StringGetter() func() (string, error) { } return vv, nil - } + }, nil } +var _ BoolProvider = (*Go)(nil) + // BoolGetter parses bool from request -func (p *Go) BoolGetter() func() (bool, error) { +func (p *Go) BoolGetter() (func() (bool, error), error) { return func() (bool, error) { v, err := p.handleGetter() if err != nil { @@ -123,7 +131,7 @@ func (p *Go) BoolGetter() func() (bool, error) { } return vv, nil - } + }, nil } func (p *Go) handleGetter() (any, error) { @@ -165,30 +173,38 @@ func (p *Go) setParam(param string, val any) error { return err } +var _ SetIntProvider = (*Go)(nil) + // IntSetter sends int request -func (p *Go) IntSetter(param string) func(int64) error { +func (p *Go) IntSetter(param string) (func(int64) error, error) { return func(val int64) error { return p.handleSetter(param, val) - } + }, nil } +var _ SetFloatProvider = (*Go)(nil) + // FloatSetter sends float request -func (p *Go) FloatSetter(param string) func(float64) error { +func (p *Go) FloatSetter(param string) (func(float64) error, error) { return func(val float64) error { return p.handleSetter(param, val) - } + }, nil } +var _ SetStringProvider = (*Go)(nil) + // StringSetter sends string request -func (p *Go) StringSetter(param string) func(string) error { +func (p *Go) StringSetter(param string) (func(string) error, error) { return func(val string) error { return p.handleSetter(param, val) - } + }, nil } +var _ SetBoolProvider = (*Go)(nil) + // BoolSetter sends bool request -func (p *Go) BoolSetter(param string) func(bool) error { +func (p *Go) BoolSetter(param string) (func(bool) error, error) { return func(val bool) error { return p.handleSetter(param, val) - } + }, nil } diff --git a/provider/http.go b/provider/http.go index cabaf2f46c..5a4dc0b1e2 100644 --- a/provider/http.go +++ b/provider/http.go @@ -178,7 +178,7 @@ func (p *HTTP) request(url string, body ...string) ([]byte, error) { var _ StringProvider = (*HTTP)(nil) // StringGetter sends string request -func (p *HTTP) StringGetter() func() (string, error) { +func (p *HTTP) StringGetter() (func() (string, error), error) { return func() (string, error) { b, err := p.request(p.url, p.body) @@ -187,14 +187,14 @@ func (p *HTTP) StringGetter() func() (string, error) { } return string(b), err - } + }, nil } var _ FloatProvider = (*HTTP)(nil) // FloatGetter parses float from request -func (p *HTTP) FloatGetter() func() (float64, error) { - g := p.StringGetter() +func (p *HTTP) FloatGetter() (func() (float64, error), error) { + g, err := p.StringGetter() return func() (float64, error) { s, err := g() @@ -205,31 +205,31 @@ func (p *HTTP) FloatGetter() func() (float64, error) { f, err := strconv.ParseFloat(s, 64) return f * p.scale, err - } + }, err } var _ IntProvider = (*HTTP)(nil) // IntGetter parses int64 from request -func (p *HTTP) IntGetter() func() (int64, error) { - g := p.FloatGetter() +func (p *HTTP) IntGetter() (func() (int64, error), error) { + g, err := p.FloatGetter() return func() (int64, error) { f, err := g() return int64(math.Round(f)), err - } + }, err } var _ BoolProvider = (*HTTP)(nil) // BoolGetter parses bool from request -func (p *HTTP) BoolGetter() func() (bool, error) { - g := p.StringGetter() +func (p *HTTP) BoolGetter() (func() (bool, error), error) { + g, err := p.StringGetter() return func() (bool, error) { s, err := g() return util.Truish(s), err - } + }, err } func (p *HTTP) set(param string, val interface{}) error { @@ -251,35 +251,35 @@ func (p *HTTP) set(param string, val interface{}) error { var _ SetIntProvider = (*HTTP)(nil) // IntSetter sends int request -func (p *HTTP) IntSetter(param string) func(int64) error { +func (p *HTTP) IntSetter(param string) (func(int64) error, error) { return func(val int64) error { return p.set(param, val) - } + }, nil } var _ SetFloatProvider = (*HTTP)(nil) // FloatSetter sends int request -func (p *HTTP) FloatSetter(param string) func(float64) error { +func (p *HTTP) FloatSetter(param string) (func(float64) error, error) { return func(val float64) error { return p.set(param, val) - } + }, nil } var _ SetStringProvider = (*HTTP)(nil) // StringSetter sends string request -func (p *HTTP) StringSetter(param string) func(string) error { +func (p *HTTP) StringSetter(param string) (func(string) error, error) { return func(val string) error { return p.set(param, val) - } + }, nil } var _ SetBoolProvider = (*HTTP)(nil) // BoolSetter sends bool request -func (p *HTTP) BoolSetter(param string) func(bool) error { +func (p *HTTP) BoolSetter(param string) (func(bool) error, error) { return func(val bool) error { return p.set(param, val) - } + }, nil } diff --git a/provider/http_test.go b/provider/http_test.go index d8e0b66154..593d2cbeba 100644 --- a/provider/http_test.go +++ b/provider/http_test.go @@ -9,6 +9,7 @@ import ( "github.com/evcc-io/evcc/util" "github.com/samber/lo" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type httpHandler struct { @@ -32,8 +33,11 @@ func TestHttpGet(t *testing.T) { uriUrl, _ := url.Parse(uri) - res, err := p.StringGetter()() - assert.NoError(t, err) + g, err := p.StringGetter() + require.NoError(t, err) + + res, err := g() + require.NoError(t, err) assert.Equal(t, uriUrl.Path, h.req.URL.Path) assert.Equal(t, h.val, res) } @@ -48,8 +52,11 @@ func TestHttpSet(t *testing.T) { uriUrl, _ := url.Parse(uri) - err := p.StringSetter("baz")("4711") - assert.NoError(t, err) + s, err := p.StringSetter("baz") + require.NoError(t, err) + + err = s("4711") + require.NoError(t, err) assert.Equal(t, uriUrl.Path, h.req.URL.Path) assert.Equal(t, "baz=4711", h.req.URL.RawQuery) } diff --git a/provider/javascript.go b/provider/javascript.go index 553df9523a..9071af7041 100644 --- a/provider/javascript.go +++ b/provider/javascript.go @@ -57,8 +57,10 @@ func NewJavascriptProviderFromConfig(other map[string]interface{}) (Provider, er return p, nil } +var _ FloatProvider = (*Javascript)(nil) + // FloatGetter parses float from request -func (p *Javascript) FloatGetter() func() (float64, error) { +func (p *Javascript) FloatGetter() (func() (float64, error), error) { return func() (float64, error) { v, err := p.handleGetter() if err != nil { @@ -66,11 +68,13 @@ func (p *Javascript) FloatGetter() func() (float64, error) { } return cast.ToFloat64E(v) - } + }, nil } +var _ IntProvider = (*Javascript)(nil) + // IntGetter parses int64 from request -func (p *Javascript) IntGetter() func() (int64, error) { +func (p *Javascript) IntGetter() (func() (int64, error), error) { return func() (int64, error) { v, err := p.handleGetter() if err != nil { @@ -78,11 +82,13 @@ func (p *Javascript) IntGetter() func() (int64, error) { } return cast.ToInt64E(v) - } + }, nil } +var _ StringProvider = (*Javascript)(nil) + // StringGetter parses string from request -func (p *Javascript) StringGetter() func() (string, error) { +func (p *Javascript) StringGetter() (func() (string, error), error) { return func() (string, error) { v, err := p.handleGetter() if err != nil { @@ -90,11 +96,13 @@ func (p *Javascript) StringGetter() func() (string, error) { } return cast.ToStringE(v) - } + }, nil } +var _ BoolProvider = (*Javascript)(nil) + // BoolGetter parses bool from request -func (p *Javascript) BoolGetter() func() (bool, error) { +func (p *Javascript) BoolGetter() (func() (bool, error), error) { return func() (bool, error) { v, err := p.handleGetter() if err != nil { @@ -102,7 +110,7 @@ func (p *Javascript) BoolGetter() func() (bool, error) { } return cast.ToBoolE(v) - } + }, nil } func (p *Javascript) handleGetter() (any, error) { @@ -144,30 +152,38 @@ func (p *Javascript) setParam(param string, val any) error { return p.vm.Set(param, val) } +var _ SetIntProvider = (*Javascript)(nil) + // IntSetter sends int request -func (p *Javascript) IntSetter(param string) func(int64) error { +func (p *Javascript) IntSetter(param string) (func(int64) error, error) { return func(val int64) error { return p.handleSetter(param, val) - } + }, nil } +var _ SetFloatProvider = (*Javascript)(nil) + // FloatSetter sends float request -func (p *Javascript) FloatSetter(param string) func(float64) error { +func (p *Javascript) FloatSetter(param string) (func(float64) error, error) { return func(val float64) error { return p.handleSetter(param, val) - } + }, nil } +var _ SetStringProvider = (*Javascript)(nil) + // StringSetter sends string request -func (p *Javascript) StringSetter(param string) func(string) error { +func (p *Javascript) StringSetter(param string) (func(string) error, error) { return func(val string) error { return p.handleSetter(param, val) - } + }, nil } +var _ SetBoolProvider = (*Javascript)(nil) + // BoolSetter sends bool request -func (p *Javascript) BoolSetter(param string) func(bool) error { +func (p *Javascript) BoolSetter(param string) (func(bool) error, error) { return func(val bool) error { return p.handleSetter(param, val) - } + }, nil } diff --git a/provider/modbus.go b/provider/modbus.go index 955c44da4b..145276e8b1 100644 --- a/provider/modbus.go +++ b/provider/modbus.go @@ -208,23 +208,29 @@ func (m *Modbus) floatGetter() (f float64, err error) { return m.scale * res.Value, err } +var _ FloatProvider = (*Modbus)(nil) + // FloatGetter executes configured modbus read operation and implements func() (float64, error) -func (m *Modbus) FloatGetter() func() (f float64, err error) { - return m.floatGetter +func (m *Modbus) FloatGetter() (func() (f float64, err error), error) { + return m.floatGetter, nil } +var _ IntProvider = (*Modbus)(nil) + // IntGetter executes configured modbus read operation and implements IntProvider -func (m *Modbus) IntGetter() func() (int64, error) { - g := m.FloatGetter() +func (m *Modbus) IntGetter() (func() (int64, error), error) { + g, err := m.FloatGetter() return func() (int64, error) { res, err := g() return int64(math.Round(res)), err - } + }, err } +var _ StringProvider = (*Modbus)(nil) + // StringGetter executes configured modbus read operation and implements IntProvider -func (m *Modbus) StringGetter() func() (string, error) { +func (m *Modbus) StringGetter() (func() (string, error), error) { return func() (string, error) { b, err := m.bytesGetter() if err != nil { @@ -232,7 +238,7 @@ func (m *Modbus) StringGetter() func() (string, error) { } return strings.TrimSpace(string(bytes.TrimLeft(b, "\x00"))), nil - } + }, nil } // UintFromBytes converts byte slice to bigendian uint value @@ -253,8 +259,10 @@ func UintFromBytes(bytes []byte) (u uint64, err error) { return u, err } +var _ BoolProvider = (*Modbus)(nil) + // BoolGetter executes configured modbus read operation and implements IntProvider -func (m *Modbus) BoolGetter() func() (bool, error) { +func (m *Modbus) BoolGetter() (func() (bool, error), error) { return func() (bool, error) { bytes, err := m.bytesGetter() if err != nil { @@ -263,22 +271,24 @@ func (m *Modbus) BoolGetter() func() (bool, error) { u, err := UintFromBytes(bytes) return u > 0, err - } + }, nil } +var _ SetFloatProvider = (*Modbus)(nil) + // FloatSetter executes configured modbus write operation and implements SetFloatProvider -func (m *Modbus) FloatSetter(_ string) func(float64) error { - return func(val float64) error { - op := m.op.MBMD - if op.FuncCode == 0 { - return errors.New("modbus plugin does not support writing to sunspec") - } +func (m *Modbus) FloatSetter(_ string) (func(float64) error, error) { + op := m.op.MBMD + if op.FuncCode == 0 { + return nil, errors.New("modbus plugin does not support writing to sunspec") + } - // if funccode is configured, execute the read directly - if op.FuncCode != gridx.FuncCodeWriteMultipleRegisters { - return fmt.Errorf("invalid write function code: %d", op.FuncCode) - } + // need multiple registers for float + if op.FuncCode != gridx.FuncCodeWriteMultipleRegisters { + return nil, fmt.Errorf("invalid write function code: %d", op.FuncCode) + } + return func(val float64) error { val = m.scale * val var err error @@ -298,17 +308,19 @@ func (m *Modbus) FloatSetter(_ string) func(float64) error { } return err - } + }, nil } +var _ SetIntProvider = (*Modbus)(nil) + // IntSetter executes configured modbus write operation and implements SetIntProvider -func (m *Modbus) IntSetter(_ string) func(int64) error { - return func(val int64) error { - op := m.op.MBMD - if op.FuncCode == 0 { - return errors.New("modbus plugin does not support writing to sunspec") - } +func (m *Modbus) IntSetter(_ string) (func(int64) error, error) { + op := m.op.MBMD + if op.FuncCode == 0 { + return nil, errors.New("modbus plugin does not support writing to sunspec") + } + return func(val int64) error { ival := int64(m.scale * float64(val)) // if funccode is configured, execute the read directly @@ -351,12 +363,14 @@ func (m *Modbus) IntSetter(_ string) func(int64) error { } return err - } + }, nil } +var _ SetBoolProvider = (*Modbus)(nil) + // BoolSetter executes configured modbus write operation and implements SetBoolProvider -func (m *Modbus) BoolSetter(param string) func(bool) error { - set := m.IntSetter(param) +func (m *Modbus) BoolSetter(param string) (func(bool) error, error) { + set, err := m.IntSetter(param) return func(val bool) error { var ival int64 @@ -365,5 +379,5 @@ func (m *Modbus) BoolSetter(param string) func(bool) error { } return set(ival) - } + }, err } diff --git a/provider/mqtt.go b/provider/mqtt.go index 54199c67fd..13b33e0091 100644 --- a/provider/mqtt.go +++ b/provider/mqtt.go @@ -98,10 +98,8 @@ func (p *Mqtt) WithPipeline(pipeline *pipeline.Pipeline) *Mqtt { return p } -var _ FloatProvider = (*Mqtt)(nil) - // newReceiver creates a msgHandler and subscribes it to the topic. -func (m *Mqtt) newReceiver() *msgHandler { +func (m *Mqtt) newReceiver() (*msgHandler, error) { h := &msgHandler{ topic: m.topic, scale: m.scale, @@ -109,44 +107,46 @@ func (m *Mqtt) newReceiver() *msgHandler { val: util.NewMonitor[string](m.timeout), } - m.client.Listen(m.topic, h.receive) - return h + err := m.client.Listen(m.topic, h.receive) + return h, err } +var _ FloatProvider = (*Mqtt)(nil) + // FloatGetter creates handler for float64 from MQTT topic that returns cached value -func (m *Mqtt) FloatGetter() func() (float64, error) { - h := m.newReceiver() - return h.floatGetter +func (m *Mqtt) FloatGetter() (func() (float64, error), error) { + h, err := m.newReceiver() + return h.floatGetter, err } var _ IntProvider = (*Mqtt)(nil) // IntGetter creates handler for int64 from MQTT topic that returns cached value -func (m *Mqtt) IntGetter() func() (int64, error) { - h := m.newReceiver() - return h.intGetter +func (m *Mqtt) IntGetter() (func() (int64, error), error) { + h, err := m.newReceiver() + return h.intGetter, err } var _ StringProvider = (*Mqtt)(nil) // StringGetter creates handler for string from MQTT topic that returns cached value -func (m *Mqtt) StringGetter() func() (string, error) { - h := m.newReceiver() - return h.stringGetter +func (m *Mqtt) StringGetter() (func() (string, error), error) { + h, err := m.newReceiver() + return h.stringGetter, err } var _ BoolProvider = (*Mqtt)(nil) // BoolGetter creates handler for string from MQTT topic that returns cached value -func (m *Mqtt) BoolGetter() func() (bool, error) { - h := m.newReceiver() - return h.boolGetter +func (m *Mqtt) BoolGetter() (func() (bool, error), error) { + h, err := m.newReceiver() + return h.boolGetter, err } var _ SetIntProvider = (*Mqtt)(nil) // IntSetter publishes topic with parameter replaced by int value -func (m *Mqtt) IntSetter(param string) func(int64) error { +func (m *Mqtt) IntSetter(param string) (func(int64) error, error) { return func(v int64) error { payload, err := setFormattedValue(m.payload, param, v) if err != nil { @@ -154,13 +154,13 @@ func (m *Mqtt) IntSetter(param string) func(int64) error { } return m.client.Publish(m.topic, m.retained, payload) - } + }, nil } var _ SetBoolProvider = (*Mqtt)(nil) // BoolSetter invokes script with parameter replaced by bool value -func (m *Mqtt) BoolSetter(param string) func(bool) error { +func (m *Mqtt) BoolSetter(param string) (func(bool) error, error) { return func(v bool) error { payload, err := setFormattedValue(m.payload, param, v) if err != nil { @@ -168,13 +168,13 @@ func (m *Mqtt) BoolSetter(param string) func(bool) error { } return m.client.Publish(m.topic, m.retained, payload) - } + }, nil } var _ SetStringProvider = (*Mqtt)(nil) // StringSetter invokes script with parameter replaced by string value -func (m *Mqtt) StringSetter(param string) func(string) error { +func (m *Mqtt) StringSetter(param string) (func(string) error, error) { return func(v string) error { payload, err := setFormattedValue(m.payload, param, v) if err != nil { @@ -182,5 +182,5 @@ func (m *Mqtt) StringSetter(param string) func(string) error { } return m.client.Publish(m.topic, m.retained, payload) - } + }, nil } diff --git a/provider/mqtt/client.go b/provider/mqtt/client.go index 658a349057..98b4ef6f9f 100644 --- a/provider/mqtt/client.go +++ b/provider/mqtt/client.go @@ -7,6 +7,7 @@ import ( "strings" "sync" "sync/atomic" + "time" paho "github.com/eclipse/paho.mqtt.golang" "github.com/evcc-io/evcc/api" @@ -129,18 +130,25 @@ func (m *Client) Publish(topic string, retained bool, payload interface{}) error } // Listen validates uniqueness and registers and attaches listener -func (m *Client) Listen(topic string, callback func(string)) { +func (m *Client) Listen(topic string, callback func(string)) error { m.mux.Lock() m.listener[topic] = append(m.listener[topic], callback) m.mux.Unlock() - m.listen(topic) + token := m.listen(topic) + + select { + case <-time.After(request.Timeout): + return fmt.Errorf("subscribe: %s: %w", topic, api.ErrTimeout) + case <-token.Done(): + return nil + } } // ListenSetter creates a /set listener that resets the payload after handling -func (m *Client) ListenSetter(topic string, callback func(string) error) { +func (m *Client) ListenSetter(topic string, callback func(string) error) error { topic += "/set" - m.Listen(topic, func(payload string) { + err := m.Listen(topic, func(payload string) { if err := callback(payload); err != nil { m.log.ERROR.Printf("set %s: %v", topic, err) } @@ -148,10 +156,11 @@ func (m *Client) ListenSetter(topic string, callback func(string) error) { m.log.ERROR.Printf("clear: %s: %v", topic, err) } }) + return err } // listen attaches listener to topic -func (m *Client) listen(topic string) { +func (m *Client) listen(topic string) paho.Token { token := m.Client.Subscribe(topic, m.Qos, func(c paho.Client, msg paho.Message) { payload := string(msg.Payload()) m.log.TRACE.Printf("recv %s: '%v'", topic, payload) @@ -165,7 +174,7 @@ func (m *Client) listen(topic string) { } } }) - go m.WaitForToken("subscribe", topic, token) + return token } // WaitForToken synchronously waits until token operation completed diff --git a/provider/mqtt_timeout.go b/provider/mqtt_timeout.go index 1a3d71036a..f5f085fef1 100644 --- a/provider/mqtt_timeout.go +++ b/provider/mqtt_timeout.go @@ -9,29 +9,44 @@ func NewTimeoutHandler(ticker func() (string, error)) *TimeoutHandler { return &TimeoutHandler{ticker} } -func (h *TimeoutHandler) BoolGetter(g func() (bool, error)) func() (bool, error) { +func (h *TimeoutHandler) BoolGetter(p BoolProvider) (func() (bool, error), error) { + g, err := p.BoolGetter() + if err != nil { + return nil, err + } + return func() (val bool, err error) { if val, err = g(); err == nil { _, err = h.ticker() } return val, err - } + }, nil } -func (h *TimeoutHandler) FloatGetter(g func() (float64, error)) func() (float64, error) { +func (h *TimeoutHandler) FloatGetter(p FloatProvider) (func() (float64, error), error) { + g, err := p.FloatGetter() + if err != nil { + return nil, err + } + return func() (val float64, err error) { if val, err = g(); err == nil { _, err = h.ticker() } return val, err - } + }, nil } -func (h *TimeoutHandler) StringGetter(g func() (string, error)) func() (string, error) { +func (h *TimeoutHandler) StringGetter(p StringProvider) (func() (string, error), error) { + g, err := p.StringGetter() + if err != nil { + return nil, err + } + return func() (val string, err error) { if val, err = g(); err == nil { _, err = h.ticker() } return val, err - } + }, nil } diff --git a/provider/prometheus.go b/provider/prometheus.go index 6b9abb4fe8..ef244601b8 100644 --- a/provider/prometheus.go +++ b/provider/prometheus.go @@ -86,7 +86,7 @@ func (p *Prometheus) Query() (model.Value, error) { var _ FloatProvider = (*Prometheus)(nil) // FloatGetter expects scalar value from query response as float -func (p *Prometheus) FloatGetter() func() (float64, error) { +func (p *Prometheus) FloatGetter() (func() (float64, error), error) { return func() (float64, error) { res, err := p.Query() if err != nil { @@ -99,16 +99,16 @@ func (p *Prometheus) FloatGetter() func() (float64, error) { scalarVal := res.(*model.Scalar) return float64(scalarVal.Value), nil - } + }, nil } var _ IntProvider = (*Prometheus)(nil) // IntGetter expects scalar value from query response as int -func (p *Prometheus) IntGetter() func() (int64, error) { - floatGetter := p.FloatGetter() +func (p *Prometheus) IntGetter() (func() (int64, error), error) { + g, err := p.FloatGetter() return func() (int64, error) { - float, err := floatGetter() + float, err := g() return int64(math.Round(float)), err - } + }, err } diff --git a/provider/script.go b/provider/script.go index e10063642a..e17b207e37 100644 --- a/provider/script.go +++ b/provider/script.go @@ -133,8 +133,10 @@ func (p *Script) exec(script string) (string, error) { return s, nil } +var _ StringProvider = (*Script)(nil) + // StringGetter returns string from exec result. Only STDOUT is considered. -func (p *Script) StringGetter() func() (string, error) { +func (p *Script) StringGetter() (func() (string, error), error) { return func() (string, error) { if time.Since(p.updated) > p.cache { p.val, p.err = p.exec(p.script) @@ -156,12 +158,14 @@ func (p *Script) StringGetter() func() (string, error) { } return p.val, p.err - } + }, nil } +var _ FloatProvider = (*Script)(nil) + // FloatGetter parses float from exec result -func (p *Script) FloatGetter() func() (float64, error) { - g := p.StringGetter() +func (p *Script) FloatGetter() (func() (float64, error), error) { + g, err := p.StringGetter() return func() (float64, error) { s, err := g() @@ -175,22 +179,26 @@ func (p *Script) FloatGetter() func() (float64, error) { } return f, err - } + }, err } +var _ IntProvider = (*Script)(nil) + // IntGetter parses int64 from exec result -func (p *Script) IntGetter() func() (int64, error) { - g := p.FloatGetter() +func (p *Script) IntGetter() (func() (int64, error), error) { + g, err := p.FloatGetter() return func() (int64, error) { f, err := g() return int64(math.Round(f)), err - } + }, err } +var _ BoolProvider = (*Script)(nil) + // BoolGetter parses bool from exec result. "on", "true" and 1 are considered truish. -func (p *Script) BoolGetter() func() (bool, error) { - g := p.StringGetter() +func (p *Script) BoolGetter() (func() (bool, error), error) { + g, err := p.StringGetter() return func() (bool, error) { s, err := g() @@ -199,11 +207,13 @@ func (p *Script) BoolGetter() func() (bool, error) { } return util.Truish(s), nil - } + }, err } +var _ SetIntProvider = (*Script)(nil) + // IntSetter invokes script with parameter replaced by int value -func (p *Script) IntSetter(param string) func(int64) error { +func (p *Script) IntSetter(param string) (func(int64) error, error) { // return func to access cached value return func(i int64) error { cmd, err := util.ReplaceFormatted(p.script, map[string]interface{}{ @@ -215,11 +225,13 @@ func (p *Script) IntSetter(param string) func(int64) error { } return err - } + }, nil } +var _ SetBoolProvider = (*Script)(nil) + // BoolSetter invokes script with parameter replaced by bool value -func (p *Script) BoolSetter(param string) func(bool) error { +func (p *Script) BoolSetter(param string) (func(bool) error, error) { // return func to access cached value return func(b bool) error { cmd, err := util.ReplaceFormatted(p.script, map[string]interface{}{ @@ -231,5 +243,5 @@ func (p *Script) BoolSetter(param string) func(bool) error { } return err - } + }, nil } diff --git a/provider/sma.go b/provider/sma.go index 2eb9e58452..948de84ad4 100644 --- a/provider/sma.go +++ b/provider/sma.go @@ -69,8 +69,10 @@ func NewSMAFromConfig(other map[string]interface{}) (Provider, error) { return provider, err } +var _ FloatProvider = (*SMA)(nil) + // FloatGetter creates handler for float64 -func (p *SMA) FloatGetter() func() (float64, error) { +func (p *SMA) FloatGetter() (func() (float64, error), error) { return func() (float64, error) { values, err := p.device.Values() if err != nil { @@ -78,19 +80,21 @@ func (p *SMA) FloatGetter() func() (float64, error) { } return sma.AsFloat(values[p.value]) * p.scale, nil - } + }, nil } +var _ IntProvider = (*SMA)(nil) + // IntGetter creates handler for int64 -func (p *SMA) IntGetter() func() (int64, error) { - fl := p.FloatGetter() +func (p *SMA) IntGetter() (func() (int64, error), error) { + g, err := p.FloatGetter() return func() (int64, error) { - f, err := fl() + f, err := g() if err != nil { return 0, err } return int64(f), nil - } + }, err } diff --git a/provider/socket.go b/provider/socket.go index 6661c6f081..77a909de20 100644 --- a/provider/socket.go +++ b/provider/socket.go @@ -140,18 +140,18 @@ func (p *Socket) listen() { var _ StringProvider = (*Socket)(nil) // StringGetter sends string request -func (p *Socket) StringGetter() func() (string, error) { +func (p *Socket) StringGetter() (func() (string, error), error) { return func() (string, error) { val, err := p.val.Get() return string(val), err - } + }, nil } var _ FloatProvider = (*Socket)(nil) // FloatGetter parses float from string getter -func (p *Socket) FloatGetter() func() (float64, error) { - g := p.StringGetter() +func (p *Socket) FloatGetter() (func() (float64, error), error) { + g, err := p.StringGetter() return func() (float64, error) { s, err := g() @@ -162,29 +162,29 @@ func (p *Socket) FloatGetter() func() (float64, error) { f, err := strconv.ParseFloat(s, 64) return f * p.scale, err - } + }, err } var _ IntProvider = (*Socket)(nil) // IntGetter parses int64 from float getter -func (p *Socket) IntGetter() func() (int64, error) { - g := p.FloatGetter() +func (p *Socket) IntGetter() (func() (int64, error), error) { + g, err := p.FloatGetter() return func() (int64, error) { f, err := g() return int64(math.Round(f)), err - } + }, err } var _ BoolProvider = (*Socket)(nil) // BoolGetter parses bool from string getter -func (p *Socket) BoolGetter() func() (bool, error) { - g := p.StringGetter() +func (p *Socket) BoolGetter() (func() (bool, error), error) { + g, err := p.StringGetter() return func() (bool, error) { s, err := g() return util.Truish(s), err - } + }, err } diff --git a/provider/socket_test.go b/provider/socket_test.go index 551b06f547..09f63180ca 100644 --- a/provider/socket_test.go +++ b/provider/socket_test.go @@ -48,7 +48,9 @@ func TestSocketProvider(t *testing.T) { <-p.(*Socket).val.Done() - g := p.(IntProvider).IntGetter() + g, err := p.(IntProvider).IntGetter() + require.NoError(t, err) + i, err := g() require.NoError(t, err) require.Equal(t, int64(1), i) diff --git a/server/mqtt.go b/server/mqtt.go index ab8d21ca6a..33a06f2fc5 100644 --- a/server/mqtt.go +++ b/server/mqtt.go @@ -128,138 +128,174 @@ func (m *MQTT) publish(topic string, retained bool, payload interface{}) { m.publishComplex(topic, retained, payload) } -func (m *MQTT) listenSetters(topic string, site site.API, lp loadpoint.API) { - m.Handler.ListenSetter(topic+"/mode", func(payload string) error { - mode, err := api.ChargeModeString(payload) - if err == nil { - lp.SetMode(mode) - } - return err - }) - m.Handler.ListenSetter(topic+"/minSoc", func(payload string) error { - soc, err := strconv.Atoi(payload) - if err == nil { - lp.SetMinSoc(soc) - } - return err - }) - m.Handler.ListenSetter(topic+"/targetEnergy", func(payload string) error { - val, err := parseFloat(payload) - if err == nil { - lp.SetTargetEnergy(val) - } - return err - }) - m.Handler.ListenSetter(topic+"/targetSoc", func(payload string) error { - soc, err := strconv.Atoi(payload) - if err == nil { - lp.SetTargetSoc(soc) - } - return err - }) - m.Handler.ListenSetter(topic+"/targetTime", func(payload string) error { - val, err := time.Parse(time.RFC3339, payload) - if err == nil { - err = lp.SetTargetTime(val) - } else if string(payload) == "null" { - err = lp.SetTargetTime(time.Time{}) - } - return err - }) - m.Handler.ListenSetter(topic+"/minCurrent", func(payload string) error { - current, err := parseFloat(payload) - if err == nil { - lp.SetMinCurrent(current) - } - return err - }) - m.Handler.ListenSetter(topic+"/maxCurrent", func(payload string) error { - current, err := parseFloat(payload) - if err == nil { - lp.SetMaxCurrent(current) - } - return err - }) - m.Handler.ListenSetter(topic+"/phases", func(payload string) error { - phases, err := strconv.Atoi(payload) - if err == nil { - err = lp.SetPhases(phases) - } - return err - }) - m.Handler.ListenSetter(topic+"/vehicle", func(payload string) error { - vehicle, err := strconv.Atoi(payload) - if err == nil { - if vehicle > 0 { - if vehicles := site.GetVehicles(); vehicle <= len(vehicles) { - lp.SetVehicle(vehicles[vehicle-1]) +func (m *MQTT) listenSetters(topic string, site site.API, lp loadpoint.API) error { + var err error + + if err == nil { + err = m.Handler.ListenSetter(topic+"/mode", func(payload string) error { + mode, err := api.ChargeModeString(payload) + if err == nil { + lp.SetMode(mode) + } + return err + }) + } + if err == nil { + err = m.Handler.ListenSetter(topic+"/minSoc", func(payload string) error { + soc, err := strconv.Atoi(payload) + if err == nil { + lp.SetMinSoc(soc) + } + return err + }) + } + if err == nil { + err = m.Handler.ListenSetter(topic+"/targetEnergy", func(payload string) error { + val, err := parseFloat(payload) + if err == nil { + lp.SetTargetEnergy(val) + } + return err + }) + } + if err == nil { + err = m.Handler.ListenSetter(topic+"/targetSoc", func(payload string) error { + soc, err := strconv.Atoi(payload) + if err == nil { + lp.SetTargetSoc(soc) + } + return err + }) + } + if err == nil { + err = m.Handler.ListenSetter(topic+"/targetTime", func(payload string) error { + val, err := time.Parse(time.RFC3339, payload) + if err == nil { + err = lp.SetTargetTime(val) + } else if string(payload) == "null" { + err = lp.SetTargetTime(time.Time{}) + } + return err + }) + } + if err == nil { + err = m.Handler.ListenSetter(topic+"/minCurrent", func(payload string) error { + current, err := parseFloat(payload) + if err == nil { + lp.SetMinCurrent(current) + } + return err + }) + } + if err == nil { + err = m.Handler.ListenSetter(topic+"/maxCurrent", func(payload string) error { + current, err := parseFloat(payload) + if err == nil { + lp.SetMaxCurrent(current) + } + return err + }) + } + if err == nil { + err = m.Handler.ListenSetter(topic+"/phases", func(payload string) error { + phases, err := strconv.Atoi(payload) + if err == nil { + err = lp.SetPhases(phases) + } + return err + }) + } + if err == nil { + err = m.Handler.ListenSetter(topic+"/vehicle", func(payload string) error { + vehicle, err := strconv.Atoi(payload) + if err == nil { + if vehicle > 0 { + if vehicles := site.GetVehicles(); vehicle <= len(vehicles) { + lp.SetVehicle(vehicles[vehicle-1]) + } else { + err = fmt.Errorf("invalid vehicle: %d", vehicle) + } } else { - err = fmt.Errorf("invalid vehicle: %d", vehicle) + lp.SetVehicle(nil) } - } else { - lp.SetVehicle(nil) } - } - return err - }) - m.Handler.ListenSetter(topic+"/enableThreshold", func(payload string) error { - threshold, err := parseFloat(payload) - if err == nil { - lp.SetEnableThreshold(threshold) - } - return err - }) - m.Handler.ListenSetter(topic+"/disableThreshold", func(payload string) error { - threshold, err := parseFloat(payload) - if err == nil { - lp.SetDisableThreshold(threshold) - } - return err - }) + return err + }) + } + if err == nil { + err = m.Handler.ListenSetter(topic+"/enableThreshold", func(payload string) error { + threshold, err := parseFloat(payload) + if err == nil { + lp.SetEnableThreshold(threshold) + } + return err + }) + } + if err == nil { + err = m.Handler.ListenSetter(topic+"/disableThreshold", func(payload string) error { + threshold, err := parseFloat(payload) + if err == nil { + lp.SetDisableThreshold(threshold) + } + return err + }) + } + + return err } // Run starts the MQTT publisher for the MQTT API func (m *MQTT) Run(site site.API, in <-chan util.Param) { // site setters - m.Handler.ListenSetter(m.root+"/site/prioritySoc", func(payload string) error { + if err := m.Handler.ListenSetter(m.root+"/site/prioritySoc", func(payload string) error { val, err := parseFloat(payload) if err == nil { err = site.SetPrioritySoc(val) } return err - }) + }); err != nil { + m.log.ERROR.Println(err) + } - m.Handler.ListenSetter(m.root+"/site/bufferSoc", func(payload string) error { + if err := m.Handler.ListenSetter(m.root+"/site/bufferSoc", func(payload string) error { val, err := parseFloat(payload) if err == nil { err = site.SetBufferSoc(val) } return err - }) + }); err != nil { + m.log.ERROR.Println(err) + } - m.Handler.ListenSetter(m.root+"/site/bufferStartSoc", func(payload string) error { + if err := m.Handler.ListenSetter(m.root+"/site/bufferStartSoc", func(payload string) error { val, err := parseFloat(payload) if err == nil { err = site.SetBufferStartSoc(val) } return err - }) + }); err != nil { + m.log.ERROR.Println(err) + } - m.Handler.ListenSetter(m.root+"/site/residualPower", func(payload string) error { + if err := m.Handler.ListenSetter(m.root+"/site/residualPower", func(payload string) error { val, err := parseFloat(payload) if err == nil { err = site.SetResidualPower(val) } return err - }) + }); err != nil { + m.log.ERROR.Println(err) + } - m.Handler.ListenSetter(m.root+"/site/smartCostLimit", func(payload string) error { + if err := m.Handler.ListenSetter(m.root+"/site/smartCostLimit", func(payload string) error { val, err := parseFloat(payload) if err == nil { err = site.SetSmartCostLimit(val) } return err - }) + }); err != nil { + m.log.ERROR.Println(err) + } // number of loadpoints topic := fmt.Sprintf("%s/loadpoints", m.root) @@ -268,7 +304,9 @@ func (m *MQTT) Run(site site.API, in <-chan util.Param) { // loadpoint setters for id, lp := range site.Loadpoints() { topic := fmt.Sprintf("%s/loadpoints/%d", m.root, id+1) - m.listenSetters(topic, site, lp) + if err := m.listenSetters(topic, site, lp); err != nil { + m.log.ERROR.Println(err) + } } // TODO remove deprecated topics