Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.12", "3.13"]
python-version: ["3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
Expand Down
6 changes: 4 additions & 2 deletions pyporscheconnectapi/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
AUDIENCE = "https://api.porsche.com"
CLIENT_ID = "XhygisuebbrqQ80byOuU5VncxLIm8E6H"
X_CLIENT_ID = "41843fb4-691d-4970-85c7-2673e8ecef40"
USER_AGENT = "pyporscheconnectapi/0.5.0"
# The Porsche app user agent: the API only wakes the car up for
# currentoverview requests that carry it.
USER_AGENT = "de.porsche.one/20.26.36-row+186611 (android)"
API_BASE_URL = "https://api.ppa.porsche.com/app"
AUTHORIZATION_URL = f"https://{AUTHORIZATION_SERVER}/authorize"
TOKEN_URL = f"https://{AUTHORIZATION_SERVER}/oauth/token"
Expand Down Expand Up @@ -54,7 +56,6 @@
"CHARGING_RATE",
"CHARGING_SETTINGS",
"CHARGING_SUMMARY",
"CLIMATIZER_STATE",
"DEPARTURES",
"E_RANGE",
"FUEL_LEVEL",
Expand All @@ -63,6 +64,7 @@
"GPS_LOCATION",
"HEATING_STATE",
"HVAC_STATE",
"HVAC_SUMMARY",
"INTERMEDIATE_SERVICE_RANGE",
"INTERMEDIATE_SERVICE_TIME",
"LOCK_STATE_VEHICLE",
Expand Down
1 change: 1 addition & 0 deletions pyporscheconnectapi/remote_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ async def climatise_on(
payload = {
"key": "REMOTE_CLIMATIZER_START",
"payload": {
"spin": None,
"climateZonesEnabled": {
"frontLeft": front_left,
"frontRight": front_right,
Expand Down
4 changes: 2 additions & 2 deletions pyporscheconnectapi/retry.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ async def send_with_retries(
msg = f"transport error on {description}: {exc}"
raise PorscheExceptionError(msg) from exc
delay = compute_retry_delay(None, attempt)
_LOGGER.warning(
_LOGGER.debug(
"Transient transport error on %s (%s) - retrying in %.1fs (attempt %d/%d)",
description,
exc.__class__.__name__,
Expand All @@ -84,7 +84,7 @@ async def send_with_retries(
if response.status_code not in RETRY_STATUS_CODES or attempt == max_retries:
return response
delay = compute_retry_delay(response, attempt)
_LOGGER.warning(
_LOGGER.debug(
"Transient HTTP %s on %s - retrying in %.1fs (attempt %d/%d)",
response.status_code,
description,
Expand Down
12 changes: 9 additions & 3 deletions pyporscheconnectapi/vehicle.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def has_ice_drivetrain(self) -> bool:
@property
def has_remote_climatisation(self) -> bool:
"""Return True if vehicle has remote climatisation ability."""
return self.data.__contains__("CLIMATIZER_STATE")
return isinstance(self.data.get("HVAC_SUMMARY"), dict)

@property
def has_direct_charge(self) -> bool:
Expand All @@ -137,9 +137,15 @@ def privacy_mode(self) -> bool | None:
@property
def remote_climatise_on(self) -> bool | None:
"""Return True if remote climatisation is on."""
_LOGGER.debug("Remote climatisation is: %s", self.data.get("CLIMATIZER_STATE", {}).get("isOn"))
status = (self.data.get("HVAC_SUMMARY") or {}).get("status")
_LOGGER.debug("Remote climatisation status is: %s", status)
return status is not None and status != "OFF"

return self.data.get("CLIMATIZER_STATE", {}).get("isOn")
@property
def seat_heating(self) -> dict | None:
"""Return the climate zones the vehicle reports, with their state."""
zones = (self.data.get("HVAC_SUMMARY") or {}).get("climateZonesEnabled")
return zones if isinstance(zones, dict) else None

@property
def vehicle_locked(self) -> bool:
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

setup(
name="pyporscheconnectapi-bartolije",
version="0.5.0",
version="0.5.1",
author="Johan Isaksson",
author_email="johan@generatorhallen.se",
description="Python library and CLI for communicating with Porsche Connect API.",
Expand Down
9 changes: 7 additions & 2 deletions tests/fixtures/vehicle_overview.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@
"value": {"percent": 73}
},
{
"key": "CLIMATIZER_STATE",
"key": "HVAC_SUMMARY",
"status": {"isEnabled": true},
"value": {"isOn": false}
"value": {
"type": "CLIMATIZER",
"status": "OFF",
"targetTemperature": 293.15,
"climateZonesEnabled": {"frontLeft": false, "frontRight": false}
}
},
{
"key": "REMOTE_ACCESS_AUTHORIZATION",
Expand Down
2 changes: 1 addition & 1 deletion tests/fixtures/vehicle_overview_combustion.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
{"key": "E_RANGE", "status": {"isEnabled": false, "cause": "NOT_SUPPORTED"}},
{"key": "BATTERY_LEVEL", "status": {"isEnabled": false, "cause": "NOT_SUPPORTED"}},
{"key": "CHARGING_SUMMARY", "status": {"isEnabled": false, "cause": "NOT_SUPPORTED"}},
{"key": "CLIMATIZER_STATE", "status": {"isEnabled": false, "cause": "NOT_SUPPORTED"}},
{"key": "HVAC_SUMMARY", "status": {"isEnabled": false, "cause": "NOT_SUPPORTED"}},
{
"key": "GLOBAL_PRIVACY_MODE",
"status": {"isEnabled": true},
Expand Down
1 change: 1 addition & 0 deletions tests/test_remote_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ async def test_climatise_on_posts_remote_climatizer_start(
body = json.loads(route.calls.last.request.content)
assert body["key"] == "REMOTE_CLIMATIZER_START"
assert body["payload"]["targetTemperature"] == 294.15
assert body["payload"]["spin"] is None
assert body["payload"]["climateZonesEnabled"] == {
"frontLeft": False, "frontRight": False,
"rearLeft": False, "rearRight": False,
Expand Down
23 changes: 20 additions & 3 deletions tests/test_vehicle.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ async def test_get_stored_overview_populates_vehicle_data(

# Measurements flattened into vehicle.data by key.
assert vehicle.data["BATTERY_LEVEL"] == {"percent": 73}
assert vehicle.data["CLIMATIZER_STATE"] == {"isOn": False}
assert vehicle.data["HVAC_SUMMARY"]["status"] == "OFF"
assert vehicle.data["LOCK_STATE_VEHICLE"] == {"isLocked": True}


Expand Down Expand Up @@ -127,15 +127,32 @@ def test_drivetrain_capabilities(


def test_has_remote_climatisation(authed_connection: Connection):
"""has_remote_climatisation flips on the presence of the CLIMATIZER_STATE key."""
"""Climatisation is read from HVAC_SUMMARY (CLIMATIZER_STATE no longer reports it)."""
no_climate = PorscheVehicle(connection=authed_connection, data={})
assert no_climate.has_remote_climatisation is False
assert no_climate.remote_climatise_on is False
assert no_climate.seat_heating is None

legacy = PorscheVehicle(
connection=authed_connection,
data={"CLIMATIZER_STATE": {"isOn": True}},
)
assert legacy.has_remote_climatisation is False

zones = {"frontLeft": True, "frontRight": False}
with_climate = PorscheVehicle(
connection=authed_connection,
data={"CLIMATIZER_STATE": {"isOn": False}},
data={"HVAC_SUMMARY": {"status": "OFF", "climateZonesEnabled": zones}},
)
assert with_climate.has_remote_climatisation is True
assert with_climate.remote_climatise_on is False
assert with_climate.seat_heating == zones

running = PorscheVehicle(
connection=authed_connection,
data={"HVAC_SUMMARY": {"status": "ON"}},
)
assert running.remote_climatise_on is True


def test_has_remote_services(authed_connection: Connection):
Expand Down
Loading