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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user