Ladelimit: Key gemessen, Schreibpfad gebaut
Der Key heißt CHLT, drei Bytes. Identifiziert per Differenzmessung gegen macOS' eigene Ladebegrenzung, ohne einen einzigen Schreibzugriff: 80 % → 50 01 00 95 % → 5F 01 00 85 % → 55 01 00 Erstes Byte ist die Prozentzahl, zweites offenbar „Limit aktiv", drittes unbenutzt. Drei Messpunkte, weil einer Zufall sein kann: BACC hatte beim ersten Vergleich ebenfalls gepasst — ein Zähler, in dem gerade 50 stand. ChargerConfiguration aus ioreg sah zunächst wie eine zweite Quelle aus (niedriges Byte 0x50 = 80), blieb aber unverändert, als CHLT längst auf 95 stand. Also Zufall, und gut, dass darauf nichts gebaut wurde. Geschrieben wird nur das erste Byte, und nur zwischen 80 und 100 — dem Bereich, den macOS selbst anbietet und in dem gemessen wurde. Darunter ist ungemessenes Gebiet. Bei den Lüftern liefert die Firmware mit F0Mn/F0Mx eigene Grenzen mit, an denen sich ein Sicherheitsnetz festhalten kann; hier gibt es das nicht, hier gibt es nur die Messung. Ein falscher Wert im Lade-Subsystem ist die eine Operation in diesem Projekt, die den Akku dauerhaft beschädigen kann. Nach jedem Schreiben wird zurückgelesen und gemeldet, was tatsächlich steht — nicht, was gewünscht war. „Kein Limit" heißt 100 Prozent und nicht das Aktiv-Byte auf null: diese Kodierung hat macOS nie geschrieben, sie wäre also ungemessen. Bedienung im Lüfter-Bereich der Einstellungen und in der Lüfterkachel, also auch im Menüleisten-Popover. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -119,6 +119,7 @@ public final class FanControl {
|
||||
/// abstürzt, beendet wird oder hängt — fallen die Lüfter im Helfer nach
|
||||
/// fünf Sekunden auf Automatik zurück.
|
||||
public func start() {
|
||||
refreshChargeLimit()
|
||||
guard installState == .installed, heartbeatTimer == nil else { return }
|
||||
|
||||
heartbeatTimer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { _ in
|
||||
@@ -160,4 +161,25 @@ public final class FanControl {
|
||||
Task { @MainActor in self?.apply(data) }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Ladelimit
|
||||
|
||||
/// Das eingestellte Ladelimit in Prozent. `nil` heißt: keines, oder noch
|
||||
/// nicht gelesen.
|
||||
public private(set) var chargeLimit: Int?
|
||||
|
||||
public func refreshChargeLimit() {
|
||||
proxy()?.chargeLimit { [weak self] percent in
|
||||
Task { @MainActor in self?.chargeLimit = percent > 0 ? percent : nil }
|
||||
}
|
||||
}
|
||||
|
||||
/// Setzt das Limit und übernimmt, was der Helfer **zurückgelesen** hat —
|
||||
/// nicht das, was gewünscht war. Ein Schreibvorgang, den der SMC verwirft,
|
||||
/// darf sich in der Oberfläche nicht als Erfolg zeigen.
|
||||
public func setChargeLimit(_ percent: Int) {
|
||||
proxy()?.setChargeLimit(percent: percent) { [weak self] result in
|
||||
Task { @MainActor in self?.chargeLimit = result > 0 ? result : nil }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user