Repository navigation
feat: add PowerStatus plugin for live power and battery monitoring - #484
Conversation
Add a new panel-only PowerStatus plugin that displays real-time system power consumption, AC input, battery charging/discharging wattage, battery health, temperature, and cycle count. Adapted from PowerTop's IOKit power telemetry reading, integrated as a MacTools dashboard widget with no feature rows or shortcuts.
- Add missing protocol properties (requestPermissionGuidance, shortcutBindingResolver) - Import IOKit.ps for power source notification types - Mark non-Sendable properties as nonisolated(unsafe) for Swift 6 concurrency - Add required settings capability to plugin.json manifest - Fix test parameter ordering and flow state for time estimation tests
…s panel The widget declared a fixed span height of 4 (32pt), which clipped the content. The view now measures its intrinsic height through a preference key and reports it via context.reportContentHeight, and the declared span is estimated from the snapshot's visible rows as a starting point.
When macOS stops reporting AvgTimeToEmpty, estimate the remaining time from stored energy (remaining capacity x pack voltage) divided by an EMA-smoothed discharge power over a ~60s window, following PowerTop's approach. Prefer the macOS value when it is valid, label computed estimates distinctly, and add a tooltip explaining the basis.
Show how power moves between the AC adapter, system, and battery for each flow state: AC to system when plugged in, AC to system plus AC to battery while charging, battery to system on battery power, and both sources feeding the system during supplemental discharge. Flow lines animate with moving dashes that respect Reduce Motion, and line thickness scales with wattage.
|
Thanks for adding this plugin! I found a few things to address before merging:
The 14 existing plugin tests pass locally. Both CI failures above still need to be resolved. Thanks! |
Charging now shows one AC node forking into system and battery, and supplemental discharge merges AC and battery into one system feed, instead of duplicating the source or destination icon per row.
Canvas closures are not re-evaluated per animation frame, so the withAnimation dash phase never moved. Drive the phase from TimelineView(.animation) instead, pausing when reduce motion is on.
Fix two places where flow state was incorrectly classified as .charging without checking isOnAC: - Fallback path (line 161): now returns .discharging when not on AC - resolveBatteryFlow telemetry branch (line 222): same guard Ground-truth from live IOKit confirmed negative BatteryPower/amperage indicates discharging; negative = charging was inverted.
Add 'Details' button in header that toggles expand/collapse of component details panel. When tapped, shows expandable panel with voltage, current, power, battery health, cycle count, temperature and other IOKit device data.
|
Thanks for the careful review! All three points are addressed in 1. Battery health calculationAll four capacity fields ( Health now walks a real-mAh chain — Added four focused tests, including your 90% fixture:
2. Localization
I also removed One note: 3. Website plugin list
Local verification
|
|
@ggbond268 PTAL |
Read the gas gauge's compensated full-charge capacity (FccComp1) from the AppleSmartBatteryPack node, which is the figure macOS shows as Maximum Capacity, and round the percentage up instead of truncating so 88.49% reads as 89% like the OS does.
Show full-charge over design capacity in one tile and move the time estimate beside the remaining capacity, which also evens the grid out at three full rows. Count the merged tile once so the declared widget height still matches the rendered rows.
|
Pushed two follow-ups: Battery health now matches System SettingsThe panel showed 84% while System Settings reported 89%. Two separate causes: Wrong capacity field. System Settings' "Maximum Capacity" is the gas gauge's temperature- and age-compensated reading, Integer truncation. The percentage now rounds up rather than to nearest.
The last row is a live reading from my machine: nearest-rounding reports 88 there while System Settings says 89, so only ceiling matches across the range the value actually takes. Reading order is Verified on live hardware: panel reports 89%, Layout
Tests27/27 pass. New coverage:
|
|
Thanks for the updates! The earlier health, localization, and generated-data issues are resolved. I found one remaining issue in Charging can still be reported as supplemental battery discharge. With AC connected, Could you handle signed current consistently and respect The 27 existing plugin tests pass locally. Thanks! |
With AC connected and PowerTelemetryData missing or empty, a charging current was reported as supplemental discharge. Both the no-telemetry fallback and the raw-current branch in resolveBatteryFlow treated both current directions as discharge, and a charging pack with no readable current could be reported as idle. Classify from the signed Amperage x Voltage once and reuse it in both paths, so a positive current is charging, a negative one is discharge, and the charger flag is honored when no current is readable.
|
Thanks — fixed in 4e716f5. Both the no-telemetry fallback and the raw-current branch of The root cause was that the flow classification only existed twice, with opposite sign conventions:
So each branch had to invert the sign by hand, and the fallback had it wrong. Positive current came out as I now classify once from the signed
Your repro (AC + Added
|
Resolve conflicts in the README "Monitoring & calendar" rows by keeping both the upstream container/VM wording and the PowerStatus panel wording in the English and Chinese READMEs.
|
@ggbond268 PTAL again |
|
Thanks for the fix! The charging-direction issue is resolved. I reproduced two remaining display issues on
The 30 existing tests pass; two temporary regression checks reproduce these cases. Please add focused coverage for the resulting state and power values. These findings concern the plugin's display calculations. Thanks! |
On AC with no battery current, resolveBatteryFlow fell through to .idle, so the diagram drew the pack as the source even though the adapter was carrying the system alone, as when the pack is full or charging paused. Return .onAC on power instead, keeping .idle off power only. Without telemetry the fallback also echoed the battery rate into the system draw, so effectiveACOutputW added it back and reported double the real AC input for a charging pack. Hold the system draw at zero for charging and treat an empty telemetry dict like a missing one. Add focused coverage for the on-AC no-current state and for the charging power values under missing and empty telemetry.
|
Both display issues are fixed in 3e97da6. 1. AC shown as idle. The fix returns 2. Charging double count. With no telemetry the fallback returned Empty telemetry ( The off-power paths are unchanged: unplugged with real load still resolves to Added |
|
Thanks for the update! The AC/idle issue and double-counting are fixed. One part of the missing-telemetry handling still needs adjustment: With only a measured battery charging rate of 10 W, the current UI shows system load and AC input as 0 W, while the header and diagram show 10 W of AC power. That 10 W tells us the battery's charging rate; it does not establish total AC input or system load. Could you distinguish unavailable readings from actual zero values and display them as All 32 existing tests pass locally. I'd recommend addressing this remaining display issue before merging. Thanks! |
Without PowerTelemetryData the pack's current is the only measured quantity. Total AC input and the system draw are not established by it, so reporting them as 0 W made the header and diagram claim 10 W of AC power for a 10 W charge rate. `systemPowerW` and `acInputW` are now optional, and the metric rows, header, subtitle, and flow diagram render an unestablished reading as an em dash. A measured 0 stays 0. The charge/discharge rate is still shown on its own, and off power the pack's rate still establishes the system draw because it is the only source. Also drops the write-only `batteryPowerW` snapshot field and an unused `extractString` helper.
|
Addressed in d6b39e1. Unavailable is no longer rendered as 0 W. Without
Tests. Replaced the regression that assumed AC input equals the charging rate with
While here I also dropped the write-only |
Summary
Plugin Details
power-statusAppleSmartBatteryserviceChanges
Plugins/PowerStatus/— Complete plugin implementation (models, sampler, view model, component view, plugin class, tests, localization)changes/unreleased/power-status-plugin.md— Changelog fragmentREADME.md/README.zh-CN.md— Updated monitoring section descriptionTesting
make build-plugin PLUGIN=PowerStatusmake test TEST_FILTER=PowerStatusPluginTests