diff --git a/charger/homeassistant.go b/charger/homeassistant.go index 852a56850..2f31b6d63 100644 --- a/charger/homeassistant.go +++ b/charger/homeassistant.go @@ -1,11 +1,12 @@ package charger -//go:generate go tool decorate -f decorateHomeAssistant -b *HomeAssistant -r api.Charger -t api.Meter,api.MeterEnergy,api.PhaseCurrents,api.PhaseVoltages +//go:generate go tool decorate -f decorateHomeAssistant -b *HomeAssistant -r api.Charger -t api.Meter,api.MeterEnergy,api.PhaseCurrents,api.PhaseVoltages,api.PhaseSwitcher,api.PhaseGetter // -t api.CurrentGetter import ( "errors" "fmt" + "strconv" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util" @@ -39,6 +40,7 @@ func NewHomeAssistantFromConfig(other map[string]any) (api.Charger, error) { 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 { @@ -76,6 +78,8 @@ func NewHomeAssistantFromConfig(other map[string]any) (api.Charger, error) { // decorators for optional interfaces var power, energy func() (float64, error) var currents, voltages func() (float64, float64, float64, error) + var phases1p3p func(int) error + var phasesG func() (int, error) if cc.Power != "" { power = func() (float64, error) { return conn.GetFloatState(cc.Power) } @@ -98,7 +102,22 @@ func NewHomeAssistantFromConfig(other map[string]any) (api.Charger, error) { return nil, fmt.Errorf("voltages: %w", err) } - return decorateHomeAssistant(c, power, energy, currents, voltages), nil + // phase switching (optional) + if cc.Phases != "" { + phases1p3p = func(phases int) error { + return conn.CallSelectService(cc.Phases, strconv.Itoa(phases)) + } + + phasesG = func() (int, error) { + val, err := conn.GetIntState(cc.Phases) + if err != nil { + return 0, err + } + return int(val), nil + } + } + + return decorateHomeAssistant(c, power, energy, currents, voltages, phases1p3p, phasesG), nil } var _ api.Charger = (*HomeAssistant)(nil) diff --git a/charger/homeassistant_decorators.go b/charger/homeassistant_decorators.go index d16f901c7..2040ad1fb 100644 --- a/charger/homeassistant_decorators.go +++ b/charger/homeassistant_decorators.go @@ -8,7 +8,7 @@ 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)) 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), phaseSwitcher func(int) error, phaseGetter func() (int, error)) api.Charger { caps := make(map[reflect.Type]any) if meter != nil { @@ -27,6 +27,14 @@ func decorateHomeAssistant(base *HomeAssistant, meter func() (float64, error), m caps[reflect.TypeFor[api.PhaseVoltages]()] = &decorateHomeAssistantPhaseVoltagesImpl{phaseVoltages: phaseVoltages} } + if phaseSwitcher != nil { + caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateHomeAssistantPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} + } + + if phaseGetter != nil { + caps[reflect.TypeFor[api.PhaseGetter]()] = &decorateHomeAssistantPhaseGetterImpl{phaseGetter: phaseGetter} + } + if len(caps) == 0 { return base } @@ -68,6 +76,22 @@ func (impl *decorateHomeAssistantPhaseCurrentsImpl) Currents() (float64, float64 return impl.phaseCurrents() } +type decorateHomeAssistantPhaseGetterImpl struct { + phaseGetter func() (int, error) +} + +func (impl *decorateHomeAssistantPhaseGetterImpl) GetPhases() (int, error) { + return impl.phaseGetter() +} + +type decorateHomeAssistantPhaseSwitcherImpl struct { + phaseSwitcher func(int) error +} + +func (impl *decorateHomeAssistantPhaseSwitcherImpl) Phases1p3p(p0 int) error { + return impl.phaseSwitcher(p0) +} + type decorateHomeAssistantPhaseVoltagesImpl struct { phaseVoltages func() (float64, float64, float64, error) } diff --git a/templates/definition/charger/homeassistant.yaml b/templates/definition/charger/homeassistant.yaml index 4dc44a19b..9cc8c82bb 100644 --- a/templates/definition/charger/homeassistant.yaml +++ b/templates/definition/charger/homeassistant.yaml @@ -142,6 +142,17 @@ params: help: en: Entity ID for L3 voltage measurement in volts de: Entitäts-ID für L3 Spannungsmessung in Volt + + - name: phases + description: + de: Phasenumschaltungs-Entität + en: Phase switching entity + service: homeassistant/entities?uri={uri}&domain=select,input_select + example: "select.charger_phases" + advanced: true + help: + en: Entity ID for 1p/3p phase switching (select entity with options "1" and "3") + de: Entitäts-ID für 1p/3p Phasenumschaltung (Select-Entität mit Optionen "1" und "3") render: | type: homeassistant uri: {{ .uri }} @@ -163,3 +174,6 @@ render: | - {{ .voltageL2 }} - {{ .voltageL3 }} {{- end }} + {{- if .phases }} + phases: {{ .phases }} + {{- end }} diff --git a/util/homeassistant/connection.go b/util/homeassistant/connection.go index c05e66811..1b7f6456e 100644 --- a/util/homeassistant/connection.go +++ b/util/homeassistant/connection.go @@ -267,6 +267,21 @@ func (c *Connection) CallNumberService(entity string, value float64) error { return c.CallService(domain, "set_value", data) } +// CallSelectService is a convenience method for setting select entity options. +func (c *Connection) CallSelectService(entity, option string) error { + domain, err := domain(entity) + if err != nil { + return err + } + + data := map[string]any{ + "entity_id": entity, + "option": option, + } + + return c.CallService(domain, "select_option", data) +} + // GetPhaseFloatStates retrieves three phase values (currents, voltages, etc.) func (c *Connection) GetPhaseFloatStates(entities []string) (float64, float64, float64, error) { if len(entities) != 3 {