From 0c9589d4053602ed618c0e72b21fd5ffab053634 Mon Sep 17 00:00:00 2001 From: Kelyan Pegeot Selme <75201282+kelyaenn@users.noreply.github.com> Date: Mon, 6 Jan 2025 08:31:08 +0100 Subject: [PATCH] Add new endpoint for tyre pressure (#1411) * feat: Type pressure * Lint * Fix tests * One more --------- Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> --- src/renault_api/cli/renault_vehicle.py | 30 ++++ src/renault_api/kamereon/__init__.py | 1 + src/renault_api/kamereon/models.py | 14 ++ src/renault_api/kamereon/schemas.py | 3 + src/renault_api/renault_vehicle.py | 12 ++ tests/cli/test_vehicle.py | 154 +++++++++++------- tests/fixtures.py | 13 ++ .../kamereon/vehicle_data/tyre-pressure.json | 16 ++ tests/kamereon/test_kamereon_vehicle_data.py | 20 +++ tests/test_renault_vehicle.py | 9 + 10 files changed, 209 insertions(+), 63 deletions(-) create mode 100644 tests/fixtures/kamereon/vehicle_data/tyre-pressure.json diff --git a/src/renault_api/cli/renault_vehicle.py b/src/renault_api/cli/renault_vehicle.py index cca64cf1..60baa23f 100644 --- a/src/renault_api/cli/renault_vehicle.py +++ b/src/renault_api/cli/renault_vehicle.py @@ -162,6 +162,7 @@ async def display_status( await update_lock_status(vehicle, status_table, ctx_data) await update_res_state(vehicle, status_table, ctx_data) await update_hvac_status(vehicle, status_table, ctx_data) + await update_tyre_pressure(vehicle, status_table, ctx_data) if ctx_data["json"]: click.echo(json.dumps(status_table)) return @@ -222,6 +223,35 @@ async def update_battery_status( update_status_table(status_table, key, value, unit) +async def update_tyre_pressure( + vehicle: RenaultVehicle, status_table: Dict[str, Any], ctx_data: Dict[str, Any] +) -> None: + """Update status table from get_tyre_pressure.""" + try: + if not await vehicle.supports_endpoint("pressure"): # pragma: no cover + return + response = await vehicle.get_tyre_pressure() + except QuotaLimitException as exc: # pragma: no cover + raise click.ClickException(repr(exc)) from exc + except KamereonResponseException as exc: # pragma: no cover + click.echo(f"pressure: {exc.error_details}", err=True) + return + + if ctx_data["json"]: + status_table["pressure"] = response.raw_data + return + + items = [ + ("Front left pressure", response.flPressure, "bar"), + ("Front right pressure", response.frPressure, "bar"), + ("Rear left pressure", response.rlPressure, "bar"), + ("Rear right pressure", response.rrPressure, "bar"), + ] + + for key, value, unit in items: + update_status_table(status_table, key, value, unit) + + async def update_charge_mode( vehicle: RenaultVehicle, status_table: Dict[str, Any], ctx_data: Dict[str, Any] ) -> None: diff --git a/src/renault_api/kamereon/__init__.py b/src/renault_api/kamereon/__init__.py index fd9da50e..801fb6b4 100644 --- a/src/renault_api/kamereon/__init__.py +++ b/src/renault_api/kamereon/__init__.py @@ -34,6 +34,7 @@ "location": {"version": 1}, "lock-status": {"version": 1}, "notification-settings": {"version": 1}, + "pressure": {"version": 1}, "res-state": {"version": 1}, } _KCA_POST_ENDPOINTS: Dict[str, Any] = { diff --git a/src/renault_api/kamereon/models.py b/src/renault_api/kamereon/models.py index ff5452ef..8647cc69 100644 --- a/src/renault_api/kamereon/models.py +++ b/src/renault_api/kamereon/models.py @@ -402,6 +402,20 @@ def get_charging_status(self) -> Optional[enums.ChargeState]: ) from err +@dataclass +class KamereonVehicleTyrePressureData(KamereonVehicleDataAttributes): + """Kamereon vehicle tyre-pressure data.""" + + flPressure: Optional[int] + frPressure: Optional[int] + rlPressure: Optional[int] + rrPressure: Optional[int] + flStatus: Optional[int] + frStatus: Optional[int] + rlStatus: Optional[int] + rrStatus: Optional[int] + + @dataclass class KamereonVehicleLocationData(KamereonVehicleDataAttributes): """Kamereon vehicle data location attributes.""" diff --git a/src/renault_api/kamereon/schemas.py b/src/renault_api/kamereon/schemas.py index e3030e1f..aca1bc0c 100644 --- a/src/renault_api/kamereon/schemas.py +++ b/src/renault_api/kamereon/schemas.py @@ -37,6 +37,9 @@ models.KamereonVehicleBatteryStatusData, base_schema=BaseSchema )() +KamereonVehicleTyrePressureDataSchema = marshmallow_dataclass.class_schema( + models.KamereonVehicleTyrePressureData, base_schema=BaseSchema +)() KamereonVehicleLocationDataSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleLocationData, base_schema=BaseSchema diff --git a/src/renault_api/renault_vehicle.py b/src/renault_api/renault_vehicle.py index 41dcf861..9eba02c2 100644 --- a/src/renault_api/renault_vehicle.py +++ b/src/renault_api/renault_vehicle.py @@ -136,6 +136,18 @@ async def get_battery_status(self) -> models.KamereonVehicleBatteryStatusData: response.get_attributes(schemas.KamereonVehicleBatteryStatusDataSchema), ) + async def get_tyre_pressure(self) -> models.KamereonVehicleTyrePressureData: + """Get vehicle tyre pressure.""" + response = await self.session.get_vehicle_data( + account_id=self.account_id, + vin=self.vin, + endpoint="pressure", + ) + return cast( + models.KamereonVehicleTyrePressureData, + response.get_attributes(schemas.KamereonVehicleTyrePressureDataSchema), + ) + async def get_location(self) -> models.KamereonVehicleLocationData: """Get vehicle location.""" response = await self.session.get_vehicle_data( diff --git a/tests/cli/test_vehicle.py b/tests/cli/test_vehicle.py index fdba1995..ba813bd3 100644 --- a/tests/cli/test_vehicle.py +++ b/tests/cli/test_vehicle.py @@ -35,54 +35,66 @@ EXPECTED_STATUS = { "captur_ii.1.json": ( - "----------------- ----------------------\n" - "Total mileage 5566.78 km\n" - "Fuel autonomy 35.0 km\n" - "Fuel quantity 3.0 L\n" - "GPS Latitude 48.1234567\n" - "GPS Longitude 11.1234567\n" - "GPS last updated 2020-02-18 17:58:38\n" - "Lock status locked\n" - "Lock last updated 2022-02-02 14:51:13\n" - "Engine state Stopped, ready for RES\n" - "----------------- ----------------------\n" + "-------------------- ----------------------\n" + "Total mileage 5566.78 km\n" + "Fuel autonomy 35.0 km\n" + "Fuel quantity 3.0 L\n" + "GPS Latitude 48.1234567\n" + "GPS Longitude 11.1234567\n" + "GPS last updated 2020-02-18 17:58:38\n" + "Lock status locked\n" + "Lock last updated 2022-02-02 14:51:13\n" + "Engine state Stopped, ready for RES\n" + "Front left pressure 2460 bar\n" + "Front right pressure 2730 bar\n" + "Rear left pressure 2790 bar\n" + "Rear right pressure 2790 bar\n" + "-------------------- ----------------------\n" ), "captur_ii.2.json": ( - "----------------- -------------------------\n" - "Battery level 50 %\n" - "Last updated 2020-11-17 09:06:48\n" - "Range estimate 128 km\n" - "Plug state PlugState.UNPLUGGED\n" - "Charging state ChargeState.NOT_IN_CHARGE\n" - "Charge mode always\n" - "Total mileage 5566.78 km\n" - "Fuel autonomy 35.0 km\n" - "Fuel quantity 3.0 L\n" - "GPS Latitude 48.1234567\n" - "GPS Longitude 11.1234567\n" - "GPS last updated 2020-02-18 17:58:38\n" - "Lock status locked\n" - "Lock last updated 2022-02-02 14:51:13\n" - "Engine state Stopped, ready for RES\n" - "----------------- -------------------------\n" + "-------------------- -------------------------\n" + "Battery level 50 %\n" + "Last updated 2020-11-17 09:06:48\n" + "Range estimate 128 km\n" + "Plug state PlugState.UNPLUGGED\n" + "Charging state ChargeState.NOT_IN_CHARGE\n" + "Charge mode always\n" + "Total mileage 5566.78 km\n" + "Fuel autonomy 35.0 km\n" + "Fuel quantity 3.0 L\n" + "GPS Latitude 48.1234567\n" + "GPS Longitude 11.1234567\n" + "GPS last updated 2020-02-18 17:58:38\n" + "Lock status locked\n" + "Lock last updated 2022-02-02 14:51:13\n" + "Engine state Stopped, ready for RES\n" + "Front left pressure 2460 bar\n" + "Front right pressure 2730 bar\n" + "Rear left pressure 2790 bar\n" + "Rear right pressure 2790 bar\n" + "-------------------- -------------------------\n" ), "twingo_ze.1.json": ( - "----------------- -------------------------\n" - "Battery level 50 %\n" - "Last updated 2020-11-17 09:06:48\n" - "Range estimate 128 km\n" - "Plug state PlugState.UNPLUGGED\n" - "Charging state ChargeState.NOT_IN_CHARGE\n" - "Charge mode always\n" - "Total mileage 49114.27 km\n" - "GPS Latitude 48.1234567\n" - "GPS Longitude 11.1234567\n" - "GPS last updated 2020-02-18 17:58:38\n" - "Lock status locked\n" - "Lock last updated 2022-02-02 14:51:13\n" - "Engine state Stopped, ready for RES\n" - "HVAC status on\n" - "----------------- -------------------------\n" + "-------------------- -------------------------\n" + "Battery level 50 %\n" + "Last updated 2020-11-17 09:06:48\n" + "Range estimate 128 km\n" + "Plug state PlugState.UNPLUGGED\n" + "Charging state ChargeState.NOT_IN_CHARGE\n" + "Charge mode always\n" + "Total mileage 49114.27 km\n" + "GPS Latitude 48.1234567\n" + "GPS Longitude 11.1234567\n" + "GPS last updated 2020-02-18 17:58:38\n" + "Lock status locked\n" + "Lock last updated 2022-02-02 14:51:13\n" + "Engine state Stopped, ready for RES\n" + "HVAC status on\n" + "Front left pressure 2460 bar\n" + "Front right pressure 2730 bar\n" + "Rear left pressure 2790 bar\n" + "Rear right pressure 2790 bar\n" + "-------------------- -------------------------\n" ), "zoe_40.1.json": ( "-------------------- -------------------------\n" @@ -96,6 +108,10 @@ "Engine state Stopped, ready for RES\n" "HVAC status off\n" "External temperature 8.0 °C\n" + "Front left pressure 2460 bar\n" + "Front right pressure 2730 bar\n" + "Rear left pressure 2790 bar\n" + "Rear right pressure 2790 bar\n" "-------------------- -------------------------\n" ), "zoe_40.2.json": ( @@ -110,27 +126,35 @@ "Engine state Stopped, ready for RES\n" "HVAC status off\n" "External temperature 8.0 °C\n" + "Front left pressure 2460 bar\n" + "Front right pressure 2730 bar\n" + "Rear left pressure 2790 bar\n" + "Rear right pressure 2790 bar\n" "-------------------- -------------------------\n" ), "zoe_50.1.json": ( - "----------------- -------------------------\n" - "Battery level 50 %\n" - "Last updated 2020-11-17 09:06:48\n" - "Range estimate 128 km\n" - "Plug state PlugState.UNPLUGGED\n" - "Charging state ChargeState.NOT_IN_CHARGE\n" - "Charge mode always\n" - "Total mileage 5785.75 km\n" - "Fuel autonomy 0.0 km\n" - "Fuel quantity 0.0 L\n" - "GPS Latitude 48.1234567\n" - "GPS Longitude 11.1234567\n" - "GPS last updated 2020-02-18 17:58:38\n" - "Lock status locked\n" - "Lock last updated 2022-02-02 14:51:13\n" - "Engine state Stopped, ready for RES\n" - "HVAC status on\n" - "----------------- -------------------------\n" + "-------------------- -------------------------\n" + "Battery level 50 %\n" + "Last updated 2020-11-17 09:06:48\n" + "Range estimate 128 km\n" + "Plug state PlugState.UNPLUGGED\n" + "Charging state ChargeState.NOT_IN_CHARGE\n" + "Charge mode always\n" + "Total mileage 5785.75 km\n" + "Fuel autonomy 0.0 km\n" + "Fuel quantity 0.0 L\n" + "GPS Latitude 48.1234567\n" + "GPS Longitude 11.1234567\n" + "GPS last updated 2020-02-18 17:58:38\n" + "Lock status locked\n" + "Lock last updated 2022-02-02 14:51:13\n" + "Engine state Stopped, ready for RES\n" + "HVAC status on\n" + "Front left pressure 2460 bar\n" + "Front right pressure 2730 bar\n" + "Rear left pressure 2790 bar\n" + "Rear right pressure 2790 bar\n" + "-------------------- -------------------------\n" ), "zoe_40.1_json.json": ( '{"battery-status": {"timestamp": "2020-11-17T09:06:48+01:00", ' @@ -139,7 +163,11 @@ '"charge-mode": {"chargeMode": "always"}, ' '"cockpit": {"totalMileage": 49114.27}, ' '"res-state": {"details": "Stopped, ready for RES", "code": "10"}, ' - '"hvac-status": {"externalTemperature": 8.0, "hvacStatus": "off"}}\n' + '"hvac-status": {"externalTemperature": 8.0, "hvacStatus": "off"}, ' + '"pressure": {"flPressure": 2460, "frPressure": 2730, ' + '"rlPressure": 2790, "rrPressure": 2790, ' + '"flStatus": 0, "frStatus": 0, ' + '"rlStatus": 0, "rrStatus": 0}}\n' ), } diff --git a/tests/fixtures.py b/tests/fixtures.py index b6f6d90b..85d7869e 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -277,6 +277,18 @@ def inject_get_battery_status( ) +def inject_get_tyre_pressure( + mocked_responses: aioresponses, filename: str = "vehicle_data/tyre-pressure.json" +) -> str: + """Inject sample tyre-pressure.""" + urlpath = f"{ADAPTER_PATH}/pressure?{DEFAULT_QUERY_STRING}" + return inject_data( + mocked_responses, + urlpath, + filename, + ) + + def inject_get_location(mocked_responses: aioresponses) -> str: """Inject sample location.""" urlpath = f"{ADAPTER_PATH}/location?{DEFAULT_QUERY_STRING}" @@ -501,6 +513,7 @@ def inject_vehicle_status(mocked_responses: aioresponses, vehicle: str) -> None: inject_get_hvac_status(mocked_responses, vehicle) inject_get_charge_mode(mocked_responses) inject_get_cockpit(mocked_responses, vehicle) + inject_get_tyre_pressure(mocked_responses) def ensure_redacted(data: Mapping[str, Any], to_redact: list[str] = TO_REDACT) -> None: diff --git a/tests/fixtures/kamereon/vehicle_data/tyre-pressure.json b/tests/fixtures/kamereon/vehicle_data/tyre-pressure.json new file mode 100644 index 00000000..0dd7462d --- /dev/null +++ b/tests/fixtures/kamereon/vehicle_data/tyre-pressure.json @@ -0,0 +1,16 @@ +{ + "data": { + "type": "Car", + "id": "VF1AAAAA555777999", + "attributes": { + "flPressure": 2460, + "frPressure": 2730, + "rlPressure": 2790, + "rrPressure": 2790, + "flStatus": 0, + "frStatus": 0, + "rlStatus": 0, + "rrStatus": 0 + } + } +} diff --git a/tests/kamereon/test_kamereon_vehicle_data.py b/tests/kamereon/test_kamereon_vehicle_data.py index 94bb08b0..771635ea 100644 --- a/tests/kamereon/test_kamereon_vehicle_data.py +++ b/tests/kamereon/test_kamereon_vehicle_data.py @@ -105,6 +105,26 @@ def test_battery_status_2() -> None: assert vehicle_data.get_charging_status() == enums.ChargeState.CHARGE_IN_PROGRESS +def test_tyre_pressure() -> None: + """Test vehicle data for tyre-pressure.json.""" + response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( + f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_data/tyre-pressure.json", + schemas.KamereonVehicleDataResponseSchema, + ) + response.raise_for_error_code() + assert response.data is not None + assert response.data.raw_data["attributes"] == { + "flPressure": 2460, + "frPressure": 2730, + "rlPressure": 2790, + "rrPressure": 2790, + "flStatus": 0, + "frStatus": 0, + "rlStatus": 0, + "rrStatus": 0, + } + + def test_cockpit_zoe() -> None: """Test vehicle data for cockpit.zoe.json.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( diff --git a/tests/test_renault_vehicle.py b/tests/test_renault_vehicle.py index 92ce8447..def074d5 100644 --- a/tests/test_renault_vehicle.py +++ b/tests/test_renault_vehicle.py @@ -119,6 +119,15 @@ async def test_get_battery_status( assert await vehicle.get_battery_status() +@pytest.mark.asyncio +async def test_get_tyre_pressure( + vehicle: RenaultVehicle, mocked_responses: aioresponses +) -> None: + """Test get_tyre_pressure.""" + fixtures.inject_get_tyre_pressure(mocked_responses) + assert await vehicle.get_tyre_pressure() + + @pytest.mark.asyncio async def test_get_location( vehicle: RenaultVehicle, mocked_responses: aioresponses