---
title: "ChargeSpeed: live iPhone charging watts, straight from the PMU"
description: "I built a small SwiftUI app that reads my iPhone's charger and battery sensors through private IOKit APIs and shows real-time watts, volts, amps, the negotiated USB-PD profile, and temperatures. Source and an unsigned IPA are on GitHub. It can never ship on the App Store, and here's why."
publishedAt: 2026-09-12T18:00:00.000Z
updatedAt: 2026-09-13T21:00:00.000Z
canonical: https://gregwilson.tech/ios-charging-monitor
coverImage: https://gregwilson.tech/_astro/ios-charging-monitor.CqNZt9UD_ZEropK.jpeg
tags: ["ios", "iphone", "swift", "swiftui", "iokit", "battery", "charging", "private-api"]
---

I have a drawer full of USB-C chargers and cables, plus a few MagSafe and Qi pads, and until recently I had no idea which of them were any good. iOS tells you exactly one thing when you plug in or set the phone down: a lightning bolt. Not the wattage, not what the charger negotiated, not whether the cable is quietly throttling everything to 7 W or the pad is stuck at trickle speed. Just the bolt.

There are apps on the App Store that claim to show charging wattage. They can't read the sensors, because Apple doesn't expose them to third-party apps, so they guess: watch the battery percentage tick up, multiply by the battery's rated capacity, divide by time. That's close enough on a good day, minutes behind at best, and wrong whenever the phone throttles or pauses charging. I wanted the real number.

