Skip to content

Support Porsche mobile auth flow and Macan EV data - #84

Closed
tietjen wants to merge 1 commit into
CJNE:mainfrom
tietjen:codex/mobile-app-auth-flow
Closed

Support Porsche mobile auth flow and Macan EV data#84
tietjen wants to merge 1 commit into
CJNE:mainfrom
tietjen:codex/mobile-app-auth-flow

Conversation

@tietjen

@tietjen tietjen commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

Summary

This updates the library to work with Porsche's current mobile auth flow and restores live vehicle data for newer vehicles such as the Macan EV.

The previous implementation relied on the older native app auth assumptions and older vehicle status endpoints. Porsche now uses a different mobile client flow and current vehicle data is available through app/connect/v1/vehicles/....

What changed

Auth flow

  • switch constants to the current Porsche mobile client configuration
  • add PKCE verifier/challenge handling
  • persist cookies and code_verifier across captcha/login continuation
  • improve captcha extraction for the current Porsche/Auth0 login page
  • handle resume/redirect flow more robustly
  • support passkey enrollment skip during login continuation
  • improve token exchange with code_verifier

Connection handling

  • add support for absolute requests
  • add portal/DCGW request support
  • improve request headers to match Porsche portal/app expectations
  • expose better error details including response body and request URL

Vehicle/account data

  • normalize newer portal/mobile vehicle payloads into the legacy library shape
  • use app/connect/v1/vehicles/... for current overview data
  • support current charging/SoC/range style payloads for newer EVs
  • normalize pairing/connectivity/permissions style nodes for newer vehicles
  • avoid legacy picture endpoint usage where it is no longer relevant

Tested

Tested against a real Porsche account and Macan EV payloads:

  • login including captcha
  • token exchange
  • current overview retrieval
  • live values for:
    • state of charge
    • charging status
    • charging power
    • electric range
    • charging target

Notes

This PR intentionally focuses on the underlying library. Home Assistant integration changes should be handled separately once the updated library is available.

@CJNE

CJNE commented Mar 15, 2026

Copy link
Copy Markdown
Owner

Thank you for this PR, it looks promising!
I tried it and it was not working with older vehicles, for example battery reported 0 for my Taycan 2021.
So we need to figure out a way to make it work with new and old output before we can merge.

Comment on lines +13 to +40
def _normalize_engine(vehicle: dict) -> str:
"""Best-effort mapping of the portal vehicle payload to drivetrain type."""
model_type = vehicle.get("modelType", {})
if model_type.get("engine"):
return model_type["engine"]
description = str(vehicle.get("modelDescription", "")).lower()
if description in {"macan", "taycan"}:
return "BEV"
return "COMBUSTION"


def _normalize_vehicle(vehicle: dict) -> dict:
"""Normalize the portal vehicle payload to the legacy library shape."""
model_name = vehicle.get("modelDescription") or vehicle.get("modelName") or vehicle.get("vin", "Porsche")
return {
"vin": vehicle["vin"],
"name": model_name,
"modelName": model_name,
"modelType": {
"year": vehicle.get("modelYear") or vehicle.get("modelType", {}).get("year", "not available"),
"engine": _normalize_engine(vehicle),
},
"systemInfo": vehicle.get("systemInfo", {}),
"timestamp": vehicle.get("validFrom") or vehicle.get("timestamp"),
"portalVehicle": vehicle,
}


Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why this is needed, should be removed?

v = PorscheVehicle(
vin=vehicle["vin"],
data=vehicle,
data=_normalize_vehicle(vehicle),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See previous comment.

Comment on lines +22 to +45
def _normalize_engine(vehicle: dict) -> str:
"""Best-effort mapping of the portal vehicle payload to drivetrain type."""
description = str(vehicle.get("modelDescription", "")).lower()
if description in {"macan", "taycan"}:
return "BEV"
return "COMBUSTION"


def _normalize_portal_vehicle(vehicle: dict) -> dict:
"""Normalize the portal vehicle payload to the legacy library shape."""
model_name = vehicle.get("modelDescription") or vehicle.get("modelName") or vehicle.get("vin", "Porsche")
return {
"vin": vehicle["vin"],
"name": model_name,
"modelName": model_name,
"modelType": {
"year": vehicle.get("modelYear", "not available"),
"engine": _normalize_engine(vehicle),
},
"systemInfo": {},
"timestamp": vehicle.get("validFrom"),
"portalVehicle": vehicle,
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue, this is not needed (and does not work to determine the type of drivetrain anyway).

Comment on lines +285 to +290
overview = await self.connection.get(f"/connect/v1/vehicles/{self.vin}")
self.status = {
"appVehicle": overview,
}
normalized = _normalize_portal_vehicle(overview)
self.data = self.data | normalized

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change breaks the entire library, because no data will come out of it in the structure required for the remaining functions to work. Please provide an explaination of why this change is needed?

"Could not get capabilities, error communicating with API: %s",
err.message,
)
_LOGGER.debug("Skipping picture lookup for vehicle %s; legacy picture endpoint is no longer used", self.vin)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems this endpoint is still providing the URLs, so why remove it? Unless there is a better place to retrieve them from?

@fredriklj

fredriklj commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

It looks to me like the auth flow is working, but there are some mistakes made in vehicle.py that prevents the library from working as intended. And I don't understand the reasons for those changes.

Also, @tietjen, I am curious about what was actually not working for you with your Macan? I was under the impression the library mostly worked fine with the newer Macan as well? But I notice you have tested this to get "live" data, is that through polling current overview regularly, or through some other interface?

@fredriklj

Copy link
Copy Markdown
Collaborator

I noted now that the captcha flow isn't working.. PorscheCaptchaRequiredError need to carry the code_verifier, and it must be resubmitted through cli.py. Not too hard to fix, but let's decide first if we want to move to this authorization flow.

@fredriklj
fredriklj requested a review from CJNE August 29, 2026 13:33
Comment on lines +93 to +104
async def portal_get(self, url, params=None):
"""Make a GET request to the Porsche DCGW portal API."""
return await self.absolute_request("GET", f"{DCGW_BASE_URL}{url}", params=params)

async def get_portal_config(self):
"""Fetch and cache portal localization/config data."""
config = await self.portal_get(f"/core/config/v1/{self.country_code}/{self.country_code}/")
localization = config.get("localization", {})
self.country_code = str(localization.get("countryCode", self.country_code)).lower()
self.language_code = localization.get("languageCode", self.language_code)
return config

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this used for?

@fredriklj

Copy link
Copy Markdown
Collaborator

Core functionality of this PR has been refactored into #96. Closing this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants