Home Assistant: Enable insecure option for self-signed certificates in oauth2 flow (#30648)
Co-authored-by: andig <cpuidle@gmail.com> Co-authored-by: Michael Geers <michael@geers.tv>
This commit is contained in:
parent
d7a294f06e
commit
e5b9a014c5
29 changed files with 336 additions and 115 deletions
|
|
@ -52,7 +52,7 @@
|
|||
|
||||
<div v-if="authRequired">
|
||||
<PropertyEntry
|
||||
v-for="param in authParams"
|
||||
v-for="param in authNormalParams"
|
||||
:id="`${deviceType}Param${param.Name}`"
|
||||
:key="param.Name"
|
||||
v-bind="param"
|
||||
|
|
@ -60,6 +60,19 @@
|
|||
:service-values="serviceValues[param.Name]"
|
||||
:currency="currency"
|
||||
/>
|
||||
<PropertyCollapsible v-if="authAdvancedParams.length">
|
||||
<template #advanced>
|
||||
<PropertyEntry
|
||||
v-for="param in authAdvancedParams"
|
||||
:id="`${deviceType}Param${param.Name}`"
|
||||
:key="param.Name"
|
||||
v-bind="param"
|
||||
v-model="values[param.Name]"
|
||||
:service-values="serviceValues[param.Name]"
|
||||
:currency="currency"
|
||||
/>
|
||||
</template>
|
||||
</PropertyCollapsible>
|
||||
|
||||
<div v-if="auth.code">
|
||||
<hr class="my-5" />
|
||||
|
|
@ -330,6 +343,12 @@ export default defineComponent({
|
|||
const { params = [] } = this.template?.Auth ?? {};
|
||||
return this.templateParams.filter((p) => params.includes(p.Name));
|
||||
},
|
||||
authNormalParams() {
|
||||
return this.authParams.filter((p: TemplateParam) => !p.Advanced && !p.Deprecated);
|
||||
},
|
||||
authAdvancedParams() {
|
||||
return this.authParams.filter((p: TemplateParam) => p.Advanced || p.Deprecated);
|
||||
},
|
||||
normalParams() {
|
||||
return this.templateParams.filter((p) => !p.Advanced && !p.Deprecated);
|
||||
},
|
||||
|
|
@ -429,7 +448,13 @@ export default defineComponent({
|
|||
return this.template?.Auth && !this.auth.ok;
|
||||
},
|
||||
authValuesMissing() {
|
||||
return this.template?.Auth && Object.values(this.authValues).some((value) => !value);
|
||||
const authParamNames: string[] = this.template?.Auth?.params ?? [];
|
||||
return (
|
||||
authParamNames.length > 0 &&
|
||||
this.templateParams
|
||||
.filter((p: TemplateParam) => authParamNames.includes(p.Name) && p.Required)
|
||||
.some((p: TemplateParam) => !this.values[p.Name])
|
||||
);
|
||||
},
|
||||
authValues() {
|
||||
const params = this.template?.Auth?.params ?? [];
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ export type TemplateParam = {
|
|||
Advanced: boolean;
|
||||
Deprecated: boolean;
|
||||
Default?: string | number | boolean;
|
||||
Type?: string;
|
||||
Choice?: string[];
|
||||
Service?: string;
|
||||
Usages?: TemplateParamUsage[];
|
||||
|
|
@ -89,11 +90,13 @@ export function handleError(e: any, msg: string) {
|
|||
|
||||
export function applyDefaultsFromTemplate(template: Template | null, values: DeviceValues) {
|
||||
const params = template?.Params || [];
|
||||
params
|
||||
.filter((p) => p.Default && !values[p.Name])
|
||||
.forEach((p) => {
|
||||
params.forEach((p) => {
|
||||
if (p.Default && !values[p.Name]) {
|
||||
values[p.Name] = p.Default;
|
||||
});
|
||||
} else if (p.Type === "Bool" && values[p.Name] === undefined) {
|
||||
values[p.Name] = false; // initialize
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function customChargerName(type: ConfigType, isHeating: boolean) {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,12 @@ export const prepareAuthLogin = async (state: AuthState, providerId: string) =>
|
|||
state.loading = true;
|
||||
state.error = null;
|
||||
|
||||
const url = `providerauth/login?id=${encodeURIComponent(providerId)}`;
|
||||
let url = `providerauth/login?id=${encodeURIComponent(providerId)}`;
|
||||
// restore the config modal stack on callback
|
||||
const returnTo = window.location.hash.split("?")[1];
|
||||
if (returnTo) {
|
||||
url += `&return=${encodeURIComponent(returnTo)}`;
|
||||
}
|
||||
const { status, data } = await baseApi.get<ProviderLoginResponse>(url, {
|
||||
validateStatus: (code) => [200, 400].includes(code),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,23 +21,21 @@ func init() {
|
|||
|
||||
func NewHomeAssistantSwitchFromConfig(other map[string]any) (api.Charger, error) {
|
||||
var cc struct {
|
||||
embed `mapstructure:",squash"`
|
||||
URI string
|
||||
Token_ string `mapstructure:"token"` // TODO deprecated
|
||||
Home_ string `mapstructure:"home"` // TODO deprecated
|
||||
Enable string
|
||||
Power string
|
||||
StandbyPower float64
|
||||
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.Enable, cc.Power, cc.StandbyPower)
|
||||
return NewHomeAssistantSwitch(cc.embed, cc.URI, cc.Home_, cc.Insecure, cc.Enable, cc.Power, cc.StandbyPower)
|
||||
}
|
||||
|
||||
func NewHomeAssistantSwitch(embed embed, uri, home, enable, power string, standbypower float64) (api.Charger, error) {
|
||||
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")
|
||||
}
|
||||
|
|
@ -49,7 +47,7 @@ func NewHomeAssistantSwitch(embed embed, uri, home, enable, power string, standb
|
|||
|
||||
log := util.NewLogger("ha-switch")
|
||||
|
||||
conn, err := homeassistant.NewConnection(log, uri, home)
|
||||
conn, err := homeassistant.NewConnection(log, uri, home, insecure)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,18 +28,16 @@ func init() {
|
|||
// NewHomeAssistantFromConfig creates a HomeAssistant charger from generic config
|
||||
func NewHomeAssistantFromConfig(other map[string]any) (api.Charger, error) {
|
||||
var cc struct {
|
||||
URI string
|
||||
Token_ string `mapstructure:"token"` // TODO deprecated
|
||||
Home_ string `mapstructure:"home"` // 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
|
||||
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
|
||||
Phases string // optional - select entity for 1p/3p phase switching
|
||||
homeassistant.Config `mapstructure:",squash"`
|
||||
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
|
||||
Phases string // optional - select entity for 1p/3p phase switching
|
||||
}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
|
|
@ -61,7 +59,7 @@ func NewHomeAssistantFromConfig(other map[string]any) (api.Charger, error) {
|
|||
|
||||
log := util.NewLogger("ha-charger")
|
||||
|
||||
conn, err := homeassistant.NewConnection(log, cc.URI, cc.Home_)
|
||||
conn, err := cc.Config.NewConnection(log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@ type HomeAssistant struct {
|
|||
// NewHomeAssistantFromConfig creates a new Home Assistant messenger
|
||||
func NewHomeAssistantFromConfig(other map[string]any) (api.Messenger, error) {
|
||||
var cc struct {
|
||||
URI string
|
||||
Notify string
|
||||
Data map[string]any
|
||||
homeassistant.Config `mapstructure:",squash"`
|
||||
Notify string
|
||||
Data map[string]any
|
||||
}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
|
|
@ -40,7 +40,7 @@ func NewHomeAssistantFromConfig(other map[string]any) (api.Messenger, error) {
|
|||
|
||||
log := util.NewLogger("homeassistant")
|
||||
|
||||
conn, err := homeassistant.NewConnection(log, cc.URI, "")
|
||||
conn, err := cc.Config.NewConnection(log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,15 +18,13 @@ func init() {
|
|||
// NewHomeAssistantFromConfig creates a HomeAssistant meter from generic config
|
||||
func NewHomeAssistantFromConfig(other map[string]any) (api.Meter, error) {
|
||||
cc := struct {
|
||||
URI string
|
||||
Token_ string `mapstructure:"token"` // TODO deprecated
|
||||
Home_ string `mapstructure:"home"` // TODO deprecated
|
||||
Power string
|
||||
Energy string
|
||||
Currents []string
|
||||
Voltages []string
|
||||
Powers []string
|
||||
Soc string
|
||||
homeassistant.Config `mapstructure:",squash"`
|
||||
Power string
|
||||
Energy string
|
||||
Currents []string
|
||||
Voltages []string
|
||||
Powers []string
|
||||
Soc string
|
||||
|
||||
// pv
|
||||
pvMaxACPower `mapstructure:",squash"`
|
||||
|
|
@ -57,7 +55,7 @@ func NewHomeAssistantFromConfig(other map[string]any) (api.Meter, error) {
|
|||
|
||||
log := util.NewLogger("ha-meter")
|
||||
|
||||
conn, err := homeassistant.NewConnection(log, cc.URI, cc.Home_)
|
||||
conn, err := cc.Config.NewConnection(log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ func NewDemoFromConfig(_ context.Context, other map[string]any) (oauth2.TokenSou
|
|||
Method string
|
||||
RedirectUri string
|
||||
Secret string
|
||||
Scope string // advanced auth param, used by e2e tests
|
||||
}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
|
|
|
|||
|
|
@ -44,10 +44,15 @@ type Handler struct {
|
|||
log *util.Logger
|
||||
secret []byte
|
||||
providers map[string]api.AuthProvider
|
||||
states map[string]string
|
||||
states map[string]stateEntry
|
||||
updateC chan string
|
||||
}
|
||||
|
||||
type stateEntry struct {
|
||||
id string
|
||||
returnTo string // config modal query to restore on callback
|
||||
}
|
||||
|
||||
// TODO get status from update channel
|
||||
func (a *Handler) run(paramC chan<- util.Param) {
|
||||
for range a.updateC {
|
||||
|
|
@ -97,7 +102,7 @@ func (a *Handler) handleLogin(w http.ResponseWriter, r *http.Request) {
|
|||
// Generate a new state and store the provider
|
||||
state := NewState()
|
||||
encryptedState := state.Encrypt(a.secret)
|
||||
a.states[encryptedState] = id
|
||||
a.states[encryptedState] = stateEntry{id: id, returnTo: r.URL.Query().Get("return")}
|
||||
|
||||
// Schedule cleanup for stale state entries after state becomes invalid
|
||||
time.AfterFunc(stateValidity, func() {
|
||||
|
|
@ -176,11 +181,12 @@ func (a *Handler) handleCallback(w http.ResponseWriter, r *http.Request) {
|
|||
defer a.mu.Unlock()
|
||||
|
||||
// Find the corresponding provider
|
||||
id, ok := a.states[encryptedState]
|
||||
entry, ok := a.states[encryptedState]
|
||||
if !ok {
|
||||
a.redirectToError(w, r, "no provider found for state")
|
||||
return
|
||||
}
|
||||
id := entry.id
|
||||
|
||||
provider, ok := a.providers[id]
|
||||
if !ok {
|
||||
|
|
@ -198,5 +204,11 @@ func (a *Handler) handleCallback(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/#/config?callbackCompleted="+url.QueryEscape(id), http.StatusFound)
|
||||
// restore the config modal stack alongside the completion marker
|
||||
query := "callbackCompleted=" + url.QueryEscape(id)
|
||||
if entry.returnTo != "" {
|
||||
query = entry.returnTo + "&" + query
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/#/config?"+query, http.StatusFound)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ func init() {
|
|||
log: util.NewLogger("providerauth"),
|
||||
secret: secret[:],
|
||||
providers: make(map[string]api.AuthProvider),
|
||||
states: make(map[string]string),
|
||||
states: make(map[string]stateEntry),
|
||||
updateC: make(chan string, 1),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ requirements:
|
|||
de: Home Assistant Instanzen in deinem Netzwerk werden automatisch erkannt und passende Entitäten (z.B. `switch.*`, `sensor.*`) werden vorgeschlagen.
|
||||
auth:
|
||||
type: homeassistant
|
||||
params: [uri]
|
||||
params: [uri, insecure]
|
||||
params:
|
||||
- name: uri
|
||||
description:
|
||||
|
|
@ -27,24 +27,26 @@ params:
|
|||
deprecated: true
|
||||
- name: home
|
||||
deprecated: true
|
||||
- name: insecure
|
||||
- name: switch
|
||||
description:
|
||||
de: Entity ID des schaltbaren Geräts
|
||||
en: Entity ID of the switch device
|
||||
service: homeassistant/entities?uri={uri}&domain=switch
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=switch
|
||||
example: switch.smartsocket
|
||||
required: true
|
||||
- name: power
|
||||
description:
|
||||
de: Entity ID für Leistungsmessung
|
||||
en: Entity ID for power measurement
|
||||
service: homeassistant/entities?uri={uri}&domain=sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=sensor
|
||||
example: sensor.smartsocket_power
|
||||
- preset: switchsocket
|
||||
render: |
|
||||
type: homeassistant-switch
|
||||
uri: {{ .uri }}
|
||||
home: {{ .home }} # deprecated
|
||||
insecure: {{ .insecure }}
|
||||
enable: {{ .switch }}
|
||||
power: {{ .power }}
|
||||
{{ include "switchsocket" . }}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ requirements:
|
|||
de: Home Assistant Instanzen in deinem Netzwerk werden automatisch erkannt und passende Wallbox-Entitäten und Services (z.B. `sensor.*`, `switch.*`, `number.*`) werden vorgeschlagen.
|
||||
auth:
|
||||
type: homeassistant
|
||||
params: [uri]
|
||||
params: [uri, insecure]
|
||||
params:
|
||||
- name: uri
|
||||
description:
|
||||
|
|
@ -23,11 +23,12 @@ params:
|
|||
de: " " # overwrite default
|
||||
service: homeassistant/instances
|
||||
required: true
|
||||
- name: insecure
|
||||
- name: status
|
||||
description:
|
||||
de: Ladestatus-Sensor
|
||||
en: Charging status sensor
|
||||
service: homeassistant/entities?uri={uri}&domain=sensor,binary_sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=sensor,binary_sensor
|
||||
example: "sensor.charger_status"
|
||||
required: true
|
||||
help:
|
||||
|
|
@ -37,7 +38,7 @@ params:
|
|||
description:
|
||||
de: Aktivierungsstatus-Sensor
|
||||
en: Enabled status sensor
|
||||
service: homeassistant/entities?uri={uri}&domain=sensor,binary_sensor,switch
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=sensor,binary_sensor,switch
|
||||
example: "binary_sensor.charger_enabled"
|
||||
required: true
|
||||
help:
|
||||
|
|
@ -47,7 +48,7 @@ params:
|
|||
description:
|
||||
de: Aktivierungsschalter
|
||||
en: Enable switch
|
||||
service: homeassistant/entities?uri={uri}&domain=switch,input_boolean
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=switch,input_boolean
|
||||
example: "switch.charger_enable"
|
||||
required: true
|
||||
help:
|
||||
|
|
@ -57,7 +58,7 @@ params:
|
|||
description:
|
||||
de: Maximale Stromstärke-Entität [A]
|
||||
en: Maximum current entity [A]
|
||||
service: homeassistant/entities?uri={uri}&domain=number,input_number
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=number,input_number
|
||||
example: "number.charger_max_current"
|
||||
required: true
|
||||
help:
|
||||
|
|
@ -67,7 +68,7 @@ params:
|
|||
description:
|
||||
de: Leistungsentität
|
||||
en: Power entity
|
||||
service: homeassistant/entities?uri={uri}&domain=sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=sensor
|
||||
example: "sensor.charger_power"
|
||||
advanced: true
|
||||
help:
|
||||
|
|
@ -77,7 +78,7 @@ params:
|
|||
description:
|
||||
de: Energieentität
|
||||
en: Energy entity
|
||||
service: homeassistant/entities?uri={uri}&domain=sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=sensor
|
||||
example: "sensor.charger_energy"
|
||||
advanced: true
|
||||
help:
|
||||
|
|
@ -87,7 +88,7 @@ params:
|
|||
description:
|
||||
de: L1 Stromentität
|
||||
en: L1 current entity
|
||||
service: homeassistant/entities?uri={uri}&domain=sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=sensor
|
||||
example: "sensor.charger_current_l1"
|
||||
advanced: true
|
||||
help:
|
||||
|
|
@ -97,7 +98,7 @@ params:
|
|||
description:
|
||||
de: L2 Stromentität
|
||||
en: L2 current entity
|
||||
service: homeassistant/entities?uri={uri}&domain=sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=sensor
|
||||
example: "sensor.charger_current_l2"
|
||||
advanced: true
|
||||
help:
|
||||
|
|
@ -107,7 +108,7 @@ params:
|
|||
description:
|
||||
de: L3 Stromentität
|
||||
en: L3 current entity
|
||||
service: homeassistant/entities?uri={uri}&domain=sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=sensor
|
||||
example: "sensor.charger_current_l3"
|
||||
advanced: true
|
||||
help:
|
||||
|
|
@ -117,7 +118,7 @@ params:
|
|||
description:
|
||||
de: L1 Spannungsentität
|
||||
en: L1 voltage entity
|
||||
service: homeassistant/entities?uri={uri}&domain=sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=sensor
|
||||
example: "sensor.charger_voltage_l1"
|
||||
advanced: true
|
||||
help:
|
||||
|
|
@ -127,7 +128,7 @@ params:
|
|||
description:
|
||||
de: L2 Spannungsentität
|
||||
en: L2 voltage entity
|
||||
service: homeassistant/entities?uri={uri}&domain=sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=sensor
|
||||
example: "sensor.charger_voltage_l2"
|
||||
advanced: true
|
||||
help:
|
||||
|
|
@ -137,7 +138,7 @@ params:
|
|||
description:
|
||||
de: L3 Spannungsentität
|
||||
en: L3 voltage entity
|
||||
service: homeassistant/entities?uri={uri}&domain=sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=sensor
|
||||
example: "sensor.charger_voltage_l3"
|
||||
advanced: true
|
||||
help:
|
||||
|
|
@ -152,7 +153,7 @@ params:
|
|||
description:
|
||||
de: Phasenumschaltungs-Entität
|
||||
en: Phase switching entity
|
||||
service: homeassistant/entities?uri={uri}&domain=select,input_select
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=select,input_select
|
||||
example: "select.charger_phases"
|
||||
advanced: true
|
||||
help:
|
||||
|
|
@ -162,6 +163,7 @@ params:
|
|||
render: |
|
||||
type: homeassistant
|
||||
uri: {{ .uri }}
|
||||
insecure: {{ .insecure }}
|
||||
status: {{ .status }}
|
||||
enabled: {{ .enabled }}
|
||||
enable: {{ .enable }}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ products:
|
|||
- brand: Home Assistant
|
||||
auth:
|
||||
type: homeassistant
|
||||
params: [uri]
|
||||
params: [uri, insecure]
|
||||
params:
|
||||
- name: uri
|
||||
required: true
|
||||
|
|
@ -27,7 +27,8 @@ params:
|
|||
en: >
|
||||
Home Assistant notify service in the form `domain.service` (e.g. `notify.mobile_app_android`).
|
||||
Leave empty to create a persistent notification in the HA UI.
|
||||
service: homeassistant/services?uri={uri}&domain=notify
|
||||
service: homeassistant/services?uri={uri}&insecure={insecure}&domain=notify
|
||||
- name: insecure
|
||||
- name: critical
|
||||
type: bool
|
||||
default: false
|
||||
|
|
@ -56,6 +57,7 @@ params:
|
|||
render: |
|
||||
type: homeassistant
|
||||
uri: {{ .uri }}
|
||||
insecure: {{ .insecure }}
|
||||
{{- if .notify }}
|
||||
notify: {{ .notify }}
|
||||
{{- end }}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ requirements:
|
|||
de: Home Assistant Instanzen in deinem Netzwerk werden automatisch erkannt und passende Entitäten (z.B. `sensor.*`) werden vorgeschlagen.
|
||||
auth:
|
||||
type: homeassistant
|
||||
params: [uri]
|
||||
params: [uri, insecure]
|
||||
params:
|
||||
- name: usage
|
||||
choice: ["grid", "pv", "battery", "aux", "charge"]
|
||||
|
|
@ -31,7 +31,7 @@ params:
|
|||
de: Leistungsentität
|
||||
en: Power Entity
|
||||
required: true
|
||||
service: homeassistant/entities?uri={uri}&domain=sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&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/entities?uri={uri}&domain=sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&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/entities?uri={uri}&domain=sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&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/entities?uri={uri}&domain=sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&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/entities?uri={uri}&domain=sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&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/entities?uri={uri}&domain=sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&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/entities?uri={uri}&domain=sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&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/entities?uri={uri}&domain=sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=sensor
|
||||
example: "sensor.house_voltage_l3"
|
||||
advanced: true
|
||||
help:
|
||||
|
|
@ -111,7 +111,7 @@ params:
|
|||
description:
|
||||
de: Batterieladestand
|
||||
en: Battery State of Charge
|
||||
service: homeassistant/entities?uri={uri}&domain=sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=sensor
|
||||
example: "sensor.battery_soc"
|
||||
advanced: true
|
||||
help:
|
||||
|
|
@ -122,7 +122,7 @@ params:
|
|||
description:
|
||||
de: Normalbetrieb Skript
|
||||
en: Normal Mode Script
|
||||
service: homeassistant/entities?uri={uri}&domain=script
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=script
|
||||
example: "script.battery_normal"
|
||||
advanced: true
|
||||
help:
|
||||
|
|
@ -133,7 +133,7 @@ params:
|
|||
description:
|
||||
de: Halten Skript
|
||||
en: Hold Mode Script
|
||||
service: homeassistant/entities?uri={uri}&domain=script
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=script
|
||||
example: "script.battery_hold"
|
||||
advanced: true
|
||||
help:
|
||||
|
|
@ -144,18 +144,20 @@ params:
|
|||
description:
|
||||
de: Netzladung Skript
|
||||
en: Grid Charge Mode Script
|
||||
service: homeassistant/entities?uri={uri}&domain=script
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=script
|
||||
example: "script.battery_grid_charge"
|
||||
advanced: true
|
||||
help:
|
||||
en: Optional script to grid-charge the home battery.
|
||||
de: Optionales Skript um die Heimbatterie aus dem Netz zu laden.
|
||||
- name: insecure
|
||||
- name: maxacpower
|
||||
- preset: battery-params
|
||||
render: |
|
||||
type: homeassistant
|
||||
uri: {{ .uri }}
|
||||
home: {{ .home }} # deprecated
|
||||
insecure: {{ .insecure }}
|
||||
power: {{ .power }}
|
||||
energy: {{ .energy }}
|
||||
currents:
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ requirements:
|
|||
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: [uri]
|
||||
params: [uri, insecure]
|
||||
params:
|
||||
- preset: vehicle-common
|
||||
- name: uri
|
||||
|
|
@ -25,11 +25,12 @@ params:
|
|||
deprecated: true
|
||||
- name: home
|
||||
deprecated: true
|
||||
- name: insecure
|
||||
- name: soc
|
||||
description:
|
||||
de: Ladezustand [%]
|
||||
en: State of charge [%]
|
||||
service: homeassistant/entities?uri={uri}&domain=sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=sensor
|
||||
example: "sensor.vehicle_soc"
|
||||
required: true
|
||||
help:
|
||||
|
|
@ -39,7 +40,7 @@ params:
|
|||
description:
|
||||
de: Restreichweite [km]
|
||||
en: Remaining range [km]
|
||||
service: homeassistant/entities?uri={uri}&domain=sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=sensor
|
||||
example: "sensor.vehicle_range"
|
||||
help:
|
||||
en: Entity ID for the vehicle's remaining range in kilometers
|
||||
|
|
@ -48,7 +49,7 @@ params:
|
|||
description:
|
||||
de: Ladestatus
|
||||
en: Charging status
|
||||
service: homeassistant/entities?uri={uri}&domain=sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=sensor
|
||||
example: "sensor.vehicle_charging"
|
||||
help:
|
||||
en: Entity ID for charging status (A=disconnected, B=connected, C=charging)
|
||||
|
|
@ -57,7 +58,7 @@ params:
|
|||
description:
|
||||
de: Ziel-Ladezustand [%]
|
||||
en: Target state of charge [%]
|
||||
service: homeassistant/entities?uri={uri}&domain=number,input_number
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=number,input_number
|
||||
example: "number.vehicle_target_state_of_charge"
|
||||
help:
|
||||
en: Entity ID for the vehicle's target state of charge in percent (`number` or `input_number` entity)
|
||||
|
|
@ -66,7 +67,7 @@ params:
|
|||
description:
|
||||
de: Kilometerstand [km]
|
||||
en: Odometer [km]
|
||||
service: homeassistant/entities?uri={uri}&domain=sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=sensor
|
||||
example: "sensor.vehicle_odometer"
|
||||
help:
|
||||
en: Entity ID for the vehicle's odometer reading in kilometers
|
||||
|
|
@ -75,7 +76,7 @@ params:
|
|||
description:
|
||||
de: Klimatisierung aktiv
|
||||
en: Climatisation active
|
||||
service: homeassistant/entities?uri={uri}&domain=binary_sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=binary_sensor
|
||||
example: "binary_sensor.vehicle_climater"
|
||||
help:
|
||||
en: Entity ID for the vehicle's climatisation state (`binary_sensor` with `on`/`off` state)
|
||||
|
|
@ -84,7 +85,7 @@ params:
|
|||
description:
|
||||
de: Ladeende
|
||||
en: Finish time
|
||||
service: homeassistant/entities?uri={uri}&domain=sensor
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=sensor
|
||||
example: "sensor.vehicle_finish_time"
|
||||
help:
|
||||
en: Entity ID for the estimated charging finish time (ISO8601 or Unix timestamp)
|
||||
|
|
@ -93,7 +94,7 @@ params:
|
|||
description:
|
||||
de: Service zum Laden starten
|
||||
en: Service to start charging
|
||||
service: homeassistant/entities?uri={uri}&domain=script,switch
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=script,switch
|
||||
example: "script.vehicle_start_charge; switch.vehicle_charger"
|
||||
help:
|
||||
en: Entity ID for a script or a switch that starts charging the vehicle. Only useful with the [docs.evcc.io](https://docs.evcc.io/en/docs/devices/chargers#vehicle-api-only-charger). If a switch is provided, Service to stop charging must be left empty.
|
||||
|
|
@ -102,7 +103,7 @@ params:
|
|||
description:
|
||||
de: Service zum Laden stoppen
|
||||
en: Service to stop charging
|
||||
service: homeassistant/entities?uri={uri}&domain=script
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=script
|
||||
example: "script.vehicle_stop_charge"
|
||||
help:
|
||||
en: Entity ID for a script that stops charging the vehicle. Only useful with the [docs.evcc.io](https://docs.evcc.io/en/docs/devices/chargers#vehicle-api-only-charger).
|
||||
|
|
@ -111,7 +112,7 @@ params:
|
|||
description:
|
||||
de: Service zum Aufwecken
|
||||
en: Service to wake up vehicle
|
||||
service: homeassistant/entities?uri={uri}&domain=script,button
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=script,button
|
||||
example: "script.vehicle_wakeup; button.wakeup_vehicle"
|
||||
help:
|
||||
en: Entity ID for a script or a button that wakes up the vehicle
|
||||
|
|
@ -120,7 +121,7 @@ params:
|
|||
description:
|
||||
de: Ladestromstärke setzen [A]
|
||||
en: Set charging current [A]
|
||||
service: homeassistant/entities?uri={uri}&domain=number,input_number
|
||||
service: homeassistant/entities?uri={uri}&insecure={insecure}&domain=number,input_number
|
||||
example: "number.vehicle_charging_current"
|
||||
help:
|
||||
en: Entity ID for setting the maximum charging current in amperes (`number` or `input_number` entity)
|
||||
|
|
@ -134,6 +135,7 @@ render: |
|
|||
{{ include "vehicle-features" . }}
|
||||
uri: {{ .uri }}
|
||||
home: {{ .home }} # deprecated
|
||||
insecure: {{ .insecure }}
|
||||
sensors:
|
||||
soc: {{ .soc }}
|
||||
range: {{ .range }}
|
||||
|
|
|
|||
|
|
@ -153,8 +153,8 @@ test.describe("backup and restore", async () => {
|
|||
const backupConfirmModal = page.getByTestId("backup-restore-confirm-modal");
|
||||
await expectModalVisible(backupConfirmModal);
|
||||
await backupConfirmModal.getByRole("button", { name: "Download backup" }).click();
|
||||
await expectModalHidden(backupConfirmModal);
|
||||
const download = await downloadPromise;
|
||||
await expectModalHidden(backupConfirmModal);
|
||||
await expectModalVisible(backupModal);
|
||||
await backupModal.locator(".btn-close").click();
|
||||
await expectModalHidden(backupModal);
|
||||
|
|
|
|||
17
tests/config-bool-service-demo.tpl.yaml
Normal file
17
tests/config-bool-service-demo.tpl.yaml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
template: bool-service-demo
|
||||
group: generic
|
||||
products:
|
||||
- description:
|
||||
generic: Bool Service Meter
|
||||
params:
|
||||
- name: usage
|
||||
choice: ["grid"]
|
||||
- name: insecure
|
||||
type: bool
|
||||
advanced: true
|
||||
- name: entity
|
||||
description:
|
||||
generic: Entity
|
||||
service: demo/insecure?insecure={insecure}
|
||||
render: |
|
||||
type: custom
|
||||
42
tests/config-bool-service.spec.ts
Normal file
42
tests/config-bool-service.spec.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { test, expect } from "@playwright/test";
|
||||
import { start, stop, baseUrl } from "./evcc";
|
||||
import { expectModalVisible, getDatalistOptions } from "./utils";
|
||||
|
||||
test.use({ baseURL: baseUrl() });
|
||||
|
||||
const templateFlags = [
|
||||
"--disable-auth",
|
||||
"--template-type",
|
||||
"meter",
|
||||
"--template",
|
||||
"tests/config-bool-service-demo.tpl.yaml",
|
||||
];
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await start(undefined, undefined, templateFlags);
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
await stop();
|
||||
});
|
||||
|
||||
test.describe("config bool service param", async () => {
|
||||
test("unset bool defaults to false and fills service placeholder", async ({ page }) => {
|
||||
// service call fires with insecure=false even though the bool was never touched
|
||||
const servicePromise = page.waitForRequest(
|
||||
/\/config\/service\/demo\/insecure\?insecure=false$/
|
||||
);
|
||||
|
||||
await page.goto("/#/config");
|
||||
await page.getByRole("button", { name: "Add grid meter" }).click();
|
||||
const meterModal = page.getByTestId("meter-modal");
|
||||
await expectModalVisible(meterModal);
|
||||
await meterModal.getByLabel("Manufacturer").selectOption("Bool Service Meter");
|
||||
|
||||
await servicePromise;
|
||||
|
||||
// entity datalist is populated from the echoed flag
|
||||
const entity = meterModal.getByLabel("Entity");
|
||||
expect(await getDatalistOptions(entity)).toEqual(["insecure=false"]);
|
||||
});
|
||||
});
|
||||
|
|
@ -6,7 +6,7 @@ products:
|
|||
en: Auth Demo Meter
|
||||
auth:
|
||||
type: demo
|
||||
params: ["server", "method", "redirectUri", "secret"]
|
||||
params: ["server", "method", "redirectUri", "secret", "scope"]
|
||||
params:
|
||||
- name: usage
|
||||
choice: ["grid"]
|
||||
|
|
@ -30,6 +30,11 @@ params:
|
|||
generic: Secret
|
||||
required: true
|
||||
mask: true
|
||||
- name: scope
|
||||
description:
|
||||
generic: Scope
|
||||
advanced: true
|
||||
default: "read"
|
||||
- name: power
|
||||
description:
|
||||
generic: Power
|
||||
|
|
|
|||
|
|
@ -79,8 +79,7 @@ test.describe("config device auth", async () => {
|
|||
await expect(successBanner).toContainText("Authorization Successful");
|
||||
await expect(successBanner).toContainText("Demo Auth is now connected and ready to use");
|
||||
|
||||
// After successful auth, reopen the meter modal to continue configuration
|
||||
await page.getByRole("button", { name: "Add grid meter" }).click();
|
||||
// modal reopens automatically after auth; continue configuration
|
||||
await expectModalVisible(meterModal);
|
||||
await meterModal.getByLabel("Manufacturer").selectOption("Auth Demo Meter");
|
||||
|
||||
|
|
@ -135,6 +134,58 @@ test.describe("config device auth", async () => {
|
|||
await expect(meterModal.getByRole("button", { name: "Validate & save" })).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("redirect auth restores the open modal on return", async ({ page }) => {
|
||||
await page.goto("/#/config");
|
||||
|
||||
// open the grid meter modal and prepare auth
|
||||
await page.getByRole("button", { name: "Add grid meter" }).click();
|
||||
const meterModal = page.getByTestId("meter-modal");
|
||||
await expectModalVisible(meterModal);
|
||||
await meterModal.getByLabel("Manufacturer").selectOption("Auth Demo Meter");
|
||||
await meterModal.getByLabel("Server").fill(simulatorUrl());
|
||||
await meterModal.getByLabel("Redirect URI").fill(getRedirectUri(page.url()));
|
||||
await meterModal.getByLabel("Authentication Method").selectOption("redirect");
|
||||
await meterModal.getByLabel("Secret").fill(secret);
|
||||
await meterModal.getByRole("button", { name: "Prepare connection" }).click();
|
||||
|
||||
// jump to provider in same page
|
||||
const loginLink = meterModal.getByRole("link", { name: "Connect to localhost" });
|
||||
await expect(loginLink).toBeVisible();
|
||||
await loginLink.evaluate((el) => el.removeAttribute("target"));
|
||||
await loginLink.click();
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
const loginButton = page.getByRole("button", { name: "Login Successfully" });
|
||||
await expect(loginButton).toBeVisible();
|
||||
await loginButton.evaluate((btn: HTMLButtonElement) => btn.click());
|
||||
|
||||
// callback restores the modal query alongside the completion marker
|
||||
await page.waitForURL(/.*\/#\/config.*callbackCompleted.*/);
|
||||
expect(page.url()).toContain("meter[type:grid]");
|
||||
|
||||
// modal reopens automatically and success banner is shown
|
||||
await expectModalVisible(meterModal);
|
||||
await expect(page.getByTestId("auth-success-banner")).toBeVisible();
|
||||
});
|
||||
|
||||
test("advanced auth field is hidden until expanded", async ({ page }) => {
|
||||
await page.goto("/#/config");
|
||||
|
||||
await page.getByRole("button", { name: "Add grid meter" }).click();
|
||||
const meterModal = page.getByTestId("meter-modal");
|
||||
await expectModalVisible(meterModal);
|
||||
await meterModal.getByLabel("Manufacturer").selectOption("Auth Demo Meter");
|
||||
|
||||
// normal auth fields are shown, advanced "Scope" is collapsed
|
||||
await expect(meterModal.getByLabel("Server")).toBeVisible();
|
||||
await expect(meterModal.getByLabel("Scope")).not.toBeVisible();
|
||||
|
||||
// expand advanced section manually
|
||||
await meterModal.getByRole("button", { name: "Show advanced settings" }).click();
|
||||
await expect(meterModal.getByLabel("Scope")).toBeVisible();
|
||||
await expect(meterModal.getByLabel("Scope")).toHaveValue("read");
|
||||
});
|
||||
|
||||
test("create grid meter with device-code auth", async ({ page }) => {
|
||||
await page.goto("/#/config");
|
||||
|
||||
|
|
|
|||
17
util/homeassistant/config.go
Normal file
17
util/homeassistant/config.go
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package homeassistant
|
||||
|
||||
import "github.com/evcc-io/evcc/util"
|
||||
|
||||
// Config holds the common Home Assistant instance connection parameters.
|
||||
// It is embedded into device configs via mapstructure squash.
|
||||
type Config struct {
|
||||
URI string
|
||||
Token_ string `mapstructure:"token"` // TODO deprecated
|
||||
Home_ string `mapstructure:"home"` // TODO deprecated
|
||||
Insecure bool // optional - allow self-signed certificates
|
||||
}
|
||||
|
||||
// NewConnection creates a Home Assistant connection from the config
|
||||
func (c Config) NewConnection(log *util.Logger) (*Connection, error) {
|
||||
return NewConnection(log, c.URI, c.Home_, c.Insecure)
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"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/samber/lo"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
|
@ -23,7 +24,7 @@ type Connection struct {
|
|||
}
|
||||
|
||||
// NewConnection creates a new Home Assistant connection
|
||||
func NewConnection(log *util.Logger, uri, home string) (*Connection, error) {
|
||||
func NewConnection(log *util.Logger, uri, home string, insecure bool) (*Connection, error) {
|
||||
if home != "" {
|
||||
log.WARN.Printf("using deprecated 'home' parameter '%s', please use 'uri' instead", home)
|
||||
}
|
||||
|
|
@ -35,11 +36,17 @@ func NewConnection(log *util.Logger, uri, home string) (*Connection, error) {
|
|||
c := &Connection{
|
||||
Helper: request.NewHelper(log),
|
||||
instance: &proxyInstance{
|
||||
home: home,
|
||||
uri: util.DefaultScheme(strings.TrimSuffix(uri, "/"), "http"),
|
||||
home: home,
|
||||
uri: util.DefaultScheme(strings.TrimSuffix(uri, "/"), "http"),
|
||||
insecure: insecure,
|
||||
},
|
||||
}
|
||||
|
||||
// override the transport to accept self-signed certificates
|
||||
if insecure {
|
||||
c.Client.Transport = request.NewTripper(log, transport.Insecure())
|
||||
}
|
||||
|
||||
// Set up authentication headers
|
||||
c.Client.Transport = &oauth2.Transport{
|
||||
Base: c.Client.Transport,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
type proxyInstance struct {
|
||||
mu sync.Mutex
|
||||
home, uri string
|
||||
insecure bool
|
||||
oauth2.TokenSource
|
||||
}
|
||||
|
||||
|
|
@ -42,7 +43,7 @@ func (inst *proxyInstance) Token() (*oauth2.Token, error) {
|
|||
defer inst.mu.Unlock()
|
||||
|
||||
if inst.TokenSource == nil {
|
||||
ts, err := NewHomeAssistant(uri)
|
||||
ts, err := NewHomeAssistant(uri, inst.insecure)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,14 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/evcc-io/evcc/plugin/auth"
|
||||
"github.com/evcc-io/evcc/server/network"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/transport"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
|
|
@ -21,8 +23,9 @@ func init() {
|
|||
|
||||
func NewHomeAssistantFromConfig(other map[string]any) (oauth2.TokenSource, error) {
|
||||
var cc struct {
|
||||
URI string
|
||||
Home string // TODO remove deprecated
|
||||
URI string
|
||||
Home string // TODO remove deprecated
|
||||
Insecure bool
|
||||
}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
|
|
@ -38,10 +41,10 @@ func NewHomeAssistantFromConfig(other map[string]any) (oauth2.TokenSource, error
|
|||
}
|
||||
}
|
||||
|
||||
return NewHomeAssistant(uri)
|
||||
return NewHomeAssistant(uri, cc.Insecure)
|
||||
}
|
||||
|
||||
func NewHomeAssistant(uri string) (oauth2.TokenSource, error) {
|
||||
func NewHomeAssistant(uri string, insecure bool) (oauth2.TokenSource, error) {
|
||||
uri = strings.TrimRight(uri, "/") // normalize
|
||||
|
||||
extUrl := network.Config().ExternalURL()
|
||||
|
|
@ -76,5 +79,11 @@ func NewHomeAssistant(uri string) (oauth2.TokenSource, error) {
|
|||
log := util.NewLogger("homeassistant")
|
||||
ctx := util.WithLogger(context.Background(), log)
|
||||
|
||||
if insecure {
|
||||
log.WARN.Println("insecure mode enabled - TLS certificate verification is disabled, use only for trusted local/self-signed instances")
|
||||
httpClient := &http.Client{Transport: transport.Insecure()}
|
||||
ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
|
||||
}
|
||||
|
||||
return auth.NewOAuth(ctx, "HomeAssistant", host, &oc)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"maps"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/evcc-io/evcc/server/service"
|
||||
|
|
@ -35,7 +36,14 @@ func connectionFromRequest(req *http.Request) (*Connection, error) {
|
|||
if uri == "" {
|
||||
return nil, errors.New("missing uri")
|
||||
}
|
||||
return NewConnection(log, uri, "")
|
||||
var insecure bool
|
||||
if s := req.URL.Query().Get("insecure"); s != "" {
|
||||
var err error
|
||||
if insecure, err = strconv.ParseBool(s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return NewConnection(log, uri, "", insecure)
|
||||
}
|
||||
|
||||
// domainsFromRequest parses the comma-separated "domain" query parameter.
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ func init() {
|
|||
mux.HandleFunc("GET /country", getCountry)
|
||||
mux.HandleFunc("GET /{country}/city", getCity)
|
||||
mux.HandleFunc("GET /modbus", getModbus)
|
||||
mux.HandleFunc("GET /insecure", getInsecure)
|
||||
|
||||
service.Register("demo", mux)
|
||||
}
|
||||
|
|
@ -39,6 +40,10 @@ func getCity(w http.ResponseWriter, req *http.Request) {
|
|||
json.NewEncoder(w).Encode(cities)
|
||||
}
|
||||
|
||||
func getInsecure(w http.ResponseWriter, req *http.Request) {
|
||||
json.NewEncoder(w).Encode([]string{"insecure=" + req.URL.Query().Get("insecure")})
|
||||
}
|
||||
|
||||
func getModbus(w http.ResponseWriter, req *http.Request) {
|
||||
// Verify that either uri or device is provided (mimics modbus connection params)
|
||||
uri := req.URL.Query().Get("uri")
|
||||
|
|
|
|||
|
|
@ -353,6 +353,12 @@ params:
|
|||
help:
|
||||
en: Integrated device. No charging sessions
|
||||
de: Fest angeschlossenes Gerät. Keine Ladevorgänge
|
||||
- name: insecure
|
||||
type: bool
|
||||
advanced: true
|
||||
description:
|
||||
en: Accept self-signed certificates
|
||||
de: Selbstsignierte Zertifikate akzeptieren
|
||||
- name: defaultmode
|
||||
type: int
|
||||
advanced: true
|
||||
|
|
|
|||
|
|
@ -38,7 +38,8 @@ func test(t *testing.T, tmpl Template, values map[string]any, cb func(values map
|
|||
cb(values)
|
||||
}
|
||||
|
||||
func testAuth(other map[string]any) error {
|
||||
func testAuth(tmpl Template) error {
|
||||
other := tmpl.Auth
|
||||
if len(other) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -54,7 +55,11 @@ func testAuth(other map[string]any) error {
|
|||
|
||||
params := make(map[string]any)
|
||||
for _, p := range cc.Params {
|
||||
params[p] = "foo"
|
||||
if _, param := tmpl.ParamByName(p); param.Type == TypeBool {
|
||||
params[p] = true
|
||||
} else {
|
||||
params[p] = "foo"
|
||||
}
|
||||
}
|
||||
|
||||
_, err := auth.NewFromConfig(context.TODO(), cc.Type, params)
|
||||
|
|
@ -94,7 +99,7 @@ func TestClass(t *testing.T, class Class, instantiate func(t *testing.T, values
|
|||
values["host"] = "localhost"
|
||||
|
||||
// test auth configuration
|
||||
if err := testAuth(tmpl.Auth); err != nil {
|
||||
if err := testAuth(tmpl); err != nil {
|
||||
t.Error("authorization:", err)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,11 +26,9 @@ func init() {
|
|||
// Constructor from YAML config
|
||||
func NewHomeAssistantVehicleFromConfig(other map[string]any) (api.Vehicle, error) {
|
||||
var cc struct {
|
||||
embed `mapstructure:",squash"`
|
||||
URI string
|
||||
Token_ string `mapstructure:"token"` // TODO deprecated
|
||||
Home_ string `mapstructure:"home"` // TODO deprecated
|
||||
Sensors struct {
|
||||
embed `mapstructure:",squash"`
|
||||
homeassistant.Config `mapstructure:",squash"`
|
||||
Sensors struct {
|
||||
Soc string // required
|
||||
Range string // optional
|
||||
Status string // optional
|
||||
|
|
@ -57,7 +55,7 @@ func NewHomeAssistantVehicleFromConfig(other map[string]any) (api.Vehicle, error
|
|||
|
||||
log := util.NewLogger("ha-vehicle")
|
||||
|
||||
conn, err := homeassistant.NewConnection(log, cc.URI, cc.Home_)
|
||||
conn, err := cc.Config.NewConnection(log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue