<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol]]></title><description><![CDATA[<p dir="auto">Hallo. Hier ein neues Skript von mir. Ich verwende in meiner Visualisierung (VIS 1) an diversen Stellen den (Lade)zustand von Batterie/Akku. Bisher habe ich das mit Grafiken (png, svg) realisiert.</p>
<p dir="auto">Dieses Skript erzeugt dynamisch ein farbliches Symbol im SVG Format. Diese reicht von rot bis grün. Der Prozentsatz ist zentriert enthalten. Weiterhin können auch kräftiger Farben oder ein Ladesymbol (frei positionierbar) aktiviert werden. Statt % kann auch jede andere Bezeichnung für den Wert, oder ein komplett anderer Text genutzt werden.</p>
<p dir="auto">Funktioniert mit VIS 1, VIS 2. Sollte aber auch anderen Modulen laufen.<br />
<img src="/assets/uploads/files/1763754898847-bildschirmfoto_21-11-2025_205149_192.168.10.99.jpeg" alt="Bildschirmfoto_21-11-2025_205149_192.168.10.99.jpeg" class=" img-fluid img-markdown" /></p>
<p dir="auto"><s>Mit ein wenig Spielerei und Experimentierfreudigkeit kann man da auch andere Farben verwenden.</s></p>
<p dir="auto">Der Code generiert einen SVG Code der in einem Datenpunkt (<strong>Zeichen</strong>) gespeichert wird. Zur Darstellung wird in VIS 1 das <strong>String (unescaped)</strong> verwendet, das mit dem entsprechenden Datenpunkt verbunden ist.</p>
<p dir="auto">Der Code:</p>
<pre><code>//Ersteller: Ro75
//Datum: 22.11.2025
//Version: 1.0.19
//Javascript: 8.9.2
//NodeJS: 20.x / 22.x

// clamp: sorgt dafür, dass ein Wert nie kleiner als Minimum oder größer als Maximum wird. Nützlich für Prozentwerte.
function clamp(v, a, b) {
  return Math.max(a, Math.min(b, v));
}

// uid: erzeugt eine eindeutige ID, damit mehrere SVGs auf derselben Seite ohne Konflikte funktionieren.
function uid(prefix = 'id') {
  return `${prefix}-${Math.random().toString(36).slice(2, 9)}`;
}

// hslToRgb: wandelt HSL-Farben in RGB um, damit kann später die Helligkeit berechnent werden.
function hslToRgb(h, s, l) {
  s /= 100;
  l /= 100;
  const k = n =&gt; (n + h / 30) % 12;
  const a = s * Math.min(l, 1 - l);
  const f = n =&gt; l - a * Math.max(-1,
    Math.min(k(n) - 3, Math.min(9 - k(n), 1))
  );
  return [Math.round(255 * f(0)), Math.round(255 * f(8)), Math.round(255 * f(4))];
}

// luminance: berechnet die wahrgenommene Helligkeit einer Farbe. Wichtig für gut lesbaren Text.
function luminance(r, g, b) {
  const srgb = [r, g, b].map(c =&gt; {
    c /= 255;
    return (c &lt;= 0.04045) ? c / 12.92
      : Math.pow((c + 0.055) / 1.055, 2.4);
  });
  return 0.2126 * srgb[0] + 0.7152 * srgb[1] + 0.0722 * srgb[2];
}

// SAMPLE_POINTS: Tabelle für die Breite des Füllbalkens bei verschiedenen Prozentwerten für harmonische Übergänge.
const SAMPLE_POINTS = [
  { p: 0, w: 2 }, { p: 5, w: 10 }, { p: 10, w: 19 }, { p: 15, w: 29 },
  { p: 20, w: 38 }, { p: 25, w: 48 }, { p: 30, w: 58 }, { p: 35, w: 67 },
  { p: 40, w: 77 }, { p: 45, w: 86 }, { p: 50, w: 96 }, { p: 55, w: 106 },
  { p: 60, w: 115 }, { p: 65, w: 125 }, { p: 70, w: 134 }, { p: 75, w: 144 },
  { p: 80, w: 154 }, { p: 85, w: 163 }, { p: 90, w: 173 }, { p: 95, w: 182 },
  { p: 100, w: 192 }
];

// interpolatedWidth: berechnet die Breite des Füllbalkens aus SAMPLE_POINTS, auch Zwischenwerte.
function interpolatedWidth(percent) {
  const p = clamp(percent, 0, 100);

  for (const s of SAMPLE_POINTS) if (s.p === p) return s.w;

  let lower = SAMPLE_POINTS[0], upper = SAMPLE_POINTS[SAMPLE_POINTS.length - 1];

  for (let i = 0; i &lt; SAMPLE_POINTS.length - 1; i++) {
    const a = SAMPLE_POINTS[i], b = SAMPLE_POINTS[i + 1];
    if (p &gt; a.p &amp;&amp; p &lt; b.p) { lower = a; upper = b; break; }
    if (p === b.p) return b.w;
  }

  const t = (p - lower.p) / (upper.p - lower.p);
  return Math.round(lower.w + t * (upper.w - lower.w));
}

// getDynamicLetterSpacing: fügt bei runden Ziffern etwas mehr Abstand ein, damit der Text optisch sauber wirkt.
function getDynamicLetterSpacing(text) {
  const belly = ['0', '3', '6', '8', '9'];
  const t = String(text ?? "");
  const count = [...t].filter(c =&gt; belly.includes(c)).length;
  const spacing = count * 0.04;
  return spacing === 0 ? null : `${spacing}em`;
}

// getFillColor: berechnet die Füllfarbe je nach Farbschema und Ladestand.
function getFillColor(p, strongColors, colorScheme) {

  const raw = colorScheme ?? "default";
  const scheme = raw.toLowerCase();

  // Prüfe auf benutzerdefinierte Farben
  const isHex  = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(raw);
  const isRgb  = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i.test(raw);
  const isRgba = /^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*((0?\.?\d+)|1|0)\s*\)$/i.test(raw);

  // -----------------------------------------------------
  // BENUTZERDEFINIERTE FARBEN → RGB → HSL → dynamischer Verlauf
  // -----------------------------------------------------
  if (isHex || isRgb || isRgba) {

    let r, g, b;

    if (isHex) {
      let hex = raw.slice(1);
      if (hex.length === 3)
        hex = hex.split("").map(x =&gt; x + x).join("");
      r = parseInt(hex.slice(0, 2), 16);
      g = parseInt(hex.slice(2, 4), 16);
      b = parseInt(hex.slice(4, 6), 16);
    }
    else {
      // rgb(...) oder rgba(...)
      const nums = raw.match(/\d+\.?\d*/g).map(Number);
      [r, g, b] = nums;
    }

    // RGB → HSL
    const rf = r / 255, gf = g / 255, bf = b / 255;
    const max = Math.max(rf, gf, bf), min = Math.min(rf, gf, bf);
    const delta = max - min;

    let h = 0;

    if (delta !== 0) {
      if (max === rf)       h = 60 * (((gf - bf) / delta) % 6);
      else if (max === gf)  h = 60 * ((bf - rf) / delta + 2);
      else                  h = 60 * ((rf - gf) / delta + 4);
    }
    if (h &lt; 0) h += 360;

    const l = (max + min) / 2;
    const s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));

    const hue = Math.round(h);
    const saturation = Math.round(s * 100);
    const lightness = strongColors ? (20 + p * 0.25) : (35 + p * 0.3);

    return `hsl(${hue},${saturation}%,${lightness}%)`;
  }

  // -----------------------------------------------------
  // STANDARD-SCHEMEN
  // -----------------------------------------------------
  let hue, saturation, lightness;

  switch (scheme) {
    case 'green':  hue = 120; saturation = strongColors ? 100 : 80; lightness = strongColors ? 25 + p/4 : 35 + p*0.3; break;
    case 'yellow': hue =  50; saturation = strongColors ? 100 : 85; lightness = strongColors ? 25 + p*0.3 : 35 + p*0.3; break;
    case 'blue':   hue = 210; saturation = strongColors ? 100 : 75; lightness = strongColors ? 20 + p*0.25 : 35 + p*0.3; break;
    case 'red':    hue =   0; saturation = strongColors ? 100 : 75; lightness = strongColors ? 20 + p*0.25 : 35 + p*0.3; break;
    case 'orange': hue =  30; saturation = strongColors ? 100 : 80; lightness = strongColors ? 20 + p*0.25 : 35 + p*0.3; break;
    case 'brown':  hue =  25; saturation = strongColors ? 85 : 65; lightness = strongColors ? 20 + p*0.2 : 25 + p*0.25; break;
    case 'grey':   hue =   0; saturation = strongColors ? 15 :  0; lightness = strongColors ? 20 + p*0.4 : 25 + p*0.4; break;
    case 'purple': hue = 275; saturation = strongColors ? 95 : 75; lightness = strongColors ? 25 + p*0.25 : 35 + p*0.3; break;
    case 'black':  hue =   0; saturation = strongColors ? 10 :  0; lightness = strongColors ?  1 + p*0.27 :  3 + p*0.2; break;

    default:
      hue = Math.round(p * 1.2);
      saturation = strongColors ? 100 : 90;
      lightness = strongColors ? 35 : 50;
      break;
  }

  return `hsl(${hue},${saturation}%,${lightness}%)`;
}

// getBoltGradientFromScheme: bestimmt den Farbverlauf des Blitzsymbols je nach Schema.
function getBoltGradientFromScheme(strongColors, boltColorScheme) {
  const scheme = (
    boltColorScheme === 'default' ? 'default' : (boltColorScheme ?? 'default')
  ).toLowerCase();

  if (scheme === 'default') return ['#f7b23b', '#f59e0b'];

  let hue, saturation;
  switch (scheme) {
    case 'green': hue = 120; saturation = strongColors ? 100 : 80; break;
    case 'yellow': hue = 50; saturation = strongColors ? 100 : 85; break;
    case 'blue': hue = 210; saturation = strongColors ? 100 : 75; break;
    case 'red': hue = 0; saturation = strongColors ? 100 : 75; break;
    case 'orange': hue = 30; saturation = strongColors ? 100 : 80; break;
    case 'brown': hue = 25; saturation = strongColors ? 85 : 65; break;
    case 'grey': hue = 0; saturation = strongColors ? 15 : 0; break;
    case 'purple': hue = 275; saturation = strongColors ? 95 : 75; break;
    case 'black': hue = 0; saturation = strongColors ? 10 : 0; break;
    default: hue = 45; saturation = 100; break;
  }

  const lightLow = strongColors ? 25 : 40;
  const lightHigh = strongColors ? 65 : 70;

  return [`hsl(${hue},${saturation}%,${lightHigh}%)`, `hsl(${hue},${saturation}%,${lightLow}%)`];
}

// parseRightBackground: prüft, ob ein rechter Hintergrund gesetzt ist oder 'default' (dann keiner).
function parseRightBackground(value) {
  if (!value || value === "default") return null;
  return value;
}

// generateBatterySvg: Hauptfunktion, erzeugt das komplette Batterie-SVG inklusive Form, Text, Farben, Blitz und Effekten.
function generateBatterySvg(
  percent,
  decimalPlaces = 0,
  labelSuffix = '%',
  customLabel = null,
  showPercent = true,
  strongColors = false,
  colorScheme = 'default',
  showBolt = false,
  boltPos = 100,
  blinkBolt = false,
  boltColorScheme = 'default',
  rightBackground = 'default'
) {
  const raw = Number(percent);
  const p = clamp(Number.isFinite(raw) ? raw : 0, 0, 100);

  const viewBoxW = 264, viewBoxH = 129;
  const outer = { x: 20, y: 24, w: 200, h: 80, rx: 18 };
  const inner = { x: 24, y: 28, h: 72, rx: 12 };
  const maxInnerWidth = 192;

  const fillW = interpolatedWidth(p);
  const fillColor = getFillColor(p, strongColors, colorScheme);

  const rightCustom = parseRightBackground(rightBackground);
  const rightStartX = inner.x + fillW;
  const rightWidth = maxInnerWidth - fillW;

  const nums = (fillColor.match(/-?\d+(\.\d+)?/g) || []).map(Number);
  const [hVal = 0, sVal = 0, lVal = 50] = nums;
  const [r, g, b] = hslToRgb(hVal, sVal, lVal);
  const lum = luminance(r, g, b);
  const textFill = lum &gt; 0.55 ? '#000' : '#fff';
  const outlineColor = (textFill === '#fff') ? 'rgba(0,0,0,0.85)' : 'rgba(255,255,255,0.95)';

  const formattedValue = Number(p).toFixed(decimalPlaces);
  const formattedTrimmed = decimalPlaces === 0
    ? String(Math.round(Number(formattedValue)))
    : formattedValue;

  const displayText = customLabel ?? `${formattedTrimmed}${labelSuffix}`;

  const fontSize = Math.max(12, Math.round(inner.h * 0.33 * 2.25 * 1.10));
  const textCenterX = inner.x + maxInnerWidth / 2;
  const textCenterY = inner.y + inner.h / 2;
  const TEXT_DY_EM = 0.35;

  const contact = { x: 224, y: 46, w: 20, h: 36 };
  const contactCenterY = contact.y + contact.h / 2;

  const boltViewBox = { w: 102.7, h: 186.8 };
  const boltTargetH = outer.h * 1.7;
  const boltScale = boltTargetH / boltViewBox.h;
  const boltOffsetY = contactCenterY + 26;

  const clampedBoltPos = clamp(boltPos, 0, 100);

  const boltMinX = -37.0;
  const boltMaxX = 185.0;

  const boltX = boltMinX + (boltMaxX - boltMinX) * (clampedBoltPos / 100);

  const boltTransform = `
    translate(${boltX}, ${boltOffsetY})
    scale(${boltScale})
    translate(${-boltViewBox.w / 2}, ${-boltViewBox.h / 2})
  `.trim();

  const id = uid('b');

  const boltAnimation = blinkBolt ? `
    &lt;style&gt;
      @keyframes blinkBolt-${id} {
        0%, 100% { opacity: 1; }
        50% { opacity: 0.6; }
      }
      .blinking-bolt-${id} {
        animation: blinkBolt-${id} 1.8s ease-in-out infinite;
      }
    &lt;/style&gt;` : '';

  const boltClass = blinkBolt ? `blinking-bolt-${id}` : '';

  const [boltColorLight, boltColorDark] = getBoltGradientFromScheme(strongColors, boltColorScheme);

  const dynamicLetterSpacing = getDynamicLetterSpacing(displayText);
  const letterSpacingAttr = dynamicLetterSpacing ? `letter-spacing="${dynamicLetterSpacing}"` : '';

  return `
    &lt;svg xmlns="http://www.w3.org/2000/svg"
         viewBox="0 0 ${viewBoxW} ${viewBoxH}"
         width="100%" height="100%"
         preserveAspectRatio="xMidYMid meet"&gt;

      ${boltAnimation}

      &lt;defs&gt;
        &lt;linearGradient id="glass-${id}" x1="0" y1="0" x2="0" y2="1"&gt;
          &lt;stop offset="0%" stop-color="#ffffff" stop-opacity="0.80"/&gt;
          &lt;stop offset="100%" stop-color="#ffffff" stop-opacity="0.10"/&gt;
        &lt;/linearGradient&gt;

        &lt;linearGradient id="diagGlass-${id}" x1="0" y1="0" x2="1" y2="1"&gt;
          &lt;stop offset="0%" stop-color="#ffffff" stop-opacity="0.75"/&gt;
          &lt;stop offset="45%" stop-color="#ffffff" stop-opacity="0.22"/&gt;
          &lt;stop offset="100%" stop-color="#ffffff" stop-opacity="0.03"/&gt;
        &lt;/linearGradient&gt;

        &lt;pattern id="stripes-${id}" width="8" height="8" patternUnits="userSpaceOnUse"&gt;
          &lt;rect width="8" height="8" fill="transparent"/&gt;
          &lt;path d="M-1,6 l8,-6 M-1,10 l8,-6"
                stroke="#fff" stroke-opacity="0.08" stroke-width="1"/&gt;
        &lt;/pattern&gt;

        &lt;clipPath id="clip-fill-${id}"&gt;
          &lt;rect x="${inner.x}" y="${inner.y}" width="${maxInnerWidth}"
                height="${inner.h}" rx="${inner.rx}" ry="${inner.rx}" /&gt;
        &lt;/clipPath&gt;

        &lt;linearGradient id="boltGradient-${id}" x1="0" y1="0" x2="0" y2="1"&gt;
          &lt;stop offset="0%" stop-color="${boltColorLight}"/&gt;
          &lt;stop offset="100%" stop-color="${boltColorDark}"/&gt;
        &lt;/linearGradient&gt;

        &lt;symbol id="boltSymbol-${id}" viewBox="0 0 102.7 186.8"&gt;
          &lt;path fill="url(#boltGradient-${id})" stroke="#000" stroke-width="6" stroke-linejoin="round"
                d="m34.8 2-32 96h32l-16 80 80-112h-48l32-64h-48z"/&gt;
        &lt;/symbol&gt;
      &lt;/defs&gt;

      &lt;!-- ÄUSSERER RAHMEN --&gt;
      &lt;rect x="${outer.x}" y="${outer.y}" width="${outer.w}" height="${outer.h}"
            rx="${outer.rx}" fill="#222" stroke="#ddd" stroke-width="4"/&gt;

      &lt;!-- FÜLLBEREICH LINKS (universeller Fix) --&gt;
      ${
        fillW &gt;= maxInnerWidth
          ? `&lt;rect x="${inner.x}" y="${inner.y}" width="${maxInnerWidth}" height="${inner.h}"
                   rx="${inner.rx}" ry="${inner.rx}" fill="${fillColor}"/&gt;`
          : (
              fillW &gt; 0
                ? `&lt;path d="
                     M ${inner.x + inner.rx} ${inner.y}
                     L ${inner.x + fillW} ${inner.y}
                     L ${inner.x + fillW} ${inner.y + inner.h}
                     L ${inner.x + inner.rx} ${inner.y + inner.h}
                     A ${inner.rx} ${inner.rx} 0 0 1 ${inner.x} ${inner.y + inner.h - inner.rx}
                     L ${inner.x} ${inner.y + inner.rx}
                     A ${inner.rx} ${inner.rx} 0 0 1 ${inner.x + inner.rx} ${inner.y}
                     Z"
                   fill="${fillColor}"/&gt;`
                : ""
            )
      }

      &lt;!-- RECHTE HINTERGRUNDHÄLFTE --&gt;
      ${
        rightBackground === "default" || fillW &gt;= maxInnerWidth
          ? ""
          : (rightWidth &gt; 0
              ? `&lt;path d="M ${rightStartX} ${inner.y}
                         L ${rightStartX + rightWidth - inner.rx} ${inner.y}
                         A ${inner.rx} ${inner.rx} 0 0 1 ${rightStartX + rightWidth} ${inner.y + inner.rx}
                         L ${rightStartX + rightWidth} ${inner.y + inner.h - inner.rx}
                         A ${inner.rx} ${inner.rx} 0 0 1 ${rightStartX + rightWidth - inner.rx} ${inner.y + inner.h}
                         L ${rightStartX} ${inner.y + inner.h}
                         Z"
                     fill="${rightCustom}"/&gt;`
              : "")
      }

      &lt;!-- GLAS UND TEXTURIERUNG --&gt;
      &lt;g clip-path="url(#clip-fill-${id})"&gt;
        &lt;rect x="${inner.x}" y="${inner.y}" width="${fillW}" height="${inner.h}"
              fill="url(#stripes-${id})" opacity="0.95"/&gt;
        &lt;rect x="${inner.x}" y="${inner.y}" width="${fillW}" height="${inner.h}"
              fill="url(#glass-${id})" opacity="0.25"/&gt;
      &lt;/g&gt;

      &lt;!-- DIAGONALER GLASEFFEKT --&gt;
      &lt;rect x="${outer.x}" y="${outer.y}" width="${outer.w}" height="${outer.h}"
            rx="${outer.rx}" fill="url(#diagGlass-${id})" opacity="0.9"/&gt;

      &lt;!-- KONTAKT --&gt;
      &lt;rect x="224" y="46" width="20" height="36" rx="6" fill="#ccc" stroke="#888" stroke-width="2"/&gt;

      &lt;!-- BLITZ --&gt;
      ${showBolt ? `&lt;use href="#boltSymbol-${id}" class="${boltClass}" transform="${boltTransform}"/&gt;` : ""}

      &lt;!-- TEXT --&gt;
      ${
        showPercent
          ? `&lt;g transform="translate(${textCenterX}, ${textCenterY})"&gt;
              &lt;text text-anchor="middle"
                    font-family="Arial, Helvetica, sans-serif"
                    font-size="${fontSize}"
                    font-weight="700"
                    fill="${textFill}"
                    stroke="${outlineColor}"
                    stroke-width="${Math.max(2, Math.round(fontSize * 0.15))}"
                    paint-order="stroke"
                    dy="${TEXT_DY_EM}em"
                    ${letterSpacingAttr}&gt;
                ${displayText}
              &lt;/text&gt;
            &lt;/g&gt;`
          : ""
      }

    &lt;/svg&gt;
  `.trim();
}
</code></pre>
<p dir="auto"><strong>DOKUMENTATION: Prarameterübersicht</strong></p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Parameter</th>
<th>Typ</th>
<th>Standardwert</th>
<th>Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>percent</strong></td>
<td><code>number</code></td>
<td><em>erforderlich</em></td>
<td>Ladezustand der Batterie (<code>0–100</code>). Werte außerhalb werden automatisch begrenzt.</td>
</tr>
<tr>
<td><strong>decimalPlaces</strong></td>
<td><code>number</code></td>
<td><code>0</code></td>
<td>Anzahl der Nachkommastellen für die Wertanzeige.</td>
</tr>
<tr>
<td><strong>labelSuffix</strong></td>
<td><code>string</code></td>
<td><code>'%'</code></td>
<td>Text, der hinter dem Prozentwert angezeigt wird (<code>%</code>, <code>V</code>, <code>mAh</code>, …).</td>
</tr>
<tr>
<td><strong>customLabel</strong></td>
<td><code>string \| null</code></td>
<td><code>null</code></td>
<td>Überschreibt die Textanzeige vollständig (z. B. <code>"FULL"</code>). Ignoriert <code>decimalPlaces</code> und <code>labelSuffix</code>.</td>
</tr>
<tr>
<td><strong>showPercent</strong></td>
<td><code>boolean</code></td>
<td><code>true</code></td>
<td>Steuert, ob Text in der Batterie angezeigt wird.</td>
</tr>
<tr>
<td><strong>strongColors</strong></td>
<td><code>boolean</code></td>
<td><code>false</code></td>
<td>Aktiviert kräftigere Farben und stärkere Kontraste (für Füllfarbe und Blitz).</td>
</tr>
<tr>
<td><strong>colorScheme</strong></td>
<td><code>string</code></td>
<td><code>'default'</code></td>
<td>Farbschema für den <em>gefüllten linken Bereich</em>. Unterstützte Werte siehe unten.</td>
</tr>
<tr>
<td><strong>showBolt</strong></td>
<td><code>boolean</code></td>
<td><code>false</code></td>
<td>Zeigt ein ⚡-Blitzsymbol an.</td>
</tr>
<tr>
<td><strong>boltPos</strong></td>
<td><code>number</code></td>
<td><code>100</code></td>
<td>Horizontale Position des Blitzes (<code>0 = links</code>, <code>100 = rechts</code>).</td>
</tr>
<tr>
<td><strong>blinkBolt</strong></td>
<td><code>boolean</code></td>
<td><code>false</code></td>
<td>Aktiviert einen regelmäßigen „Atmen“-Blinkeffekt des Blitzsymbols.</td>
</tr>
<tr>
<td><strong>boltColorScheme</strong></td>
<td><code>string</code></td>
<td><code>'default'</code></td>
<td>Farbschema des Blitz-Symbols (siehe Liste unten).</td>
</tr>
<tr>
<td><strong>rightBackground</strong></td>
<td><code>string</code></td>
<td><code>'default'</code></td>
<td>Hintergrund des <em>rechten, leeren Bereichs</em>. Unterstützt: <code>'default'</code>, <code>HEX</code>, <code>RGB</code>, <code>RGBA</code>.</td>
</tr>
</tbody>
</table>
<p dir="auto"><strong>DOKUMENTATION: Unterstützte Farbschemata (colorScheme)</strong></p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Name</th>
<th>Beschreibung</th>
<th>Verlauf / Charakteristik</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>'default'</code></td>
<td>Standardverlauf: grün → gelb → rot</td>
<td>Dynamisch abhängig vom Prozentwert</td>
</tr>
<tr>
<td><code>'green'</code></td>
<td>Grüntöne</td>
<td>Dunkelgrün → Hellgrün</td>
</tr>
<tr>
<td><code>'yellow'</code></td>
<td>Gelbtöne</td>
<td>Ocker → Hellgelb</td>
</tr>
<tr>
<td><code>'blue'</code></td>
<td>Blautöne</td>
<td>Marineblau → Hellblau</td>
</tr>
<tr>
<td><code>'red'</code></td>
<td>Rottöne</td>
<td>Dunkelrot → Hellrot</td>
</tr>
<tr>
<td><code>'orange'</code></td>
<td>Orangetöne</td>
<td>Dunkelorange → Hellorange</td>
</tr>
<tr>
<td><code>'brown'</code></td>
<td>Brauntöne</td>
<td>Dunkelbraun → Mittelbraun</td>
</tr>
<tr>
<td><code>'grey'</code></td>
<td>Grautöne</td>
<td>Mittelgrau → Hellgrau</td>
</tr>
<tr>
<td><code>'purple'</code></td>
<td>Violett / Purpur</td>
<td>Dunkles Lila → helleres Violett</td>
</tr>
<tr>
<td><code>'black'</code></td>
<td>Schwarzschema</td>
<td>Tiefschwarz → Dunkelgrau</td>
</tr>
<tr>
<td><strong>HEX</strong></td>
<td>z. B. <code>#00ff88</code></td>
<td>Wird automatisch in <strong>dynamischen HSL-Verlauf</strong> umgerechnet</td>
</tr>
<tr>
<td><strong>RGB</strong></td>
<td>z. B. <code>rgb(0,128,128)</code></td>
<td>ebenfalls → dynamischer HSL-Verlauf</td>
</tr>
<tr>
<td><strong>RGBA</strong></td>
<td>z. B. <code>rgba(0,128,128,0.5)</code></td>
<td>ebenfalls → dynamischer HSL-Verlauf</td>
</tr>
</tbody>
</table>
<p dir="auto">Dynamik bei Custom-Farben</p>
<p dir="auto">HEX/RGB/RGBA werden intern in HSL umgerechnet und dann mit einem Verlauf versehen (abhängig vom Ladezustand und strongColors).<br />
→ Dadurch funktionieren auch Custom-Farben dynamisch.</p>
<p dir="auto"><strong>DOKUMENTATION: Unterstützte Farbschemata (boltColorScheme)</strong></p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Name</th>
<th>Beschreibung / Verlauf</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>'default'</code></td>
<td>Orange → Gelb</td>
</tr>
<tr>
<td><code>'green'</code></td>
<td>Dunkelgrün → Hellgrün</td>
</tr>
<tr>
<td><code>'yellow'</code></td>
<td>Ocker → Hellgelb</td>
</tr>
<tr>
<td><code>'blue'</code></td>
<td>Marineblau → Hellblau</td>
</tr>
<tr>
<td><code>'red'</code></td>
<td>Dunkelrot → Hellrot</td>
</tr>
<tr>
<td><code>'orange'</code></td>
<td>Dunkelorange → Hellorange</td>
</tr>
<tr>
<td><code>'brown'</code></td>
<td>Dunkelbraun → Mittelbraun</td>
</tr>
<tr>
<td><code>'grey'</code></td>
<td>Mittelgrau → Hellgrau</td>
</tr>
<tr>
<td><code>'purple'</code></td>
<td>Dunkles Lila → helleres Violett</td>
</tr>
<tr>
<td><code>'black'</code></td>
<td>Tiefschwarz → Dunkelgrau</td>
</tr>
</tbody>
</table>
<p dir="auto"><strong>DOKUMENTATION: Unterstützte Werte für den rechten Hintergrund (rightBackground)</strong></p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Wert</th>
<th>Beispiel</th>
<th>Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>'default'</code></td>
<td>–</td>
<td>Spezieller Glasschimmer-Effekt</td>
</tr>
<tr>
<td><strong>HEX</strong></td>
<td><code>#ffffff</code></td>
<td>Fester Farbwert</td>
</tr>
<tr>
<td><strong>RGB</strong></td>
<td><code>rgb(0,128,128)</code></td>
<td>Fester Farbwert</td>
</tr>
<tr>
<td><strong>RGBA</strong></td>
<td><code>rgba(0,128,128,0.4)</code></td>
<td>Transparente Farben möglich</td>
</tr>
</tbody>
</table>
<p dir="auto">Hinweise zur Farbdarstellung<br />
Bei strongColors = true:</p>
<ul>
<li>stärkere Sättigung</li>
<li>dunklerer Startpunkt</li>
<li>mehr Kontrast</li>
<li>grellerer Blitzverlauf</li>
</ul>
<p dir="auto">Bei strongColors = false:</p>
<ul>
<li>weicherer, neutraler Verlauf</li>
<li>dezenter Blitz</li>
</ul>
<p dir="auto"><strong>DOKUMENTATION: Blitzsymbol (showBolt, boltPos, blinkBolt)</strong></p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Option</th>
<th>Wirkung</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>showBolt: true</code></td>
<td>Zeigt das ⚡-Symbol.</td>
</tr>
<tr>
<td><code>boltPos</code></td>
<td>Position des Blitzsymbols auf der horizontalen Achse der Batterie (<code>0–100</code>).</td>
</tr>
<tr>
<td><code>blinkBolt: true</code></td>
<td>Aktiviert weiches Pulsieren (Opacity 1 → 0.6 → 1).</td>
</tr>
</tbody>
</table>
<p dir="auto">BEISPIEL mit Speicherung des SVG Code in einen Datenpunkt</p>
<pre><code>const ZielDP = '0_userdata.0.Batterie1'; // bitte anpassen

const dValue = getState('fritzdect.0.DECT_099950330172.battery').val; // bitte anpassen
const decimalPlaces = 0; // bitte anpassen
const labelSuffix = '%'; // bitte anpassen
const customLabel = null; // bitte anpassen
const showPercent = true; // bitte anpassen, z.B. Datenpunkt für zentrale Festlegung
const strongColors = true; // bitte anpassen, z.B. Datenpunkt für zentrale Festlegung
const colorScheme = 'default'; // bitte anpassen, z.B. Datenpunkt für zentrale Festlegung
const showBolt = false; // bitte anpassen, z.B. Datenpunkt für zentrale Festlegung
const boltPos = 100; // bitte anpassen, z.B. Datenpunkt für zentrale Festlegung
const blinkBolt = false; // bitte anpassen
const boltColorScheme = 'default'; // bitte anpassen, z.B. Datenpunkt für zentrale Festlegung
const rightBackground = 'default'; // bitte anpassen, z.B. Datenpunkt für zentrale Festlegung

//Funktionsaufruf mit Speicherung der SVG in einen Datenpunkt
setState(ZielDP, generateBatterySvg(dValue, decimalPlaces, labelSuffix, customLabel, showPercent, strongColors, colorScheme, showBolt, boltPos, blinkBolt, boltColorScheme, rightBackground), true);
</code></pre>
<p dir="auto">Viel Spaß beim testen und benutzen.</p>
<p dir="auto">Ro75.</p>
<p dir="auto">1.0.1: Korrekturen<br />
1.0.3: wahlweise kräftiger Farben und Ladesymbol<br />
1.0.5: Ladesymbol frei beweglich, freier Suffix (% oder z.B. V) oder komplett freier Text, Wert mit X Kommastellen<br />
1.0.6: Sortierung der Parameter, Ladesymbol kann auf Wunsch sanft blinken, Dokumentation und Beispiel angepasst<br />
1.0.8: Korrektur vom erstellten SVG-Code. Dieser kann nun in Dateien verwendet werden - ohne Fehler. Weiterer Parameter zur Steuerung des Farbschemas vom Ladesymbol.<br />
1.0.17: weitere Korrekturen und weiterer Parameter <strong>rightBackground</strong> . Damit kann das gesamte SVG frei definiert werden.<br />
1.0.19: Der Paramter <strong>colorScheme</strong> akzeptiert jetzt nicht nur 'default' und ein Farbschema aus der Liste. Jetzt kann jeder beliebige HEX, RGB oder RGBA Wert Verwendung finden.</p>
<p dir="auto"></p><section class="spoiler-wrapper"><button class="spoiler-control btn btn-default">Archiv - Version 1.0.8</button><section style="display:none" class="spoiler-content"><p></p>
<pre><code>//Ersteller: Ro75
//Datum: 13.11.2025
//Version: 1.0.8
//Javascript: 8.9.2
//NodeJS: 20.x / 22.x

// dynamische Betterie-Icon Generierung

// -------------------------------------------------------
// Hilfsfunktionen
// -------------------------------------------------------
function clamp(v, a, b) { return Math.max(a, Math.min(b, v)); }
function uid(prefix = 'id') { return `${prefix}-${Math.random().toString(36).slice(2,9)}`; }

function hslToRgb(h, s, l) {
 s /= 100; l /= 100;
 const k = n =&gt; (n + h / 30) % 12;
 const a = s * Math.min(l, 1 - l);
 const f = n =&gt; l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
 return [Math.round(255 * f(0)), Math.round(255 * f(8)), Math.round(255 * f(4))];
}

function luminance(r, g, b) {
 const srgb = [r, g, b].map(c =&gt; {
   c /= 255;
   return (c &lt;= 0.04045) ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
 });
 return 0.2126 * srgb[0] + 0.7152 * srgb[1] + 0.0722 * srgb[2];
}

const SAMPLE_POINTS = [
 { p: 0, w: 2 }, { p: 5, w: 10 }, { p: 10, w: 19 }, { p: 15, w: 29 }, { p: 20, w: 38 },
 { p: 25, w: 48 }, { p: 30, w: 58 }, { p: 35, w: 67 }, { p: 40, w: 77 }, { p: 45, w: 86 },
 { p: 50, w: 96 }, { p: 55, w: 106 }, { p: 60, w: 115 }, { p: 65, w: 125 }, { p: 70, w: 134 },
 { p: 75, w: 144 }, { p: 80, w: 154 }, { p: 85, w: 163 }, { p: 90, w: 173 }, { p: 95, w: 182 },
 { p: 100, w: 192 }
];

function interpolatedWidth(percent) {
 const p = clamp(percent, 0, 100);
 for (const s of SAMPLE_POINTS) if (s.p === p) return s.w;
 let lower = SAMPLE_POINTS[0], upper = SAMPLE_POINTS[SAMPLE_POINTS.length - 1];
 for (let i = 0; i &lt; SAMPLE_POINTS.length - 1; i++) {
   const a = SAMPLE_POINTS[i], b = SAMPLE_POINTS[i + 1];
   if (p &gt; a.p &amp;&amp; p &lt; b.p) { lower = a; upper = b; break; }
   if (p === b.p) return b.w;
 }
 const t = (p - lower.p) / (upper.p - lower.p);
 return Math.round(lower.w + t * (upper.w - lower.w));
}

// -------------------------------------------------------
// Farb-Schemata mit strongColors-Effekt
// -------------------------------------------------------
function getFillColor(p, strongColors, colorScheme) {
 let hue;
 let saturation;
 let lightness;

 switch ((colorScheme || 'default').toLowerCase()) {
   case 'green':
     hue = 120;
     saturation = strongColors ? 100 : 80;
     lightness = strongColors ? 25 + (p / 100) * 25 : 35 + (p / 100) * 30;
     break;
   case 'yellow':
     hue = 50;
     saturation = strongColors ? 100 : 85;
     lightness = strongColors ? 25 + (p / 100) * 30 : 35 + (p / 100) * 30;
     break;
   case 'blue':
     hue = 210;
     saturation = strongColors ? 100 : 75;
     lightness = strongColors ? 20 + (p / 100) * 25 : 35 + (p / 100) * 30;
     break;
   case 'red':
     hue = 0;
     saturation = strongColors ? 100 : 75;
     lightness = strongColors ? 20 + (p / 100) * 25 : 35 + (p / 100) * 30;
     break;
   case 'orange':
     hue = 30;
     saturation = strongColors ? 100 : 80;
     lightness = strongColors ? 20 + (p / 100) * 25 : 35 + (p / 100) * 30;
     break;
   case 'brown':
     hue = 25;
     saturation = strongColors ? 85 : 65;
     lightness = strongColors ? 20 + (p / 100) * 20 : 25 + (p / 100) * 25;
     break;
   case 'grey':
     hue = 0;
     saturation = strongColors ? 15 : 0;
     lightness = strongColors ? 20 + (p / 100) * 40 : 25 + (p / 100) * 40;
     break;
   case 'purple':
     hue = 275;
     saturation = strongColors ? 95 : 75;
     lightness = strongColors ? 25 + (p / 100) * 25 : 35 + (p / 100) * 30;
     break;
   case 'black':
     hue = 0;
     saturation = strongColors ? 10 : 0;
     lightness = strongColors ? 1 + (p / 100) * 27 : 3 + (p / 100) * 20;
     break;
   default: // Standard: grün → rot Verlauf
     hue = Math.round((p / 100) * 120);
     saturation = strongColors ? 100 : 90;
     lightness = strongColors ? 35 : 50;
     break;
 }

 return `hsl(${hue},${saturation}%,${lightness}%)`;
}

// -------------------------------------------------------
// Blitz-Farbverlauf passend zum Farbschema
// -------------------------------------------------------
function getBoltGradientFromScheme(strongColors, boltColorScheme) {
 const scheme = (boltColorScheme || 'default').toLowerCase();

 if (scheme === 'default') {
   return ['#f7b23b', '#f59e0b'];
 }

 let hue, saturation;
 switch (scheme) {
   case 'green': hue = 120; saturation = strongColors ? 100 : 80; break;
   case 'yellow': hue = 50; saturation = strongColors ? 100 : 85; break;
   case 'blue': hue = 210; saturation = strongColors ? 100 : 75; break;
   case 'red': hue = 0; saturation = strongColors ? 100 : 75; break;
   case 'orange': hue = 30; saturation = strongColors ? 100 : 80; break;
   case 'brown': hue = 25; saturation = strongColors ? 85 : 65; break;
   case 'grey': hue = 0; saturation = strongColors ? 15 : 0; break;
   case 'purple': hue = 275; saturation = strongColors ? 95 : 75; break;
   case 'black': hue = 0; saturation = strongColors ? 10 : 0; break;
   default: hue = 45; saturation = 100; break;
 }

 const lightLow = strongColors ? 25 : 40;
 const lightHigh = strongColors ? 65 : 70;

 return [
   `hsl(${hue},${saturation}%,${lightLow}%)`,
   `hsl(${hue},${saturation}%,${lightHigh}%)`
 ];
}

// -------------------------------------------------------
// Hauptfunktion
// -------------------------------------------------------
function generateBatterySvg(percent, decimalPlaces = 0, labelSuffix = '%', customLabel = null, showPercent = true, strongColors = false, colorScheme = 'default', showBolt = false, boltPos = 100, blinkBolt = false, boltColorScheme = 'default')
{
 const raw = Number(percent);
 const p = clamp(Number.isFinite(raw) ? raw : 0, 0, 100);

 const viewBoxW = 264, viewBoxH = 129;
 const outer = { x: 20, y: 24, w: 200, h: 80, rx: 18 };
 const inner = { x: 24, y: 28, h: 72, rx: 12 };
 const maxInnerWidth = 192;

 const fillW = interpolatedWidth(p);
 const fillColor = getFillColor(p, strongColors, colorScheme);

 const nums = (fillColor.match(/-?\d+(\.\d+)?/g) || []).map(Number);
 const [hVal = 0, sVal = 0, lVal = 50] = nums;
 const [r, g, b] = hslToRgb(hVal, sVal, lVal);
 const lum = luminance(r, g, b);
 const textFill = lum &gt; 0.55 ? '#000' : '#fff';
 const outlineColor = (textFill === '#fff') ? 'rgba(0,0,0,0.85)' : 'rgba(255,255,255,0.95)';

 const formattedValue = Number(p).toFixed(decimalPlaces);
 const formattedTrimmed = (decimalPlaces === 0) ? String(Math.round(Number(formattedValue))) : formattedValue;
 const displayText = customLabel ?? `${formattedTrimmed}${labelSuffix}`;

 const fontSize = Math.max(12, Math.round(inner.h * 0.33 * 2.25));
 const textCenterX = inner.x + maxInnerWidth / 2;
 const textCenterY = inner.y + inner.h / 2;
 const TEXT_DY_EM = 0.35;

 const contact = { x: 224, y: 46, w: 20, h: 36 };
 const contactCenterX = contact.x + contact.w / 2;
 const contactCenterY = contact.y + contact.h / 2;

 const boltViewBox = { w: 102.7, h: 186.8 };
 const boltTargetH = outer.h * 1.7;
 const boltScale = boltTargetH / boltViewBox.h;
 const boltOffsetY = contactCenterY + 26;

 const clampedBoltPos = clamp(boltPos, 0, 100);
 const leftEdge = 0;
 const rightEdge = contactCenterX - 50;
 const baseX = leftEdge + ((rightEdge - leftEdge) * (clampedBoltPos / 100));
 let extraOffset = -30;
 if (clampedBoltPos === 0) extraOffset = -15;
 else if (clampedBoltPos === 100) extraOffset = 0;
 const boltX = baseX + extraOffset;

 const boltTransform = `
   translate(${boltX}, ${boltOffsetY})
   scale(${boltScale})
   translate(${-boltViewBox.w / 2}, ${-boltViewBox.h / 2})
 `.trim();

 const id = uid('b');
 const boltAnimation = blinkBolt ? `
   &lt;style&gt;
     @keyframes blinkBolt-${id} {
       0%, 100% { opacity: 1; }
       50% { opacity: 0.6; }
     }
     .blinking-bolt-${id} {
       animation: blinkBolt-${id} 1.8s ease-in-out infinite;
     }
   &lt;/style&gt;` : '';
 const boltClass = blinkBolt ? `blinking-bolt-${id}` : '';

 const [boltColorDark, boltColorLight] = getBoltGradientFromScheme(strongColors, boltColorScheme || colorScheme);

 return `
   &lt;svg xmlns="http://www.w3.org/2000/svg"
        xmlns:xlink="http://www.w3.org/1999/xlink"
        viewBox="0 0 ${viewBoxW} ${viewBoxH}"
        width="100%" height="100%"
        preserveAspectRatio="xMidYMid meet"&gt;
     ${boltAnimation}
     &lt;defs&gt;
       &lt;linearGradient id="glass-${id}" x1="0" y1="0" x2="0" y2="1"&gt;
         &lt;stop offset="0%" stop-color="#ffffff" stop-opacity="0.80"/&gt;
         &lt;stop offset="100%" stop-color="#ffffff" stop-opacity="0.10"/&gt;
       &lt;/linearGradient&gt;
       &lt;linearGradient id="diagGlass-${id}" x1="0" y1="0" x2="1" y2="1"&gt;
         &lt;stop offset="0%" stop-color="#ffffff" stop-opacity="0.75"/&gt;
         &lt;stop offset="45%" stop-color="#ffffff" stop-opacity="0.22"/&gt;
         &lt;stop offset="100%" stop-color="#ffffff" stop-opacity="0.03"/&gt;
       &lt;/linearGradient&gt;
       &lt;pattern id="stripes-${id}" width="8" height="8" patternUnits="userSpaceOnUse"&gt;
         &lt;rect width="8" height="8" fill="transparent"/&gt;
         &lt;path d="M-1,6 l8,-6 M-1,10 l8,-6" stroke="#fff" stroke-opacity="0.08" stroke-width="1"/&gt;
       &lt;/pattern&gt;
       &lt;clipPath id="clip-fill-${id}"&gt;
         &lt;rect x="${inner.x}" y="${inner.y}" width="${fillW}" height="${inner.h}" rx="${inner.rx}" ry="${inner.rx}"/&gt;
       &lt;/clipPath&gt;
       &lt;linearGradient id="boltGradient-${id}" x1="8.7" x2="80.9" y1="17.1" y2="142.1" gradientUnits="userSpaceOnUse"&gt;
         &lt;stop offset="0" stop-color="${boltColorLight}"/&gt;
         &lt;stop offset="1" stop-color="${boltColorDark}"/&gt;
       &lt;/linearGradient&gt;
       &lt;symbol id="boltSymbol-${id}" viewBox="0 0 102.7 186.8"&gt;
         &lt;path fill="url(#boltGradient-${id})" stroke="#000" stroke-width="6" stroke-linejoin="round"
               d="m34.8 2-32 96h32l-16 80 80-112h-48l32-64h-48z"/&gt;
       &lt;/symbol&gt;
     &lt;/defs&gt;
     &lt;rect x="${outer.x}" y="${outer.y}" width="${outer.w}" height="${outer.h}" rx="${outer.rx}"
           fill="#222" stroke="#ddd" stroke-width="4"/&gt;
     &lt;rect x="${inner.x}" y="${inner.y}" width="${fillW}" height="${inner.h}"
           rx="${inner.rx}" ry="${inner.rx}" fill="${fillColor}"/&gt;
     &lt;g clip-path="url(#clip-fill-${id})"&gt;
       &lt;rect x="${inner.x}" y="${inner.y}" width="${fillW}" height="${inner.h}"
             rx="${inner.rx}" fill="url(#stripes-${id})" opacity="0.95"/&gt;
       &lt;rect x="${inner.x}" y="${inner.y}" width="${fillW}" height="${inner.h}"
             rx="${inner.rx}" fill="url(#glass-${id})" opacity="0.25"/&gt;
     &lt;/g&gt;
     &lt;rect x="${outer.x}" y="${outer.y}" width="${outer.w}" height="${outer.h}"
           rx="${outer.rx}" fill="url(#diagGlass-${id})" opacity="0.9"/&gt;
     &lt;rect x="224" y="46" width="20" height="36" rx="6" fill="#ccc" stroke="#888" stroke-width="2"/&gt;
     ${showBolt ? `&lt;use href="#boltSymbol-${id}" class="${boltClass}" transform="${boltTransform}"/&gt;` : ''}
     ${showPercent ? `
       &lt;g transform="translate(${textCenterX}, ${textCenterY})"&gt;
         &lt;text text-anchor="middle"
               font-family="Arial, Helvetica, sans-serif"
               font-size="${fontSize}" font-weight="700"
               fill="${textFill}" stroke="${outlineColor}"
               stroke-width="${Math.max(2, Math.round(fontSize * 0.15))}"
               paint-order="stroke" dy="${TEXT_DY_EM}em"&gt;${displayText}&lt;/text&gt;
       &lt;/g&gt;` : ''}
   &lt;/svg&gt;`.trim();
}
</code></pre>
<p dir="auto"></p></section></section><p></p>
<p dir="auto"></p><section class="spoiler-wrapper"><button class="spoiler-control btn btn-default">Archiv - Version 1.0.17</button><section style="display:none" class="spoiler-content"><p></p>
<pre><code>//Ersteller: Ro75
//Datum: 21.11.2025
//Version: 1.0.17
//Javascript: 8.9.2
//NodeJS: 20.x / 22.x

// Version 1.0.17
// Dynamische Batterie-Icon Generierung
// ------------------------------------------------------

// clamp: sorgt dafür, dass ein Wert nie kleiner als Minimum oder größer als Maximum wird. Nützlich für Prozentwerte.
function clamp(v, a, b) {
 return Math.max(a, Math.min(b, v));
}

// uid: erzeugt eine eindeutige ID, damit mehrere SVGs auf derselben Seite ohne Konflikte funktionieren.
function uid(prefix = 'id') {
 return `${prefix}-${Math.random().toString(36).slice(2, 9)}`;
}

// hslToRgb: wandelt HSL-Farben in RGB um, damit kann später die Helligkeit berechnent werden.
function hslToRgb(h, s, l) {
 s /= 100;
 l /= 100;
 const k = n =&gt; (n + h / 30) % 12;
 const a = s * Math.min(l, 1 - l);
 const f = n =&gt; l - a * Math.max(-1,
   Math.min(k(n) - 3, Math.min(9 - k(n), 1))
 );
 return [Math.round(255 * f(0)), Math.round(255 * f(8)), Math.round(255 * f(4))];
}

// luminance: berechnet die wahrgenommene Helligkeit einer Farbe. Wichtig für gut lesbaren Text.
function luminance(r, g, b) {
 const srgb = [r, g, b].map(c =&gt; {
   c /= 255;
   return (c &lt;= 0.04045) ? c / 12.92
     : Math.pow((c + 0.055) / 1.055, 2.4);
 });
 return 0.2126 * srgb[0] + 0.7152 * srgb[1] + 0.0722 * srgb[2];
}

// SAMPLE_POINTS: Tabelle für die Breite des Füllbalkens bei verschiedenen Prozentwerten für harmonische Übergänge.
const SAMPLE_POINTS = [
 { p: 0, w: 2 }, { p: 5, w: 10 }, { p: 10, w: 19 }, { p: 15, w: 29 },
 { p: 20, w: 38 }, { p: 25, w: 48 }, { p: 30, w: 58 }, { p: 35, w: 67 },
 { p: 40, w: 77 }, { p: 45, w: 86 }, { p: 50, w: 96 }, { p: 55, w: 106 },
 { p: 60, w: 115 }, { p: 65, w: 125 }, { p: 70, w: 134 }, { p: 75, w: 144 },
 { p: 80, w: 154 }, { p: 85, w: 163 }, { p: 90, w: 173 }, { p: 95, w: 182 },
 { p: 100, w: 192 }
];

// interpolatedWidth: berechnet die Breite des Füllbalkens aus SAMPLE_POINTS, auch Zwischenwerte.
function interpolatedWidth(percent) {
 const p = clamp(percent, 0, 100);

 for (const s of SAMPLE_POINTS) if (s.p === p) return s.w;

 let lower = SAMPLE_POINTS[0], upper = SAMPLE_POINTS[SAMPLE_POINTS.length - 1];

 for (let i = 0; i &lt; SAMPLE_POINTS.length - 1; i++) {
   const a = SAMPLE_POINTS[i], b = SAMPLE_POINTS[i + 1];
   if (p &gt; a.p &amp;&amp; p &lt; b.p) { lower = a; upper = b; break; }
   if (p === b.p) return b.w;
 }

 const t = (p - lower.p) / (upper.p - lower.p);
 return Math.round(lower.w + t * (upper.w - lower.w));
}

