Homeassistant: align implementations (BC) (#24237)
This commit is contained in:
parent
fc8cad205f
commit
db5c58dfce
8 changed files with 193 additions and 617 deletions
|
|
@ -2,21 +2,16 @@ package charger
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/request"
|
||||
"github.com/evcc-io/evcc/util/transport"
|
||||
"github.com/evcc-io/evcc/util/homeassistant"
|
||||
)
|
||||
|
||||
type HomeAssistantSwitch struct {
|
||||
baseURL string
|
||||
conn *homeassistant.Connection
|
||||
switchEntity string
|
||||
powerEntity string
|
||||
*request.Helper
|
||||
*switchSocket
|
||||
}
|
||||
|
||||
|
|
@ -27,7 +22,7 @@ func init() {
|
|||
func NewHomeAssistantSwitchFromConfig(other map[string]interface{}) (api.Charger, error) {
|
||||
var cc struct {
|
||||
embed `mapstructure:",squash"`
|
||||
BaseURL string
|
||||
URI string
|
||||
Token string
|
||||
SwitchEntity string
|
||||
PowerEntity string
|
||||
|
|
@ -38,17 +33,10 @@ func NewHomeAssistantSwitchFromConfig(other map[string]interface{}) (api.Charger
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return NewHomeAssistantSwitch(cc.embed, cc.BaseURL, cc.Token, cc.SwitchEntity, cc.PowerEntity, cc.StandbyPower)
|
||||
return NewHomeAssistantSwitch(cc.embed, cc.URI, cc.Token, cc.SwitchEntity, cc.PowerEntity, cc.StandbyPower)
|
||||
}
|
||||
|
||||
func NewHomeAssistantSwitch(embed embed, baseURL, token, switchEntity, powerEntity string, standbypower float64) (api.Charger, error) {
|
||||
c := &HomeAssistantSwitch{
|
||||
baseURL: strings.TrimSuffix(baseURL, "/"),
|
||||
switchEntity: switchEntity,
|
||||
powerEntity: powerEntity,
|
||||
Helper: request.NewHelper(util.NewLogger("ha-switch")),
|
||||
}
|
||||
|
||||
func NewHomeAssistantSwitch(embed embed, uri, token, switchEntity, powerEntity string, standbypower float64) (api.Charger, error) {
|
||||
if switchEntity == "" {
|
||||
return nil, errors.New("missing switch entity")
|
||||
}
|
||||
|
|
@ -58,55 +46,34 @@ func NewHomeAssistantSwitch(embed embed, baseURL, token, switchEntity, powerEnti
|
|||
return nil, errors.New("missing either power entity or negative standbypower")
|
||||
}
|
||||
|
||||
c.switchSocket = NewSwitchSocket(&embed, c.Enabled, c.currentPower, standbypower)
|
||||
c.Helper.Client.Transport = &transport.Decorator{
|
||||
Decorator: transport.DecorateHeaders(map[string]string{
|
||||
"Authorization": "Bearer " + token,
|
||||
"Content-Type": "application/json",
|
||||
}),
|
||||
Base: c.Helper.Client.Transport,
|
||||
log := util.NewLogger("ha-switch")
|
||||
conn, err := homeassistant.NewConnection(log, uri, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := &HomeAssistantSwitch{
|
||||
switchEntity: switchEntity,
|
||||
powerEntity: powerEntity,
|
||||
conn: conn,
|
||||
}
|
||||
|
||||
c.switchSocket = NewSwitchSocket(&embed, c.Enabled, c.currentPower, standbypower)
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Enabled implements the api.Charger interface
|
||||
func (c *HomeAssistantSwitch) Enabled() (bool, error) {
|
||||
var res struct {
|
||||
State string `json:"state"`
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("%s/api/states/%s", c.baseURL, c.switchEntity)
|
||||
err := c.Helper.GetJSON(uri, &res)
|
||||
|
||||
return res.State == "on", err
|
||||
return c.conn.GetBoolState(c.switchEntity)
|
||||
}
|
||||
|
||||
// Enable implements the api.Charger interface
|
||||
func (c *HomeAssistantSwitch) Enable(enable bool) error {
|
||||
service := "turn_off"
|
||||
if enable {
|
||||
service = "turn_on"
|
||||
}
|
||||
|
||||
data := map[string]any{"entity_id": c.switchEntity}
|
||||
// the domain must not be necessary a 'switch' - it can be also an `input_boolean`
|
||||
domain := strings.Split(c.switchEntity, ".")[0]
|
||||
|
||||
uri := fmt.Sprintf("%s/api/services/%s/%s", c.baseURL, domain, service)
|
||||
req, _ := request.New(http.MethodPost, uri, request.MarshalJSON(data), request.JSONEncoding)
|
||||
|
||||
return c.Helper.DoJSON(req, nil)
|
||||
return c.conn.CallSwitchService(c.switchEntity, enable)
|
||||
}
|
||||
|
||||
// currentPower implements the api.Meter interface (optional)
|
||||
func (c *HomeAssistantSwitch) currentPower() (float64, error) {
|
||||
var res struct {
|
||||
State float64 `json:"state,string"`
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("%s/api/states/%s", c.baseURL, c.powerEntity)
|
||||
err := c.Helper.GetJSON(uri, &res)
|
||||
|
||||
return res.State, err
|
||||
return c.conn.GetFloatState(c.powerEntity)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
package charger
|
||||
|
||||
//go:generate go tool decorate -f decorateHomeAssistant -b *HomeAssistant -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.PhaseCurrents,Currents,func() (float64, float64, float64, error)" -t "api.PhaseVoltages,Voltages,func() (float64, float64, float64, error)" -t "api.CurrentGetter,GetMaxCurrent,func() (float64, error)"
|
||||
//go:generate go tool decorate -f decorateHomeAssistant -b *HomeAssistant -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.PhaseCurrents,Currents,func() (float64, float64, float64, error)" -t "api.PhaseVoltages,Voltages,func() (float64, float64, float64, error)"
|
||||
// -t "api.CurrentGetter,GetMaxCurrent,func() (float64, error)"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
|
|
@ -18,10 +18,6 @@ type HomeAssistant struct {
|
|||
status string
|
||||
enabled string
|
||||
enable string
|
||||
power string
|
||||
energy string
|
||||
currentsE []string
|
||||
voltagesE []string
|
||||
maxcurrent string
|
||||
}
|
||||
|
||||
|
|
@ -32,16 +28,16 @@ func init() {
|
|||
// NewHomeAssistantFromConfig creates a HomeAssistant charger from generic config
|
||||
func NewHomeAssistantFromConfig(other map[string]interface{}) (api.Charger, error) {
|
||||
cc := struct {
|
||||
BaseURL string
|
||||
URI string
|
||||
Token string
|
||||
Status string // required - sensor for charge status
|
||||
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
|
||||
MaxCurrent string // optional - number entity for setting max current
|
||||
}{}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
|
|
@ -58,7 +54,8 @@ func NewHomeAssistantFromConfig(other map[string]interface{}) (api.Charger, erro
|
|||
return nil, errors.New("missing enable switch entity")
|
||||
}
|
||||
|
||||
conn, err := homeassistant.NewConnection(cc.BaseURL, cc.Token)
|
||||
log := util.NewLogger("ha-charger")
|
||||
conn, err := homeassistant.NewConnection(log, cc.URI, cc.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -68,18 +65,29 @@ func NewHomeAssistantFromConfig(other map[string]interface{}) (api.Charger, erro
|
|||
status: cc.Status,
|
||||
enabled: cc.Enabled,
|
||||
enable: cc.Enable,
|
||||
power: cc.Power,
|
||||
energy: cc.Energy,
|
||||
maxcurrent: cc.MaxCurrent,
|
||||
}
|
||||
|
||||
// decorators for optional interfaces
|
||||
var power, energy func() (float64, error)
|
||||
var currents, voltages func() (float64, float64, float64, error)
|
||||
|
||||
if cc.Power != "" {
|
||||
power = func() (float64, error) { return conn.GetFloatState(cc.Power) }
|
||||
}
|
||||
if cc.Energy != "" {
|
||||
energy = func() (float64, error) { return conn.GetFloatState(cc.Energy) }
|
||||
}
|
||||
|
||||
// Set up phase currents (optional)
|
||||
if len(cc.Currents) > 0 {
|
||||
res, err := homeassistant.ValidatePhaseEntities(cc.Currents)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("currents: %w", err)
|
||||
}
|
||||
c.currentsE = res
|
||||
currents = func() (float64, float64, float64, error) {
|
||||
return conn.GetPhaseFloatStates(res)
|
||||
}
|
||||
}
|
||||
|
||||
// Set up phase voltages (optional)
|
||||
|
|
@ -88,45 +96,19 @@ func NewHomeAssistantFromConfig(other map[string]interface{}) (api.Charger, erro
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("voltages: %w", err)
|
||||
}
|
||||
c.voltagesE = res
|
||||
voltages = func() (float64, float64, float64, error) {
|
||||
return conn.GetPhaseFloatStates(res)
|
||||
}
|
||||
}
|
||||
|
||||
// decorators for optional interfaces
|
||||
var meter func() (float64, error)
|
||||
var meterEnergy func() (float64, error)
|
||||
var phaseCurrents func() (float64, float64, float64, error)
|
||||
var phaseVoltages func() (float64, float64, float64, error)
|
||||
var currentGetter func() (float64, error)
|
||||
|
||||
if c.maxcurrent != "" {
|
||||
currentGetter = c.getMaxCurrent
|
||||
}
|
||||
if c.power != "" {
|
||||
meter = c.currentPower
|
||||
}
|
||||
if c.energy != "" {
|
||||
meterEnergy = c.totalEnergy
|
||||
}
|
||||
if c.currentsE != nil {
|
||||
phaseCurrents = c.currents
|
||||
}
|
||||
if c.voltagesE != nil {
|
||||
phaseVoltages = c.voltages
|
||||
}
|
||||
|
||||
return decorateHomeAssistant(c, meter, meterEnergy, phaseCurrents, phaseVoltages, currentGetter), nil
|
||||
return decorateHomeAssistant(c, power, energy, currents, voltages), nil
|
||||
}
|
||||
|
||||
var _ api.Charger = (*HomeAssistant)(nil)
|
||||
|
||||
// Status implements the api.ChargeState interface
|
||||
func (c *HomeAssistant) Status() (api.ChargeStatus, error) {
|
||||
state, err := c.conn.GetState(c.status)
|
||||
if err != nil {
|
||||
return api.StatusNone, err
|
||||
}
|
||||
|
||||
return homeassistant.ParseChargeStatus(state)
|
||||
return c.conn.GetChargeStatus(c.status)
|
||||
}
|
||||
|
||||
// Enabled implements the api.Charger interface
|
||||
|
|
@ -139,42 +121,7 @@ func (c *HomeAssistant) Enable(enable bool) error {
|
|||
return c.conn.CallSwitchService(c.enable, enable)
|
||||
}
|
||||
|
||||
// MaxCurrent implements the api.CurrentController interface
|
||||
// MaxCurrent implements the api.Charger interface
|
||||
func (c *HomeAssistant) MaxCurrent(current int64) error {
|
||||
if c.maxcurrent == "" {
|
||||
return api.ErrNotAvailable
|
||||
}
|
||||
|
||||
return c.conn.CallNumberService(c.maxcurrent, float64(current))
|
||||
}
|
||||
|
||||
// getMaxCurrent implements the api.CurrentGetter interface
|
||||
func (c *HomeAssistant) getMaxCurrent() (float64, error) {
|
||||
value, err := c.conn.GetFloatState(c.maxcurrent)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Return value as integer amperes
|
||||
return math.Round(value), nil
|
||||
}
|
||||
|
||||
// currentPower implements the api.Meter interface
|
||||
func (c *HomeAssistant) currentPower() (float64, error) {
|
||||
return c.conn.GetFloatState(c.power)
|
||||
}
|
||||
|
||||
// totalEnergy implements the api.MeterEnergy interface
|
||||
func (c *HomeAssistant) totalEnergy() (float64, error) {
|
||||
return c.conn.GetFloatState(c.energy)
|
||||
}
|
||||
|
||||
// currents implements the api.PhaseCurrents interface
|
||||
func (c *HomeAssistant) currents() (float64, float64, float64, error) {
|
||||
return c.conn.GetPhaseStates(c.currentsE)
|
||||
}
|
||||
|
||||
// voltages implements the api.PhaseVoltages interface
|
||||
func (c *HomeAssistant) voltages() (float64, float64, float64, error) {
|
||||
return c.conn.GetPhaseStates(c.voltagesE)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ import (
|
|||
"github.com/evcc-io/evcc/api"
|
||||
)
|
||||
|
||||
func decorateHomeAssistant(base *HomeAssistant, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), phaseVoltages func() (float64, float64, float64, error), currentGetter func() (float64, error)) api.Charger {
|
||||
func decorateHomeAssistant(base *HomeAssistant, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), phaseVoltages func() (float64, float64, float64, error)) api.Charger {
|
||||
switch {
|
||||
case currentGetter == nil && meter == nil:
|
||||
case meter == nil:
|
||||
return base
|
||||
|
||||
case currentGetter == nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages == nil:
|
||||
case meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages == nil:
|
||||
return &struct {
|
||||
*HomeAssistant
|
||||
api.Meter
|
||||
|
|
@ -22,7 +22,7 @@ func decorateHomeAssistant(base *HomeAssistant, meter func() (float64, error), m
|
|||
},
|
||||
}
|
||||
|
||||
case currentGetter == nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages == nil:
|
||||
case meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages == nil:
|
||||
return &struct {
|
||||
*HomeAssistant
|
||||
api.Meter
|
||||
|
|
@ -37,7 +37,7 @@ func decorateHomeAssistant(base *HomeAssistant, meter func() (float64, error), m
|
|||
},
|
||||
}
|
||||
|
||||
case currentGetter == nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages == nil:
|
||||
case meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages == nil:
|
||||
return &struct {
|
||||
*HomeAssistant
|
||||
api.Meter
|
||||
|
|
@ -52,7 +52,7 @@ func decorateHomeAssistant(base *HomeAssistant, meter func() (float64, error), m
|
|||
},
|
||||
}
|
||||
|
||||
case currentGetter == nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages == nil:
|
||||
case meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages == nil:
|
||||
return &struct {
|
||||
*HomeAssistant
|
||||
api.Meter
|
||||
|
|
@ -71,7 +71,7 @@ func decorateHomeAssistant(base *HomeAssistant, meter func() (float64, error), m
|
|||
},
|
||||
}
|
||||
|
||||
case currentGetter == nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages != nil:
|
||||
case meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages != nil:
|
||||
return &struct {
|
||||
*HomeAssistant
|
||||
api.Meter
|
||||
|
|
@ -86,7 +86,7 @@ func decorateHomeAssistant(base *HomeAssistant, meter func() (float64, error), m
|
|||
},
|
||||
}
|
||||
|
||||
case currentGetter == nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages != nil:
|
||||
case meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages != nil:
|
||||
return &struct {
|
||||
*HomeAssistant
|
||||
api.Meter
|
||||
|
|
@ -105,7 +105,7 @@ func decorateHomeAssistant(base *HomeAssistant, meter func() (float64, error), m
|
|||
},
|
||||
}
|
||||
|
||||
case currentGetter == nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages != nil:
|
||||
case meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages != nil:
|
||||
return &struct {
|
||||
*HomeAssistant
|
||||
api.Meter
|
||||
|
|
@ -124,7 +124,7 @@ func decorateHomeAssistant(base *HomeAssistant, meter func() (float64, error), m
|
|||
},
|
||||
}
|
||||
|
||||
case currentGetter == nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages != nil:
|
||||
case meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages != nil:
|
||||
return &struct {
|
||||
*HomeAssistant
|
||||
api.Meter
|
||||
|
|
@ -146,198 +146,11 @@ func decorateHomeAssistant(base *HomeAssistant, meter func() (float64, error), m
|
|||
phaseVoltages: phaseVoltages,
|
||||
},
|
||||
}
|
||||
|
||||
case currentGetter != nil && meter == nil:
|
||||
return &struct {
|
||||
*HomeAssistant
|
||||
api.CurrentGetter
|
||||
}{
|
||||
HomeAssistant: base,
|
||||
CurrentGetter: &decorateHomeAssistantCurrentGetterImpl{
|
||||
currentGetter: currentGetter,
|
||||
},
|
||||
}
|
||||
|
||||
case currentGetter != nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages == nil:
|
||||
return &struct {
|
||||
*HomeAssistant
|
||||
api.CurrentGetter
|
||||
api.Meter
|
||||
}{
|
||||
HomeAssistant: base,
|
||||
CurrentGetter: &decorateHomeAssistantCurrentGetterImpl{
|
||||
currentGetter: currentGetter,
|
||||
},
|
||||
Meter: &decorateHomeAssistantMeterImpl{
|
||||
meter: meter,
|
||||
},
|
||||
}
|
||||
|
||||
case currentGetter != nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages == nil:
|
||||
return &struct {
|
||||
*HomeAssistant
|
||||
api.CurrentGetter
|
||||
api.Meter
|
||||
api.MeterEnergy
|
||||
}{
|
||||
HomeAssistant: base,
|
||||
CurrentGetter: &decorateHomeAssistantCurrentGetterImpl{
|
||||
currentGetter: currentGetter,
|
||||
},
|
||||
Meter: &decorateHomeAssistantMeterImpl{
|
||||
meter: meter,
|
||||
},
|
||||
MeterEnergy: &decorateHomeAssistantMeterEnergyImpl{
|
||||
meterEnergy: meterEnergy,
|
||||
},
|
||||
}
|
||||
|
||||
case currentGetter != nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages == nil:
|
||||
return &struct {
|
||||
*HomeAssistant
|
||||
api.CurrentGetter
|
||||
api.Meter
|
||||
api.PhaseCurrents
|
||||
}{
|
||||
HomeAssistant: base,
|
||||
CurrentGetter: &decorateHomeAssistantCurrentGetterImpl{
|
||||
currentGetter: currentGetter,
|
||||
},
|
||||
Meter: &decorateHomeAssistantMeterImpl{
|
||||
meter: meter,
|
||||
},
|
||||
PhaseCurrents: &decorateHomeAssistantPhaseCurrentsImpl{
|
||||
phaseCurrents: phaseCurrents,
|
||||
},
|
||||
}
|
||||
|
||||
case currentGetter != nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages == nil:
|
||||
return &struct {
|
||||
*HomeAssistant
|
||||
api.CurrentGetter
|
||||
api.Meter
|
||||
api.MeterEnergy
|
||||
api.PhaseCurrents
|
||||
}{
|
||||
HomeAssistant: base,
|
||||
CurrentGetter: &decorateHomeAssistantCurrentGetterImpl{
|
||||
currentGetter: currentGetter,
|
||||
},
|
||||
Meter: &decorateHomeAssistantMeterImpl{
|
||||
meter: meter,
|
||||
},
|
||||
MeterEnergy: &decorateHomeAssistantMeterEnergyImpl{
|
||||
meterEnergy: meterEnergy,
|
||||
},
|
||||
PhaseCurrents: &decorateHomeAssistantPhaseCurrentsImpl{
|
||||
phaseCurrents: phaseCurrents,
|
||||
},
|
||||
}
|
||||
|
||||
case currentGetter != nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages != nil:
|
||||
return &struct {
|
||||
*HomeAssistant
|
||||
api.CurrentGetter
|
||||
api.Meter
|
||||
api.PhaseVoltages
|
||||
}{
|
||||
HomeAssistant: base,
|
||||
CurrentGetter: &decorateHomeAssistantCurrentGetterImpl{
|
||||
currentGetter: currentGetter,
|
||||
},
|
||||
Meter: &decorateHomeAssistantMeterImpl{
|
||||
meter: meter,
|
||||
},
|
||||
PhaseVoltages: &decorateHomeAssistantPhaseVoltagesImpl{
|
||||
phaseVoltages: phaseVoltages,
|
||||
},
|
||||
}
|
||||
|
||||
case currentGetter != nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages != nil:
|
||||
return &struct {
|
||||
*HomeAssistant
|
||||
api.CurrentGetter
|
||||
api.Meter
|
||||
api.MeterEnergy
|
||||
api.PhaseVoltages
|
||||
}{
|
||||
HomeAssistant: base,
|
||||
CurrentGetter: &decorateHomeAssistantCurrentGetterImpl{
|
||||
currentGetter: currentGetter,
|
||||
},
|
||||
Meter: &decorateHomeAssistantMeterImpl{
|
||||
meter: meter,
|
||||
},
|
||||
MeterEnergy: &decorateHomeAssistantMeterEnergyImpl{
|
||||
meterEnergy: meterEnergy,
|
||||
},
|
||||
PhaseVoltages: &decorateHomeAssistantPhaseVoltagesImpl{
|
||||
phaseVoltages: phaseVoltages,
|
||||
},
|
||||
}
|
||||
|
||||
case currentGetter != nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages != nil:
|
||||
return &struct {
|
||||
*HomeAssistant
|
||||
api.CurrentGetter
|
||||
api.Meter
|
||||
api.PhaseCurrents
|
||||
api.PhaseVoltages
|
||||
}{
|
||||
HomeAssistant: base,
|
||||
CurrentGetter: &decorateHomeAssistantCurrentGetterImpl{
|
||||
currentGetter: currentGetter,
|
||||
},
|
||||
Meter: &decorateHomeAssistantMeterImpl{
|
||||
meter: meter,
|
||||
},
|
||||
PhaseCurrents: &decorateHomeAssistantPhaseCurrentsImpl{
|
||||
phaseCurrents: phaseCurrents,
|
||||
},
|
||||
PhaseVoltages: &decorateHomeAssistantPhaseVoltagesImpl{
|
||||
phaseVoltages: phaseVoltages,
|
||||
},
|
||||
}
|
||||
|
||||
case currentGetter != nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages != nil:
|
||||
return &struct {
|
||||
*HomeAssistant
|
||||
api.CurrentGetter
|
||||
api.Meter
|
||||
api.MeterEnergy
|
||||
api.PhaseCurrents
|
||||
api.PhaseVoltages
|
||||
}{
|
||||
HomeAssistant: base,
|
||||
CurrentGetter: &decorateHomeAssistantCurrentGetterImpl{
|
||||
currentGetter: currentGetter,
|
||||
},
|
||||
Meter: &decorateHomeAssistantMeterImpl{
|
||||
meter: meter,
|
||||
},
|
||||
MeterEnergy: &decorateHomeAssistantMeterEnergyImpl{
|
||||
meterEnergy: meterEnergy,
|
||||
},
|
||||
PhaseCurrents: &decorateHomeAssistantPhaseCurrentsImpl{
|
||||
phaseCurrents: phaseCurrents,
|
||||
},
|
||||
PhaseVoltages: &decorateHomeAssistantPhaseVoltagesImpl{
|
||||
phaseVoltages: phaseVoltages,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type decorateHomeAssistantCurrentGetterImpl struct {
|
||||
currentGetter func() (float64, error)
|
||||
}
|
||||
|
||||
func (impl *decorateHomeAssistantCurrentGetterImpl) GetMaxCurrent() (float64, error) {
|
||||
return impl.currentGetter()
|
||||
}
|
||||
|
||||
type decorateHomeAssistantMeterImpl struct {
|
||||
meter func() (float64, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,12 +27,12 @@ func init() {
|
|||
// NewHomeAssistantFromConfig creates a HomeAssistant meter from generic config
|
||||
func NewHomeAssistantFromConfig(other map[string]interface{}) (api.Meter, error) {
|
||||
cc := struct {
|
||||
BaseURL string `mapstructure:"baseurl"`
|
||||
Token string `mapstructure:"token"`
|
||||
Power string `mapstructure:"power"`
|
||||
Energy string `mapstructure:"energy"`
|
||||
Currents []string `mapstructure:"currents"`
|
||||
Voltages []string `mapstructure:"voltages"`
|
||||
URI string
|
||||
Token string
|
||||
Power string
|
||||
Energy string
|
||||
Currents []string
|
||||
Voltages []string
|
||||
}{}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
|
|
@ -43,7 +43,8 @@ func NewHomeAssistantFromConfig(other map[string]interface{}) (api.Meter, error)
|
|||
return nil, errors.New("missing power sensor entity")
|
||||
}
|
||||
|
||||
conn, err := homeassistant.NewConnection(cc.BaseURL, cc.Token)
|
||||
log := util.NewLogger("ha-meter")
|
||||
conn, err := homeassistant.NewConnection(log, cc.URI, cc.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -73,43 +74,20 @@ func NewHomeAssistantFromConfig(other map[string]interface{}) (api.Meter, error)
|
|||
}
|
||||
|
||||
// decorators for optional interfaces
|
||||
var meterEnergy func() (float64, error)
|
||||
var phaseCurrents func() (float64, float64, float64, error)
|
||||
var phaseVoltages func() (float64, float64, float64, error)
|
||||
var energy func() (float64, error)
|
||||
var currents, voltages func() (float64, float64, float64, error)
|
||||
|
||||
if m.energy != "" {
|
||||
meterEnergy = m.TotalEnergy
|
||||
energy = m.totalEnergy
|
||||
}
|
||||
if m.currentEntities[0] != "" {
|
||||
phaseCurrents = m.Currents
|
||||
currents = m.currents
|
||||
}
|
||||
if m.voltageEntities[0] != "" {
|
||||
phaseVoltages = m.Voltages
|
||||
voltages = m.voltages
|
||||
}
|
||||
|
||||
return decorateHomeAssistant(m, meterEnergy, phaseCurrents, phaseVoltages), nil
|
||||
}
|
||||
|
||||
// NewHomeAssistant creates HomeAssistant meter
|
||||
func NewHomeAssistant(baseURL, token, power, energy string, currents, voltages []string) (*HomeAssistant, error) {
|
||||
if power == "" {
|
||||
return nil, errors.New("missing power sensor entity")
|
||||
}
|
||||
|
||||
conn, err := homeassistant.NewConnection(baseURL, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := &HomeAssistant{
|
||||
conn: conn,
|
||||
power: power,
|
||||
energy: energy,
|
||||
currentEntities: currents,
|
||||
voltageEntities: voltages,
|
||||
}
|
||||
|
||||
return m, nil
|
||||
return decorateHomeAssistant(m, energy, currents, voltages), nil
|
||||
}
|
||||
|
||||
var _ api.Meter = (*HomeAssistant)(nil)
|
||||
|
|
@ -119,26 +97,17 @@ func (m *HomeAssistant) CurrentPower() (float64, error) {
|
|||
return m.conn.GetFloatState(m.power)
|
||||
}
|
||||
|
||||
// TotalEnergy implements the api.MeterEnergy interface
|
||||
func (m *HomeAssistant) TotalEnergy() (float64, error) {
|
||||
if m.energy == "" {
|
||||
return 0, api.ErrNotAvailable
|
||||
}
|
||||
// totalEnergy implements the api.MeterEnergy interface
|
||||
func (m *HomeAssistant) totalEnergy() (float64, error) {
|
||||
return m.conn.GetFloatState(m.energy)
|
||||
}
|
||||
|
||||
// Currents implements the api.PhaseCurrents interface
|
||||
func (m *HomeAssistant) Currents() (float64, float64, float64, error) {
|
||||
if m.currentEntities[0] == "" {
|
||||
return 0, 0, 0, api.ErrNotAvailable
|
||||
}
|
||||
return m.conn.GetPhaseStates(m.currentEntities)
|
||||
// currents implements the api.PhaseCurrents interface
|
||||
func (m *HomeAssistant) currents() (float64, float64, float64, error) {
|
||||
return m.conn.GetPhaseFloatStates(m.currentEntities)
|
||||
}
|
||||
|
||||
// Voltages implements the api.PhaseVoltages interface
|
||||
func (m *HomeAssistant) Voltages() (float64, float64, float64, error) {
|
||||
if m.voltageEntities[0] == "" {
|
||||
return 0, 0, 0, api.ErrNotAvailable
|
||||
}
|
||||
return m.conn.GetPhaseStates(m.voltageEntities)
|
||||
// voltages implements the api.PhaseVoltages interface
|
||||
func (m *HomeAssistant) voltages() (float64, float64, float64, error) {
|
||||
return m.conn.GetPhaseFloatStates(m.voltageEntities)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,8 @@ requirements:
|
|||
evcc: ["skiptest"]
|
||||
params:
|
||||
- name: baseurl
|
||||
description:
|
||||
de: Basis-URL der Home Assistant Instanz
|
||||
en: Base URL of the Home Assistant instance
|
||||
deprecated: true
|
||||
- name: uri
|
||||
required: true
|
||||
example: http://homeassistant.local:8123
|
||||
- name: token
|
||||
|
|
@ -37,7 +36,7 @@ params:
|
|||
- preset: switchsocket
|
||||
render: |
|
||||
type: homeassistant-switch
|
||||
baseurl: {{ .baseurl }}
|
||||
uri: {{ if (eq .uri "") }}{{ .baseurl }}{{ else }}{{ .uri }}{{ end }}
|
||||
token: {{ .token }}
|
||||
switchentity: {{ .switchentity }}
|
||||
powerentity: {{ .powerentity }}
|
||||
|
|
|
|||
|
|
@ -11,11 +11,8 @@ requirements:
|
|||
params:
|
||||
- preset: vehicle-common
|
||||
- name: uri
|
||||
description:
|
||||
de: Home Assistant URI
|
||||
en: Home Assistant URI
|
||||
example: "http://192.168.1.10:8123"
|
||||
required: true
|
||||
example: http://homeassistant.local:8123
|
||||
- name: token
|
||||
description:
|
||||
de: Home Assistant Long-Lived Access Token
|
||||
|
|
|
|||
|
|
@ -21,18 +21,17 @@ type Connection struct {
|
|||
}
|
||||
|
||||
// NewConnection creates a new Home Assistant connection
|
||||
func NewConnection(baseURL, token string) (*Connection, error) {
|
||||
if baseURL == "" {
|
||||
return nil, errors.New("missing baseURL")
|
||||
func NewConnection(log *util.Logger, uri, token string) (*Connection, error) {
|
||||
if uri == "" {
|
||||
return nil, errors.New("missing uri")
|
||||
}
|
||||
if token == "" {
|
||||
return nil, errors.New("missing token")
|
||||
}
|
||||
|
||||
log := util.NewLogger("homeassistant")
|
||||
c := &Connection{
|
||||
Helper: request.NewHelper(log.Redact(token)),
|
||||
uri: strings.TrimSuffix(baseURL, "/"),
|
||||
uri: strings.TrimSuffix(uri, "/"),
|
||||
}
|
||||
|
||||
// Set up authentication headers
|
||||
|
|
@ -49,8 +48,8 @@ func NewConnection(baseURL, token string) (*Connection, error) {
|
|||
|
||||
// StateResponse represents a Home Assistant entity state
|
||||
type StateResponse struct {
|
||||
State string `json:"state"`
|
||||
Attributes map[string]interface{} `json:"attributes"`
|
||||
State string `json:"state"`
|
||||
Attributes map[string]any `json:"attributes"`
|
||||
}
|
||||
|
||||
// GetState retrieves the state of an entity
|
||||
|
|
@ -69,6 +68,21 @@ func (c *Connection) GetState(entity string) (string, error) {
|
|||
return res.State, nil
|
||||
}
|
||||
|
||||
// GetIntState retrieves the state of an entity as int64
|
||||
func (c *Connection) GetIntState(entity string) (int64, error) {
|
||||
state, err := c.GetState(entity)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
value, err := strconv.ParseInt(state, 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid numeric state '%s' for entity %s: %w", state, entity, err)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// GetFloatState retrieves the state of an entity as float64
|
||||
func (c *Connection) GetFloatState(entity string) (float64, error) {
|
||||
state, err := c.GetState(entity)
|
||||
|
|
@ -102,8 +116,54 @@ func (c *Connection) GetBoolState(entity string) (bool, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// chargeStatusMap maps Home Assistant states to EVCC charge status
|
||||
var chargeStatusMap = map[string]api.ChargeStatus{
|
||||
// Status C - Charging
|
||||
"c": api.StatusC,
|
||||
"charging": api.StatusC,
|
||||
"on": api.StatusC,
|
||||
"true": api.StatusC,
|
||||
"active": api.StatusC,
|
||||
"1": api.StatusC,
|
||||
|
||||
// Status B - Connected/Ready
|
||||
"b": api.StatusB,
|
||||
"connected": api.StatusB,
|
||||
"ready": api.StatusB,
|
||||
"plugged": api.StatusB,
|
||||
"charging_completed": api.StatusB,
|
||||
"initialising": api.StatusB,
|
||||
"preparing": api.StatusB,
|
||||
"2": api.StatusB,
|
||||
|
||||
// Status A - Disconnected
|
||||
"a": api.StatusA,
|
||||
"disconnected": api.StatusA,
|
||||
"off": api.StatusA,
|
||||
"none": api.StatusA,
|
||||
"unavailable": api.StatusA,
|
||||
"unknown": api.StatusA,
|
||||
"notreadyforcharging": api.StatusA,
|
||||
"not_plugged": api.StatusA,
|
||||
"0": api.StatusA,
|
||||
}
|
||||
|
||||
// GetChargeStatus maps Home Assistant states to api.ChargeStatus
|
||||
func (c *Connection) GetChargeStatus(entity string) (api.ChargeStatus, error) {
|
||||
state, err := c.GetState(entity)
|
||||
if err != nil {
|
||||
return api.StatusNone, err
|
||||
}
|
||||
|
||||
if status, ok := chargeStatusMap[strings.ToLower(strings.TrimSpace(state))]; ok {
|
||||
return status, nil
|
||||
}
|
||||
|
||||
return api.StatusNone, fmt.Errorf("unknown charge status: %s", state)
|
||||
}
|
||||
|
||||
// CallService calls a Home Assistant service
|
||||
func (c *Connection) CallService(domain, service string, data map[string]interface{}) error {
|
||||
func (c *Connection) CallService(domain, service string, data map[string]any) error {
|
||||
uri := fmt.Sprintf("%s/api/services/%s/%s", c.uri, domain, service)
|
||||
|
||||
req, err := request.New(http.MethodPost, uri, request.MarshalJSON(data), request.JSONEncoding)
|
||||
|
|
@ -128,7 +188,7 @@ func (c *Connection) CallSwitchService(entity string, turnOn bool) error {
|
|||
service = "turn_on"
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
data := map[string]any{
|
||||
"entity_id": entity,
|
||||
}
|
||||
|
||||
|
|
@ -137,7 +197,7 @@ func (c *Connection) CallSwitchService(entity string, turnOn bool) error {
|
|||
|
||||
// CallNumberService is a convenience method for setting number entity values
|
||||
func (c *Connection) CallNumberService(entity string, value float64) error {
|
||||
data := map[string]interface{}{
|
||||
data := map[string]any{
|
||||
"entity_id": entity,
|
||||
"value": value,
|
||||
}
|
||||
|
|
@ -145,8 +205,8 @@ func (c *Connection) CallNumberService(entity string, value float64) error {
|
|||
return c.CallService("number", "set_value", data)
|
||||
}
|
||||
|
||||
// GetPhaseStates retrieves three phase values (currents, voltages, etc.)
|
||||
func (c *Connection) GetPhaseStates(entities []string) (float64, float64, float64, error) {
|
||||
// GetPhaseFloatStates retrieves three phase values (currents, voltages, etc.)
|
||||
func (c *Connection) GetPhaseFloatStates(entities []string) (float64, float64, float64, error) {
|
||||
if len(entities) != 3 {
|
||||
return 0, 0, 0, errors.New("invalid phase entities")
|
||||
}
|
||||
|
|
@ -180,45 +240,3 @@ func ValidatePhaseEntities(entities []string) ([]string, error) {
|
|||
return nil, fmt.Errorf("must contain three-phase entities (L1, L2, L3), got %d", len(entities))
|
||||
}
|
||||
}
|
||||
|
||||
// chargeStatusMap maps Home Assistant states to EVCC charge status
|
||||
var chargeStatusMap = map[string]api.ChargeStatus{
|
||||
// Status C - Charging
|
||||
"c": api.StatusC,
|
||||
"charging": api.StatusC,
|
||||
"on": api.StatusC,
|
||||
"true": api.StatusC,
|
||||
"active": api.StatusC,
|
||||
"1": api.StatusC,
|
||||
|
||||
// Status B - Connected/Ready
|
||||
"b": api.StatusB,
|
||||
"connected": api.StatusB,
|
||||
"ready": api.StatusB,
|
||||
"plugged": api.StatusB,
|
||||
"charging_completed": api.StatusB,
|
||||
"initialising": api.StatusB,
|
||||
"preparing": api.StatusB,
|
||||
"2": api.StatusB,
|
||||
|
||||
// Status A - Disconnected
|
||||
"a": api.StatusA,
|
||||
"disconnected": api.StatusA,
|
||||
"off": api.StatusA,
|
||||
"none": api.StatusA,
|
||||
"unavailable": api.StatusA,
|
||||
"unknown": api.StatusA,
|
||||
"notreadyforcharging": api.StatusA,
|
||||
"not_plugged": api.StatusA,
|
||||
"0": api.StatusA,
|
||||
}
|
||||
|
||||
// ParseChargeStatus maps Home Assistant states to EVCC charge status
|
||||
func ParseChargeStatus(state string) (api.ChargeStatus, error) {
|
||||
normalized := strings.ToLower(strings.TrimSpace(state))
|
||||
if status, ok := chargeStatusMap[normalized]; ok {
|
||||
return status, nil
|
||||
}
|
||||
|
||||
return api.StatusNone, fmt.Errorf("unknown charge status: %s", state)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,25 +2,18 @@ package vehicle
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/request"
|
||||
"github.com/evcc-io/evcc/util/transport"
|
||||
"github.com/evcc-io/evcc/util/homeassistant"
|
||||
)
|
||||
|
||||
type HomeAssistant struct {
|
||||
*embed
|
||||
*request.Helper
|
||||
uri string
|
||||
soc string
|
||||
conn *homeassistant.Connection
|
||||
soc string
|
||||
}
|
||||
|
||||
// Register on startup
|
||||
|
|
@ -64,66 +57,57 @@ func NewHomeAssistantVehicleFromConfig(other map[string]any) (api.Vehicle, error
|
|||
return nil, errors.New("missing soc sensor")
|
||||
}
|
||||
|
||||
res := &HomeAssistant{
|
||||
embed: &cc.embed,
|
||||
Helper: request.NewHelper(util.NewLogger("ha-vehicle").Redact(cc.Token)),
|
||||
uri: strings.TrimSuffix(cc.URI, "/"),
|
||||
soc: cc.Sensors.Soc,
|
||||
log := util.NewLogger("ha-vehicle")
|
||||
conn, err := homeassistant.NewConnection(log, cc.URI, cc.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res.Client.Transport = &transport.Decorator{
|
||||
Base: res.Client.Transport,
|
||||
Decorator: transport.DecorateHeaders(map[string]string{
|
||||
"Authorization": "Bearer " + cc.Token,
|
||||
}),
|
||||
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)
|
||||
chargeEnable func(bool) error
|
||||
wakeup func() error
|
||||
maxCurrent func(int64) error
|
||||
getMaxCurrent func() (float64, error)
|
||||
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) { return res.getIntSensor(cc.Sensors.LimitSoc) }
|
||||
limitSoc = func() (int64, error) { return conn.GetIntState(cc.Sensors.LimitSoc) }
|
||||
}
|
||||
if cc.Sensors.Status != "" {
|
||||
status = func() (api.ChargeStatus, error) { return res.status(cc.Sensors.Status) }
|
||||
status = func() (api.ChargeStatus, error) { return conn.GetChargeStatus(cc.Sensors.Status) }
|
||||
}
|
||||
if cc.Sensors.Range != "" {
|
||||
rng = func() (int64, error) { return res.getIntSensor(cc.Sensors.Range) }
|
||||
rng = func() (int64, error) { return conn.GetIntState(cc.Sensors.Range) }
|
||||
}
|
||||
if cc.Sensors.Odometer != "" {
|
||||
odo = func() (float64, error) { return res.getFloatSensor(cc.Sensors.Odometer) }
|
||||
odo = func() (float64, error) { return conn.GetFloatState(cc.Sensors.Odometer) }
|
||||
}
|
||||
if cc.Sensors.Climater != "" {
|
||||
climater = func() (bool, error) { return res.getBoolSensor(cc.Sensors.Climater) }
|
||||
climater = func() (bool, error) { return conn.GetBoolState(cc.Sensors.Climater) }
|
||||
}
|
||||
if cc.Sensors.FinishTime != "" {
|
||||
finish = func() (time.Time, error) { return res.getTimeSensor(cc.Sensors.FinishTime) }
|
||||
finish = func() (time.Time, error) { return res.finishTime(cc.Sensors.FinishTime) }
|
||||
}
|
||||
if cc.Services.Start != "" && cc.Services.Stop != "" {
|
||||
chargeEnable = func(enable bool) error {
|
||||
if enable {
|
||||
return res.callScript(cc.Services.Start)
|
||||
}
|
||||
return res.callScript(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 res.callScript(cc.Services.Wakeup) }
|
||||
wakeup = func() error { return conn.CallSwitchService(cc.Services.Wakeup, true) }
|
||||
}
|
||||
if cc.Services.SetMaxCurrent != "" {
|
||||
maxCurrent = func(current int64) error { return res.setMaxCurrent(cc.Services.SetMaxCurrent, current) }
|
||||
getMaxCurrent = func() (float64, error) { return res.getFloatSensor(cc.Services.SetMaxCurrent) }
|
||||
maxcurrent = func(current int64) error { return conn.CallNumberService(cc.Services.SetMaxCurrent, float64(current)) }
|
||||
}
|
||||
|
||||
// decorate all features
|
||||
|
|
@ -134,109 +118,20 @@ func NewHomeAssistantVehicleFromConfig(other map[string]any) (api.Vehicle, error
|
|||
rng,
|
||||
odo,
|
||||
climater,
|
||||
maxCurrent,
|
||||
getMaxCurrent,
|
||||
maxcurrent,
|
||||
nil,
|
||||
finish,
|
||||
wakeup,
|
||||
chargeEnable,
|
||||
enable,
|
||||
), nil
|
||||
}
|
||||
|
||||
func (v *HomeAssistant) Soc() (float64, error) {
|
||||
return v.getFloatSensor(v.soc)
|
||||
return v.conn.GetFloatState(v.soc)
|
||||
}
|
||||
|
||||
// Calls /api/states/<entity> and returns .state
|
||||
func (v *HomeAssistant) getState(entity string) (string, error) {
|
||||
var res struct {
|
||||
State string `json:"state"`
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("%s/api/states/%s", v.uri, url.PathEscape(entity))
|
||||
if err := v.GetJSON(uri, &res); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if res.State == "unknown" || res.State == "unavailable" {
|
||||
return "", api.ErrNotAvailable
|
||||
}
|
||||
|
||||
return res.State, nil
|
||||
}
|
||||
|
||||
func (v *HomeAssistant) callScript(script string) error {
|
||||
// All configured services are scripts, so always use script.turn_on
|
||||
payload := fmt.Sprintf(`{"entity_id": "%s"}`, script)
|
||||
uri := fmt.Sprintf("%s/api/services/script/turn_on", v.uri)
|
||||
req, _ := request.New(http.MethodPost, uri, strings.NewReader(payload))
|
||||
_, err := v.DoBody(req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (v *HomeAssistant) setMaxCurrent(entity string, current int64) error {
|
||||
// Determine service domain from entity prefix
|
||||
parts := strings.SplitN(entity, ".", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid entity format: %s", entity)
|
||||
}
|
||||
|
||||
domain := parts[0]
|
||||
var service string
|
||||
|
||||
switch domain {
|
||||
case "number":
|
||||
service = "set_value"
|
||||
case "input_number":
|
||||
service = "set_value"
|
||||
default:
|
||||
return fmt.Errorf("unsupported entity domain: %s", domain)
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"entity_id": entity,
|
||||
"value": current,
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("%s/api/services/%s/%s", v.uri, domain, service)
|
||||
req, _ := request.New(http.MethodPost, uri, request.MarshalJSON(data), request.JSONEncoding)
|
||||
_, err := v.DoBody(req)
|
||||
return err
|
||||
}
|
||||
|
||||
// generic helpers for fetching and parsing sensor values
|
||||
func (v *HomeAssistant) getFloatSensor(entity string) (float64, error) {
|
||||
s, err := v.getState(entity)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return strconv.ParseFloat(s, 64)
|
||||
}
|
||||
|
||||
func (v *HomeAssistant) getIntSensor(entity string) (int64, error) {
|
||||
s, err := v.getState(entity)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int64(f), nil // truncation
|
||||
}
|
||||
|
||||
func (v *HomeAssistant) getBoolSensor(entity string) (bool, error) {
|
||||
s, err := v.getState(entity)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return slices.Contains([]string{"on", "true", "1", "active"}, strings.ToLower(s)), nil
|
||||
}
|
||||
|
||||
func (v *HomeAssistant) getTimeSensor(entity string) (time.Time, error) {
|
||||
s, err := v.getState(entity)
|
||||
func (v *HomeAssistant) finishTime(entity string) (time.Time, error) {
|
||||
s, err := v.conn.GetState(entity)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
|
@ -248,39 +143,10 @@ func (v *HomeAssistant) getTimeSensor(entity string) (time.Time, error) {
|
|||
return time.Parse(time.RFC3339, s)
|
||||
}
|
||||
|
||||
// status returns evcc charge status (optional, private)
|
||||
func (v *HomeAssistant) status(sensor string) (api.ChargeStatus, error) {
|
||||
var haStatusMap = map[string]api.ChargeStatus{
|
||||
"c": api.StatusC,
|
||||
"charging": api.StatusC,
|
||||
"on": api.StatusC,
|
||||
"true": api.StatusC,
|
||||
"active": api.StatusC,
|
||||
"b": api.StatusB,
|
||||
"connected": api.StatusB,
|
||||
"ready": api.StatusB,
|
||||
"plugged": api.StatusB,
|
||||
"charging_completed": api.StatusB,
|
||||
"initialising": api.StatusB,
|
||||
"a": api.StatusA,
|
||||
"disconnected": api.StatusA,
|
||||
"off": api.StatusA,
|
||||
"none": api.StatusA,
|
||||
"unavailable": api.StatusA,
|
||||
"unknown": api.StatusA,
|
||||
"notreadyforcharging": api.StatusA,
|
||||
"not_plugged": api.StatusA,
|
||||
func (v *HomeAssistant) enable(on, off string, enable bool) error {
|
||||
if enable {
|
||||
return v.conn.CallSwitchService(on, true)
|
||||
}
|
||||
|
||||
s, err := v.getState(sensor)
|
||||
if err != nil {
|
||||
return api.StatusNone, err
|
||||
}
|
||||
|
||||
state := strings.ToLower(s)
|
||||
if mapped, ok := haStatusMap[state]; ok {
|
||||
return mapped, nil
|
||||
}
|
||||
|
||||
return api.StatusNone, errors.New("invalid state: " + s)
|
||||
return v.conn.CallSwitchService(off, true)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue