evcc-io/vehicle/homeassistant.go

156 lines
3.8 KiB
Go

package vehicle
import (
"errors"
"strconv"
"time"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/homeassistant"
)
type HomeAssistant struct {
*embed
conn *homeassistant.Connection
soc string
}
// Register on startup
func init() {
registry.Add("homeassistant", NewHomeAssistantVehicleFromConfig)
}
// Constructor from YAML config
func NewHomeAssistantVehicleFromConfig(other map[string]any) (api.Vehicle, error) {
cc := struct {
embed `mapstructure:",squash"`
_URI string `mapstructure:"uri"` // TODO deprecated
_Token string `mapstructure:"token"` // TODO deprecated
Home string
Sensors struct {
Soc string // required
Range string // optional
Status string // optional
LimitSoc string // optional
Odometer string // optional
Climater string // optional
FinishTime string // optional
}
Services struct {
Start string `mapstructure:"start_charging"` // script.* optional
Stop string `mapstructure:"stop_charging"` // script.* optional
Wakeup string // script.* optional
SetMaxCurrent string // number.* or input_number.* optional
}
}{
Home: "Home",
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
if cc.Sensors.Soc == "" {
return nil, errors.New("missing soc sensor")
}
log := util.NewLogger("ha-vehicle")
conn, err := homeassistant.NewConnection(log, cc.Home)
if err != nil {
return nil, err
}
res := &HomeAssistant{
embed: &cc.embed,
conn: conn,
soc: cc.Sensors.Soc,
}
// prepare optional feature functions with concise names
var (
limitSoc func() (int64, error)
status func() (api.ChargeStatus, error)
rng func() (int64, error)
odo func() (float64, error)
climater func() (bool, error)
finish func() (time.Time, error)
enable func(bool) error
wakeup func() error
maxcurrent func(int64) error
)
if cc.Sensors.LimitSoc != "" {
limitSoc = func() (int64, error) {
f, err := conn.GetFloatState(cc.Sensors.LimitSoc)
return int64(f), err
}
}
if cc.Sensors.Status != "" {
status = func() (api.ChargeStatus, error) { return conn.GetChargeStatus(cc.Sensors.Status) }
}
if cc.Sensors.Range != "" {
rng = func() (int64, error) {
f, err := conn.GetFloatState(cc.Sensors.Range)
return int64(f), err
}
}
if cc.Sensors.Odometer != "" {
odo = func() (float64, error) { return conn.GetFloatState(cc.Sensors.Odometer) }
}
if cc.Sensors.Climater != "" {
climater = func() (bool, error) { return conn.GetBoolState(cc.Sensors.Climater) }
}
if cc.Sensors.FinishTime != "" {
finish = func() (time.Time, error) { return res.finishTime(cc.Sensors.FinishTime) }
}
if cc.Services.Start != "" && cc.Services.Stop != "" {
enable = func(enable bool) error { return res.enable(cc.Services.Start, cc.Services.Stop, enable) }
}
if cc.Services.Wakeup != "" {
wakeup = func() error { return conn.CallSwitchService(cc.Services.Wakeup, true) }
}
if cc.Services.SetMaxCurrent != "" {
maxcurrent = func(current int64) error { return conn.CallNumberService(cc.Services.SetMaxCurrent, float64(current)) }
}
// decorate all features
return decorateVehicle(
res,
limitSoc,
status,
rng,
odo,
climater,
maxcurrent,
nil,
finish,
wakeup,
enable,
), nil
}
func (v *HomeAssistant) Soc() (float64, error) {
return v.conn.GetFloatState(v.soc)
}
func (v *HomeAssistant) finishTime(entity string) (time.Time, error) {
s, err := v.conn.GetState(entity)
if err != nil {
return time.Time{}, err
}
if ts, err := strconv.ParseInt(s, 10, 64); err == nil {
return time.Unix(ts, 0), nil
}
return time.Parse(time.RFC3339, s)
}
func (v *HomeAssistant) enable(on, off string, enable bool) error {
if enable {
return v.conn.CallSwitchService(on, true)
}
return v.conn.CallSwitchService(off, true)
}