// getDynamicLetterSpacing: fügt bei runden Ziffern etwas mehr Abstand ein, damit der Text optisch sauber wirkt.
function getDynamicLetterSpacing(text) {
 const belly = ['0', '3', '6', '8', '9'];
 const t = String(text ?? "");
 const count = [...t].filter(c =&gt; belly.includes(c)).length;
 const spacing = count * 0.04;
 return spacing === 0 ? null : `${spacing}em`;
}

// getFillColor: berechnet die Füllfarbe je nach Farbschema und Ladestand.
function getFillColor(p, strongColors, colorScheme) {
 let hue, saturation, lightness;
 const scheme = (
   colorScheme === 'default' ? 'default' : (colorScheme ?? 'default')
 ).toLowerCase();

 switch (scheme) {
   case 'green': hue = 120; saturation = strongColors ? 100 : 80; lightness = strongColors ? 25 + p/4 : 35 + p*0.3; break;
   case 'yellow': hue = 50; saturation = strongColors ? 100 : 85; lightness = strongColors ? 25 + p*0.3 : 35 + p*0.3; break;
   case 'blue': hue = 210; saturation = strongColors ? 100 : 75; lightness = strongColors ? 20 + p*0.25 : 35 + p*0.3; break;
   case 'red': hue = 0; saturation = strongColors ? 100 : 75; lightness = strongColors ? 20 + p*0.25 : 35 + p*0.3; break;
   case 'orange': hue = 30; saturation = strongColors ? 100 : 80; lightness = strongColors ? 20 + p*0.25 : 35 + p*0.3; break;
   case 'brown': hue = 25; saturation = strongColors ? 85 : 65; lightness = strongColors ? 20 + p*0.2 : 25 + p*0.25; break;
   case 'grey': hue = 0; saturation = strongColors ? 15 : 0; lightness = strongColors ? 20 + p*0.4 : 25 + p*0.4; break;
   case 'purple': hue = 275; saturation = strongColors ? 95 : 75; lightness = strongColors ? 25 + p*0.25 : 35 + p*0.3; break;
   case 'black': hue = 0; saturation = strongColors ? 10 : 0; lightness = strongColors ? 1 + p*0.27 : 3 + p*0.2; break;
   default:
     hue = Math.round(p * 1.2);
     saturation = strongColors ? 100 : 90;
     lightness = strongColors ? 35 : 50;
     break;
 }

 return `hsl(${hue},${saturation}%,${lightness}%)`;
}

// getBoltGradientFromScheme: bestimmt den Farbverlauf des Blitzsymbols je nach Schema.
function getBoltGradientFromScheme(strongColors, boltColorScheme) {
 const scheme = (
   boltColorScheme === 'default' ? 'default' : (boltColorScheme ?? 'default')
 ).toLowerCase();

 if (scheme === 'default') return ['#f7b23b', '#f59e0b'];

 let hue, saturation;
 switch (scheme) {
   case 'green': hue = 120; saturation = strongColors ? 100 : 80; break;
   case 'yellow': hue = 50; saturation = strongColors ? 100 : 85; break;
   case 'blue': hue = 210; saturation = strongColors ? 100 : 75; break;
   case 'red': hue = 0; saturation = strongColors ? 100 : 75; break;
   case 'orange': hue = 30; saturation = strongColors ? 100 : 80; break;
   case 'brown': hue = 25; saturation = strongColors ? 85 : 65; break;
   case 'grey': hue = 0; saturation = strongColors ? 15 : 0; break;
   case 'purple': hue = 275; saturation = strongColors ? 95 : 75; break;
   case 'black': hue = 0; saturation = strongColors ? 10 : 0; break;
   default: hue = 45; saturation = 100; break;
 }

 const lightLow = strongColors ? 25 : 40;
 const lightHigh = strongColors ? 65 : 70;

 return [`hsl(${hue},${saturation}%,${lightHigh}%)`, `hsl(${hue},${saturation}%,${lightLow}%)`];
}

// parseRightBackground: prüft, ob ein rechter Hintergrund gesetzt ist oder 'default' (dann keiner).
function parseRightBackground(value) {
 if (!value || value === "default") return null;
 return value;
}

// generateBatterySvg: Hauptfunktion, erzeugt das komplette Batterie-SVG inklusive Form, Text, Farben, Blitz und Effekten.
function generateBatterySvg(
 percent,
 decimalPlaces = 0,
 labelSuffix = '%',
 customLabel = null,
 showPercent = true,
 strongColors = false,
 colorScheme = 'default',
 showBolt = false,
 boltPos = 100,
 blinkBolt = false,
 boltColorScheme = 'default',
 rightBackground = 'default'
) {
 const raw = Number(percent);
 const p = clamp(Number.isFinite(raw) ? raw : 0, 0, 100);

 const viewBoxW = 264, viewBoxH = 129;
 const outer = { x: 20, y: 24, w: 200, h: 80, rx: 18 };
 const inner = { x: 24, y: 28, h: 72, rx: 12 };
 const maxInnerWidth = 192;

 const fillW = interpolatedWidth(p);
 const fillColor = getFillColor(p, strongColors, colorScheme);

 const rightCustom = parseRightBackground(rightBackground);
 const rightStartX = inner.x + fillW;
 const rightWidth = maxInnerWidth - fillW;

 const nums = (fillColor.match(/-?\d+(\.\d+)?/g) || []).map(Number);
 const [hVal = 0, sVal = 0, lVal = 50] = nums;
 const [r, g, b] = hslToRgb(hVal, sVal, lVal);
 const lum = luminance(r, g, b);
 const textFill = lum &gt; 0.55 ? '#000' : '#fff';
 const outlineColor = (textFill === '#fff') ? 'rgba(0,0,0,0.85)' : 'rgba(255,255,255,0.95)';

 const formattedValue = Number(p).toFixed(decimalPlaces);
 const formattedTrimmed = decimalPlaces === 0
   ? String(Math.round(Number(formattedValue)))
   : formattedValue;

 const displayText = customLabel ?? `${formattedTrimmed}${labelSuffix}`;

 const fontSize = Math.max(12, Math.round(inner.h * 0.33 * 2.25 * 1.10));
 const textCenterX = inner.x + maxInnerWidth / 2;
 const textCenterY = inner.y + inner.h / 2;
 const TEXT_DY_EM = 0.35;

 const contact = { x: 224, y: 46, w: 20, h: 36 };
 const contactCenterY = contact.y + contact.h / 2;

 const boltViewBox = { w: 102.7, h: 186.8 };
 const boltTargetH = outer.h * 1.7;
 const boltScale = boltTargetH / boltViewBox.h;
 const boltOffsetY = contactCenterY + 26;

 const clampedBoltPos = clamp(boltPos, 0, 100);

 const boltMinX = -37.0;
 const boltMaxX = 185.0;

 const boltX = boltMinX + (boltMaxX - boltMinX) * (clampedBoltPos / 100);

 const boltTransform = `
   translate(${boltX}, ${boltOffsetY})
   scale(${boltScale})
   translate(${-boltViewBox.w / 2}, ${-boltViewBox.h / 2})
 `.trim();

 const id = uid('b');

 const boltAnimation = blinkBolt ? `
   &lt;style&gt;
     @keyframes blinkBolt-${id} {
       0%, 100% { opacity: 1; }
       50% { opacity: 0.6; }
     }
     .blinking-bolt-${id} {
       animation: blinkBolt-${id} 1.8s ease-in-out infinite;
     }
   &lt;/style&gt;` : '';

 const boltClass = blinkBolt ? `blinking-bolt-${id}` : '';

 const [boltColorLight, boltColorDark] = getBoltGradientFromScheme(strongColors, boltColorScheme);

 const dynamicLetterSpacing = getDynamicLetterSpacing(displayText);
 const letterSpacingAttr = dynamicLetterSpacing ? `letter-spacing="${dynamicLetterSpacing}"` : '';

 return `
   &lt;svg xmlns="http://www.w3.org/2000/svg"
        viewBox="0 0 ${viewBoxW} ${viewBoxH}"
        width="100%" height="100%"
        preserveAspectRatio="xMidYMid meet"&gt;

     ${boltAnimation}

     &lt;defs&gt;
       &lt;linearGradient id="glass-${id}" x1="0" y1="0" x2="0" y2="1"&gt;
         &lt;stop offset="0%" stop-color="#ffffff" stop-opacity="0.80"/&gt;
         &lt;stop offset="100%" stop-color="#ffffff" stop-opacity="0.10"/&gt;
       &lt;/linearGradient&gt;

       &lt;linearGradient id="diagGlass-${id}" x1="0" y1="0" x2="1" y2="1"&gt;
         &lt;stop offset="0%" stop-color="#ffffff" stop-opacity="0.75"/&gt;
         &lt;stop offset="45%" stop-color="#ffffff" stop-opacity="0.22"/&gt;
         &lt;stop offset="100%" stop-color="#ffffff" stop-opacity="0.03"/&gt;
       &lt;/linearGradient&gt;

       &lt;pattern id="stripes-${id}" width="8" height="8" patternUnits="userSpaceOnUse"&gt;
         &lt;rect width="8" height="8" fill="transparent"/&gt;
         &lt;path d="M-1,6 l8,-6 M-1,10 l8,-6"
               stroke="#fff" stroke-opacity="0.08" stroke-width="1"/&gt;
       &lt;/pattern&gt;

       &lt;clipPath id="clip-fill-${id}"&gt;
         &lt;rect x="${inner.x}" y="${inner.y}" width="${maxInnerWidth}"
               height="${inner.h}" rx="${inner.rx}" ry="${inner.rx}" /&gt;
       &lt;/clipPath&gt;

       &lt;linearGradient id="boltGradient-${id}" x1="0" y1="0" x2="0" y2="1"&gt;
         &lt;stop offset="0%" stop-color="${boltColorLight}"/&gt;
         &lt;stop offset="100%" stop-color="${boltColorDark}"/&gt;
       &lt;/linearGradient&gt;

       &lt;symbol id="boltSymbol-${id}" viewBox="0 0 102.7 186.8"&gt;
         &lt;path fill="url(#boltGradient-${id})" stroke="#000" stroke-width="6" stroke-linejoin="round"
               d="m34.8 2-32 96h32l-16 80 80-112h-48l32-64h-48z"/&gt;
       &lt;/symbol&gt;
     &lt;/defs&gt;

     &lt;!-- ÄUSSERER RAHMEN --&gt;
     &lt;rect x="${outer.x}" y="${outer.y}" width="${outer.w}" height="${outer.h}"
           rx="${outer.rx}" fill="#222" stroke="#ddd" stroke-width="4"/&gt;

     &lt;!-- FÜLLBEREICH LINKS (universeller Fix) --&gt;
     ${
       fillW &gt;= maxInnerWidth
         ? `&lt;rect x="${inner.x}" y="${inner.y}" width="${maxInnerWidth}" height="${inner.h}"
                  rx="${inner.rx}" ry="${inner.rx}" fill="${fillColor}"/&gt;`
         : (
             fillW &gt; 0
               ? `&lt;path d="
                    M ${inner.x + inner.rx} ${inner.y}
                    L ${inner.x + fillW} ${inner.y}
                    L ${inner.x + fillW} ${inner.y + inner.h}
                    L ${inner.x + inner.rx} ${inner.y + inner.h}
                    A ${inner.rx} ${inner.rx} 0 0 1 ${inner.x} ${inner.y + inner.h - inner.rx}
                    L ${inner.x} ${inner.y + inner.rx}
                    A ${inner.rx} ${inner.rx} 0 0 1 ${inner.x + inner.rx} ${inner.y}
                    Z"
                  fill="${fillColor}"/&gt;`
               : ""
           )
     }

     &lt;!-- RECHTE HINTERGRUNDHÄLFTE --&gt;
     ${
       rightBackground === "default" || fillW &gt;= maxInnerWidth
         ? ""
         : (rightWidth &gt; 0
             ? `&lt;path d="M ${rightStartX} ${inner.y}
                        L ${rightStartX + rightWidth - inner.rx} ${inner.y}
                        A ${inner.rx} ${inner.rx} 0 0 1 ${rightStartX + rightWidth} ${inner.y + inner.rx}
                        L ${rightStartX + rightWidth} ${inner.y + inner.h - inner.rx}
                        A ${inner.rx} ${inner.rx} 0 0 1 ${rightStartX + rightWidth - inner.rx} ${inner.y + inner.h}
                        L ${rightStartX} ${inner.y + inner.h}
                        Z"
                    fill="${rightCustom}"/&gt;`
             : "")
     }

     &lt;!-- GLAS UND TEXTURIERUNG --&gt;
     &lt;g clip-path="url(#clip-fill-${id})"&gt;
       &lt;rect x="${inner.x}" y="${inner.y}" width="${fillW}" height="${inner.h}"
             fill="url(#stripes-${id})" opacity="0.95"/&gt;
       &lt;rect x="${inner.x}" y="${inner.y}" width="${fillW}" height="${inner.h}"
             fill="url(#glass-${id})" opacity="0.25"/&gt;
     &lt;/g&gt;

     &lt;!-- DIAGONALER GLASEFFEKT --&gt;
     &lt;rect x="${outer.x}" y="${outer.y}" width="${outer.w}" height="${outer.h}"
           rx="${outer.rx}" fill="url(#diagGlass-${id})" opacity="0.9"/&gt;

     &lt;!-- KONTAKT --&gt;
     &lt;rect x="224" y="46" width="20" height="36" rx="6" fill="#ccc" stroke="#888" stroke-width="2"/&gt;

     &lt;!-- BLITZ --&gt;
     ${showBolt ? `&lt;use href="#boltSymbol-${id}" class="${boltClass}" transform="${boltTransform}"/&gt;` : ""}

     &lt;!-- TEXT --&gt;
     ${
       showPercent
         ? `&lt;g transform="translate(${textCenterX}, ${textCenterY})"&gt;
             &lt;text text-anchor="middle"
                   font-family="Arial, Helvetica, sans-serif"
                   font-size="${fontSize}"
                   font-weight="700"
                   fill="${textFill}"
                   stroke="${outlineColor}"
                   stroke-width="${Math.max(2, Math.round(fontSize * 0.15))}"
                   paint-order="stroke"
                   dy="${TEXT_DY_EM}em"
                   ${letterSpacingAttr}&gt;
               ${displayText}
             &lt;/text&gt;
           &lt;/g&gt;`
         : ""
     }

   &lt;/svg&gt;
 `.trim();
}
</code></pre>
<p dir="auto"></p></section></section><p></p>
<p dir="auto"></p><section class="spoiler-wrapper"><button class="spoiler-control btn btn-default">Beispielcode aus meiner Umgebung</button><section style="display:none" class="spoiler-content"><p></p>
<pre><code>const Battery_Basis = '0_userdata.0.Battery.';

// Batterie-Datenpunkte mit Lade-Datenpunkt
const batteryDevices = {
   Netatmo: { 
       id: 'netatmo.0.63ac6778fe0c0b93e3004238.70-ee-50-90-e4-10.02-00-00-90-60-64.BatteryStatus',
       loading_id: null
   },
   HeizungWZ: { 
       id: 'fritzdect.0.DECT_139790949411.battery',
       loading_id: null
   },
   HeizungKZ: { 
       id: 'fritzdect.0.DECT_099950330172.battery',
       loading_id: null
   },
   Roborock: { 
       id: 'roborock.0.Devices.6oZ4aK34EEIzJLLLLLLzbX.deviceStatus.battery',
       loading_id: 'alias.0.Charging.Roborock'
   },
   HueWZ: { 
       id: 'hue.0.WZ_Deckenlampe.battery',
       loading_id: null
   },
   HueWZSB: { 
       id: 'hue.0.WZ_Sitzbereich.battery',
       loading_id: null
   },
   HueKueche: { 
       id: 'hue.0.Küche_Deckenlampe.battery',
       loading_id: null
   },
   HueKZ: { 
       id: 'hue.0.Yasu.battery',
       loading_id: null
   },
   OwnYXXX: { 
       id: 'owntracks.0.users.yh.battery',
       loading_id: 'alias.0.Charging.YXXX_Handy'
   },
   OwnLoXXX: { 
       id: 'owntracks.0.users.lh.battery',
       loading_id: 'alias.0.Charging.LoXXX_Handy'
   },
   OwnRoXXX: { 
       id: 'owntracks.0.users.rh.battery',
       loading_id: 'alias.0.Charging.RoXXX_Handy'
   }
};

// Version 1.0.17
// Dynamische Batterie-Icon Generierung
// ------------------------------------------------------

// clamp: sorgt dafür, dass ein Wert nie kleiner als Minimum oder größer als Maximum wird. Nützlich für Prozentwerte.
function clamp(v, a, b) {
 return Math.max(a, Math.min(b, v));
}

// uid: erzeugt eine eindeutige ID, damit mehrere SVGs auf derselben Seite ohne Konflikte funktionieren.
function uid(prefix = 'id') {
 return `${prefix}-${Math.random().toString(36).slice(2, 9)}`;
}

// hslToRgb: wandelt HSL-Farben in RGB um, damit kann später die Helligkeit berechnent werden.
function hslToRgb(h, s, l) {
 s /= 100;
 l /= 100;
 const k = n =&gt; (n + h / 30) % 12;
 const a = s * Math.min(l, 1 - l);
 const f = n =&gt; l - a * Math.max(-1,
   Math.min(k(n) - 3, Math.min(9 - k(n), 1))
 );
 return [Math.round(255 * f(0)), Math.round(255 * f(8)), Math.round(255 * f(4))];
}

// luminance: berechnet die wahrgenommene Helligkeit einer Farbe. Wichtig für gut lesbaren Text.
function luminance(r, g, b) {
 const srgb = [r, g, b].map(c =&gt; {
   c /= 255;
   return (c &lt;= 0.04045) ? c / 12.92
     : Math.pow((c + 0.055) / 1.055, 2.4);
 });
 return 0.2126 * srgb[0] + 0.7152 * srgb[1] + 0.0722 * srgb[2];
}

// SAMPLE_POINTS: Tabelle für die Breite des Füllbalkens bei verschiedenen Prozentwerten für harmonische Übergänge.
const SAMPLE_POINTS = [
 { p: 0, w: 2 }, { p: 5, w: 10 }, { p: 10, w: 19 }, { p: 15, w: 29 },
 { p: 20, w: 38 }, { p: 25, w: 48 }, { p: 30, w: 58 }, { p: 35, w: 67 },
 { p: 40, w: 77 }, { p: 45, w: 86 }, { p: 50, w: 96 }, { p: 55, w: 106 },
 { p: 60, w: 115 }, { p: 65, w: 125 }, { p: 70, w: 134 }, { p: 75, w: 144 },
 { p: 80, w: 154 }, { p: 85, w: 163 }, { p: 90, w: 173 }, { p: 95, w: 182 },
 { p: 100, w: 192 }
];

// interpolatedWidth: berechnet die Breite des Füllbalkens aus SAMPLE_POINTS, auch Zwischenwerte.
function interpolatedWidth(percent) {
 const p = clamp(percent, 0, 100);

 for (const s of SAMPLE_POINTS) if (s.p === p) return s.w;

 let lower = SAMPLE_POINTS[0], upper = SAMPLE_POINTS[SAMPLE_POINTS.length - 1];

 for (let i = 0; i &lt; SAMPLE_POINTS.length - 1; i++) {
   const a = SAMPLE_POINTS[i], b = SAMPLE_POINTS[i + 1];
   if (p &gt; a.p &amp;&amp; p &lt; b.p) { lower = a; upper = b; break; }
   if (p === b.p) return b.w;
 }

 const t = (p - lower.p) / (upper.p - lower.p);
 return Math.round(lower.w + t * (upper.w - lower.w));
}

// getDynamicLetterSpacing: fügt bei runden Ziffern etwas mehr Abstand ein, damit der Text optisch sauber wirkt.
function getDynamicLetterSpacing(text) {
 const belly = ['0', '3', '6', '8', '9'];
 const t = String(text ?? "");
 const count = [...t].filter(c =&gt; belly.includes(c)).length;
 const spacing = count * 0.04;
 return spacing === 0 ? null : `${spacing}em`;
}

// getFillColor: berechnet die Füllfarbe je nach Farbschema und Ladestand.
function getFillColor(p, strongColors, colorScheme) {
 let hue, saturation, lightness;
 const scheme = (
   colorScheme === 'default' ? 'default' : (colorScheme ?? 'default')
 ).toLowerCase();

 switch (scheme) {
   case 'green': hue = 120; saturation = strongColors ? 100 : 80; lightness = strongColors ? 25 + p/4 : 35 + p*0.3; break;
   case 'yellow': hue = 50; saturation = strongColors ? 100 : 85; lightness = strongColors ? 25 + p*0.3 : 35 + p*0.3; break;
   case 'blue': hue = 210; saturation = strongColors ? 100 : 75; lightness = strongColors ? 20 + p*0.25 : 35 + p*0.3; break;
   case 'red': hue = 0; saturation = strongColors ? 100 : 75; lightness = strongColors ? 20 + p*0.25 : 35 + p*0.3; break;
   case 'orange': hue = 30; saturation = strongColors ? 100 : 80; lightness = strongColors ? 20 + p*0.25 : 35 + p*0.3; break;
   case 'brown': hue = 25; saturation = strongColors ? 85 : 65; lightness = strongColors ? 20 + p*0.2 : 25 + p*0.25; break;
   case 'grey': hue = 0; saturation = strongColors ? 15 : 0; lightness = strongColors ? 20 + p*0.4 : 25 + p*0.4; break;
   case 'purple': hue = 275; saturation = strongColors ? 95 : 75; lightness = strongColors ? 25 + p*0.25 : 35 + p*0.3; break;
   case 'black': hue = 0; saturation = strongColors ? 10 : 0; lightness = strongColors ? 1 + p*0.27 : 3 + p*0.2; break;
   default:
     hue = Math.round(p * 1.2);
     saturation = strongColors ? 100 : 90;
     lightness = strongColors ? 35 : 50;
     break;
 }

 return `hsl(${hue},${saturation}%,${lightness}%)`;
}

// getBoltGradientFromScheme: bestimmt den Farbverlauf des Blitzsymbols je nach Schema.
function getBoltGradientFromScheme(strongColors, boltColorScheme) {
 const scheme = (
   boltColorScheme === 'default' ? 'default' : (boltColorScheme ?? 'default')
 ).toLowerCase();

 if (scheme === 'default') return ['#f7b23b', '#f59e0b'];

 let hue, saturation;
 switch (scheme) {
   case 'green': hue = 120; saturation = strongColors ? 100 : 80; break;
   case 'yellow': hue = 50; saturation = strongColors ? 100 : 85; break;
   case 'blue': hue = 210; saturation = strongColors ? 100 : 75; break;
   case 'red': hue = 0; saturation = strongColors ? 100 : 75; break;
   case 'orange': hue = 30; saturation = strongColors ? 100 : 80; break;
   case 'brown': hue = 25; saturation = strongColors ? 85 : 65; break;
   case 'grey': hue = 0; saturation = strongColors ? 15 : 0; break;
   case 'purple': hue = 275; saturation = strongColors ? 95 : 75; break;
   case 'black': hue = 0; saturation = strongColors ? 10 : 0; break;
   default: hue = 45; saturation = 100; break;
 }

 const lightLow = strongColors ? 25 : 40;
 const lightHigh = strongColors ? 65 : 70;

 return [`hsl(${hue},${saturation}%,${lightHigh}%)`, `hsl(${hue},${saturation}%,${lightLow}%)`];
}

// parseRightBackground: prüft, ob ein rechter Hintergrund gesetzt ist oder 'default' (dann keiner).
function parseRightBackground(value) {
 if (!value || value === "default") return null;
 return value;
}

// generateBatterySvg: Hauptfunktion, erzeugt das komplette Batterie-SVG inklusive Form, Text, Farben, Blitz und Effekten.
function generateBatterySvg(
 percent,
 decimalPlaces = 0,
 labelSuffix = '%',
 customLabel = null,
 showPercent = true,
 strongColors = false,
 colorScheme = 'default',
 showBolt = false,
 boltPos = 100,
 blinkBolt = false,
 boltColorScheme = 'default',
 rightBackground = 'default'
) {
 const raw = Number(percent);
 const p = clamp(Number.isFinite(raw) ? raw : 0, 0, 100);

 const viewBoxW = 264, viewBoxH = 129;
 const outer = { x: 20, y: 24, w: 200, h: 80, rx: 18 };
 const inner = { x: 24, y: 28, h: 72, rx: 12 };
 const maxInnerWidth = 192;

 const fillW = interpolatedWidth(p);
 const fillColor = getFillColor(p, strongColors, colorScheme);

 const rightCustom = parseRightBackground(rightBackground);
 const rightStartX = inner.x + fillW;
 const rightWidth = maxInnerWidth - fillW;

 const nums = (fillColor.match(/-?\d+(\.\d+)?/g) || []).map(Number);
 const [hVal = 0, sVal = 0, lVal = 50] = nums;
 const [r, g, b] = hslToRgb(hVal, sVal, lVal);
 const lum = luminance(r, g, b);
 const textFill = lum &gt; 0.55 ? '#000' : '#fff';
 const outlineColor = (textFill === '#fff') ? 'rgba(0,0,0,0.85)' : 'rgba(255,255,255,0.95)';

 const formattedValue = Number(p).toFixed(decimalPlaces);
 const formattedTrimmed = decimalPlaces === 0
   ? String(Math.round(Number(formattedValue)))
   : formattedValue;

 const displayText = customLabel ?? `${formattedTrimmed}${labelSuffix}`;

 const fontSize = Math.max(12, Math.round(inner.h * 0.33 * 2.25 * 1.10));
 const textCenterX = inner.x + maxInnerWidth / 2;
 const textCenterY = inner.y + inner.h / 2;
 const TEXT_DY_EM = 0.35;

 const contact = { x: 224, y: 46, w: 20, h: 36 };
 const contactCenterY = contact.y + contact.h / 2;

 const boltViewBox = { w: 102.7, h: 186.8 };
 const boltTargetH = outer.h * 1.7;
 const boltScale = boltTargetH / boltViewBox.h;
 const boltOffsetY = contactCenterY + 26;

 const clampedBoltPos = clamp(boltPos, 0, 100);

 const boltMinX = -37.0;
 const boltMaxX = 185.0;

 const boltX = boltMinX + (boltMaxX - boltMinX) * (clampedBoltPos / 100);

 const boltTransform = `
   translate(${boltX}, ${boltOffsetY})
   scale(${boltScale})
   translate(${-boltViewBox.w / 2}, ${-boltViewBox.h / 2})
 `.trim();

 const id = uid('b');

 const boltAnimation = blinkBolt ? `
   &lt;style&gt;
     @keyframes blinkBolt-${id} {
       0%, 100% { opacity: 1; }
       50% { opacity: 0.6; }
     }
     .blinking-bolt-${id} {
       animation: blinkBolt-${id} 1.8s ease-in-out infinite;
     }
   &lt;/style&gt;` : '';

 const boltClass = blinkBolt ? `blinking-bolt-${id}` : '';

 const [boltColorLight, boltColorDark] = getBoltGradientFromScheme(strongColors, boltColorScheme);

 const dynamicLetterSpacing = getDynamicLetterSpacing(displayText);
 const letterSpacingAttr = dynamicLetterSpacing ? `letter-spacing="${dynamicLetterSpacing}"` : '';

 return `
   &lt;svg xmlns="http://www.w3.org/2000/svg"
        viewBox="0 0 ${viewBoxW} ${viewBoxH}"
        width="100%" height="100%"
        preserveAspectRatio="xMidYMid meet"&gt;

     ${boltAnimation}

     &lt;defs&gt;
       &lt;linearGradient id="glass-${id}" x1="0" y1="0" x2="0" y2="1"&gt;
         &lt;stop offset="0%" stop-color="#ffffff" stop-opacity="0.80"/&gt;
         &lt;stop offset="100%" stop-color="#ffffff" stop-opacity="0.10"/&gt;
       &lt;/linearGradient&gt;

       &lt;linearGradient id="diagGlass-${id}" x1="0" y1="0" x2="1" y2="1"&gt;
         &lt;stop offset="0%" stop-color="#ffffff" stop-opacity="0.75"/&gt;
         &lt;stop offset="45%" stop-color="#ffffff" stop-opacity="0.22"/&gt;
         &lt;stop offset="100%" stop-color="#ffffff" stop-opacity="0.03"/&gt;
       &lt;/linearGradient&gt;

       &lt;pattern id="stripes-${id}" width="8" height="8" patternUnits="userSpaceOnUse"&gt;
         &lt;rect width="8" height="8" fill="transparent"/&gt;
         &lt;path d="M-1,6 l8,-6 M-1,10 l8,-6"
               stroke="#fff" stroke-opacity="0.08" stroke-width="1"/&gt;
       &lt;/pattern&gt;

       &lt;clipPath id="clip-fill-${id}"&gt;
         &lt;rect x="${inner.x}" y="${inner.y}" width="${maxInnerWidth}"
               height="${inner.h}" rx="${inner.rx}" ry="${inner.rx}" /&gt;
       &lt;/clipPath&gt;

       &lt;linearGradient id="boltGradient-${id}" x1="0" y1="0" x2="0" y2="1"&gt;
         &lt;stop offset="0%" stop-color="${boltColorLight}"/&gt;
         &lt;stop offset="100%" stop-color="${boltColorDark}"/&gt;
       &lt;/linearGradient&gt;

       &lt;symbol id="boltSymbol-${id}" viewBox="0 0 102.7 186.8"&gt;
         &lt;path fill="url(#boltGradient-${id})" stroke="#000" stroke-width="6" stroke-linejoin="round"
               d="m34.8 2-32 96h32l-16 80 80-112h-48l32-64h-48z"/&gt;
       &lt;/symbol&gt;
     &lt;/defs&gt;

     &lt;!-- ÄUSSERER RAHMEN --&gt;
     &lt;rect x="${outer.x}" y="${outer.y}" width="${outer.w}" height="${outer.h}"
           rx="${outer.rx}" fill="#222" stroke="#ddd" stroke-width="4"/&gt;

     &lt;!-- FÜLLBEREICH LINKS (universeller Fix) --&gt;
     ${
       fillW &gt;= maxInnerWidth
         ? `&lt;rect x="${inner.x}" y="${inner.y}" width="${maxInnerWidth}" height="${inner.h}"
                  rx="${inner.rx}" ry="${inner.rx}" fill="${fillColor}"/&gt;`
         : (
             fillW &gt; 0
               ? `&lt;path d="
                    M ${inner.x + inner.rx} ${inner.y}
                    L ${inner.x + fillW} ${inner.y}
                    L ${inner.x + fillW} ${inner.y + inner.h}
                    L ${inner.x + inner.rx} ${inner.y + inner.h}
                    A ${inner.rx} ${inner.rx} 0 0 1 ${inner.x} ${inner.y + inner.h - inner.rx}
                    L ${inner.x} ${inner.y + inner.rx}
                    A ${inner.rx} ${inner.rx} 0 0 1 ${inner.x + inner.rx} ${inner.y}
                    Z"
                  fill="${fillColor}"/&gt;`
               : ""
           )
     }

     &lt;!-- RECHTE HINTERGRUNDHÄLFTE --&gt;
     ${
       rightBackground === "default" || fillW &gt;= maxInnerWidth
         ? ""
         : (rightWidth &gt; 0
             ? `&lt;path d="M ${rightStartX} ${inner.y}
                        L ${rightStartX + rightWidth - inner.rx} ${inner.y}
                        A ${inner.rx} ${inner.rx} 0 0 1 ${rightStartX + rightWidth} ${inner.y + inner.rx}
                        L ${rightStartX + rightWidth} ${inner.y + inner.h - inner.rx}
                        A ${inner.rx} ${inner.rx} 0 0 1 ${rightStartX + rightWidth - inner.rx} ${inner.y + inner.h}
                        L ${rightStartX} ${inner.y + inner.h}
                        Z"
                    fill="${rightCustom}"/&gt;`
             : "")
     }

     &lt;!-- GLAS UND TEXTURIERUNG --&gt;
     &lt;g clip-path="url(#clip-fill-${id})"&gt;
       &lt;rect x="${inner.x}" y="${inner.y}" width="${fillW}" height="${inner.h}"
             fill="url(#stripes-${id})" opacity="0.95"/&gt;
       &lt;rect x="${inner.x}" y="${inner.y}" width="${fillW}" height="${inner.h}"
             fill="url(#glass-${id})" opacity="0.25"/&gt;
     &lt;/g&gt;

     &lt;!-- DIAGONALER GLASEFFEKT --&gt;
     &lt;rect x="${outer.x}" y="${outer.y}" width="${outer.w}" height="${outer.h}"
           rx="${outer.rx}" fill="url(#diagGlass-${id})" opacity="0.9"/&gt;

     &lt;!-- KONTAKT --&gt;
     &lt;rect x="224" y="46" width="20" height="36" rx="6" fill="#ccc" stroke="#888" stroke-width="2"/&gt;

     &lt;!-- BLITZ --&gt;
     ${showBolt ? `&lt;use href="#boltSymbol-${id}" class="${boltClass}" transform="${boltTransform}"/&gt;` : ""}

     &lt;!-- TEXT --&gt;
     ${
       showPercent
         ? `&lt;g transform="translate(${textCenterX}, ${textCenterY})"&gt;
             &lt;text text-anchor="middle"
                   font-family="Arial, Helvetica, sans-serif"
                   font-size="${fontSize}"
                   font-weight="700"
                   fill="${textFill}"
                   stroke="${outlineColor}"
                   stroke-width="${Math.max(2, Math.round(fontSize * 0.15))}"
                   paint-order="stroke"
                   dy="${TEXT_DY_EM}em"
                   ${letterSpacingAttr}&gt;
               ${displayText}
             &lt;/text&gt;
           &lt;/g&gt;`
         : ""
     }

   &lt;/svg&gt;
 `.trim();
}


// beliebige DP-Werte zu echtem Boolean normalisieren
function toBool(val) {
   if (val === true) return true;
   if (val === 1) return true;
   if (typeof val === 'string') {
       const s = val.trim().toLowerCase();
       return s === 'true' || s === 'on' || s === 'charging' || s === 'laden' || s === '1';
   }
   return false;
}

// States erstellen &amp; Listener setzen
Object.entries(batteryDevices).forEach(([name, dev]) =&gt; {
   if (!dev || typeof dev.id !== 'string' || dev.id.length === 0) return;

   const id = dev.id;
   const loading_id = dev.loading_id; // false oder String
   const DescVisible = (getState('0_userdata.0.Battery.LabelVisible') || { val: true }).val;
   const StrongColor = (getState('0_userdata.0.Battery.StrongColor') || { val: false }).val;
   const colorScheme = (getState('0_userdata.0.Battery.ColorSchema') || { val: 'default' }).val;
   const boltblink = (getState('0_userdata.0.Battery.BlinkBolt') || { val: false }).val;
   const boltpos = (getState('0_userdata.0.Battery.PosBolt') || { val: 100 }).val;
   const boltcolorScheme = (getState('0_userdata.0.Battery.ColorSchemaBolt') || { val: 'default' }).val;
   const stateId = Battery_Basis + name;

   if (!existsState(stateId)) {
       createState(stateId, '', { type: 'string', read: true, write: true });
   }

   const getShowBolt = () =&gt; {
       if (typeof loading_id === 'string' &amp;&amp; loading_id.length &gt; 0) {
           const s = getState(loading_id);
           return s ? toBool(s.val) : false;
       }
       return false;
   };

   on({ id, change: 'ne' }, dp =&gt; {
       const batteryVal = dp &amp;&amp; dp.state ? dp.state.val : (getState(id) || { val: null }).val;
       const ShowBolt = getShowBolt();
       setState(stateId, generateBatterySvg(batteryVal, 0, '%', null, DescVisible, StrongColor, colorScheme, ShowBolt, boltpos, boltblink, boltcolorScheme, 'rgba(0,128,128,0.3'), true);
   });

   const initBattery = (getState(id) || { val: null }).val;
   const ShowBolt = getShowBolt();
   setState(stateId, generateBatterySvg(initBattery, 0, '%', null, DescVisible, StrongColor, colorScheme, ShowBolt, boltpos, boltblink, boltcolorScheme, 'rgba(0,128,128,0.3'), true);

   if (typeof loading_id === 'string' &amp;&amp; loading_id.length &gt; 0) {
       const loadingStateId = Battery_Basis + name + '.loading';
       if (!existsState(loadingStateId)) {
           createState(loadingStateId, false, { type: 'boolean', read: true, write: true });
       }
       setState(loadingStateId, ShowBolt, true);

       on({ id: loading_id, change: 'ne' }, dp =&gt; {
           const ShowBolt = dp &amp;&amp; dp.state ? toBool(dp.state.val) : getShowBolt();
           setState(loadingStateId, ShowBolt, true);

           const curBattery = (getState(id) || { val: null }).val;
           setState(stateId, generateBatterySvg(curBattery, 0, '%', null, DescVisible, StrongColor, colorScheme, ShowBolt, boltpos, boltblink, boltcolorScheme, 'rgba(0,128,128,0.3'), true);
       });
   }
});

on(['0_userdata.0.Battery.LabelVisible','0_userdata.0.Battery.StrongColor','0_userdata.0.Battery.ColorSchema','0_userdata.0.Battery.BlinkBolt','0_userdata.0.Battery.PosBolt','0_userdata.0.Battery.ColorSchemaBolt'], function(dp) {
   Object.entries(batteryDevices).forEach(([name, { id, loading_id }]) =&gt; {
       const DescVisible = (getState('0_userdata.0.Battery.LabelVisible') || { val: true }).val;
       const StrongColor = (getState('0_userdata.0.Battery.StrongColor') || { val: false }).val;
       const colorScheme = (getState('0_userdata.0.Battery.ColorSchema') || { val: 'default' }).val;
       const boltblink = (getState('0_userdata.0.Battery.BlinkBolt') || { val: false }).val;
       const boltpos = (getState('0_userdata.0.Battery.PosBolt') || { val: 100 }).val;
       const boltcolorScheme = (getState('0_userdata.0.Battery.ColorSchemaBolt') || { val: 'default' }).val;

       const stateId = Battery_Basis + name;

       const val = (getState(id) || { val: null }).val;

       let ShowBolt = false;
       if (typeof loading_id === 'string' &amp;&amp; loading_id.length &gt; 0) {
           const state = getState(loading_id);
           ShowBolt = state ? toBool(state.val) : false;
       }

       setState(stateId, generateBatterySvg(val, 0, '%', null, DescVisible, StrongColor, colorScheme, ShowBolt, boltpos, boltblink, boltcolorScheme, 'rgba(0,128,128,0.3'), true);
   });
});
</code></pre>
<p dir="auto"></p></section></section><p></p>
]]></description><link>https://forum.iobroker.net/topic/82868/skript-zur-dynamischen-generierung-batterie-akku-symbol</link><generator>RSS for Node</generator><lastBuildDate>Tue, 19 May 2026 18:18:14 GMT</lastBuildDate><atom:link href="https://forum.iobroker.net/topic/82868.rss" rel="self" type="application/rss+xml"/><pubDate>Tue, 11 Nov 2025 14:56:00 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Wed, 17 Dec 2025 20:22:21 GMT]]></title><description><![CDATA[<p dir="auto">Eventuell kannst du auch noch was aus "Beispielcode aus meiner Umgebung" entnehmen.</p>
<p dir="auto">Ro75.</p>
]]></description><link>https://forum.iobroker.net/post/1314805</link><guid isPermaLink="true">https://forum.iobroker.net/post/1314805</guid><dc:creator><![CDATA[Ro75]]></dc:creator><pubDate>Wed, 17 Dec 2025 20:22:21 GMT</pubDate></item><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Wed, 17 Dec 2025 20:20:07 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/ro75" aria-label="Profile: Ro75">@<bdi>Ro75</bdi></a> sagte in <a href="/post/1314802">Skript zur dynamischen Generierung Batterie/Akku Symbol</a>:</p>
<blockquote>
<p dir="auto">unterschiedlihe zieldatenpunkte und datenpunkte mit den werten zur ladung.</p>
</blockquote>
<p dir="auto">Genau das habe ich gemacht</p>
]]></description><link>https://forum.iobroker.net/post/1314803</link><guid isPermaLink="true">https://forum.iobroker.net/post/1314803</guid><dc:creator><![CDATA[michihorn]]></dc:creator><pubDate>Wed, 17 Dec 2025 20:20:07 GMT</pubDate></item><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Wed, 17 Dec 2025 20:18:57 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/michihorn" aria-label="Profile: michihorn">@<bdi>michihorn</bdi></a> am skript hättest du gar nichts ändern oder anpassen brauchen. du hättest nur die funktion mit den unterschiedlichen parametern mehrfach aufrufen müssen, sprich unterschiedlihe zieldatenpunkte und datenpunkte mit den werten zur ladung.</p>
<p dir="auto">Ro75.</p>
]]></description><link>https://forum.iobroker.net/post/1314802</link><guid isPermaLink="true">https://forum.iobroker.net/post/1314802</guid><dc:creator><![CDATA[Ro75]]></dc:creator><pubDate>Wed, 17 Dec 2025 20:18:57 GMT</pubDate></item><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Wed, 17 Dec 2025 20:15:18 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/ro75" aria-label="Profile: Ro75">@<bdi>Ro75</bdi></a> Ich habe mal das Script etwas erweitert, damit ich die Batt.Ladung meiner 6 Tablets darstellen kann. Der Bolt ist nun auch dynamisch je nach Tablet. Ich bin sicher das kann man eleganter machen, aber funktioniert.<br />
Ansonsten ist dein Script nahezu unverändert.</p>
<pre><code>//Ersteller: Ro75
//Datum: 22.11.2025
//Version: 1.0.19
//Javascript: 8.9.2
//NodeJS: 20.x / 22.x

//Stromversorgung Tablets
const PF = 'tuya.1.bfae60c4e925ac6395xjeg.1'; // bitte anpassen
const PK = 'tuya.1.bf12fc3c00a2407c0ezo9x.1'; // bitte anpassen
const POG = 'tuya.1.bf2fb948ad6f4d3915reap.1'; // bitte anpassen
const PWG = 'tuya.1.88008560d8f15bd1d73c.1'; // bitte anpassen
const PB = 'tuya.1.bf55e3ce44e8927d2actif.1'; // bitte anpassen
const PWZ = 'tuya.1.824307882462ab3b0506.1'; // bitte anpassen
const Laden = "0_userdata.0.System.Tablet_Batt.Laden" // bitte anpassen

//Datenpunkte zur VIS
const ZielF = '0_userdata.0.System.Tablet_Batt.Flur'; // bitte anpassen
const ZielK = '0_userdata.0.System.Tablet_Batt.Küche'; // bitte anpassen
const ZielOG = '0_userdata.0.System.Tablet_Batt.OG'; // bitte anpassen
const ZielWG = '0_userdata.0.System.Tablet_Batt.WG'; // bitte anpassen
const ZielB = '0_userdata.0.System.Tablet_Batt.Büro'; // bitte anpassen
const ZielWZ = '0_userdata.0.System.Tablet_Batt.WZ'; // bitte anpassen

//Batt Level der Tablets
const LevelF = 'fullybrowser.0.Flur.Info.batteryLevel'; // bitte anpassen
const LevelK = 'fullybrowser.0.Küche.Info.batteryLevel'; // bitte anpassen
const LevelOG = 'fullybrowser.0.OG.Info.batteryLevel'; // bitte anpassen
const LevelWG = 'fullybrowser.0.WG.Info.batteryLevel'; // bitte anpassen
const LevelB = 'fullybrowser.0.Büro.Info.batteryLevel'; // bitte anpassen
const LevelWZ = 'fullybrowser.0.WZ.Info.batteryLevel'; // bitte anpassen
var dValue


const batt = [LevelF, LevelK, LevelOG, LevelWG, LevelB, LevelWZ];

