Home Assistant: add plugin source, support temp for heating switch devices (BC) (#31915)
This commit is contained in:
parent
28d36a51f0
commit
a5956a6804
4 changed files with 173 additions and 92 deletions
|
|
@ -1,79 +0,0 @@
|
||||||
package charger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/evcc-io/evcc/api"
|
|
||||||
"github.com/evcc-io/evcc/util"
|
|
||||||
"github.com/evcc-io/evcc/util/homeassistant"
|
|
||||||
)
|
|
||||||
|
|
||||||
type HomeAssistantSwitch struct {
|
|
||||||
conn *homeassistant.Connection
|
|
||||||
enable string
|
|
||||||
power string
|
|
||||||
*switchSocket
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
registry.Add("homeassistant-switch", NewHomeAssistantSwitchFromConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHomeAssistantSwitchFromConfig(other map[string]any) (api.Charger, error) {
|
|
||||||
var cc struct {
|
|
||||||
embed `mapstructure:",squash"`
|
|
||||||
homeassistant.Config `mapstructure:",squash"`
|
|
||||||
Enable string
|
|
||||||
Power string
|
|
||||||
StandbyPower float64
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := util.DecodeOther(other, &cc); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewHomeAssistantSwitch(cc.embed, cc.URI, cc.Home_, cc.Insecure, cc.Enable, cc.Power, cc.StandbyPower)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHomeAssistantSwitch(embed embed, uri, home string, insecure bool, enable, power string, standbypower float64) (api.Charger, error) {
|
|
||||||
if enable == "" {
|
|
||||||
return nil, errors.New("missing enable switch entity")
|
|
||||||
}
|
|
||||||
|
|
||||||
// standbypower < 0 ensures that currentPower is never used by the switch socket if not present
|
|
||||||
if power == "" && standbypower >= 0 {
|
|
||||||
return nil, errors.New("missing either power entity or negative standbypower")
|
|
||||||
}
|
|
||||||
|
|
||||||
log := util.NewLogger("ha-switch")
|
|
||||||
|
|
||||||
conn, err := homeassistant.NewConnection(log, uri, home, insecure)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c := &HomeAssistantSwitch{
|
|
||||||
enable: enable,
|
|
||||||
power: power,
|
|
||||||
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) {
|
|
||||||
return c.conn.GetBoolState(c.enable)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable implements the api.Charger interface
|
|
||||||
func (c *HomeAssistantSwitch) Enable(enable bool) error {
|
|
||||||
return c.conn.CallSwitchService(c.enable, enable)
|
|
||||||
}
|
|
||||||
|
|
||||||
// currentPower implements the api.Meter interface (optional)
|
|
||||||
func (c *HomeAssistantSwitch) currentPower() (float64, error) {
|
|
||||||
return c.conn.GetFloatState(c.power)
|
|
||||||
}
|
|
||||||
|
|
@ -2,9 +2,11 @@ package charger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/evcc-io/evcc/api"
|
"github.com/evcc-io/evcc/api"
|
||||||
"github.com/evcc-io/evcc/api/implement"
|
"github.com/evcc-io/evcc/api/implement"
|
||||||
|
"github.com/evcc-io/evcc/charger/measurement"
|
||||||
"github.com/evcc-io/evcc/core/loadpoint"
|
"github.com/evcc-io/evcc/core/loadpoint"
|
||||||
"github.com/evcc-io/evcc/plugin"
|
"github.com/evcc-io/evcc/plugin"
|
||||||
"github.com/evcc-io/evcc/util"
|
"github.com/evcc-io/evcc/util"
|
||||||
|
|
@ -23,13 +25,14 @@ type SwitchSocket struct {
|
||||||
|
|
||||||
func NewSwitchSocketFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) {
|
func NewSwitchSocketFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) {
|
||||||
var cc struct {
|
var cc struct {
|
||||||
embed `mapstructure:",squash"`
|
embed `mapstructure:",squash"`
|
||||||
Enabled plugin.Config
|
Enabled plugin.Config
|
||||||
Enable plugin.Config
|
Enable plugin.Config
|
||||||
Power plugin.Config
|
Power *plugin.Config
|
||||||
Energy *plugin.Config
|
Energy *plugin.Config
|
||||||
Soc *plugin.Config
|
Soc *plugin.Config
|
||||||
StandbyPower float64
|
measurement.Temperature `mapstructure:",squash"` // optional, for heating devices
|
||||||
|
StandbyPower float64
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := util.DecodeOther(other, &cc); err != nil {
|
if err := util.DecodeOther(other, &cc); err != nil {
|
||||||
|
|
@ -51,6 +54,11 @@ func NewSwitchSocketFromConfig(ctx context.Context, other map[string]any) (api.C
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// standbypower < 0 ensures that power is never used by the switch socket if not present
|
||||||
|
if power == nil && cc.StandbyPower >= 0 {
|
||||||
|
return nil, errors.New("missing either power or negative standbypower")
|
||||||
|
}
|
||||||
|
|
||||||
c := &SwitchSocket{
|
c := &SwitchSocket{
|
||||||
Caps: implement.New(),
|
Caps: implement.New(),
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
|
|
@ -68,7 +76,17 @@ func NewSwitchSocketFromConfig(ctx context.Context, other map[string]any) (api.C
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for heating devices, the soc slot holds temperature in °C — fall back to temp getter
|
||||||
|
temp, limitTemp, err := cc.Temperature.Configure(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if soc == nil && temp != nil {
|
||||||
|
soc = temp
|
||||||
|
}
|
||||||
implement.May(c, implement.Battery(soc))
|
implement.May(c, implement.Battery(soc))
|
||||||
|
implement.May(c, implement.SocLimiter(limitTemp))
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
91
plugin/homeassistant.go
Normal file
91
plugin/homeassistant.go
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/evcc-io/evcc/util"
|
||||||
|
"github.com/evcc-io/evcc/util/homeassistant"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HomeAssistant reads and writes Home Assistant entity states
|
||||||
|
type HomeAssistant struct {
|
||||||
|
conn *homeassistant.Connection
|
||||||
|
entity string
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registry.AddCtx("homeassistant", NewHomeAssistantPluginFromConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHomeAssistantPluginFromConfig creates a Home Assistant plugin
|
||||||
|
func NewHomeAssistantPluginFromConfig(ctx context.Context, other map[string]any) (Plugin, error) {
|
||||||
|
var cc struct {
|
||||||
|
homeassistant.Config `mapstructure:",squash"`
|
||||||
|
Entity string
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := util.DecodeOther(other, &cc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cc.Entity == "" {
|
||||||
|
return nil, errors.New("missing entity")
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := homeassistant.NewConnection(util.NewLogger("homeassistant"), cc.URI, cc.Home_, cc.Insecure)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &HomeAssistant{
|
||||||
|
conn: conn,
|
||||||
|
entity: cc.Entity,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ StringGetter = (*HomeAssistant)(nil)
|
||||||
|
|
||||||
|
// StringGetter returns the entity state
|
||||||
|
func (p *HomeAssistant) StringGetter() (func() (string, error), error) {
|
||||||
|
return func() (string, error) {
|
||||||
|
res, err := p.conn.GetState(p.entity)
|
||||||
|
return res.State, err
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ FloatGetter = (*HomeAssistant)(nil)
|
||||||
|
|
||||||
|
// FloatGetter returns the entity state as float
|
||||||
|
func (p *HomeAssistant) FloatGetter() (func() (float64, error), error) {
|
||||||
|
return func() (float64, error) {
|
||||||
|
return p.conn.GetFloatState(p.entity)
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ IntGetter = (*HomeAssistant)(nil)
|
||||||
|
|
||||||
|
// IntGetter returns the entity state as int
|
||||||
|
func (p *HomeAssistant) IntGetter() (func() (int64, error), error) {
|
||||||
|
return func() (int64, error) {
|
||||||
|
return p.conn.GetIntState(p.entity)
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ BoolGetter = (*HomeAssistant)(nil)
|
||||||
|
|
||||||
|
// BoolGetter returns the entity state as bool
|
||||||
|
func (p *HomeAssistant) BoolGetter() (func() (bool, error), error) {
|
||||||
|
return func() (bool, error) {
|
||||||
|
return p.conn.GetBoolState(p.entity)
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ BoolSetter = (*HomeAssistant)(nil)
|
||||||
|
|
||||||
|
// BoolSetter invokes the entity's turn_on/turn_off service
|
||||||
|
func (p *HomeAssistant) BoolSetter(param string) (func(bool) error, error) {
|
||||||
|
return func(enable bool) error {
|
||||||
|
return p.conn.CallSwitchService(p.entity, enable)
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
@ -41,12 +41,63 @@ params:
|
||||||
en: Entity ID for power measurement
|
en: Entity ID for power measurement
|
||||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=sensor
|
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=sensor
|
||||||
example: sensor.smartsocket_power
|
example: sensor.smartsocket_power
|
||||||
|
- name: temp
|
||||||
|
description:
|
||||||
|
de: Entity ID für Temperaturmessung
|
||||||
|
en: Entity ID for temperature measurement
|
||||||
|
help:
|
||||||
|
de: Nur für Heizgeräte
|
||||||
|
en: Heating devices only
|
||||||
|
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=sensor
|
||||||
|
example: sensor.heatpump_temperature
|
||||||
|
advanced: true
|
||||||
|
- name: limittemp
|
||||||
|
description:
|
||||||
|
de: Entity ID für Temperaturbegrenzung
|
||||||
|
en: Entity ID for temperature limit
|
||||||
|
help:
|
||||||
|
de: Im Gerät konfigurierte Begrenzung, nur für Heizgeräte
|
||||||
|
en: Limit configured in the device, heating devices only
|
||||||
|
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=sensor
|
||||||
|
example: sensor.heatpump_target_temperature
|
||||||
|
advanced: true
|
||||||
- preset: switchsocket
|
- preset: switchsocket
|
||||||
render: |
|
render: |
|
||||||
type: homeassistant-switch
|
type: switchsocket
|
||||||
uri: {{ .uri }}
|
enabled:
|
||||||
home: {{ .home }} # deprecated
|
source: homeassistant
|
||||||
insecure: {{ .insecure }}
|
uri: {{ .uri }}
|
||||||
enable: {{ .switch }}
|
home: {{ .home }} # deprecated
|
||||||
power: {{ .power }}
|
insecure: {{ .insecure }}
|
||||||
|
entity: {{ .switch }}
|
||||||
|
enable:
|
||||||
|
source: homeassistant
|
||||||
|
uri: {{ .uri }}
|
||||||
|
home: {{ .home }} # deprecated
|
||||||
|
insecure: {{ .insecure }}
|
||||||
|
entity: {{ .switch }}
|
||||||
|
{{- if .power }}
|
||||||
|
power:
|
||||||
|
source: homeassistant
|
||||||
|
uri: {{ .uri }}
|
||||||
|
home: {{ .home }} # deprecated
|
||||||
|
insecure: {{ .insecure }}
|
||||||
|
entity: {{ .power }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .temp }}
|
||||||
|
temp:
|
||||||
|
source: homeassistant
|
||||||
|
uri: {{ .uri }}
|
||||||
|
home: {{ .home }} # deprecated
|
||||||
|
insecure: {{ .insecure }}
|
||||||
|
entity: {{ .temp }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .limittemp }}
|
||||||
|
limittemp:
|
||||||
|
source: homeassistant
|
||||||
|
uri: {{ .uri }}
|
||||||
|
home: {{ .home }} # deprecated
|
||||||
|
insecure: {{ .insecure }}
|
||||||
|
entity: {{ .limittemp }}
|
||||||
|
{{- end }}
|
||||||
{{ include "switchsocket" . }}
|
{{ include "switchsocket" . }}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue