Der Akku bekommt ein Gedächtnis
Verlauf über 24 Stunden, sieben Tage und dreißig Tage, aufgezeichnet alle fünf Minuten. Als Säulen und nicht als Linie: eine Linie verbindet auch dort, wo nichts gemessen wurde, und ein zugeklappter Rechner hatte keinen Ladestand — eine Linie darüber behauptet einen. Dazu Segelmodus und Hitzeschutz als reine Regeln im Helfer. Beide haben zwei Schwellen, eine zum Anhalten und eine zum Weitermachen. Ohne diesen Abstand hinge der Akku am Limit und würde bei jedem verlorenen Prozent nachgeladen — hunderte winzige Ladevorgänge am Tag, und genau die altern ihn. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
edb327cabe
commit
f289d02c37
@@ -66,3 +66,81 @@ public enum ChargeSafety {
|
||||
return percent
|
||||
}
|
||||
}
|
||||
|
||||
/// Segelmodus und Hitzeschutz — die beiden Regeln, die *über* dem Limit liegen.
|
||||
///
|
||||
/// **Warum im Helfer und nicht in der App.** Beide greifen in das Laden ein,
|
||||
/// und ein falscher Eingriff dort ist die eine Operation in diesem Projekt, die
|
||||
/// den Akku dauerhaft beschädigen kann. Was der Helfer tut, ist von außen nicht
|
||||
/// abschaltbar — dieselbe Begründung wie bei `FanSafety`.
|
||||
public enum ChargePolicy {
|
||||
|
||||
/// Wie weit der Ladestand unter das Limit fallen muss, bevor wieder geladen
|
||||
/// wird.
|
||||
///
|
||||
/// **Der eigentliche Zweck des Segelns.** Ohne Abstand hängt der Akku
|
||||
/// dauerhaft am Limit und wird bei jedem verlorenen Prozent nachgeladen —
|
||||
/// hunderte winzige Ladevorgänge am Tag, und genau die altern ihn. Mit
|
||||
/// Abstand lädt er einmal und segelt dann von selbst hinunter.
|
||||
public static let sailingHysteresis = 5
|
||||
|
||||
/// Ab dieser Zellentemperatur wird nicht geladen.
|
||||
///
|
||||
/// Hitze schadet einem Lithium-Akku mehr als jeder Ladestand. Der Wert ist
|
||||
/// bewusst konservativ: macOS selbst drosselt später, aber macOS kennt auch
|
||||
/// die Ladehistorie des Geräts, und wir kennen sie nicht.
|
||||
public static let maximumChargingCelsius = 40.0
|
||||
|
||||
/// Unter dieser Temperatur wird wieder geladen.
|
||||
///
|
||||
/// Auch hier ein Abstand, aus demselben Grund wie beim Segeln: genau an der
|
||||
/// Schwelle schaltete es sonst im Sekundentakt an und aus.
|
||||
public static let resumeChargingCelsius = 36.0
|
||||
|
||||
/// Ob jetzt geladen werden soll.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - charge: Ladestand, 0 bis 1.
|
||||
/// - limit: Das eingestellte Limit in Prozent.
|
||||
/// - celsius: Zellentemperatur, falls bekannt. Unbekannt heißt: kein
|
||||
/// Hitzeschutz — eine Temperatur zu erfinden wäre schlimmer als keine.
|
||||
/// - wasCharging: Ob zuletzt geladen wurde. Daran hängt der Abstand:
|
||||
/// beide Regeln haben zwei Schwellen, eine zum Anhalten und eine zum
|
||||
/// Weitermachen.
|
||||
public static func shouldCharge(charge: Double, limit: Int, celsius: Double?,
|
||||
wasCharging: Bool) -> Bool {
|
||||
if let celsius {
|
||||
if wasCharging, celsius >= maximumChargingCelsius { return false }
|
||||
if !wasCharging, celsius >= resumeChargingCelsius { return false }
|
||||
}
|
||||
|
||||
let percent = Int((min(max(charge, 0), 1) * 100).rounded())
|
||||
// Ein Limit von hundert ist keines — dann gilt nur der Hitzeschutz.
|
||||
guard limit < ChargeSafety.noLimit else { return true }
|
||||
|
||||
if wasCharging { return percent < limit }
|
||||
return percent <= limit - sailingHysteresis
|
||||
}
|
||||
|
||||
/// Warum gerade nicht geladen wird — für die Anzeige.
|
||||
public enum Reason: Equatable, Sendable {
|
||||
case charging
|
||||
case atLimit(Int)
|
||||
case sailing(Int)
|
||||
case tooHot(Double)
|
||||
}
|
||||
|
||||
public static func reason(charge: Double, limit: Int, celsius: Double?,
|
||||
wasCharging: Bool) -> Reason {
|
||||
if let celsius {
|
||||
let threshold = wasCharging ? maximumChargingCelsius : resumeChargingCelsius
|
||||
if celsius >= threshold { return .tooHot(celsius) }
|
||||
}
|
||||
guard shouldCharge(charge: charge, limit: limit, celsius: celsius,
|
||||
wasCharging: wasCharging) else {
|
||||
let percent = Int((min(max(charge, 0), 1) * 100).rounded())
|
||||
return percent >= limit ? .atLimit(limit) : .sailing(limit)
|
||||
}
|
||||
return .charging
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user