on({ id: batt, change: 'any' }, function (dp) {

    // clamp: sorgt dafür, dass ein Wert nie kleiner als Minimum oder größer als Maximum wird. Nützlich für Prozentwerte.
    function clamp(v, a, b) {
        return Math.max(a, Math.min(b, v));
    }

    // uid: erzeugt eine eindeutige ID, damit mehrere SVGs auf derselben Seite ohne Konflikte funktionieren.
    function uid(prefix = 'id') {
        return `${prefix}-${Math.random().toString(36).slice(2, 9)}`;
    }

    // hslToRgb: wandelt HSL-Farben in RGB um, damit kann später die Helligkeit berechnent werden.
    function hslToRgb(h, s, l) {
        s /= 100;
        l /= 100;
        const k = n =&gt; (n + h / 30) % 12;
        const a = s * Math.min(l, 1 - l);
        const f = n =&gt; l - a * Math.max(-1,
            Math.min(k(n) - 3, Math.min(9 - k(n), 1))
        );
        return [Math.round(255 * f(0)), Math.round(255 * f(8)), Math.round(255 * f(4))];
    }

    // luminance: berechnet die wahrgenommene Helligkeit einer Farbe. Wichtig für gut lesbaren Text.
    function luminance(r, g, b) {
        const srgb = [r, g, b].map(c =&gt; {
            c /= 255;
            return (c &lt;= 0.04045) ? c / 12.92
                : Math.pow((c + 0.055) / 1.055, 2.4);
        });
        return 0.2126 * srgb[0] + 0.7152 * srgb[1] + 0.0722 * srgb[2];
    }

    // SAMPLE_POINTS: Tabelle für die Breite des Füllbalkens bei verschiedenen Prozentwerten für harmonische Übergänge.
    const SAMPLE_POINTS = [
        { p: 0, w: 2 }, { p: 5, w: 10 }, { p: 10, w: 19 }, { p: 15, w: 29 },
        { p: 20, w: 38 }, { p: 25, w: 48 }, { p: 30, w: 58 }, { p: 35, w: 67 },
        { p: 40, w: 77 }, { p: 45, w: 86 }, { p: 50, w: 96 }, { p: 55, w: 106 },
        { p: 60, w: 115 }, { p: 65, w: 125 }, { p: 70, w: 134 }, { p: 75, w: 144 },
        { p: 80, w: 154 }, { p: 85, w: 163 }, { p: 90, w: 173 }, { p: 95, w: 182 },
        { p: 100, w: 192 }
    ];

    // interpolatedWidth: berechnet die Breite des Füllbalkens aus SAMPLE_POINTS, auch Zwischenwerte.
    function interpolatedWidth(percent) {
        const p = clamp(percent, 0, 100);

        for (const s of SAMPLE_POINTS) if (s.p === p) return s.w;

        let lower = SAMPLE_POINTS[0], upper = SAMPLE_POINTS[SAMPLE_POINTS.length - 1];

        for (let i = 0; i &lt; SAMPLE_POINTS.length - 1; i++) {
            const a = SAMPLE_POINTS[i], b = SAMPLE_POINTS[i + 1];
            if (p &gt; a.p &amp;&amp; p &lt; b.p) { lower = a; upper = b; break; }
            if (p === b.p) return b.w;
        }

        const t = (p - lower.p) / (upper.p - lower.p);
        return Math.round(lower.w + t * (upper.w - lower.w));
    }

    // getDynamicLetterSpacing: fügt bei runden Ziffern etwas mehr Abstand ein, damit der Text optisch sauber wirkt.
    function getDynamicLetterSpacing(text) {
        const belly = ['0', '3', '6', '8', '9'];
        const t = String(text ?? "");
        const count = [...t].filter(c =&gt; belly.includes(c)).length;
        const spacing = count * 0.04;
        return spacing === 0 ? null : `${spacing}em`;
    }

    // getFillColor: berechnet die Füllfarbe je nach Farbschema und Ladestand.
    function getFillColor(p, strongColors, colorScheme) {

        const raw = colorScheme ?? "default";
        const scheme = raw.toLowerCase();

        // Prüfe auf benutzerdefinierte Farben
        const isHex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(raw);
        const isRgb = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i.test(raw);
        const isRgba = /^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*((0?\.?\d+)|1|0)\s*\)$/i.test(raw);

        // -----------------------------------------------------
        // BENUTZERDEFINIERTE FARBEN → RGB → HSL → dynamischer Verlauf
        // -----------------------------------------------------
        if (isHex || isRgb || isRgba) {

            let r, g, b;

            if (isHex) {
                let hex = raw.slice(1);
                if (hex.length === 3)
                    hex = hex.split("").map(x =&gt; x + x).join("");
                r = parseInt(hex.slice(0, 2), 16);
                g = parseInt(hex.slice(2, 4), 16);
                b = parseInt(hex.slice(4, 6), 16);
            }
            else {
                // rgb(...) oder rgba(...)
                const nums = raw.match(/\d+\.?\d*/g).map(Number);
                [r, g, b] = nums;
            }

            // RGB → HSL
            const rf = r / 255, gf = g / 255, bf = b / 255;
            const max = Math.max(rf, gf, bf), min = Math.min(rf, gf, bf);
            const delta = max - min;

            let h = 0;

            if (delta !== 0) {
                if (max === rf) h = 60 * (((gf - bf) / delta) % 6);
                else if (max === gf) h = 60 * ((bf - rf) / delta + 2);
                else h = 60 * ((rf - gf) / delta + 4);
            }
            if (h &lt; 0) h += 360;

            const l = (max + min) / 2;
            const s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));

            const hue = Math.round(h);
            const saturation = Math.round(s * 100);
            const lightness = strongColors ? (20 + p * 0.25) : (35 + p * 0.3);

            return `hsl(${hue},${saturation}%,${lightness}%)`;
        }

        // -----------------------------------------------------
        // STANDARD-SCHEMEN
        // -----------------------------------------------------
        let hue, saturation, lightness;

        switch (scheme) {
            case 'green': hue = 120; saturation = strongColors ? 100 : 80; lightness = strongColors ? 25 + p / 4 : 35 + p * 0.3; break;
            case 'yellow': hue = 50; saturation = strongColors ? 100 : 85; lightness = strongColors ? 25 + p * 0.3 : 35 + p * 0.3; break;
            case 'blue': hue = 210; saturation = strongColors ? 100 : 75; lightness = strongColors ? 20 + p * 0.25 : 35 + p * 0.3; break;
            case 'red': hue = 0; saturation = strongColors ? 100 : 75; lightness = strongColors ? 20 + p * 0.25 : 35 + p * 0.3; break;
            case 'orange': hue = 30; saturation = strongColors ? 100 : 80; lightness = strongColors ? 20 + p * 0.25 : 35 + p * 0.3; break;
            case 'brown': hue = 25; saturation = strongColors ? 85 : 65; lightness = strongColors ? 20 + p * 0.2 : 25 + p * 0.25; break;
            case 'grey': hue = 0; saturation = strongColors ? 15 : 0; lightness = strongColors ? 20 + p * 0.4 : 25 + p * 0.4; break;
            case 'purple': hue = 275; saturation = strongColors ? 95 : 75; lightness = strongColors ? 25 + p * 0.25 : 35 + p * 0.3; break;
            case 'black': hue = 0; saturation = strongColors ? 10 : 0; lightness = strongColors ? 1 + p * 0.27 : 3 + p * 0.2; break;

            default:
                hue = Math.round(p * 1.2);
                saturation = strongColors ? 100 : 90;
                lightness = strongColors ? 35 : 50;
                break;
        }

        return `hsl(${hue},${saturation}%,${lightness}%)`;
    }

    // getBoltGradientFromScheme: bestimmt den Farbverlauf des Blitzsymbols je nach Schema.
    function getBoltGradientFromScheme(strongColors, boltColorScheme) {
        const scheme = (
            boltColorScheme === 'default' ? 'default' : (boltColorScheme ?? 'default')
        ).toLowerCase();

        if (scheme === 'default') return ['#f7b23b', '#f59e0b'];

        let hue, saturation;
        switch (scheme) {
            case 'green': hue = 120; saturation = strongColors ? 100 : 80; break;
            case 'yellow': hue = 50; saturation = strongColors ? 100 : 85; break;
            case 'blue': hue = 210; saturation = strongColors ? 100 : 75; break;
            case 'red': hue = 0; saturation = strongColors ? 100 : 75; break;
            case 'orange': hue = 30; saturation = strongColors ? 100 : 80; break;
            case 'brown': hue = 25; saturation = strongColors ? 85 : 65; break;
            case 'grey': hue = 0; saturation = strongColors ? 15 : 0; break;
            case 'purple': hue = 275; saturation = strongColors ? 95 : 75; break;
            case 'black': hue = 0; saturation = strongColors ? 10 : 0; break;
            default: hue = 45; saturation = 100; break;
        }

        const lightLow = strongColors ? 25 : 40;
        const lightHigh = strongColors ? 65 : 70;

        return [`hsl(${hue},${saturation}%,${lightHigh}%)`, `hsl(${hue},${saturation}%,${lightLow}%)`];
    }

    // parseRightBackground: prüft, ob ein rechter Hintergrund gesetzt ist oder 'default' (dann keiner).
    function parseRightBackground(value) {
        if (!value || value === "default") return null;
        return value;
    }

    // generateBatterySvg: Hauptfunktion, erzeugt das komplette Batterie-SVG inklusive Form, Text, Farben, Blitz und Effekten.
    function generateBatterySvg(
        percent,
        decimalPlaces = 0,
        labelSuffix = '%',
        customLabel = null,
        showPercent = true,
        strongColors = false,
        colorScheme = 'default',
        showBolt = true,
        boltPos = 100,
        blinkBolt = true,
        boltColorScheme = 'default',
        rightBackground = 'default'
    ) {
        const raw = Number(percent);
        const p = clamp(Number.isFinite(raw) ? raw : 0, 0, 100);

        const viewBoxW = 264, viewBoxH = 129;
        const outer = { x: 20, y: 24, w: 200, h: 80, rx: 18 };
        const inner = { x: 24, y: 28, h: 72, rx: 12 };
        const maxInnerWidth = 192;

        const fillW = interpolatedWidth(p);
        const fillColor = getFillColor(p, strongColors, colorScheme);

        const rightCustom = parseRightBackground(rightBackground);
        const rightStartX = inner.x + fillW;
        const rightWidth = maxInnerWidth - fillW;

        const nums = (fillColor.match(/-?\d+(\.\d+)?/g) || []).map(Number);
        const [hVal = 0, sVal = 0, lVal = 50] = nums;
        const [r, g, b] = hslToRgb(hVal, sVal, lVal);
        const lum = luminance(r, g, b);
        const textFill = lum &gt; 0.55 ? '#000' : '#fff';
        const outlineColor = (textFill === '#fff') ? 'rgba(0,0,0,0.85)' : 'rgba(255,255,255,0.95)';

        const formattedValue = Number(p).toFixed(decimalPlaces);
        const formattedTrimmed = decimalPlaces === 0
            ? String(Math.round(Number(formattedValue)))
            : formattedValue;

        const displayText = customLabel ?? `${formattedTrimmed}${labelSuffix}`;

        const fontSize = Math.max(12, Math.round(inner.h * 0.33 * 2.25 * 1.10));
        const textCenterX = inner.x + maxInnerWidth / 2;
        const textCenterY = inner.y + inner.h / 2;
        const TEXT_DY_EM = 0.35;

        const contact = { x: 224, y: 46, w: 20, h: 36 };
        const contactCenterY = contact.y + contact.h / 2;

        const boltViewBox = { w: 102.7, h: 186.8 };
        const boltTargetH = outer.h * 1.7;
        const boltScale = boltTargetH / boltViewBox.h;
        const boltOffsetY = contactCenterY + 26;

        const clampedBoltPos = clamp(boltPos, 0, 100);

        const boltMinX = -37.0;
        const boltMaxX = 185.0;

        const boltX = boltMinX + (boltMaxX - boltMinX) * (clampedBoltPos / 100);

        const boltTransform = `
    translate(${boltX}, ${boltOffsetY})
    scale(${boltScale})
    translate(${-boltViewBox.w / 2}, ${-boltViewBox.h / 2})
  `.trim();

        const id = uid('b');

        const boltAnimation = blinkBolt ? `
    &lt;style&gt;
      @keyframes blinkBolt-${id} {
        0%, 100% { opacity: 1; }
        50% { opacity: 0.6; }
      }
      .blinking-bolt-${id} {
        animation: blinkBolt-${id} 1.8s ease-in-out infinite;
      }
    &lt;/style&gt;` : '';

        const boltClass = blinkBolt ? `blinking-bolt-${id}` : '';

        const [boltColorLight, boltColorDark] = getBoltGradientFromScheme(strongColors, boltColorScheme);

        const dynamicLetterSpacing = getDynamicLetterSpacing(displayText);
        const letterSpacingAttr = dynamicLetterSpacing ? `letter-spacing="${dynamicLetterSpacing}"` : '';

        return `
    &lt;svg xmlns="http://www.w3.org/2000/svg"
         viewBox="0 0 ${viewBoxW} ${viewBoxH}"
         width="100%" height="100%"
         preserveAspectRatio="xMidYMid meet"&gt;
 
      ${boltAnimation}
 
      &lt;defs&gt;
        &lt;linearGradient id="glass-${id}" x1="0" y1="0" x2="0" y2="1"&gt;
          &lt;stop offset="0%" stop-color="#ffffff" stop-opacity="0.80"/&gt;
          &lt;stop offset="100%" stop-color="#ffffff" stop-opacity="0.10"/&gt;
        &lt;/linearGradient&gt;
 
        &lt;linearGradient id="diagGlass-${id}" x1="0" y1="0" x2="1" y2="1"&gt;
          &lt;stop offset="0%" stop-color="#ffffff" stop-opacity="0.75"/&gt;
          &lt;stop offset="45%" stop-color="#ffffff" stop-opacity="0.22"/&gt;
          &lt;stop offset="100%" stop-color="#ffffff" stop-opacity="0.03"/&gt;
        &lt;/linearGradient&gt;
 
        &lt;pattern id="stripes-${id}" width="8" height="8" patternUnits="userSpaceOnUse"&gt;
          &lt;rect width="8" height="8" fill="transparent"/&gt;
          &lt;path d="M-1,6 l8,-6 M-1,10 l8,-6"
                stroke="#fff" stroke-opacity="0.08" stroke-width="1"/&gt;
        &lt;/pattern&gt;
 
        &lt;clipPath id="clip-fill-${id}"&gt;
          &lt;rect x="${inner.x}" y="${inner.y}" width="${maxInnerWidth}"
                height="${inner.h}" rx="${inner.rx}" ry="${inner.rx}" /&gt;
        &lt;/clipPath&gt;
 
        &lt;linearGradient id="boltGradient-${id}" x1="0" y1="0" x2="0" y2="1"&gt;
          &lt;stop offset="0%" stop-color="${boltColorLight}"/&gt;
          &lt;stop offset="100%" stop-color="${boltColorDark}"/&gt;
        &lt;/linearGradient&gt;
 
        &lt;symbol id="boltSymbol-${id}" viewBox="0 0 102.7 186.8"&gt;
          &lt;path fill="url(#boltGradient-${id})" stroke="#000" stroke-width="6" stroke-linejoin="round"
                d="m34.8 2-32 96h32l-16 80 80-112h-48l32-64h-48z"/&gt;
        &lt;/symbol&gt;
      &lt;/defs&gt;
 
      &lt;!-- ÄUSSERER RAHMEN --&gt;
      &lt;rect x="${outer.x}" y="${outer.y}" width="${outer.w}" height="${outer.h}"
            rx="${outer.rx}" fill="#222" stroke="#ddd" stroke-width="4"/&gt;
 
      &lt;!-- FÜLLBEREICH LINKS (universeller Fix) --&gt;
      ${fillW &gt;= maxInnerWidth
                ? `&lt;rect x="${inner.x}" y="${inner.y}" width="${maxInnerWidth}" height="${inner.h}"
                   rx="${inner.rx}" ry="${inner.rx}" fill="${fillColor}"/&gt;`
                : (
                    fillW &gt; 0
                        ? `&lt;path d="
                     M ${inner.x + inner.rx} ${inner.y}
                     L ${inner.x + fillW} ${inner.y}
                     L ${inner.x + fillW} ${inner.y + inner.h}
                     L ${inner.x + inner.rx} ${inner.y + inner.h}
                     A ${inner.rx} ${inner.rx} 0 0 1 ${inner.x} ${inner.y + inner.h - inner.rx}
                     L ${inner.x} ${inner.y + inner.rx}
                     A ${inner.rx} ${inner.rx} 0 0 1 ${inner.x + inner.rx} ${inner.y}
                     Z"
                   fill="${fillColor}"/&gt;`
                        : ""
                )
            }
 
      &lt;!-- RECHTE HINTERGRUNDHÄLFTE --&gt;
      ${rightBackground === "default" || fillW &gt;= maxInnerWidth
                ? ""
                : (rightWidth &gt; 0
                    ? `&lt;path d="M ${rightStartX} ${inner.y}
                         L ${rightStartX + rightWidth - inner.rx} ${inner.y}
                         A ${inner.rx} ${inner.rx} 0 0 1 ${rightStartX + rightWidth} ${inner.y + inner.rx}
                         L ${rightStartX + rightWidth} ${inner.y + inner.h - inner.rx}
                         A ${inner.rx} ${inner.rx} 0 0 1 ${rightStartX + rightWidth - inner.rx} ${inner.y + inner.h}
                         L ${rightStartX} ${inner.y + inner.h}
                         Z"
                     fill="${rightCustom}"/&gt;`
                    : "")
            }
 
      &lt;!-- GLAS UND TEXTURIERUNG --&gt;
      &lt;g clip-path="url(#clip-fill-${id})"&gt;
        &lt;rect x="${inner.x}" y="${inner.y}" width="${fillW}" height="${inner.h}"
              fill="url(#stripes-${id})" opacity="0.95"/&gt;
        &lt;rect x="${inner.x}" y="${inner.y}" width="${fillW}" height="${inner.h}"
              fill="url(#glass-${id})" opacity="0.25"/&gt;
      &lt;/g&gt;
 
      &lt;!-- DIAGONALER GLASEFFEKT --&gt;
      &lt;rect x="${outer.x}" y="${outer.y}" width="${outer.w}" height="${outer.h}"
            rx="${outer.rx}" fill="url(#diagGlass-${id})" opacity="0.9"/&gt;
 
      &lt;!-- KONTAKT --&gt;
      &lt;rect x="224" y="46" width="20" height="36" rx="6" fill="#ccc" stroke="#888" stroke-width="2"/&gt;
 
      &lt;!-- BLITZ --&gt;
      ${showBolt ? `&lt;use href="#boltSymbol-${id}" class="${boltClass}" transform="${boltTransform}"/&gt;` : ""}
 
      &lt;!-- TEXT --&gt;
      ${showPercent
                ? `&lt;g transform="translate(${textCenterX}, ${textCenterY})"&gt;
              &lt;text text-anchor="middle"
                    font-family="Arial, Helvetica, sans-serif"
                    font-size="${fontSize}"
                    font-weight="700"
                    fill="${textFill}"
                    stroke="${outlineColor}"
                    stroke-width="${Math.max(2, Math.round(fontSize * 0.15))}"
                    paint-order="stroke"
                    dy="${TEXT_DY_EM}em"
                    ${letterSpacingAttr}&gt;
                ${displayText}
              &lt;/text&gt;
            &lt;/g&gt;`
                : ""
            }
 
    &lt;/svg&gt;
  `.trim();
    }

    switch (dp.id.split('.')[2]) {
        case 'Flur':
            if (getState(PF).val == true) {
                setState(Laden, true)
            } else {
                setState(Laden, false)
            }
            break;

        case 'Küche':
            if (getState(PK).val == true) {
                setState(Laden, true)
            } else {
                setState(Laden, false)
            }
            break;

        case 'OG':
            if (getState(POG).val == true) {
                setState(Laden, true)
            } else {
                setState(Laden, false)
            }
            break;

        case 'WG':
            if (getState(PWG).val == true) {
                setState(Laden, true)
            } else {
                setState(Laden, false)
            }
            break;

        case 'Büro':
            if (getState(PB).val == true) {
                setState(Laden, true)
            } else {
                setState(Laden, false)
            }
            break;

        case 'WZ':
            if (getState(PWZ).val == true) {
                setState(Laden, true)
            } else {
                setState(Laden, false)
            }
            break;
            ;

    };
    const decimalPlaces = 0; // bitte anpassen
    const labelSuffix = '%'; // bitte anpassen
    const customLabel = null; // bitte anpassen
    const showPercent = true; // bitte anpassen, z.B. Datenpunkt für zentrale Festlegung
    const strongColors = true; // bitte anpassen, z.B. Datenpunkt für zentrale Festlegung
    const colorScheme = 'default'; // bitte anpassen, z.B. Datenpunkt für zentrale Festlegung
    const showBolt = getState(Laden).val; // bitte anpassen, z.B. Datenpunkt für zentrale Festlegung
    //const showBolt = false; // bitte anpassen, z.B. Datenpunkt für zentrale Festlegung
    const boltPos = 0; // bitte anpassen, z.B. Datenpunkt für zentrale Festlegung
    const blinkBolt = true; // bitte anpassen
    const boltColorScheme = 'default'; // bitte anpassen, z.B. Datenpunkt für zentrale Festlegung
    const rightBackground = 'default'; // bitte anpassen, z.B. Datenpunkt für zentrale Festlegung

    //Funktionsaufruf mit Speicherung der SVG in einen Datenpunkt

    switch (dp.id.split('.')[2]) {
        case 'Flur':
            dValue = getState(LevelF).val;
            setState(ZielF, generateBatterySvg(dValue, decimalPlaces, labelSuffix, customLabel, showPercent, strongColors, colorScheme, showBolt, boltPos, blinkBolt, boltColorScheme, rightBackground), true);
            break;

        case 'Küche':
            dValue = getState(LevelK).val;
            setState(ZielK, generateBatterySvg(dValue, decimalPlaces, labelSuffix, customLabel, showPercent, strongColors, colorScheme, showBolt, boltPos, blinkBolt, boltColorScheme, rightBackground), true);
            break;

        case 'OG':
            dValue = getState(LevelOG).val;
            setState(ZielOG, generateBatterySvg(dValue, decimalPlaces, labelSuffix, customLabel, showPercent, strongColors, colorScheme, showBolt, boltPos, blinkBolt, boltColorScheme, rightBackground), true);
            break;

        case 'WG':
            dValue = getState(LevelWG).val;
            setState(ZielWG, generateBatterySvg(dValue, decimalPlaces, labelSuffix, customLabel, showPercent, strongColors, colorScheme, showBolt, boltPos, blinkBolt, boltColorScheme, rightBackground), true);
            break;

        case 'Büro':
            dValue = getState(LevelB).val;
            setState(ZielB, generateBatterySvg(dValue, decimalPlaces, labelSuffix, customLabel, showPercent, strongColors, colorScheme, showBolt, boltPos, blinkBolt, boltColorScheme, rightBackground), true);
            break;

        case 'WZ':
            dValue = getState(LevelWZ).val;
            setState(ZielWZ, generateBatterySvg(dValue, decimalPlaces, labelSuffix, customLabel, showPercent, strongColors, colorScheme, showBolt, boltPos, blinkBolt, boltColorScheme, rightBackground), true);
            break;
            ;

    };
    log((dp.id.split('.')[2]) + " " + dp.state.val + " %")
});
</code></pre>
<p dir="auto"><img src="/assets/uploads/files/1766002432655-batt.png" alt="BATT.png" class=" img-fluid img-markdown" /><br />
Nochmal Danke für das Script</p>
]]></description><link>https://forum.iobroker.net/post/1314801</link><guid isPermaLink="true">https://forum.iobroker.net/post/1314801</guid><dc:creator><![CDATA[michihorn]]></dc:creator><pubDate>Wed, 17 Dec 2025 20:15:18 GMT</pubDate></item><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Wed, 17 Dec 2025 16:04:01 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/michihorn" aria-label="Profile: michihorn">@<bdi>michihorn</bdi></a> also das mit dem Ladeblitz (an/aus) musst du selber lösen. Sprich anhand der Werte ermitteln (wird z.B. größer als der verherige Wert) oder du hast einen Datenpunkt der dir sagt, es wird geladen. Also das Skript erkennt es selber nicht. Du übergibst mit der Variable <strong>showBolt</strong> nur ein false oder true. Die Position legst du mit <strong>boltPos</strong> fest. Links ist 0 und ganz rechts ist 100, jeder andere Wert dazwischen eben eine Position zwischen linker und rechter Kante.</p>
<p dir="auto">Ro75.</p>
]]></description><link>https://forum.iobroker.net/post/1314764</link><guid isPermaLink="true">https://forum.iobroker.net/post/1314764</guid><dc:creator><![CDATA[Ro75]]></dc:creator><pubDate>Wed, 17 Dec 2025 16:04:01 GMT</pubDate></item><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Wed, 17 Dec 2025 11:22:36 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/ro75" aria-label="Profile: Ro75">@<bdi>Ro75</bdi></a> sagte in <a href="/post/1314581">Skript zur dynamischen Generierung Batterie/Akku Symbol</a>:</p>
<blockquote>
<p dir="auto">Dann fehlt dir wohl code. Du musst dir schon post #1 komplett durch arbeiten. Es reicht nicht nur der Funktionsaufruf. Die Funktion selbst und weiterer Code gehören dazu.</p>
</blockquote>
<p dir="auto">Danke ich habe es hinbekommen, wie machst du das mit dem Bolt? Wie erkennt dein Script das gerade geladen wird? Und wie kann ich den Bolt weiter nach Rechts versetzen bis schon bei Pos 120. Gruß Michael</p>
]]></description><link>https://forum.iobroker.net/post/1314670</link><guid isPermaLink="true">https://forum.iobroker.net/post/1314670</guid><dc:creator><![CDATA[michihorn]]></dc:creator><pubDate>Wed, 17 Dec 2025 11:22:36 GMT</pubDate></item><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Tue, 16 Dec 2025 20:35:07 GMT]]></title><description><![CDATA[<p dir="auto">Dann fehlt dir wohl code. Du musst dir schon post #1 komplett durch arbeiten. Es reicht nicht nur der Funktionsaufruf. Die Funktion selbst und weiterer Code gehören dazu.</p>
<p dir="auto">Ro75</p>
]]></description><link>https://forum.iobroker.net/post/1314581</link><guid isPermaLink="true">https://forum.iobroker.net/post/1314581</guid><dc:creator><![CDATA[Ro75]]></dc:creator><pubDate>Tue, 16 Dec 2025 20:35:07 GMT</pubDate></item><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Tue, 16 Dec 2025 20:30:11 GMT]]></title><description><![CDATA[<p dir="auto">javascript.0	21:29:12.906	error	<br />
Error: ReferenceError: generateBatterySvg is not defined<br />
javascript.0	21:29:12.906	error	<br />
at script.js.Trash.Akkuscript:18:10<br />
javascript.0	21:29:12.906	error	<br />
at script.js.Trash.Akkuscript:24:3<br />
javascript.0	21:29:12.906	error	<br />
at Script.runInContext (node:vm:149:12)</p>
]]></description><link>https://forum.iobroker.net/post/1314579</link><guid isPermaLink="true">https://forum.iobroker.net/post/1314579</guid><dc:creator><![CDATA[michihorn]]></dc:creator><pubDate>Tue, 16 Dec 2025 20:30:11 GMT</pubDate></item><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Tue, 16 Dec 2025 20:13:01 GMT]]></title><description><![CDATA[<p dir="auto">Ist das der gesamte code? Was für ein Fehler kommt denn?</p>
<p dir="auto">Ro75</p>
]]></description><link>https://forum.iobroker.net/post/1314577</link><guid isPermaLink="true">https://forum.iobroker.net/post/1314577</guid><dc:creator><![CDATA[Ro75]]></dc:creator><pubDate>Tue, 16 Dec 2025 20:13:01 GMT</pubDate></item><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Tue, 16 Dec 2025 19:42:55 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/ro75" aria-label="Profile: Ro75">@<bdi>Ro75</bdi></a> sagte in <a href="/post/1307599">Skript zur dynamischen Generierung Batterie/Akku Symbol</a>:</p>
<blockquote>
<p dir="auto">generateBatterySvg</p>
</blockquote>
<p dir="auto">Hallöchen<br />
bei moppert Aufruf der Aufruf generateBatterySvg</p>
<p dir="auto">setState(ZielDPHW, generateBatterySvg(getState(dValue).val, decimalPlaces, labelSuffix, customLabel, showPercent, strongColors, colorScheme, showBolt, boltPos, blinkBolt), true);</p>
<p dir="auto">Hab ich was falsch gemacht?<br />
Gruß<br />
Michael</p>
]]></description><link>https://forum.iobroker.net/post/1314574</link><guid isPermaLink="true">https://forum.iobroker.net/post/1314574</guid><dc:creator><![CDATA[michihorn]]></dc:creator><pubDate>Tue, 16 Dec 2025 19:42:55 GMT</pubDate></item><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Sat, 22 Nov 2025 13:29:19 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/sigi234" aria-label="Profile: sigi234">@<bdi>sigi234</bdi></a> ja du hast recht. Das Skript war schon korrekt, nur oben stand die 17 statt 19. Ist korrigiert. Danke.</p>
<p dir="auto">Ro75.</p>
]]></description><link>https://forum.iobroker.net/post/1310069</link><guid isPermaLink="true">https://forum.iobroker.net/post/1310069</guid><dc:creator><![CDATA[Ro75]]></dc:creator><pubDate>Sat, 22 Nov 2025 13:29:19 GMT</pubDate></item><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Sat, 22 Nov 2025 13:28:04 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/ro75" aria-label="Profile: Ro75">@<bdi>Ro75</bdi></a> sagte in <a href="/post/1310066">Skript zur dynamischen Generierung Batterie/Akku Symbol</a>:</p>
<blockquote>
<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/sigi234" aria-label="Profile: sigi234">@<bdi>sigi234</bdi></a> was meinst du damit? Das Skript 1.0.19 ist in Post 1.</p>
<p dir="auto">Ro75.</p>
</blockquote>
<p dir="auto">Jetzt schon, vorher stand da 1.0.17</p>
]]></description><link>https://forum.iobroker.net/post/1310068</link><guid isPermaLink="true">https://forum.iobroker.net/post/1310068</guid><dc:creator><![CDATA[sigi234]]></dc:creator><pubDate>Sat, 22 Nov 2025 13:28:04 GMT</pubDate></item><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Sat, 22 Nov 2025 13:15:52 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/sigi234" aria-label="Profile: sigi234">@<bdi>sigi234</bdi></a> was meinst du damit? Das Skript 1.0.19 ist in Post 1.</p>
<p dir="auto">Ro75.</p>
]]></description><link>https://forum.iobroker.net/post/1310066</link><guid isPermaLink="true">https://forum.iobroker.net/post/1310066</guid><dc:creator><![CDATA[Ro75]]></dc:creator><pubDate>Sat, 22 Nov 2025 13:15:52 GMT</pubDate></item><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Sat, 22 Nov 2025 12:53:40 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/ro75" aria-label="Profile: Ro75">@<bdi>Ro75</bdi></a> sagte in <a href="/post/1309998">Skript zur dynamischen Generierung Batterie/Akku Symbol</a>:</p>
<blockquote>
<p dir="auto">1.0.19:</p>
</blockquote>
<p dir="auto">Wo ist der?</p>
]]></description><link>https://forum.iobroker.net/post/1310059</link><guid isPermaLink="true">https://forum.iobroker.net/post/1310059</guid><dc:creator><![CDATA[sigi234]]></dc:creator><pubDate>Sat, 22 Nov 2025 12:53:40 GMT</pubDate></item><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Sat, 22 Nov 2025 09:50:50 GMT]]></title><description><![CDATA[<p dir="auto">1.0.19: Der Paramter <strong>colorScheme</strong> akzeptiert jetzt nicht nur 'default' und ein Farbschema aus der Liste. Jetzt kann jeder beliebige HEX, RGB oder RGBA Wert Verwendung finden. Code, Archiv und Doku angepasst in Post 1.</p>
<p dir="auto">Ro75.</p>
]]></description><link>https://forum.iobroker.net/post/1309998</link><guid isPermaLink="true">https://forum.iobroker.net/post/1309998</guid><dc:creator><![CDATA[Ro75]]></dc:creator><pubDate>Sat, 22 Nov 2025 09:50:50 GMT</pubDate></item><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Fri, 21 Nov 2025 20:23:54 GMT]]></title><description><![CDATA[<p dir="auto">Ich habe das Skript aktualisiert. Neuer Stand: 1.0.17. Aktueller Code, Beispiel, Archiv, Code aus meiner Umgebung und Parameterbeschreibung &gt;&gt; ALLES in Post 1.</p>
<p dir="auto">Ro75.</p>
]]></description><link>https://forum.iobroker.net/post/1309924</link><guid isPermaLink="true">https://forum.iobroker.net/post/1309924</guid><dc:creator><![CDATA[Ro75]]></dc:creator><pubDate>Fri, 21 Nov 2025 20:23:54 GMT</pubDate></item><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Sat, 15 Nov 2025 17:00:21 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/ro75" aria-label="Profile: ro75">@<bdi>ro75</bdi></a> sagte in <a href="/post/1308119">Skript zur dynamischen Generierung Batterie/Akku Symbol</a>:</p>
<blockquote>
<p dir="auto">wenn ich dann den korrigierten und erweiterten Code einstelle, passt du dann dein Beispiel an?</p>
</blockquote>
<p dir="auto">Dafür muss sich auch jemand interessieren.<br />
Für mich reicht der aktuelle usecase erstmal aus.</p>
]]></description><link>https://forum.iobroker.net/post/1308468</link><guid isPermaLink="true">https://forum.iobroker.net/post/1308468</guid><dc:creator><![CDATA[OliverIO]]></dc:creator><pubDate>Sat, 15 Nov 2025 17:00:21 GMT</pubDate></item><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Thu, 13 Nov 2025 21:26:23 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/ro75" aria-label="Profile: ro75">@<bdi>ro75</bdi></a> Jetzt funktioniert es auch bei mir wie erwartet (über Datenpunkt, externe Datei und Aufruf im Browser). Danke für die Idee, die Umsetzung und natürlich für die Zeit.</p>
]]></description><link>https://forum.iobroker.net/post/1308168</link><guid isPermaLink="true">https://forum.iobroker.net/post/1308168</guid><dc:creator><![CDATA[Rene55]]></dc:creator><pubDate>Thu, 13 Nov 2025 21:26:23 GMT</pubDate></item><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Thu, 13 Nov 2025 21:17:52 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/ro75" aria-label="Profile: ro75">@<bdi>ro75</bdi></a><br />
Kurzes feedback:<br />
Die neue Version (1.0.8) funktioniert bei mir top. <img src="https://forum.iobroker.net/assets/plugins/nodebb-plugin-emoji/emoji/android/1f44d.png?v=ba16ebd4856" class="not-responsive emoji emoji-android emoji--+1" style="height:23px;width:auto;vertical-align:middle" title=":+1:" alt="👍" /><br />
Export in Datei klappt jetzt einwandfrei.<br />
Vielen Dank.</p>
]]></description><link>https://forum.iobroker.net/post/1308166</link><guid isPermaLink="true">https://forum.iobroker.net/post/1308166</guid><dc:creator><![CDATA[wolfi913]]></dc:creator><pubDate>Thu, 13 Nov 2025 21:17:52 GMT</pubDate></item><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Thu, 13 Nov 2025 20:53:15 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/ro75" aria-label="Profile: ro75">@<bdi>ro75</bdi></a> sagte in <a href="/post/1307599">Skript zur dynamischen Generierung Batterie/Akku Symbol</a>:</p>
<blockquote>
<p dir="auto">Sollte aber auch mit VIS 2 oder anderen Modulen laufen.</p>
</blockquote>
<p dir="auto">Ja, aber für VIS-2 gibt es ein eigenes Widget.</p>
<p dir="auto">Vis 2 - Messgeräte - Batterie</p>
<p dir="auto">Für VIS1 aber ausgezeichnet!</p>
]]></description><link>https://forum.iobroker.net/post/1308159</link><guid isPermaLink="true">https://forum.iobroker.net/post/1308159</guid><dc:creator><![CDATA[sigi234]]></dc:creator><pubDate>Thu, 13 Nov 2025 20:53:15 GMT</pubDate></item><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Thu, 13 Nov 2025 17:15:13 GMT]]></title><description><![CDATA[<p dir="auto">Version 1.0.8 mit Fehlerkorrektur ist raus. Die SVG-Codes können nun ohne Probleme in eine Datei gepackt werden. Weiterer Parameter für Farbschema Ladesymbol.</p>
<p dir="auto"><img src="/assets/uploads/files/1763053856756-0c23d90d-60af-4125-a042-b0cd70863eed-image.png" alt="0c23d90d-60af-4125-a042-b0cd70863eed-image.png" class=" img-fluid img-markdown" /></p>
<p dir="auto"><img src="/assets/uploads/files/1763054103880-test3.svg" alt="test3.svg" class=" img-fluid img-markdown" /> <img src="/assets/uploads/files/1763054103873-test2.svg" alt="test2.svg" class=" img-fluid img-markdown" /> <img src="/assets/uploads/files/1763054103853-test1.svg" alt="test1.svg" class=" img-fluid img-markdown" /></p>
<p dir="auto">Ro75.</p>
]]></description><link>https://forum.iobroker.net/post/1308129</link><guid isPermaLink="true">https://forum.iobroker.net/post/1308129</guid><dc:creator><![CDATA[Ro75]]></dc:creator><pubDate>Thu, 13 Nov 2025 17:15:13 GMT</pubDate></item><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Thu, 13 Nov 2025 16:33:13 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/oliverio" aria-label="Profile: OliverIO">@<bdi>OliverIO</bdi></a> wenn ich dann den korrigierten und erweiterten Code einstelle, passt du dann dein Beispiel an?</p>
<p dir="auto">Ro75.</p>
]]></description><link>https://forum.iobroker.net/post/1308119</link><guid isPermaLink="true">https://forum.iobroker.net/post/1308119</guid><dc:creator><![CDATA[Ro75]]></dc:creator><pubDate>Thu, 13 Nov 2025 16:33:13 GMT</pubDate></item><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Thu, 13 Nov 2025 16:25:38 GMT]]></title><description><![CDATA[<p dir="auto">Ich arbeite gerade an einem Fix, der das Problem mit den Dateien löst. Weiterhin wird es einen weiteren Parameter geben, welcher das Farbschema vom Ladeblitz beeinflusst.</p>
<p dir="auto">Ro75.</p>
]]></description><link>https://forum.iobroker.net/post/1308116</link><guid isPermaLink="true">https://forum.iobroker.net/post/1308116</guid><dc:creator><![CDATA[Ro75]]></dc:creator><pubDate>Thu, 13 Nov 2025 16:25:38 GMT</pubDate></item><item><title><![CDATA[Reply to Skript zur dynamischen Generierung Batterie&#x2F;Akku Symbol on Thu, 13 Nov 2025 10:34:10 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/rene55" aria-label="Profile: rene55">@<bdi>rene55</bdi></a> <a class="plugin-mentions-user plugin-mentions-a" href="/user/ro75" aria-label="Profile: Ro75">@<bdi>Ro75</bdi></a><br />
Nur zur Info falls es weiterhilft.<br />
Mit der Version 1.0.3 funktioniert es auch (zumindest bei mir) tadellos mit dem Speichern in eine Datei (mit und ohne Blitz) für die Nutzung im Energiefluss-erweitert-Adapter. Klappt perfekt. Mit Version 1.0.6 bekomme ich da leider auch die gleichen Probleme wenn ich's in eine Datei wegspeichere und dann über den Browser aufrufen möchte. Mir persönlich reichen aber die Einstellmöglichkeiten der "alten" Version voll und ganz.</p>
]]></description><link>https://forum.iobroker.net/post/1308063</link><guid isPermaLink="true">https://forum.iobroker.net/post/1308063</guid><dc:creator><![CDATA[wolfi913]]></dc:creator><pubDate>Thu, 13 Nov 2025 10:34:10 GMT</pubDate></item></channel></rss>