Created
October 2, 2025 17:11
-
-
Save Leibinger015/dfdaf53a3ab67179e480ccf9dba37664 to your computer and use it in GitHub Desktop.
My Battery Dashboard for - almost - all iPhone models
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // An German Scriptable-App-Script named "BatteryFitness++" – the battery analytics (HTML/WebView variant). | |
| // | |
| // Eingabe (Analysedatei per Share Sheet übergabe als umgewandelte .txt-Datei) | |
| // | |
| // Skript = www.anb030.de (28092025) | |
| // | |
| let text = args.plainTexts && args.plainTexts.length > 0 ? args.plainTexts[0] : null; | |
| if (!text) { | |
| let err = ` | |
| <html><body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; padding:20px;"> | |
| <h1 style="color:#c0392b">⚠<br>Da ist wohl etwas schiefgelaufen.<br>Es wurde keine "Analytics-Datei" übergeben.</h1> | |
| <p>ⓘ<br>Bitte zuerst die "Analytics"-Datei aus der <strong>Einstellungen</strong>-App öffnen. Wähle dort nacheinander die Optionen <strong>Datenschutz & Sicherheit → Analyse & Verbesserungen *¹ → Analysedaten</strong>. Öffne anschließend die gewünschte "Analytics"-Datei und teile diese per Share-Sheet (Teilen-Menü) und Speichere die Datei in der "Dateien"-App von Apple. <br> | |
| <br><em><strong>*¹ Hinwris:</strong> Aktivieren Sie bitte 24 Stunden vorher den Schalter bei <strong>iPhone- & Watch-Analyse teilen</strong>, damit die "Analytics..."-Datei zum auslesen erstellt werden kann.</em><br> | |
| <br>ⓘ <br>Suche die Datei in der „Dateien“-App und ändere die Dateiendung (Suffix) <strong>*²</strong> per längerem Fingertipp → Kontext-Menü-Auswahl „Umbenennen“ von „.synced” zu „.txt” um. Teile diese dann wiederum über das Share-Sheet (Teilen-Menü) mit dem Scriptable-Skript <strong>*³</strong> „BatteryFitness++”. Nun sollte sich die Scriptable-App automatisch öffnen und alle relevanten Akku-Daten anzeigen.<br><br> | |
| <em><strong>*² Hinwris: </strong>In der "Dateien"-App muss zwingend eine kleine Einstellung vorgenommen werden: Tippe oben rechts auf die drei "•••" Punkte → Scroll runter zu "Darstellungsoptionen" → Scroll wiederum runter zu "Alle Dateinamensuffixe | |
| einblenden" und Aktiviere den Schalter.</em><br><br> | |
| <em><strong>*³ Hinwris: </strong>In der "Scriptable"-App muss zwingend eine kleine Einstellung vorgenommen werden: Die erstellte Skript-Karte namens „BatteryFitness++“ muss über die drei Punkte "•••" erneut geöffnet werden → Tippe links über der Tastatur auf das Menü-Symbol „≒“ → Anschließend auf den Menüpunkt „Share Sheet Inputs“ → Setze alle Häkchen, um das korrekte Teilen für das Teilen-Menü zu aktivieren.</em></p> | |
| </body></html>`; | |
| let wvErr = new WebView(); | |
| await wvErr.loadHTML(err); | |
| await wvErr.present(); | |
| return; | |
| } | |
| // --- Mapping-Tabelle (Device identifier -> Marketing name) ---------- | |
| const deviceMap = { | |
| "iPhone1,1": "iPhone 1 Gen.", | |
| "iPhone1,2": "iPhone 3G", | |
| "iPhone2,1": "iPhone 3GS", | |
| "iPhone3,1": "iPhone 4 GSM", | |
| "iPhone3,2": "iPhone 4 GSMrevA", | |
| "iPhone3,3": "iPhone 4 CDMA", | |
| "iPhone4,1": "iPhone 4S", | |
| "iPhone5,1": "iPhone 5 GSM", | |
| "iPhone5,2": "iPhone 5 GSM+CDMA", | |
| "iPhone5,3": "iPhone 5c GSM", | |
| "iPhone5,4": "iPhone 5c GSM+CDMA", | |
| "iPhone6,1": "iPhone 5s GSM", | |
| "iPhone6,2": "iPhone 5s GSM+CDMA", | |
| "iPhone7,2": "iPhone 6", | |
| "iPhone7,1": "iPhone 6 Plus", | |
| "iPhone8,1": "iPhone 6s", | |
| "iPhone8,2": "iPhone 6s Plus", | |
| "iPhone8,4": "iPhone SE 1 Gen.", | |
| "iPhone9,1": "iPhone 7 Global", | |
| "iPhone9,2": "iPhone 7 Plus Global", | |
| "iPhone9,3": "iPhone 7 GSM", | |
| "iPhone9,4": "iPhone 7 Plus GSM", | |
| "iPhone10,1": "iPhone 8", | |
| "iPhone10,2": "iPhone 8 Plus", | |
| "iPhone10,3": "iPhone X Global", | |
| "iPhone10,6": "iPhone X GSM", | |
| "iPhone11,2": "iPhone XS", | |
| "iPhone11,4": "iPhone XS Max China", | |
| "iPhone11,6": "iPhone XS Max", | |
| "iPhone11,8": "iPhone XR", | |
| "iPhone12,1": "iPhone 11", | |
| "iPhone12,3": "iPhone 11 Pro", | |
| "iPhone12,5": "iPhone 11 Pro Max", | |
| "iPhone12,8": "iPhone SE 2 Gen.", | |
| "iPhone13,1": "iPhone 12 mini", | |
| "iPhone13,2": "iPhone 12", | |
| "iPhone13,3": "iPhone 12 Pro", | |
| "iPhone13,4": "iPhone 12 Pro Max", | |
| "iPhone14,2": "iPhone 13 Pro", | |
| "iPhone14,3": "iPhone 13 Pro Max", | |
| "iPhone14,4": "iPhone 13 mini", | |
| "iPhone14,5": "iPhone 13", | |
| "iPhone14,6": "iPhone SE 3 Gen.", | |
| "iPhone14,7": "iPhone 14", | |
| "iPhone14,8": "iPhone 14 Plus", | |
| "iPhone15,2": "iPhone 14 Pro", | |
| "iPhone15,3": "iPhone 14 Pro Max", | |
| "iPhone15,4": "iPhone 15", | |
| "iPhone15,5": "iPhone 15 Plus", | |
| "iPhone16,1": "iPhone 15 Pro", | |
| "iPhone16,2": "iPhone 15 Pro Max", | |
| "iPhone17,1": "iPhone 16 Pro", | |
| "iPhone17,2": "iPhone 16 Pro Max", | |
| "iPhone17,3": "iPhone 16", | |
| "iPhone17,4": "iPhone 16 Plus", | |
| "iPhone17,5": "iPhone 16e", | |
| "iPhone18,3": "iPhone 17", | |
| "iPhone18,4": "iPhone 17 Air", | |
| "iPhone18,1": "iPhone 17 Pro", | |
| "iPhone18,2": "iPhone 17 Pro Max" | |
| }; | |
| // --- Hilfsfunktionen -------------------------------------------------- | |
| function extractValueNum(key) { | |
| let regex = new RegExp(`"${key}":(\\d+)`); | |
| let match = text.match(regex); | |
| return match ? Number(match[1]) : null; | |
| } | |
| function extractDevice() { | |
| // sucht device":"iPhoneXX,X oder iPhoneX,X | |
| let regex = /"device":"(iPhone\d+,\d+)"/; | |
| let match = text.match(regex); | |
| return match ? match[1] : null; | |
| } | |
| function formatTemperatureForDisplay(value) { | |
| if (value === null) return "N/A"; | |
| if (value < 100) return value.toFixed(1).replace(".0", ",0"); | |
| return (value / 10).toFixed(1).replace(".", ","); | |
| } | |
| function healthColor(pct) { | |
| if (pct === null) return "#999"; | |
| if (pct >= 90) return "#27ae60"; // grün | |
| if (pct >= 85) return "#f1c40f"; // gelb | |
| if (pct >= 80) return "#f39c12"; // orange | |
| return "#c0392b"; // rot | |
| } | |
| function formatMonthYear(d) { | |
| let m = (d.getMonth() + 1).toString().padStart(2, "0"); | |
| let y = d.getFullYear(); | |
| return `${m}.${y}`; | |
| } | |
| function safe(val, fallback = "N/A") { | |
| return (val === null || val === undefined) ? fallback : String(val); | |
| } | |
| function escapeHtml(s) { | |
| if (s === null || s === undefined) return ""; | |
| return String(s).replace(/[&<>"']/g, function (m) { | |
| return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[m]; | |
| }); | |
| } | |
| // --- Werte auslesen --------------------------------------------------- | |
| let deviceCode = extractDevice(); | |
| let deviceName = deviceCode && deviceMap[deviceCode] ? deviceMap[deviceCode] : ""; | |
| let cycleTotal = extractValueNum("last_value_CycleCount"); | |
| let cycleSinceQmax = extractValueNum("last_value_CycleCountLastQmax"); | |
| let healthPercent = extractValueNum("last_value_MaximumCapacityPercent"); | |
| let currentCapacity = extractValueNum("last_value_AppleRawMaxCapacity"); | |
| let designCapacity = extractValueNum("last_value_MaximumFCC"); | |
| let totalOperatingTime = extractValueNum("last_value_TotalOperatingTime"); | |
| let updateTime = extractValueNum("last_value_UpdateTime"); | |
| let minTempRaw = extractValueNum("last_value_MinimumTemperature"); | |
| let avgTempRaw = extractValueNum("last_value_AverageTemperature"); | |
| let maxTempRaw = extractValueNum("last_value_MaximumTemperature"); | |
| // --- Berechnungen ----------------------------------------------------- | |
| let updateDate = updateTime ? new Date(updateTime * 1000) : new Date(); | |
| let updateDateStr = updateDate.toLocaleDateString(undefined, { day: '2-digit', month: '2-digit', year: 'numeric' }); | |
| let totalDays = totalOperatingTime ? Math.round(totalOperatingTime / 24) : null; | |
| const HOURS_PER_YEAR = 365.25 * 24; | |
| let years = totalOperatingTime ? (totalOperatingTime / HOURS_PER_YEAR) : null; | |
| let yearsStr = years !== null ? years.toFixed(1).replace(".", ",") : "N/A"; | |
| let firstUseStr = "unbekannt"; | |
| if (totalOperatingTime !== null && updateTime !== null) { | |
| let firstUseMillis = (updateTime * 1000) - (totalOperatingTime * 3600 * 1000); | |
| let firstUseDate = new Date(firstUseMillis); | |
| firstUseStr = formatMonthYear(firstUseDate); | |
| } | |
| let minTemp = formatTemperatureForDisplay(minTempRaw); | |
| let avgTemp = formatTemperatureForDisplay(avgTempRaw); | |
| let maxTemp = formatTemperatureForDisplay(maxTempRaw); | |
| let currentCapacityStr = currentCapacity !== null ? `${currentCapacity} mAh` : "N/A"; | |
| let designCapacityStr = designCapacity !== null ? `${designCapacity} mAh` : "N/A"; | |
| let healthPercentStr = healthPercent !== null ? `${healthPercent} %` : "N/A"; | |
| let healthBarColor = healthColor(healthPercent); | |
| let healthBarWidth = healthPercent !== null ? Math.max(0, Math.min(100, healthPercent)) : 0; | |
| // --- HTML ------------------------------------------------------------- | |
| let html = ` | |
| <!doctype html> | |
| <html> | |
| <head> | |
| <meta name="viewport" content="width=device-width,initial-scale=1"> | |
| <style> | |
| body { background:#0f1720; color:#e6eef6; margin:0; padding:20px; font-family:-apple-system, BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial; } | |
| .container { max-width:820px; margin:0 auto; } | |
| .card { background:rgba(255,255,255,0.02); border-radius:12px; padding:18px; } | |
| .title { display:flex; align-items:center; gap:12px; } | |
| .logo { width:48px; height:48px; border-radius:10px; background:#111827; display:flex; align-items:center; justify-content:center; font-weight:700; color:#9fb7ff; font-size:18px; } | |
| h1 { margin:0; font-size:18px; } | |
| p.sub { margin:4px 0 12px; color:#9aa4b2; font-size:13px; } | |
| .row { display:flex; gap:16px; flex-wrap:wrap; } | |
| .block { background:rgba(255,255,255,0.03); padding:12px; border-radius:8px; flex:1 1 240px; min-width:200px; } | |
| .label { color:#9aa4b2; font-size:12px; margin-bottom:6px; } | |
| .value { font-size:20px; font-weight:600; color:#eaf2ff; } | |
| .small { font-size:16px; font-weight:600; margin-top:6px; color:#eaf2ff; } | |
| .deviceLabel { color:#9aa4b2; font-size:12px; margin-bottom:4px; } | |
| .deviceName { font-size:22px; font-weight:700; color:#ffffff; margin-bottom:8px; } | |
| .barOuter { background:rgba(255,255,255,0.06); border-radius:999px; height:12px; width:100%; } | |
| .barInner { height:12px; border-radius:999px; width:${healthBarWidth}%; background:${healthBarColor}; } | |
| table { width:100%; margin-top:8px; border-collapse:collapse; } | |
| td { padding:8px 6px; } | |
| .temp { font-weight:700; font-size:16px; } | |
| .muted { color:#9aa4b2; } | |
| .footer { margin-top:14px; color:#9aa4b2; font-size:13px; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="card"> | |
| <div class="title"> | |
| <div class="logo">⚡️</div> | |
| <div> | |
| <h1>Akku-Analyse<br>Status aus Datei: ${escapeHtml(updateDateStr)}</h1> | |
| <div class="sub">(Zusammenfassung)</div> | |
| </div> | |
| </div> | |
| <div class="row" style="margin-top:14px;"> | |
| <div class="block"> | |
| <div class="deviceLabel">Geräte Modell</div> | |
| <div class="deviceName">${escapeHtml(deviceName)}</div> | |
| <div class="label">Ladezyklen (gesamt)</div> | |
| <div class="value">${escapeHtml(safe(cycleTotal, "N/A"))}</div> | |
| <div class="label" style="margin-top:8px;">Ladezyklen seit Kalibrierung</div> | |
| <div class="value">${escapeHtml(safe(cycleSinceQmax, "N/A"))}</div> | |
| </div> | |
| <div class="block"> | |
| <div class="label">Akkugesundheit <br>(≥ 90% 🟩, 85-89% 🟨, | |
| 80-84% 🟧, ≤ 79%🟥)</div> | |
| <div class="value">${escapeHtml(healthPercentStr)}</div> | |
| <div class="barOuter"><div class="barInner"></div></div> | |
| <div class="small" style="font-size:13px; font-weight:400;">Akku-Kapazität laut Hersteller: <br>• Soll: ${escapeHtml(designCapacityStr)} <br>• Ist: ${escapeHtml(currentCapacityStr)}</div> | |
| </div> | |
| <div class="block"> | |
| <div class="label">Betriebszeit seit Ersteinsatz (Alter des Akkus)</div> | |
| <div class="value">${totalDays !== null ? totalDays + " Tage" : "N/A"}</div> | |
| <div class="small" style="font-size:13px; font-weight:400;">Ungefährer Ersteinsatz vor: ${yearsStr !== "N/A" ? yearsStr + " Jahre" : "N/A"} (${escapeHtml(firstUseStr)})</div> | |
| </div> | |
| </div> | |
| <div style="margin-top:14px;"> | |
| <div class="label">Temperaturverlauf seit Ersteinsatz</div> | |
| <table> | |
| <tr><td class="muted">Tiefstwert bis dato</td><td class="temp">${escapeHtml(minTemp)} °C</td></tr> | |
| <tr><td class="muted">Durchschnitt</td><td class="temp">${escapeHtml(avgTemp)} °C</td></tr> | |
| <tr><td class="muted">Höchstwert bis dato</td><td class="temp">${escapeHtml(maxTemp)} °C</td></tr> | |
| </table> | |
| </div> | |
| <div class="footer"> | |
| UpdateTime: ${escapeHtml(updateDateStr)} • Quelle: Analysedaten <br>© by www.anb030.de (v20250928)<br> Programmiert mit Liebe ❤️ | |
| </div> | |
| </div> | |
| </div> | |
| </body> | |
| </html> | |
| `; | |
| let wv = new WebView(); | |
| await wv.loadHTML(html); | |
| await wv.present(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Der Akku-Fitness-Workaround: So enthüllt Scriptable die tatsächliche Akkugesundheit deines (alten) iPhones. ☑︎ Mit meinem Skript und der Anleitung kannst du die Akkudaten direkt am iPhone auslesen ohne externe Geräte wie dem Mac.
Anleitung und Artikel (in Deutsch): anb030.de
Kurz erklärt:
ⓘ
Bitte zuerst die "Analytics"-Datei aus der Einstellungen-App öffnen. Wähle dort nacheinander die Optionen Datenschutz & Sicherheit → Analyse & Verbesserungen *¹ → Analysedaten. Öffne anschließend die gewünschte "Analytics"-Datei und teile diese per Share-Sheet (Teilen-Menü) und Speichere die Datei in der "Dateien"-App von Apple.
*¹ Hinwris: Aktivieren Sie bitte 24 Stunden vorher den Schalter bei iPhone- & Watch-Analyse teilen, damit die "Analytics..."-Datei zum auslesen erstellt werden kann.
ⓘ
Suche die Datei in der „Dateien“-App und ändere die Dateiendung (Suffix) *² per längerem Fingertipp → Kontext-Menü-Auswahl „Umbenennen“ von „.synced” zu „.txt” um. Teile diese dann wiederum über das Share-Sheet (Teilen-Menü) mit dem Scriptable-Skript *³ „BatteryFitness++”. Nun sollte sich die Scriptable-App automatisch öffnen und alle relevanten Akku-Daten anzeigen.
*² Hinwris: In der "Dateien"-App muss zwingend eine kleine Einstellung vorgenommen werden: Tippe oben rechts auf die drei "•••" Punkte → Scroll runter zu "Darstellungsoptionen" → Scroll wiederum runter zu "Alle Dateinamensuffixe einblenden" und Aktiviere den Schalter.
*³ Hinwris: In der "Scriptable"-App muss zwingend eine kleine Einstellung vorgenommen werden: Die erstellte Skript-Karte namens „BatteryFitness++“ muss über die drei Punkte "•••" erneut geöffnet werden → Tippe links über der Tastatur auf das Menü-Symbol „≒“ → Anschließend auf den Menüpunkt „Share Sheet Inputs“ → Setze alle Häkchen, um das korrekte Teilen für das Teilen-Menü zu aktivieren.