Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Leibinger015/3d981c72b91fb498ec2cf17b12a3b56c to your computer and use it in GitHub Desktop.
Save Leibinger015/3d981c72b91fb498ec2cf17b12a3b56c to your computer and use it in GitHub Desktop.
My Battery Dashboard for - almost - all iPhone models
// My Battery Dashboard for - almost - all iPhone models
//
// An English-translated Scriptable-App-Script named "BatteryFitness++" – the battery analytics (HTML/WebView variant).
//
// Input 'Analytics' file passed via Share Sheet as a converted (.txt = suffix) file
//
// Script = www.anb030.de (28092025) Make with love.
//
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>Something went wrong.<br>No "Analytics file" was provided.</h1>
<p>ⓘ<br>Please first open the "Analytics" file from the <strong>Settings</strong> app. There, select the options in order: <strong>Privacy & Security → Analytics & Improvements *¹ → Analytics Data</strong>. Then open the desired "Analytics" file, share it via the Share Sheet (Share menu) and save it to Apple’s "Files" app. <br>
<br><em><strong>*¹ Note:</strong> Please enable the switch <strong>Share iPhone & Watch Analytics</strong> at least 24 hours in advance so that the "Analytics..." file can be generated for extraction.</em><br>
<br>ⓘ <br>Locate the file in the “Files” app and change its file extension (suffix) <strong>*²</strong> by long-pressing → context menu option “Rename” from “.synced” to “.txt”. Then share it again via the Share Sheet (Share menu) with the Scriptable script <strong>*³</strong> “BatteryFitness++”. The Scriptable app should then open automatically and display all relevant battery data.<br><br>
<em><strong>*² Note: </strong>In the "Files" app you must enable a small setting: Tap the three "•••" dots at the top right → Scroll down to "View Options" → Scroll further down to "Show All File Extensions" and enable the switch.</em><br><br>
<em><strong>*³ Note: </strong>In the "Scriptable" app you must enable a small setting: Open the created script card named “BatteryFitness++” again via the three dots "•••" → Tap the menu icon “≒” above the keyboard → Then tap the menu item “Share Sheet Inputs” → Check all boxes to enable proper sharing for the Share menu.</em></p>
</body></html>`;
let wvErr = new WebView();
await wvErr.loadHTML(err);
await wvErr.present();
return;
}
// --- Mapping table (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"
};
// --- Helper functions --------------------------------------------------
function extractValueNum(key) {
let regex = new RegExp(`"${key}":(\\d+)`);
let match = text.match(regex);
return match ? Number(match[1]) : null;
}
function extractDevice() {
// searches for device":"iPhoneXX,X or 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"; // green
if (pct >= 85) return "#f1c40f"; // yellow
if (pct >= 80) return "#f39c12"; // orange
return "#c0392b"; // red
}
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];
});
}
// --- Extract values ---------------------------------------------------
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");
// --- Calculations -----------------------------------------------------
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 = "unknown";
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 output ------------------------------------------------------
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>Battery Analysis<br>Status from file: ${escapeHtml(updateDateStr)}</h1>
<div class="sub">(Summary)</div>
</div>
</div>
<div class="row" style="margin-top:14px;">
<div class="block">
<div class="deviceLabel">Device Model</div>
<div class="deviceName">${escapeHtml(deviceName)}</div>
<div class="label">Charge Cycles (total)</div>
<div class="value">${escapeHtml(safe(cycleTotal, "N/A"))}</div>
<div class="label" style="margin-top:8px;">Charge Cycles since Calibration</div>
<div class="value">${escapeHtml(safe(cycleSinceQmax, "N/A"))}</div>
</div>
<div class="block">
<div class="label">Battery Health <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;">Battery Capacity according to manufacturer: <br>• Design: ${escapeHtml(designCapacityStr)} <br>• Current: ${escapeHtml(currentCapacityStr)}</div>
</div>
<div class="block">
<div class="label">Operating Time since First Use (Battery Age)</div>
<div class="value">${totalDays !== null ? totalDays + " days" : "N/A"}</div>
<div class="small" style="font-size:13px; font-weight:400;">Approximate first use: ${yearsStr !== "N/A" ? yearsStr + " years ago" : "N/A"} (${escapeHtml(firstUseStr)})</div>
</div>
</div>
<div style="margin-top:14px;">
<div class="label">Temperature History since First Use</div>
<table>
<tr><td class="muted">Lowest Recorded</td><td class="temp">${escapeHtml(minTemp)} °C</td></tr>
<tr><td class="muted">Average</td><td class="temp">${escapeHtml(avgTemp)} °C</td></tr>
<tr><td class="muted">Highest Recorded</td><td class="temp">${escapeHtml(maxTemp)} °C</td></tr>
</table>
</div>
<div class="footer">
UpdateTime: ${escapeHtml(updateDateStr)} • Source: Analytics Data <br>© by www.anb030.de (v20250928)<br> Coded with love ❤️
</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

The battery fitness workaround: How Scriptable reveals the actual battery health of your (old) iPhone. ☑︎ With my script and instructions, you can read the battery data directly on your iPhone without external devices such as a Mac.

Instructions and article (in German): anb030.de

IMG_6478

Briefly explained:


Please first open the "Analytics" file from the Settings app. There, select the options in order: Privacy & Security → Analytics & Improvements *¹ → Analytics Data. Then open the desired "Analytics" file, share it via the Share Sheet (Share menu) and save it to Apple’s "Files" app.

*¹ Note: Please enable the switch Share iPhone & Watch Analytics at least 24 hours in advance so that the "Analytics..." file can be generated for extraction.


Locate the file in the “Files” app and change its file extension (suffix) *² by long-pressing → context menu option “Rename” from “.synced” to “.txt”. Then share it again via the Share Sheet (Share menu) with the Scriptable script *³ “BatteryFitness++”. The Scriptable app should then open automatically and display all relevant battery data.

*² Note: In the "Files" app you must enable a small setting: Tap the three "•••" dots at the top right → Scroll down to "View Options" → Scroll further down to "Show All File Extensions" and enable the switch.

*³ Note: In the "Scriptable" app you must enable a small setting: Open the created script card named “BatteryFitness++” again via the three dots "•••" → Tap the menu icon “≒” above the keyboard → Then tap the menu item “Share Sheet Inputs” → Check all boxes to enable proper sharing for the Share menu.

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