Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ extension HyundaiEuropeAPIClient {
switch command {
case .lock:
return ccs2 ? ("ccs2/control/door", ["command": "close"])
: ("/control/door", ["action": "close", "deviceId": deviceId])
: ("control/door", ["action": "close", "deviceId": deviceId])
case .unlock:
return ccs2 ? ("ccs2/control/door", ["command": "open"])
: ("/control/door", ["action": "open", "deviceId": deviceId])
: ("control/door", ["action": "open", "deviceId": deviceId])
case .startClimate(let options):
// EU CCS2 vehicles only accept temperatures on the
// 0.5°C grid (15.0–30.0). The car silently no-ops when
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,30 +199,30 @@ public final class HyundaiEuropeAPIClient: APIClientBase, APIClientProtocol {
return
}

let body = [
"deviceId": configuration.deviceId,
let body: [String: Any] = [
"deviceId": configuration.deviceId ?? "",
Comment on lines +202 to +203
"pin": pin
]

let headers = authorizedHeaders(authToken: authToken)

let bodyData = try? JSONSerialization.data(
withJSONObject: body, options: []
// Route through performJSONRequest so the PIN/control-token
// request is captured in the HTTP logs and its status is
// validated. Previously this used a raw URLSession call, so a
// failure here was invisible to diagnostics and surfaced only
// as a generic "Failed to get command token".
let (_, json, _) = try await performJSONRequest(
url: "\(baseURL)/api/v1/user/pin?token=",
method: .PUT,
headers: authorizedHeaders(authToken: authToken),
body: body,
requestType: .sendCommand
)

var request = URLRequest(url: URL(string: "\(baseURL)/api/v1/user/pin?token=")!)
request.httpMethod = "PUT"
request.httpBody = bodyData

for (key, value) in headers {
request.setValue(value, forHTTPHeaderField: key)
}

let (data, _) = try await urlSession.data(for: request)
guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let token = json["controlToken"] as? String,
guard let token = json["controlToken"] as? String,
let expires = json["expiresTime"] as? Int else {
throw APIError(message: "Failed to get command token", apiName: apiName)
throw APIError(
message: "PIN verification failed — check that the account PIN is correct.",
Comment on lines +222 to +223
apiName: apiName
)
}

commandToken = token
Expand Down Expand Up @@ -277,13 +277,24 @@ public final class HyundaiEuropeAPIClient: APIClientBase, APIClientProtocol {
// MARK: - Commands

public func sendCommand(for vehicle: Vehicle, command: VehicleCommand, authToken: AuthToken) async throws {
let (path, body) = commandPathAndBody(for: command)
let ccs2 = vehicle.marketOptions?.ccs2Supported ?? false
let (path, body) = commandPathAndBody(for: command, ccs2: ccs2)
let url =
"\(baseURL)/api/\(ccs2 ? "v2" : "v1")"
+ "/spa/vehicles/\(vehicle.regId)/\(path)"
try await setCommandToken(authToken: authToken)
let header = commandHeaders(authToken: authToken, ccs2: ccs2)

// CCS2 (Gen5W) cars authenticate commands with a PIN-derived
// control token; legacy cars use the normal access token and
// have no PIN step at all. Fetching a control token for a
// legacy car is what produced the "Failed to get command token"
// error — the PIN endpoint isn't part of the legacy flow.
let header: [String: String]
if ccs2 {
try await setCommandToken(authToken: authToken)
header = commandHeaders(authToken: authToken, ccs2: ccs2)
} else {
header = authorizedHeaders(authToken: authToken, ccs2: ccs2)
}

_ = try await performJSONRequest(
url: url,
Expand Down