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
- Karte einfügenDashboard → bearbeiten → Karte hinzufügen → Manuell, dann den Code aus dem nächsten Abschnitt einfügen.
- Akku angebenErsetzen Sie im Block
variablesobensensor.ma_voiture_batteriedurch die Akku-Entität Ihres Autos. Ändern Sie auchnomund, wenn Sie möchten, das Foto (image) und die Akkukapazität (capacite_kwh). - 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.
- 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.
| Marke | Sensor für „batterie“ |
|---|---|
| Xpeng | Der Akkustand-Sensor von XPENG Direct oder vom WiCAN. Für einen Xpeng ist die Xpeng-Karte vollständiger (Akkudiagnose über den WiCAN). |
| Renault · Dacia | Den Sensor Akku des Renault-Geräts (in %). |
| MG | Den Sensor SoC (Akkustand in %) des MQTT-Geräts des Autos. |
| Škoda | Den Sensor Akkustand (in %) des Škoda-Geräts. |
| Kia · Hyundai · Genesis | Den Sensor EV Battery Level (in %) des Kia-Uvo-Geräts. |
| Tesla | Den Sensor Akkustand (in %) des Auto-Geräts. |
| Peugeot · Citroën · DS · Opel | Den Sensor Battery (in %) des Stellantis-Vehicles-Geräts. |
| Volkswagen · Cupra · SEAT · Audi | Den Sensor Akkustand (in %) des VW-Group-Connect-Geräts. |
| BMW · Mini | Den Sensor Akkustand (in %), den BMW CarData für das Auto anlegt. |
| Ford | Den Sensor Akkustand (in %) des Ford-Geräts. |
| Nissan | Den Sensor Battery (in %) des NissanConnect-Geräts. |
| Volvo | Den Sensor Akku (in %) des Volvo-Geräts. |
| Polestar | Den Sensor Akkustand (in %) des Polestar-Geräts. |
| Mercedes-Benz | Den Sensor Akkustand (State of Charge, in %) des Mercedes-Geräts. |
| BYD | Den Sensor Akkustand (in %), den der WiCAN per MQTT veröffentlicht. |
Wie die Karte Ihre Daten findet
- Sie geht von der Entität
batterieaus 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
| Variable | Zweck |
|---|---|
| nom | Name oben auf der Karte. |
| image | Autofoto: '' für keines, oder /local/… (Ordner config/www von Home Assistant). |
| batterie | Die einzige Pflicht-Entität: der Akkustand in %. |
| capacite_kwh | Akkukapazität in kWh, um die bis zum Ziel fehlenden kWh zu schätzen. 0, um sie auszublenden. |
| pression_min, pression_max | Als normal geltender Reifendruckbereich in bar. |
| autonomie | Reichweiten-Entität erzwingen (km). |
| odometre | Kilometerstand-Entität erzwingen. |
| en_charge | Entität „lädt“ erzwingen (Ein/Aus-Sensor oder Ladezustand). |
| branche | Entität „angesteckt“ erzwingen. |
| puissance | Entität der Ladeleistung erzwingen (kW oder W). |
| limite | Entität des Ladelimits oder -ziels erzwingen (%). |
| temps_restant | Entität der verbleibenden Ladezeit erzwingen (Minuten, Stunden oder Sekunden). |
| verrou | Schloss-Entität erzwingen: eine lock.-Entität oder ein Sensor der Klasse „Schloss“. |
| position | Positions-Tracker erzwingen (device_tracker.), um „Zu Hause“ anzuzeigen. |
| temp_habitacle | Innenraum-Temperatursensor erzwingen. |
| pression_av_g, pression_av_d, pression_ar_g, pression_ar_d | Jeden Reifendrucksensor erzwingen (vorne links, vorne rechts, hinten links, hinten rechts). |
Gut zu wissen
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