Files
macos-stats/Stats/Modules/Battery/BatteryReader.swift
Serhiy Mytrovtsiy b448979aa2 - add temperature module (CPU and GPU temperature);
- fix value margin in network widget view;
- add missing widgets name;
- improve battery module updates;
2020-04-04 11:57:38 +02:00

190 lines
6.4 KiB
Swift

//
// BatteryReader.swift
// Stats
//
// Created by Serhiy Mytrovtsiy on 14/01/2020.
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
import IOKit.ps
struct BatteryUsage {
var powerSource: String = ""
var state: String = ""
var isCharged: Bool = false
var level: Double = 0
var cycles: Int = 0
var health: Int = 0
var amperage: Int = 0
var voltage: Double = 0
var temperature: Double = 0
var ACwatts: Int = 0
var ACstatus: Bool = false
var timeToEmpty: Int = 0
var timeToCharge: Int = 0
}
class BatteryReader: Reader {
public var name: String = "Battery"
public var enabled: Bool = true
public var available: Bool {
get {
if !self.internalChecked {
let snapshot = IOPSCopyPowerSourcesInfo().takeRetainedValue()
let sources = IOPSCopyPowerSourcesList(snapshot).takeRetainedValue() as Array
self.hasInternalBattery = sources.count > 0
self.internalChecked = true
}
return self.hasInternalBattery
}
}
public var optional: Bool = false
public var initialized: Bool = false
public var callback: (BatteryUsage) -> Void = {_ in}
private var service: io_connect_t = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleSmartBattery"))
private var internalChecked: Bool = false
private var hasInternalBattery: Bool = false
private var source: CFRunLoopSource?
private var loop: CFRunLoop?
init(_ updater: @escaping (BatteryUsage) -> Void) {
self.callback = updater
self.read()
}
public func start() {
let context = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
source = IOPSNotificationCreateRunLoopSource({ (context) in
guard let ctx = context else {
return
}
let watcher = Unmanaged<BatteryReader>.fromOpaque(ctx).takeUnretainedValue()
watcher.read()
}, context).takeRetainedValue()
loop = RunLoop.current.getCFRunLoop()
CFRunLoopAddSource(loop, source, .defaultMode)
}
public func stop() {
guard let runLoop = loop, let source = source else {
return
}
CFRunLoopRemoveSource(runLoop, source, .defaultMode)
}
public func read() {
if !self.enabled && self.initialized { return }
self.initialized = true
let psInfo = IOPSCopyPowerSourcesInfo().takeRetainedValue()
let psList = IOPSCopyPowerSourcesList(psInfo).takeRetainedValue() as [CFTypeRef]
for ps in psList {
if let list = IOPSGetPowerSourceDescription(psInfo, ps).takeUnretainedValue() as? Dictionary<String, Any> {
let powerSource = list[kIOPSPowerSourceStateKey] as? String ?? "AC Power"
let state = list[kIOPSBatteryHealthKey] as! String
let isCharged = list[kIOPSIsChargedKey] as? Bool ?? false
var cap = Float(list[kIOPSCurrentCapacityKey] as! Int) / 100
let timeToEmpty = Int(list[kIOPSTimeToEmptyKey] as! Int)
let timeToCharged = Int(list[kIOPSTimeToFullChargeKey] as! Int)
let cycles = self.getIntValue("CycleCount" as CFString) ?? 0
let maxCapacity = self.getIntValue("MaxCapacity" as CFString) ?? 1
let designCapacity = self.getIntValue("DesignCapacity" as CFString) ?? 1
let amperage = self.getIntValue("Amperage" as CFString) ?? 0
let voltage = self.getVoltage() ?? 0
let temperature = self.getTemperature() ?? 0
var ACwatts: Int = 0
if let ACDetails = IOPSCopyExternalPowerAdapterDetails() {
if let ACList = ACDetails.takeUnretainedValue() as? Dictionary<String, Any> {
guard let watts = ACList[kIOPSPowerAdapterWattsKey] else {
return
}
ACwatts = Int(watts as! Int)
}
}
let ACstatus = self.getBoolValue("IsCharging" as CFString) ?? false
if powerSource == "Battery Power" {
cap = 0 - cap
}
DispatchQueue.main.async(execute: {
let usage = BatteryUsage(
powerSource: powerSource,
state: state,
isCharged: isCharged,
level: Double(cap),
cycles: cycles,
health: (100 * maxCapacity) / designCapacity,
amperage: amperage,
voltage: voltage,
temperature: temperature,
ACwatts: ACwatts,
ACstatus: ACstatus,
timeToEmpty: timeToEmpty,
timeToCharge: timeToCharged
)
self.callback(usage)
})
}
}
}
public func toggleEnable(_ value: Bool) {
self.enabled = value
}
private func getBoolValue(_ forIdentifier: CFString) -> Bool? {
if let value = IORegistryEntryCreateCFProperty(self.service, forIdentifier, kCFAllocatorDefault, 0) {
return value.takeRetainedValue() as? Bool
}
return nil
}
private func getIntValue(_ identifier: CFString) -> Int? {
if let value = IORegistryEntryCreateCFProperty(self.service, identifier, kCFAllocatorDefault, 0) {
return value.takeRetainedValue() as? Int
}
return nil
}
private func getDoubleValue(_ identifier: CFString) -> Double? {
if let value = IORegistryEntryCreateCFProperty(self.service, identifier, kCFAllocatorDefault, 0) {
return value.takeRetainedValue() as? Double
}
return nil
}
private func getVoltage() -> Double? {
if let value = self.getDoubleValue("Voltage" as CFString) {
return value / 1000.0
}
return nil
}
private func getTemperature() -> Double? {
if let value = IORegistryEntryCreateCFProperty(self.service, "Temperature" as CFString, kCFAllocatorDefault, 0) {
return value.takeRetainedValue() as! Double / 100.0
}
return nil
}
}