24
24
import Foundation
25
25
import SwiftUI
26
26
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.
32
32
class BatteryInfoManager : ObservableObject {
33
- /// The current maximum capacity of the battery in mAh (AppleRawMaxCapacity).
33
+ // The current maximum capacity of the battery in mAh (AppleRawMaxCapacity).
34
34
@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.
36
36
@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.
38
38
@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`.
40
40
@Published var health : Double = 0.0
41
- /// Indicates whether the battery is currently charging.
41
+ // Indicates whether the battery is currently charging.
42
42
@Published var isCharging : Bool = false
43
- /// The current charge percentage of the battery (CurrentCapacity).
43
+ // The current charge percentage of the battery (CurrentCapacity).
44
44
@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.
46
46
@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.
48
48
@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.
50
50
@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.
52
52
@Published var inputwatt : Double = 0.0
53
- /// The battery's internal temperature in degrees Celsius.
53
+ // The battery's internal temperature in degrees Celsius.
54
54
@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.
56
56
@Published var batteryPower : Double = 0.0
57
- /// The current voltage of the battery in Volts.
57
+ // The current voltage of the battery in Volts.
58
58
@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.
60
60
@Published var batteryAmperage : Double = 0.0
61
- /// The serial number of the battery.
61
+ // The serial number of the battery.
62
62
@Published var serialNumber : String = " -- "
63
63
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.
65
68
init ( ) {
66
69
updateBatteryInfo ( )
67
70
}
68
71
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.
74
77
func updateBatteryInfo( ) {
75
78
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
-
88
79
let process = Process ( )
89
80
process. executableURL = URL ( fileURLWithPath: " /bin/zsh " )
90
81
process. arguments = [ " -c " , " ioreg -r -c AppleSmartBattery | grep -E 'DesignCapacity|CycleCount|Serial|Temperature|CurrentCapacity|AppleRawMaxCapacity' " ]
91
82
let pipe = Pipe ( )
92
83
process. standardOutput = pipe
93
84
94
85
do {
95
- try po. run ( )
96
- po. waitUntilExit ( )
97
- let dt = pp. fileHandleForReading. readDataToEndOfFile ( )
98
- var output = String ( data: dt, encoding: . utf8) ?? " "
99
-
100
86
try process. run ( )
101
87
process. waitUntilExit ( )
102
88
let data = pipe. fileHandleForReading. readDataToEndOfFile ( )
103
- output + = String ( data: data, encoding: . utf8) ?? " "
89
+ let output = String ( data: data, encoding: . utf8) ?? " "
104
90
105
91
await parseBatteryInfo ( from: output)
106
92
} catch {
@@ -109,19 +95,48 @@ class BatteryInfoManager: ObservableObject {
109
95
}
110
96
}
111
97
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.
118
98
@MainActor
119
99
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
+
120
134
// --- Parse Design Capacity (ioreg) ---
121
135
if let match = output. range ( of: " \" DesignCapacity \" = ([0-9]+) " , options: . regularExpression) {
122
136
let valueStr = String ( output [ match] ) . components ( separatedBy: " = " ) . last? . trimmingCharacters ( in: . whitespaces) ?? " 0 "
123
137
designCapacity = Int ( valueStr. trimmingCharacters ( in: CharacterSet ( charactersIn: " \" " ) ) ) ?? 0
124
138
}
139
+
125
140
// --- Parse Current Max Capacity & Calculate Health (ioreg) ---
126
141
if let match = output. range ( of: " \" AppleRawMaxCapacity \" = ([0-9]+) " , options: . regularExpression) {
127
142
let valueStr = String ( output [ match] ) . components ( separatedBy: " = " ) . last? . trimmingCharacters ( in: . whitespaces) ?? " 0 "
@@ -130,101 +145,12 @@ class BatteryInfoManager: ObservableObject {
130
145
health = ( Double ( batteryCapacity) / Double( designCapacity) ) * 100
131
146
}
132
147
}
148
+
133
149
// --- Parse Cycle Count (ioreg) ---
134
150
if let match = output. range ( of: " \" CycleCount \" = ([0-9]+) " , options: . regularExpression) {
135
151
let valueStr = String ( output [ match] ) . components ( separatedBy: " = " ) . last? . trimmingCharacters ( in: . whitespaces) ?? " 0 "
136
152
cycleCount = Int ( valueStr. trimmingCharacters ( in: CharacterSet ( charactersIn: " \" " ) ) ) ?? 0
137
153
}
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
+
229
155
}
230
156
}
0 commit comments