Kühlbox: Zonen richtig erkennen, Stellbefehle nicht mehr abschneiden

Zwei Fehler am Gerät gefunden.

Einzonen-Boxen als Doppelzone erkannt: Manche senden den langen
Datensatz einer Zweizonen-Box mit, füllen den zweiten Block aber mit
Nullen. Die Nutzlastlänge allein taugt deshalb nicht als Kriterium. Als
zweizonig gilt jetzt nur, wo dieser Block überhaupt Inhalt hat, und in
den Geräteeinstellungen lässt sich die Erkennung auf "Eine Zone" oder
"Zwei Zonen" festnageln.

Einschalten reagierte nicht: Ein/Aus und Betriebsart schicken den
kompletten Einstellungsblock, der mit dem langen Datensatz auf 31 Byte
kommt. Ohne ausgehandelte MTU nimmt BLE aber nur 20 Nutzbytes je
Schreibvorgang an, der Rest wurde stillschweigend verworfen. Die
Solltemperatur ging als kurzes Paket durch, alles Längere nicht. Lange
Pakete werden jetzt anhand von maximumWriteValueLength aufgeteilt und
versetzt geschrieben; die Statusabfrage danach wartet entsprechend
länger. Aus dem kurzen Datensatz ist der Befehl übrigens genau 20 Byte
groß und hätte gerade noch gepasst - deshalb trat es nur an dieser Box
auf.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
BiasF
2026-08-30 14:21:28 +02:00
co-authored by Claude Opus 5
parent 45d1dc087d
commit daea73d649
9 changed files with 183 additions and 4 deletions
+28 -2
View File
@@ -81,6 +81,10 @@ final class BMSSession: NSObject {
private var jbdState = JBDState()
private var wattCycleState = WattCycleState()
private(set) var alpicoolState = AlpicoolState()
/// Aus den Geräteeinstellungen; übersteuert die automatische Erkennung.
var fridgeZoneMode: FridgeZoneMode = .automatic {
didSet { alpicoolState.zoneMode = fridgeZoneMode }
}
/// Ob die Kühlbox in dieser Sitzung schon angemeldet wurde.
private var didBind = false
private var buffer: [UInt8] = []
@@ -372,7 +376,27 @@ final class BMSSession: NSObject {
guard let endpoint = currentEndpoint, peripheral.state == .connected else { return }
sentFrameCount += 1
lastSendAt = Date()
peripheral.writeValue(data, for: endpoint.write, type: endpoint.writeType)
// Was in einen Schreibvorgang passt, hängt an der ausgehandelten MTU.
// Längeres muss aufgeteilt werden, sonst verwirft das Gerät es
// wortlos der Einstellungsblock einer Kühlbox ist so ein Fall.
let limit = peripheral.maximumWriteValueLength(for: endpoint.writeType)
let pieces = AlpicoolProtocol.chunks(data, limit: limit)
for (index, piece) in pieces.enumerated() {
guard index > 0 else {
peripheral.writeValue(piece, for: endpoint.write, type: endpoint.writeType)
continue
}
DispatchQueue.main.asyncAfter(
deadline: .now() + Double(index) * AlpicoolProtocol.chunkDelay
) { [weak self] in
guard let self, self.peripheral.state == .connected,
let current = self.currentEndpoint else { return }
self.peripheral.writeValue(piece, for: current.write, type: current.writeType)
}
}
// Sofort melden, sonst sieht die Diagnose sekundenlang nach Stillstand
// aus, obwohl gerade gesucht wird.
publishDiagnostics()
@@ -384,7 +408,8 @@ final class BMSSession: NSObject {
/// die Anzeige dem Gerät folgt statt der Vermutung.
func sendControl(_ packet: Data) {
send(packet)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) { [weak self] in
// Genug Abstand, damit ein aufgeteiltes Paket vollständig draußen ist.
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
guard let self, self.dialect == .alpicool else { return }
self.send(AlpicoolProtocol.packet(.query))
}
@@ -403,6 +428,7 @@ final class BMSSession: NSObject {
buffer = fridgeRemainder
adopt(.alpicool)
for frame in fridgeFrames { alpicoolState.apply(frame) }
alpicoolState.zoneMode = fridgeZoneMode
onFridgeState?(alpicoolState)
publish(alpicoolState.snapshot(deviceID: deviceID, rssi: nil),
usable: alpicoolState.hasStatus)