Kühlbox nur verbinden, während man sie ansieht

Jede Verbindung meldet sich am Display der Box an – sie piept, und wer
gerade davorsteht, wird gestört. Dauerhaft verbunden zu sein ist dort
also nicht unsichtbar wie beim BMS, sondern lästig.

Die Kühlbox wird deshalb nur noch verbunden, solange ihre Ansicht offen
ist: am iPhone über die Detailansicht, an der Uhr über einen eigenen
Befehl, den sie beim Öffnen und Schliessen schickt. Ein Stellbefehl geht
weiterhin immer durch – ist die Box nicht verbunden, wird er gemerkt und
löst den Verbindungsaufbau aus.

Abgeriegelt sind alle drei Wege, über die bisher verbunden wurde: beim
Start, im Takt des Wiederverbindens, und – das war das eigentliche Loch –
sobald das Gerät in den Werbedaten auftaucht. Da die App dauerhaft
scannt, hätte allein dieser Weg die Box weiter angefunkt. Dass sie in
Reichweite ist, ist kein Grund, sie anzufassen.

Auf der Übersicht steht dafür der zuletzt gestellte Stand statt der
Messwerte: Sollwert, Ein/Aus, Eco oder Max, dazu wann er gestellt wurde.
Das bleibt richtig, auch wenn es von gestern ist – ein Sollwert ändert
sich nur, wenn jemand ihn ändert. Eine Innentemperatur von gestern sähe
dagegen aus wie eine von jetzt, und man würde ihr glauben. Genau das
sichern die neuen Prüfungen ab: Hauptwert ist der Sollwert, gemessene
Temperatur und Bordspannung kommen nicht vor.

Der Stand liegt in den Einstellungen (FridgeSettings) mit einem
Zeitstempel, der „zuletzt geändert" bedeutet und nicht „zuletzt gesehen" –
sonst behauptete die Kachel Frische, wo sich nichts getan hat. Die Uhr
bekommt dasselbe Bild: WatchFridge trägt jetzt ein Kennzeichen isLive.

BMS und Neigungsmesser bleiben dauerhaft verbunden. Sie stören nicht, und
ihre Werte will man laufend sehen.

