Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Leibinger015/dfdaf53a3ab67179e480ccf9dba37664 to your computer and use it in GitHub Desktop.
Save Leibinger015/dfdaf53a3ab67179e480ccf9dba37664 to your computer and use it in GitHub Desktop.
My Battery Dashboard for - almost - all iPhone models
// 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 { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[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();
@Leibinger015
Copy link
Author

Leibinger015 commented Oct 2, 2025

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

IMG_6478

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment