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
+55
View File
@@ -506,11 +506,66 @@ for frame in AlpicoolProtocol.extractFrames(from:
dual.apply(frame)
}
checkEqual("Zweizonen-Box erkannt", dual.isDualZone, true)
dual.zoneMode = .single
checkEqual("auch eine echte Zweizonen-Box laesst sich auf eine reduzieren",
dual.isDualZone, false)
dual.zoneMode = .automatic
checkEqual("Soll rechts negativ", dual.rightTarget, -2)
checkEqual("Ist rechts negativ", dual.rightCurrent, -5)
checkEqual("Zustand meldet laufenden Kompressor",
dual.snapshot(deviceID: UUID(), rssi: nil).state, "Kühlt (Eco)")
// Einzonen-Box, die den langen Datensatz trotzdem sendet: der zweite Block
// ist dann leer und darf nicht als zweite Zone durchgehen.
var paddedPayload = fridgePayload
paddedPayload += [UInt8](repeating: 0, count: 10)
var padded = AlpicoolState()
for frame in AlpicoolProtocol.extractFrames(from:
alpicoolResponse(command: 0x01, payload: paddedPayload)).frames {
padded.apply(frame)
}
checkEqual("leerer zweiter Block gilt nicht als zweite Zone", padded.isDualZone, false)
// Schreibgrenze: ein Stellbefehl aus dem kurzen Datensatz passt mit genau
// 20 Byte noch in einen Schreibvorgang, aus dem langen nicht mehr. Ohne
// Aufteilen verwirft die Box ihn wortlos genau das war der Fehler.
checkEqual("Stellbefehl aus dem kurzen Datensatz", offBytes.count, 20)
guard let longPacket = padded.settingsCommand(poweredOn: true) else {
check("Stellbefehl aus dem langen Datensatz wird gebaut", false); exit(1)
}
check("aus dem langen Datensatz wird er zu lang für einen Schreibvorgang",
longPacket.count > 20, "-> \(longPacket.count) Byte")
let pieces = AlpicoolProtocol.chunks(longPacket, limit: 20)
checkEqual("wird in zwei Stücke geteilt", pieces.count, 2)
checkEqual("kein Stück überschreitet die Grenze", pieces.allSatisfy { $0.count <= 20 }, true)
checkEqual("zusammengesetzt ergibt sich das Original",
[UInt8](pieces.reduce(Data(), +)), [UInt8](longPacket))
checkEqual("kurze Pakete bleiben ungeteilt",
AlpicoolProtocol.chunks(AlpicoolProtocol.packet(.query), limit: 20).count, 1)
checkEqual("Einschaltbyte im langen Befehl", [UInt8](longPacket)[5], 0x01)
checkEqual("Hauptwert heisst dann schlicht Temperatur",
padded.snapshot(deviceID: UUID(), rssi: nil)
.metrics.first { $0.key == "temp_left" }?.label, "Temperatur")
checkEqual("keine Werte der rechten Zone in den Messwerten",
padded.snapshot(deviceID: UUID(), rssi: nil)
.metrics.contains { $0.key == "temp_right" }, false)
// Dasselbe mit 0xFF gefülltem zweiten Block.
var ffPayload = fridgePayload
ffPayload += [UInt8](repeating: 0xFF, count: 10)
var ffState = AlpicoolState()
for frame in AlpicoolProtocol.extractFrames(from:
alpicoolResponse(command: 0x01, payload: ffPayload)).frames {
ffState.apply(frame)
}
checkEqual("mit 0xFF gefuellter Block gilt nicht als zweite Zone", ffState.isDualZone, false)
// Handeinstellung schlaegt die Erkennung in beide Richtungen.
padded.zoneMode = .dual
checkEqual("Handeinstellung erzwingt zwei Zonen", padded.isDualZone, true)
padded.zoneMode = .single
checkEqual("Handeinstellung erzwingt eine Zone", padded.isDualZone, false)
// Echo und Status in einer Benachrichtigung: die Box antwortet auf
// Stellbefehle mit zwei aneinandergehängten Paketen.
let echoThenStatus = alpicoolResponse(command: 0x02, payload: [0x01]) + fridgeStream