Watt Budget Ladekosten-Rechner

Eine Home-Assistant-Karte für jedes Elektroauto

Akku, Reichweite, laufende Ladung, Verriegelung, Reifen, Kilometerstand: eine Karte für Renault, Tesla, Kia, BMW, Peugeot, Volkswagen und die anderen. Sie geben nur eine Entität an, den Akku, und die Karte findet den Rest über das Gerät Ihres Autos.

  • Kostenlos, zum Kopieren und Einfügen
  • Eine einzige Entität angeben
  • Eine einzige Erweiterung: button-card
  • Texte in 4 Sprachen, je nach Home Assistant
Mein Auto
Zu Hause · vor 3 Min. gesehen
Eingesteckt
47%176 km●
050 %100 %
Türen
Verriegelt
Kilometer
33.950 km
Ziel
100 %
Die Karte mit Beispielwerten: Es werden nur die Zeilen angezeigt, für die die Karte Daten findet.

Was Sie brauchen

  • Home Assistant mit der Erweiterung button-card aus HACS (Kategorie Frontend).
  • Ihr Auto in Home Assistant, über eine beliebige Integration: Die Anleitung Ihrer Marke erklärt, wie.
  • Der Akkustand des Autos in % als Entität (zum Beispiel sensor.mein_auto_akku). Die Tabelle unten sagt, welche Entität bei welcher Marke zu nehmen ist.

1Die Karte installieren

  1. Karte einfügenDashboard → bearbeiten → Karte hinzufügen → Manuell, dann den Code aus dem nächsten Abschnitt einfügen.
  2. Akku angebenErsetzen Sie im Block variables oben sensor.ma_voiture_batterie durch die Akku-Entität Ihres Autos. Ändern Sie auch nom und, wenn Sie möchten, das Foto (image) und die Akkukapazität (capacite_kwh).
  3. Ansehen, was erscheintDie Karte zeigt die Daten, die sie erkennt (Reichweite, Laden, Stecker, Verriegelung, Kilometerstand, Reifen, Öffnungen …) und lässt den Rest weg, ohne etwas zu zerstören.
  4. Bei Bedarf korrigierenFehlt ein Wert oder ist er falsch, erzwingen Sie die Entität mit der passenden Variable (Tabelle „Variablen“).

Der Code der Karte

Fügen Sie ihn unverändert ein und ändern Sie dann den Block variables oben.

type: custom:button-card
show_name: false
show_icon: false
show_state: false
show_label: true
tap_action:
  action: none
hold_action:
  action: none
triggers_update: all
grid_options:
  columns: full
variables:
  nom: Ma voiture                            # nom affiché en haut de la carte
  image: ''                                  # photo de la voiture : '' pour aucune, ou /local/… (dossier config/www)
  batterie: sensor.ma_voiture_batterie       # LA SEULE ENTITÉ OBLIGATOIRE : le niveau de batterie (en %) de votre voiture
  capacite_kwh: 0                            # capacité de la batterie en kWh (facultatif : sert à estimer les kWh manquants ; 0 pour ne pas les afficher)
  pression_min: 2.0                          # plage de pression des pneus jugée normale (bar)
  pression_max: 3.0
  # Facultatif : la carte retrouve seule les autres données à partir de l'appareil de la voiture.
  # Si l'une est fausse ou absente, indiquez l'entité à utiliser (ex. sensor.ma_voiture_autonomie).
  autonomie: ''
  odometre: ''
  en_charge: ''
  branche: ''
  puissance: ''
  limite: ''
  temps_restant: ''
  verrou: ''
  position: ''
  temp_habitacle: ''
  pression_av_g: ''
  pression_av_d: ''
  pression_ar_g: ''
  pression_ar_d: ''
extra_styles: >-
  @keyframes cvmv { to { background-position: 26px 0; } }
  @keyframes cvpulse { 50% { opacity: .4; } }
  .cvhalo { animation: cvpulse 2.4s ease-in-out infinite; }
  @keyframes cvpl { 50% { opacity: .7; } }
