forked from fritob/Camper-Monitor
Kühlbox: weniger Piepen, Diagnose zum Weitergeben
Dein Befund grenzt es ein: die Solltemperatur lässt sich stellen, also kommen Schreibvorgänge an. Es scheitert nur am Einstellungsblock. Das mehrfache Piepen war meine Schuld. Beantwortet die Box die Anmeldung nicht, habe ich sie vor jedem Stellbefehl erneut angemeldet - und die Box quittiert jede Anmeldung mit einem Ton. Jetzt geschieht das höchstens einmal je Verbindung. Weiter komme ich nicht ohne die Antwort deiner Box. Ihre Länge entscheidet darüber, wie lang der Stellbefehl sein muss, und sie steht jetzt als "Statusdaten der Box" in der Diagnose. Dazu ein Knopf, der alles zusammen in die Zwischenablage legt - Merkmalsbaum, Weg, Schreibart, letzten Befehl und letzte Antwort. Das abzutippen wäre zuviel verlangt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -95,6 +95,9 @@ final class BMSSession: NSObject {
|
|||||||
/// teils auch unangemeldet an, Stellbefehle nicht – deshalb wird vor
|
/// teils auch unangemeldet an, Stellbefehle nicht – deshalb wird vor
|
||||||
/// einem Befehl notfalls noch einmal angemeldet.
|
/// einem Befehl notfalls noch einmal angemeldet.
|
||||||
private(set) var bindAcknowledged = false
|
private(set) var bindAcknowledged = false
|
||||||
|
/// Ob vor einem Stellbefehl schon einmal nachgemeldet wurde. Jedes Mal
|
||||||
|
/// anzumelden lässt die Box bei jedem Tastendruck erneut piepen.
|
||||||
|
private var didRebindForControl = false
|
||||||
private var buffer: [UInt8] = []
|
private var buffer: [UInt8] = []
|
||||||
private var pollTimer: DispatchSourceTimer?
|
private var pollTimer: DispatchSourceTimer?
|
||||||
private var lastResponse: Data?
|
private var lastResponse: Data?
|
||||||
@@ -257,6 +260,7 @@ final class BMSSession: NSObject {
|
|||||||
didUnlock = false
|
didUnlock = false
|
||||||
didBind = false
|
didBind = false
|
||||||
bindAcknowledged = false
|
bindAcknowledged = false
|
||||||
|
didRebindForControl = false
|
||||||
buffer.removeAll()
|
buffer.removeAll()
|
||||||
activationToken += 1
|
activationToken += 1
|
||||||
let token = activationToken
|
let token = activationToken
|
||||||
@@ -486,9 +490,12 @@ final class BMSSession: NSObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hat die Box die Anmeldung nie beantwortet, wird sie hier nachgeholt.
|
// Hat die Box die Anmeldung nie beantwortet, wird sie einmal je
|
||||||
// Ein Stellbefehl an eine unangemeldete Box wird sonst verworfen.
|
// Verbindung nachgeholt. Ein Stellbefehl an eine unangemeldete Box
|
||||||
guard bindAcknowledged else {
|
// wird sonst womöglich verworfen - jedes Mal anzumelden lässt die Box
|
||||||
|
// aber bei jedem Tastendruck zusätzlich piepen.
|
||||||
|
guard bindAcknowledged || didRebindForControl else {
|
||||||
|
didRebindForControl = true
|
||||||
send(AlpicoolProtocol.packet(.bind))
|
send(AlpicoolProtocol.packet(.bind))
|
||||||
queue.asyncAfter(deadline: .now() + 0.4) { [weak self] in
|
queue.asyncAfter(deadline: .now() + 0.4) { [weak self] in
|
||||||
self?.deliverControl(packet, attempt: 0)
|
self?.deliverControl(packet, attempt: 0)
|
||||||
@@ -671,6 +678,8 @@ final class BMSSession: NSObject {
|
|||||||
isBound: dialect == .alpicool ? bindAcknowledged : nil,
|
isBound: dialect == .alpicool ? bindAcknowledged : nil,
|
||||||
confirmedWrites: confirmedWrites,
|
confirmedWrites: confirmedWrites,
|
||||||
lastWriteError: lastWriteError,
|
lastWriteError: lastWriteError,
|
||||||
|
fridgePayloadHex: alpicoolState.lastPayload.isEmpty ? nil
|
||||||
|
: alpicoolState.lastPayload.map { String(format: "%02X", $0) }.joined(separator: " "),
|
||||||
gattSummary: gattSummary,
|
gattSummary: gattSummary,
|
||||||
sentFrames: sentFrameCount,
|
sentFrames: sentFrameCount,
|
||||||
receivedBytes: receivedByteCount,
|
receivedBytes: receivedByteCount,
|
||||||
|
|||||||
@@ -86,6 +86,10 @@ struct BMSDiagnostics: Hashable {
|
|||||||
var confirmedWrites: Int
|
var confirmedWrites: Int
|
||||||
/// Fehler des letzten bestätigten Schreibvorgangs.
|
/// Fehler des letzten bestätigten Schreibvorgangs.
|
||||||
var lastWriteError: String?
|
var lastWriteError: String?
|
||||||
|
/// Nur bei Kühlboxen: die vollständige Nutzlast der letzten Statusantwort.
|
||||||
|
/// Daran hängt die Länge des Stellbefehls, deshalb gehört sie in die
|
||||||
|
/// Diagnose.
|
||||||
|
var fridgePayloadHex: String?
|
||||||
/// Vollständiger Dienst-/Merkmalsbaum des Geräts.
|
/// Vollständiger Dienst-/Merkmalsbaum des Geräts.
|
||||||
var gattSummary: [String]
|
var gattSummary: [String]
|
||||||
var sentFrames: Int
|
var sentFrames: Int
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ struct DeviceDetailView: View {
|
|||||||
@State private var editedName = ""
|
@State private var editedName = ""
|
||||||
@State private var keyInput = ""
|
@State private var keyInput = ""
|
||||||
@State private var showDeleteConfirmation = false
|
@State private var showDeleteConfirmation = false
|
||||||
|
@State private var didCopyReport = false
|
||||||
@AppStorage(AppSettings.showDiagnosticsKey) private var showDiagnostics = false
|
@AppStorage(AppSettings.showDiagnosticsKey) private var showDiagnostics = false
|
||||||
|
|
||||||
/// Ob die technischen Angaben eingeblendet werden.
|
/// Ob die technischen Angaben eingeblendet werden.
|
||||||
@@ -272,6 +273,33 @@ struct DeviceDetailView: View {
|
|||||||
return String(format: "0x%02X", byte)
|
return String(format: "0x%02X", byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Alles auf einmal, zum Weitergeben. Einzeln abzutippen ist zuviel
|
||||||
|
/// verlangt, und gerade der Merkmalsbaum ist zu lang dafür.
|
||||||
|
private func report(_ info: BMSDiagnostics) -> String {
|
||||||
|
var lines = [
|
||||||
|
"Gerät: \(currentDevice.name) (\(currentDevice.role.title))",
|
||||||
|
"Protokoll: \(info.dialect)",
|
||||||
|
"Verbunden: \(info.isConnected ? "ja" : "nein")",
|
||||||
|
"Empfang abonniert: \(info.isNotifyActive ? "ja" : "nein")",
|
||||||
|
]
|
||||||
|
if let position = info.endpointPosition {
|
||||||
|
lines.append("Weg: \(position.index) von \(position.total)")
|
||||||
|
}
|
||||||
|
if let endpoint = info.endpointLabel { lines.append("Merkmal: \(endpoint)") }
|
||||||
|
if let isBound = info.isBound { lines.append("Angemeldet: \(isBound ? "ja" : "nein")") }
|
||||||
|
lines.append("Gesendet: \(info.sentFrames) · empfangen: \(info.receivedBytes) Byte"
|
||||||
|
+ " · bestätigt: \(info.confirmedWrites)")
|
||||||
|
if let writeError = info.lastWriteError { lines.append("Schreibfehler: \(writeError)") }
|
||||||
|
if let command = info.lastCommandHex { lines.append("Letzter Stellbefehl: \(command)") }
|
||||||
|
if let hex = info.lastResponseHex { lines.append("Letzte Antwort: \(hex)") }
|
||||||
|
if let payload = info.fridgePayloadHex { lines.append("Statusdaten: \(payload)") }
|
||||||
|
if !info.gattSummary.isEmpty {
|
||||||
|
lines.append("Merkmale:")
|
||||||
|
lines.append(contentsOf: info.gattSummary)
|
||||||
|
}
|
||||||
|
return lines.joined(separator: "\n")
|
||||||
|
}
|
||||||
|
|
||||||
/// Welches Protokoll das BMS spricht und was zuletzt ankam.
|
/// Welches Protokoll das BMS spricht und was zuletzt ankam.
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var bmsDiagnosticsSection: some View {
|
private var bmsDiagnosticsSection: some View {
|
||||||
@@ -350,6 +378,22 @@ struct DeviceDetailView: View {
|
|||||||
.textSelection(.enabled)
|
.textSelection(.enabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let payload = info.fridgePayloadHex {
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Text("Statusdaten der Box (\(payload.split(separator: " ").count) Byte)")
|
||||||
|
Text(payload)
|
||||||
|
.font(.caption.monospaced())
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.textSelection(.enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
UIPasteboard.general.string = report(info)
|
||||||
|
didCopyReport = true
|
||||||
|
} label: {
|
||||||
|
Label(didCopyReport ? "Diagnose kopiert" : "Diagnose kopieren",
|
||||||
|
systemImage: didCopyReport ? "checkmark" : "doc.on.doc")
|
||||||
|
}
|
||||||
} header: {
|
} header: {
|
||||||
Text("Diagnose")
|
Text("Diagnose")
|
||||||
} footer: {
|
} footer: {
|
||||||
|
|||||||
Reference in New Issue
Block a user