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 <noreply@anthropic.com>
49 lines
1.8 KiB
Swift
49 lines
1.8 KiB
Swift
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
|
|
}
|
|
}
|