Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 29 additions & 22 deletions KiaExtension/GetCarPowerLevelStatusHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ class GetCarPowerLevelStatusHandler: NSObject, INGetCarPowerLevelStatusIntentHan
} else {
try manager.store(status: status)
}
result = status.state.toIntentResponse(carId: carId, vehicleParameters: vehicleParameters, lastUpdateDate: status.lastUpdateTime)
logDebug("Loaded car status '\(status.state.vehicle.green.batteryManagement.batteryRemain.ratio)'", category: .vehicle)
result = status.status.toIntentResponse(carId: carId, vehicleParameters: vehicleParameters, lastUpdateDate: status.lastUpdateTime)
logDebug("Loaded car status '\(status.status.green.batteryManagement.batteryRemain.ratio)'", category: .vehicle)
} catch {
if let error = error as? ApiError {
switch error {
Expand All @@ -92,7 +92,7 @@ class GetCarPowerLevelStatusHandler: NSObject, INGetCarPowerLevelStatusIntentHan
logDebug("Returning cached data for failure", category: .vehicle)
manager.restoreOutdatedData()
if let cachedData = try? manager.vehicleState {
return cachedData.state.toIntentResponse(carId: carId, vehicleParameters: vehicleParameters, lastUpdateDate: cachedData.lastUpdateTime)
return cachedData.status.toIntentResponse(carId: carId, vehicleParameters: vehicleParameters, lastUpdateDate: cachedData.lastUpdateTime)
} else {
logDebug("No cached data, returning failure", category: .vehicle)
manager.removeLastUpdateDate()
Expand All @@ -118,7 +118,11 @@ class GetCarPowerLevelStatusHandler: NSObject, INGetCarPowerLevelStatusIntentHan

}
logDebug("Handler: Returning mocking data", category: .vehicle)
return VehicleStateResponse.lowBatteryPreview.state.toIntentResponse(carId: carId, vehicleParameters: vehicleParameters, lastUpdateDate: .now - 1 * 60)
return KiaVehicleStatusMapper.map(response: VehicleStateResponse.lowBatteryPreview).status.toIntentResponse(
carId: carId,
vehicleParameters: vehicleParameters,
lastUpdateDate: .now - 1 * 60
)
} else if let cachedData = try? manager.vehicleState {
// Use data from cache
if cachedData.lastUpdateTime + 5 * 60 < Date.now {
Expand All @@ -133,7 +137,7 @@ class GetCarPowerLevelStatusHandler: NSObject, INGetCarPowerLevelStatusIntentHan
}

logDebug("Handler: Use cached data", category: .vehicle)
return cachedData.state.toIntentResponse(carId: carId, vehicleParameters: vehicleParameters, lastUpdateDate: cachedData.lastUpdateTime)
return cachedData.status.toIntentResponse(carId: carId, vehicleParameters: vehicleParameters, lastUpdateDate: cachedData.lastUpdateTime)
} else {
// Get data from server
await credentialsHandler.continueOrWaitForCredentials()
Expand All @@ -160,7 +164,7 @@ class GetCarPowerLevelStatusHandler: NSObject, INGetCarPowerLevelStatusIntentHan
// Send initial update from cached data immediately
if let cachedData = try? manager.vehicleState {
logDebug("Updater: Sending initial update from cached data", category: .vehicle)
let response = cachedData.state.toIntentResponse(carId: carId, vehicleParameters: vehicleParameters, lastUpdateDate: .now - 1 * 60)
let response = cachedData.status.toIntentResponse(carId: carId, vehicleParameters: vehicleParameters, lastUpdateDate: .now - 1 * 60)
observer.didUpdate(getCarPowerLevelStatus: response)
}

Expand All @@ -179,20 +183,18 @@ class GetCarPowerLevelStatusHandler: NSObject, INGetCarPowerLevelStatusIntentHan

logDebug("Updater: Received MQTT update", category: .mqtt)

let response = mqttStatus.state.toIntentResponse(
let mappedStatus = KiaVehicleStatusMapper.map(state: mqttStatus.state.vehicle)
let response = mappedStatus.toIntentResponse(
carId: carId,
vehicleParameters: self.vehicleParameters,
lastUpdateDate: mqttStatus.lastUpdateTime
)

// Update stored status
do {
let status = VehicleStateResponse(
resultCode: "S",
serviceNumber: "0",
returnCode: "0",
let status = VehicleStatusSnapshot(
lastUpdateTime: mqttStatus.lastUpdateTime,
state: mqttStatus.state
status: mappedStatus
)
try manager.store(status: status)
} catch {
Expand Down Expand Up @@ -302,7 +304,7 @@ class GetCarPowerLevelStatusHandler: NSObject, INGetCarPowerLevelStatusIntentHan
}
}

extension VehicleStateWrapper {
extension VehicleStatus {
/// Converts vehicle status to Apple Maps compatible INGetCarPowerLevelStatusIntentResponse
/// - Parameters:
/// - carId: Unique identifier for the vehicle
Expand All @@ -312,10 +314,10 @@ extension VehicleStateWrapper {
let result: INGetCarPowerLevelStatusIntentResponse

let dateComponents = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute, .second], from: lastUpdateDate)
let chargingInformation = vehicle.green.chargingInformation
let batteryManagement = vehicle.green.batteryManagement
let drivetrain = vehicle.drivetrain
let batteryCapacity = Double(batteryManagement.batteryCapacity.value)
let chargingInformation = green.chargingInformation
let batteryManagement = green.batteryManagement
let driveTrain = drivetrain
let batteryCapacity = estimatedBatteryCapacity(from: batteryManagement)
let batteryRemain = Float(batteryManagement.batteryRemain.ratio)

result = .init(code: .success, userActivity: nil)
Expand All @@ -325,29 +327,34 @@ extension VehicleStateWrapper {
result.chargingFormulaArguments = vehicleParameters.chargingFormulaArguments(maximumBatteryCapacity: batteryCapacity, unit: .kilojoules)

result.maximumDistance = .init(value: vehicleParameters.maximumDistance, unit: .kilometers)
result.distanceRemaining = .init(value: Double(drivetrain.fuelSystem.dte.total), unit: drivetrain.fuelSystem.dte.unit.measuremntUnit)
result.distanceRemaining = .init(value: Double(driveTrain.fuelSystem.dte.total), unit: driveTrain.fuelSystem.dte.unit.measuremntUnit)

result.maximumDistanceElectric = .init(value: vehicleParameters.maximumDistance, unit: .kilometers)
result.distanceRemainingElectric = .init(value: Double(drivetrain.fuelSystem.dte.total), unit: drivetrain.fuelSystem.dte.unit.measuremntUnit)
result.distanceRemainingElectric = .init(value: Double(driveTrain.fuelSystem.dte.total), unit: driveTrain.fuelSystem.dte.unit.measuremntUnit)

result.minimumBatteryCapacity = .init(value: 0, unit: .kilowattHours)
result.currentBatteryCapacity = .init(value: batteryCapacity * 0.01 * Double(batteryRemain), unit: .kilojoules)
result.maximumBatteryCapacity = .init(value: batteryCapacity, unit: .kilojoules)

result.charging = chargingInformation.electricCurrentLevel.state == 1
result.charging = isCharging
if result.charging == true {
let charging = chargingInformation.charging
let measurement = Measurement<UnitDuration>(value: charging.remainTime, unit: charging.remainTimeUnit.unitDuration)
result.minutesToFull = Int(measurement.converted(to: .minutes).value)
result.activeConnector = .ccs2
} else {
result.minutesToFull = chargingInformation.estimatedTime.quick
result.minutesToFull = nil
result.activeConnector = nil
}

result.chargePercentRemaining = batteryRemain / 100

return result
}
}

private func estimatedBatteryCapacity(from batteryManagement: VehicleStatus.Green.BatteryManagement) -> Double {
let ratio = batteryManagement.batteryRemain.ratio
guard ratio > 0 else { return batteryManagement.batteryRemain.value }
return batteryManagement.batteryRemain.value * 100 / ratio
}
}
13 changes: 12 additions & 1 deletion KiaMaps.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -91,16 +91,27 @@
Api/ApiResponse.swift,
Api/Helpers/BoolPropertyWrapper.swift,
Api/Helpers/DatePropertyWrapper.swift,
Api/Kia/HMGAuthClient.swift,
Api/Kia/HMGMQTTClient.swift,
Api/Kia/HMGVehicleClient.swift,
Api/Kia/KiaApiConfiguration.swift,
Api/Kia/KiaApiEndpoint.swift,
Api/Kia/KiaMQTTModels.swift,
Api/Kia/KiaVehicleStatusMapper.swift,
Api/Models/AuthenticationModels.swift,
Api/Models/ClimateControlModels.swift,
Api/Models/ConnectorAuthorizationModels.swift,
Api/Models/MQTTModels.swift,
Api/Models/NotificationRegistrationResponse.swift,
Api/Models/SignInResponse.swift,
Api/Models/UserIntegrationResponse.swift,
Api/Models/VehicleResponse.swift,
Api/Models/VehicleStatus.swift,
Api/Models/VehicleStatusResponse.swift,
Api/Models/VehicleTypes.swift,
Api/Porsche/PorscheApiEndpoint.swift,
Api/Porsche/PorscheAuthClient.swift,
Api/Porsche/PorscheModels.swift,
Api/Porsche/PorscheVehicleMapper.swift,
Authorization/Authorization.swift,
Authorization/DarwinNotificationHelper.swift,
Authorization/Keychain.swift,
Expand Down
2 changes: 1 addition & 1 deletion KiaMaps/App/ApiExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ extension Api {
}

/// Fetch cached vehicle status with automatic token refresh
func vehicleCachedStatusWithAutoRefresh(_ vehicleId: UUID) async throws -> VehicleStateResponse {
func vehicleCachedStatusWithAutoRefresh(_ vehicleId: UUID) async throws -> VehicleStatusSnapshot {
return try await executeWithAutoRefresh {
return try await self.vehicleCachedStatus(vehicleId)
}
Expand Down
37 changes: 16 additions & 21 deletions KiaMaps/App/MainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ struct MainView: View {
@State var state: ViewState
@State var vehicles: [Vehicle] = []
@State var selectedVehicle: Vehicle? = nil
@State var selectedVehicleState: VehicleStateResponse? = nil
@State var selectedVehicleState: VehicleStatusSnapshot? = nil
@State var isSelectedVahicleExpanded = true
@State var lastUpdateDate: Date?
@State var showingProfile = false
@State var loginRetry = false

// MQTT Integration State
@StateObject private var mqttManager: MQTTManager
@State private var currentVehicleState: VehicleState?
@State private var currentVehicleState: VehicleStatus?
@State private var mqttConnectionStatus: MQTTConnectionStatus = .disconnected
@State private var receivedMQTTUpdate = false

Expand Down Expand Up @@ -115,7 +115,7 @@ struct MainView: View {
.onAppear {
setupMQTTIntegration()
}
.onChange(of: selectedVehicleState?.state.vehicle.isCharging) { _, isCharging in
.onChange(of: selectedVehicleState?.status.isCharging) { _, isCharging in
handleChargingStateChange(isCharging ?? false)
}
.onChange(of: mqttManager.connectionStatus) { _, connectionStatus in
Expand All @@ -133,7 +133,7 @@ struct MainView: View {
var contentView: some View {
if let selectedVehicle = selectedVehicle, let selectedVehicleState = selectedVehicleState {
// Use MQTT-updated status if available, otherwise use API status
let currentStatus = currentVehicleState ?? selectedVehicleState.state.vehicle
let currentStatus = currentVehicleState ?? selectedVehicleState.status

OverviewPageView(
brandName: api.configuration.brandName,
Expand All @@ -156,7 +156,7 @@ struct MainView: View {

// MARK: - Vehicle Status Icons (for toolbar)

private func VehicleStateIcons(status: VehicleStateResponse) -> some View {
private func VehicleStateIcons(status: VehicleStatusSnapshot) -> some View {
HStack(spacing: KiaDesign.Spacing.small) {
// Last update indicator
VStack(spacing: 2) {
Expand All @@ -170,7 +170,7 @@ struct MainView: View {
}

// Battery status
let batteryLevel = status.state.vehicle.green.batteryManagement.batteryRemain.ratio
let batteryLevel = status.status.green.batteryManagement.batteryRemain.ratio
VStack(spacing: 2) {
if batteryLevel > 80 {
Image(systemName: "battery.100percent")
Expand All @@ -192,7 +192,7 @@ struct MainView: View {
}

// Charging status (if applicable)
if status.state.vehicle.isCharging {
if status.status.isCharging {
VStack(spacing: 2) {
Image(systemName: "bolt.circle.fill")
.font(.caption)
Expand All @@ -218,7 +218,7 @@ struct MainView: View {
}
)
.accessibilityElement(children: .combine)
.accessibilityLabel("Vehicle status: \(Int(status.state.vehicle.green.batteryManagement.batteryRemain.ratio))% battery, \(status.state.vehicle.drivingReady ? "ready" : "not ready"), updated \(timeAgoString(from: status.lastUpdateTime))")
.accessibilityLabel("Vehicle status: \(Int(status.status.green.batteryManagement.batteryRemain.ratio))% battery, \(status.status.drivingReady ? "ready" : "not ready"), updated \(timeAgoString(from: status.lastUpdateTime))")
}

// MARK: - Helper Views
Expand Down Expand Up @@ -276,9 +276,9 @@ struct MainView: View {
if let cachedVehicle = try? manager.vehicleState {
selectedVehicleState = cachedVehicle
} else {
let VehicleState = try await api.vehicleCachedStatusWithAutoRefresh(vehicle.vehicleId)
try manager.store(status: VehicleState)
selectedVehicleState = VehicleState
let vehicleStatus = try await api.vehicleCachedStatusWithAutoRefresh(vehicle.vehicleId)
try manager.store(status: vehicleStatus)
selectedVehicleState = vehicleStatus
}

state = .authorized
Expand Down Expand Up @@ -360,7 +360,7 @@ struct MainView: View {
mqttConnectionStatus = mqttManager.connectionStatus

// Start MQTT if car is already charging
if selectedVehicleState?.state.vehicle.isCharging == true {
if selectedVehicleState?.status.isCharging == true {
startMQTTCommunication()
}
}
Expand All @@ -370,21 +370,16 @@ struct MainView: View {
guard let VehicleState = VehicleState else { return }
receivedMQTTUpdate = true

if let status = selectedVehicleState {
let status = KiaVehicleStatusMapper.map(state: VehicleState.state.vehicle)
if selectedVehicleState != nil {
selectedVehicleState = .init(
resultCode: status.resultCode,
serviceNumber: status.serviceNumber,
returnCode: status.returnCode,
lastUpdateTime: VehicleState.lastUpdateTime,
state: .init(vehicle: VehicleState.state.vehicle)
status: status
)
} else {
selectedVehicleState = .init(
resultCode: "S",
serviceNumber: "0",
returnCode: "0",
lastUpdateTime: VehicleState.lastUpdateTime,
state: .init(vehicle: VehicleState.state.vehicle)
status: status
)
}
}
Expand Down
4 changes: 2 additions & 2 deletions KiaMaps/App/Views/Components/TemperatureDial.swift
Original file line number Diff line number Diff line change
Expand Up @@ -314,15 +314,15 @@ struct TemperatureDial: View {

/// Complete climate control interface with temperature dial and additional controls
struct ClimateControlView: View {
let vehicleState: VehicleState?
let vehicleState: VehicleStatus?
let unit: TemperatureUnit

@State private var targetTemperature: Double = 22
@State private var isClimateOn: Bool = false
@State private var fanSpeed: Double = 3
@State private var isAutoMode: Bool = true

init(vehicleState: VehicleState? = nil, unit: TemperatureUnit = .celsius) {
init(vehicleState: VehicleStatus? = nil, unit: TemperatureUnit = .celsius) {
self.vehicleState = vehicleState
self.unit = unit
}
Expand Down
20 changes: 10 additions & 10 deletions KiaMaps/App/Views/Components/VehicleSilhouetteView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import SwiftUI

/// Interactive vehicle silhouette showing doors, windows, and status indicators
struct VehicleSilhouetteView: View {
let vehicleState: VehicleState
let vehicleState: VehicleStatus
let onDoorTap: ((DoorPosition) -> Void)?
let onTireTap: ((TirePosition) -> Void)?
let onChargingPortTap: (() -> Void)?
Expand Down Expand Up @@ -90,7 +90,7 @@ struct VehicleSilhouetteView: View {
}

init(
vehicleState: VehicleState,
vehicleState: VehicleStatus,
onDoorTap: ((DoorPosition) -> Void)? = nil,
onTireTap: ((TirePosition) -> Void)? = nil,
onChargingPortTap: (() -> Void)? = nil,
Expand Down Expand Up @@ -566,7 +566,7 @@ struct VehicleSilhouetteView: View {

/// Expandable detail view showing comprehensive vehicle status
struct VehicleStateDetailView: View {
let vehicleState: VehicleState
let vehicleState: VehicleStatus
let selectedElement: VehicleSilhouetteView.InteractiveElement?

var body: some View {
Expand Down Expand Up @@ -993,7 +993,7 @@ struct VehicleStateDetailView: View {

/// Container view combining silhouette with expandable details
struct InteractiveVehicleSilhouetteView: View {
let vehicleState: VehicleState
let vehicleState: VehicleStatus

@State private var selectedElement: VehicleSilhouetteView.InteractiveElement?
@State private var showingDetails = false
Expand Down Expand Up @@ -1093,40 +1093,40 @@ struct InteractiveVehicleSilhouetteView: View {
Text("Standard Scenario")
.font(KiaDesign.Typography.title2)

VehicleSilhouetteView(vehicleState: MockVehicleData.standard)
VehicleSilhouetteView(vehicleState: KiaVehicleStatusMapper.map(state: MockVehicleData.standard))

Divider()

// Interactive silhouette - charging scenario
Text("Charging Scenario (AC)")
.font(KiaDesign.Typography.title2)

InteractiveVehicleSilhouetteView(vehicleState: MockVehicleData.charging)
InteractiveVehicleSilhouetteView(vehicleState: KiaVehicleStatusMapper.map(state: MockVehicleData.charging))

Divider()

// Fast charging scenario
Text("Fast Charging Scenario (DC)")
.font(KiaDesign.Typography.title2)

InteractiveVehicleSilhouetteView(vehicleState: MockVehicleData.fastCharging)
InteractiveVehicleSilhouetteView(vehicleState: KiaVehicleStatusMapper.map(state: MockVehicleData.fastCharging))

Divider()

// Low tire pressure demo
Text("Low Tire Pressure Demo")
.font(KiaDesign.Typography.title2)

InteractiveVehicleSilhouetteView(vehicleState: MockVehicleData.lowTirePressure)
InteractiveVehicleSilhouetteView(vehicleState: KiaVehicleStatusMapper.map(state: MockVehicleData.lowTirePressure))
}
.padding()
}
.background(KiaDesign.Colors.background)
}

// MARK: - VehicleState Extensions
// MARK: - VehicleStatus Extensions

extension VehicleState {
extension VehicleStatus {
func isDoorOpen(_ door: VehicleSilhouetteView.DoorPosition) -> Bool {
let doors = cabin.door
switch door {
Expand Down
Loading