styles:
  card:
    - background: |
        [[[
        const s0 = parseFloat(states[variables.batterie]?.state);
        const s = isNaN(s0) ? 76 : Math.max(0, Math.min(100, s0));
        const S = [[0, [255, 93, 93]], [25, [255, 138, 61]], [45, [247, 209, 84]], [70, [61, 220, 151]], [100, [79, 163, 255]]];
        let c = [79, 163, 255];
        for (let i = 1; i < S.length; i++) { if (s <= S[i][0]) { const t = (s - S[i - 1][0]) / (S[i][0] - S[i - 1][0]); c = S[i - 1][1].map((x, j) => Math.round(x + (S[i][1][j] - x) * t)); break; } }
        return 'radial-gradient(60% 190px at 50% 0, rgba(' + c.join(',') + ',0.14), transparent 75%), #1c1f24';
        ]]]
    - border: 1px solid #2b3038
    - border-radius: 18px
    - padding: 16px
    - box-shadow: none
    - overflow: hidden
    - pointer-events: auto
  label:
    - width: 100%
    - text-align: left
    - white-space: normal
    - justify-self: stretch
    - pointer-events: auto
label: |
  [[[
  const V = variables;
  const S = e => (e ? states[e]?.state : undefined);
  const N = e => { const x = parseFloat(S(e)); return (e && !isNaN(x)) ? x : null; };
  const lang = String(hass.language || 'fr').slice(0, 2);
  const PC = lang === 'en' ? '%' : ' %';
  const fr = (x, d) => x === null ? '—' : x.toFixed(d).replace('.', lang === 'en' ? '.' : ',');
  const TT = {
    fr: { charging: 'En charge', driving: 'En route', parked: 'Garée', home: 'À la maison', away: 'Hors domicile', seen: 'vu', nodata: 'pas de donnée', now: "à l'instant", ago: (n, u) => 'il y a ' + n + ' ' + u, d: 'j', target: 'Cible', targetIn: (t, d) => t + PC + ' dans ' + d, maxAt: k => 'Max ' + k + ' km à 100 %', missing: k => 'manque ' + k + ' kWh', locked: 'Verrouillée', unlocked: 'Déverrouillée', plugged: 'Branchée', unplugged: 'Non branchée', homeChip: 'Maison', lvl: ['Critique', 'Bas', 'Moyen', 'Bon', 'Plein'], cabin: 'Habitacle', hv: 'Batterie', odo: 'Odomètre', outside: 'Extérieur', tw: 'Pneus et ouvertures', ok: 'Tout est OK', tyres: n => n + ' pneu' + (n > 1 ? 's' : '') + ' à vérifier', opens: n => n + ' ouverture' + (n > 1 ? 's' : '') + ' ouverte' + (n > 1 ? 's' : ''), hist: 'Historique de la batterie', noEnt: 'Entité introuvable : ', fix: 'Corrigez la variable « batterie » de la carte.' },
    en: { charging: 'Charging', driving: 'Driving', parked: 'Parked', home: 'At home', away: 'Away from home', seen: 'seen', nodata: 'no data', now: 'just now', ago: (n, u) => n + ' ' + u + ' ago', d: 'd', target: 'Target', targetIn: (t, d) => t + PC + ' in ' + d, maxAt: k => 'Max ' + k + ' km at 100%', missing: k => k + ' kWh missing', locked: 'Locked', unlocked: 'Unlocked', plugged: 'Plugged in', unplugged: 'Unplugged', homeChip: 'Home', lvl: ['Critical', 'Low', 'Medium', 'Good', 'Full'], cabin: 'Cabin', hv: 'Battery', odo: 'Odometer', outside: 'Outside', tw: 'Tyres and openings', ok: 'All good', tyres: n => n + ' tyre' + (n > 1 ? 's' : '') + ' to check', opens: n => n + ' opening' + (n > 1 ? 's' : '') + ' open', hist: 'Battery history', noEnt: 'Entity not found: ', fix: 'Fix the “batterie” variable of the card.' },
    es: { charging: 'Cargando', driving: 'En marcha', parked: 'Aparcado', home: 'En casa', away: 'Fuera de casa', seen: 'visto', nodata: 'sin datos', now: 'ahora mismo', ago: (n, u) => 'hace ' + n + ' ' + u, d: 'd', target: 'Objetivo', targetIn: (t, d) => t + PC + ' en ' + d, maxAt: k => 'Máx. ' + k + ' km al 100 %', missing: k => 'faltan ' + k + ' kWh', locked: 'Bloqueado', unlocked: 'Desbloqueado', plugged: 'Conectado', unplugged: 'Desconectado', homeChip: 'Casa', lvl: ['Crítico', 'Bajo', 'Medio', 'Bueno', 'Lleno'], cabin: 'Habitáculo', hv: 'Batería', odo: 'Cuentakm', outside: 'Exterior', tw: 'Neumáticos y aperturas', ok: 'Todo correcto', tyres: n => n + ' neumático' + (n > 1 ? 's' : '') + ' a revisar', opens: n => n + ' apertura' + (n > 1 ? 's' : '') + ' abierta' + (n > 1 ? 's' : ''), hist: 'Historial de la batería', noEnt: 'Entidad no encontrada: ', fix: 'Corrige la variable «batterie» de la tarjeta.' },
    de: { charging: 'Lädt', driving: 'Unterwegs', parked: 'Geparkt', home: 'Zu Hause', away: 'Nicht zu Hause', seen: 'zuletzt gesehen', nodata: 'keine Daten', now: 'gerade eben', ago: (n, u) => 'vor ' + n + ' ' + u, d: 'T', target: 'Ziel', targetIn: (t, d) => t + PC + ' in ' + d, maxAt: k => 'Max. ' + k + ' km bei 100 %', missing: k => 'es fehlen ' + k + ' kWh', locked: 'Verriegelt', unlocked: 'Entriegelt', plugged: 'Angesteckt', unplugged: 'Nicht angesteckt', homeChip: 'Zuhause', lvl: ['Kritisch', 'Niedrig', 'Mittel', 'Gut', 'Voll'], cabin: 'Innenraum', hv: 'Akku', odo: 'Kilometer', outside: 'Außen', tw: 'Reifen und Öffnungen', ok: 'Alles in Ordnung', tyres: n => n + ' Reifen prüfen', opens: n => n + ' Öffnung' + (n > 1 ? 'en' : '') + ' offen', hist: 'Akkuverlauf', noEnt: 'Entität nicht gefunden: ', fix: 'Korrigieren Sie die Variable „batterie“ der Karte.' }
  };
  const T = TT[lang] || TT.en;

  const C = { g: '#3ddc97', am: '#f5a524', rd: '#ff5d5d', bl: '#4fa3ff', yl: '#f7d154', vi: '#b18cff', or: '#ff8a3d', mu: '#8a919c' };
  if (!V.batterie || !states[V.batterie]) {
    return '<div style="color:#e8eaed;font-size:13px;line-height:1.4"><b style="color:' + C.am + '">' + T.noEnt + '<code>' + (V.batterie || '—') + '</code></b><br>' + T.fix + '</div>';
  }

  /* Retrouver les autres entités de la voiture : même appareil que l'entité « batterie » (ou, à défaut, même début de nom), puis classe d'appareil et mots du nom. */
  const REG = hass.entities || {};
  const dev = REG[V.batterie]?.device_id;
  const base = (V.batterie.split('.')[1] || '').replace(/_(battery_level|battery|soc|state_of_charge|ev_battery_level|batterie|niveau_de_batterie|niveau_batterie|ladezustand|nivel_de_bateria|bateria)$/, '');
  const cand = Object.keys(states).filter(k => dev ? REG[k]?.device_id === dev : k.indexOf('.' + base + '_') > 0);
  const at = k => states[k]?.attributes || {};
  const nm = k => (k.split('.')[1] + ' ' + (at(k).friendly_name || '')).toLowerCase();
  const dc = k => at(k).device_class;
  const un = k => String(at(k).unit_of_measurement || '');
  const find = (dom, test) => cand.find(k => k.startsWith(dom + '.') && test(k)) || null;
  const use = (v, ...fs) => { if (v) return v; for (const f of fs) { const r = f(); if (r) return r; } return null; };
  const ON = /^(on|true|yes|charging|in_progress|charge_in_progress|chargingactive|started|laden|en_charge|cargando)$/i;
  const PLUG = /^(on|true|yes|connected|plugged|plugged_in|plugged_in_charging|charging|angesteckt|branche|conectado)$/i;

  const eSoc = V.batterie;
  const eRng = use(V.autonomie, () => find('sensor', k => (dc(k) === 'distance' || /^(km|mi)$/.test(un(k))) && /range|autonom|reichweite/.test(nm(k)) && !/max|total|fuel|carburant|100|target|cible|limit/.test(nm(k))));
  const eOdo = use(V.odometre, () => find('sensor', k => (dc(k) === 'distance' || /^(km|mi)$/.test(un(k))) && /odom|mileage|kilom|kilometer|cuentakm|kilometrage|kilométrage/.test(nm(k))));
  const eChg = use(V.en_charge, () => find('binary_sensor', k => dc(k) === 'battery_charging'),
    () => find('binary_sensor', k => /charging|en_charge|laden|cargando/.test(nm(k)) && !/plug|cable|prise|stecker|connect|enchufe/.test(nm(k))),
    () => find('sensor', k => /charging_state|charge_state|charging_status|charge_status|etat_de_charge|ladestatus|ladevorgang|estado_de_carga/.test(nm(k)) && !/power|puiss|leistung|potencia|remain|restant|limit/.test(nm(k))));
  const ePlg = use(V.branche, () => find('binary_sensor', k => dc(k) === 'plug'),
    () => find('binary_sensor', k => /plug|cable|prise|stecker|enchufe/.test(nm(k)) && !/wifi|online|internet|remote/.test(nm(k))),
    () => find('sensor', k => /plug_state|plugged|prise|stecker|enchufe/.test(nm(k))));
  const ePow = use(V.puissance, () => find('sensor', k => (dc(k) === 'power' || /^k?w$/i.test(un(k))) && /charg|lad|carga|puiss|leist|potencia/.test(nm(k))));
  const eTgt = use(V.limite, () => cand.find(k => /^(sensor|number)\./.test(k) && un(k) === '%' && k !== eSoc && /limit|target|cible|objectif|ziel|max_?soc|objetivo/.test(nm(k))) || null);
  const eRem = use(V.temps_restant, () => find('sensor', k => /remain|restant|time_to_full|until_full|time_to_complete|verbleib|restante/.test(nm(k)) && (dc(k) === 'duration' || /^(min|h|s)$/.test(un(k)))));
  const eLck = use(V.verrou, () => find('lock', k => true), () => find('binary_sensor', k => dc(k) === 'lock'));
  const eTrk = use(V.position, () => find('device_tracker', k => true));
  const eCab = use(V.temp_habitacle, () => find('sensor', k => dc(k) === 'temperature' && /interior|cabin|inside|habitacle|innen|interior|cabina/.test(nm(k))));
  const eOut = find('sensor', k => dc(k) === 'temperature' && /outside|outdoor|exterior|ext[eé]rieur|außen|aussen/.test(nm(k)));
  const eBat = find('sensor', k => dc(k) === 'temperature' && /battery|batterie|hv|akku|bater/.test(nm(k)) && !/interior|cabin|habitacle/.test(nm(k)));
  const e12v = find('sensor', k => dc(k) === 'voltage' && /12|aux/.test(nm(k)));
  const eSpd = find('sensor', k => dc(k) === 'speed');
  const eSeen = find('sensor', k => dc(k) === 'timestamp' && /last.?(seen|update|updated|contact|sync|connection)|derni[eè]re|aktualis|actualiz/.test(nm(k)));

  const soc = N(eSoc);
  const socV = soc === null ? 0 : Math.max(0, Math.min(100, soc));
  const rngRaw = N(eRng);
  const rng = rngRaw !== null && /^mi$/.test(un(eRng)) ? rngRaw * 1.609 : rngRaw;
  const pwRaw = N(ePow), pw = pwRaw !== null && /^w$/i.test(un(ePow)) ? pwRaw / 1000 : pwRaw;
  const charging = (eChg ? ON.test(String(S(eChg))) : false) || (pw !== null && pw > 0.2);
  const plugged = charging || (ePlg ? PLUG.test(String(S(ePlg))) : false);
  const tgtRaw = N(eTgt), tgt = tgtRaw !== null && tgtRaw > 0 && tgtRaw <= 100 ? tgtRaw : 100;
  const miss = (V.capacite_kwh > 0 && soc !== null) ? Math.max(0, (tgt - soc) / 100 * V.capacite_kwh) : null;
  const remRaw = N(eRem), rem = remRaw === null ? null : Math.round(un(eRem) === 'h' ? remRaw * 60 : un(eRem) === 's' ? remRaw / 60 : remRaw);
  const spd = N(eSpd) || 0;
  const driving = !charging && spd > 3;
  let locked = null;
  if (eLck) { const l = String(S(eLck)).toLowerCase(); locked = eLck.startsWith('lock.') ? l === 'locked' : dc(eLck) === 'lock' ? l === 'off' : (l === 'locked' || l === 'on'); }

  let home = null;
  if (eTrk) {
    const ts = String(S(eTrk)).toLowerCase(), ta = at(eTrk), hz = states['zone.home']?.attributes;
    if (ts === 'home') home = true;
    else if (ta.latitude !== undefined && hz) { const dLa = (ta.latitude - hz.latitude) * 111320, dLo = (ta.longitude - hz.longitude) * 111320 * Math.cos(hz.latitude * Math.PI / 180); home = Math.sqrt(dLa * dLa + dLo * dLo) < 200; }
    else if (ts !== 'unknown' && ts !== 'unavailable') home = false;
  }
  const seenIso = (eSeen && S(eSeen)) || states[eSoc].last_reported || states[eSoc].last_updated;
  const age = seenIso ? Math.round((Date.now() - new Date(seenIso).getTime()) / 60000) : null;
  const ageTxt = age === null || isNaN(age) ? T.nodata : age < 1 ? T.now : age < 60 ? T.ago(age, 'min') : age < 1440 ? T.ago(Math.round(age / 60), 'h') : T.ago(Math.round(age / 1440), T.d);

  const STOPS = [[0, [255, 93, 93]], [25, [255, 138, 61]], [45, [247, 209, 84]], [70, [61, 220, 151]], [100, [79, 163, 255]]];
  const colAt = s => { for (let i = 1; i < STOPS.length; i++) { if (s <= STOPS[i][0]) { const a = STOPS[i - 1], b = STOPS[i], t = (s - a[0]) / (b[0] - a[0]); return '#' + a[1].map((v, j) => Math.round(v + (b[1][j] - v) * t).toString(16).padStart(2, '0')).join(''); } } return '#4fa3ff'; };
  const sc = colAt(socV);
  const lvl = T.lvl[socV < 20 ? 0 : socV < 40 ? 1 : socV < 60 ? 2 : socV <= 90 ? 3 : 4];
  const mix = (c, a) => c + Math.round(a * 255).toString(16).padStart(2, '0');
  const IC = (ic, n, c) => '<span style="width:' + n + 'px;height:' + n + 'px;flex:none;display:inline-flex;overflow:hidden;align-items:center;justify-content:center;line-height:0"><ha-icon icon="' + ic + '" style="--mdc-icon-size:' + n + 'px' + (c ? ';color:' + c : '') + '"></ha-icon></span>';

  let st, stIc, stC;
  if (charging) { st = T.charging + (pw ? ' ' + fr(pw, 1) + ' kW' : ''); stIc = 'mdi:lightning-bolt'; stC = C.g; }
  else if (driving) { st = T.driving; stIc = 'mdi:steering'; stC = C.bl; }
  else if (plugged) { st = T.plugged; stIc = 'mdi:power-plug'; stC = C.bl; }
  else { st = T.parked; stIc = 'mdi:parking'; stC = C.vi; }

  const where = home === null ? '' : home ? T.home : T.away;
  const sub = where + (where ? ' · ' : '') + (driving ? Math.round(spd) + ' km/h' : T.seen + ' ' + ageTxt);

  let line;
  if (charging) {
    line = rem ? T.targetIn(Math.round(tgt), rem >= 60 ? Math.floor(rem / 60) + ' h ' + String(rem % 60).padStart(2, '0') : rem + ' min') : T.target + ' ' + Math.round(tgt) + PC;
  } else if (driving) {
    line = (rng !== null && soc > 0) ? T.maxAt(fr(rng * 100 / soc, 0)) : '';
  } else {
    line = T.target + ' ' + Math.round(tgt) + PC + (miss !== null && miss > 0 ? ' · ' + T.missing(fr(miss, 1)) : '');
  }

  const chip = (ic, t, c) => '<span style="display:inline-flex;align-items:center;gap:5px;border-radius:20px;padding:4px 10px;font-size:12px;margin:0 4px 5px 0;background:' + mix(c, 0.14) + ';color:' + c + '">' + IC(ic, 15) + t + '</span>';
  let chips = '';
  if (locked !== null) chips += locked ? chip('mdi:lock', T.locked, C.g) : chip('mdi:lock-open-variant', T.unlocked, C.am);
  if (charging) chips += chip('mdi:ev-plug-type2', pw ? fr(pw, 1) + ' kW' : T.charging, C.yl);
  else if (eChg || ePlg) chips += plugged ? chip('mdi:power-plug', T.plugged, C.bl) : chip('mdi:power-plug-off', T.unplugged, C.mu);
  if (home) chips += chip('mdi:home', T.homeChip, C.vi);

  const tile = (ic, k, v, c, vc) => '<div style="background:#252a31;border-radius:12px;padding:8px 10px;min-width:0;text-align:center;border-top:2px solid ' + c + '"><div style="font-size:11px;color:' + C.mu + ';display:flex;align-items:center;justify-content:center;gap:5px;white-space:nowrap">' + IC(ic, 14, c) + '<span>' + k + '</span></div><div style="font-size:14px;font-weight:500;white-space:nowrap;margin-top:3px;text-align:center;color:' + (vc || '#e8eaed') + '">' + v + '</div></div>';
  const tmp = e => { const x = N(e); return x === null ? null : fr(/f/i.test(un(e)) ? (x - 32) * 5 / 9 : x, 0) + ' °C'; };
  const odoRaw = N(eOdo), odo = odoRaw !== null && /^mi$/.test(un(eOdo)) ? odoRaw * 1.609 : odoRaw;
  const v12 = N(e12v);
  let tiles = '';
  if (tmp(eCab) !== null) tiles += tile('mdi:car-seat', T.cabin, tmp(eCab), C.or);
  if (tmp(eOut) !== null) tiles += tile('mdi:thermometer', T.outside, tmp(eOut), C.bl);
  if (tmp(eBat) !== null) tiles += tile('mdi:battery-high', T.hv, tmp(eBat), C.bl);
  if (v12 !== null) tiles += tile('mdi:car-battery', '12 V', fr(v12, 1) + ' V', C.yl, v12 < 12.2 ? C.rd : null);
  if (odo !== null) tiles += tile('mdi:counter', T.odo, Math.round(odo).toLocaleString(lang) + ' km', C.vi);

  /* Pneus : par position (avant/arrière, gauche/droite) ; les pressions en kPa ou en psi sont converties en bar. */
  const POS = {
    fl: [V.pression_av_g, /front.?left|avant.?gauche|vorn.?links|vorne.?links|delantera.?izq|\bfl\b|\blf\b|\bav.?g\b/],
    fr: [V.pression_av_d, /front.?right|avant.?droit|vorn.?rechts|vorne.?rechts|delantera.?der|\bfr\b|\brf\b|\bav.?d\b/],
    rl: [V.pression_ar_g, /rear.?left|arri[eè]re.?gauche|hinten.?links|trasera.?izq|\brl\b|\blr\b|\bar.?g\b/],
    rr: [V.pression_ar_d, /rear.?right|arri[eè]re.?droit|hinten.?rechts|trasera.?der|\brr\b|\bar.?d\b/]
  };
  const tyreAll = cand.filter(k => k.startsWith('sensor.') && dc(k) === 'pressure' && /tire|tyre|pneu|reifen|neum/.test(nm(k)));
  const bar = e => { const x = N(e); if (x === null) return null; const u = un(e).toLowerCase(); return u === 'kpa' ? x / 100 : u === 'psi' ? x / 14.5038 : u === 'pa' ? x / 100000 : u === 'hpa' || u === 'mbar' ? x / 1000 : x; };
  const used = new Set(); const tyre = {};
  for (const p of ['fl', 'fr', 'rl', 'rr']) { const e = POS[p][0] || tyreAll.find(k => !used.has(k) && POS[p][1].test(nm(k))); if (e) { used.add(e); tyre[p] = e; } }
  const rest = tyreAll.filter(k => !used.has(k));
  let nTyre = 0;
  const pill = e => { const p = bar(e), ok = p !== null && p >= V.pression_min && p <= V.pression_max; if (!ok) nTyre++; const c = ok ? C.g : C.am;
    return '<div style="display:flex;align-items:center;gap:6px;background:#252a31;border-radius:10px;padding:6px 8px;font-size:13px"><span style="width:8px;height:8px;border-radius:50%;background:' + c + ';flex:none"></span><b style="font-weight:500">' + fr(p, 2) + '</b><span style="color:' + C.mu + ';font-size:11px;margin-left:auto">bar</span></div>'; };
  const tyreList = ['fl', 'fr', 'rl', 'rr'].filter(p => tyre[p]).map(p => tyre[p]).concat(rest.slice(0, 4 - Object.keys(tyre).length));
  const tyreHtml = tyreList.map(pill).join('');
  const nOpen = cand.filter(k => (k.startsWith('binary_sensor.') && (dc(k) === 'door' || dc(k) === 'window' || dc(k) === 'opening') && S(k) === 'on') || (k.startsWith('cover.') && dc(k) === 'window' && S(k) === 'open')).length;
  const hasOpen = cand.some(k => k.startsWith('binary_sensor.') && (dc(k) === 'door' || dc(k) === 'window'));
  const showTW = tyreList.length > 0 || hasOpen;
  const sumTxt = (nTyre === 0 && nOpen === 0) ? T.ok : [nTyre ? T.tyres(nTyre) : '', nOpen ? T.opens(nOpen) : ''].filter(x => x).join(' · ');
  const sumC = (nTyre === 0 && nOpen === 0) ? C.g : C.am;

  const SEC = 'background:#20242a;border:1px solid #2b3038;border-radius:14px;padding:10px 12px;margin-top:10px';
  const H4 = (ic, t, r, rc) => '<div style="display:flex;justify-content:space-between;align-items:center;font-size:12px;color:' + C.mu + ';margin-bottom:8px"><span style="display:flex;align-items:center;gap:5px">' + IC(ic, 15) + t + '</span><span style="color:' + rc + '">' + r + '</span></div>';
  const MORE = 'onclick="event.stopPropagation();this.dispatchEvent(new CustomEvent(\'hass-more-info\',{bubbles:true,composed:true,detail:{entityId:\'' + eSoc + '\'}}))" title="' + T.hist + '"';

  let H = '<div style="color:#e8eaed;font-size:13px;line-height:1.35">';
  H += '<div style="display:flex;justify-content:space-between;align-items:flex-start"><div><div style="font-size:18px;font-weight:500">' + V.nom + '</div><div style="font-size:12px;color:' + C.mu + '">' + sub + '</div></div>';
  H += '<span style="font-size:12px;padding:3px 10px;border-radius:20px;background:' + mix(stC, 0.18) + ';color:' + stC + ';display:inline-flex;gap:4px;align-items:center;white-space:nowrap">' + IC(stIc, 14) + st + '</span></div>';

  if (V.image) {
    H += '<div style="position:relative;margin:4px 0 0">';
    if (charging) H += '<div class="cvhalo" style="position:absolute;left:8%;right:8%;bottom:0;height:26%;border-radius:50%;background:radial-gradient(closest-side,' + mix(sc, 0.6) + ',transparent)"></div>';
    H += '<img src="' + V.image + '" style="width:100%;max-width:460px;display:block;margin:0 auto;position:relative"></div>';
  } else { H += '<div style="height:10px"></div>'; }

  H += '<div ' + MORE + ' style="display:flex;align-items:baseline;gap:10px;white-space:nowrap;cursor:pointer"><span style="font-size:34px;font-weight:500;line-height:1;color:' + sc + '">' + (soc === null ? '—' : Math.round(soc) + PC) + '</span><span style="font-size:16px">' + (rng === null ? '' : fr(rng, 0) + ' km') + '</span><span style="font-size:12px;margin-left:auto;color:' + sc + '">' + lvl + '</span></div>';
  H += '<div style="font-size:12px;color:' + C.mu + ';margin:5px 0 7px">' + line + '</div>';
  const SPEC = 'linear-gradient(90deg,#ff5d5d 0%,#ff8a3d 25%,#f7d154 45%,#3ddc97 70%,#4fa3ff 100%)';
  H += '<div ' + MORE + ' style="position:relative;height:12px;margin:2px 0 4px;cursor:pointer">';
  H += '<div style="position:absolute;inset:0;border-radius:6px;overflow:hidden"><div style="position:absolute;inset:0;background:' + SPEC + '"></div>';
  if (charging) H += '<div style="position:absolute;top:0;bottom:0;left:0;width:' + socV + '%;background:repeating-linear-gradient(110deg,rgba(255,255,255,.22) 0 8px,transparent 8px 20px);background-size:26px 100%;animation:cvmv .9s linear infinite"></div>';
  H += '<div style="position:absolute;top:0;bottom:0;right:0;width:' + (100 - socV) + '%;background:#252a31"></div></div>';
  H += '<div style="position:absolute;top:-1px;bottom:-1px;left:calc(' + Math.min(100, tgt) + '% - 1px);width:2px;background:#ffffff66;border-radius:1px"></div>';
  H += '<div style="position:absolute;top:-3px;bottom:-3px;left:calc(' + socV + '% - 2px);width:4px;border-radius:2px;background:#fff;box-shadow:0 0 8px #fff' + (charging ? ';animation:cvpl 1.6s ease-in-out infinite' : '') + '"></div></div>';
  H += '<div style="display:flex;justify-content:space-between;font-size:10px;color:#5d646e;margin-bottom:10px"><span>0</span><span>20</span><span>40</span><span>60</span><span>80</span><span>100</span></div>';
  if (chips) H += '<div>' + chips + '</div>';
  if (tiles) H += '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(78px,1fr));gap:8px;margin-top:5px">' + tiles + '</div>';
  if (showTW) H += '<div style="' + SEC + '">' + H4('mdi:tire', T.tw, sumTxt, sumC) + (tyreHtml ? '<div style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px">' + tyreHtml + '</div>' : '') + '</div>';

  return H + '</div>';
  ]]]

Welche Entität für Ihre Marke?

Für jede Marke hier der Sensor für die Variable batterie, gemäß den Anleitungen dieser Seite. Die genauen Namen hängen von der Sprache Ihres Home Assistant ab: Suchen Sie den Sensor unter Einstellungen → Geräte & Dienste → Entitäten.

MarkeSensor für „batterie“
XpengDer Akkustand-Sensor von XPENG Direct oder vom WiCAN. Für einen Xpeng ist die Xpeng-Karte vollständiger (Akkudiagnose über den WiCAN).
Renault · DaciaDen Sensor Akku des Renault-Geräts (in %).
MGDen Sensor SoC (Akkustand in %) des MQTT-Geräts des Autos.
ŠkodaDen Sensor Akkustand (in %) des Škoda-Geräts.
Kia · Hyundai · GenesisDen Sensor EV Battery Level (in %) des Kia-Uvo-Geräts.
TeslaDen Sensor Akkustand (in %) des Auto-Geräts.
Peugeot · Citroën · DS · OpelDen Sensor Battery (in %) des Stellantis-Vehicles-Geräts.
Volkswagen · Cupra · SEAT · AudiDen Sensor Akkustand (in %) des VW-Group-Connect-Geräts.
BMW · MiniDen Sensor Akkustand (in %), den BMW CarData für das Auto anlegt.
FordDen Sensor Akkustand (in %) des Ford-Geräts.
NissanDen Sensor Battery (in %) des NissanConnect-Geräts.
VolvoDen Sensor Akku (in %) des Volvo-Geräts.
PolestarDen Sensor Akkustand (in %) des Polestar-Geräts.
Mercedes-BenzDen Sensor Akkustand (State of Charge, in %) des Mercedes-Geräts.
BYDDen Sensor Akkustand (in %), den der WiCAN per MQTT veröffentlicht.

