BMS: Warten auf die Notify-Bestätigung nicht mehr endlos
Die Suche nach dem richtigen Verbindungsweg blieb auf dem ersten Kandidaten stehen: gesendet wurde erst, nachdem iOS das Abonnieren der Notify-Charakteristik bestätigt hat. Bestätigt ein Modul das nie – bei der WattCycle-Batterie auf FFF1 der Fall –, wurde weder je eine Anfrage geschickt noch zum nächsten Kandidaten gewechselt. Die Diagnose zeigte dauerhaft "1 von 16" und "0 Anfragen / 0 Byte". Die Aktivierung eines Kandidaten hat jetzt ein Zeitlimit von vier Sekunden. Läuft es ab, geht es zum nächsten; ist es der einzige Weg, wird trotzdem gesendet – manche Module antworten auch ohne bestätigtes Abonnement. Ein Token verhindert, dass verspätete Rückmeldungen eines bereits verworfenen Kandidaten den aktuellen durcheinanderbringen. Damit sich Fortschritt überhaupt beobachten lässt, wird die Diagnose jetzt bei jeder gesendeten Anfrage aktualisiert statt nur am Ende einer Runde, und zeigt zusätzlich Verbindungszustand, ob der Empfang abonniert ist, und wann zuletzt gesendet wurde. Die Suche ist enger getaktet, damit alle Wege in gut zwei Minuten durch sind. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -70,11 +70,16 @@ final class BMSSession: NSObject {
|
||||
private var gattSummary: [String] = []
|
||||
/// Runden ohne verwertbare Antwort auf dem aktuellen Kandidaten.
|
||||
private var silentRounds = 0
|
||||
/// Zählt hoch, sobald ein Kandidat aktiviert wird. Späte Rückmeldungen
|
||||
/// eines bereits verworfenen Kandidaten lassen sich so ignorieren.
|
||||
private var activationToken = 0
|
||||
private var isNotifyActive = false
|
||||
private var lastSendAt: Date?
|
||||
|
||||
/// Abstand zwischen zwei Abfragerunden im Normalbetrieb.
|
||||
var pollInterval: TimeInterval = 5
|
||||
/// Kürzer, solange noch gesucht wird – sonst dauert das Durchprobieren lang.
|
||||
private var searchInterval: TimeInterval = 8
|
||||
private var searchInterval: TimeInterval = 6
|
||||
|
||||
private var currentEndpoint: Endpoint? {
|
||||
endpoints.indices.contains(endpointIndex) ? endpoints[endpointIndex] : nil
|
||||
@@ -175,9 +180,26 @@ final class BMSSession: NSObject {
|
||||
private func activateCurrentEndpoint() {
|
||||
guard let endpoint = currentEndpoint else { return }
|
||||
silentRounds = 0
|
||||
isNotifyActive = false
|
||||
buffer.removeAll()
|
||||
activationToken += 1
|
||||
let token = activationToken
|
||||
|
||||
peripheral.setNotifyValue(true, for: endpoint.notify)
|
||||
publishDiagnostics()
|
||||
|
||||
// Manche Module bestätigen das Abonnieren nie. Ohne Zeitlimit bliebe
|
||||
// die Suche hier für immer stehen, ohne je etwas zu senden.
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 4) { [weak self] in
|
||||
guard let self, self.activationToken == token, !self.isNotifyActive else { return }
|
||||
if self.endpoints.count > 1 {
|
||||
self.advanceEndpoint()
|
||||
} else {
|
||||
// Einziger Weg – trotzdem versuchen zu senden, vielleicht
|
||||
// antwortet das Gerät auch ohne bestätigtes Abonnement.
|
||||
self.beginPolling()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wechselt auf den nächsten Kandidaten. Sind alle durch, wird von vorn
|
||||
@@ -214,7 +236,7 @@ final class BMSSession: NSObject {
|
||||
DalyProtocol.requestFrame(.soc),
|
||||
JBDProtocol.requestFrame(.basicInfo),
|
||||
DalyProtocol.modbusReadFrame(),
|
||||
], spacing: 1.2, thenGiveUpAfter: 5)
|
||||
], spacing: 0.6, thenGiveUpAfter: 3)
|
||||
case .dalyClassic:
|
||||
sendSequence(DalyProtocol.Command.allCases.map { DalyProtocol.requestFrame($0) },
|
||||
spacing: 0.25, thenGiveUpAfter: 2)
|
||||
@@ -269,7 +291,11 @@ final class BMSSession: NSObject {
|
||||
private func send(_ data: Data) {
|
||||
guard let endpoint = currentEndpoint, peripheral.state == .connected else { return }
|
||||
sentFrameCount += 1
|
||||
lastSendAt = Date()
|
||||
peripheral.writeValue(data, for: endpoint.write, type: endpoint.writeType)
|
||||
// Sofort melden, sonst sieht die Diagnose sekundenlang nach Stillstand
|
||||
// aus, obwohl gerade gesucht wird.
|
||||
publishDiagnostics()
|
||||
}
|
||||
|
||||
// MARK: - Auswertung
|
||||
@@ -335,9 +361,12 @@ final class BMSSession: NSObject {
|
||||
endpointLabel: currentEndpoint?.label,
|
||||
endpointPosition: endpoints.isEmpty ? nil : .init(endpointIndex + 1, endpoints.count),
|
||||
serviceUUID: currentEndpoint?.write.service?.uuid.uuidString,
|
||||
isConnected: peripheral.state == .connected,
|
||||
isNotifyActive: isNotifyActive,
|
||||
gattSummary: gattSummary,
|
||||
sentFrames: sentFrameCount,
|
||||
receivedBytes: receivedByteCount,
|
||||
lastSendAt: lastSendAt,
|
||||
lastResponseHex: lastResponse.map { $0.map { String(format: "%02X", $0) }.joined(separator: " ") },
|
||||
updated: Date()
|
||||
))
|
||||
@@ -402,6 +431,8 @@ extension BMSSession: CBPeripheralDelegate {
|
||||
return
|
||||
}
|
||||
if characteristic.isNotifying, characteristic == currentEndpoint?.notify {
|
||||
isNotifyActive = true
|
||||
publishDiagnostics()
|
||||
beginPolling()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,10 +73,14 @@ struct BMSDiagnostics: Hashable {
|
||||
/// Der wievielte von wie vielen Kandidaten das ist.
|
||||
var endpointPosition: Pair?
|
||||
var serviceUUID: String?
|
||||
var isConnected: Bool
|
||||
/// Ob das Gerät das Abonnieren der Notify-Charakteristik bestätigt hat.
|
||||
var isNotifyActive: Bool
|
||||
/// Vollständiger Dienst-/Merkmalsbaum des Geräts.
|
||||
var gattSummary: [String]
|
||||
var sentFrames: Int
|
||||
var receivedBytes: Int
|
||||
var lastSendAt: Date?
|
||||
var lastResponseHex: String?
|
||||
var updated: Date
|
||||
|
||||
|
||||
Reference in New Issue
Block a user