Phase 1: reproduzierbare HA-Testumgebung als Skript
Die urspruengliche Wegwerf-Testinstanz war verloren, weil sie nur als Anleitung existierte. testumgebung/aufsetzen.sh baut sie jetzt vollstaendig neu auf: Container, Nutzer, langlebiges Token, pyscript als Custom Component, Projektdateien und nachgebildete Fahrzeugsensoren als Template-Entitaeten, die ueber Helfer zur Laufzeit steuerbar sind. Verifiziert: pyscript veroeffentlicht alle acht Entitaeten, Fahrzeugsensoren liefern Werte, beide Smoke-Tests gruen.
This commit is contained in:
+6
-1
@@ -62,7 +62,12 @@ einzige, die zwingend auf Hardware wartet.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Arbeitsumgebung herstellen und verifizieren
|
||||
## Phase 1 — Arbeitsumgebung herstellen und verifizieren ✅ ERLEDIGT (2026-08-11)
|
||||
|
||||
> Ergebnis: design-system baut und besteht 21/21, companion-app typecheckt und besteht 7/7.
|
||||
> Die verlorene Testinstanz wurde als reproduzierbares Skript neu aufgebaut — siehe
|
||||
> `testumgebung/` (Abweichung vom Plan, bewusst: das Original ging genau deshalb verloren,
|
||||
> weil es nur als Anleitung existierte).
|
||||
|
||||
**Ziel:** Beide npm-Pakete bauen, beide Smoke-Tests laufen, die HA-Testinstanz ist erreichbar.
|
||||
|
||||
|
||||
Generated
+5
-42
@@ -509,9 +509,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -613,9 +610,6 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -630,9 +624,6 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -647,9 +638,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -664,9 +652,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -681,9 +666,6 @@
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -698,9 +680,6 @@
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -715,9 +694,6 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -732,9 +708,6 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -749,9 +722,6 @@
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -766,9 +736,6 @@
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -783,9 +750,6 @@
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -800,9 +764,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -817,9 +778,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -930,6 +888,7 @@
|
||||
"integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.2.2"
|
||||
@@ -1066,6 +1025,7 @@
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
@@ -1278,6 +1238,7 @@
|
||||
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -1356,6 +1317,7 @@
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
@@ -1613,6 +1575,7 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# testumgebung/ — Wegwerf-Home-Assistant für Entwicklung und Tests
|
||||
|
||||
Setzt eine vollständige Home-Assistant-Instanz in Docker auf, in der das
|
||||
pyscript-Backend dieses Projekts echt läuft: eigene Entitäten, echte Service-Aufrufe,
|
||||
nachgebildete Fahrzeugsensoren. Gedacht als Ziel für die Abnahmekriterien aus
|
||||
`../UMSETZUNGSPLAN.md` (Phasen 1, 6, 9).
|
||||
|
||||
Die ursprüngliche Testinstanz aus der Entwicklung des HA-Panels war eine
|
||||
Wegwerfinstanz ohne Skript und ist verloren gegangen — deshalb liegt der Aufbau
|
||||
hier als ausführbares Skript statt als Anleitung.
|
||||
|
||||
## Aufsetzen
|
||||
|
||||
```bash
|
||||
./aufsetzen.sh
|
||||
```
|
||||
|
||||
Das Skript ist mehrfach ausführbar (löscht einen vorhandenen Container vorher).
|
||||
Es lädt das offizielle Image, startet den Container auf **Port 18123**, legt per
|
||||
API einen Testnutzer an, erzeugt ein langlebiges Zugriffstoken, installiert
|
||||
pyscript als Custom Component, kopiert `../homeassistant/{pyscript,www,data}`
|
||||
hinein und startet neu.
|
||||
|
||||
Am Ende schreibt es das Token nach `../companion-app/.env.local`
|
||||
(Variable `VITE_TEST_TOKEN`, gitignored) — von dort holen es die
|
||||
authentifizierten Tests.
|
||||
|
||||
**Zugang:** http://localhost:18123 · Benutzer `test` · Passwort `testtest123`
|
||||
|
||||
## Was nachgebildet wird
|
||||
|
||||
`konfiguration.yaml` bildet die Entitäten der HACS-Integration
|
||||
`TommiG1/HA_VAG-EU-Data-Act` als Template-Sensoren nach, damit das Backend echte
|
||||
Werte sieht. Steuerbar zur Laufzeit über Helfer — damit lassen sich die
|
||||
`@state_trigger`-Abläufe wirklich auslösen:
|
||||
|
||||
| Helfer | Steuert | Wofür |
|
||||
|---|---|---|
|
||||
| `input_number.test_kilometerstand` | `sensor.audi_rs_4_avant_mileage` | Reifenzähler, Fahrtabschluss-Screening |
|
||||
| `input_number.test_tankfuellstand` | `sensor.audi_rs_4_avant_fuel_level` | automatische Tankerkennung |
|
||||
| `input_number.test_reichweite` | `sensor.audi_rs_4_avant_range_primary` | Übersichtsanzeige |
|
||||
| `input_text.test_wlan` | `sensor.iphone_wifi_connection` | Fahrterkennung (Start/Ende) |
|
||||
| `input_boolean.test_tuer_vl` | Tür vorne links | „sicher abgestellt" |
|
||||
| `input_boolean.test_fenster_vl` | Fenster vorne links | „sicher abgestellt" |
|
||||
| `input_boolean.test_entriegelt` | Türschloss vorne links | „sicher abgestellt" |
|
||||
|
||||
Beispiel — Fahrt auslösen (WLAN des Fahrzeugs verbinden, dann trennen):
|
||||
|
||||
```bash
|
||||
T=$(grep VITE_TEST_TOKEN ../companion-app/.env.local | cut -d= -f2)
|
||||
curl -X POST -H "Authorization: Bearer $T" -H "Content-Type: application/json" \
|
||||
-d '{"entity_id":"input_text.test_wlan","value":"Audi_MMI"}' \
|
||||
http://localhost:18123/api/services/input_text/set_value
|
||||
```
|
||||
|
||||
Der WLAN-Name muss zu `fahrzeug.wlan_ssid` in
|
||||
`audi_dashboard/fahrzeugprofil.json` passen.
|
||||
|
||||
## Aufräumen
|
||||
|
||||
```bash
|
||||
docker rm -f audi_ha_test
|
||||
```
|
||||
|
||||
Die Konfiguration liegt außerhalb des Repos unter dem im Skript gesetzten
|
||||
`ZIEL`-Pfad; es werden keine echten Fahrzeugdaten verwendet, sondern
|
||||
`fahrzeugprofil.example.json`.
|
||||
Executable
+84
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env bash
|
||||
# Setzt die Wegwerf-Testinstanz auf. Mehrfach ausfuehrbar.
|
||||
# Siehe README.md im selben Ordner.
|
||||
set -euo pipefail
|
||||
|
||||
HIER="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJEKT="$(dirname "$HIER")"
|
||||
NAME="audi_ha_test"
|
||||
PORT="18123"
|
||||
ZIEL="${AUDI_TEST_CONFIG:-${TMPDIR:-/tmp}/audi-ha-test-config}"
|
||||
BASIS="http://localhost:${PORT}"
|
||||
|
||||
echo "==> Zielordner: $ZIEL"
|
||||
rm -rf "$ZIEL"
|
||||
mkdir -p "$ZIEL"
|
||||
|
||||
echo "==> Alten Container entfernen (falls vorhanden)"
|
||||
docker rm -f "$NAME" >/dev/null 2>&1 || true
|
||||
|
||||
echo "==> Image laden"
|
||||
docker pull ghcr.io/home-assistant/home-assistant:stable
|
||||
|
||||
echo "==> Container starten"
|
||||
docker run -d --name "$NAME" -p "${PORT}:8123" -v "$ZIEL:/config" \
|
||||
ghcr.io/home-assistant/home-assistant:stable >/dev/null
|
||||
|
||||
echo "==> Auf Home Assistant warten"
|
||||
for _ in $(seq 1 60); do
|
||||
if [ "$(curl -s -o /dev/null -w '%{http_code}' --max-time 3 "$BASIS/" || true)" != "000" ]; then
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
echo "==> Nutzer anlegen und Token erzeugen"
|
||||
TOKEN="$(node "$HIER/einrichten.mjs" "$BASIS")"
|
||||
if [ -z "$TOKEN" ]; then
|
||||
echo "FEHLER: kein Token erhalten" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> pyscript installieren"
|
||||
VERSION="$(curl -s https://api.github.com/repos/custom-components/pyscript/releases/latest \
|
||||
| grep -o '"tag_name": *"[^"]*"' | cut -d'"' -f4)"
|
||||
TMPDL="$(mktemp -d)"
|
||||
curl -sL -o "$TMPDL/pyscript.zip" \
|
||||
"https://github.com/custom-components/pyscript/archive/refs/tags/${VERSION}.zip"
|
||||
unzip -oq "$TMPDL/pyscript.zip" -d "$TMPDL"
|
||||
mkdir -p "$ZIEL/custom_components"
|
||||
cp -r "$TMPDL/pyscript-${VERSION}/custom_components/pyscript" "$ZIEL/custom_components/"
|
||||
rm -rf "$TMPDL"
|
||||
echo " pyscript $VERSION"
|
||||
|
||||
echo "==> Projektdateien einspielen"
|
||||
cp "$HIER/konfiguration.yaml" "$ZIEL/configuration.yaml"
|
||||
cp -r "$PROJEKT/homeassistant/pyscript" "$ZIEL/pyscript"
|
||||
mkdir -p "$ZIEL/www" "$ZIEL/audi_dashboard"
|
||||
cp -r "$PROJEKT/homeassistant/www/." "$ZIEL/www/"
|
||||
cp "$PROJEKT/homeassistant/data/fahrzeugprofil.example.json" \
|
||||
"$ZIEL/audi_dashboard/fahrzeugprofil.json"
|
||||
cp "$PROJEKT/homeassistant/data/shell_beleg_parser.py" "$ZIEL/audi_dashboard/"
|
||||
: > "$ZIEL/audi_dashboard/fahrten.jsonl"
|
||||
: > "$ZIEL/audi_dashboard/tankvorgaenge.jsonl"
|
||||
printf '%s' "$TOKEN" > "$ZIEL/audi_dashboard/ha_token.txt"
|
||||
|
||||
echo "==> Neu starten"
|
||||
docker restart "$NAME" >/dev/null
|
||||
|
||||
echo "==> Auf Backend warten"
|
||||
for _ in $(seq 1 60); do
|
||||
if [ "$(curl -s -o /dev/null -w '%{http_code}' --max-time 3 \
|
||||
-H "Authorization: Bearer $TOKEN" "$BASIS/api/" || true)" = "200" ]; then
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
echo "==> Token nach companion-app/.env.local schreiben"
|
||||
printf 'VITE_TEST_URL=%s\nVITE_TEST_TOKEN=%s\n' "$BASIS" "$TOKEN" \
|
||||
> "$PROJEKT/companion-app/.env.local"
|
||||
|
||||
echo
|
||||
echo "Fertig. $BASIS · Benutzer test · Passwort testtest123"
|
||||
echo "Token liegt in companion-app/.env.local (gitignored)."
|
||||
@@ -0,0 +1,102 @@
|
||||
// Legt in einer frischen Home-Assistant-Instanz einen Testnutzer an und gibt ein
|
||||
// langlebiges Zugriffstoken auf der Standardausgabe aus. Meldungen gehen auf die
|
||||
// Fehlerausgabe, damit das aufrufende Skript nur das Token einliest.
|
||||
// Aufruf: node einrichten.mjs http://localhost:18123
|
||||
|
||||
const BASIS = process.argv[2] ?? "http://localhost:18123"
|
||||
const CLIENT_ID = BASIS + "/"
|
||||
const BENUTZER = "test"
|
||||
const PASSWORT = "testtest123"
|
||||
|
||||
const melden = (text) => process.stderr.write(` ${text}\n`)
|
||||
|
||||
async function jsonPost(pfad, koerper, token) {
|
||||
const kopf = { "Content-Type": "application/json" }
|
||||
if (token) kopf.Authorization = `Bearer ${token}`
|
||||
const antwort = await fetch(BASIS + pfad, {
|
||||
method: "POST",
|
||||
headers: kopf,
|
||||
body: JSON.stringify(koerper),
|
||||
})
|
||||
const text = await antwort.text()
|
||||
if (!antwort.ok) throw new Error(`${pfad} -> ${antwort.status}: ${text.slice(0, 200)}`)
|
||||
return text ? JSON.parse(text) : {}
|
||||
}
|
||||
|
||||
// 1. Onboarding-Schritt "user"
|
||||
const { auth_code: authCode } = await jsonPost("/api/onboarding/users", {
|
||||
client_id: CLIENT_ID,
|
||||
name: "Test Nutzer",
|
||||
username: BENUTZER,
|
||||
password: PASSWORT,
|
||||
language: "de",
|
||||
})
|
||||
melden("Nutzer angelegt")
|
||||
|
||||
// 2. Auth-Code gegen ein kurzlebiges Zugriffstoken tauschen
|
||||
const tokenAntwort = await fetch(BASIS + "/auth/token", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: new URLSearchParams({
|
||||
grant_type: "authorization_code",
|
||||
code: authCode,
|
||||
client_id: CLIENT_ID,
|
||||
}).toString(),
|
||||
})
|
||||
const token = await tokenAntwort.json()
|
||||
if (!token.access_token) throw new Error("kein access_token: " + JSON.stringify(token))
|
||||
melden("Zugriffstoken erhalten")
|
||||
|
||||
// 3. Restliche Onboarding-Schritte abschliessen (Fehler sind unkritisch)
|
||||
for (const [pfad, koerper] of [
|
||||
["/api/onboarding/core_config", {}],
|
||||
["/api/onboarding/analytics", {}],
|
||||
["/api/onboarding/integration", { client_id: CLIENT_ID, redirect_uri: CLIENT_ID }],
|
||||
]) {
|
||||
try {
|
||||
await jsonPost(pfad, koerper, token.access_token)
|
||||
} catch {
|
||||
// Schritt bereits erledigt oder in dieser Version nicht vorhanden
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Langlebiges Token ueber die WebSocket-API erzeugen
|
||||
const wsUrl = BASIS.replace(/^http/, "ws") + "/api/websocket"
|
||||
const llat = await new Promise((aufloesen, ablehnen) => {
|
||||
const ws = new WebSocket(wsUrl)
|
||||
const zeitgeber = setTimeout(() => {
|
||||
ablehnen(new Error("Zeitueberschreitung beim Erzeugen des Tokens"))
|
||||
ws.close()
|
||||
}, 20000)
|
||||
ws.onmessage = (nachricht) => {
|
||||
const daten = JSON.parse(nachricht.data)
|
||||
if (daten.type === "auth_required") {
|
||||
ws.send(JSON.stringify({ type: "auth", access_token: token.access_token }))
|
||||
} else if (daten.type === "auth_ok") {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
id: 1,
|
||||
type: "auth/long_lived_access_token",
|
||||
client_name: `DataMetric360 Test ${Date.now()}`,
|
||||
lifespan: 365,
|
||||
}),
|
||||
)
|
||||
} else if (daten.type === "result") {
|
||||
clearTimeout(zeitgeber)
|
||||
if (daten.success) aufloesen(daten.result)
|
||||
else ablehnen(new Error(JSON.stringify(daten.error)))
|
||||
ws.close()
|
||||
} else if (daten.type === "auth_invalid") {
|
||||
clearTimeout(zeitgeber)
|
||||
ablehnen(new Error("auth_invalid"))
|
||||
ws.close()
|
||||
}
|
||||
}
|
||||
ws.onerror = () => {
|
||||
clearTimeout(zeitgeber)
|
||||
ablehnen(new Error("WebSocket-Verbindung fehlgeschlagen"))
|
||||
}
|
||||
})
|
||||
melden("langlebiges Token erzeugt")
|
||||
|
||||
process.stdout.write(llat)
|
||||
@@ -0,0 +1,159 @@
|
||||
# Wegwerf-Testinstanz fuer DataMetric360.
|
||||
# Bildet die Entitaeten der HACS-Integration TommiG1/HA_VAG-EU-Data-Act nach,
|
||||
# damit das pyscript-Backend echte Werte sieht. Ueber die input_*-Helfer sind
|
||||
# die Werte zur Laufzeit aenderbar (Fahrterkennung, Reifenzaehler, Tankerkennung
|
||||
# haengen an @state_trigger).
|
||||
|
||||
default_config:
|
||||
|
||||
pyscript:
|
||||
allow_all_imports: true
|
||||
|
||||
panel_custom:
|
||||
- name: audi-dashboard-panel
|
||||
sidebar_title: Mein Audi
|
||||
sidebar_icon: mdi:car-sports
|
||||
module_url: /local/audi-dashboard-panel.js?v=1
|
||||
embed_iframe: false
|
||||
trust_external_script: false
|
||||
|
||||
input_number:
|
||||
test_kilometerstand:
|
||||
name: Test Kilometerstand
|
||||
min: 0
|
||||
max: 999999
|
||||
step: 1
|
||||
initial: 48250
|
||||
mode: box
|
||||
test_tankfuellstand:
|
||||
name: Test Tankfuellstand
|
||||
min: 0
|
||||
max: 100
|
||||
step: 1
|
||||
initial: 62
|
||||
mode: box
|
||||
test_reichweite:
|
||||
name: Test Reichweite
|
||||
min: 0
|
||||
max: 1200
|
||||
step: 1
|
||||
initial: 385
|
||||
mode: box
|
||||
|
||||
input_text:
|
||||
test_wlan:
|
||||
name: Test WLAN
|
||||
initial: Zuhause
|
||||
max: 60
|
||||
|
||||
input_boolean:
|
||||
test_tuer_vl:
|
||||
name: Test Tuer vorne links
|
||||
test_fenster_vl:
|
||||
name: Test Fenster vorne links
|
||||
test_entriegelt:
|
||||
name: Test entriegelt
|
||||
|
||||
template:
|
||||
- sensor:
|
||||
- name: "iPhone WiFi Connection"
|
||||
unique_id: test_iphone_wifi
|
||||
state: "{{ states('input_text.test_wlan') }}"
|
||||
- name: "Audi RS 4 Avant Mileage"
|
||||
unique_id: test_audi_mileage
|
||||
state: "{{ states('input_number.test_kilometerstand') | int(0) }}"
|
||||
unit_of_measurement: km
|
||||
state_class: total_increasing
|
||||
- name: "Audi RS 4 Avant Fuel Level"
|
||||
unique_id: test_audi_fuel
|
||||
state: "{{ states('input_number.test_tankfuellstand') | int(0) }}"
|
||||
unit_of_measurement: "%"
|
||||
- name: "Audi RS 4 Avant Range Primary"
|
||||
unique_id: test_audi_range
|
||||
state: "{{ states('input_number.test_reichweite') | int(0) }}"
|
||||
unit_of_measurement: km
|
||||
- name: "Audi RS 4 Avant Oil Change Due"
|
||||
unique_id: test_audi_oil_due
|
||||
state: "2027-03-14T00:00:00+00:00"
|
||||
device_class: timestamp
|
||||
- name: "Audi RS 4 Avant Oil Change Distance"
|
||||
unique_id: test_audi_oil_dist
|
||||
state: "-11750"
|
||||
unit_of_measurement: km
|
||||
- name: "Audi RS 4 Avant Inspection Due"
|
||||
unique_id: test_audi_insp_due
|
||||
state: "2027-03-14T00:00:00+00:00"
|
||||
device_class: timestamp
|
||||
- name: "Audi RS 4 Avant Inspection Distance"
|
||||
unique_id: test_audi_insp_dist
|
||||
state: "-11750"
|
||||
unit_of_measurement: km
|
||||
- binary_sensor:
|
||||
- name: "Audi RS 4 Avant Front Left Door"
|
||||
unique_id: test_audi_door_fl
|
||||
state: "{{ is_state('input_boolean.test_tuer_vl', 'on') }}"
|
||||
device_class: door
|
||||
- name: "Audi RS 4 Avant Front Right Door"
|
||||
unique_id: test_audi_door_fr
|
||||
state: "off"
|
||||
device_class: door
|
||||
- name: "Audi RS 4 Avant Rear Left Door"
|
||||
unique_id: test_audi_door_rl
|
||||
state: "off"
|
||||
device_class: door
|
||||
- name: "Audi RS 4 Avant Rear Right Door"
|
||||
unique_id: test_audi_door_rr
|
||||
state: "off"
|
||||
device_class: door
|
||||
- name: "Audi RS 4 Avant Front Left Window"
|
||||
unique_id: test_audi_win_fl
|
||||
state: "{{ is_state('input_boolean.test_fenster_vl', 'on') }}"
|
||||
device_class: window
|
||||
- name: "Audi RS 4 Avant Front Right Window"
|
||||
unique_id: test_audi_win_fr
|
||||
state: "off"
|
||||
device_class: window
|
||||
- name: "Audi RS 4 Avant Rear Left Window"
|
||||
unique_id: test_audi_win_rl
|
||||
state: "off"
|
||||
device_class: window
|
||||
- name: "Audi RS 4 Avant Rear Right Window"
|
||||
unique_id: test_audi_win_rr
|
||||
state: "off"
|
||||
device_class: window
|
||||
- name: "Audi RS 4 Avant Front Left Door Lock"
|
||||
unique_id: test_audi_lock_fl
|
||||
state: "{{ is_state('input_boolean.test_entriegelt', 'on') }}"
|
||||
device_class: lock
|
||||
- name: "Audi RS 4 Avant Front Right Door Lock"
|
||||
unique_id: test_audi_lock_fr
|
||||
state: "off"
|
||||
device_class: lock
|
||||
- name: "Audi RS 4 Avant Rear Left Door Lock"
|
||||
unique_id: test_audi_lock_rl
|
||||
state: "off"
|
||||
device_class: lock
|
||||
- name: "Audi RS 4 Avant Rear Right Door Lock"
|
||||
unique_id: test_audi_lock_rr
|
||||
state: "off"
|
||||
device_class: lock
|
||||
- name: "Audi RS 4 Avant Tailgate"
|
||||
unique_id: test_audi_tailgate
|
||||
state: "off"
|
||||
device_class: door
|
||||
- name: "Audi RS 4 Avant Tailgate Lock"
|
||||
unique_id: test_audi_tailgate_lock
|
||||
state: "off"
|
||||
device_class: lock
|
||||
- name: "Audi RS 4 Avant Hood"
|
||||
unique_id: test_audi_hood
|
||||
state: "off"
|
||||
device_class: door
|
||||
- name: "Audi RS 4 Avant Hood Lock"
|
||||
unique_id: test_audi_hood_lock
|
||||
state: "off"
|
||||
device_class: lock
|
||||
|
||||
input_button:
|
||||
test_refresh:
|
||||
name: Test Refresh Now
|
||||
Reference in New Issue
Block a user