evcc-io/charger/homeassistant.go

155 lines
4.7 KiB
Go

package charger
import (
"errors"
"fmt"
"strconv"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/api/implement"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/homeassistant"
)
// HomeAssistant charger implementation
type HomeAssistant struct {
*embed
implement.Caps
conn *homeassistant.Connection
status string
states homeassistant.StatusMap
enabled string
enable string
maxcurrent string
}
func init() {
registry.Add("homeassistant", NewHomeAssistantFromConfig)
}
// NewHomeAssistantFromConfig creates a HomeAssistant charger from generic config
func NewHomeAssistantFromConfig(other map[string]any) (api.Charger, error) {
var cc struct {
embed `mapstructure:",squash"`
homeassistant.Config `mapstructure:",squash"`
Status string // required - sensor for charge status
StatusA string // optional - custom states mapped to status A
StatusB string // optional - custom states mapped to status B
StatusC string // optional - custom states mapped to status C
Enabled string // required - sensor for enabled state
Enable string // required - switch/input_boolean for enable/disable
MaxCurrent string // required - number entity for setting max current
Power string // optional - power sensor
Energy string // optional - energy sensor
Currents []string // optional - current sensors for L1, L2, L3
Voltages []string // optional - voltage sensors for L1, L2, L3
Phases string // optional - select entity for 1p/3p phase switching
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
if cc.Status == "" {
return nil, errors.New("missing status sensor entity")
}
if cc.Enabled == "" {
return nil, errors.New("missing enabled sensor entity")
}
if cc.Enable == "" {
return nil, errors.New("missing enable switch entity")
}
if cc.MaxCurrent == "" {
return nil, errors.New("missing maxcurrent number entity")
}
states, err := homeassistant.NewStatusMap(cc.StatusA, cc.StatusB, cc.StatusC)
if err != nil {
return nil, err
}
log := util.NewLogger("ha-charger")
conn, err := cc.Config.NewConnection(log)
if err != nil {
return nil, err
}
c := &HomeAssistant{
embed: &cc.embed,
Caps: implement.New(),
conn: conn,
status: cc.Status,
states: states,
enabled: cc.Enabled,
enable: cc.Enable,
maxcurrent: cc.MaxCurrent,
}
if cc.Power != "" {
implement.Has(c, implement.Meter(func() (float64, error) { return conn.GetFloatState(cc.Power) }))
}
if cc.Energy != "" {
implement.Has(c, implement.MeterEnergy(func() (float64, error) { return conn.GetFloatState(cc.Energy) }))
}
// phase currents (optional)
if phases, err := homeassistant.ValidatePhaseEntities(cc.Currents); len(phases) > 0 {
implement.Has(c, implement.PhaseCurrents(func() (float64, float64, float64, error) { return conn.GetPhaseFloatStates(phases) }))
} else if err != nil {
return nil, fmt.Errorf("currents: %w", err)
}
// phase voltages (optional)
if phases, err := homeassistant.ValidatePhaseEntities(cc.Voltages); len(phases) > 0 {
implement.Has(c, implement.PhaseVoltages(func() (float64, float64, float64, error) { return conn.GetPhaseFloatStates(phases) }))
} else if err != nil {
return nil, fmt.Errorf("voltages: %w", err)
}
// phase switching (optional)
if cc.Phases != "" {
implement.Has(c, implement.PhaseSwitcher(func(phases int) error {
return conn.CallSelectService(cc.Phases, strconv.Itoa(phases))
}))
implement.Has(c, implement.PhaseGetter(func() (int, error) {
val, err := conn.GetIntState(cc.Phases)
if err != nil {
return 0, err
}
return int(val), nil
}))
}
return c, nil
}
var _ api.Charger = (*HomeAssistant)(nil)
// Status implements the api.ChargeState interface
func (c *HomeAssistant) Status() (api.ChargeStatus, error) {
return c.conn.GetChargeStatus(c.status, c.states)
}
// Enabled implements the api.Charger interface
func (c *HomeAssistant) Enabled() (bool, error) {
return c.conn.GetBoolState(c.enabled)
}
// Enable implements the api.Charger interface
func (c *HomeAssistant) Enable(enable bool) error {
return c.conn.CallSwitchService(c.enable, enable)
}
// MaxCurrent implements the api.Charger interface
func (c *HomeAssistant) MaxCurrent(current int64) error {
return c.MaxCurrentMillis(float64(current))
}
var _ api.ChargerEx = (*HomeAssistant)(nil)
// MaxCurrentMillis implements the api.ChargerEx interface
func (c *HomeAssistant) MaxCurrentMillis(current float64) error {
return c.conn.CallNumberService(c.maxcurrent, current)
}