From a51b5cdba9f9d2bc34c8c8102fa4db454976bfc9 Mon Sep 17 00:00:00 2001
From: Thomas D <11554546+thomasddn@users.noreply.github.com>
Date: Tue, 4 Feb 2025 10:30:21 +0000
Subject: [PATCH 01/13] Add an option to set energy consumption unit (HAC-179)
---
custom_components/volvo_cars/config_flow.py | 100 ++++++++++++++++----
custom_components/volvo_cars/const.py | 5 +
custom_components/volvo_cars/sensor.py | 41 +++++++-
custom_components/volvo_cars/strings.json | 9 +-
tests/test_sensor.py | 48 ++++++++++
5 files changed, 179 insertions(+), 24 deletions(-)
diff --git a/custom_components/volvo_cars/config_flow.py b/custom_components/volvo_cars/config_flow.py
index bed8052..2110af8 100644
--- a/custom_components/volvo_cars/config_flow.py
+++ b/custom_components/volvo_cars/config_flow.py
@@ -27,6 +27,7 @@
SelectSelector,
SelectSelectorConfig,
)
+from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM
from .const import (
CONF_OTP,
@@ -35,9 +36,12 @@
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,
@@ -247,11 +251,39 @@ async def _async_create_or_update_entry(self) -> ConfigFlowResult:
data_updates=data,
)
+ 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,
+ },
)
@@ -304,26 +336,53 @@ async def async_step_init(
}
# 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 +392,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/sensor.py b/custom_components/volvo_cars/sensor.py
index a3b0f2f..f23f882 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,28 +157,34 @@ def _convert_fuel_consumption(
key="average_energy_consumption",
translation_key="average_energy_consumption",
api_field="averageEnergyConsumption",
- native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
+ native_unit_of_measurement="kWh/100 km",
device_class=SensorDeviceClass.ENERGY,
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,
+ native_unit_of_measurement="kWh/100 km",
device_class=SensorDeviceClass.ENERGY,
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,
+ native_unit_of_measurement="kWh/100 km",
device_class=SensorDeviceClass.ENERGY,
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",
diff --git a/custom_components/volvo_cars/strings.json b/custom_components/volvo_cars/strings.json
index 6d0c8ee..ba69a35 100644
--- a/custom_components/volvo_cars/strings.json
+++ b/custom_components/volvo_cars/strings.json
@@ -1196,7 +1196,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 +1206,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/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
From 8d3db5ac505afdd5c098cb5317f42108a99490b6 Mon Sep 17 00:00:00 2001
From: Thomas D <11554546+thomasddn@users.noreply.github.com>
Date: Tue, 4 Feb 2025 10:31:25 +0000
Subject: [PATCH 02/13] =?UTF-8?q?Use=20proper=20lang=20code=20for=20Norweg?=
=?UTF-8?q?ian=20Bokm=C3=A5l?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
custom_components/volvo_cars/translations/{no.json => nb.json} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename custom_components/volvo_cars/translations/{no.json => nb.json} (100%)
diff --git a/custom_components/volvo_cars/translations/no.json b/custom_components/volvo_cars/translations/nb.json
similarity index 100%
rename from custom_components/volvo_cars/translations/no.json
rename to custom_components/volvo_cars/translations/nb.json
From aa20b9393a27a323deed998df38bc849f4266abf Mon Sep 17 00:00:00 2001
From: Thomas D <11554546+thomasddn@users.noreply.github.com>
Date: Tue, 4 Feb 2025 13:23:01 +0000
Subject: [PATCH 03/13] Add sanity checks for VIN number (#83)
---
custom_components/volvo_cars/config_flow.py | 32 ++++++++++++++-------
custom_components/volvo_cars/strings.json | 3 +-
2 files changed, 24 insertions(+), 11 deletions(-)
diff --git a/custom_components/volvo_cars/config_flow.py b/custom_components/volvo_cars/config_flow.py
index 2110af8..11b49af 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
@@ -52,6 +53,7 @@
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:
@@ -95,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 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
+ 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,
},
)
diff --git a/custom_components/volvo_cars/strings.json b/custom_components/volvo_cars/strings.json
index ba69a35..5fb77bd 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 number is invalid"
},
"step": {
"otp": {
From e668520105df774ad63fad020d1ad81cbb1b6164 Mon Sep 17 00:00:00 2001
From: Thomas D <11554546+thomasddn@users.noreply.github.com>
Date: Tue, 4 Feb 2025 14:20:45 +0000
Subject: [PATCH 04/13] Allow empty device tracker picture
---
custom_components/volvo_cars/config_flow.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/custom_components/volvo_cars/config_flow.py b/custom_components/volvo_cars/config_flow.py
index 11b49af..5c58322 100644
--- a/custom_components/volvo_cars/config_flow.py
+++ b/custom_components/volvo_cars/config_flow.py
@@ -341,7 +341,8 @@ 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))
},
),
From e2106738058ad3c85a78de3640ca700eda38a134 Mon Sep 17 00:00:00 2001
From: Thomas D <11554546+thomasddn@users.noreply.github.com>
Date: Tue, 4 Feb 2025 14:43:04 +0000
Subject: [PATCH 05/13] Keep API key in sync
---
custom_components/volvo_cars/config_flow.py | 42 ++++++++++++---------
1 file changed, 25 insertions(+), 17 deletions(-)
diff --git a/custom_components/volvo_cars/config_flow.py b/custom_components/volvo_cars/config_flow.py
index 5c58322..c1e25d0 100644
--- a/custom_components/volvo_cars/config_flow.py
+++ b/custom_components/volvo_cars/config_flow.py
@@ -47,7 +47,7 @@
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
@@ -56,7 +56,7 @@
_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]
@@ -165,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 _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
schema = vol.Schema(
{
@@ -179,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,
},
)
@@ -239,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")
@@ -257,10 +254,21 @@ 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:
From ed21c8443220ac481b820ce2b5cea8e9ae1f4053 Mon Sep 17 00:00:00 2001
From: Thomas D <11554546+thomasddn@users.noreply.github.com>
Date: Tue, 4 Feb 2025 14:47:12 +0000
Subject: [PATCH 06/13] Set state class for average consumption
---
custom_components/volvo_cars/sensor.py | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/custom_components/volvo_cars/sensor.py b/custom_components/volvo_cars/sensor.py
index f23f882..9d897e5 100644
--- a/custom_components/volvo_cars/sensor.py
+++ b/custom_components/volvo_cars/sensor.py
@@ -158,7 +158,7 @@ def _convert_fuel_consumption(
translation_key="average_energy_consumption",
api_field="averageEnergyConsumption",
native_unit_of_measurement="kWh/100 km",
- device_class=SensorDeviceClass.ENERGY,
+ state_class=SensorStateClass.MEASUREMENT,
icon="mdi:car-electric",
available_fn=lambda vehicle: vehicle.has_battery_engine(),
unit_fn=_determine_energy_consumption_unit,
@@ -169,7 +169,7 @@ def _convert_fuel_consumption(
translation_key="average_energy_consumption_automatic",
api_field="averageEnergyConsumptionAutomatic",
native_unit_of_measurement="kWh/100 km",
- device_class=SensorDeviceClass.ENERGY,
+ state_class=SensorStateClass.MEASUREMENT,
icon="mdi:car-electric",
available_fn=lambda vehicle: vehicle.has_battery_engine(),
unit_fn=_determine_energy_consumption_unit,
@@ -180,7 +180,7 @@ def _convert_fuel_consumption(
translation_key="average_energy_consumption_charge",
api_field="averageEnergyConsumptionSinceCharge",
native_unit_of_measurement="kWh/100 km",
- device_class=SensorDeviceClass.ENERGY,
+ state_class=SensorStateClass.MEASUREMENT,
icon="mdi:car-electric",
available_fn=lambda vehicle: vehicle.has_battery_engine(),
unit_fn=_determine_energy_consumption_unit,
@@ -191,6 +191,7 @@ def _convert_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,
@@ -201,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,
From 7a2efba9ad83816fc0d3c305a37b04b389de5424 Mon Sep 17 00:00:00 2001
From: Thomas D <11554546+thomasddn@users.noreply.github.com>
Date: Tue, 4 Feb 2025 14:50:40 +0000
Subject: [PATCH 07/13] Grammar update
---
custom_components/volvo_cars/strings.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/custom_components/volvo_cars/strings.json b/custom_components/volvo_cars/strings.json
index 5fb77bd..40fd96a 100644
--- a/custom_components/volvo_cars/strings.json
+++ b/custom_components/volvo_cars/strings.json
@@ -32,7 +32,7 @@
},
"error": {
"invalid_auth": "Authentication failed",
- "invalid_vin": "VIN number is invalid"
+ "invalid_vin": "VIN is invalid"
},
"step": {
"otp": {
From 28ddb4fc1edd390945302ef3f13984e175514e23 Mon Sep 17 00:00:00 2001
From: Thomas D <11554546+thomasddn@users.noreply.github.com>
Date: Tue, 4 Feb 2025 15:10:35 +0000
Subject: [PATCH 08/13] Update readme
---
README.md | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 11fd340..331a1a5 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,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 +165,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. |
From 6af376d25005234471b14da548c769285dab3998 Mon Sep 17 00:00:00 2001
From: Thomas D <11554546+thomasddn@users.noreply.github.com>
Date: Fri, 7 Feb 2025 08:56:24 +0000
Subject: [PATCH 09/13] Add publish action
---
.github/workflows/publish.yml | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 .github/workflows/publish.yml
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..e69de29
From 47b0aa49b53131ef442c8587b3423f75273b50a9 Mon Sep 17 00:00:00 2001
From: Thomas D <11554546+thomasddn@users.noreply.github.com>
Date: Fri, 7 Feb 2025 10:08:47 +0000
Subject: [PATCH 10/13] Add publish action
---
.github/workflows/publish.yml | 40 ++++++++++++++++++++++
custom_components/volvo_cars/manifest.json | 2 +-
hacs.json | 4 ++-
3 files changed, 44 insertions(+), 2 deletions(-)
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/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/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
From 609e75983d462a8267da899a7f133b390e093884 Mon Sep 17 00:00:00 2001
From: Thomas D <11554546+thomasddn@users.noreply.github.com>
Date: Fri, 7 Feb 2025 11:59:55 +0100
Subject: [PATCH 11/13] Lokalise: updates
---
.../volvo_cars/translations/de.json | 10 +-
.../volvo_cars/translations/en.json | 10 +-
.../volvo_cars/translations/fr.json | 1258 +++++++++++++++++
.../volvo_cars/translations/nb.json | 12 +-
.../volvo_cars/translations/nl.json | 10 +-
.../volvo_cars/translations/pl.json | 10 +-
.../volvo_cars/translations/pt-BR.json | 10 +-
.../volvo_cars/translations/pt.json | 10 +-
.../volvo_cars/translations/sv.json | 24 +-
9 files changed, 1338 insertions(+), 16 deletions(-)
create mode 100644 custom_components/volvo_cars/translations/fr.json
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/nb.json b/custom_components/volvo_cars/translations/nb.json
index d98c123..f26a461 100644
--- a/custom_components/volvo_cars/translations/nb.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",
From d879c292ddccc91cc3f074bbeb42a0a50a5b017b Mon Sep 17 00:00:00 2001
From: Thomas D <11554546+thomasddn@users.noreply.github.com>
Date: Fri, 7 Feb 2025 15:33:44 +0000
Subject: [PATCH 12/13] Check translation files for errors
---
.github/workflows/ci.yml | 31 +++++++++++++++
scripts/__init__.py | 1 +
scripts/check_translations.py | 71 +++++++++++++++++++++++++++++++++++
3 files changed, 103 insertions(+)
create mode 100644 scripts/__init__.py
create mode 100644 scripts/check_translations.py
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/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()
From b17b90c0c668130795a8534aff2f4fe3d16011a0 Mon Sep 17 00:00:00 2001
From: Thomas D <11554546+thomasddn@users.noreply.github.com>
Date: Fri, 7 Feb 2025 15:34:51 +0000
Subject: [PATCH 13/13] Update readme
---
README.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/README.md b/README.md
index 331a1a5..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]
@@ -215,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