diff --git a/CamperMonitor/Bluetooth/BluetoothManager.swift b/CamperMonitor/Bluetooth/BluetoothManager.swift index 0ec3b95..b4ceccd 100644 --- a/CamperMonitor/Bluetooth/BluetoothManager.swift +++ b/CamperMonitor/Bluetooth/BluetoothManager.swift @@ -18,7 +18,7 @@ struct Discovery: Identifiable, Hashable { /// Der Neigungsmesser bewirbt seinen Dienst, ist also sicher erkennbar. var isLevelSensor = false /// Der Solarladeregler bewirbt seinen Dienst ebenso. - var isSolarSensor = false + var isVotronicSolarESPSensor = false //var isVictron: Bool { victronRecordType != nil } @@ -33,7 +33,7 @@ struct Discovery: Identifiable, Hashable { var subtitle: String { if isLevelSensor { return "VanAlign Neigungsmesser" } - if isSolarSensor { return "VanAlign Solarladeregler" } + if isVotronicSolarESPSensor { return "VotronicSolarESP" } return "Bluetooth-Gerät" } /*var subtitle: String { @@ -164,7 +164,7 @@ final class BluetoothManager: NSObject { //private(set) var bmsDiagnostics: [UUID: BMSDiagnostics] = [:] //private(set) var fridgeStates: [UUID: AlpicoolState] = [:] private(set) var levelStates: [UUID: LevelState] = [:] - private(set) var solarStates: [UUID: SolarState] = [:] + private(set) var votronicSolarESPStates: [UUID: VotronicSolarESPState] = [:] private(set) var isBluetoothReady = false private(set) var bluetoothStatusText = "Bluetooth wird gestartet…" @@ -201,7 +201,7 @@ final class BluetoothManager: NSObject { //private var bmsSessions: [UUID: BMSSession] = [:] private var levelSessions: [UUID: LevelSession] = [:] - private var solarSessions: [UUID: SolarSession] = [:] + private var votronicSolarESPSessions: [UUID: VotronicSolarESPSession] = [:] private var connectedPeripherals: [UUID: CBPeripheral] = [:] private var reconnectTimer: DispatchSourceTimer? @@ -285,10 +285,10 @@ final class BluetoothManager: NSObject { // voraussetzen. linkStates[DemoData.level.id] = .live - solarStates[DemoData.solar.id] = DemoData.solarState + votronicSolarESPStates[DemoData.solar.id] = DemoData.votronicSolarESPState linkStates[DemoData.solar.id] = .live - record(DemoData.solarState.snapshot(deviceID: DemoData.solar.id, rssi: -58)) - history[DemoData.solar.id] = DemoData.solarHistory() + record(DemoData.votronicSolarESPState.snapshot(deviceID: DemoData.solar.id, rssi: -58)) + history[DemoData.solar.id] = DemoData.votronicSolarESPHistory() /* for snapshot in DemoData.snapshots() { snapshots[snapshot.deviceID] = snapshot linkStates[snapshot.deviceID] = .live @@ -346,7 +346,7 @@ final class BluetoothManager: NSObject { //bmsDiagnostics = bmsDiagnostics.filter { known.contains($0.key) } //fridgeStates = fridgeStates.filter { known.contains($0.key) } levelStates = levelStates.filter { known.contains($0.key) } - solarStates = solarStates.filter { known.contains($0.key) } + votronicSolarESPStates = votronicSolarESPStates.filter { known.contains($0.key) } // Geräte, die nur auf Anforderung verbunden werden, zeigen bis dahin // ihren zuletzt gestellten Stand. @@ -556,9 +556,9 @@ final class BluetoothManager: NSObject { levelSessions[peripheralID] = nil disconnect(peripheralID) } - for (peripheralID, session) in solarSessions where !wanted.contains(peripheralID) { + for (peripheralID, session) in votronicSolarESPSessions where !wanted.contains(peripheralID) { session.stop() - solarSessions[peripheralID] = nil + votronicSolarESPSessions[peripheralID] = nil disconnect(peripheralID) } // Einstellungen an bestehende Sitzungen weiterreichen. @@ -606,11 +606,11 @@ final class BluetoothManager: NSObject { discoveryFlushTimer?.cancel(); discoveryFlushTimer = nil //for (_, session) in bmsSessions { session.stop() } for (_, session) in levelSessions { session.stop() } - for (_, session) in solarSessions { session.stop() } + for (_, session) in votronicSolarESPSessions { session.stop() } for (_, peripheral) in connectedPeripherals { central?.cancelPeripheralConnection(peripheral) } //bmsSessions.removeAll() levelSessions.removeAll() - solarSessions.removeAll() + votronicSolarESPSessions.removeAll() connectedPeripherals.removeAll() connectedSince.removeAll() //pendingControls.removeAll() @@ -806,8 +806,8 @@ final class BluetoothManager: NSObject { if let name, !name.isEmpty { entry.name = name } let services = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] ?? [] entry.isLevelSensor = services.contains(LevelSession.serviceUUID) - entry.isSolarSensor = services.contains(SolarSession.serviceUUID) - entry.looksLikeSupported = entry.isLevelSensor || entry.isSolarSensor + entry.isVotronicSolarESPSensor = services.contains(VotronicSolarESPSession.serviceUUID) + entry.looksLikeSupported = entry.isLevelSensor || entry.isVotronicSolarESPSensor pendingDiscoveries[peripheral.identifier] = entry } /*private func updateDiscovery(peripheral: CBPeripheral, @@ -965,7 +965,7 @@ extension BluetoothManager: CBCentralManagerDelegate { // Bewusst kein Draht zu `activityManager`: Der Solarertrag soll // nicht in der Live Activity/CarPlay auftauchen, die ist dem // Neigungsmesser vorbehalten. - let session = SolarSession( + let session = VotronicSolarESPSession( deviceID: device.id, peripheral: peripheral, queue: queue, @@ -973,11 +973,11 @@ extension BluetoothManager: CBCentralManagerDelegate { onStateChange: { [weak self] state in self?.publish { self?.linkStates[device.id] = state } }, - onSolarState: { [weak self] state in - self?.publish { self?.solarStates[device.id] = state } + onVotronicSolarESPState: { [weak self] state in + self?.publish { self?.votronicSolarESPStates[device.id] = state } } ) - solarSessions[peripheral.identifier] = session + votronicSolarESPSessions[peripheral.identifier] = session session.start() return } @@ -1029,8 +1029,8 @@ extension BluetoothManager: CBCentralManagerDelegate { //bmsSessions[peripheral.identifier] = nil levelSessions[peripheral.identifier]?.handleDisconnect() levelSessions[peripheral.identifier] = nil - solarSessions[peripheral.identifier]?.handleDisconnect() - solarSessions[peripheral.identifier] = nil + votronicSolarESPSessions[peripheral.identifier]?.handleDisconnect() + votronicSolarESPSessions[peripheral.identifier] = nil connectedPeripherals[peripheral.identifier] = nil let lifetime = connectedSince.removeValue(forKey: peripheral.identifier) diff --git a/CamperMonitor/Store/DemoData.swift b/CamperMonitor/Store/DemoData.swift index 9af7bb8..49c24b4 100644 --- a/CamperMonitor/Store/DemoData.swift +++ b/CamperMonitor/Store/DemoData.swift @@ -81,8 +81,8 @@ enum DemoData { } /// Mittags, gute Sonne. - static var solarState: SolarState { - var state = SolarState() + static var votronicSolarESPState: VotronicSolarESPState { + var state = VotronicSolarESPState() state.batteryVoltage = 13.9 state.pvVoltage = 19.4 state.pvCurrent = 6.2 @@ -96,7 +96,7 @@ enum DemoData { /// Ein paar Stunden Verlauf je Kanal, damit die Grafik im Demo-Modus /// sofort etwas zeigt statt erst nach ein paar Minuten Laufzeit. - static func solarHistory() -> DeviceHistory { + static func votronicSolarESPHistory() -> DeviceHistory { let now = Date() func series(around value: Double, noise: Double) -> [HistorySample] { (0..<120).reversed().map { step in diff --git a/CamperMonitor/Views/AddDeviceView.swift b/CamperMonitor/Views/AddDeviceView.swift index 4570be3..9bc2e69 100644 --- a/CamperMonitor/Views/AddDeviceView.swift +++ b/CamperMonitor/Views/AddDeviceView.swift @@ -195,7 +195,7 @@ private struct ConfigureDeviceView: View { } } else*/ if discovery.isLevelSensor { role = .leveling - } else if discovery.isSolarSensor { + } else if discovery.isVotronicSolarESPSensor { role = .solar } /*else if let name = discovery.name?.lowercased(), ["alpicool", "icecube", "ice cube", "fridge", "cool"].contains(where: name.contains) { @@ -208,8 +208,8 @@ private struct ConfigureDeviceView: View { // ohnehin darüber unter "Gefunden als". if discovery.isLevelSensor { name = "Nivellierung" - } else if discovery.isSolarSensor { - name = "Solarladeregler" + } else if discovery.isVotronicSolarESPSensor { + name = role.title } else if let advertised = discovery.name, advertised.count <= 20, advertised.contains(" ") || advertised.rangeOfCharacter(from: .decimalDigits) == nil { name = advertised diff --git a/CamperMonitor/Views/DashboardView.swift b/CamperMonitor/Views/DashboardView.swift index b22655b..dc8a9f1 100644 --- a/CamperMonitor/Views/DashboardView.swift +++ b/CamperMonitor/Views/DashboardView.swift @@ -106,7 +106,7 @@ struct DashboardView: View { Label("Noch keine Geräte", systemImage: "antenna.radiowaves.left.and.right") } description: { Text("Füge \(store.activeProfile.map { "„\($0.name)“" } ?? "diesem Fahrzeug") " - + "den Ladebooster, den Solarladeregler und das BMS hinzu.") + + "den Ladebooster, den VotronicSolarESP und das BMS hinzu.") } actions: { Button("Gerät suchen") { isAddingDevice = true } .buttonStyle(.borderedProminent) diff --git a/Shared/Bluetooth/VanAlignSolarProtocol.swift b/Shared/Bluetooth/VotronicSolarESPProtocol.swift similarity index 95% rename from Shared/Bluetooth/VanAlignSolarProtocol.swift rename to Shared/Bluetooth/VotronicSolarESPProtocol.swift index 98f9ac2..f61ebac 100644 --- a/Shared/Bluetooth/VanAlignSolarProtocol.swift +++ b/Shared/Bluetooth/VotronicSolarESPProtocol.swift @@ -1,13 +1,13 @@ import Foundation -/// Solarladeregler „VanAlign Solar" – zweiter ESP32 im Fahrzeug, liest einen +/// VotronicSolarESP – zweiter ESP32 im Fahrzeug, liest einen /// Votronic-Solarladeregler aus und stellt die Werte über einen eigenen /// BLE-Dienst bereit. Siehe `firmware/vanalign/esp32_ble_solar.yaml`. /// /// Wie beim Neigungsmesser: kein Rahmenprotokoll, jede Messgrösse liegt in /// einer eigenen Charakteristik, alle sind reine Lesewerte, der Client fragt /// sie im Takt ab. -enum VanAlignSolarProtocol { +enum VotronicSolarESPProtocol { /// Wird vom Gerät beworben, das Gerät ist darüber auffindbar. static let serviceUUID = "05C9A349-2B8E-4B1D-9C9D-C247E9A6A001" diff --git a/Shared/Bluetooth/SolarSession.swift b/Shared/Bluetooth/VotronicSolarESPSession.swift similarity index 74% rename from Shared/Bluetooth/SolarSession.swift rename to Shared/Bluetooth/VotronicSolarESPSession.swift index d138e92..080a6ad 100644 --- a/Shared/Bluetooth/SolarSession.swift +++ b/Shared/Bluetooth/VotronicSolarESPSession.swift @@ -6,25 +6,25 @@ import Foundation /// Einfacher noch als `LevelSession`: die Firmware bietet für keine /// Charakteristik `notify` an (siehe `esp32_ble_solar.yaml`), es wird also /// immer im Takt abgefragt statt abonniert. -final class SolarSession: NSObject { +final class VotronicSolarESPSession: NSObject { - static let serviceUUID = CBUUID(string: VanAlignSolarProtocol.serviceUUID) - private static let batteryVoltageUUID = CBUUID(string: VanAlignSolarProtocol.batteryVoltageUUID) - private static let pvVoltageUUID = CBUUID(string: VanAlignSolarProtocol.pvVoltageUUID) - private static let pvCurrentUUID = CBUUID(string: VanAlignSolarProtocol.pvCurrentUUID) - private static let pvPowerUUID = CBUUID(string: VanAlignSolarProtocol.pvPowerUUID) - private static let controllerTempUUID = CBUUID(string: VanAlignSolarProtocol.controllerTempUUID) - private static let statusFlagsUUID = CBUUID(string: VanAlignSolarProtocol.statusFlagsUUID) + static let serviceUUID = CBUUID(string: VotronicSolarESPProtocol.serviceUUID) + private static let batteryVoltageUUID = CBUUID(string: VotronicSolarESPProtocol.batteryVoltageUUID) + private static let pvVoltageUUID = CBUUID(string: VotronicSolarESPProtocol.pvVoltageUUID) + private static let pvCurrentUUID = CBUUID(string: VotronicSolarESPProtocol.pvCurrentUUID) + private static let pvPowerUUID = CBUUID(string: VotronicSolarESPProtocol.pvPowerUUID) + private static let controllerTempUUID = CBUUID(string: VotronicSolarESPProtocol.controllerTempUUID) + private static let statusFlagsUUID = CBUUID(string: VotronicSolarESPProtocol.statusFlagsUUID) let deviceID: UUID private let queue: DispatchQueue private let peripheral: CBPeripheral private let onUpdate: (DeviceSnapshot) -> Void private let onStateChange: (DeviceLinkState) -> Void - private let onSolarState: (SolarState) -> Void + private let onVotronicSolarESPState: (VotronicSolarESPState) -> Void private var characteristics: [CBUUID: CBCharacteristic] = [:] - private var state = SolarState() + private var state = VotronicSolarESPState() private var pollTimer: DispatchSourceTimer? /// Reicht für einen Solarregler, dessen Werte sich über Sekunden ändern – @@ -36,13 +36,13 @@ final class SolarSession: NSObject { queue: DispatchQueue, onUpdate: @escaping (DeviceSnapshot) -> Void, onStateChange: @escaping (DeviceLinkState) -> Void, - onSolarState: @escaping (SolarState) -> Void) { + onVotronicSolarESPState: @escaping (VotronicSolarESPState) -> Void) { self.deviceID = deviceID self.queue = queue self.peripheral = peripheral self.onUpdate = onUpdate self.onStateChange = onStateChange - self.onSolarState = onSolarState + self.onVotronicSolarESPState = onVotronicSolarESPState super.init() peripheral.delegate = self } @@ -87,14 +87,14 @@ final class SolarSession: NSObject { private func publish() { guard state.hasReading else { return } onStateChange(.live) - onSolarState(state) + onVotronicSolarESPState(state) onUpdate(state.snapshot(deviceID: deviceID, rssi: nil)) } } // MARK: - CBPeripheralDelegate -extension SolarSession: CBPeripheralDelegate { +extension VotronicSolarESPSession: CBPeripheralDelegate { func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { if let error { @@ -137,17 +137,17 @@ extension SolarSession: CBPeripheralDelegate { switch characteristic.uuid { case Self.batteryVoltageUUID: - state.batteryVoltage = VanAlignSolarProtocol.float(from: value) + state.batteryVoltage = VotronicSolarESPProtocol.float(from: value) case Self.pvVoltageUUID: - state.pvVoltage = VanAlignSolarProtocol.float(from: value) + state.pvVoltage = VotronicSolarESPProtocol.float(from: value) case Self.pvCurrentUUID: - state.pvCurrent = VanAlignSolarProtocol.float(from: value) + state.pvCurrent = VotronicSolarESPProtocol.float(from: value) case Self.pvPowerUUID: - state.pvPower = VanAlignSolarProtocol.float(from: value) + state.pvPower = VotronicSolarESPProtocol.float(from: value) case Self.controllerTempUUID: - state.controllerTemperature = VanAlignSolarProtocol.float(from: value) + state.controllerTemperature = VotronicSolarESPProtocol.float(from: value) case Self.statusFlagsUUID: - guard let flags = VanAlignSolarProtocol.statusFlags(from: value) else { return } + guard let flags = VotronicSolarESPProtocol.statusFlags(from: value) else { return } state.isBatteryCharging = flags.isBatteryCharging state.isBatteryDischarging = flags.isBatteryDischarging state.isControllerActive = flags.isControllerActive diff --git a/Shared/Models/ConfiguredDevice.swift b/Shared/Models/ConfiguredDevice.swift index 420280f..5b6187f 100644 --- a/Shared/Models/ConfiguredDevice.swift +++ b/Shared/Models/ConfiguredDevice.swift @@ -19,7 +19,7 @@ enum DeviceRole: String, Codable, CaseIterable, Identifiable, Sendable { //case .bms: return "Batterie / BMS" //case .fridge: return "Kühlbox" case .leveling: return "Nivellierung" - case .solar: return "Solarladeregler" + case .solar: return "VotronicSolarESP" } } diff --git a/Shared/Models/SolarState.swift b/Shared/Models/VotronicSolarESPState.swift similarity index 96% rename from Shared/Models/SolarState.swift rename to Shared/Models/VotronicSolarESPState.swift index e864cf9..c59105c 100644 --- a/Shared/Models/SolarState.swift +++ b/Shared/Models/VotronicSolarESPState.swift @@ -1,7 +1,7 @@ import Foundation /// Zustand des Solarladereglers. -struct SolarState: Equatable, Codable, Sendable { +struct VotronicSolarESPState: Equatable, Codable, Sendable { var batteryVoltage: Double? var pvVoltage: Double? var pvCurrent: Double?