Heating: add generic heatpump charger (#19008)
This commit is contained in:
parent
af8a57544a
commit
30ab9a45cc
14 changed files with 620 additions and 107 deletions
|
|
@ -6,7 +6,8 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/meter"
|
||||
"github.com/evcc-io/evcc/charger/measurement"
|
||||
meter "github.com/evcc-io/evcc/meter/measurement"
|
||||
"github.com/evcc-io/evcc/plugin"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
)
|
||||
|
|
@ -36,12 +37,8 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]interface{}
|
|||
Wakeup *plugin.Config
|
||||
Soc *plugin.Config
|
||||
Tos bool
|
||||
|
||||
// optional measurements
|
||||
Power *plugin.Config
|
||||
Energy *plugin.Config
|
||||
|
||||
Currents, Voltages []plugin.Config
|
||||
measurement.Energy `mapstructure:",squash"` // optional
|
||||
meter.Phases `mapstructure:",squash"` // optional
|
||||
}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
|
|
@ -123,12 +120,12 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]interface{}
|
|||
}
|
||||
|
||||
// decorate measurements
|
||||
powerG, energyG, err := meter.BuildMeasurements(ctx, cc.Power, cc.Energy)
|
||||
powerG, energyG, err := cc.Energy.Configure(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
currentsG, voltagesG, _, err := meter.BuildPhaseMeasurements(ctx, cc.Currents, cc.Voltages, nil)
|
||||
currentsG, voltagesG, _, err := cc.Phases.Configure(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
159
charger/heatpump.go
Normal file
159
charger/heatpump.go
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
package charger
|
||||
|
||||
// LICENSE
|
||||
|
||||
// Copyright (c) 2024 andig
|
||||
|
||||
// This module is NOT covered by the MIT license. All rights reserved.
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/charger/measurement"
|
||||
"github.com/evcc-io/evcc/plugin"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
)
|
||||
|
||||
// Heatpump charger implementation
|
||||
type Heatpump struct {
|
||||
*embed
|
||||
phases int
|
||||
power int64
|
||||
maxPowerG func() (int64, error)
|
||||
maxPowerS func(int64) error
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.AddCtx("heatpump", NewHeatpumpFromConfig)
|
||||
}
|
||||
|
||||
//go:generate go tool decorate -f decorateHeatpump -b *Heatpump -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.Battery,Soc,func() (float64, error)" -t "api.SocLimiter,GetLimitSoc,func() (int64, error)"
|
||||
|
||||
// NewHeatpumpFromConfig creates heatpump configurable charger from generic config
|
||||
func NewHeatpumpFromConfig(ctx context.Context, other map[string]interface{}) (api.Charger, error) {
|
||||
cc := struct {
|
||||
embed `mapstructure:",squash"`
|
||||
SetMaxPower plugin.Config
|
||||
GetMaxPower *plugin.Config // optional
|
||||
measurement.Temperature `mapstructure:",squash"`
|
||||
measurement.Energy `mapstructure:",squash"`
|
||||
Phases int
|
||||
}{
|
||||
embed: embed{
|
||||
Icon_: "heatpump",
|
||||
Features_: []api.Feature{api.Heating, api.IntegratedDevice},
|
||||
},
|
||||
Phases: 1,
|
||||
}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
maxPowerG, err := cc.GetMaxPower.IntGetter(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
maxPowerS, err := cc.SetMaxPower.IntSetter(ctx, "maxpower")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if !sponsor.IsAuthorized() {
|
||||
// return nil, api.ErrSponsorRequired
|
||||
// }
|
||||
|
||||
res, err := NewHeatpump(ctx, &cc.embed, maxPowerS, maxPowerG, cc.Phases)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
powerG, energyG, err := cc.Energy.Configure(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tempG, limitTempG, err := cc.Temperature.Configure(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return decorateHeatpump(res, powerG, energyG, tempG, limitTempG), nil
|
||||
}
|
||||
|
||||
// NewHeatpump creates heatpump charger
|
||||
func NewHeatpump(ctx context.Context, embed *embed, maxPowerS func(int64) error, maxPowerG func() (int64, error), phases int) (*Heatpump, error) {
|
||||
res := &Heatpump{
|
||||
embed: embed,
|
||||
maxPowerG: maxPowerG,
|
||||
maxPowerS: maxPowerS,
|
||||
phases: phases,
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (wb *Heatpump) getMaxPower() (int64, error) {
|
||||
if wb.maxPowerG == nil {
|
||||
return wb.power, nil
|
||||
}
|
||||
return wb.maxPowerG()
|
||||
}
|
||||
|
||||
func (wb *Heatpump) setMaxPower(power int64) error {
|
||||
err := wb.maxPowerS(power)
|
||||
if err == nil {
|
||||
wb.power = power
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Status implements the api.Charger interface
|
||||
func (wb *Heatpump) Status() (api.ChargeStatus, error) {
|
||||
power, err := wb.getMaxPower()
|
||||
if err != nil {
|
||||
return api.StatusNone, err
|
||||
}
|
||||
|
||||
status := map[bool]api.ChargeStatus{false: api.StatusB, true: api.StatusC}
|
||||
return status[power > 0], nil
|
||||
}
|
||||
|
||||
// Enabled implements the api.Charger interface
|
||||
func (wb *Heatpump) Enabled() (bool, error) {
|
||||
power, err := wb.getMaxPower()
|
||||
return power > 0, err
|
||||
}
|
||||
|
||||
// Enable implements the api.Charger interface
|
||||
func (wb *Heatpump) Enable(enable bool) error {
|
||||
var power int64
|
||||
if enable {
|
||||
power = wb.power
|
||||
}
|
||||
return wb.setMaxPower(power)
|
||||
}
|
||||
|
||||
// MaxCurrent implements the api.Charger interface
|
||||
func (wb *Heatpump) MaxCurrent(current int64) error {
|
||||
return wb.MaxCurrentEx(float64(current))
|
||||
}
|
||||
|
||||
// MaxCurrent implements the api.Charger interface
|
||||
func (wb *Heatpump) MaxCurrentEx(current float64) error {
|
||||
return wb.setMaxPower(int64(230 * current * float64(wb.phases)))
|
||||
}
|
||||
221
charger/heatpump_decorators.go
Normal file
221
charger/heatpump_decorators.go
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
package charger
|
||||
|
||||
// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"github.com/evcc-io/evcc/api"
|
||||
)
|
||||
|
||||
func decorateHeatpump(base *Heatpump, meter func() (float64, error), meterEnergy func() (float64, error), battery func() (float64, error), socLimiter func() (int64, error)) api.Charger {
|
||||
switch {
|
||||
case battery == nil && meter == nil && socLimiter == nil:
|
||||
return base
|
||||
|
||||
case battery == nil && meter != nil && meterEnergy == nil && socLimiter == nil:
|
||||
return &struct {
|
||||
*Heatpump
|
||||
api.Meter
|
||||
}{
|
||||
Heatpump: base,
|
||||
Meter: &decorateHeatpumpMeterImpl{
|
||||
meter: meter,
|
||||
},
|
||||
}
|
||||
|
||||
case battery == nil && meter != nil && meterEnergy != nil && socLimiter == nil:
|
||||
return &struct {
|
||||
*Heatpump
|
||||
api.Meter
|
||||
api.MeterEnergy
|
||||
}{
|
||||
Heatpump: base,
|
||||
Meter: &decorateHeatpumpMeterImpl{
|
||||
meter: meter,
|
||||
},
|
||||
MeterEnergy: &decorateHeatpumpMeterEnergyImpl{
|
||||
meterEnergy: meterEnergy,
|
||||
},
|
||||
}
|
||||
|
||||
case battery != nil && meter == nil && socLimiter == nil:
|
||||
return &struct {
|
||||
*Heatpump
|
||||
api.Battery
|
||||
}{
|
||||
Heatpump: base,
|
||||
Battery: &decorateHeatpumpBatteryImpl{
|
||||
battery: battery,
|
||||
},
|
||||
}
|
||||
|
||||
case battery != nil && meter != nil && meterEnergy == nil && socLimiter == nil:
|
||||
return &struct {
|
||||
*Heatpump
|
||||
api.Battery
|
||||
api.Meter
|
||||
}{
|
||||
Heatpump: base,
|
||||
Battery: &decorateHeatpumpBatteryImpl{
|
||||
battery: battery,
|
||||
},
|
||||
Meter: &decorateHeatpumpMeterImpl{
|
||||
meter: meter,
|
||||
},
|
||||
}
|
||||
|
||||
case battery != nil && meter != nil && meterEnergy != nil && socLimiter == nil:
|
||||
return &struct {
|
||||
*Heatpump
|
||||
api.Battery
|
||||
api.Meter
|
||||
api.MeterEnergy
|
||||
}{
|
||||
Heatpump: base,
|
||||
Battery: &decorateHeatpumpBatteryImpl{
|
||||
battery: battery,
|
||||
},
|
||||
Meter: &decorateHeatpumpMeterImpl{
|
||||
meter: meter,
|
||||
},
|
||||
MeterEnergy: &decorateHeatpumpMeterEnergyImpl{
|
||||
meterEnergy: meterEnergy,
|
||||
},
|
||||
}
|
||||
|
||||
case battery == nil && meter == nil && socLimiter != nil:
|
||||
return &struct {
|
||||
*Heatpump
|
||||
api.SocLimiter
|
||||
}{
|
||||
Heatpump: base,
|
||||
SocLimiter: &decorateHeatpumpSocLimiterImpl{
|
||||
socLimiter: socLimiter,
|
||||
},
|
||||
}
|
||||
|
||||
case battery == nil && meter != nil && meterEnergy == nil && socLimiter != nil:
|
||||
return &struct {
|
||||
*Heatpump
|
||||
api.Meter
|
||||
api.SocLimiter
|
||||
}{
|
||||
Heatpump: base,
|
||||
Meter: &decorateHeatpumpMeterImpl{
|
||||
meter: meter,
|
||||
},
|
||||
SocLimiter: &decorateHeatpumpSocLimiterImpl{
|
||||
socLimiter: socLimiter,
|
||||
},
|
||||
}
|
||||
|
||||
case battery == nil && meter != nil && meterEnergy != nil && socLimiter != nil:
|
||||
return &struct {
|
||||
*Heatpump
|
||||
api.Meter
|
||||
api.MeterEnergy
|
||||
api.SocLimiter
|
||||
}{
|
||||
Heatpump: base,
|
||||
Meter: &decorateHeatpumpMeterImpl{
|
||||
meter: meter,
|
||||
},
|
||||
MeterEnergy: &decorateHeatpumpMeterEnergyImpl{
|
||||
meterEnergy: meterEnergy,
|
||||
},
|
||||
SocLimiter: &decorateHeatpumpSocLimiterImpl{
|
||||
socLimiter: socLimiter,
|
||||
},
|
||||
}
|
||||
|
||||
case battery != nil && meter == nil && socLimiter != nil:
|
||||
return &struct {
|
||||
*Heatpump
|
||||
api.Battery
|
||||
api.SocLimiter
|
||||
}{
|
||||
Heatpump: base,
|
||||
Battery: &decorateHeatpumpBatteryImpl{
|
||||
battery: battery,
|
||||
},
|
||||
SocLimiter: &decorateHeatpumpSocLimiterImpl{
|
||||
socLimiter: socLimiter,
|
||||
},
|
||||
}
|
||||
|
||||
case battery != nil && meter != nil && meterEnergy == nil && socLimiter != nil:
|
||||
return &struct {
|
||||
*Heatpump
|
||||
api.Battery
|
||||
api.Meter
|
||||
api.SocLimiter
|
||||
}{
|
||||
Heatpump: base,
|
||||
Battery: &decorateHeatpumpBatteryImpl{
|
||||
battery: battery,
|
||||
},
|
||||
Meter: &decorateHeatpumpMeterImpl{
|
||||
meter: meter,
|
||||
},
|
||||
SocLimiter: &decorateHeatpumpSocLimiterImpl{
|
||||
socLimiter: socLimiter,
|
||||
},
|
||||
}
|
||||
|
||||
case battery != nil && meter != nil && meterEnergy != nil && socLimiter != nil:
|
||||
return &struct {
|
||||
*Heatpump
|
||||
api.Battery
|
||||
api.Meter
|
||||
api.MeterEnergy
|
||||
api.SocLimiter
|
||||
}{
|
||||
Heatpump: base,
|
||||
Battery: &decorateHeatpumpBatteryImpl{
|
||||
battery: battery,
|
||||
},
|
||||
Meter: &decorateHeatpumpMeterImpl{
|
||||
meter: meter,
|
||||
},
|
||||
MeterEnergy: &decorateHeatpumpMeterEnergyImpl{
|
||||
meterEnergy: meterEnergy,
|
||||
},
|
||||
SocLimiter: &decorateHeatpumpSocLimiterImpl{
|
||||
socLimiter: socLimiter,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type decorateHeatpumpBatteryImpl struct {
|
||||
battery func() (float64, error)
|
||||
}
|
||||
|
||||
func (impl *decorateHeatpumpBatteryImpl) Soc() (float64, error) {
|
||||
return impl.battery()
|
||||
}
|
||||
|
||||
type decorateHeatpumpMeterImpl struct {
|
||||
meter func() (float64, error)
|
||||
}
|
||||
|
||||
func (impl *decorateHeatpumpMeterImpl) CurrentPower() (float64, error) {
|
||||
return impl.meter()
|
||||
}
|
||||
|
||||
type decorateHeatpumpMeterEnergyImpl struct {
|
||||
meterEnergy func() (float64, error)
|
||||
}
|
||||
|
||||
func (impl *decorateHeatpumpMeterEnergyImpl) TotalEnergy() (float64, error) {
|
||||
return impl.meterEnergy()
|
||||
}
|
||||
|
||||
type decorateHeatpumpSocLimiterImpl struct {
|
||||
socLimiter func() (int64, error)
|
||||
}
|
||||
|
||||
func (impl *decorateHeatpumpSocLimiterImpl) GetLimitSoc() (int64, error) {
|
||||
return impl.socLimiter()
|
||||
}
|
||||
31
charger/measurement/energy.go
Normal file
31
charger/measurement/energy.go
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package measurement
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/evcc-io/evcc/plugin"
|
||||
)
|
||||
|
||||
type Energy struct {
|
||||
Power *plugin.Config // optional
|
||||
Energy *plugin.Config // optional
|
||||
}
|
||||
|
||||
func (cc *Energy) Configure(ctx context.Context) (
|
||||
func() (float64, error),
|
||||
func() (float64, error),
|
||||
error,
|
||||
) {
|
||||
powerG, err := cc.Power.FloatGetter(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("power: %w", err)
|
||||
}
|
||||
|
||||
energyG, err := cc.Energy.FloatGetter(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("energy: %w", err)
|
||||
}
|
||||
|
||||
return powerG, energyG, nil
|
||||
}
|
||||
31
charger/measurement/heating.go
Normal file
31
charger/measurement/heating.go
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package measurement
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/evcc-io/evcc/plugin"
|
||||
)
|
||||
|
||||
type Temperature struct {
|
||||
Temp *plugin.Config // optional
|
||||
LimitTemp *plugin.Config // optional
|
||||
}
|
||||
|
||||
func (cc *Temperature) Configure(ctx context.Context) (
|
||||
func() (float64, error),
|
||||
func() (int64, error),
|
||||
error,
|
||||
) {
|
||||
tempG, err := cc.Temp.FloatGetter(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("temp: %w", err)
|
||||
}
|
||||
|
||||
limitTempG, err := cc.LimitTemp.IntGetter(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("limit temp: %w", err)
|
||||
}
|
||||
|
||||
return tempG, limitTempG, nil
|
||||
}
|
||||
|
|
@ -20,9 +20,9 @@ package charger
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/charger/measurement"
|
||||
"github.com/evcc-io/evcc/plugin"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
)
|
||||
|
|
@ -30,11 +30,9 @@ import (
|
|||
// SgReady charger implementation
|
||||
type SgReady struct {
|
||||
*embed
|
||||
_mode int64
|
||||
phases int
|
||||
get func() (int64, error)
|
||||
set func(int64) error
|
||||
maxPower func(int64) error
|
||||
mode int64
|
||||
modeS func(int64) error
|
||||
modeG func() (int64, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
@ -53,15 +51,12 @@ const (
|
|||
// NewSgReadyFromConfig creates an SG Ready configurable charger from generic config
|
||||
func NewSgReadyFromConfig(ctx context.Context, other map[string]interface{}) (api.Charger, error) {
|
||||
cc := struct {
|
||||
embed `mapstructure:",squash"`
|
||||
SetMode plugin.Config
|
||||
GetMode *plugin.Config // optional
|
||||
MaxPower *plugin.Config // optional
|
||||
Power *plugin.Config // optional
|
||||
Energy *plugin.Config // optional
|
||||
Temp *plugin.Config // optional
|
||||
LimitTemp *plugin.Config // optional
|
||||
Phases int
|
||||
embed `mapstructure:",squash"`
|
||||
SetMode plugin.Config
|
||||
GetMode *plugin.Config // optional
|
||||
measurement.Temperature `mapstructure:",squash"`
|
||||
measurement.Energy `mapstructure:",squash"`
|
||||
Phases int
|
||||
}{
|
||||
embed: embed{
|
||||
Icon_: "heatpump",
|
||||
|
|
@ -74,76 +69,56 @@ func NewSgReadyFromConfig(ctx context.Context, other map[string]interface{}) (ap
|
|||
return nil, err
|
||||
}
|
||||
|
||||
set, err := cc.SetMode.IntSetter(ctx, "mode")
|
||||
modeS, err := cc.SetMode.IntSetter(ctx, "mode")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
get, err := cc.GetMode.IntGetter(ctx)
|
||||
modeG, err := cc.GetMode.IntGetter(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
maxPower, err := cc.MaxPower.IntSetter(ctx, "maxpower")
|
||||
res, err := NewSgReady(ctx, &cc.embed, modeS, modeG)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := NewSgReady(ctx, &cc.embed, set, get, maxPower, cc.Phases)
|
||||
powerG, energyG, err := cc.Energy.Configure(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// decorate power
|
||||
powerG, err := cc.Power.FloatGetter(ctx)
|
||||
tempG, limitTempG, err := cc.Temperature.Configure(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("power: %w", err)
|
||||
}
|
||||
|
||||
// decorate energy
|
||||
energyG, err := cc.Energy.FloatGetter(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("energy: %w", err)
|
||||
}
|
||||
|
||||
// decorate temp
|
||||
tempG, err := cc.Temp.FloatGetter(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("temp: %w", err)
|
||||
}
|
||||
|
||||
limitTempG, err := cc.LimitTemp.IntGetter(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("limit temp: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return decorateSgReady(res, powerG, energyG, tempG, limitTempG), nil
|
||||
}
|
||||
|
||||
// NewSgReady creates SG Ready charger
|
||||
func NewSgReady(ctx context.Context, embed *embed, set func(int64) error, get func() (int64, error), maxPower func(int64) error, phases int) (*SgReady, error) {
|
||||
func NewSgReady(ctx context.Context, embed *embed, modeS func(int64) error, modeG func() (int64, error)) (*SgReady, error) {
|
||||
res := &SgReady{
|
||||
embed: embed,
|
||||
_mode: Normal,
|
||||
set: set,
|
||||
get: get,
|
||||
maxPower: maxPower,
|
||||
phases: phases,
|
||||
embed: embed,
|
||||
mode: Normal,
|
||||
modeS: modeS,
|
||||
modeG: modeG,
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (wb *SgReady) mode() (int64, error) {
|
||||
if wb.get == nil {
|
||||
return wb._mode, nil
|
||||
func (wb *SgReady) getMode() (int64, error) {
|
||||
if wb.modeG == nil {
|
||||
return wb.mode, nil
|
||||
}
|
||||
return wb.get()
|
||||
return wb.modeG()
|
||||
}
|
||||
|
||||
// Status implements the api.Charger interface
|
||||
func (wb *SgReady) Status() (api.ChargeStatus, error) {
|
||||
mode, err := wb.mode()
|
||||
mode, err := wb.getMode()
|
||||
if err != nil {
|
||||
return api.StatusNone, err
|
||||
}
|
||||
|
|
@ -152,36 +127,27 @@ func (wb *SgReady) Status() (api.ChargeStatus, error) {
|
|||
return api.StatusNone, errors.New("stop mode")
|
||||
}
|
||||
|
||||
status := map[int64]api.ChargeStatus{Boost: api.StatusC, Normal: api.StatusB}[mode]
|
||||
return status, nil
|
||||
status := map[int64]api.ChargeStatus{Boost: api.StatusC, Normal: api.StatusB}
|
||||
return status[mode], nil
|
||||
}
|
||||
|
||||
// Enabled implements the api.Charger interface
|
||||
func (wb *SgReady) Enabled() (bool, error) {
|
||||
mode, err := wb.mode()
|
||||
mode, err := wb.getMode()
|
||||
return mode == Boost, err
|
||||
}
|
||||
|
||||
// Enable implements the api.Charger interface
|
||||
func (wb *SgReady) Enable(enable bool) error {
|
||||
mode := map[bool]int64{false: Normal, true: Boost}[enable]
|
||||
err := wb.set(mode)
|
||||
err := wb.modeS(mode)
|
||||
if err == nil {
|
||||
wb._mode = mode
|
||||
wb.mode = mode
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// MaxCurrent implements the api.Charger interface
|
||||
func (wb *SgReady) MaxCurrent(current int64) error {
|
||||
return wb.MaxCurrentEx(float64(current))
|
||||
}
|
||||
|
||||
// MaxCurrent implements the api.Charger interface
|
||||
func (wb *SgReady) MaxCurrentEx(current float64) error {
|
||||
if wb.maxPower == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return wb.maxPower(int64(230 * current * float64(wb.phases)))
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ func NewVaillantFromConfig(ctx context.Context, other map[string]interface{}) (a
|
|||
}
|
||||
}
|
||||
|
||||
sgr, err := NewSgReady(ctx, &cc.embed, set, nil, nil, cc.Phases)
|
||||
sgr, err := NewSgReady(ctx, &cc.embed, set, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/meter/measurement"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/modbus"
|
||||
"github.com/volkszaehler/mbmd/meters"
|
||||
|
|
@ -155,7 +156,7 @@ func (m *ModbusMbmd) buildPhaseProviders(readings []string) (func() (float64, fl
|
|||
}
|
||||
}
|
||||
|
||||
return collectPhaseProviders(phases), nil
|
||||
return measurement.CombinePhases(phases), nil
|
||||
}
|
||||
|
||||
// floatGetter executes configured modbus read operation and implements func() (float64, error)
|
||||
|
|
|
|||
31
meter/measurement/energy.go
Normal file
31
meter/measurement/energy.go
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package measurement
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/evcc-io/evcc/plugin"
|
||||
)
|
||||
|
||||
type Energy struct {
|
||||
Power plugin.Config
|
||||
Energy *plugin.Config // optional
|
||||
}
|
||||
|
||||
func (cc *Energy) Configure(ctx context.Context) (
|
||||
func() (float64, error),
|
||||
func() (float64, error),
|
||||
error,
|
||||
) {
|
||||
powerG, err := cc.Power.FloatGetter(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("power: %w", err)
|
||||
}
|
||||
|
||||
energyG, err := cc.Energy.FloatGetter(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("energy: %w", err)
|
||||
}
|
||||
|
||||
return powerG, energyG, nil
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package meter
|
||||
package measurement
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -8,39 +8,27 @@ import (
|
|||
"github.com/evcc-io/evcc/plugin"
|
||||
)
|
||||
|
||||
// BuildMeasurements returns typical meter measurement getters from config
|
||||
func BuildMeasurements(ctx context.Context, power, energy *plugin.Config) (func() (float64, error), func() (float64, error), error) {
|
||||
powerG, err := power.FloatGetter(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("power: %w", err)
|
||||
}
|
||||
|
||||
energyG, err := energy.FloatGetter(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("energy: %w", err)
|
||||
}
|
||||
|
||||
return powerG, energyG, nil
|
||||
type Phases struct {
|
||||
Currents, Voltages, Powers []plugin.Config // optional
|
||||
}
|
||||
|
||||
// BuildPhaseMeasurements returns typical meter measurement getters from config
|
||||
func BuildPhaseMeasurements(ctx context.Context, currents, voltages, powers []plugin.Config) (
|
||||
func (cc *Phases) Configure(ctx context.Context) (
|
||||
func() (float64, float64, float64, error),
|
||||
func() (float64, float64, float64, error),
|
||||
func() (float64, float64, float64, error),
|
||||
error,
|
||||
) {
|
||||
currentsG, err := buildPhaseProviders(ctx, currents)
|
||||
currentsG, err := buildPhaseProviders(ctx, cc.Currents)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("currents: %w", err)
|
||||
}
|
||||
|
||||
voltagesG, err := buildPhaseProviders(ctx, voltages)
|
||||
voltagesG, err := buildPhaseProviders(ctx, cc.Voltages)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("voltages: %w", err)
|
||||
}
|
||||
|
||||
powersG, err := buildPhaseProviders(ctx, powers)
|
||||
powersG, err := buildPhaseProviders(ctx, cc.Powers)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("powers: %w", err)
|
||||
}
|
||||
|
|
@ -68,11 +56,11 @@ func buildPhaseProviders(ctx context.Context, providers []plugin.Config) (func()
|
|||
phases[idx] = c
|
||||
}
|
||||
|
||||
return collectPhaseProviders(phases), nil
|
||||
return CombinePhases(phases), nil
|
||||
}
|
||||
|
||||
// collectPhaseProviders combines phase getters into combined api function
|
||||
func collectPhaseProviders(g [3]func() (float64, error)) func() (float64, float64, float64, error) {
|
||||
// CombinePhases combines phase getters into combined api function
|
||||
func CombinePhases(g [3]func() (float64, error)) func() (float64, float64, float64, error) {
|
||||
return func() (float64, float64, float64, error) {
|
||||
var res [3]float64
|
||||
for idx, currentG := range g {
|
||||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/meter/measurement"
|
||||
"github.com/evcc-io/evcc/plugin"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
)
|
||||
|
|
@ -18,11 +19,8 @@ func init() {
|
|||
// NewConfigurableFromConfig creates api.Meter from config
|
||||
func NewConfigurableFromConfig(ctx context.Context, other map[string]interface{}) (api.Meter, error) {
|
||||
cc := struct {
|
||||
Power plugin.Config
|
||||
Energy *plugin.Config // optional
|
||||
Currents []plugin.Config // optional
|
||||
Voltages []plugin.Config // optional
|
||||
Powers []plugin.Config // optional
|
||||
measurement.Energy `mapstructure:",squash"` // energy optional
|
||||
measurement.Phases `mapstructure:",squash"` // optional
|
||||
|
||||
// battery
|
||||
capacity `mapstructure:",squash"`
|
||||
|
|
@ -42,12 +40,12 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]interface{}
|
|||
return nil, err
|
||||
}
|
||||
|
||||
powerG, energyG, err := BuildMeasurements(ctx, &cc.Power, cc.Energy)
|
||||
powerG, energyG, err := cc.Energy.Configure(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
currentsG, voltagesG, powersG, err := BuildPhaseMeasurements(ctx, cc.Currents, cc.Voltages, cc.Powers)
|
||||
currentsG, voltagesG, powersG, err := cc.Phases.Configure(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/charger/openwb"
|
||||
"github.com/evcc-io/evcc/meter/measurement"
|
||||
"github.com/evcc-io/evcc/plugin"
|
||||
"github.com/evcc-io/evcc/plugin/mqtt"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
|
|
@ -73,7 +74,7 @@ func NewOpenWBFromConfig(other map[string]interface{}) (api.Meter, error) {
|
|||
curr[i] = current
|
||||
}
|
||||
|
||||
currents = collectPhaseProviders(curr)
|
||||
currents = measurement.CombinePhases(curr)
|
||||
|
||||
case "pv":
|
||||
// first pv
|
||||
|
|
|
|||
53
templates/definition/charger/idm.yaml
Normal file
53
templates/definition/charger/idm.yaml
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
template: idm
|
||||
products:
|
||||
- brand: IDM
|
||||
group: heating
|
||||
requirements:
|
||||
# evcc: ["sponsorship"]
|
||||
params:
|
||||
- name: modbus
|
||||
choice: ["tcpip"]
|
||||
- name: tempsource
|
||||
type: choice
|
||||
choice: ["", "warmwater", "buffer"]
|
||||
description:
|
||||
de: "Temperaturquelle"
|
||||
en: "Temperature source"
|
||||
- name: phases
|
||||
type: int
|
||||
advanced: true
|
||||
render: |
|
||||
type: heatpump
|
||||
phases: {{ .phases }}
|
||||
setmaxpower:
|
||||
source: modbus
|
||||
{{- include "modbus" . | indent 2 }}
|
||||
register:
|
||||
address: 74 # PV Überschussleistung
|
||||
type: holding
|
||||
decode: float32s
|
||||
scale: 0.001
|
||||
power:
|
||||
source: modbus
|
||||
{{- include "modbus" . | indent 2 }}
|
||||
register:
|
||||
address: 4122 # aktuelle Aufnahmeleistung der WP
|
||||
type: holding
|
||||
decode: float32s
|
||||
scale: 1000
|
||||
energy:
|
||||
source: modbus
|
||||
{{- include "modbus" . | indent 2 }}
|
||||
register:
|
||||
address: 4128
|
||||
type: holding
|
||||
decode: float32s
|
||||
{{- if .tempsource }}
|
||||
temp:
|
||||
source: modbus
|
||||
{{- include "modbus" . | indent 2 }}
|
||||
register:
|
||||
address: {{ if eq .tempsource "warmwater" -}} 1014 {{ else }} 1008 {{- end }} # 1014 Trinkwasser, 1008 Wärmespeicher
|
||||
type: holding
|
||||
decode: float32s
|
||||
{{- end }}
|
||||
36
templates/definition/charger/lambda-zewotherm.yaml
Normal file
36
templates/definition/charger/lambda-zewotherm.yaml
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
template: lambda-zewotherm
|
||||
products:
|
||||
- brand: Lambda
|
||||
description:
|
||||
de: EU-L Serie
|
||||
en: EU-L Series
|
||||
- brand: Zewotherm
|
||||
description:
|
||||
de: EU-L Serie
|
||||
en: EU-L Series
|
||||
group: heating
|
||||
requirements:
|
||||
# evcc: ["sponsorship"]
|
||||
params:
|
||||
- name: modbus
|
||||
choice: ["tcpip"]
|
||||
- name: phases
|
||||
type: int
|
||||
advanced: true
|
||||
render: |
|
||||
type: heatpump
|
||||
phases: {{ .phases }}
|
||||
setmaxpower:
|
||||
source: modbus
|
||||
{{- include "modbus" . | indent 2 }}
|
||||
register:
|
||||
address: 103 # PV Überschussleistung
|
||||
type: holding
|
||||
decode: int16
|
||||
power:
|
||||
source: modbus
|
||||
{{- include "modbus" . | indent 2 }}
|
||||
register:
|
||||
address: 103 # aktuelle Aufnahmeleistung der WP
|
||||
type: holding
|
||||
decode: int16
|
||||
Loading…
Add table
Add a link
Reference in a new issue