{"title":"Reading Mac Battery Health From IOKit","html_url":"https://deallocatedobjects.com/posts/reading-mac-battery-health-from-iokit","source_url":"","markdown_content":"Battery health on a Mac lives in the `AppleSmartBattery` entry in the I/O Registry. System Information shows numbers from it, and so does the battery meter in [Multimeter](https://multimeterapp.com), which is where I ran into this.\n\nA lot of the scripts and snippets you'll find for it read `DesignCapacity` and `AppleRawMaxCapacity` straight off that entry and divide one by the other. On my M1 Max MacBook Pro running macOS 27.0.1, neither key is there anymore. Here's where they went, what units everything is in, and a Swift reader that finds them.\n\n### Look Before You Code\n\n`ioreg` shows you exactly what your code will see:\n\n```bash\nioreg -rn AppleSmartBattery -d1\n```\n\n`-n` matches the entry by name, `-r` prints it as the root of its own subtree, and `-d1` stops there so you don't also get the pack, bank and cell entries underneath it. Count the top-level keys and you'll find `CycleCount`, `Voltage`, `Amperage`, `MaxCapacity` and `CurrentCapacity`, but no `DesignCapacity`, no `AppleRawMaxCapacity` and no `Temperature`:\n\n```bash\n$ ioreg -rn AppleSmartBattery -d1 | grep -cE '\"(DesignCapacity|AppleRawMaxCapacity|Temperature)\" ='\n0\n```\n\nThe capacity numbers moved into a dictionary called `BatteryData`:\n\n```text\n\"BatteryData\" = {\"FullChargeCapacity\"=6804,\"NominalChargeCapacity\"=7048,\"FullyCharged\"=0,\"AvgTimeToEmpty\"=65535,\"RemainingCapacity\"=5189,\"AbsoluteCapacity\"=0,\"MaxCapacity\"=100,\"DesignCapacity\"=8694,\"CurrentCapacity\"=80,\"BatteryPower\"=0,\"TrueRemainingCapacity\"=0}\n```\n\nThat one has `DesignCapacity`, but it still has no temperature and no `AppleRawMaxCapacity`. Those are one level down, in the `BatteryData` of the `AppleSmartBatteryPack` entry that sits under `AppleSmartBattery`. That dictionary is much bigger (gas gauge details, lifetime minimums and maximums, the pack's serial number), and it's where `Temperature`, `VirtualTemperature`, `AppleRawMaxCapacity` and another copy of `CycleCount` live on this Mac.\n\nFor pulling single values out in a script, add `-a` to get a plist and let `plutil` walk the key path:\n\n```bash\nioreg -arn AppleSmartBattery -d1 | plutil -extract 0.BatteryData.DesignCapacity raw -\n# 8694\nioreg -arn AppleSmartBatteryPack -d1 | plutil -extract 0.BatteryData.Temperature raw -\n# 3750\n```\n\nThe leading `0` is there because `ioreg -a` prints an array with one dictionary per matching entry.\n\n### MaxCapacity Is A Percentage\n\nThe trap that's easy to miss is that `MaxCapacity` and `CurrentCapacity` still exist at the top level, so code that reads them keeps running and quietly gets the wrong answer. On Apple silicon they're percentages. This Mac reports `MaxCapacity = 100` and `CurrentCapacity = 80`, which matches `pmset -g batt` saying 80%. Dividing `MaxCapacity` by `DesignCapacity` gives you 100 / 8694, a health of about 1%.\n\nThat pair is the right thing for the charge level (it's what `IOPSCopyPowerSourcesInfo` gives you as `kIOPSCurrentCapacityKey` and `kIOPSMaxCapacityKey`). For health you want two numbers in mAh:\n\n- `DesignCapacity`: what the battery held new. 8694 mAh here.\n- `NominalChargeCapacity`: what it holds now. 7048 mAh here.\n\n7048 / 8694 is 81%. System Information on the same Mac says \"Maximum Capacity: 80%\". `AppleRawMaxCapacity` (6804, the same value as `FullChargeCapacity`) would give 78%. Apple doesn't document which number or rounding its own figure uses, so I use `NominalChargeCapacity`, since it lands closest, and fall back to `AppleRawMaxCapacity` when it's missing.\n\nCycle count is the easy one: `CycleCount` is still at the top level (561 here). There's also `DesignCycleCount9C`, which is 1000 on this MacBook Pro.\n\n### Temperature And Signed Numbers\n\n`Temperature` and `VirtualTemperature` are in hundredths of a degree Celsius, so 3750 is 37.5 °C. On this Mac both keys exist only in the pack's `BatteryData`, and they read the same value.\n\nThe other unit trap is the sign. Current is negative while the battery discharges, and the text output of `ioreg` prints negative numbers as unsigned 64-bit values. Here's the pack's lifetime maximum discharge current both ways:\n\n```bash\n$ ioreg -rn AppleSmartBatteryPack -d1 | grep -o '\"MaximumDischargeCurrent\"=[0-9]*'\n\"MaximumDischargeCurrent\"=18446744073709541491\n$ ioreg -arn AppleSmartBatteryPack -d1 | plutil -extract 0.BatteryData.LifetimeData.MaximumDischargeCurrent raw -\n-10125\n```\n\nThe plist keeps the sign, and so does Swift: `NSNumber.int64Value` gives you `-10125`. If you're parsing the text output with `grep` and `awk`, anything near 18446744073709551616 is a negative number in disguise.\n\n### Power Draw\n\nThere are two ways to get watts, and the better one doesn't depend on the battery's current. `AppleSmartBattery` has a `PowerTelemetryData` dictionary with a `SystemLoad` key, the whole system's draw in milliwatts. You can check the unit against the adapter side of the same dictionary:\n\n```text\n\"SystemLoad\"=27095  \"SystemVoltageIn\"=27777  \"SystemCurrentIn\"=975\n```\n\n27777 mV x 975 mA is 27.08 W, and `SystemLoad` says 27095 mW. That reading was taken on the power adapter, which is the other reason to prefer it.\n\nThe fallback is `Amperage` (mA) times `Voltage` (mV), divided by a million for watts. That only works while the battery is the thing powering the Mac. Plugged in and not charging, this Mac reports `Amperage = 0`, and while charging the current flows into the battery, so it measures charging and tells you nothing about what the system is drawing. I count it only when the current is negative. This Mac stayed on its adapter while I wrote this, so that branch comes straight from Multimeter's reader without a fresh reading on battery.\n\n### A Reader That Checks Every Layout\n\nHere's the reader, trimmed from Multimeter and extended so it looks in all three places a key can be: the top level, `AppleSmartBattery`'s `BatteryData` and the pack's `BatteryData`. It reads specific keys instead of copying the whole property table, which includes large telemetry blobs you don't need.\n\n```swift title=\"BatteryHealth.swift\"\nimport Foundation\nimport IOKit\n\nfunc property(_ service: io_service_t, _ key: String) -> Any? {\n    IORegistryEntryCreateCFProperty(service, key as CFString, kCFAllocatorDefault, 0)?.takeRetainedValue()\n}\n\nstruct BatteryHealth {\n    var cycleCount: Int?\n    var health: Double?        // 0...1\n    var temperature: Double?   // °C\n    var watts: Double?\n}\n\nfunc readBatteryHealth() -> BatteryHealth? {\n    let battery = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching(\"AppleSmartBattery\"))\n    guard battery != 0 else { return nil }   // desktop Macs have no AppleSmartBattery\n    defer { IOObjectRelease(battery) }\n\n    // Where a key can live: the service itself, its BatteryData, and the\n    // BatteryData of its AppleSmartBatteryPack child.\n    var nested: [[String: Any]] = []\n    if let data = property(battery, \"BatteryData\") as? [String: Any] { nested.append(data) }\n    let pack = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching(\"AppleSmartBatteryPack\"))\n    if pack != 0 {\n        if let data = property(pack, \"BatteryData\") as? [String: Any] { nested.append(data) }\n        IOObjectRelease(pack)\n    }\n    func number(_ key: String) -> NSNumber? {\n        if let value = property(battery, key) as? NSNumber { return value }\n        return nested.lazy.compactMap { $0[key] as? NSNumber }.first\n    }\n\n    var result = BatteryHealth()\n    result.cycleCount = number(\"CycleCount\")?.intValue\n\n    // MaxCapacity is a percentage on Apple silicon, so compare mAh to mAh.\n    if let design = number(\"DesignCapacity\")?.doubleValue, design > 0,\n       let full = (number(\"NominalChargeCapacity\") ?? number(\"AppleRawMaxCapacity\"))?.doubleValue,\n       full > 100 {\n        result.health = min(1, full / design)\n    }\n\n    // Hundredths of a degree Celsius: 3750 is 37.5 °C.\n    if let raw = (number(\"Temperature\") ?? number(\"VirtualTemperature\"))?.doubleValue, raw > 0 {\n        result.temperature = raw / 100\n    }\n\n    // Milliwatts for the whole system, when the Mac reports it.\n    if let telemetry = property(battery, \"PowerTelemetryData\") as? [String: Any],\n       let load = (telemetry[\"SystemLoad\"] as? NSNumber)?.doubleValue, load > 0 {\n        result.watts = load / 1000\n    } else if let amperage = number(\"Amperage\")?.int64Value, amperage < 0,\n              let voltage = number(\"Voltage\")?.doubleValue {\n        // mA x mV; negative current means the battery is discharging.\n        result.watts = Double(-amperage) * voltage / 1_000_000\n    }\n    return result\n}\n```\n\nA few details in there are deliberate. The top level is checked first, so the same code works on a layout that still keeps everything flat. The `full > 100` guard throws away a capacity that is really a percentage, so a stray `MaxCapacity` can never turn into a health of 1%. `min(1, ...)` keeps a capacity reading above the design capacity from showing up as more than 100%. And every value is optional, because a desktop Mac has no `AppleSmartBattery` at all and the function returns `nil` before reading anything.\n\nCompiled with `swiftc` and run on this MacBook Pro, it prints a cycle count of 561, a health of 0.8107, a temperature of 37.5 °C and 26.588 W.\n\nFor the live charge percentage, charging state and time remaining, stay with `IOPSCopyPowerSourcesInfo`. Those are public API, they match `pmset -g batt`, and `IOPSNotificationCreateRunLoopSource` tells you when they change, so you only need to poll the registry for the slower numbers above.","html_content":"<p>Battery health on a Mac lives in the <code>AppleSmartBattery</code> entry in the I/O Registry. System Information shows numbers from it, and so does the battery meter in <a href=\"https://multimeterapp.com\">Multimeter</a>, which is where I ran into this.</p>\n<p>A lot of the scripts and snippets you’ll find for it read <code>DesignCapacity</code> and <code>AppleRawMaxCapacity</code> straight off that entry and divide one by the other. On my M1 Max MacBook Pro running macOS 27.0.1, neither key is there anymore. Here’s where they went, what units everything is in, and a Swift reader that finds them.</p>\n<h2 id=\"look-before-you-code\" data-level=\"3\">Look Before You Code</h2>\n<p><code>ioreg</code> shows you exactly what your code will see:</p>\n<figure class=\"code\" data-lang=\"bash\"><figcaption><span class=\"lang\">Shell</span></figcaption><pre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\"><code><span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">ioreg</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> -rn</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> AppleSmartBattery</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> -d1</span></span></code></pre></figure><p><code>-n</code> matches the entry by name, <code>-r</code> prints it as the root of its own subtree, and <code>-d1</code> stops there so you don’t also get the pack, bank and cell entries underneath it. Count the top-level keys and you’ll find <code>CycleCount</code>, <code>Voltage</code>, <code>Amperage</code>, <code>MaxCapacity</code> and <code>CurrentCapacity</code>, but no <code>DesignCapacity</code>, no <code>AppleRawMaxCapacity</code> and no <code>Temperature</code>:</p>\n<figure class=\"code\" data-lang=\"bash\"><figcaption><span class=\"lang\">Shell</span></figcaption><pre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\"><code><span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">$</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> ioreg</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> -rn</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> AppleSmartBattery</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> -d1</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> |</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> grep</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> -cE</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> '\"(DesignCapacity|AppleRawMaxCapacity|Temperature)\" ='</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">0</span></span></code></pre></figure><p>The capacity numbers moved into a dictionary called <code>BatteryData</code>:</p>\n<figure class=\"code\" data-lang=\"text\"><pre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\"><code><span class=\"line\"><span>\"BatteryData\" = {\"FullChargeCapacity\"=6804,\"NominalChargeCapacity\"=7048,\"FullyCharged\"=0,\"AvgTimeToEmpty\"=65535,\"RemainingCapacity\"=5189,\"AbsoluteCapacity\"=0,\"MaxCapacity\"=100,\"DesignCapacity\"=8694,\"CurrentCapacity\"=80,\"BatteryPower\"=0,\"TrueRemainingCapacity\"=0}</span></span></code></pre></figure><p>That one has <code>DesignCapacity</code>, but it still has no temperature and no <code>AppleRawMaxCapacity</code>. Those are one level down, in the <code>BatteryData</code> of the <code>AppleSmartBatteryPack</code> entry that sits under <code>AppleSmartBattery</code>. That dictionary is much bigger (gas gauge details, lifetime minimums and maximums, the pack’s serial number), and it’s where <code>Temperature</code>, <code>VirtualTemperature</code>, <code>AppleRawMaxCapacity</code> and another copy of <code>CycleCount</code> live on this Mac.</p>\n<p>For pulling single values out in a script, add <code>-a</code> to get a plist and let <code>plutil</code> walk the key path:</p>\n<figure class=\"code\" data-lang=\"bash\"><figcaption><span class=\"lang\">Shell</span></figcaption><pre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\"><code><span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">ioreg</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> -arn</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> AppleSmartBattery</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> -d1</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> |</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> plutil</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> -extract</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> 0.BatteryData.DesignCapacity</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> raw</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> -</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\"># 8694</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">ioreg</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> -arn</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> AppleSmartBatteryPack</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> -d1</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> |</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> plutil</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> -extract</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> 0.BatteryData.Temperature</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> raw</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> -</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\"># 3750</span></span></code></pre></figure><p>The leading <code>0</code> is there because <code>ioreg -a</code> prints an array with one dictionary per matching entry.</p>\n<h2 id=\"maxcapacity-is-a-percentage\" data-level=\"3\">MaxCapacity Is A Percentage</h2>\n<p>The trap that’s easy to miss is that <code>MaxCapacity</code> and <code>CurrentCapacity</code> still exist at the top level, so code that reads them keeps running and quietly gets the wrong answer. On Apple silicon they’re percentages. This Mac reports <code>MaxCapacity = 100</code> and <code>CurrentCapacity = 80</code>, which matches <code>pmset -g batt</code> saying 80%. Dividing <code>MaxCapacity</code> by <code>DesignCapacity</code> gives you 100 / 8694, a health of about 1%.</p>\n<p>That pair is the right thing for the charge level (it’s what <code>IOPSCopyPowerSourcesInfo</code> gives you as <code>kIOPSCurrentCapacityKey</code> and <code>kIOPSMaxCapacityKey</code>). For health you want two numbers in mAh:</p>\n<ul>\n<li><code>DesignCapacity</code>: what the battery held new. 8694 mAh here.</li>\n<li><code>NominalChargeCapacity</code>: what it holds now. 7048 mAh here.</li>\n</ul>\n<p>7048 / 8694 is 81%. System Information on the same Mac says “Maximum Capacity: 80%”. <code>AppleRawMaxCapacity</code> (6804, the same value as <code>FullChargeCapacity</code>) would give 78%. Apple doesn’t document which number or rounding its own figure uses, so I use <code>NominalChargeCapacity</code>, since it lands closest, and fall back to <code>AppleRawMaxCapacity</code> when it’s missing.</p>\n<p>Cycle count is the easy one: <code>CycleCount</code> is still at the top level (561 here). There’s also <code>DesignCycleCount9C</code>, which is 1000 on this MacBook Pro.</p>\n<h2 id=\"temperature-and-signed-numbers\" data-level=\"3\">Temperature And Signed Numbers</h2>\n<p><code>Temperature</code> and <code>VirtualTemperature</code> are in hundredths of a degree Celsius, so 3750 is 37.5 °C. On this Mac both keys exist only in the pack’s <code>BatteryData</code>, and they read the same value.</p>\n<p>The other unit trap is the sign. Current is negative while the battery discharges, and the text output of <code>ioreg</code> prints negative numbers as unsigned 64-bit values. Here’s the pack’s lifetime maximum discharge current both ways:</p>\n<figure class=\"code\" data-lang=\"bash\"><figcaption><span class=\"lang\">Shell</span></figcaption><pre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\"><code><span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">$</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> ioreg</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> -rn</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> AppleSmartBatteryPack</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> -d1</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> |</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> grep</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> -o</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> '\"MaximumDischargeCurrent\"=[0-9]*'</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">\"MaximumDischargeCurrent\"</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">=18446744073709541491</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">$</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> ioreg</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> -arn</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> AppleSmartBatteryPack</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> -d1</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> |</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> plutil</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> -extract</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> 0.BatteryData.LifetimeData.MaximumDischargeCurrent</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> raw</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> -</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">-10125</span></span></code></pre></figure><p>The plist keeps the sign, and so does Swift: <code>NSNumber.int64Value</code> gives you <code>-10125</code>. If you’re parsing the text output with <code>grep</code> and <code>awk</code>, anything near 18446744073709551616 is a negative number in disguise.</p>\n<h2 id=\"power-draw\" data-level=\"3\">Power Draw</h2>\n<p>There are two ways to get watts, and the better one doesn’t depend on the battery’s current. <code>AppleSmartBattery</code> has a <code>PowerTelemetryData</code> dictionary with a <code>SystemLoad</code> key, the whole system’s draw in milliwatts. You can check the unit against the adapter side of the same dictionary:</p>\n<figure class=\"code\" data-lang=\"text\"><pre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\"><code><span class=\"line\"><span>\"SystemLoad\"=27095  \"SystemVoltageIn\"=27777  \"SystemCurrentIn\"=975</span></span></code></pre></figure><p>27777 mV x 975 mA is 27.08 W, and <code>SystemLoad</code> says 27095 mW. That reading was taken on the power adapter, which is the other reason to prefer it.</p>\n<p>The fallback is <code>Amperage</code> (mA) times <code>Voltage</code> (mV), divided by a million for watts. That only works while the battery is the thing powering the Mac. Plugged in and not charging, this Mac reports <code>Amperage = 0</code>, and while charging the current flows into the battery, so it measures charging and tells you nothing about what the system is drawing. I count it only when the current is negative. This Mac stayed on its adapter while I wrote this, so that branch comes straight from Multimeter’s reader without a fresh reading on battery.</p>\n<h2 id=\"a-reader-that-checks-every-layout\" data-level=\"3\">A Reader That Checks Every Layout</h2>\n<p>Here’s the reader, trimmed from Multimeter and extended so it looks in all three places a key can be: the top level, <code>AppleSmartBattery</code>’s <code>BatteryData</code> and the pack’s <code>BatteryData</code>. It reads specific keys instead of copying the whole property table, which includes large telemetry blobs you don’t need.</p>\n<figure class=\"code\" data-lang=\"swift\"><figcaption><span class=\"file\">BatteryHealth.swift</span><span class=\"lang\">Swift</span></figcaption><pre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">import</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> Foundation</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">import</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> IOKit</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">func</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> property</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">_</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> service: io_service_t, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">_</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> key: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">-></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> Any</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">?</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">    IORegistryEntryCreateCFProperty</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(service, key </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">as</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> CFString, kCFAllocatorDefault, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">)</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">?</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">takeRetainedValue</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">()</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">struct</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> BatteryHealth</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">    var</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> cycleCount: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">?</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">    var</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> health: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">Double</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">?</span><span style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">        // 0...1</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">    var</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> temperature: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">Double</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">?</span><span style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">   // °C</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">    var</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> watts: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">Double</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">?</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">func</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> readBatteryHealth</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> BatteryHealth</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">?</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">    let</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> battery </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> IOServiceGetMatchingService</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(kIOMainPortDefault, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">IOServiceMatching</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">\"AppleSmartBattery\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">))</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">    guard</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> battery </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">!=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 0</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> else</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> { </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">return</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> nil</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> }   </span><span style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">// desktop Macs have no AppleSmartBattery</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">    defer</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> { </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">IOObjectRelease</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(battery) }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">    // Where a key can live: the service itself, its BatteryData, and the</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">    // BatteryData of its AppleSmartBatteryPack child.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">    var</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> nested: [[</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">Any</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">]] </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> []</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">    if</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> let</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> data </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> property</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(battery, </span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">\"BatteryData\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">as?</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> [</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">String</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">:</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> Any</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">] { nested.</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">append</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(data) }</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">    let</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> pack </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> IOServiceGetMatchingService</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(kIOMainPortDefault, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">IOServiceMatching</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">\"AppleSmartBatteryPack\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">))</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">    if</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> pack </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">!=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">        if</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> let</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> data </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> property</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(pack, </span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">\"BatteryData\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">as?</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> [</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">String</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">:</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> Any</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">] { nested.</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">append</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(data) }</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">        IOObjectRelease</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(pack)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    }</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">    func</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> number</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">_</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> key: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> NSNumber</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">?</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">        if</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> let</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> value </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> property</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(battery, key) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">as?</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> NSNumber { </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">return</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> value }</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">        return</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> nested.</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">lazy</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">compactMap</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> { </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">$0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">[key] </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">as?</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> NSNumber }.</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">first</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">    var</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> result </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> BatteryHealth</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">()</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    result.cycleCount </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> number</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">\"CycleCount\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">)</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">?</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.intValue</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">    // MaxCapacity is a percentage on Apple silicon, so compare mAh to mAh.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">    if</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> let</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> design </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> number</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">\"DesignCapacity\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">)</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">?</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.doubleValue, design </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">       let</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> full </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">number</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">\"NominalChargeCapacity\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">??</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> number</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">\"AppleRawMaxCapacity\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">))</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">?</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.doubleValue,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">       full </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 100</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">        result.health </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> min</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">, full </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">/</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> design)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">    // Hundredths of a degree Celsius: 3750 is 37.5 °C.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">    if</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> let</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> raw </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">number</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">\"Temperature\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">??</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> number</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">\"VirtualTemperature\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">))</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">?</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.doubleValue, raw </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">        result.temperature </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> raw </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">/</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 100</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">    // Milliwatts for the whole system, when the Mac reports it.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">    if</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> let</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> telemetry </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> property</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(battery, </span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">\"PowerTelemetryData\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">as?</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> [</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">String</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">:</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> Any</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">],</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">       let</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> load </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (telemetry[</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">\"SystemLoad\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">] </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">as?</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> NSNumber)</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">?</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.doubleValue, load </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">        result.watts </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> load </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">/</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 1000</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    } </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">else</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> if</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> let</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> amperage </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> number</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">\"Amperage\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">)</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">?</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.int64Value, amperage </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">&#x3C;</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">              let</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> voltage </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> number</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">\"Voltage\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">)</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">?</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.doubleValue {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">        // mA x mV; negative current means the battery is discharging.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">        result.watts </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> Double</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">-</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">amperage) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">*</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> voltage </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">/</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 1_000_000</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    }</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">    return</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> result</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">}</span></span></code></pre></figure><p>A few details in there are deliberate. The top level is checked first, so the same code works on a layout that still keeps everything flat. The <code>full &gt; 100</code> guard throws away a capacity that is really a percentage, so a stray <code>MaxCapacity</code> can never turn into a health of 1%. <code>min(1, ...)</code> keeps a capacity reading above the design capacity from showing up as more than 100%. And every value is optional, because a desktop Mac has no <code>AppleSmartBattery</code> at all and the function returns <code>nil</code> before reading anything.</p>\n<p>Compiled with <code>swiftc</code> and run on this MacBook Pro, it prints a cycle count of 561, a health of 0.8107, a temperature of 37.5 °C and 26.588 W.</p>\n<p>For the live charge percentage, charging state and time remaining, stay with <code>IOPSCopyPowerSourcesInfo</code>. Those are public API, they match <code>pmset -g batt</code>, and <code>IOPSNotificationCreateRunLoopSource</code> tells you when they change, so you only need to poll the registry for the slower numbers above.</p>","created_at":"2026-11-04T14:00:00.000Z","updated_at":"2026-11-04T14:00:00.000Z"}