Home Assistant: identify instance by URI (#25659)

This commit is contained in:
Michael Geers 2025-11-29 14:33:07 +01:00 • committed by GitHub
parent 7b20f49ec9
commit 9d523fdaf5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 142 additions and 109 deletions

View file

@ -20,26 +20,24 @@ func init() {
}
func NewHomeAssistantSwitchFromConfig(other map[string]any) (api.Charger, error) {
cc := struct {
var cc struct {
embed `mapstructure:",squash"`
URI_ string `mapstructure:"uri"` // TODO deprecated
URI string
Token_ string `mapstructure:"token"` // TODO deprecated
Home string
Home string // TODO deprecated
Enable string
Power string
StandbyPower float64
}{
Home: "Home",
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
return NewHomeAssistantSwitch(cc.embed, cc.Home, cc.Enable, cc.Power, cc.StandbyPower)
return NewHomeAssistantSwitch(cc.embed, cc.URI, cc.Home, cc.Enable, cc.Power, cc.StandbyPower)
}
func NewHomeAssistantSwitch(embed embed, home, enable, power string, standbypower float64) (api.Charger, error) {
func NewHomeAssistantSwitch(embed embed, uri, home, enable, power string, standbypower float64) (api.Charger, error) {
if enable == "" {
return nil, errors.New("missing enable switch entity")
}
@ -50,7 +48,8 @@ func NewHomeAssistantSwitch(embed embed, home, enable, power string, standbypowe
}
log := util.NewLogger("ha-switch")
conn, err := homeassistant.NewConnection(log, home)
conn, err := homeassistant.NewConnection(log, uri, home)
if err != nil {
return nil, err
}

View file

@ -27,10 +27,10 @@ func init() {
// NewHomeAssistantFromConfig creates a HomeAssistant charger from generic config
func NewHomeAssistantFromConfig(other map[string]any) (api.Charger, error) {
cc := struct {
URI_ string `mapstructure:"uri"` // TODO deprecated
Token_ string `mapstructure:"token"` // TODO deprecated
Home string
var cc struct {
URI string
Token_ string `mapstructure:"token"` // TODO deprecated
Home string // TODO deprecated
Status string // required - sensor for charge status
Enabled string // required - sensor for enabled state
Enable string // required - switch/input_boolean for enable/disable
@ -39,8 +39,6 @@ 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
}{
Home: "Home",
}
if err := util.DecodeOther(other, &cc); err != nil {
@ -58,7 +56,8 @@ func NewHomeAssistantFromConfig(other map[string]any) (api.Charger, error) {
}
log := util.NewLogger("ha-charger")
conn, err := homeassistant.NewConnection(log, cc.Home)
conn, err := homeassistant.NewConnection(log, cc.URI, cc.Home)
if err != nil {
return nil, err
}

View file

@ -24,9 +24,9 @@ func init() {
// NewHomeAssistantFromConfig creates a HomeAssistant meter from generic config
func NewHomeAssistantFromConfig(other map[string]any) (api.Meter, error) {
cc := struct {
URI_ string `mapstructure:"uri"` // TODO deprecated
URI string
Token_ string `mapstructure:"token"` // TODO deprecated
Home string
Home string // TODO deprecated, backward compatibility (v0.210.x)
Power string
Energy string
Currents []string
@ -41,7 +41,6 @@ func NewHomeAssistantFromConfig(other map[string]any) (api.Meter, error) {
batterySocLimits `mapstructure:",squash"`
batteryPowerLimits `mapstructure:",squash"`
}{
Home: "Home",
batterySocLimits: batterySocLimits{
MinSoc: 20,
MaxSoc: 95,
@ -57,7 +56,8 @@ func NewHomeAssistantFromConfig(other map[string]any) (api.Meter, error) {
}
log := util.NewLogger("ha-meter")
conn, err := homeassistant.NewConnection(log, cc.Home)
conn, err := homeassistant.NewConnection(log, cc.URI, cc.Home)
if err != nil {
return nil, err
}

View file

@ -4,41 +4,44 @@ products:
group: switchsockets
requirements:
evcc: ["skiptest"]
description:
en: Home Assistant instances in your network will be auto-discovered and suitable entities (e.g. `switch.*`, `sensor.*`) will be suggested.
de: Home Assistant Instanzen in deinem Netzwerk werden automatisch erkannt und passende Entitäten (z.B. `switch.*`, `sensor.*`) werden vorgeschlagen.
auth:
type: homeassistant
params: [home]
params: [uri]
params:
- name: uri
deprecated: true
description:
de: Home Assistant URI
en: Home Assistant URI
example: http://homeassistant.local:8123
help:
en: " " # overwrite default
de: " " # overwrite default
service: homeassistant/instances
required: true
- name: token
deprecated: true
- name: home
description:
de: Home Assistant Instanz
en: Home Assistant Instance
help:
en: Can be found in Home Assistant UI under Settings -> System -> General -> Name. Used to identify your instance via mDNS.
de: Kann in der Home Assistant UI unter Einstellungen -> System -> Allgemein -> Name gefunden werden. Wird verwendet, um die Instanz via mDNS zu identifizieren.
example: Home
service: homeassistant/homes
required: true
deprecated: true
- name: switch
description:
de: Entity ID des schaltbaren Geräts
en: Entity ID of the switch device
service: homeassistant/homes/{home}/entities?domain=switch
service: homeassistant/entities?uri={uri}&domain=switch
example: switch.smartsocket
required: true
- name: power
description:
de: Entity ID für Leistungsmessung
en: Entity ID for power measurement
service: homeassistant/homes/{home}/entities?domain=sensor
service: homeassistant/entities?uri={uri}&domain=sensor
example: sensor.smartsocket_power
- preset: switchsocket
render: |
type: homeassistant-switch
home: {{ .home }}
uri: {{ .uri }}
enable: {{ .switch }}
power: {{ .power }}
{{ include "switchsocket" . }}

View file

@ -4,34 +4,34 @@ products:
group: generic
requirements:
description:
en: Requires a running Home Assistant instance with suitable sensor entities. All values are Home Assistant entity IDs (e.g. `sensor.*`)
de: Erfordert eine laufende Home Assistant Instanz mit passenden Sensor-Entitäten. Alle Werte sind Home Assistant Entity IDs (z.B. `sensor.*`)
en: Home Assistant instances in your network will be auto-discovered and suitable entities (e.g. `sensor.*`) will be suggested.
de: Home Assistant Instanzen in deinem Netzwerk werden automatisch erkannt und passende Entitäten (z.B. `sensor.*`) werden vorgeschlagen.
auth:
type: homeassistant
params: [home]
params: [uri]
params:
- name: usage
choice: ["grid", "pv", "battery", "aux", "charge"]
- name: uri
deprecated: true
description:
de: Home Assistant URI
en: Home Assistant URI
example: http://homeassistant.local:8123
help:
en: " " # overwrite default
de: " " # overwrite default
service: homeassistant/instances
required: true
- name: token
deprecated: true
- name: home
description:
de: Home Assistant Instanz
en: Home Assistant Instance
help:
en: Can be found in Home Assistant UI under Settings -> System -> General -> Name. Used to identify your instance via mDNS.
de: Kann in der Home Assistant UI unter Einstellungen -> System -> Allgemein -> Name gefunden werden. Wird verwendet, um die Instanz via mDNS zu identifizieren.
example: Home
service: homeassistant/homes
required: true
deprecated: true
- name: power
description:
de: Leistungsentität
en: Power Entity
required: true
service: homeassistant/homes/{home}/entities?domain=sensor
service: homeassistant/entities?uri={uri}&domain=sensor
example: "sensor.house_power"
help:
en: Entity ID for instantaneous power measurement in watts. The entity must provide numeric values only (e.g., "1234", not "1234 W").
@ -40,7 +40,7 @@ params:
description:
de: Energieentität
en: Energy Entity
service: homeassistant/homes/{home}/entities?domain=sensor
service: homeassistant/entities?uri={uri}&domain=sensor
example: "sensor.house_energy"
advanced: true
help:
@ -50,7 +50,7 @@ params:
description:
de: L1 Stromentität
en: L1 Current Entity
service: homeassistant/homes/{home}/entities?domain=sensor
service: homeassistant/entities?uri={uri}&domain=sensor
example: "sensor.house_current_l1"
advanced: true
help:
@ -60,7 +60,7 @@ params:
description:
de: L2 Stromentität
en: L2 Current Entity
service: homeassistant/homes/{home}/entities?domain=sensor
service: homeassistant/entities?uri={uri}&domain=sensor
example: "sensor.house_current_l2"
advanced: true
help:
@ -70,7 +70,7 @@ params:
description:
de: L3 Stromentität
en: L3 Current Entity
service: homeassistant/homes/{home}/entities?domain=sensor
service: homeassistant/entities?uri={uri}&domain=sensor
example: "sensor.house_current_l3"
advanced: true
help:
@ -80,7 +80,7 @@ params:
description:
de: L1 Spannungsentität
en: L1 Voltage Entity
service: homeassistant/homes/{home}/entities?domain=sensor
service: homeassistant/entities?uri={uri}&domain=sensor
example: "sensor.house_voltage_l1"
advanced: true
help:
@ -90,7 +90,7 @@ params:
description:
de: L2 Spannungsentität
en: L2 Voltage Entity
service: homeassistant/homes/{home}/entities?domain=sensor
service: homeassistant/entities?uri={uri}&domain=sensor
example: "sensor.house_voltage_l2"
advanced: true
help:
@ -100,7 +100,7 @@ params:
description:
de: L3 Spannungsentität
en: L3 Voltage Entity
service: homeassistant/homes/{home}/entities?domain=sensor
service: homeassistant/entities?uri={uri}&domain=sensor
example: "sensor.house_voltage_l3"
advanced: true
help:
@ -110,7 +110,7 @@ params:
description:
de: Batterieladestand
en: Battery State of Charge
service: homeassistant/homes/{home}/entities?domain=sensor
service: homeassistant/entities?uri={uri}&domain=sensor
example: "sensor.battery_soc"
advanced: true
help:
@ -124,7 +124,7 @@ params:
- name: maxacpower
render: |
type: homeassistant
home: {{ .home }}
uri: {{ .uri }}
power: {{ .power }}
energy: {{ .energy }}
currents:

View file

@ -4,93 +4,93 @@ products:
group: generic
requirements:
description:
en: Requires a running Home Assistant instance with suitable vehicle entities and services. All values are Home Assistant entity IDs (e.g. `sensor.*`, `binary_sensor.*`, `number.*`, `script.*`)
de: Erfordert eine laufende Home Assistant Instanz mit passenden Fahrzeug-Entities und Services. Alle Werte sind Home Assistant Entity IDs (z.B. `sensor.*`, `binary_sensor.*`, `number.*`, `script.*`)
en: Home Assistant instances in your network will be auto-discovered and suitable vehicle entities and services (e.g. `sensor.*`) will be suggested.
de: Home Assistant Instanzen in deinem Netzwerk werden automatisch erkannt und passende Entitäten und Services (z.B. `sensor.*`) werden vorgeschlagen.
auth:
type: homeassistant
params: [home]
params: [uri]
params:
- preset: vehicle-common
- name: uri
deprecated: true
description:
de: Home Assistant URI
en: Home Assistant URI
example: http://homeassistant.local:8123
help:
en: " " # overwrite default
de: " " # overwrite default
service: homeassistant/instances
required: true
- name: token
deprecated: true
- name: home
description:
de: Home Assistant Instanz
en: Home Assistant Instance
help:
en: Can be found in Home Assistant UI under Settings -> System -> General -> Name. Used to identify your instance via mDNS.
de: Kann in der Home Assistant UI unter Einstellungen -> System -> Allgemein -> Name gefunden werden. Wird verwendet, um die Instanz via mDNS zu identifizieren.
example: Home
service: homeassistant/homes
required: true
deprecated: true
- name: soc
description:
de: Ladezustand [%]
en: State of charge [%]
service: homeassistant/homes/{home}/entities?domain=sensor
service: homeassistant/entities?uri={uri}&domain=sensor
example: "sensor.vehicle_soc"
required: true
- name: range
description:
de: Restreichweite [km]
en: Remaining range [km]
service: homeassistant/homes/{home}/entities?domain=sensor
service: homeassistant/entities?uri={uri}&domain=sensor
example: "sensor.vehicle_range"
- name: status
description:
de: Ladestatus
en: Charging status
service: homeassistant/homes/{home}/entities?domain=sensor
service: homeassistant/entities?uri={uri}&domain=sensor
example: "sensor.vehicle_charging"
- name: limitSoc
description:
de: Ziel-Ladezustand [%]
en: Target state of charge [%]
service: homeassistant/homes/{home}/entities?domain=number,input_number
service: homeassistant/entities?uri={uri}&domain=number,input_number
example: "number.vehicle_target_state_of_charge"
- name: odometer
description:
de: Kilometerstand [km]
en: Odometer [km]
service: homeassistant/homes/{home}/entities?domain=sensor
service: homeassistant/entities?uri={uri}&domain=sensor
example: "sensor.vehicle_odometer"
- name: climater
description:
de: Klimatisierung aktiv
en: Climatisation active
service: homeassistant/homes/{home}/entities?domain=binary_sensor
service: homeassistant/entities?uri={uri}&domain=binary_sensor
example: "binary_sensor.vehicle_climater"
- name: finishTime
description:
de: Ladeende (ISO8601 oder Unix)
en: Finish time (ISO8601 or Unix)
service: homeassistant/homes/{home}/entities?domain=sensor
service: homeassistant/entities?uri={uri}&domain=sensor
example: "sensor.vehicle_finish_time"
- name: start_charging
description:
de: Service zum Laden starten
en: Service to start charging
service: homeassistant/homes/{home}/entities?domain=script
service: homeassistant/entities?uri={uri}&domain=script
example: "script.vehicle_start_charge"
- name: stop_charging
description:
de: Service zum Laden stoppen
en: Service to stop charging
service: homeassistant/homes/{home}/entities?domain=script
service: homeassistant/entities?uri={uri}&domain=script
example: "script.vehicle_stop_charge"
- name: wakeup
description:
de: Service zum Aufwecken
en: Service to wake up vehicle
service: homeassistant/homes/{home}/entities?domain=script
service: homeassistant/entities?uri={uri}&domain=script
example: "script.vehicle_wakeup"
- name: setMaxCurrent
description:
de: Ladestromstärke setzen [A]
en: Set charging current [A]
service: homeassistant/homes/{home}/entities?domain=number,input_number
service: homeassistant/entities?uri={uri}&domain=number,input_number
example: "number.vehicle_charging_current"
- preset: vehicle-features
- name: streaming
@ -99,7 +99,7 @@ render: |
type: homeassistant
{{ include "vehicle-common" . }}
{{ include "vehicle-features" . }}
home: {{ .home }}
uri: {{ .uri }}
sensors:
soc: {{ .soc }}
range: {{ .range }}

View file

@ -22,10 +22,21 @@ type Connection struct {
}
// NewConnection creates a new Home Assistant connection
func NewConnection(log *util.Logger, home string) (*Connection, error) {
func NewConnection(log *util.Logger, uri, home string) (*Connection, error) {
if home != "" {
log.WARN.Printf("using deprecated 'home' parameter '%s', please use 'uri' instead", home)
}
if uri == "" && home == "" {
return nil, errors.New("missing either uri or home")
}
c := &Connection{
Helper: request.NewHelper(log),
instance: &proxyInstance{home: home},
Helper: request.NewHelper(log),
instance: &proxyInstance{
home: home,
uri: uri,
},
}
// Set up authentication headers

View file

@ -17,7 +17,8 @@ func (inst *proxyInstance) URI() string {
inst.mu.Lock()
defer inst.mu.Unlock()
if inst.uri == "" {
if inst.uri == "" && inst.home != "" {
// Try to resolve home name to URI (backward compatibility)
inst.uri = instanceUriByName(inst.home)
if inst.uri == "" {
@ -31,14 +32,17 @@ func (inst *proxyInstance) URI() string {
func (inst *proxyInstance) Token() (*oauth2.Token, error) {
uri := inst.URI()
if uri == "" {
return nil, fmt.Errorf("unknown instance: %s", inst.home)
if inst.home != "" {
return nil, fmt.Errorf("unknown instance: %s", inst.home)
}
return nil, fmt.Errorf("no URI configured")
}
inst.mu.Lock()
defer inst.mu.Unlock()
if inst.TokenSource == nil {
ts, err := NewHomeAssistant(inst.home, uri)
ts, err := NewHomeAssistant(uri)
if err != nil {
return nil, err
}

View file

@ -3,6 +3,8 @@ package homeassistant
import (
"context"
"fmt"
"net"
"net/url"
"github.com/evcc-io/evcc/plugin/auth"
"github.com/evcc-io/evcc/server/network"
@ -19,22 +21,27 @@ func init() {
func NewHomeAssistantFromConfig(other map[string]any) (oauth2.TokenSource, error) {
var cc struct {
Home string
URI string
Home string // TODO remove deprecated
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
uri := instanceUriByName(cc.Home)
if uri == "" {
return nil, fmt.Errorf("unknown instance: %s", cc.Home)
uri := cc.URI
if uri == "" && cc.Home != "" {
uri = instanceUriByName(cc.Home)
if uri == "" {
return nil, fmt.Errorf("unknown instance: %s", cc.Home)
}
}
return NewHomeAssistant(cc.Home, uri)
return NewHomeAssistant(uri)
}
func NewHomeAssistant(home, uri string) (oauth2.TokenSource, error) {
func NewHomeAssistant(uri string) (oauth2.TokenSource, error) {
extUrl := network.Config().ExternalURL()
redirectUri := extUrl + network.CallbackPath
@ -50,5 +57,16 @@ func NewHomeAssistant(home, uri string) (oauth2.TokenSource, error) {
},
}
return auth.NewOAuth(ctx, "HomeAssistant", home, &oc)
// validate url
u, err := url.Parse(uri)
if err != nil {
return nil, err
}
host := u.Host
if h, _, err := net.SplitHostPort(u.Host); err == nil {
host = h
}
return auth.NewOAuth(ctx, "HomeAssistant", host, &oc)
}

View file

@ -2,6 +2,7 @@ package homeassistant
import (
"encoding/json"
"errors"
"maps"
"net/http"
"slices"
@ -16,28 +17,27 @@ var log = util.NewLogger("homeassistant")
func init() {
mux := http.NewServeMux()
mux.HandleFunc("GET /homes", getHomes)
mux.HandleFunc("GET /homes/{home}/entities", getEntities)
mux.HandleFunc("GET /instances", getInstances)
mux.HandleFunc("GET /entities", getEntities)
service.Register("homeassistant", mux)
}
func getHomes(w http.ResponseWriter, req *http.Request) {
func getInstances(w http.ResponseWriter, req *http.Request) {
mu.Lock()
defer mu.Unlock()
jsonWrite(w, slices.Sorted(maps.Keys(instances)))
jsonWrite(w, slices.Sorted(maps.Values(instances)))
}
func getEntities(w http.ResponseWriter, req *http.Request) {
home := req.PathValue("home")
if instanceUriByName(home) == "" {
w.WriteHeader(http.StatusBadRequest)
uri := req.URL.Query().Get("uri")
if uri == "" {
jsonError(w, http.StatusBadRequest, errors.New("missing uri"))
return
}
conn, _ := NewConnection(log, home)
conn, _ := NewConnection(log, uri, "")
res, err := conn.GetStates()
if err != nil {
jsonError(w, http.StatusBadRequest, err)

View file

@ -23,11 +23,11 @@ func init() {
// Constructor from YAML config
func NewHomeAssistantVehicleFromConfig(other map[string]any) (api.Vehicle, error) {
cc := struct {
var cc struct {
embed `mapstructure:",squash"`
URI_ string `mapstructure:"uri"` // TODO deprecated
URI string
Token_ string `mapstructure:"token"` // TODO deprecated
Home string
Home string // TODO deprecated
Sensors struct {
Soc string // required
Range string // optional
@ -43,8 +43,6 @@ func NewHomeAssistantVehicleFromConfig(other map[string]any) (api.Vehicle, error
Wakeup string // script.* optional
SetMaxCurrent string // number.* or input_number.* optional
}
}{
Home: "Home",
}
if err := util.DecodeOther(other, &cc); err != nil {
@ -56,7 +54,8 @@ func NewHomeAssistantVehicleFromConfig(other map[string]any) (api.Vehicle, error
}
log := util.NewLogger("ha-vehicle")
conn, err := homeassistant.NewConnection(log, cc.Home)
conn, err := homeassistant.NewConnection(log, cc.URI, cc.Home)
if err != nil {
return nil, err
}