So I built [ChargeSpeed](https://github.com/gregsramblings/ios-charging-monitor), a small SwiftUI app that reads the phone's actual power-management sensors and shows the number I wanted:

<img src="/images/inline/ios-charging-monitor/chargespeed-screenshot.webp" width="360" height="694" loading="lazy" decoding="async" alt="ChargeSpeed showing 27.86 W from a USB-C charger, 25.92 W into the battery, 14.20 V at 1.96 A, 55% charged, session peak 31.70 W, adapter negotiated 15 V × 3 A, thermal state nominal with battery, charger, and SoC temperatures">

That's 27.86 W coming in over USB-C from a charger that negotiated a 15 V × 3 A profile, with 25.92 W of it making it into the battery at 55 % charge, and a peak of 31.70 W earlier in the session. The rest is heat and the phone doing phone things. Seeing this number exposed a few bad charging setups I'd been using for years.

The source is on GitHub at [gregsramblings/ios-charging-monitor](https://github.com/gregsramblings/ios-charging-monitor) under an MIT license, and there's an unsigned IPA on the [Releases page](https://github.com/gregsramblings/ios-charging-monitor/releases) if you'd rather skip Xcode. Either way it goes onto your own phone under your own Apple ID, because it uses private APIs and will never pass App Store review.

## What it shows

Verified on an iPhone 17 Pro Max running iOS 26:

- **Watts from the charger**, updated every second, with a sparkline. This is USB-C input voltage × current. On MagSafe there's no input current sensor, so the headline becomes watts into the battery.
- **Watts into the battery**, plus battery voltage and current.
- **Adapter name, negotiated USB-PD profile, and every profile** the adapter advertised.
- **Temperatures**: battery, charger junction, and the hottest SoC die, plus the iOS thermal state. A red "Throttling" line appears when it reaches serious or critical, and the battery reading turns amber at 38 °C and red at 42 °C.
- **Percent, charging state, Low Power Mode.**
- **A charging hold** below 100 % (Optimized Battery Charging or a charge limit), inferred from behavior since iOS won't say directly.
- **Peak watts for the current charge**, kept on screen after you unplug and reset at the next plug-in, so you can plug in a charger, walk away, and come back to the answer.

It's just as useful for wireless chargers. Set the phone on a MagSafe or Qi pad and the app shows what that pad negotiated (MagSafe at 15 W versus a Qi pad at 7.5 W, for example) and how many watts are actually reaching the battery, so you can sort out which of your pads charges at the fast rate and which one quietly tops out at trickle speed. It also found the sweet spot on my car's charging tray, which has no magnets to center the phone: slide it around with the live watts on screen until the number peaks, and that's where it goes.

## Where the numbers come from

There is no public API for any of this. The app polls three private sources once a second and merges them.

**The IOKit battery registry.** On macOS this is a treasure chest: cycle count, capacity, amperage, temperature. On a real iPhone the sandbox filters it down to two keys, "battery installed" and "external connected." Everything interesting is gone.

**powerd.** The power-source calls work from a sandboxed app and give you percent, charging state, and Low Power Mode. The adapter-details call is better than I expected: it returns the adapter's name, the negotiated voltage and current, and the full menu of USB-PD profiles the adapter offered. The charge-status call that the Batteries widget uses to show "Charging On Hold" is refused as not privileged, every time.

**The HID sensors.** This is the good part. Apple exposes the PMU and charger sensors as HID services, and a sandboxed app is allowed to create one HID event client and read them. USB-C input voltage and current, current into the battery, battery voltage, MagSafe input voltage, and a handful of temperature sensors are all there by name. Multiply input voltage by input current and you have the headline number. One gotcha: you get exactly one HID client per process. Create a second one and every reading comes back as NaN.

IOKit is a private framework on iOS, so none of this can be linked normally. The app loads it at runtime and looks up the handful of symbols it needs. That works fine for a personal build and is exactly what App Store review rejects.

## What iOS won't give you

I tried, so you don't have to. All blocked on iOS 26:

- Battery health, cycle count, and mAh capacity. The registry is filtered.
- Optimized Battery Charging, charge limit, and Clean Energy Charging settings. The PowerUI service denies sandboxed callers.
- powerd's "Charging On Hold" status and time-to-empty. Privileged, or always zero.
- Discharge current on battery. There is no sensor for it among the 75 HID services on the phone, so there's no honest time-to-empty from power either.

Since powerd won't say "Charging On Hold," the app infers it: plugged in, not charging, between 50 and 99 %, and almost no current into the battery. Plug-in transitions look like a hold for a second or two, so it has to persist for 45 seconds before it counts. When it does, the app records the percentage it held at, which tells you your charge limit even though iOS won't.

## Versus the App Store "charging speed" apps

Because those apps work from the percentage climb, they only ever see the battery side. They can't tell you what the charger negotiated, what's coming in over the cable, or why the two differ. ChargeSpeed reads the sensors directly, and keeps the same percentage-based estimate around as a fallback labeled "% rate" for phones where the sensors don't show up, which also makes it easy to see how the two compare.

## Install it

App Store Review Guideline 2.5.1 says public APIs only, and TestFlight scans for private API use too. So getting it onto your phone, under your own Apple ID, is on you. Two ways.

### Option 1: sideload the prebuilt IPA (no Xcode)

Every tagged version gets an unsigned IPA on the [Releases page](https://github.com/gregsramblings/ios-charging-monitor/releases), built by a GitHub Actions workflow straight from the tagged source. It's unsigned on purpose: a signed build would carry my team, certificate, and device list, and sideloading tools re-sign it with yours anyway.

1. Download `ChargeSpeed-vX.Y.Z.ipa` from the latest release.
2. Sign and install it with your own Apple ID using a sideloading tool such as [AltStore](https://altstore.io) or [Sideloadly](https://sideloadly.io). Follow the tool's own docs for the one-time setup on your Mac or PC.
3. The usual sideloading rules apply: a free Apple ID signature lasts 7 days (the tool refreshes it), a paid developer account signs for a year. The app needs no entitlements, so nothing special is required.

I don't support the sideloading step itself. If the tool's docs don't get you there, build from source instead.

### Option 2: build it yourself

You need a Mac with Xcode, [XcodeGen](https://github.com/yonaskolb/XcodeGen), and an Apple ID. A free Apple ID is enough; you don't need a paid Apple Developer Program membership.

Start in the terminal. Install XcodeGen (via Homebrew) if you don't have it, clone the repo, generate the Xcode project, and open it. None of this needs the phone yet:

```bash
brew install xcodegen
git clone https://github.com/gregsramblings/ios-charging-monitor.git
cd ios-charging-monitor
xcodegen generate
open ChargeSpeed.xcodeproj
```

Then, in Xcode and on the phone:

1. If Xcode doesn't know your Apple ID yet, add it under **Xcode → Settings → Accounts**. A free Apple ID shows up as a "Personal Team."
2. Select the **ChargeSpeed** target, open **Signing & Capabilities**, and pick that team. (Or set `DEVELOPMENT_TEAM` in `project.yml` and run `xcodegen generate` again.)
3. Plug the phone into the Mac and choose it as the run destination in Xcode.
4. On the phone, turn on **Settings → Privacy & Security → Developer Mode**. The switch only appears after the phone has been connected to Xcode, which is why this step comes after the previous one. The phone will restart.
5. Press Run in Xcode.
6. On first launch iOS will refuse to open the app until you trust the certificate: **Settings → General → VPN & Device Management**, tap your Apple ID under Developer App, then **Trust**.

With a free Apple ID the build expires after 7 days (run it from Xcode again to re-sign), you're limited to 3 sideloaded apps, and it only works on your own devices. A paid Apple Developer Program membership gets you a year of signing and ad hoc distribution to up to 100 devices. The Simulator runs the app too, but it reads your Mac's battery, not a phone's.

Two caveats. Apple can rename or remove any of these private APIs in any iOS update. And I've only verified it on an iPhone 17 Pro Max on iOS 26; the sensor names may differ on other models. If you run it on something else, the raw dump at the bottom of the app lists every sensor it found, and I'd be curious what shows up.