2424import Foundation
2525import SwiftUI
2626
27- /// Manages fetching and parsing battery and power information from system commands.
28- ///
29- /// This class acts as an `ObservableObject`, providing published properties
30- /// that SwiftUI views can observe to display real-time battery and power metrics.
31- /// It uses `ioreg` and the bundled `power_info` command-line tool to gather data.
27+ // Manages fetching and parsing battery and power information from system commands.
28+ //
29+ // This class acts as an `ObservableObject`, providing published properties
30+ // that SwiftUI views can observe to display real-time battery and power metrics.
31+ // It uses `ioreg` and the bundled `power_info` command-line tool to gather data.
3232class BatteryInfoManager : ObservableObject {
33- /// The current maximum capacity of the battery in mAh (AppleRawMaxCapacity).
33+ // The current maximum capacity of the battery in mAh (AppleRawMaxCapacity).
3434 @Published var batteryCapacity : Int = 0
35- /// The original design capacity of the battery in mAh.
35+ // The original design capacity of the battery in mAh.
3636 @Published var designCapacity : Int = 0
37- /// The number of charge cycles the battery has undergone.
37+ // The number of charge cycles the battery has undergone.
3838 @Published var cycleCount : Int = 0
39- /// The battery's health percentage, calculated as `(batteryCapacity / designCapacity) * 100`.
39+ // The battery's health percentage, calculated as `(batteryCapacity / designCapacity) * 100`.
4040 @Published var health : Double = 0.0
41- /// Indicates whether the battery is currently charging.
41+ // Indicates whether the battery is currently charging.
4242 @Published var isCharging : Bool = false
43- /// The current charge percentage of the battery (CurrentCapacity).
43+ // The current charge percentage of the battery (CurrentCapacity).
4444 @Published var batteryPercent : Int = 0
45- /// The voltage being supplied by the power adapter in Volts.
45+ // The voltage being supplied by the power adapter in Volts.
4646 @Published var voltage : Double = 0.0
47- /// The amperage being supplied by the power adapter in Amps.
47+ // The amperage being supplied by the power adapter in Amps.
4848 @Published var amperage : Double = 0.0
49- /// The current power consumption of the entire system in Watts.
49+ // The current power consumption of the entire system in Watts.
5050 @Published var loadwatt : Double = 0.0
51- /// The power being drawn from the power adapter in Watts.
51+ // The power being drawn from the power adapter in Watts.
5252 @Published var inputwatt : Double = 0.0
53- /// The battery's internal temperature in degrees Celsius.
53+ // The battery's internal temperature in degrees Celsius.
5454 @Published var temperature : Double = 0.0
55- /// The current power draw from/to the battery in Watts. Positive means charging, negative means discharging.
55+ // The current power draw from/to the battery in Watts. Positive means charging, negative means discharging.
5656 @Published var batteryPower : Double = 0.0
57- /// The current voltage of the battery in Volts.
57+ // The current voltage of the battery in Volts.
5858 @Published var batteryVoltage : Double = 0.0
59- /// The current amperage flow from/to the battery in Amps. Positive means charging, negative means discharging.
59+ // The current amperage flow from/to the battery in Amps. Positive means charging, negative means discharging.
6060 @Published var batteryAmperage : Double = 0.0
61- /// The serial number of the battery.
61+ // The serial number of the battery.
6262 @Published var serialNumber : String = " -- "
6363
64- /// Initializes the manager and triggers the first battery info update.
64+ @Published var batteryVoltage_mV : UInt16 = 0
65+ @Published var batteryAmperage_mA : Int16 = 0
66+
67+ // Initializes the manager and triggers the first battery info update.
6568 init ( ) {
6669 updateBatteryInfo ( )
6770 }
6871
69- /// Asynchronously fetches and updates all battery information properties.
70- ///
71- /// This function runs the `power_info` tool and `ioreg` command,
72- /// captures their output, and then calls `parseBatteryInfo` on the main thread
73- /// to update the published properties.
72+ // Asynchronously fetches and updates all battery information properties.
73+ //
74+ // This function runs the `power_info` tool and `ioreg` command,
75+ // captures their output, and then calls `parseBatteryInfo` on the main thread
76+ // to update the published properties.
7477 func updateBatteryInfo( ) {
7578 Task {
76- guard let power_info_URL = Bundle . main. url ( forResource: " power_info " , withExtension: nil ) else {
77- // TODO: Replace fatalError with more robust error handling (e.g., logging, showing alert)
78- fatalError ( " Executable 'power_info' not found in bundle. " )
79- }
80-
81- let po = Process ( )
82- po. executableURL = URL ( fileURLWithPath: " /bin/zsh " )
83- po. arguments = [ " -c " , " source ~/.zshrc; \( power_info_URL. path) " ]
84- let pp = Pipe ( )
85- po. standardOutput = pp
86- po. standardError = pp
87-
8879 let process = Process ( )
8980 process. executableURL = URL ( fileURLWithPath: " /bin/zsh " )
9081 process. arguments = [ " -c " , " ioreg -r -c AppleSmartBattery | grep -E 'DesignCapacity|CycleCount|Serial|Temperature|CurrentCapacity|AppleRawMaxCapacity' " ]
9182 let pipe = Pipe ( )
9283 process. standardOutput = pipe
9384
9485 do {
95- try po. run ( )
96- po. waitUntilExit ( )
97- let dt = pp. fileHandleForReading. readDataToEndOfFile ( )
98- var output = String ( data: dt, encoding: . utf8) ?? " "
99-
10086 try process. run ( )
10187 process. waitUntilExit ( )
10288 let data = pipe. fileHandleForReading. readDataToEndOfFile ( )
103- output + = String ( data: data, encoding: . utf8) ?? " "
89+ let output = String ( data: data, encoding: . utf8) ?? " "
10490
10591 await parseBatteryInfo ( from: output)
10692 } catch {
@@ -109,19 +95,48 @@ class BatteryInfoManager: ObservableObject {
10995 }
11096 }
11197
112- /// Parses the combined output from `power_info` and `ioreg` to update the battery properties.
113- ///
114- /// This function uses regular expressions to extract specific values from the command output string.
115- /// It must be called on the main actor because it updates `@Published` properties.
116- ///
117- /// - Parameter output: The combined string output from the system commands.
11898 @MainActor
11999 private func parseBatteryInfo( from output: String ) {
100+ loadwatt = Double ( getRawSystemPower ( ) )
101+ inputwatt = Double ( getAdapterPower ( ) )
102+ amperage = Double ( getAdapterAmperage ( ) )
103+ voltage = Double ( getAdapterVoltage ( ) )
104+ batteryVoltage = Double ( getBatteryVoltage ( ) )
105+ batteryAmperage = Double ( getBatteryAmperage ( ) )
106+ batteryPower = Double ( getBatteryPower ( ) )
107+ isCharging = getChargingStatus ( ) . contains ( " Charging " )
108+
109+ // --- Parse Temperature (ioreg - VirtualTemperature seems more reliable than power_info's) ---
110+ if let match = output. range ( of: " \" VirtualTemperature \" = ([0-9]+) " , options: . regularExpression) {
111+ let valueStr = String ( output [ match] ) . components ( separatedBy: " = " ) . last? . trimmingCharacters ( in: . whitespaces) ?? " 0 "
112+ let temperatureValue = Int ( valueStr. trimmingCharacters ( in: CharacterSet ( charactersIn: " \" " ) ) ) ?? 0
113+ temperature = Double ( temperatureValue) / 100.0
114+ }
115+
116+ // --- Parse Serial Number (ioreg) ---
117+ if let match = output. range ( of: " \" Serial \" = \" ([^ \" ]+) \" " , options: . regularExpression) {
118+ let fullMatch = String ( output [ match] )
119+ let pattern = " \" Serial \" = \" ([^ \" ]+) \" "
120+ if let regex = try ? NSRegularExpression ( pattern: pattern) ,
121+ let nsMatch = regex. firstMatch ( in: fullMatch, range: NSRange ( fullMatch. startIndex... , in: fullMatch) ) ,
122+ nsMatch. numberOfRanges > 1 ,
123+ let valueRange = Range ( nsMatch. range ( at: 1 ) , in: fullMatch) {
124+ serialNumber = String ( fullMatch [ valueRange] )
125+ }
126+ }
127+
128+ // --- Parse Current Charge Percentage (ioreg) ---
129+ if let match = output. range ( of: " \" CurrentCapacity \" = ([0-9]+) " , options: . regularExpression) {
130+ let valueStr = String ( output [ match] ) . components ( separatedBy: " = " ) . last? . trimmingCharacters ( in: . whitespaces) ?? " 0 "
131+ batteryPercent = Int ( valueStr. trimmingCharacters ( in: CharacterSet ( charactersIn: " \" " ) ) ) ?? 0
132+ }
133+
120134 // --- Parse Design Capacity (ioreg) ---
121135 if let match = output. range ( of: " \" DesignCapacity \" = ([0-9]+) " , options: . regularExpression) {
122136 let valueStr = String ( output [ match] ) . components ( separatedBy: " = " ) . last? . trimmingCharacters ( in: . whitespaces) ?? " 0 "
123137 designCapacity = Int ( valueStr. trimmingCharacters ( in: CharacterSet ( charactersIn: " \" " ) ) ) ?? 0
124138 }
139+
125140 // --- Parse Current Max Capacity & Calculate Health (ioreg) ---
126141 if let match = output. range ( of: " \" AppleRawMaxCapacity \" = ([0-9]+) " , options: . regularExpression) {
127142 let valueStr = String ( output [ match] ) . components ( separatedBy: " = " ) . last? . trimmingCharacters ( in: . whitespaces) ?? " 0 "
@@ -130,101 +145,12 @@ class BatteryInfoManager: ObservableObject {
130145 health = ( Double ( batteryCapacity) / Double( designCapacity) ) * 100
131146 }
132147 }
148+
133149 // --- Parse Cycle Count (ioreg) ---
134150 if let match = output. range ( of: " \" CycleCount \" = ([0-9]+) " , options: . regularExpression) {
135151 let valueStr = String ( output [ match] ) . components ( separatedBy: " = " ) . last? . trimmingCharacters ( in: . whitespaces) ?? " 0 "
136152 cycleCount = Int ( valueStr. trimmingCharacters ( in: CharacterSet ( charactersIn: " \" " ) ) ) ?? 0
137153 }
138- // --- Parse Charging Status (power_info) ---
139- if let match = output. range ( of: " battery_status=([a-zA-Z]+) " , options: . regularExpression) {
140- let valueStr = String ( output [ match] ) . components ( separatedBy: " = " ) . last? . trimmingCharacters ( in: . whitespaces) ?? " Idle "
141- isCharging = valueStr. contains ( " Charging " )
142- }
143- // --- Parse Current Charge Percentage (ioreg) ---
144- if let match = output. range ( of: " \" CurrentCapacity \" = ([0-9]+) " , options: . regularExpression) {
145- let valueStr = String ( output [ match] ) . components ( separatedBy: " = " ) . last? . trimmingCharacters ( in: . whitespaces) ?? " 0 "
146- batteryPercent = Int ( valueStr. trimmingCharacters ( in: CharacterSet ( charactersIn: " \" " ) ) ) ?? 0
147- }
148- // --- Parse Adapter Voltage (power_info) ---
149- let patternV = " adapter_voltage=([0-9]+(?: \\ .[0-9]+)?)V "
150- if let regex = try ? NSRegularExpression ( pattern: patternV) {
151- let matches = regex. matches ( in: output, range: NSRange ( output. startIndex... , in: output) )
152- if let match = matches. first, let range = Range ( match. range ( at: 1 ) , in: output) {
153- let valueStr = String ( output [ range] )
154- voltage = Double ( valueStr) ?? 0.0
155- }
156- }
157- // --- Parse Adapter Amperage (power_info) ---
158- let patternA = " adapter_amperage=([0-9]+(?: \\ .[0-9]+)?)A "
159- if let regex = try ? NSRegularExpression ( pattern: patternA) {
160- let matches = regex. matches ( in: output, range: NSRange ( output. startIndex... , in: output) )
161- if let match = matches. first, let range = Range ( match. range ( at: 1 ) , in: output) {
162- let valueStr = String ( output [ range] )
163- amperage = Double ( valueStr) ?? 0.0
164- }
165- }
166- // --- Parse System Power (power_info) ---
167- let patternSysP = " sys_power=([0-9]+(?: \\ .[0-9]+)?)W "
168- if let regex = try ? NSRegularExpression ( pattern: patternSysP) {
169- let matches = regex. matches ( in: output, range: NSRange ( output. startIndex... , in: output) )
170- if let match = matches. first, let range = Range ( match. range ( at: 1 ) , in: output) {
171- let valueStr = String ( output [ range] )
172- loadwatt = Double ( valueStr) ?? 0.0
173- }
174- }
175- // --- Parse Adapter Power (power_info) ---
176- let patternAdpP = " adapter_power=([0-9]+(?: \\ .[0-9]+)?)W "
177- if let regex = try ? NSRegularExpression ( pattern: patternAdpP) {
178- let matches = regex. matches ( in: output, range: NSRange ( output. startIndex... , in: output) )
179- if let match = matches. first, let range = Range ( match. range ( at: 1 ) , in: output) {
180- let valueStr = String ( output [ range] )
181- inputwatt = Double ( valueStr) ?? 0.0
182- }
183- }
184- // --- Parse Battery Power (power_info) ---
185- let patternBattP = " battery_power=( \\ -?[0-9]+(?: \\ .[0-9]+)?)W " // Allow negative
186- if let regex = try ? NSRegularExpression ( pattern: patternBattP) {
187- let matches = regex. matches ( in: output, range: NSRange ( output. startIndex... , in: output) )
188- if let match = matches. first, let range = Range ( match. range ( at: 1 ) , in: output) {
189- let valueStr = String ( output [ range] )
190- batteryPower = Double ( valueStr) ?? 0.0
191- }
192- }
193- // --- Parse Battery Voltage (power_info) ---
194- let patternBattV = " battery_voltage=([0-9]+(?: \\ .[0-9]+)?)V "
195- if let regex = try ? NSRegularExpression ( pattern: patternBattV) {
196- let matches = regex. matches ( in: output, range: NSRange ( output. startIndex... , in: output) )
197- if let match = matches. first, let range = Range ( match. range ( at: 1 ) , in: output) {
198- let valueStr = String ( output [ range] )
199- batteryVoltage = Double ( valueStr) ?? 0.0
200- }
201- }
202- // --- Parse Battery Amperage (power_info) ---
203- let patternBattA = " battery_amperage=( \\ -?[0-9]+(?: \\ .[0-9]+)?)A " // Allow negative
204- if let regex = try ? NSRegularExpression ( pattern: patternBattA) {
205- let matches = regex. matches ( in: output, range: NSRange ( output. startIndex... , in: output) )
206- if let match = matches. first, let range = Range ( match. range ( at: 1 ) , in: output) {
207- let valueStr = String ( output [ range] )
208- batteryAmperage = Double ( valueStr) ?? 0.0
209- }
210- }
211-
212- // --- Parse Temperature (ioreg - VirtualTemperature seems more reliable than power_info's) ---
213- if let match = output. range ( of: " \" VirtualTemperature \" = ([0-9]+) " , options: . regularExpression) {
214- let valueStr = String ( output [ match] ) . components ( separatedBy: " = " ) . last? . trimmingCharacters ( in: . whitespaces) ?? " 0 "
215- let temperatureValue = Int ( valueStr. trimmingCharacters ( in: CharacterSet ( charactersIn: " \" " ) ) ) ?? 0
216- temperature = Double ( temperatureValue) / 100.0
217- }
218- // --- Parse Serial Number (ioreg) ---
219- if let match = output. range ( of: " \" Serial \" = \" ([^ \" ]+) \" " , options: . regularExpression) {
220- let fullMatch = String ( output [ match] )
221- let pattern = " \" Serial \" = \" ([^ \" ]+) \" "
222- if let regex = try ? NSRegularExpression ( pattern: pattern) ,
223- let nsMatch = regex. firstMatch ( in: fullMatch, range: NSRange ( fullMatch. startIndex... , in: fullMatch) ) ,
224- nsMatch. numberOfRanges > 1 ,
225- let valueRange = Range ( nsMatch. range ( at: 1 ) , in: fullMatch) {
226- serialNumber = String ( fullMatch [ valueRange] )
227- }
228- }
154+
229155 }
230156}
0 commit comments