forked from fritob/Camper-Monitor
Kühlbox wieder bedienbar, Einbaulage richtig messen
Zwei Fehler aus dem letzten Stand, beide von mir eingebaut. Die Kühlbox liess sich nicht mehr bedienen. Die Wartesperre gegen den Verbindungssturm hat nicht unterschieden, warum eine Verbindung endete: ein Abbruch einer Verbindung, die stand, wurde genauso lange gesperrt wie ein Fehlschlag. Im Fahrzeug ist so ein Abbruch der Normalfall, und die Box war danach bis zu einer Minute nicht erreichbar. Ein Tippen ging in dieser Zeit lautlos verloren, weil es ohne Sitzung ins Leere fiel. Jetzt zählt die Dauer: was mindestens drei Sekunden stand, gilt als zustandegekommen und wird nach zwei Sekunden wieder aufgenommen. Nur was sofort wieder abbricht, zählt als Fehlschlag und wird gestaffelt gesperrt - gegen das Wechselspiel, wenn eine andere App das Gerät belegt. Dazu gehen Stellbefehle nicht mehr verloren. Fehlt die Verbindung, wird der Befehl aufgehoben, die Sperre für dieses Gerät aufgehoben und sofort ein Versuch angestossen; sobald der Kanal steht, geht er raus. Ein Befehl, der älter als eine halbe Minute ist, verfällt - ein verspätetes Ausschalten würde nur überraschen. Die Ansicht sagt es, statt den Schalter stumm zurückspringen zu lassen. Zweitens der Assistent für die Einbaulage. Er hat die zweite Bewegung von der gekippten Lage aus gemessen statt von der Ruhelage. Damit steckte in jeder zweiten Messung das Zurückkippen aus der ersten, und es schlugen immer beide Achsen aus. Die Ruhelage ist jetzt der Bezug für beide Bewegungen, und ein eigener Schritt dazwischen nimmt sie neu auf. Auch die Auswertung war zu streng: sie hat jede Bewegung für sich beurteilt und abgelehnt, sobald die andere Achse mitlief. Von Hand gekippt läuft sie aber immer mit. Beurteilt werden jetzt beide Bewegungen gemeinsam - es gewinnt die Zuordnung, die zusammen am meisten erklärt. Zweimal um dieselbe Achse gekippt wird weiterhin erkannt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -96,6 +96,12 @@ final class BMSSession: NSObject {
|
||||
private var lastResponse: Data?
|
||||
private var lastCommand: Data?
|
||||
private var lastCommandAt: Date?
|
||||
/// Stellbefehle, die kamen, bevor der Kanal stand. Sie jetzt schon zu
|
||||
/// senden hiesse, sie an einen womöglich falschen Kandidaten zu schicken;
|
||||
/// sie fallen zu lassen hiesse, ein Tippen zu verschlucken.
|
||||
private var waitingControls: [(packet: Data, queuedAt: Date)] = []
|
||||
/// So lange darf ein Befehl warten, bevor er verfällt.
|
||||
private let controlLifetime: TimeInterval = 30
|
||||
private var receivedByteCount = 0
|
||||
private var sentFrameCount = 0
|
||||
private var gattSummary: [String] = []
|
||||
@@ -291,6 +297,7 @@ final class BMSSession: NSObject {
|
||||
|
||||
private func beginPolling() {
|
||||
pollTimer?.cancel()
|
||||
flushWaitingControls()
|
||||
poll()
|
||||
let interval = dialect == .unknown ? searchInterval : pollInterval
|
||||
let timer = DispatchSource.makeTimerSource(queue: queue)
|
||||
@@ -419,6 +426,18 @@ final class BMSSession: NSObject {
|
||||
func sendControl(_ packet: Data) {
|
||||
lastCommand = packet
|
||||
lastCommandAt = Date()
|
||||
|
||||
// Erst wenn der Dialekt steht, ist auch der richtige Kanal bekannt.
|
||||
guard dialect == .alpicool, currentEndpoint != nil,
|
||||
peripheral.state == .connected else {
|
||||
waitingControls.append((packet, Date()))
|
||||
if waitingControls.count > 4 {
|
||||
waitingControls.removeFirst(waitingControls.count - 4)
|
||||
}
|
||||
publishDiagnostics()
|
||||
return
|
||||
}
|
||||
|
||||
send(packet)
|
||||
// Genug Abstand, damit ein aufgeteiltes Paket vollständig draußen ist.
|
||||
queue.asyncAfter(deadline: .now() + 1.0) { [weak self] in
|
||||
@@ -427,6 +446,20 @@ final class BMSSession: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
/// Schickt raus, was während des Verbindungsaufbaus aufgelaufen ist.
|
||||
private func flushWaitingControls() {
|
||||
guard dialect == .alpicool, !waitingControls.isEmpty else { return }
|
||||
let due = waitingControls.filter {
|
||||
Date().timeIntervalSince($0.queuedAt) < controlLifetime
|
||||
}
|
||||
waitingControls.removeAll()
|
||||
for (index, entry) in due.enumerated() {
|
||||
queue.asyncAfter(deadline: .now() + Double(index) * 0.4) { [weak self] in
|
||||
self?.sendControl(entry.packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Auswertung
|
||||
private func consume(_ data: Data) {
|
||||
lastResponse = data
|
||||
|
||||
Reference in New Issue
Block a user