From 6d7b32d622b72764e9d6efacad5106f4d9821a34 Mon Sep 17 00:00:00 2001 From: fototeddy Date: Sat, 5 Sep 2026 00:04:20 +0200 Subject: [PATCH] Add solar charge controller integration and firmware App: - New DeviceRole.solar with its own BLE session/protocol/state (SolarSession, VanAlignSolarProtocol, SolarState), mirroring the leveling sensor but deliberately not wired into the Live Activity. - BluetoothManager now keeps an in-memory history per metric (voltage, current, power) instead of a single primary-metric series; cleared on app restart by design, not persisted to disk. - DeviceDetailView shows a channel picker above the history chart when a device has more than one chartable metric. - AddDeviceView recognizes the solar service UUID during setup. - DemoData gets a simulated solar device so the integration can be checked in the simulator without hardware. Firmware: - Add esp32_ble_solar.yaml (Votronic solar charge controller over BLE) and simulated variants (esp32_ble_sim.yaml, esp32_ble_solar_sim.yaml) for testing without a vehicle. - esp32_ble.yaml: set flash_size/psram for the ESP32-S3 N16R8 module. Co-Authored-By: Claude Sonnet 5 --- .../Bluetooth/BluetoothManager.swift | 73 +++- CamperMonitor/Store/DemoData.swift | 41 ++- CamperMonitor/Views/AddDeviceView.swift | 4 + CamperMonitor/Views/DeviceDetailView.swift | 52 ++- Shared/Bluetooth/SolarSession.swift | 160 +++++++++ Shared/Bluetooth/VanAlignSolarProtocol.swift | 55 +++ Shared/Models/ConfiguredDevice.swift | 6 +- Shared/Models/SolarState.swift | 48 +++ .../UserInterfaceState.xcuserstate | Bin 107470 -> 126731 bytes .../xcschemes/xcschememanagement.plist | 4 +- firmware/vanalign/esp32_ble.yaml | 6 + firmware/vanalign/esp32_ble_sim.yaml | 332 ++++++++++++++++++ firmware/vanalign/esp32_ble_solar.yaml | 233 ++++++++++++ firmware/vanalign/esp32_ble_solar_sim.yaml | 265 ++++++++++++++ 14 files changed, 1257 insertions(+), 22 deletions(-) create mode 100644 Shared/Bluetooth/SolarSession.swift create mode 100644 Shared/Bluetooth/VanAlignSolarProtocol.swift create mode 100644 Shared/Models/SolarState.swift create mode 100644 firmware/vanalign/esp32_ble_sim.yaml create mode 100644 firmware/vanalign/esp32_ble_solar.yaml create mode 100644 firmware/vanalign/esp32_ble_solar_sim.yaml diff --git a/CamperMonitor/Bluetooth/BluetoothManager.swift b/CamperMonitor/Bluetooth/BluetoothManager.swift index f27a945..813a825 100644 --- a/CamperMonitor/Bluetooth/BluetoothManager.swift +++ b/CamperMonitor/Bluetooth/BluetoothManager.swift @@ -17,6 +17,8 @@ struct Discovery: Identifiable, Hashable { var looksLikeSupported: Bool /// Der Neigungsmesser bewirbt seinen Dienst, ist also sicher erkennbar. var isLevelSensor = false + /// Der Solarladeregler bewirbt seinen Dienst ebenso. + var isSolarSensor = false //var isVictron: Bool { victronRecordType != nil } @@ -31,6 +33,7 @@ struct Discovery: Identifiable, Hashable { var subtitle: String { if isLevelSensor { return "VanAlign Neigungsmesser" } + if isSolarSensor { return "VanAlign Solarladeregler" } return "Bluetooth-Gerät" } /*var subtitle: String { @@ -125,6 +128,12 @@ struct HistorySample: Identifiable, Hashable { let value: Double } +/// Verlauf mehrerer Messgrössen eines Geräts, je Metrik-Schlüssel (siehe +/// `Metric.key`). Nur im Arbeitsspeicher: Sinn ist die Grafik während der +/// laufenden App-Sitzung, nicht ein dauerhaftes Log – ein Neustart der App +/// oder das Beenden im Hintergrund darf den Verlauf also verwerfen. +typealias DeviceHistory = [String: [HistorySample]] + /// Zentrale Bluetooth-Schicht: scannt dauerhaft nach Geräten und hält /// parallel die Verbindung zum Neigungsmesser. /// @@ -150,11 +159,12 @@ final class BluetoothManager: NSObject { private(set) var snapshots: [UUID: DeviceSnapshot] = [:] private(set) var linkStates: [UUID: DeviceLinkState] = [:] private(set) var discoveries: [UUID: Discovery] = [:] - private(set) var history: [UUID: [HistorySample]] = [:] + private(set) var history: [UUID: DeviceHistory] = [:] //private(set) var diagnostics: [UUID: VictronDiagnostics] = [:] //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 isBluetoothReady = false private(set) var bluetoothStatusText = "Bluetooth wird gestartet…" @@ -191,6 +201,7 @@ final class BluetoothManager: NSObject { //private var bmsSessions: [UUID: BMSSession] = [:] private var levelSessions: [UUID: LevelSession] = [:] + private var solarSessions: [UUID: SolarSession] = [:] private var connectedPeripherals: [UUID: CBPeripheral] = [:] private var reconnectTimer: DispatchSourceTimer? @@ -269,6 +280,11 @@ final class BluetoothManager: NSObject { bluetoothStatusText = "Demo-Modus" //fridgeStates[DemoData.fridge.id] = DemoData.fridgeState levelStates[DemoData.level.id] = DemoData.levelState + + solarStates[DemoData.solar.id] = DemoData.solarState + linkStates[DemoData.solar.id] = .live + record(DemoData.solarState.snapshot(deviceID: DemoData.solar.id, rssi: -58)) + history[DemoData.solar.id] = DemoData.solarHistory() /* for snapshot in DemoData.snapshots() { snapshots[snapshot.deviceID] = snapshot linkStates[snapshot.deviceID] = .live @@ -326,6 +342,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) } // Geräte, die nur auf Anforderung verbunden werden, zeigen bis dahin // ihren zuletzt gestellten Stand. @@ -535,6 +552,11 @@ final class BluetoothManager: NSObject { levelSessions[peripheralID] = nil disconnect(peripheralID) } + for (peripheralID, session) in solarSessions where !wanted.contains(peripheralID) { + session.stop() + solarSessions[peripheralID] = nil + disconnect(peripheralID) + } // Einstellungen an bestehende Sitzungen weiterreichen. for device in devices { //bmsSessions[device.peripheralID]?.fridgeZoneMode = device.fridgeZoneMode @@ -580,9 +602,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 (_, peripheral) in connectedPeripherals { central?.cancelPeripheralConnection(peripheral) } //bmsSessions.removeAll() levelSessions.removeAll() + solarSessions.removeAll() connectedPeripherals.removeAll() connectedSince.removeAll() //pendingControls.removeAll() @@ -692,15 +716,20 @@ final class BluetoothManager: NSObject { private func record(_ snapshot: DeviceSnapshot) { publish { self.snapshots[snapshot.deviceID] = snapshot - guard let primary = snapshot.primaryMetric, let value = primary.value else { return } - var samples = self.history[snapshot.deviceID] ?? [] - // Höchstens alle fünf Sekunden einen Punkt aufnehmen. - if let last = samples.last, snapshot.timestamp.timeIntervalSince(last.time) < 5 { return } - samples.append(HistorySample(time: snapshot.timestamp, value: value)) - if samples.count > self.historyLimit { - samples.removeFirst(samples.count - self.historyLimit) + var deviceHistory = self.history[snapshot.deviceID] ?? [:] + for metric in snapshot.metrics { + guard let value = metric.value else { continue } + var samples = deviceHistory[metric.key] ?? [] + // Höchstens alle fünf Sekunden einen Punkt aufnehmen. + if let last = samples.last, + snapshot.timestamp.timeIntervalSince(last.time) < 5 { continue } + samples.append(HistorySample(time: snapshot.timestamp, value: value)) + if samples.count > self.historyLimit { + samples.removeFirst(samples.count - self.historyLimit) + } + deviceHistory[metric.key] = samples } - self.history[snapshot.deviceID] = samples + self.history[snapshot.deviceID] = deviceHistory } } @@ -773,7 +802,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.looksLikeSupported = entry.isLevelSensor + entry.isSolarSensor = services.contains(SolarSession.serviceUUID) + entry.looksLikeSupported = entry.isLevelSensor || entry.isSolarSensor pendingDiscoveries[peripheral.identifier] = entry } /*private func updateDiscovery(peripheral: CBPeripheral, @@ -917,6 +947,27 @@ extension BluetoothManager: CBCentralManagerDelegate { return } + if device.role == .solar { + // Bewusst kein Draht zu `activityManager`: Der Solarertrag soll + // nicht in der Live Activity/CarPlay auftauchen, die ist dem + // Neigungsmesser vorbehalten. + let session = SolarSession( + deviceID: device.id, + peripheral: peripheral, + queue: queue, + onUpdate: { [weak self] snapshot in self?.record(snapshot) }, + onStateChange: { [weak self] state in + self?.publish { self?.linkStates[device.id] = state } + }, + onSolarState: { [weak self] state in + self?.publish { self?.solarStates[device.id] = state } + } + ) + solarSessions[peripheral.identifier] = session + session.start() + return + } + /*let session = BMSSession( deviceID: device.id, peripheral: peripheral, @@ -964,6 +1015,8 @@ extension BluetoothManager: CBCentralManagerDelegate { //bmsSessions[peripheral.identifier] = nil levelSessions[peripheral.identifier]?.handleDisconnect() levelSessions[peripheral.identifier] = nil + solarSessions[peripheral.identifier]?.handleDisconnect() + solarSessions[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 e595f27..a39807f 100644 --- a/CamperMonitor/Store/DemoData.swift +++ b/CamperMonitor/Store/DemoData.swift @@ -59,8 +59,47 @@ enum DemoData { name: "Nivellierung", role: .leveling, profileID: Profile.defaultID, peripheralID: UUID(uuidString: "00000000-0000-0000-0000-0000000000E1")!) + static let solar = ConfiguredDevice( + id: UUID(uuidString: "00000000-0000-0000-0000-0000000000D0")!, + name: "Solar Dach", role: .solar, profileID: Profile.defaultID, + peripheralID: UUID(uuidString: "00000000-0000-0000-0000-0000000000D1")!) + static var devices: [ConfiguredDevice] { - [/*solar, booster, battery, fridge, */level/*, caravanSolar*/] + [/*booster, battery, fridge, */level, solar/*, caravanSolar*/] + } + + /// Mittags, gute Sonne. + static var solarState: SolarState { + var state = SolarState() + state.batteryVoltage = 13.9 + state.pvVoltage = 19.4 + state.pvCurrent = 6.2 + state.pvPower = 120 + state.controllerTemperature = 34 + state.isBatteryCharging = true + state.isControllerActive = true + state.isCurrentLimited = false + return state + } + + /// 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 { + let now = Date() + func series(around value: Double, noise: Double) -> [HistorySample] { + (0..<120).reversed().map { step in + let t = Double(step) + let wave = sin(t / 14) * value * noise + cos(t / 31) * value * (noise / 2) + return HistorySample(time: now.addingTimeInterval(-t * 60), + value: max(0, value + wave)) + } + } + return [ + "pv_power": series(around: 120, noise: 0.35), + "pv_voltage": series(around: 19.4, noise: 0.08), + "pv_current": series(around: 6.2, noise: 0.3), + "battery_voltage": series(around: 13.9, noise: 0.03), + ] } /// Leicht schräg stehend, damit die Libelle etwas zu zeigen hat. diff --git a/CamperMonitor/Views/AddDeviceView.swift b/CamperMonitor/Views/AddDeviceView.swift index 5a75518..4570be3 100644 --- a/CamperMonitor/Views/AddDeviceView.swift +++ b/CamperMonitor/Views/AddDeviceView.swift @@ -195,6 +195,8 @@ private struct ConfigureDeviceView: View { } } else*/ if discovery.isLevelSensor { role = .leveling + } else if discovery.isSolarSensor { + role = .solar } /*else if let name = discovery.name?.lowercased(), ["alpicool", "icecube", "ice cube", "fridge", "cool"].contains(where: name.contains) { role = .fridge @@ -206,6 +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 let advertised = discovery.name, advertised.count <= 20, advertised.contains(" ") || advertised.rangeOfCharacter(from: .decimalDigits) == nil { name = advertised diff --git a/CamperMonitor/Views/DeviceDetailView.swift b/CamperMonitor/Views/DeviceDetailView.swift index ad9d236..a755c75 100644 --- a/CamperMonitor/Views/DeviceDetailView.swift +++ b/CamperMonitor/Views/DeviceDetailView.swift @@ -35,9 +35,31 @@ struct DeviceDetailView: View { store.devices.first { $0.id == device.id } ?? device } + /// Nur solange die App läuft: siehe `DeviceHistory`. + @State private var selectedHistoryMetricKey: String? + private var snapshot: DeviceSnapshot? { bluetooth.snapshots[device.id] } private var linkState: DeviceLinkState { bluetooth.linkStates[device.id] ?? .searching } - private var samples: [HistorySample] { bluetooth.history[device.id] ?? [] } + private var deviceHistory: DeviceHistory { bluetooth.history[device.id] ?? [:] } + + /// Nur Metriken, zu denen sich bereits ein Verlauf mit mehr als einem + /// Punkt angesammelt hat – sonst gäbe es nichts zu zeichnen. + private var chartableMetrics: [Metric] { + (snapshot?.metrics ?? []).filter { (deviceHistory[$0.key]?.count ?? 0) > 1 } + } + + private var selectedMetric: Metric? { + if let key = selectedHistoryMetricKey, + let metric = chartableMetrics.first(where: { $0.key == key }) { + return metric + } + return chartableMetrics.first(where: \.isPrimary) ?? chartableMetrics.first + } + + private var samples: [HistorySample] { + guard let selectedMetric else { return [] } + return deviceHistory[selectedMetric.key] ?? [] + } var body: some View { List { @@ -65,20 +87,38 @@ struct DeviceDetailView: View { } } - if samples.count > 1, let primary = snapshot?.primaryMetric { - Section("Verlauf – \(primary.label)") { + if let metric = selectedMetric, samples.count > 1 { + Section { + if chartableMetrics.count > 1 { + Picker("Messgrösse", selection: Binding( + get: { metric.key }, + set: { selectedHistoryMetricKey = $0 } + )) { + ForEach(chartableMetrics) { candidate in + Text(candidate.label).tag(candidate.key) + } + } + .pickerStyle(.segmented) + .listRowInsets(EdgeInsets()) + .padding(.horizontal) + .padding(.top, 4) + } Chart(samples) { sample in AreaMark(x: .value("Zeit", sample.time), - y: .value(primary.label, sample.value)) + y: .value(metric.label, sample.value)) .foregroundStyle(.tint.opacity(0.15)) LineMark(x: .value("Zeit", sample.time), - y: .value(primary.label, sample.value)) + y: .value(metric.label, sample.value)) .foregroundStyle(.tint) .interpolationMethod(.monotone) } - .chartYAxisLabel(primary.unit) + .chartYAxisLabel(metric.unit) .frame(height: 180) .padding(.vertical, 8) + } header: { + Text("Verlauf – \(metric.label)") + } footer: { + Text("Nur für die laufende Sitzung – wird beim Neustart der App verworfen.") } } diff --git a/Shared/Bluetooth/SolarSession.swift b/Shared/Bluetooth/SolarSession.swift new file mode 100644 index 0000000..d138e92 --- /dev/null +++ b/Shared/Bluetooth/SolarSession.swift @@ -0,0 +1,160 @@ +import CoreBluetooth +import Foundation + +/// Hält die Verbindung zum Solarladeregler. +/// +/// 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 { + + 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) + + 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 var characteristics: [CBUUID: CBCharacteristic] = [:] + private var state = SolarState() + private var pollTimer: DispatchSourceTimer? + + /// Reicht für einen Solarregler, dessen Werte sich über Sekunden ändern – + /// deutlich seltener als beim Ausrichten mit dem Neigungsmesser. + var pollInterval: TimeInterval = 5 + + init(deviceID: UUID, + peripheral: CBPeripheral, + queue: DispatchQueue, + onUpdate: @escaping (DeviceSnapshot) -> Void, + onStateChange: @escaping (DeviceLinkState) -> Void, + onSolarState: @escaping (SolarState) -> Void) { + self.deviceID = deviceID + self.queue = queue + self.peripheral = peripheral + self.onUpdate = onUpdate + self.onStateChange = onStateChange + self.onSolarState = onSolarState + super.init() + peripheral.delegate = self + } + + // MARK: - Lebenszyklus + + func start() { + onStateChange(.connecting) + peripheral.discoverServices([Self.serviceUUID]) + } + + func stop() { + pollTimer?.cancel() + pollTimer = nil + characteristics.removeAll() + } + + func handleDisconnect() { + pollTimer?.cancel() + pollTimer = nil + characteristics.removeAll() + } + + // MARK: - Abfrage + + private func startPolling() { + guard pollTimer == nil else { return } + let timer = DispatchSource.makeTimerSource(queue: queue) + timer.schedule(deadline: .now(), repeating: pollInterval) + timer.setEventHandler { [weak self] in self?.readAll() } + timer.resume() + pollTimer = timer + } + + private func readAll() { + guard peripheral.state == .connected else { return } + for characteristic in characteristics.values where characteristic.properties.contains(.read) { + peripheral.readValue(for: characteristic) + } + } + + private func publish() { + guard state.hasReading else { return } + onStateChange(.live) + onSolarState(state) + onUpdate(state.snapshot(deviceID: deviceID, rssi: nil)) + } +} + +// MARK: - CBPeripheralDelegate + +extension SolarSession: CBPeripheralDelegate { + + func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { + if let error { + onStateChange(.failed(error.localizedDescription)) + return + } + guard let service = peripheral.services?.first(where: { $0.uuid == Self.serviceUUID }) else { + onStateChange(.failed("Solarladeregler-Dienst nicht gefunden")) + return + } + peripheral.discoverCharacteristics( + [Self.batteryVoltageUUID, Self.pvVoltageUUID, Self.pvCurrentUUID, + Self.pvPowerUUID, Self.controllerTempUUID, Self.statusFlagsUUID], + for: service + ) + } + + func peripheral(_ peripheral: CBPeripheral, + didDiscoverCharacteristicsFor service: CBService, + error: Error?) { + guard error == nil, let found = service.characteristics else { + onStateChange(.failed(error?.localizedDescription ?? "Keine Merkmale gefunden")) + return + } + for characteristic in found { + characteristics[characteristic.uuid] = characteristic + } + guard !characteristics.isEmpty else { + onStateChange(.failed("Solarwerte nicht gefunden")) + return + } + startPolling() + readAll() + } + + func peripheral(_ peripheral: CBPeripheral, + didUpdateValueFor characteristic: CBCharacteristic, + error: Error?) { + guard error == nil, let value = characteristic.value else { return } + + switch characteristic.uuid { + case Self.batteryVoltageUUID: + state.batteryVoltage = VanAlignSolarProtocol.float(from: value) + case Self.pvVoltageUUID: + state.pvVoltage = VanAlignSolarProtocol.float(from: value) + case Self.pvCurrentUUID: + state.pvCurrent = VanAlignSolarProtocol.float(from: value) + case Self.pvPowerUUID: + state.pvPower = VanAlignSolarProtocol.float(from: value) + case Self.controllerTempUUID: + state.controllerTemperature = VanAlignSolarProtocol.float(from: value) + case Self.statusFlagsUUID: + guard let flags = VanAlignSolarProtocol.statusFlags(from: value) else { return } + state.isBatteryCharging = flags.isBatteryCharging + state.isBatteryDischarging = flags.isBatteryDischarging + state.isControllerActive = flags.isControllerActive + state.isCurrentLimited = flags.isCurrentLimited + default: + return + } + publish() + } +} diff --git a/Shared/Bluetooth/VanAlignSolarProtocol.swift b/Shared/Bluetooth/VanAlignSolarProtocol.swift new file mode 100644 index 0000000..98f9ac2 --- /dev/null +++ b/Shared/Bluetooth/VanAlignSolarProtocol.swift @@ -0,0 +1,55 @@ +import Foundation + +/// Solarladeregler „VanAlign Solar" – 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 { + + /// Wird vom Gerät beworben, das Gerät ist darüber auffindbar. + static let serviceUUID = "05C9A349-2B8E-4B1D-9C9D-C247E9A6A001" + + static let batteryVoltageUUID = "05C9A349-2B8E-4B1D-9C9D-C247E9A6A101" + static let pvVoltageUUID = "05C9A349-2B8E-4B1D-9C9D-C247E9A6A102" + static let pvCurrentUUID = "05C9A349-2B8E-4B1D-9C9D-C247E9A6A103" + static let pvPowerUUID = "05C9A349-2B8E-4B1D-9C9D-C247E9A6A104" + static let controllerTempUUID = "05C9A349-2B8E-4B1D-9C9D-C247E9A6A105" + /// Bit0 Batterie lädt, Bit1 Batterie entlädt, Bit2 PV-Regler aktiv, + /// Bit3 PV-Strombegrenzung, Bit4 AES aktiv. + static let statusFlagsUUID = "05C9A349-2B8E-4B1D-9C9D-C247E9A6A106" + + /// Liest einen Messwert aus vier Bytes, little-endian – wie beim + /// Neigungsmesser legt die Firmware den Float per `memcpy` ab. + static func float(from data: Data) -> Double? { + guard data.count >= 4 else { return nil } + var raw: UInt32 = 0 + for (index, byte) in data.prefix(4).enumerated() { + raw |= UInt32(byte) << UInt32(8 * index) + } + let value = Float(bitPattern: raw) + guard value.isFinite else { return nil } + return Double(value) + } + + struct StatusFlags { + var isBatteryCharging = false + var isBatteryDischarging = false + var isControllerActive = false + var isCurrentLimited = false + var isAESActive = false + } + + static func statusFlags(from data: Data) -> StatusFlags? { + guard let byte = data.first else { return nil } + var flags = StatusFlags() + flags.isBatteryCharging = byte & (1 << 0) != 0 + flags.isBatteryDischarging = byte & (1 << 1) != 0 + flags.isControllerActive = byte & (1 << 2) != 0 + flags.isCurrentLimited = byte & (1 << 3) != 0 + flags.isAESActive = byte & (1 << 4) != 0 + return flags + } +} diff --git a/Shared/Models/ConfiguredDevice.swift b/Shared/Models/ConfiguredDevice.swift index d84579f..65c050d 100644 --- a/Shared/Models/ConfiguredDevice.swift +++ b/Shared/Models/ConfiguredDevice.swift @@ -4,33 +4,33 @@ import Foundation /// welche Kennzahl als "Hauptwert" auf der Kachel gross dargestellt wird. enum DeviceRole: String, Codable, CaseIterable, Identifiable, Sendable { //case chargeBooster - //case solarCharger //case batteryMonitor //case bms //case fridge case leveling + case solar var id: String { rawValue } var title: String { switch self { //case .chargeBooster: return "Ladebooster" - //case .solarCharger: return "Solarladeregler" //case .batteryMonitor: return "Batteriemonitor" //case .bms: return "Batterie / BMS" //case .fridge: return "Kühlbox" case .leveling: return "Nivellierung" + case .solar: return "Solarladeregler" } } var symbol: String { switch self { //case .chargeBooster: return "bolt.car" - //case .solarCharger: return "sun.max" //case .batteryMonitor: return "gauge.with.dots.needle.bottom.50percent" //case .bms: return "battery.100percent.bolt" //case .fridge: return "refrigerator" case .leveling: return "level" + case .solar: return "sun.max" } } diff --git a/Shared/Models/SolarState.swift b/Shared/Models/SolarState.swift new file mode 100644 index 0000000..e864cf9 --- /dev/null +++ b/Shared/Models/SolarState.swift @@ -0,0 +1,48 @@ +import Foundation + +/// Zustand des Solarladereglers. +struct SolarState: Equatable, Codable, Sendable { + var batteryVoltage: Double? + var pvVoltage: Double? + var pvCurrent: Double? + var pvPower: Double? + var controllerTemperature: Double? + + var isBatteryCharging: Bool? + var isBatteryDischarging: Bool? + var isControllerActive: Bool? + var isCurrentLimited: Bool? + + var hasReading: Bool { + batteryVoltage != nil || pvVoltage != nil || pvCurrent != nil || pvPower != nil + } + + /// Kurzer Klartext, wie bei den übrigen Geräten als "Zustand" angezeigt. + var stateText: String? { + guard isControllerActive != nil else { return nil } + if isBatteryCharging == true { return "Lädt" } + if isControllerActive == true { return "Aktiv" } + return "Inaktiv" + } + + func snapshot(deviceID: UUID, rssi: Int?) -> DeviceSnapshot { + var snapshot = DeviceSnapshot(deviceID: deviceID, timestamp: Date(), rssi: rssi) + snapshot.metrics = [ + Metric("pv_power", "Solarleistung", pvPower, unit: "W", precision: 0, primary: true), + Metric("pv_voltage", "PV-Spannung", pvVoltage, unit: "V", precision: 1), + Metric("pv_current", "PV-Strom", pvCurrent, unit: "A", precision: 1), + Metric("battery_voltage", "Batteriespannung", batteryVoltage, unit: "V", precision: 2), + ] + if let controllerTemperature { + snapshot.metrics.append( + Metric("controller_temperature", "Reglertemperatur", controllerTemperature, + unit: "°C", precision: 0) + ) + } + snapshot.state = stateText + if isCurrentLimited == true { + snapshot.offReasons = ["PV-Strombegrenzung aktiv"] + } + return snapshot + } +} diff --git a/VanAligneiOS.xcodeproj/project.xcworkspace/xcuserdata/christopher.xcuserdatad/UserInterfaceState.xcuserstate b/VanAligneiOS.xcodeproj/project.xcworkspace/xcuserdata/christopher.xcuserdatad/UserInterfaceState.xcuserstate index 26962c3928064749a75d45ddb653bf2a1d3d351e..b25ab53623fb86eef65646b8facbdbc3e39ff5fb 100644 GIT binary patch literal 126731 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 ZzrX+PpRKq0-+w0j{|x@`pKxaPe*wOC)iVG9 literal 107470 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 z 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 Z_<#SLVanAligneComplication.xcscheme_^#shared#^_ orderHint - 2 + 3 VanAligneiOSWidgetExtension.xcscheme_^#shared#^_ orderHint - 3 + 2 diff --git a/firmware/vanalign/esp32_ble.yaml b/firmware/vanalign/esp32_ble.yaml index fc9165b..9d2572f 100644 --- a/firmware/vanalign/esp32_ble.yaml +++ b/firmware/vanalign/esp32_ble.yaml @@ -22,9 +22,15 @@ esphome: esp32: board: esp32-s3-devkitc-1 + flash_size: 16MB framework: type: esp-idf +# N16R8: 16 MB Flash + 8 MB PSRAM, beim S3 als Octal-PSRAM angebunden. +psram: + mode: octal + speed: 80MHz + logger: level: WARN diff --git a/firmware/vanalign/esp32_ble_sim.yaml b/firmware/vanalign/esp32_ble_sim.yaml new file mode 100644 index 0000000..c7c92cd --- /dev/null +++ b/firmware/vanalign/esp32_ble_sim.yaml @@ -0,0 +1,332 @@ +# VanAlign Pro - Neigungsmessung über BLE (SIMULATION) +# +# Kopie von esp32_ble.yaml für den Fall, dass gerade kein MPU6050 zum +# Anschliessen vorhanden ist. Der `platform: mpu6050`-Sensor sowie der +# i2c-Bus wurden entfernt und durch Template-Sensoren ersetzt, die +# plausible, sich langsam ändernde Beschleunigungswerte erzeugen (ein +# gedachter Sensor, der gemütlich hin- und herschaukelt). Pitch/Roll, +# Kalibrierung und die BLE-Charakteristiken funktionieren dadurch exakt wie +# im Original - nur eben ohne angeschlossene Hardware. +# +# Name und Friendly Name sind bewusst auf "-sim" abgeändert, damit dieses +# Gerät im Netzwerk/BLE nicht mit einem echten VanAlign-Gerät kollidiert. +# +# Sobald wieder ein echter MPU6050 verfügbar ist, einfach esp32_ble.yaml +# weiterverwenden - diese Datei ist nur zum Testen der App/BLE-Anbindung. + +esphome: + name: vanalign-sim + friendly_name: "VanAlign Pro (Sim)" + + +esp32: + board: esp32-s3-devkitc-1 + flash_size: 16MB + framework: + type: esp-idf + +# N16R8: 16 MB Flash + 8 MB PSRAM, beim S3 als Octal-PSRAM angebunden. +psram: + mode: octal + speed: 80MHz + + +logger: + level: WARN + +espnow: + channel: 1 + +sensor: + # Simulierte Rohwerte anstelle des physischen MPU6050. Die Sensor-Lage + # (Pitch/Roll) wandert langsam und stetig, wie es ein tatsächlich leicht + # schaukelndes Fahrzeug/Werkstück tun würde (Perioden ~75s/~113s). + - platform: template + name: "MPU6050 Accel X (Sim)" + id: accel_x + internal: true + update_interval: 0.1s + lambda: |- + float t = millis() / 1000.0f; + float pitch_rad = (15.0f * sin(t / 12.0f)) * 3.14159265f / 180.0f; + float roll_rad = (10.0f * sin(t / 18.0f + 1.0f)) * 3.14159265f / 180.0f; + return -9.80665f * sin(roll_rad) * cos(pitch_rad); + + - platform: template + name: "MPU6050 Accel Y (Sim)" + id: accel_y + internal: true + update_interval: 0.1s + lambda: |- + float t = millis() / 1000.0f; + float pitch_rad = (15.0f * sin(t / 12.0f)) * 3.14159265f / 180.0f; + return 9.80665f * sin(pitch_rad); + + - platform: template + name: "MPU6050 Accel Z (Sim)" + id: accel_z + internal: true + update_interval: 0.1s + lambda: |- + float t = millis() / 1000.0f; + float pitch_rad = (15.0f * sin(t / 12.0f)) * 3.14159265f / 180.0f; + float roll_rad = (10.0f * sin(t / 18.0f + 1.0f)) * 3.14159265f / 180.0f; + return 9.80665f * cos(roll_rad) * cos(pitch_rad); + + # Gyro-Werte werden nur zur Anzeige simuliert (kleine Winkelgeschwindigkeit + # passend zur Schaukelbewegung oben, kein realer Bezug nötig). + - platform: template + name: "MPU6050 Gyro X-Achse" + id: mpu_gyro_x + update_interval: 0.1s + lambda: |- + float t = millis() / 1000.0f; + return (10.0f / 18.0f) * cos(t / 18.0f + 1.0f) * 3.14159265f / 180.0f; + + - platform: template + name: "MPU6050 Gyro Y-Achse" + id: mpu_gyro_y + update_interval: 0.1s + lambda: |- + float t = millis() / 1000.0f; + return (15.0f / 12.0f) * cos(t / 12.0f) * 3.14159265f / 180.0f; + + - platform: template + name: "MPU6050 Gyro Z-Achse" + id: mpu_gyro_z + update_interval: 0.1s + lambda: |- + return 0.0f; + + - platform: template + name: "Neigung Pitch" + id: pitch + icon: mdi:caravan + unit_of_measurement: "°" + accuracy_decimals: 1 + update_interval: 0.1s + lambda: |- + if (isnan(id(accel_x).state) || isnan(id(accel_y).state) || isnan(id(accel_z).state)) { + return NAN; + } + float raw = atan2(id(accel_y).state, sqrt(pow(id(accel_x).state, 2) + pow(id(accel_z).state, 2))) * (180.0 / 3.14159265); + return raw - id(pitch_offset); // Offset wird hier subtrahiert + filters: + - sliding_window_moving_average: + window_size: 8 + send_every: 1 + - exponential_moving_average: + alpha: 0.2 + + - platform: template + name: "Neigung Roll" + id: roll + icon: mdi:axis-x-rotate-clockwise + unit_of_measurement: "°" + accuracy_decimals: 1 + update_interval: 0.1s + lambda: |- + if (isnan(id(accel_x).state) || isnan(id(accel_z).state)) { + return NAN; + } + float raw = atan2(-id(accel_x).state, id(accel_z).state) * (180.0 / 3.14159265); + return raw - id(roll_offset); // Offset wird hier subtrahiert + filters: + - sliding_window_moving_average: + window_size: 8 + send_every: 1 + - exponential_moving_average: + alpha: 0.2 + +globals: + - id: pitch_offset + type: float + restore_value: yes + initial_value: '0.0' + - id: roll_offset + type: float + restore_value: yes + initial_value: '0.0' + # Die Einbaulage liegt im Gerät, nicht in den Apps: Sie beschreibt, wie der + # Sensor im Fahrzeug sitzt – eine Eigenschaft des Einbaus, nicht des Telefons. + # Damit sehen iPhone, Uhr und Android dasselbe, ohne sie je einzeln zu + # bestimmen. Angewandt wird sie weiterhin in den Apps; das Gerät verwahrt sie + # nur, sonst rechneten ältere Clients die Korrektur ein zweites Mal. + - id: orientation_version + type: uint8_t + restore_value: yes + initial_value: '0' + - id: orientation_source + type: uint8_t + restore_value: yes + initial_value: '0' + - id: orientation_invert_long + type: bool + restore_value: yes + initial_value: 'false' + - id: orientation_invert_lat + type: bool + restore_value: yes + initial_value: 'false' + - id: orientation_twist + type: float + restore_value: yes + initial_value: '0.0' + + - id: enable_captive + type: bool + restore_value: yes + initial_value: 'false' + +esp32_ble_server: + services: + - uuid: 2a24b789-7aab-4535-af3e-ee76a35cc42d + advertise: true + characteristics: + - id: pitch_ble + uuid: cad48e28-7fbe-41cf-bae9-d77a6c233424 + description: "Pitch" + read: true + value: !lambda |- + std::vector v(sizeof(float)); + float val = id(pitch).state; + memcpy(v.data(), &val, sizeof(float)); + return v; + - id: roll_ble + uuid: cad48e28-7fbe-41cf-bae9-d77a6c233425 + description: "Roll" + read: true + value: !lambda |- + std::vector v(sizeof(float)); + float val = id(roll).state; + memcpy(v.data(), &val, sizeof(float)); + return v; + # Die gespeicherten Nullpunkte, zwei Floats. Daran erkennen die Apps, + # ob überhaupt schon kalibriert wurde. + - id: offsets_ble + uuid: cad48e28-7fbe-41cf-bae9-d77a6c233426 + description: "Kalibrier-Offsets" + read: true + value: !lambda |- + std::vector v(2 * sizeof(float)); + float p = id(pitch_offset); + float r = id(roll_offset); + memcpy(v.data(), &p, sizeof(float)); + memcpy(v.data() + sizeof(float), &r, sizeof(float)); + return v; + + # Die Einbaulage, acht Byte: + # + # 0 Version, 1 = gültig gesetzt, 0 = nie geschrieben + # 1 Längsachse: 0 = Pitch des Sensors, 1 = Roll des Sensors + # 2 längs umgekehrt (0/1) + # 3 quer umgekehrt (0/1) + # 4..7 Verdrehung um die Hochachse, float32, Grad + - id: orientation_ble + uuid: cad48e28-7fbe-41cf-bae9-d77a6c233428 + description: "Einbaulage" + read: true + write: true + value: !lambda |- + std::vector v(8, 0); + v[0] = id(orientation_version); + v[1] = id(orientation_source); + v[2] = id(orientation_invert_long) ? 1 : 0; + v[3] = id(orientation_invert_lat) ? 1 : 0; + float t = id(orientation_twist); + memcpy(v.data() + 4, &t, sizeof(float)); + return v; + on_write: + then: + - lambda: |- + if (x.size() < 8 || x[0] != 1) { + ESP_LOGW("vanalign", "Einbaulage verworfen: %d Byte, Version %d", + (int) x.size(), x.empty() ? -1 : (int) x[0]); + return; + } + float t; + memcpy(&t, x.data() + 4, sizeof(float)); + if (!std::isfinite(t) || fabsf(t) > 180.0f) { + ESP_LOGW("vanalign", "Einbaulage verworfen: Verdrehung %.1f", t); + return; + } + id(orientation_version) = 1; + id(orientation_source) = x[1]; + id(orientation_invert_long) = x[2] != 0; + id(orientation_invert_lat) = x[3] != 0; + id(orientation_twist) = t; + ESP_LOGI("vanalign", "Einbaulage gespeichert: Quelle=%d laengs=%d quer=%d verdreht=%.1f", + (int) x[1], (int) x[2], (int) x[3], t); + + - id: calib_ble + uuid: cad48e28-7fbe-41cf-bae9-d77a6c233427 + description: "Kalibriere Neigung" + write: true + on_write: + then: + - lambda: |- + bool reset = !x.empty() && (x[0] == 0x00 || x[0] == '0'); + if (reset) { + id(reset_calibration).execute(); + } else { + id(calibrate_level).execute(); + } + +#web_server: +# port: 80 + + +#ota: +# platform: web_server + + +#wifi: +# ap: +# ssid: "VanAlign-Setup" +# password: "kalibrierung" + + + +script: + # Die aktuelle Lage wird zur neuen Null. Knopf und Bluetooth laufen hier + # zusammen, damit sie nicht auseinanderdriften. + - id: calibrate_level + then: + - lambda: |- + if (isnan(id(accel_x).state) || isnan(id(accel_y).state) || isnan(id(accel_z).state)) { + ESP_LOGW("vanalign", "Kalibrierung abgebrochen: keine Sensorwerte"); + return; + } + id(pitch_offset) = atan2(id(accel_y).state, sqrt(pow(id(accel_x).state, 2) + pow(id(accel_z).state, 2))) * (180.0 / 3.14159265); + id(roll_offset) = atan2(-id(accel_x).state, id(accel_z).state) * (180.0 / 3.14159265); + ESP_LOGI("vanalign", "Kalibriert: pitch_offset=%.2f roll_offset=%.2f", id(pitch_offset), id(roll_offset)); + + - id: reset_calibration + then: + - lambda: |- + id(pitch_offset) = 0.0f; + id(roll_offset) = 0.0f; + ESP_LOGI("vanalign", "Kalibrierung zurückgesetzt"); + +button: + - platform: template + name: "Kalibriere Neigung" + id: calib_button + on_press: + - script.execute: calibrate_level + + - platform: template + name: "Kalibrierung zurücksetzen" + id: calib_reset_button + on_press: + - script.execute: reset_calibration + - platform: restart + name: "ESP Restart" + +text_sensor: + - platform: template + name: "Firmware Version" + id: firmware_version + icon: mdi:tag + lambda: |- + return {"v1.0.2-sim"}; diff --git a/firmware/vanalign/esp32_ble_solar.yaml b/firmware/vanalign/esp32_ble_solar.yaml new file mode 100644 index 0000000..16ca5c9 --- /dev/null +++ b/firmware/vanalign/esp32_ble_solar.yaml @@ -0,0 +1,233 @@ +# VanAlign Solar - Votronic Solarladeregler über BLE +# +# Zweiter ESP32 im Fahrzeug, unabhängig vom Neigungssensor (esp32_ble.yaml). +# Liest den Votronic-Solarladeregler über den Displaylink-Port (UART) mit der +# externen Komponente github://syssi/esphome-votronic aus und stellt die +# Werte - genau wie beim Neigungssensor - über einen eigenen BLE-Service +# bereit. Zusätzlich (für Debug-Zwecke) läuft WLAN mit und die Werte werden +# auch auf dem eingebauten Webserver (Port 80) angezeigt - MQTT und die API +# sind weiterhin nicht enthalten. WLAN-Zugangsdaten liegen in secrets.yaml +# (lokal anzulegen, ist per .gitignore ausgeschlossen). +# +# Verkabelung: UART TX=GPIO4, RX=GPIO5 an den Displaylink-Port des Reglers, +# Baudrate 1000 (kein Tippfehler - das Votronic-Protokoll nutzt diese +# ungewöhnlich niedrige Rate). Board/Pins ggf. an die tatsächlich verbaute +# Hardware anpassen, hier als ESP32-S3-DevKitC-1 wie beim Neigungssensor +# angenommen. +# +# BLE-Service 05c9a349-2b8e-4b1d-9c9d-c247e9a6a001 (wird beworben): +# +# Charakteristik UUID (Ende) Inhalt +# Batteriespg. ...a101 Float32 LE, Volt +# PV-Spannung ...a102 Float32 LE, Volt +# PV-Strom ...a103 Float32 LE, Ampere +# PV-Leistung ...a104 Float32 LE, Watt +# Reglertemp. ...a105 Float32 LE, °C +# Statusflags ...a106 1 Byte, Bitmaske: Bit0 Batterie lädt, +# Bit1 Batterie entlädt, Bit2 PV-Regler +# aktiv, Bit3 PV-Strombegrenzung, Bit4 AES +# PV-Modus-ID ...a107 1 Byte, roher Wert aus pv_mode_setting_id +# Batteriestatus ...a108 1 Byte, rohe Bitmaske aus dem Regler +# Reglerstatus ...a109 1 Byte, rohe Bitmaske aus dem Regler +# +# Alle Charakteristiken sind reine Lesewerte, wie beim Neigungssensor fragt +# der Client sie im Takt ab. + +esphome: + name: vanalign-solar + friendly_name: "VanAlign Solar" + +esp32: + board: esp32-s3-devkitc-1 + flash_size: 16MB + framework: + type: esp-idf + +# N16R8: 16 MB Flash + 8 MB PSRAM, beim S3 als Octal-PSRAM angebunden. +psram: + mode: octal + speed: 80MHz + +logger: + level: WARN + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + +web_server: + port: 80 + +external_components: + - source: github://syssi/esphome-votronic@main + refresh: 0s + +uart: + - id: uart_0 + baud_rate: 1000 + tx_pin: GPIO4 + rx_pin: GPIO5 + +votronic: + - id: votronic0 + uart_id: uart_0 + rx_timeout: 150ms + throttle: 2s + +sensor: + - platform: votronic + votronic_id: votronic0 + battery_computer_battery_voltage: + name: "Batteriespannung" + id: battery_voltage + pv_voltage: + name: "PV Spannung" + id: pv_voltage + pv_current: + name: "PV Strom" + id: pv_current + pv_power: + name: "PV Leistung" + id: pv_power + pv_controller_temperature: + name: "Reglertemperatur" + id: pv_temperature + pv_mode_setting_id: + name: "PV Modus-ID" + id: pv_mode_id + pv_battery_status_bitmask: + name: "PV Batteriestatus (Bitmaske)" + id: pv_battery_status_bitmask + pv_controller_status_bitmask: + name: "PV Reglerstatus (Bitmaske)" + id: pv_controller_status_bitmask + +binary_sensor: + - platform: votronic + votronic_id: votronic0 + battery_computer_charging: + name: "Batterie lädt" + id: battery_charging + battery_computer_discharging: + name: "Batterie entlädt" + id: battery_discharging + pv_controller_active: + name: "PV Regler aktiv" + id: pv_active + pv_current_reduction: + name: "PV Strombegrenzung" + id: pv_current_reduction + pv_aes_active: + name: "PV AES aktiv" + id: pv_aes_active + +text_sensor: + - platform: votronic + votronic_id: votronic0 + pv_mode_setting: + name: "PV Modus" + pv_battery_status: + name: "PV Batteriestatus" + pv_controller_status: + name: "PV Reglerstatus" + - platform: template + name: "Firmware Version" + id: firmware_version + icon: mdi:tag + lambda: |- + return {"v1.0.0"}; + +esp32_ble_server: + services: + - uuid: 05c9a349-2b8e-4b1d-9c9d-c247e9a6a001 + advertise: true + characteristics: + - id: battery_voltage_ble + uuid: 05c9a349-2b8e-4b1d-9c9d-c247e9a6a101 + description: "Batteriespannung" + read: true + value: !lambda |- + std::vector v(sizeof(float)); + float val = id(battery_voltage).state; + memcpy(v.data(), &val, sizeof(float)); + return v; + - id: pv_voltage_ble + uuid: 05c9a349-2b8e-4b1d-9c9d-c247e9a6a102 + description: "PV Spannung" + read: true + value: !lambda |- + std::vector v(sizeof(float)); + float val = id(pv_voltage).state; + memcpy(v.data(), &val, sizeof(float)); + return v; + - id: pv_current_ble + uuid: 05c9a349-2b8e-4b1d-9c9d-c247e9a6a103 + description: "PV Strom" + read: true + value: !lambda |- + std::vector v(sizeof(float)); + float val = id(pv_current).state; + memcpy(v.data(), &val, sizeof(float)); + return v; + - id: pv_power_ble + uuid: 05c9a349-2b8e-4b1d-9c9d-c247e9a6a104 + description: "PV Leistung" + read: true + value: !lambda |- + std::vector v(sizeof(float)); + float val = id(pv_power).state; + memcpy(v.data(), &val, sizeof(float)); + return v; + - id: pv_temperature_ble + uuid: 05c9a349-2b8e-4b1d-9c9d-c247e9a6a105 + description: "Reglertemperatur" + read: true + value: !lambda |- + std::vector v(sizeof(float)); + float val = id(pv_temperature).state; + memcpy(v.data(), &val, sizeof(float)); + return v; + # Bit0 Batterie lädt, Bit1 Batterie entlädt, Bit2 PV-Regler aktiv, + # Bit3 PV-Strombegrenzung, Bit4 AES aktiv. + - id: status_flags_ble + uuid: 05c9a349-2b8e-4b1d-9c9d-c247e9a6a106 + description: "Statusflags" + read: true + value: !lambda |- + std::vector v(1, 0); + uint8_t flags = 0; + if (id(battery_charging).state) flags |= (1 << 0); + if (id(battery_discharging).state) flags |= (1 << 1); + if (id(pv_active).state) flags |= (1 << 2); + if (id(pv_current_reduction).state) flags |= (1 << 3); + if (id(pv_aes_active).state) flags |= (1 << 4); + v[0] = flags; + return v; + - id: pv_mode_id_ble + uuid: 05c9a349-2b8e-4b1d-9c9d-c247e9a6a107 + description: "PV Modus-ID" + read: true + value: !lambda |- + std::vector v(1, 0); + v[0] = (uint8_t) id(pv_mode_id).state; + return v; + - id: pv_battery_status_ble + uuid: 05c9a349-2b8e-4b1d-9c9d-c247e9a6a108 + description: "PV Batteriestatus (Bitmaske)" + read: true + value: !lambda |- + std::vector v(1, 0); + v[0] = (uint8_t) id(pv_battery_status_bitmask).state; + return v; + - id: pv_controller_status_ble + uuid: 05c9a349-2b8e-4b1d-9c9d-c247e9a6a109 + description: "PV Reglerstatus (Bitmaske)" + read: true + value: !lambda |- + std::vector v(1, 0); + v[0] = (uint8_t) id(pv_controller_status_bitmask).state; + return v; + +button: + - platform: restart + name: "ESP Restart" diff --git a/firmware/vanalign/esp32_ble_solar_sim.yaml b/firmware/vanalign/esp32_ble_solar_sim.yaml new file mode 100644 index 0000000..7c2a90a --- /dev/null +++ b/firmware/vanalign/esp32_ble_solar_sim.yaml @@ -0,0 +1,265 @@ +# VanAlign Solar - Votronic Solarladeregler über BLE (SIMULATION) +# +# Kopie von esp32_ble_solar.yaml für den Fall, dass gerade kein Votronic- +# Regler zum Anschliessen vorhanden ist. Die `platform: votronic`-Sensoren +# sowie UART/external_components wurden entfernt und durch Template-Sensoren +# ersetzt, die einen plausiblen Tagesverlauf simulieren: PV-Spannung/-Strom +# folgen einem "Sonnenstand" (ein gedachter, ca. 6 Minuten langer Tag), die +# Batteriespannung schwankt gemütlich mit, und die Statusflags/Bitmasken +# leiten sich aus diesen Werten ab. Die BLE-Charakteristiken funktionieren +# dadurch exakt wie im Original - nur eben ohne angeschlossene Hardware. +# +# Name und Friendly Name sind bewusst auf "-sim" abgeändert, damit dieses +# Gerät im BLE-Umfeld nicht mit einem echten VanAlign-Solar-Gerät kollidiert. +# +# Für Debug-Zwecke läuft WLAN mit und die Werte werden zusätzlich auf dem +# eingebauten Webserver (Port 80) angezeigt; WLAN-Zugangsdaten liegen in +# secrets.yaml (lokal anzulegen, ist per .gitignore ausgeschlossen). Alle +# simulierten Werte aktualisieren sich höchstens jede Sekunde, damit sich +# Änderungen beim Debuggen zügig zeigen. +# +# Sobald wieder ein echter Votronic-Regler verfügbar ist, einfach +# esp32_ble_solar.yaml weiterverwenden - diese Datei ist nur zum Testen der +# App/BLE-Anbindung. + +esphome: + name: vanalign-solar-sim + friendly_name: "VanAlign Solar (Sim)" + +esp32: + board: esp32-s3-devkitc-1 + flash_size: 16MB + framework: + type: esp-idf + +# N16R8: 16 MB Flash + 8 MB PSRAM, beim S3 als Octal-PSRAM angebunden. +psram: + mode: octal + speed: 80MHz + +logger: + level: WARN + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + +web_server: + port: 80 + +sensor: + # Simulierter "Sonnenstand": 0 nachts, sanfter Buckel tagsüber, Periode + # ca. 6 Minuten - reicht zum Beobachten eines vollen Auf/Ab in der App. + - platform: template + name: "Sonnenstand (Sim)" + id: sun_level + internal: true + update_interval: 1s + lambda: |- + float t = millis() / 1000.0f; + return std::max(0.0f, sinf(t / 180.0f)); + + - platform: template + name: "Batteriespannung (Sim)" + id: battery_voltage + unit_of_measurement: "V" + accuracy_decimals: 2 + update_interval: 1s + lambda: |- + float t = millis() / 1000.0f; + return 12.6f + 0.8f * id(sun_level).state + 0.05f * sin(t / 20.0f); + + - platform: template + name: "PV Spannung (Sim)" + id: pv_voltage + unit_of_measurement: "V" + accuracy_decimals: 2 + update_interval: 1s + lambda: |- + float t = millis() / 1000.0f; + return id(sun_level).state > 0.01f ? (19.0f + 1.0f * sin(t / 25.0f)) : 0.0f; + + - platform: template + name: "PV Strom (Sim)" + id: pv_current + unit_of_measurement: "A" + accuracy_decimals: 2 + update_interval: 1s + lambda: |- + float t = millis() / 1000.0f; + return id(sun_level).state * (6.0f + 1.5f * sin(t / 17.0f)); + + - platform: template + name: "PV Leistung (Sim)" + id: pv_power + unit_of_measurement: "W" + accuracy_decimals: 1 + update_interval: 1s + lambda: |- + return id(pv_voltage).state * id(pv_current).state; + + - platform: template + name: "Reglertemperatur (Sim)" + id: pv_temperature + unit_of_measurement: "°C" + accuracy_decimals: 1 + update_interval: 1s + lambda: |- + float t = millis() / 1000.0f; + return 22.0f + 10.0f * id(sun_level).state + 1.0f * sin(t / 11.0f); + + - platform: template + name: "PV Modus-ID (Sim)" + id: pv_mode_id + update_interval: 1s + lambda: |- + return 3.0f; // fester simulierter Modus (MPPT) + + - platform: template + name: "PV Batteriestatus (Bitmaske, Sim)" + id: pv_battery_status_bitmask + update_interval: 1s + lambda: |- + return id(sun_level).state > 0.01f ? 1.0f : 0.0f; + + - platform: template + name: "PV Reglerstatus (Bitmaske, Sim)" + id: pv_controller_status_bitmask + update_interval: 1s + lambda: |- + return id(sun_level).state > 0.01f ? 2.0f : 0.0f; + +binary_sensor: + - platform: template + name: "Batterie lädt (Sim)" + id: battery_charging + lambda: |- + return id(pv_current).state > 0.2f; + + - platform: template + name: "Batterie entlädt (Sim)" + id: battery_discharging + lambda: |- + return id(pv_current).state <= 0.2f; + + - platform: template + name: "PV Regler aktiv (Sim)" + id: pv_active + lambda: |- + return id(sun_level).state > 0.01f; + + - platform: template + name: "PV Strombegrenzung (Sim)" + id: pv_current_reduction + lambda: |- + float t = millis() / 1000.0f; + return id(pv_active).state && sin(t / 60.0f) > 0.9f; + + - platform: template + name: "PV AES aktiv (Sim)" + id: pv_aes_active + lambda: |- + return id(battery_voltage).state > 13.3f; + +text_sensor: + - platform: template + name: "Firmware Version" + id: firmware_version + icon: mdi:tag + lambda: |- + return {"v1.0.0-sim"}; + +esp32_ble_server: + services: + - uuid: 05c9a349-2b8e-4b1d-9c9d-c247e9a6a001 + advertise: true + characteristics: + - id: battery_voltage_ble + uuid: 05c9a349-2b8e-4b1d-9c9d-c247e9a6a101 + description: "Batteriespannung" + read: true + value: !lambda |- + std::vector v(sizeof(float)); + float val = id(battery_voltage).state; + memcpy(v.data(), &val, sizeof(float)); + return v; + - id: pv_voltage_ble + uuid: 05c9a349-2b8e-4b1d-9c9d-c247e9a6a102 + description: "PV Spannung" + read: true + value: !lambda |- + std::vector v(sizeof(float)); + float val = id(pv_voltage).state; + memcpy(v.data(), &val, sizeof(float)); + return v; + - id: pv_current_ble + uuid: 05c9a349-2b8e-4b1d-9c9d-c247e9a6a103 + description: "PV Strom" + read: true + value: !lambda |- + std::vector v(sizeof(float)); + float val = id(pv_current).state; + memcpy(v.data(), &val, sizeof(float)); + return v; + - id: pv_power_ble + uuid: 05c9a349-2b8e-4b1d-9c9d-c247e9a6a104 + description: "PV Leistung" + read: true + value: !lambda |- + std::vector v(sizeof(float)); + float val = id(pv_power).state; + memcpy(v.data(), &val, sizeof(float)); + return v; + - id: pv_temperature_ble + uuid: 05c9a349-2b8e-4b1d-9c9d-c247e9a6a105 + description: "Reglertemperatur" + read: true + value: !lambda |- + std::vector v(sizeof(float)); + float val = id(pv_temperature).state; + memcpy(v.data(), &val, sizeof(float)); + return v; + # Bit0 Batterie lädt, Bit1 Batterie entlädt, Bit2 PV-Regler aktiv, + # Bit3 PV-Strombegrenzung, Bit4 AES aktiv. + - id: status_flags_ble + uuid: 05c9a349-2b8e-4b1d-9c9d-c247e9a6a106 + description: "Statusflags" + read: true + value: !lambda |- + std::vector v(1, 0); + uint8_t flags = 0; + if (id(battery_charging).state) flags |= (1 << 0); + if (id(battery_discharging).state) flags |= (1 << 1); + if (id(pv_active).state) flags |= (1 << 2); + if (id(pv_current_reduction).state) flags |= (1 << 3); + if (id(pv_aes_active).state) flags |= (1 << 4); + v[0] = flags; + return v; + - id: pv_mode_id_ble + uuid: 05c9a349-2b8e-4b1d-9c9d-c247e9a6a107 + description: "PV Modus-ID" + read: true + value: !lambda |- + std::vector v(1, 0); + v[0] = (uint8_t) id(pv_mode_id).state; + return v; + - id: pv_battery_status_ble + uuid: 05c9a349-2b8e-4b1d-9c9d-c247e9a6a108 + description: "PV Batteriestatus (Bitmaske)" + read: true + value: !lambda |- + std::vector v(1, 0); + v[0] = (uint8_t) id(pv_battery_status_bitmask).state; + return v; + - id: pv_controller_status_ble + uuid: 05c9a349-2b8e-4b1d-9c9d-c247e9a6a109 + description: "PV Reglerstatus (Bitmaske)" + read: true + value: !lambda |- + std::vector v(1, 0); + v[0] = (uint8_t) id(pv_controller_status_bitmask).state; + return v; + +button: + - platform: restart + name: "ESP Restart"