Nicht nachgezogen ist die Android-App – dort verbindet der
BluetoothManager weiterhin dauerhaft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
BiasF
2026-08-31 22:00:10 +02:00
co-authored by Claude Opus 5
parent 8aa8658b5c
commit 5fec4a55dd
16 changed files with 404 additions and 38 deletions
+12
View File
@@ -34,6 +34,18 @@ enum DeviceRole: String, Codable, CaseIterable, Identifiable, Sendable {
}
}
/// Ob nur verbunden wird, während die Ansicht des Geräts offen ist.
///
/// Die Kühlbox meldet jede Verbindung an ihrem Display an und verlangt je
/// nach Modell sogar einen Tastendruck; dauerhaft verbunden zu sein ist
/// dort also nicht unsichtbar, sondern lästig. Ihre Werte ändern sich
/// ausserdem langsam, und was auf der Übersicht zählt der Sollwert
/// ändert sich überhaupt nur, wenn jemand ihn ändert.
///
/// BMS und Neigungsmesser bleiben dagegen verbunden: Sie stören nicht, und
/// ihre Werte will man laufend sehen.
var connectsOnDemand: Bool { self == .fridge }
/// Victron-Geräte werden passiv über das Advertisement gelesen, das Daly BMS
/// braucht eine echte GATT-Verbindung.
var transport: DeviceTransport {
+47
View File
@@ -0,0 +1,47 @@
import Foundation
/// Der zuletzt bekannte Stand einer Kühlbox ohne Messwerte.
///
/// Die Box wird nur noch verbunden, während man ihre Ansicht offen hat: Jede
/// Verbindung meldet sich an ihrem Display an und stört, wer sie gerade
/// bedient. Auf der Übersicht steht deshalb, was zuletzt **eingestellt** war.
///
/// Messwerte gehören bewusst nicht dazu. Eine Innentemperatur von vorgestern
/// sähe aus wie eine von jetzt, und man würde ihr glauben eine Solltemperatur
/// dagegen ändert sich nur, wenn jemand sie ändert.
struct FridgeSettings: Codable, Equatable, Sendable {
var isPoweredOn: Bool
var isEco: Bool
var isLocked: Bool
var isDualZone: Bool
var usesFahrenheit: Bool
var leftTarget: Int?
var rightTarget: Int?
/// Wann dieser Stand von der Box kam.
var updated: Date
var unitSymbol: String { usesFahrenheit ? "°F" : "°C" }
/// In Worten, wie es auf der Kachel steht.
var stateText: String {
guard isPoweredOn else { return "Aus" }
return isEco ? "Eco" : "Max"
}
/// Was die Übersicht zeigt: die Sollwerte, sonst nichts.
func snapshot(deviceID: UUID) -> DeviceSnapshot {
var snapshot = DeviceSnapshot(deviceID: deviceID, timestamp: updated, rssi: nil)
var metrics = [
Metric("target_left", isDualZone ? "Soll links" : "Solltemperatur",
leftTarget.map(Double.init), unit: unitSymbol, precision: 0, primary: true),
]
if isDualZone {
metrics.append(Metric("target_right", "Soll rechts",
rightTarget.map(Double.init), unit: unitSymbol, precision: 0))
}
snapshot.metrics = metrics
snapshot.state = stateText
if isLocked { snapshot.offReasons = ["Bedienfeld gesperrt"] }
return snapshot
}
}
+31
View File
@@ -103,6 +103,14 @@ struct WatchDevice: Codable, Equatable, Identifiable, Sendable {
/// Eine eigene Struktur statt `AlpicoolState`: dort steckt der Rohdatensatz mit
/// drin, den auf die Uhr zu schicken nichts brächte.
struct WatchFridge: Codable, Equatable, Sendable {
/// Ob die Box gerade wirklich verbunden ist.
///
/// Sie wird nur verbunden, während jemand ihre Ansicht offen hat jede
/// Verbindung meldet sich an ihrem Display an. Sonst stehen hier die
/// zuletzt gestellten Werte, und die Messwerte fehlen.
var isLive: Bool
/// Wann der Stand zuletzt von der Box kam.
var updated: Date
var isPoweredOn: Bool
var isEco: Bool
var isLocked: Bool
@@ -133,6 +141,9 @@ enum WatchCommand: Codable, Equatable, Sendable {
case fridgeEco(device: UUID, eco: Bool)
case fridgeLock(device: UUID, locked: Bool)
case fridgeTarget(device: UUID, zone: WatchFridgeZone, value: Int)
/// Die Kühlbox-Ansicht auf der Uhr wurde geöffnet oder geschlossen. Das
/// iPhone verbindet die Box daraufhin oder gibt sie wieder frei.
case fridgeSession(device: UUID, wanted: Bool)
/// Ob der Befehl den schnellen Sendetakt verlängert.
var wantsLiveUpdates: Bool {
@@ -149,6 +160,26 @@ enum WatchFridgeZone: String, Codable, Equatable, Sendable {
// MARK: - Verpacken
extension WatchFridge {
/// Aus dem zuletzt gestellten Stand, ohne Messwerte.
init(_ settings: FridgeSettings) {
self.init(isLive: false,
updated: settings.updated,
isPoweredOn: settings.isPoweredOn,
isEco: settings.isEco,
isLocked: settings.isLocked,
isDualZone: settings.isDualZone,
unitSymbol: settings.unitSymbol,
leftTarget: settings.leftTarget,
leftCurrent: nil,
rightTarget: settings.rightTarget,
rightCurrent: nil,
minTarget: settings.usesFahrenheit ? -22 : -30,
maxTarget: settings.usesFahrenheit ? 68 : 20,
batteryVolts: nil)
}
}
extension WatchPayload {
func encoded() throws -> Data { try WatchCoder.encoder.encode(self) }