Wie die Karte Ihre Daten findet

  • Sie geht von der Entität batterie aus und sucht dann die anderen Entitäten desselben Geräts in Home Assistant (oder, wenn das Gerät unbekannt ist, deren Name gleich beginnt).
  • Sie erkennt sie an ihrer Klasse (Entfernung, Leistung, Druck, Stecker, Schloss, Temperatur, Spannung) und an Wörtern im Namen, auf Französisch, Englisch, Spanisch und Deutsch.
  • Drücke in kPa oder psi, Entfernungen in Meilen und Temperaturen in °F werden umgerechnet (bar, km, °C).
  • Was angezeigt wird, hängt davon ab, was gefunden wird: Fehlende Daten zeigen einfach nichts.

Variablen

VariableZweck
nomName oben auf der Karte.
imageAutofoto: '' für keines, oder /local/… (Ordner config/www von Home Assistant).
batterieDie einzige Pflicht-Entität: der Akkustand in %.
capacite_kwhAkkukapazität in kWh, um die bis zum Ziel fehlenden kWh zu schätzen. 0, um sie auszublenden.
pression_min, pression_maxAls normal geltender Reifendruckbereich in bar.
autonomieReichweiten-Entität erzwingen (km).
odometreKilometerstand-Entität erzwingen.
en_chargeEntität „lädt“ erzwingen (Ein/Aus-Sensor oder Ladezustand).
brancheEntität „angesteckt“ erzwingen.
puissanceEntität der Ladeleistung erzwingen (kW oder W).
limiteEntität des Ladelimits oder -ziels erzwingen (%).
temps_restantEntität der verbleibenden Ladezeit erzwingen (Minuten, Stunden oder Sekunden).
verrouSchloss-Entität erzwingen: eine lock.-Entität oder ein Sensor der Klasse „Schloss“.
positionPositions-Tracker erzwingen (device_tracker.), um „Zu Hause“ anzuzeigen.
temp_habitacleInnenraum-Temperatursensor erzwingen.
pression_av_g, pression_av_d, pression_ar_g, pression_ar_dJeden Reifendrucksensor erzwingen (vorne links, vorne rechts, hinten links, hinten rechts).

Gut zu wissen

Mit simulierten Zuständen getestet, nicht mit jeder Marke

Die Logik der Karte wurde mit simulierten Zuständen getestet, die mehrere Integrationsstile nachahmen (englische oder französische Namen, Drücke in kPa oder psi, bekanntes oder unbekanntes Gerät), aber nicht in einem echten Home Assistant mit jeder Marke. Nicht alle Integrationen liefern dieselben Entitäten: Wird ein Wert nicht erkannt, erzwingen Sie ihn mit der passenden Variable oder schreiben Sie mir, damit ich die Erkennung verbessere.

Weiterführend

Kommt Ihr Auto auf anderem Weg in Home Assistant? Teilen Sie Ihre Methode: Die nächsten Anleitungen richten sich nach Ihren Rückmeldungen.

Was kostet eine Ladung wirklich? Nachttarife, Solarstrom, Schnelllader und XCombo-Codes: Watt Budget rechnet alles aus, kostenlos und ohne Konto.

Watt Budget öffnen