diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 05d42f8..e1f7df9 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -81,6 +81,37 @@ jobs:
pip install -r requirements.txt
pip install -r requirements.test.txt
+ translations:
+ name: Check translations
+ runs-on: ubuntu-latest
+ needs:
+ - info
+ - prepare
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v4.2.2
+
+ - name: Set up Python ${{ env.DEFAULT_PYTHON }}
+ id: python
+ uses: actions/setup-python@v5.3.0
+ with:
+ python-version: ${{ env.DEFAULT_PYTHON }}
+
+ - name: Restore Python ${{ env.DEFAULT_PYTHON }} virtual environment
+ id: cache-venv
+ uses: actions/cache/restore@v4.2.0
+ with:
+ path: venv
+ fail-on-cache-miss: true
+ key: >-
+ ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.info.outputs.python_cache_key }}
+
+ - name: Run translations check
+ run: |
+ . venv/bin/activate
+ python --version
+ python scripts/check_translations.py --ignore-errors
+
mypy:
name: Check mypy
runs-on: ubuntu-latest
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index e69de29..ad7214b 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,40 @@
+name: Publish
+
+on:
+ release:
+ types:
+ - published
+ workflow_dispatch:
+
+jobs:
+ release:
+ name: Publish zip file
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ env:
+ INTEGRATION_PATH: ${{ github.workspace }}/custom_components/volvo_cars
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v4.2.2
+
+ - name: Set version in manifest.json
+ run: |
+ # Extract version from tag (e.g., "refs/tags/v1.2.3" becomes "1.2.3")
+ version="${GITHUB_REF#refs/tags/v}"
+ echo "Setting version to ${version}"
+ # Update the "version" key in manifest.json (assumes a simple "version": "..." line)
+ sed -i -E "s/\"version\": \"[^\"]+\"/\"version\": \"${version}\"/" ${INTEGRATION_PATH}/manifest.json
+
+ - name: Zip
+ run: |
+ cd $INTEGRATION_PATH
+ zip volvo_cars.zip -r ./
+ pwd
+ ls -l *.zip
+
+ - name: Include zip in release
+ uses: softprops/action-gh-release@v2.2.1
+ with:
+ files: ${{ env.INTEGRATION_PATH }}/volvo_cars.zip
+ fail_on_unmatched_files: true
diff --git a/README.md b/README.md
index 11fd340..aaf4fd1 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,7 @@ Supported regions include: Europe, the Middle East, and Africa. For a complete l
Now check out the features section for details on what you'll get!
[![GitHub release][releases-shield]][releases]
+[![Github downloads][downloads-shield]][releases]
[![CI main][ci-main-shield]][ci-workflow]
[![CI dev][ci-dev-shield]][ci-workflow]
[![HACS][hacs-shield]][hacs]
@@ -21,7 +22,7 @@ Now check out the features section for details on what you'll get!
- Multiple cars
- Multiple accounts
-- Multiple languages: Dutch (nl), English (en), German (de), Norwegian (no), Polish (pl), Portuguese (pt & pt-BR), Swedish (sv)
+- Multiple languages: Dutch (nl), English (en), French (fr), German (de), Norwegian Bokmål (nb), Polish (pl), Portuguese Brazil (pt-BR), Portuguese Portugal (pt), Swedish (sv)
@@ -165,7 +166,8 @@ Once a car has been added, you can configure additional options for it.
| ----------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------- |
| Volvo API key | The generated API key in the developer account. | |
| Device tracker picture | The picture that will be shown on the map. | |
-| Fuel consumption unit | You can choose between `l/100km`, `mpg (UK)` and `mpg (US)`. | Cars with a combustion engine. |
+| Energy consumption unit | You can choose between `kWh/100 km` and `mi/kWh`. | Cars with a battery engine. |
+| Fuel consumption unit | You can choose between `l/100 km`, `mpg (UK)` and `mpg (US)` | Cars with a combustion engine. |
| Images transparent background | Whether or not you want transparent a background for the exterior images. | Depending on the image URL provided by the Volvo API. |
| Images background color | Choose the background color for the exterior images. | Depending on the image URL provided by the Volvo API. |
@@ -214,6 +216,7 @@ When I'm coding, I run on coffee, Coca-Cola*, and Lays* potato chips. If you'd l
\* No affiliation with these brands — just personal favorites!
[releases-shield]: https://img.shields.io/github/v/release/thomasddn/ha-volvo-cars?style=flat-square
+[downloads-shield]: https://img.shields.io/github/downloads/thomasddn/ha-volvo-cars/total?style=flat-square
[releases]: https://github.com/thomasddn/ha-volvo-cars/releases
[ci-dev-shield]: https://img.shields.io/github/actions/workflow/status/thomasddn/ha-volvo-cars/ci.yml?branch=develop&style=flat-square&label=develop
[ci-main-shield]: https://img.shields.io/github/actions/workflow/status/thomasddn/ha-volvo-cars/ci.yml?branch=main&style=flat-square&label=main
diff --git a/custom_components/volvo_cars/config_flow.py b/custom_components/volvo_cars/config_flow.py
index bed8052..c1e25d0 100644
--- a/custom_components/volvo_cars/config_flow.py
+++ b/custom_components/volvo_cars/config_flow.py
@@ -4,6 +4,7 @@
from collections.abc import Mapping
import logging
+import re
from typing import TYPE_CHECKING, Any, Self
from urllib import parse
@@ -27,6 +28,7 @@
SelectSelector,
SelectSelectorConfig,
)
+from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM
from .const import (
CONF_OTP,
@@ -35,22 +37,26 @@
DOMAIN,
MANUFACTURER,
OPT_DEVICE_TRACKER_PICTURE,
+ OPT_ENERGY_CONSUMPTION_UNIT,
OPT_FUEL_CONSUMPTION_UNIT,
OPT_IMG_BG_COLOR,
OPT_IMG_TRANSPARENT,
+ OPT_UNIT_ENERGY_KWH_PER_100KM,
+ OPT_UNIT_ENERGY_MILES_PER_KWH,
OPT_UNIT_LITER_PER_100KM,
OPT_UNIT_MPG_UK,
OPT_UNIT_MPG_US,
)
-from .coordinator import VolvoCarsConfigEntry, VolvoCarsData
+from .coordinator import VolvoCarsData
from .factory import async_create_auth_api
from .store import VolvoCarsStoreManager
from .volvo.models import AuthorizationModel, VolvoAuthException
_LOGGER = logging.getLogger(__name__)
+_VIN_REGEX = re.compile(r"[A-Z0-9]{17}")
-def get_setting(entry: VolvoCarsConfigEntry, key: str) -> Any:
+def get_setting(entry: ConfigEntry, key: str) -> Any:
"""Get setting from options with a fallback to config."""
if key in entry.options:
return entry.options[key]
@@ -91,21 +97,31 @@ async def async_step_user(
errors: dict[str, str] = {}
if user_input is not None:
- flow = await self._async_authenticate(
- user_input[CONF_VIN], user_input, errors
- )
+ vin = str(user_input[CONF_VIN]).strip().upper()
+
+ if _VIN_REGEX.fullmatch(vin) is None:
+ errors[CONF_VIN] = "invalid_vin"
+ else:
+ flow = await self._async_authenticate(vin, user_input, errors)
- if flow is not None:
- return flow
+ if flow is not None:
+ return flow
+ user_input = user_input or {}
schema = vol.Schema(
{
- vol.Required(CONF_USERNAME, default=self._username or ""): str,
- vol.Required(CONF_PASSWORD, default=self._password or ""): str,
- vol.Required(CONF_VIN, default=self._vin or ""): str,
- vol.Required(CONF_VCC_API_KEY, default=self._api_key or ""): str,
+ vol.Required(
+ CONF_USERNAME, default=user_input.get(CONF_USERNAME, "")
+ ): str,
+ vol.Required(
+ CONF_PASSWORD, default=user_input.get(CONF_PASSWORD, "")
+ ): str,
+ vol.Required(CONF_VIN, default=user_input.get(CONF_VIN, "")): str,
+ vol.Required(
+ CONF_VCC_API_KEY, default=user_input.get(CONF_VCC_API_KEY, "")
+ ): str,
vol.Optional(
- CONF_FRIENDLY_NAME, default=self._friendly_name or ""
+ CONF_FRIENDLY_NAME, default=user_input.get(CONF_FRIENDLY_NAME, "")
): str,
},
)
@@ -149,12 +165,15 @@ async def async_step_reauth_confirm(
reauth_entry = self._get_reauth_entry()
if user_input is not None:
- flow = await self._async_authenticate(
- reauth_entry.data[CONF_VIN], user_input, errors
- )
+ vin = str(reauth_entry.data[CONF_VIN]).strip().upper()
- if flow is not None:
- return flow
+ if _VIN_REGEX.fullmatch(vin) is None:
+ errors[CONF_VIN] = "invalid_vin"
+ else:
+ flow = await self._async_authenticate(vin, user_input, errors)
+
+ if flow is not None:
+ return flow
schema = vol.Schema(
{
@@ -163,7 +182,8 @@ async def async_step_reauth_confirm(
): str,
vol.Required(CONF_PASSWORD, default=""): str,
vol.Required(
- CONF_VCC_API_KEY, default=reauth_entry.data.get(CONF_VCC_API_KEY)
+ CONF_VCC_API_KEY,
+ default=get_setting(reauth_entry, CONF_VCC_API_KEY),
): str,
},
)
@@ -223,13 +243,6 @@ async def _async_authenticate(
return None
async def _async_create_or_update_entry(self) -> ConfigFlowResult:
- data = {
- CONF_USERNAME: self._username,
- CONF_VIN: self._vin,
- CONF_VCC_API_KEY: self._api_key,
- CONF_FRIENDLY_NAME: self._friendly_name,
- }
-
if self._auth_result and self._auth_result.token:
if self.unique_id is None:
raise ConfigEntryError("Config entry has no unique_id")
@@ -241,17 +254,56 @@ async def _async_create_or_update_entry(self) -> ConfigFlowResult:
refresh_token=self._auth_result.token.refresh_token,
)
+ data = {
+ CONF_USERNAME: self._username,
+ CONF_VIN: self._vin,
+ CONF_VCC_API_KEY: self._api_key,
+ CONF_FRIENDLY_NAME: self._friendly_name,
+ }
+
if self.source == SOURCE_REAUTH:
+ # Keep API key in sync with options
+ reauth_entry = self._get_reauth_entry()
+ options = dict(reauth_entry.options) if reauth_entry else {}
+ options.update({CONF_VCC_API_KEY: self._api_key})
+
return self.async_update_reload_and_abort(
- self._get_reauth_entry(),
- data_updates=data,
+ self._get_reauth_entry(), data_updates=data, options=options
+ )
+
+ def _default_energy_unit() -> str:
+ return (
+ OPT_UNIT_ENERGY_MILES_PER_KWH
+ if (
+ self.hass.config.units == US_CUSTOMARY_SYSTEM
+ or self.hass.config.country in ("UK", "US")
+ )
+ else OPT_UNIT_ENERGY_KWH_PER_100KM
)
+ def _default_fuel_unit() -> str:
+ if self.hass.config.country == "UK":
+ return OPT_UNIT_MPG_UK
+
+ if (
+ self.hass.config.units == US_CUSTOMARY_SYSTEM
+ or self.hass.config.country == "US"
+ ):
+ return OPT_UNIT_MPG_US
+
+ return OPT_UNIT_LITER_PER_100KM
+
_LOGGER.debug("Creating entry")
return self.async_create_entry(
title=f"{MANUFACTURER} {self._vin}",
data=data,
- options={OPT_FUEL_CONSUMPTION_UNIT: OPT_UNIT_LITER_PER_100KM},
+ options={
+ CONF_VCC_API_KEY: self._api_key,
+ OPT_ENERGY_CONSUMPTION_UNIT: _default_energy_unit(),
+ OPT_FUEL_CONSUMPTION_UNIT: _default_fuel_unit(),
+ OPT_IMG_BG_COLOR: [0, 0, 0],
+ OPT_IMG_TRANSPARENT: True,
+ },
)
@@ -297,33 +349,61 @@ async def async_step_init(
OPT_DEVICE_TRACKER_PICTURE,
default=get_setting(
self.config_entry, OPT_DEVICE_TRACKER_PICTURE
- ),
+ )
+ or vol.UNDEFINED,
): EntitySelector(EntitySelectorConfig(domain=Platform.IMAGE))
},
),
}
# Units
+ unit_schema: dict[vol.Marker, Any] = {}
+
+ if coordinator.vehicle.has_battery_engine():
+ unit_schema.update(
+ {
+ vol.Required(
+ OPT_ENERGY_CONSUMPTION_UNIT,
+ default=get_setting(
+ self.config_entry, OPT_ENERGY_CONSUMPTION_UNIT
+ ),
+ ): SelectSelector(
+ SelectSelectorConfig(
+ options=[
+ OPT_UNIT_ENERGY_KWH_PER_100KM,
+ OPT_UNIT_ENERGY_MILES_PER_KWH,
+ ],
+ multiple=False,
+ translation_key=OPT_ENERGY_CONSUMPTION_UNIT,
+ )
+ )
+ }
+ )
+
if coordinator.vehicle.has_combustion_engine():
- schema.update(
- _create_section(
- "units",
- {
- vol.Required(OPT_FUEL_CONSUMPTION_UNIT): SelectSelector(
- SelectSelectorConfig(
- options=[
- OPT_UNIT_LITER_PER_100KM,
- OPT_UNIT_MPG_UK,
- OPT_UNIT_MPG_US,
- ],
- multiple=False,
- translation_key=OPT_FUEL_CONSUMPTION_UNIT,
- )
+ unit_schema.update(
+ {
+ vol.Required(
+ OPT_FUEL_CONSUMPTION_UNIT,
+ default=get_setting(
+ self.config_entry, OPT_FUEL_CONSUMPTION_UNIT
+ ),
+ ): SelectSelector(
+ SelectSelectorConfig(
+ options=[
+ OPT_UNIT_LITER_PER_100KM,
+ OPT_UNIT_MPG_UK,
+ OPT_UNIT_MPG_US,
+ ],
+ multiple=False,
+ translation_key=OPT_FUEL_CONSUMPTION_UNIT,
)
- },
- )
+ )
+ }
)
+ schema.update(_create_section("units", unit_schema))
+
# Images
url = coordinator.vehicle.images.exterior_image_url
url_parts = parse.urlparse(url)
@@ -333,10 +413,13 @@ async def async_step_init(
_create_section(
"images",
{
- vol.Optional(OPT_IMG_TRANSPARENT, default=True): bool,
+ vol.Optional(
+ OPT_IMG_TRANSPARENT,
+ default=get_setting(self.config_entry, OPT_IMG_TRANSPARENT),
+ ): bool,
vol.Optional(
OPT_IMG_BG_COLOR,
- default=[255, 255, 255],
+ default=get_setting(self.config_entry, OPT_IMG_BG_COLOR),
): ColorRGBSelector(),
},
)
diff --git a/custom_components/volvo_cars/const.py b/custom_components/volvo_cars/const.py
index f7634a4..76aee7d 100644
--- a/custom_components/volvo_cars/const.py
+++ b/custom_components/volvo_cars/const.py
@@ -28,14 +28,19 @@
MANUFACTURER = "Volvo"
OPT_DEVICE_TRACKER_PICTURE = "device_tracker_picture"
+OPT_ENERGY_CONSUMPTION_UNIT = "energy_consumption_unit"
OPT_FUEL_CONSUMPTION_UNIT = "fuel_consumption_unit"
OPT_IMG_BG_COLOR = "image_bg_color"
OPT_IMG_TRANSPARENT = "image_transparent"
+OPT_UNIT_ENERGY_KWH_PER_100KM = "kwh_100km"
+OPT_UNIT_ENERGY_MILES_PER_KWH = "miles_kwh"
+
OPT_UNIT_LITER_PER_100KM = "l_100km"
OPT_UNIT_MPG_UK = "mpg_uk"
OPT_UNIT_MPG_US = "mpg_us"
+
SERVICE_REFRESH_DATA = "refresh_data"
SERVICE_PARAM_DATA = "data"
SERVICE_PARAM_ENTRY = "entry"
diff --git a/custom_components/volvo_cars/manifest.json b/custom_components/volvo_cars/manifest.json
index 40668aa..e17b273 100644
--- a/custom_components/volvo_cars/manifest.json
+++ b/custom_components/volvo_cars/manifest.json
@@ -9,5 +9,5 @@
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/thomasddn/ha-volvo-cars/issues",
"requirements": [],
- "version": "1.3.0"
+ "version": "0.0.0"
}
\ No newline at end of file
diff --git a/custom_components/volvo_cars/sensor.py b/custom_components/volvo_cars/sensor.py
index a3b0f2f..9d897e5 100644
--- a/custom_components/volvo_cars/sensor.py
+++ b/custom_components/volvo_cars/sensor.py
@@ -23,7 +23,10 @@
from .const import (
DATA_BATTERY_CAPACITY,
DATA_REQUEST_COUNT,
+ OPT_ENERGY_CONSUMPTION_UNIT,
OPT_FUEL_CONSUMPTION_UNIT,
+ OPT_UNIT_ENERGY_KWH_PER_100KM,
+ OPT_UNIT_ENERGY_MILES_PER_KWH,
OPT_UNIT_LITER_PER_100KM,
OPT_UNIT_MPG_UK,
OPT_UNIT_MPG_US,
@@ -46,8 +49,8 @@ class VolvoCarsSensorDescription(VolvoCarsDescription, SensorEntityDescription):
"""Describes a Volvo Cars sensor entity."""
value_fn: Callable[[VolvoCarsValue, VolvoCarsConfigEntry], Any] | None = None
- available_fn: Callable[[VolvoCarsVehicle], bool] = lambda vehicle: True
unit_fn: Callable[[VolvoCarsConfigEntry], str] | None = None
+ available_fn: Callable[[VolvoCarsVehicle], bool] = lambda vehicle: True
def _availability_status(field: VolvoCarsValue, _: VolvoCarsConfigEntry) -> str:
@@ -65,6 +68,30 @@ def _calculate_time_to_service(field: VolvoCarsValue, _: VolvoCarsConfigEntry) -
return value
+def _determine_energy_consumption_unit(entry: VolvoCarsConfigEntry) -> str:
+ unit_key = entry.options.get(
+ OPT_ENERGY_CONSUMPTION_UNIT, OPT_UNIT_ENERGY_KWH_PER_100KM
+ )
+
+ return "kWh/100 km" if unit_key == OPT_UNIT_ENERGY_KWH_PER_100KM else "mi/kWh"
+
+
+def _convert_energy_consumption(
+ field: VolvoCarsValue, entry: VolvoCarsConfigEntry
+) -> Decimal:
+ value = Decimal(field.value)
+ unit_key = entry.options.get(
+ OPT_ENERGY_CONSUMPTION_UNIT, OPT_UNIT_ENERGY_KWH_PER_100KM
+ )
+
+ converted_value = value
+
+ if unit_key == OPT_UNIT_ENERGY_MILES_PER_KWH:
+ converted_value = (100 / Decimal(1.609344) / value) if value else Decimal(0)
+
+ return round(converted_value, 1)
+
+
def _determine_fuel_consumption_unit(entry: VolvoCarsConfigEntry) -> str:
unit_key = entry.options.get(OPT_FUEL_CONSUMPTION_UNIT, OPT_UNIT_LITER_PER_100KM)
@@ -130,34 +157,41 @@ def _convert_fuel_consumption(
key="average_energy_consumption",
translation_key="average_energy_consumption",
api_field="averageEnergyConsumption",
- native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
- device_class=SensorDeviceClass.ENERGY,
+ native_unit_of_measurement="kWh/100 km",
+ state_class=SensorStateClass.MEASUREMENT,
icon="mdi:car-electric",
available_fn=lambda vehicle: vehicle.has_battery_engine(),
+ unit_fn=_determine_energy_consumption_unit,
+ value_fn=_convert_energy_consumption,
),
VolvoCarsSensorDescription(
key="average_energy_consumption_automatic",
translation_key="average_energy_consumption_automatic",
api_field="averageEnergyConsumptionAutomatic",
- native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
- device_class=SensorDeviceClass.ENERGY,
+ native_unit_of_measurement="kWh/100 km",
+ state_class=SensorStateClass.MEASUREMENT,
icon="mdi:car-electric",
available_fn=lambda vehicle: vehicle.has_battery_engine(),
+ unit_fn=_determine_energy_consumption_unit,
+ value_fn=_convert_energy_consumption,
),
VolvoCarsSensorDescription(
key="average_energy_consumption_charge",
translation_key="average_energy_consumption_charge",
api_field="averageEnergyConsumptionSinceCharge",
- native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
- device_class=SensorDeviceClass.ENERGY,
+ native_unit_of_measurement="kWh/100 km",
+ state_class=SensorStateClass.MEASUREMENT,
icon="mdi:car-electric",
available_fn=lambda vehicle: vehicle.has_battery_engine(),
+ unit_fn=_determine_energy_consumption_unit,
+ value_fn=_convert_energy_consumption,
),
VolvoCarsSensorDescription(
key="average_fuel_consumption",
translation_key="average_fuel_consumption",
api_field="averageFuelConsumption",
native_unit_of_measurement="L/100 km",
+ state_class=SensorStateClass.MEASUREMENT,
icon="mdi:gas-station",
available_fn=lambda vehicle: vehicle.has_combustion_engine(),
unit_fn=_determine_fuel_consumption_unit,
@@ -168,6 +202,7 @@ def _convert_fuel_consumption(
translation_key="average_fuel_consumption_automatic",
api_field="averageFuelConsumptionAutomatic",
native_unit_of_measurement="L/100 km",
+ state_class=SensorStateClass.MEASUREMENT,
icon="mdi:gas-station",
available_fn=lambda vehicle: vehicle.has_combustion_engine(),
unit_fn=_determine_fuel_consumption_unit,
diff --git a/custom_components/volvo_cars/strings.json b/custom_components/volvo_cars/strings.json
index 6d0c8ee..40fd96a 100644
--- a/custom_components/volvo_cars/strings.json
+++ b/custom_components/volvo_cars/strings.json
@@ -31,7 +31,8 @@
"reauth_successful": "Successfully updated credentials"
},
"error": {
- "invalid_auth": "Authentication failed"
+ "invalid_auth": "Authentication failed",
+ "invalid_vin": "VIN is invalid"
},
"step": {
"otp": {
@@ -1196,7 +1197,8 @@
},
"units": {
"data": {
- "fuel_consumption_unit": "Fuel consumption unit"
+ "fuel_consumption_unit": "Fuel consumption unit",
+ "energy_consumption_unit": "Energy consumption unit"
},
"name": "Units"
}
@@ -1205,6 +1207,12 @@
}
},
"selector": {
+ "energy_consumption_unit": {
+ "options": {
+ "kwh_100km": "kWh/100 km",
+ "miles_kwh": "mi/kWh"
+ }
+ },
"fuel_consumption_unit": {
"options": {
"l_100km": "L/100 km",
diff --git a/custom_components/volvo_cars/translations/de.json b/custom_components/volvo_cars/translations/de.json
index 0af4a75..ec4f226 100644
--- a/custom_components/volvo_cars/translations/de.json
+++ b/custom_components/volvo_cars/translations/de.json
@@ -31,7 +31,8 @@
"reauth_successful": "Zugangsdaten erfolgreich aktualisiert"
},
"error": {
- "invalid_auth": "Authentifizierung fehlgeschlagen"
+ "invalid_auth": "Authentifizierung fehlgeschlagen",
+ "invalid_vin": "Fahrzeug-Identifikationsnummer ist ungültig"
},
"step": {
"otp": {
@@ -1196,6 +1197,7 @@
},
"units": {
"data": {
+ "energy_consumption_unit": "Einheit Energieverbrauch",
"fuel_consumption_unit": "Verbrauchseinheit"
},
"name": "Einheit"
@@ -1205,6 +1207,12 @@
}
},
"selector": {
+ "energy_consumption_unit": {
+ "options": {
+ "kwh_100km": "kWh/100 km",
+ "miles_kwh": "mi/kWh"
+ }
+ },
"fuel_consumption_unit": {
"options": {
"l_100km": "L/100 km",
diff --git a/custom_components/volvo_cars/translations/en.json b/custom_components/volvo_cars/translations/en.json
index 96c7266..aa1456f 100644
--- a/custom_components/volvo_cars/translations/en.json
+++ b/custom_components/volvo_cars/translations/en.json
@@ -31,7 +31,8 @@
"reauth_successful": "Successfully updated credentials"
},
"error": {
- "invalid_auth": "Authentication failed"
+ "invalid_auth": "Authentication failed",
+ "invalid_vin": "VIN is invalid"
},
"step": {
"otp": {
@@ -1196,6 +1197,7 @@
},
"units": {
"data": {
+ "energy_consumption_unit": "Energy consumption unit",
"fuel_consumption_unit": "Fuel consumption unit"
},
"name": "Units"
@@ -1205,6 +1207,12 @@
}
},
"selector": {
+ "energy_consumption_unit": {
+ "options": {
+ "kwh_100km": "kWh/100 km",
+ "miles_kwh": "mi/kWh"
+ }
+ },
"fuel_consumption_unit": {
"options": {
"l_100km": "L/100 km",
diff --git a/custom_components/volvo_cars/translations/fr.json b/custom_components/volvo_cars/translations/fr.json
new file mode 100644
index 0000000..28af978
--- /dev/null
+++ b/custom_components/volvo_cars/translations/fr.json
@@ -0,0 +1,1258 @@
+{
+ "common": {
+ "api_timestamp": "Horodatage API",
+ "car_error": "Erreur voiture",
+ "completed": "Terminé",
+ "connection_failure": "Échec de la connexion",
+ "delivered": "Transmis",
+ "door_open": "Portière ouverte",
+ "error": "Erreur",
+ "high_pressure": "Pression élevée",
+ "last_refresh": "Dernière actualisation",
+ "last_result": "Dernier résultat",
+ "low_pressure": "Pression faible",
+ "not_allowed_privacy_enabled": "Non autorisé, protection de la vie privée activée",
+ "ok": "OK",
+ "pressure": "Pression",
+ "rejected": "Rejeté",
+ "running": "Allumé",
+ "time_frame_passed": "Délai écoulé",
+ "timeout": "Expiré",
+ "unknown": "Inconnu",
+ "vcc_api_key": "Clé d'API Volvo",
+ "vehicle_in_sleep": "Véhicule en veille",
+ "very_low_pressure": "Pression très faible",
+ "waiting": "En attente",
+ "wrong_usage": "Mauvaise utilisation"
+ },
+ "config": {
+ "abort": {
+ "already_configured": "La voiture est déjà configurée",
+ "reauth_successful": "Mise à jour des informations d'identification réussie"
+ },
+ "error": {
+ "invalid_auth": "Échec de l'authentification",
+ "invalid_vin": "Le numéro VIN est incorrect"
+ },
+ "step": {
+ "otp": {
+ "data": {
+ "otp": "OTP"
+ },
+ "description": "Saisissez l'OTP que vous avez reçu dans votre boîte mail."
+ },
+ "reauth_confirm": {
+ "description": "L'intégration de Volvo Cars doit ré-authentifier votre compte.",
+ "title": "Informations d’identification expirées"
+ },
+ "user": {
+ "data": {
+ "friendly_name": "Nom d'affichage",
+ "password": "Mot de passe",
+ "username": "Nom d'utilisateur",
+ "vcc_api_key": "Clé d'API Volvo",
+ "vin": "Numéro d'identification du véhicule"
+ },
+ "data_description": {
+ "friendly_name": "Cette valeur est utilisée dans l'ID d'entité (volvo_[nom d'affichage]_[clé]). Si elle est laissée vide, le VIN sera utilisé.",
+ "vin": "Le VIN comporte 17 caractères et commence probablement par YV1 ou YV4."
+ },
+ "description": "Saisissez les informations d'identification de votre compte Volvo, le numéro VIN et la clé API de développeur Volvo (https://developer.volvocars.com/)."
+ }
+ }
+ },
+ "entity": {
+ "binary_sensor": {
+ "brake_fluid_level_warning": {
+ "name": "Liquide de frein",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "brake_light_center_warning": {
+ "name": "Feu stop central",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "brake_light_left_warning": {
+ "name": "Feu stop gauche",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "brake_light_right_warning": {
+ "name": "Feu stop droit",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "coolant_level_warning": {
+ "name": "Niveau du liquide de refroidissement",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "daytime_running_light_left_warning": {
+ "name": "Feu de jour gauche",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "daytime_running_light_right_warning": {
+ "name": "Feu de jour droit",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "door_front_left": {
+ "name": "Portière avant gauche",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "door_front_right": {
+ "name": "Portière avant droite",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "door_rear_left": {
+ "name": "Portière arrière gauche",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "door_rear_right": {
+ "name": "Portière arrière droite",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "engine_status": {
+ "name": "État du moteur",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "fog_light_front_warning": {
+ "name": "Feu antibrouillard avant",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "fog_light_rear_warning": {
+ "name": "Feu antibrouillard arrière",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "hazard_lights_warning": {
+ "name": "Feux de détresse",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "high_beam_left_warning": {
+ "name": "Feu de route gauche",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "high_beam_right_warning": {
+ "name": "Feu de route droit",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "hood": {
+ "name": "Capot",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "low_beam_left_warning": {
+ "name": "Feu de croisement gauche",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "low_beam_right_warning": {
+ "name": "Feu de croisement droit",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "oil_level_warning": {
+ "name": "Niveau d'huile",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ },
+ "level": {
+ "name": "Niveau",
+ "state": {
+ "no_warning": "OK",
+ "service_required": "Entretien nécessaire",
+ "too_high": "Trop élevé",
+ "too_low": "Trop faible"
+ }
+ }
+ }
+ },
+ "position_light_front_left_warning": {
+ "name": "Feu de position avant gauche",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "position_light_front_right_warning": {
+ "name": "Feu de position avant droit",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "position_light_rear_left_warning": {
+ "name": "Feu de position arrière gauche",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "position_light_rear_right_warning": {
+ "name": "Feu de position arrière droit",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "registration_plate_light_warning": {
+ "name": "Éclairage plaque d'immatriculation",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "reverse_lights_warning": {
+ "name": "Feux de recul",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "service_warning": {
+ "name": "Entretien",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ },
+ "reason": {
+ "name": "Motif",
+ "state": {
+ "distance_driven_almost_time_for_service": "Entretien périodique à prévoir",
+ "distance_driven_overdue_for_service": "Entretien périodique en retard",
+ "distance_driven_time_for_service": "Entretien périodique à effectuer",
+ "engine_hours_almost_time_for_service": "Entretien moteur à prévoir",
+ "engine_hours_overdue_for_service": "Entretien moteur en retard",
+ "engine_hours_time_for_service": "Entretien moteur à effectuer",
+ "no_warning": "OK",
+ "regular_maintenance_almost_time_for_service": "Entretien à prévoir",
+ "regular_maintenance_overdue_for_service": "Entretien en retard",
+ "regular_maintenance_time_for_service": "Entretien à effectuer",
+ "unknown_warning": "Avertissement inconnu"
+ }
+ }
+ }
+ },
+ "side_mark_lights_warning": {
+ "name": "Feux de position latéraux",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "sunroof": {
+ "name": "Toit ouvrant",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "tailgate": {
+ "name": "Hayon",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "tank_lid": {
+ "name": "Trappe à carburant",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "turn_indication_front_left_warning": {
+ "name": "Clignotant avant gauche",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "turn_indication_front_right_warning": {
+ "name": "Clignotant avant droit",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "turn_indication_rear_left_warning": {
+ "name": "Clignotant arrière gauche",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "turn_indication_rear_right_warning": {
+ "name": "Clignotant arrière droit",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "tyre_front_left": {
+ "name": "Pneu avant gauche",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ },
+ "pressure": {
+ "name": "Pression",
+ "state": {
+ "high_pressure": "Pression élevée",
+ "low_pressure": "Pression faible",
+ "no_warning": "OK",
+ "very_low_pressure": "Pression très faible"
+ }
+ }
+ }
+ },
+ "tyre_front_right": {
+ "name": "Pneu avant droit",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ },
+ "pressure": {
+ "name": "Pression",
+ "state": {
+ "high_pressure": "Pression élevée",
+ "low_pressure": "Pression faible",
+ "no_warning": "OK",
+ "very_low_pressure": "Pression très faible"
+ }
+ }
+ }
+ },
+ "tyre_rear_left": {
+ "name": "Pneu arrière gauche",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ },
+ "pressure": {
+ "name": "Pression",
+ "state": {
+ "high_pressure": "Pression élevée",
+ "low_pressure": "Pression faible",
+ "no_warning": "OK",
+ "very_low_pressure": "Pression très faible"
+ }
+ }
+ }
+ },
+ "tyre_rear_right": {
+ "name": "Pneu arrière droit",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ },
+ "pressure": {
+ "name": "Pression",
+ "state": {
+ "high_pressure": "Pression élevée",
+ "low_pressure": "Pression faible",
+ "no_warning": "OK",
+ "very_low_pressure": "Pression très faible"
+ }
+ }
+ }
+ },
+ "washer_fluid_level_warning": {
+ "name": "Liquide lave-glace",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "window_front_left": {
+ "name": "Fenêtre avant gauche",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "window_front_right": {
+ "name": "Fenêtre avant droite",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "window_rear_left": {
+ "name": "Fenêtre arrière gauche",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "window_rear_right": {
+ "name": "Fenêtre arrière droite",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ }
+ },
+ "button": {
+ "climatization_start": {
+ "name": "Démarrer la climatisation",
+ "state_attributes": {
+ "last_result": {
+ "name": "Dernier résultat",
+ "state": {
+ "car_error": "Erreur voiture",
+ "completed": "Terminé",
+ "connection_failure": "Échec de la connexion",
+ "delivered": "Transmis",
+ "not_allowed_privacy_enabled": "Non autorisé, protection de la vie privée activée",
+ "not_allowed_wrong_usage_mode": "Mauvaise utilisation",
+ "rejected": "Rejeté",
+ "running": "Allumé",
+ "timeout": "Expiré",
+ "unknown": "Inconnu",
+ "vehicle_in_sleep": "Véhicule en veille",
+ "waiting": "En attente"
+ }
+ }
+ }
+ },
+ "climatization_stop": {
+ "name": "Arrêter la climatisation",
+ "state_attributes": {
+ "last_result": {
+ "name": "Dernier résultat",
+ "state": {
+ "car_error": "Erreur voiture",
+ "completed": "Terminé",
+ "connection_failure": "Échec de la connexion",
+ "delivered": "Transmis",
+ "not_allowed_privacy_enabled": "Non autorisé, protection de la vie privée activée",
+ "not_allowed_wrong_usage_mode": "Mauvaise utilisation",
+ "rejected": "Rejeté",
+ "running": "Allumé",
+ "timeout": "Expiré",
+ "unknown": "Inconnu",
+ "vehicle_in_sleep": "Véhicule en veille",
+ "waiting": "En attente"
+ }
+ }
+ }
+ },
+ "engine_start": {
+ "name": "Démarrer le moteur",
+ "state_attributes": {
+ "last_result": {
+ "name": "Dernier résultat",
+ "state": {
+ "car_error": "Erreur voiture",
+ "completed": "Terminé",
+ "connection_failure": "Échec de la connexion",
+ "delivered": "Transmis",
+ "not_allowed_privacy_enabled": "Non autorisé, protection de la vie privée activée",
+ "not_allowed_wrong_usage_mode": "Mauvaise utilisation",
+ "rejected": "Rejeté",
+ "running": "Allumé",
+ "timeout": "Expiré",
+ "unknown": "Inconnu",
+ "vehicle_in_sleep": "Véhicule en veille",
+ "waiting": "En attente"
+ }
+ }
+ }
+ },
+ "engine_stop": {
+ "name": "Arrêter le moteur",
+ "state_attributes": {
+ "last_result": {
+ "name": "Dernier résultat",
+ "state": {
+ "car_error": "Erreur voiture",
+ "completed": "Terminé",
+ "connection_failure": "Échec de la connexion",
+ "delivered": "Transmis",
+ "not_allowed_privacy_enabled": "Non autorisé, protection de la vie privée activée",
+ "not_allowed_wrong_usage_mode": "Mauvaise utilisation",
+ "rejected": "Rejeté",
+ "running": "Allumé",
+ "timeout": "Expiré",
+ "unknown": "Inconnu",
+ "vehicle_in_sleep": "Véhicule en veille",
+ "waiting": "En attente"
+ }
+ }
+ }
+ },
+ "flash": {
+ "name": "Clignoter",
+ "state_attributes": {
+ "last_result": {
+ "name": "Dernier résultat",
+ "state": {
+ "car_error": "Erreur voiture",
+ "completed": "Terminé",
+ "connection_failure": "Échec de la connexion",
+ "delivered": "Transmis",
+ "not_allowed_privacy_enabled": "Non autorisé, protection de la vie privée activée",
+ "not_allowed_wrong_usage_mode": "Mauvaise utilisation",
+ "rejected": "Rejeté",
+ "timeout": "Expiré",
+ "unknown": "Inconnu",
+ "vehicle_in_sleep": "Véhicule en veille"
+ }
+ }
+ }
+ },
+ "honk": {
+ "name": "Avertisseur sonore",
+ "state_attributes": {
+ "last_result": {
+ "name": "Dernier résultat",
+ "state": {
+ "car_error": "Erreur voiture",
+ "completed": "Terminé",
+ "connection_failure": "Échec de la connexion",
+ "delivered": "Transmis",
+ "not_allowed_privacy_enabled": "Non autorisé, protection de la vie privée activée",
+ "not_allowed_wrong_usage_mode": "Mauvaise utilisation",
+ "rejected": "Rejeté",
+ "timeout": "Expiré",
+ "unknown": "Inconnu",
+ "vehicle_in_sleep": "Véhicule en veille"
+ }
+ }
+ }
+ },
+ "honk_flash": {
+ "name": "Klaxonner et clignoter",
+ "state_attributes": {
+ "last_result": {
+ "name": "Dernier résultat",
+ "state": {
+ "car_error": "Erreur voiture",
+ "completed": "Terminé",
+ "connection_failure": "Échec de la connexion",
+ "delivered": "Transmis",
+ "not_allowed_privacy_enabled": "Non autorisé, protection de la vie privée activée",
+ "not_allowed_wrong_usage_mode": "Mauvaise utilisation",
+ "rejected": "Rejeté",
+ "timeout": "Expiré",
+ "unknown": "Inconnu",
+ "vehicle_in_sleep": "Véhicule en veille"
+ }
+ }
+ }
+ },
+ "update_data": {
+ "name": "Mettre à jour les données"
+ }
+ },
+ "device_tracker": {
+ "location": {
+ "name": "Emplacement",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "direction": {
+ "name": "Direction"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ }
+ },
+ "image": {
+ "exterior": {
+ "name": "Extérieur côté passager"
+ },
+ "exterior_back": {
+ "name": "Extérieur arrière"
+ },
+ "exterior_back_driver": {
+ "name": "Extérieur arrière conducteur"
+ },
+ "exterior_back_passenger": {
+ "name": "Extérieur arrière passager"
+ },
+ "exterior_front": {
+ "name": "Extérieur avant"
+ },
+ "exterior_front_driver": {
+ "name": "Extérieur avant conducteur"
+ },
+ "exterior_front_passenger": {
+ "name": "Extérieur avant passager"
+ },
+ "exterior_side_driver": {
+ "name": "Extérieur côté conducteur"
+ },
+ "interior": {
+ "name": "Intérieur"
+ }
+ },
+ "lock": {
+ "lock": {
+ "name": "Verrouillage centralisé",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ },
+ "last_result": {
+ "name": "Dernier résultat",
+ "state": {
+ "completed": "Terminé",
+ "connection_failure": "Échec de la connexion",
+ "delivered": "Transmis",
+ "not_allowed_privacy_enabled": "Non autorisé, protection de la vie privée activée",
+ "not_allowed_wrong_usage_mode": "Mauvaise utilisation",
+ "rejected": "Rejeté",
+ "timeout": "Expiré",
+ "unable_to_lock_door_open": "Portière ouverte",
+ "unknown": "Inconnu",
+ "unlock_time_frame_passed": "Délai écoulé",
+ "vehicle_in_sleep": "Véhicule en veille"
+ }
+ }
+ }
+ },
+ "lock_reduced_guard": {
+ "name": "Verrouillage avec alarme réduite",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ },
+ "last_result": {
+ "name": "Dernier résultat",
+ "state": {
+ "completed": "Terminé",
+ "connection_failure": "Échec de la connexion",
+ "delivered": "Transmis",
+ "not_allowed_privacy_enabled": "Non autorisé, protection de la vie privée activée",
+ "not_allowed_wrong_usage_mode": "Mauvaise utilisation",
+ "rejected": "Rejeté",
+ "timeout": "Expiré",
+ "unable_to_lock_door_open": "Portière ouverte",
+ "unknown": "Inconnu",
+ "unlock_time_frame_passed": "Délai écoulé",
+ "vehicle_in_sleep": "Véhicule en veille"
+ }
+ }
+ }
+ }
+ },
+ "number": {
+ "data_update_interval": {
+ "name": "Intervalle de mise à jour des données"
+ },
+ "engine_run_time": {
+ "name": "Durée de fonctionnement du moteur"
+ }
+ },
+ "sensor": {
+ "api_request_count": {
+ "name": "Nombre de requêtes API"
+ },
+ "api_status": {
+ "name": "État de l’API",
+ "state_attributes": {
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "availability": {
+ "name": "Connexion à la voiture",
+ "state": {
+ "available": "Disponible",
+ "car_in_use": "Voiture en cours d'utilisation",
+ "no_internet": "Pas d'internet",
+ "power_saving_mode": "Mode économie d'énergie",
+ "unavailable": "Indisponible",
+ "unspecified": "Inconnu"
+ },
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "average_energy_consumption": {
+ "name": "Consommation moyenne d'énergie TM",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "average_energy_consumption_automatic": {
+ "name": "Consommation moyenne d'énergie TA",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "average_energy_consumption_charge": {
+ "name": "Consommation énergétique moyenne depuis la charge",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "average_fuel_consumption": {
+ "name": "Consommation moyenne de carburant TM",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "average_fuel_consumption_automatic": {
+ "name": "Consommation moyenne de carburant TA",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "average_speed": {
+ "name": "Vitesse moyenne TM",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "average_speed_automatic": {
+ "name": "Vitesse moyenne TA",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "battery_capacity": {
+ "name": "Capacité de la batterie",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "battery_charge_level": {
+ "name": "Niveau de charge de la batterie",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "charging_connection_status": {
+ "name": "État de la connexion de charge",
+ "state": {
+ "connection_status_connected_ac": "Connecté AC",
+ "connection_status_connected_dc": "Connecté DC",
+ "connection_status_disconnected": "Déconnecté",
+ "connection_status_fault": "Erreur",
+ "connection_status_unspecified": "Inconnu"
+ },
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "charging_system_status": {
+ "name": "État du chargement",
+ "state": {
+ "charging_system_charging": "En charge",
+ "charging_system_done": "Terminé",
+ "charging_system_fault": "Erreur",
+ "charging_system_idle": "Inactif",
+ "charging_system_scheduled": "Programmé",
+ "charging_system_unspecified": "Inconnu"
+ },
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "distance_to_empty_battery": {
+ "name": "Autonomie batterie",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "distance_to_empty_tank": {
+ "name": "Autonomie carburant",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "distance_to_service": {
+ "name": "Distance jusqu'au prochain entretien",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "engine_time_to_service": {
+ "name": "Heures d'utilisation jusqu'au prochain entretien",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "estimated_charging_time": {
+ "name": "Temps de charge estimé",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "fuel_amount": {
+ "name": "Quantité de carburant",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "odometer": {
+ "name": "Compteur kilométrique",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "time_to_service": {
+ "name": "Temps restant jusqu'au prochain entretien",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "trip_meter_automatic": {
+ "name": "Distance TA",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ },
+ "trip_meter_manual": {
+ "name": "Distance TM",
+ "state_attributes": {
+ "api_timestamp": {
+ "name": "Horodatage API"
+ },
+ "last_refresh": {
+ "name": "Dernière actualisation"
+ }
+ }
+ }
+ }
+ },
+ "exceptions": {
+ "lock_failure": {
+ "message": "Échec de la commande du véhicule {command}. Statut: {status}. Message: {message}"
+ }
+ },
+ "options": {
+ "abort": {
+ "no_options_available": "Aucune option disponible pour votre véhicule."
+ },
+ "step": {
+ "init": {
+ "sections": {
+ "api": {
+ "data": {
+ "vcc_api_key": "Clé d'API Volvo"
+ },
+ "name": "API"
+ },
+ "device_tracker": {
+ "data": {
+ "device_tracker_picture": "Image"
+ },
+ "name": "Suivi d'appareil"
+ },
+ "images": {
+ "data": {
+ "image_bg_color": "Couleur d'arrière-plan des images",
+ "image_transparent": "Arrière-plan des images transparent"
+ },
+ "data_description": {
+ "image_bg_color": "Applicable uniquement si la case \"transparent\" n'est pas cochée."
+ },
+ "name": "Images"
+ },
+ "units": {
+ "data": {
+ "energy_consumption_unit": "Unité de consommation d'énergie",
+ "fuel_consumption_unit": "Unité de consommation de carburant"
+ },
+ "name": "Unités"
+ }
+ }
+ }
+ }
+ },
+ "selector": {
+ "energy_consumption_unit": {
+ "options": {
+ "kwh_100km": "kWh/100 km",
+ "miles_kwh": "mi/kWh"
+ }
+ },
+ "fuel_consumption_unit": {
+ "options": {
+ "l_100km": "L/100 km",
+ "mpg_uk": "mpg (UK)",
+ "mpg_us": "mpg (US)"
+ }
+ },
+ "refresh_data_param": {
+ "options": {
+ "availability": "Disponibilité",
+ "brakes": "Freins",
+ "diagnostics": "Diagnostics",
+ "doors": "Portières",
+ "engine": "Moteur",
+ "engine_status": "État du moteur",
+ "fuel": "Carburant",
+ "location": "Localisation",
+ "odometer": "Compteur kilométrique",
+ "recharge_status": "État de la recharge",
+ "statistics": "Statistiques",
+ "tyres": "Pneus",
+ "warnings": "Avertissements",
+ "windows": "Fenêtres"
+ }
+ }
+ },
+ "services": {
+ "refresh_data": {
+ "description": "Actualiser toutes les données ou uniquement des parties spécifiques.",
+ "fields": {
+ "data": {
+ "description": "Laissez le champ vide pour actualiser toutes les données.",
+ "name": "Donnée"
+ },
+ "entry": {
+ "description": "L'entrée à rafraîchir. Laissez vide pour actualiser les données de toutes les entrées.",
+ "name": "Entrée"
+ }
+ },
+ "name": "Actualiser les données"
+ }
+ }
+}
\ No newline at end of file
diff --git a/custom_components/volvo_cars/translations/no.json b/custom_components/volvo_cars/translations/nb.json
similarity index 99%
rename from custom_components/volvo_cars/translations/no.json
rename to custom_components/volvo_cars/translations/nb.json
index d98c123..f26a461 100644
--- a/custom_components/volvo_cars/translations/no.json
+++ b/custom_components/volvo_cars/translations/nb.json
@@ -31,7 +31,8 @@
"reauth_successful": "Oppdatering av autentiseringsinformasjon vellykket."
},
"error": {
- "invalid_auth": "Oppdatering av autentiseringsinformasjon misslyktes."
+ "invalid_auth": "Oppdatering av autentiseringsinformasjon misslyktes.",
+ "invalid_vin": "Ikke gyldig understellsnummer"
},
"step": {
"otp": {
@@ -1035,7 +1036,7 @@
"charging_system_charging": "Lader",
"charging_system_done": "Ferdig",
"charging_system_fault": "Feil",
- "charging_system_idle": "Tomgang",
+ "charging_system_idle": "Inaktiv",
"charging_system_scheduled": "Planlagt",
"charging_system_unspecified": "Ukjent"
},
@@ -1196,6 +1197,7 @@
},
"units": {
"data": {
+ "energy_consumption_unit": "Enhet for strømforbruk",
"fuel_consumption_unit": "Drivstofforbruk enhet"
},
"name": "Enheter"
@@ -1205,6 +1207,12 @@
}
},
"selector": {
+ "energy_consumption_unit": {
+ "options": {
+ "kwh_100km": "kWh/100 km",
+ "miles_kwh": "mi/kWh"
+ }
+ },
"fuel_consumption_unit": {
"options": {
"l_100km": "L/100 km",
diff --git a/custom_components/volvo_cars/translations/nl.json b/custom_components/volvo_cars/translations/nl.json
index 38218fc..93c9718 100644
--- a/custom_components/volvo_cars/translations/nl.json
+++ b/custom_components/volvo_cars/translations/nl.json
@@ -31,7 +31,8 @@
"reauth_successful": "Inloggegevens succesvol bijgewerkt"
},
"error": {
- "invalid_auth": "Aanmelden mislukt"
+ "invalid_auth": "Aanmelden mislukt",
+ "invalid_vin": "Ongeldige VIN"
},
"step": {
"otp": {
@@ -1196,6 +1197,7 @@
},
"units": {
"data": {
+ "energy_consumption_unit": "Eenheid energieverbruik",
"fuel_consumption_unit": "Eenheid brandstofverbruik"
},
"name": "Eenheden"
@@ -1205,6 +1207,12 @@
}
},
"selector": {
+ "energy_consumption_unit": {
+ "options": {
+ "kwh_100km": "kWh/100 km",
+ "miles_kwh": "mi/kWh"
+ }
+ },
"fuel_consumption_unit": {
"options": {
"l_100km": "L/100 km",
diff --git a/custom_components/volvo_cars/translations/pl.json b/custom_components/volvo_cars/translations/pl.json
index 9f6a629..2f49ee4 100644
--- a/custom_components/volvo_cars/translations/pl.json
+++ b/custom_components/volvo_cars/translations/pl.json
@@ -31,7 +31,8 @@
"reauth_successful": "Dane uwierzytelniające zostały zaktualizowane"
},
"error": {
- "invalid_auth": "Autentykacja nie powiodła się"
+ "invalid_auth": "Autentykacja nie powiodła się",
+ "invalid_vin": "VIN jest nieprawidłowy"
},
"step": {
"otp": {
@@ -1196,6 +1197,7 @@
},
"units": {
"data": {
+ "energy_consumption_unit": "Jednostka zużycia energii",
"fuel_consumption_unit": "Jednostka zużycia paliwa"
},
"name": "Jednostki"
@@ -1205,6 +1207,12 @@
}
},
"selector": {
+ "energy_consumption_unit": {
+ "options": {
+ "kwh_100km": "kWh/100 km",
+ "miles_kwh": "mi/kWh"
+ }
+ },
"fuel_consumption_unit": {
"options": {
"l_100km": "L/100 km",
diff --git a/custom_components/volvo_cars/translations/pt-BR.json b/custom_components/volvo_cars/translations/pt-BR.json
index c93ba61..98da225 100644
--- a/custom_components/volvo_cars/translations/pt-BR.json
+++ b/custom_components/volvo_cars/translations/pt-BR.json
@@ -31,7 +31,8 @@
"reauth_successful": "Credenciais atualizadas com sucesso"
},
"error": {
- "invalid_auth": "Falha na autenticação"
+ "invalid_auth": "Falha na autenticação",
+ "invalid_vin": ""
},
"step": {
"otp": {
@@ -1196,6 +1197,7 @@
},
"units": {
"data": {
+ "energy_consumption_unit": "",
"fuel_consumption_unit": "Unidade de consumo de combustível"
},
"name": "Unidades"
@@ -1205,6 +1207,12 @@
}
},
"selector": {
+ "energy_consumption_unit": {
+ "options": {
+ "kwh_100km": "kWh/100 km",
+ "miles_kwh": "mi/kWh"
+ }
+ },
"fuel_consumption_unit": {
"options": {
"l_100km": "L/100 km",
diff --git a/custom_components/volvo_cars/translations/pt.json b/custom_components/volvo_cars/translations/pt.json
index 7cc0efc..50d282e 100644
--- a/custom_components/volvo_cars/translations/pt.json
+++ b/custom_components/volvo_cars/translations/pt.json
@@ -31,7 +31,8 @@
"reauth_successful": "Credenciais atualizadas com êxito"
},
"error": {
- "invalid_auth": "Falha na autenticação"
+ "invalid_auth": "Falha na autenticação",
+ "invalid_vin": "Número VIN inválido"
},
"step": {
"otp": {
@@ -1196,6 +1197,7 @@
},
"units": {
"data": {
+ "energy_consumption_unit": "Unidade de consumo de energia",
"fuel_consumption_unit": "Unidade de consumo de combustível"
},
"name": "Unidades"
@@ -1205,6 +1207,12 @@
}
},
"selector": {
+ "energy_consumption_unit": {
+ "options": {
+ "kwh_100km": "kWh/100 km",
+ "miles_kwh": "mi/kWh"
+ }
+ },
"fuel_consumption_unit": {
"options": {
"l_100km": "L/100 km",
diff --git a/custom_components/volvo_cars/translations/sv.json b/custom_components/volvo_cars/translations/sv.json
index 7192764..d10756e 100644
--- a/custom_components/volvo_cars/translations/sv.json
+++ b/custom_components/volvo_cars/translations/sv.json
@@ -15,7 +15,7 @@
"ok": "OK",
"pressure": "Tryck",
"rejected": "Avvisad",
- "running": "Löper",
+ "running": "Kör",
"time_frame_passed": "Tidsram passerad",
"timeout": "Timeout",
"unknown": "Okänd",
@@ -31,7 +31,8 @@
"reauth_successful": "Autentiseringsuppgifterna har uppdaterats"
},
"error": {
- "invalid_auth": "Autentiseringen misslyckades"
+ "invalid_auth": "Autentiseringen misslyckades",
+ "invalid_vin": "VIN-numret är felaktigt"
},
"step": {
"otp": {
@@ -633,7 +634,7 @@
"not_allowed_privacy_enabled": "Inte tillåtet, sekretess aktiverad",
"not_allowed_wrong_usage_mode": "Fel användning",
"rejected": "Avvisad",
- "running": "Löper",
+ "running": "Kör",
"timeout": "Timeout",
"unknown": "Okänd",
"vehicle_in_sleep": "Fordonet i dvala",
@@ -655,7 +656,7 @@
"not_allowed_privacy_enabled": "Inte tillåtet, sekretess aktiverad",
"not_allowed_wrong_usage_mode": "Fel användning",
"rejected": "Avvisad",
- "running": "Löper",
+ "running": "Kör",
"timeout": "Timeout",
"unknown": "Okänd",
"vehicle_in_sleep": "Fordonet i dvala",
@@ -677,7 +678,7 @@
"not_allowed_privacy_enabled": "Inte tillåtet, sekretess aktiverad",
"not_allowed_wrong_usage_mode": "Fel användning",
"rejected": "Avvisad",
- "running": "Löper",
+ "running": "Kör",
"timeout": "Timeout",
"unknown": "Okänd",
"vehicle_in_sleep": "Fordonet i dvala",
@@ -699,7 +700,7 @@
"not_allowed_privacy_enabled": "Inte tillåtet, sekretess aktiverad",
"not_allowed_wrong_usage_mode": "Fel användning",
"rejected": "Avvisad",
- "running": "Löper",
+ "running": "Kör",
"timeout": "Timeout",
"unknown": "Okänd",
"vehicle_in_sleep": "Fordonet i dvala",
@@ -1033,9 +1034,9 @@
"name": "Laddningsstatus",
"state": {
"charging_system_charging": "Laddar",
- "charging_system_done": "Utfört",
+ "charging_system_done": "Laddning klar",
"charging_system_fault": "Fel",
- "charging_system_idle": "På tomgång",
+ "charging_system_idle": "Ingen laddning",
"charging_system_scheduled": "Schemalagd",
"charging_system_unspecified": "Okänd"
},
@@ -1196,6 +1197,7 @@
},
"units": {
"data": {
+ "energy_consumption_unit": "Energiförbrukningsenhet",
"fuel_consumption_unit": "Bränsleförbrukningsenhet"
},
"name": "Enheter"
@@ -1205,6 +1207,12 @@
}
},
"selector": {
+ "energy_consumption_unit": {
+ "options": {
+ "kwh_100km": "kWh/100 km",
+ "miles_kwh": "mi/kWh"
+ }
+ },
"fuel_consumption_unit": {
"options": {
"l_100km": "L/100 km",
diff --git a/hacs.json b/hacs.json
index a0b8a53..25ae754 100644
--- a/hacs.json
+++ b/hacs.json
@@ -2,5 +2,7 @@
"name": "Volvo Cars",
"homeassistant": "2024.11.0",
"hide_default_branch": true,
- "render_readme": true
+ "render_readme": true,
+ "zip_release": true,
+ "filename": "volvo_cars.zip"
}
\ No newline at end of file
diff --git a/scripts/__init__.py b/scripts/__init__.py
new file mode 100644
index 0000000..a9a18d7
--- /dev/null
+++ b/scripts/__init__.py
@@ -0,0 +1 @@
+"""Scripts."""
diff --git a/scripts/check_translations.py b/scripts/check_translations.py
new file mode 100644
index 0000000..59f6211
--- /dev/null
+++ b/scripts/check_translations.py
@@ -0,0 +1,71 @@
+"""Check translation files for errors."""
+
+import json
+from pathlib import Path
+import sys
+
+BASE_TRANSLATION = Path("custom_components/volvo_cars/strings.json")
+TRANSLATIONS_DIR = Path("custom_components/volvo_cars/translations")
+
+
+def _flatten_items(data: dict, parent: str = "") -> dict:
+ """Return a dict mapping dot-separated keys to their values."""
+ items = {}
+ for key, value in data.items():
+ full_key = f"{parent}::{key}" if parent else key
+ if isinstance(value, dict):
+ items.update(_flatten_items(value, full_key))
+ else:
+ items[full_key] = value
+ return items
+
+
+def _load_json(path: Path) -> dict:
+ with path.open(encoding="utf-8") as f:
+ return json.load(f)
+
+
+def _is_empty(value) -> bool:
+ return value is None or (isinstance(value, str) and not value.strip())
+
+
+def main():
+ """Check the translation files for errors."""
+
+ ignore_errors = "--ignore-errors" in sys.argv
+ errors = 0
+
+ base_data = _load_json(BASE_TRANSLATION)
+ base_items = _flatten_items(base_data)
+ base_keys = set(base_items.keys())
+
+ for file in TRANSLATIONS_DIR.glob("*.json"):
+ print(f"--- {file.name} ---")
+
+ translation_data = _load_json(file)
+ trans_items = _flatten_items(translation_data)
+ trans_keys = set(trans_items.keys())
+
+ missing = base_keys - trans_keys
+ orphaned = trans_keys - base_keys
+ empty_values = [k for k, v in trans_items.items() if _is_empty(v)]
+
+ if missing:
+ print(" Missing keys:", sorted(missing))
+ errors = errors + len(missing)
+
+ if orphaned:
+ print(" Orphaned keys:", sorted(orphaned))
+ errors = errors + len(orphaned)
+
+ if empty_values:
+ print(" Empty values:", sorted(empty_values))
+ errors = errors + len(empty_values)
+
+ print()
+
+ sys.exit(0) if ignore_errors else sys.exit(1 if errors > 0 else 0)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/test_sensor.py b/tests/test_sensor.py
index fbf51ab..4263839 100644
--- a/tests/test_sensor.py
+++ b/tests/test_sensor.py
@@ -6,7 +6,10 @@
from pytest_homeassistant_custom_component.common import MockConfigEntry
from custom_components.volvo_cars.const import (
+ OPT_ENERGY_CONSUMPTION_UNIT,
OPT_FUEL_CONSUMPTION_UNIT,
+ OPT_UNIT_ENERGY_KWH_PER_100KM,
+ OPT_UNIT_ENERGY_MILES_PER_KWH,
OPT_UNIT_LITER_PER_100KM,
OPT_UNIT_MPG_UK,
OPT_UNIT_MPG_US,
@@ -99,3 +102,48 @@ async def test_fuel_unit_conversion(
assert entity
assert entity.state == value
assert entity.attributes.get("unit_of_measurement") == unit_of_measurement
+
+
+@pytest.mark.parametrize(
+ ("entity_id", "unit", "value", "unit_of_measurement"),
+ [
+ (
+ "sensor.volvo_myvolvo_average_energy_consumption",
+ OPT_UNIT_ENERGY_KWH_PER_100KM,
+ "22.6",
+ "kWh/100 km",
+ ),
+ (
+ "sensor.volvo_myvolvo_average_energy_consumption",
+ OPT_UNIT_ENERGY_MILES_PER_KWH,
+ "2.7",
+ "mi/kWh",
+ ),
+ ],
+)
+@pytest.mark.use_model("xc40_bev")
+async def test_energy_unit_conversion(
+ hass: HomeAssistant,
+ enable_custom_integrations: None,
+ mock_config_entry: MockConfigEntry,
+ entity_id: str,
+ unit: str,
+ value: str,
+ unit_of_measurement: str,
+) -> None:
+ """Test energy unit conversion."""
+
+ with patch("custom_components.volvo_cars.PLATFORMS", [Platform.SENSOR]):
+ assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
+ await hass.async_block_till_done()
+
+ hass.config_entries.async_update_entry(
+ mock_config_entry,
+ options={OPT_ENERGY_CONSUMPTION_UNIT: unit},
+ )
+ await hass.async_block_till_done()
+
+ entity = hass.states.get(entity_id)
+ assert entity
+ assert entity.state == value
+ assert entity.attributes.get("unit_of_measurement") == unit_of_measurement