167 lines
4.5 KiB
Go
167 lines
4.5 KiB
Go
package meter
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/evcc-io/evcc/api"
|
|
"github.com/evcc-io/evcc/api/implement"
|
|
"github.com/evcc-io/evcc/meter/lgpcs"
|
|
"github.com/evcc-io/evcc/util"
|
|
)
|
|
|
|
// LgEss implements the api.Meter interface
|
|
type LgEss struct {
|
|
implement.Caps
|
|
usage string // grid, pv, battery
|
|
conn *lgpcs.Com // communication with the lgpcs device
|
|
}
|
|
|
|
func init() {
|
|
registry.Add("lgess8", NewLgEss8FromConfig)
|
|
registry.Add("lgess15", NewLgEss15FromConfig)
|
|
}
|
|
|
|
func NewLgEss8FromConfig(other map[string]any) (api.Meter, error) {
|
|
return NewLgEssFromConfig(other, lgpcs.LgEss8)
|
|
}
|
|
|
|
func NewLgEss15FromConfig(other map[string]any) (api.Meter, error) {
|
|
return NewLgEssFromConfig(other, lgpcs.LgEss15)
|
|
}
|
|
|
|
// NewLgEssFromConfig creates an LgEss Meter from generic config
|
|
func NewLgEssFromConfig(other map[string]any, essType lgpcs.Model) (api.Meter, error) {
|
|
cc := struct {
|
|
batteryCapacity `mapstructure:",squash"`
|
|
batterySocLimits `mapstructure:",squash"`
|
|
batteryPowerLimits `mapstructure:",squash"`
|
|
URI, Usage string
|
|
Registration, Password string
|
|
Cache time.Duration
|
|
}{
|
|
batterySocLimits: batterySocLimits{
|
|
MinSoc: 20,
|
|
MaxSoc: 95,
|
|
},
|
|
Cache: time.Second,
|
|
}
|
|
|
|
if err := util.DecodeOther(other, &cc); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if cc.Usage == "" {
|
|
return nil, errors.New("missing usage")
|
|
}
|
|
|
|
return NewLgEss(cc.URI, cc.Usage, cc.Registration, cc.Password, cc.Cache, cc.batteryCapacity, cc.batterySocLimits, cc.batteryPowerLimits, essType)
|
|
}
|
|
|
|
// NewLgEss creates an LgEss Meter
|
|
func NewLgEss(uri, usage, registration, password string, cache time.Duration, batteryCapacity batteryCapacity, batterySocLimits batterySocLimits, batteryPowerLimits batteryPowerLimits, essType lgpcs.Model) (api.Meter, error) {
|
|
conn, err := lgpcs.GetInstance(uri, registration, password, cache, essType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
m := &LgEss{
|
|
Caps: implement.New(),
|
|
usage: strings.ToLower(usage),
|
|
conn: conn,
|
|
}
|
|
|
|
implement.May(m, implement.BatteryCapacity(batteryCapacity.Decorator()))
|
|
implement.May(m, implement.BatteryPowerLimiter(batteryPowerLimits.Decorator()))
|
|
|
|
if m.usage == "grid" && essType != lgpcs.LgEss15 {
|
|
implement.Has(m, implement.MeterEnergy(m.totalEnergy))
|
|
}
|
|
|
|
if usage == "battery" {
|
|
implement.Has(m, implement.Battery(m.batterySoc))
|
|
implement.May(m, implement.BatterySocLimiter(batterySocLimits.Decorator()))
|
|
|
|
if version, err := conn.GetFirmwareVersion(); err == nil && version >= 7430 {
|
|
implement.Has(m, implement.BatteryController(m.batteryMode(batterySocLimits)))
|
|
}
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
// CurrentPower implements the api.Meter interface
|
|
func (m *LgEss) CurrentPower() (float64, error) {
|
|
data, err := m.conn.Data()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
switch m.usage {
|
|
case "grid":
|
|
return data.GetGridPower(), nil
|
|
case "pv":
|
|
return data.GetPvTotalPower(), nil
|
|
case "battery":
|
|
return data.GetBatConvPower(), nil
|
|
default:
|
|
return 0, fmt.Errorf("invalid usage: %s", m.usage)
|
|
}
|
|
}
|
|
|
|
// totalEnergy implements the api.MeterEnergy interface
|
|
func (m *LgEss) totalEnergy() (float64, error) {
|
|
data, err := m.conn.Data()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
switch m.usage {
|
|
case "grid":
|
|
return data.GetCurrentGridFeedInEnergy() / 1e3, nil
|
|
default:
|
|
return 0, fmt.Errorf("invalid usage: %s", m.usage)
|
|
}
|
|
}
|
|
|
|
// batterySoc implements the api.Battery interface
|
|
func (m *LgEss) batterySoc() (float64, error) {
|
|
data, err := m.conn.Data()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return data.GetBatUserSoc(), nil
|
|
}
|
|
|
|
// batteryMode implements the api.BatteryController interface
|
|
func (m *LgEss) batteryMode(batterySocLimits batterySocLimits) func(api.BatteryMode) error {
|
|
return func(mode api.BatteryMode) error {
|
|
switch mode {
|
|
case api.BatteryNormal:
|
|
// firmeware bug: battery not discharging after hold mode
|
|
// if battery is sleeping, wake up with charging for 10sec
|
|
m.conn.BatteryMode("on", 100, true)
|
|
time.Sleep(10 * time.Second)
|
|
// now turn Battery discharge on
|
|
return m.conn.BatteryMode("on", int(batterySocLimits.MinSoc), true)
|
|
case api.BatteryHold:
|
|
soc, err := m.batterySoc()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// soc needs to be the next higher int value to stop discharging immediately
|
|
// example: batterySoc=50.7 -> set 51
|
|
if int(soc)+1 < 100 {
|
|
soc++
|
|
}
|
|
return m.conn.BatteryMode("on", int(soc), true)
|
|
case api.BatteryCharge:
|
|
return m.conn.BatteryMode("on", int(batterySocLimits.MaxSoc), true)
|
|
default:
|
|
return api.ErrNotAvailable
|
|
}
|
|
}
|
|
}
|