"""Cars and EVs — odometer-driven service duties. Data module of the suggested-setups signature catalog — see ``helpers/signatures/_model.py`` for the direction semantics and the method contract (every entry cites and is verified against the integration's source; drift-probed weekly).""" from __future__ import annotations from ._model import ConsumableSignature, IntegrationSignature from ._shared import ( ANNUAL_SERVICE_DISTANCE_TO_SERVICE, ANNUAL_SERVICE_MILEAGE, ANNUAL_SERVICE_ODOMETER, ANNUAL_SERVICE_TESLA_ODOMETER, BIKE_CHAIN_ODOMETER, BIKE_CHAIN_TOTAL_DISTANCE, BIKE_SERVICE_TOTAL_DISTANCE, TIRE_ROTATION_MILEAGE, TIRE_ROTATION_ODOMETER, TIRE_ROTATION_TESLA_ODOMETER, ) # Duties two integrations of this module name identically (same key, same # countdown) — one object each, so the verbatim-duplicate tripwire holds. # 'Service inspection distance': Audi Connect, Volkswagen (WeConnect) and PyCupra. _ANNUAL_SERVICE_INSPECTION_DISTANCE = ConsumableSignature( ("service_inspection_distance",), "Annual Service", "value_below", delta_units=1000 ) # 'Days to service': the unofficial Polestar API and Smart #1/#3. _ANNUAL_SERVICE_DAYS_TO_SERVICE = ConsumableSignature( ("days_to_service",), "Annual Service", "duration_left", below_hours=336 ) # The volkswagencarnet / pycupra dashboard instruments ('Service inspection # days', 'Oil inspection days' / 'distance') — one object per duty for both. _ANNUAL_SERVICE_INSPECTION_DAYS = ConsumableSignature( ("service_inspection_days",), "Annual Service", "duration_left", below_hours=336 ) _OIL_SERVICE_INSPECTION_DAYS = ConsumableSignature(("oil_inspection_days",), "Oil Service", "duration_left", below_hours=336) _OIL_SERVICE_INSPECTION_DISTANCE = ConsumableSignature( ("oil_inspection_distance",), "Oil Service", "value_below", delta_units=1000 ) SIGNATURES: dict[str, IntegrationSignature] = { "kia_uvo": IntegrationSignature( name="Hyundai / Kia Connect", verified="2026-07-18 @ Hyundai-Kia-Connect/kia_uvo master", source=( "Hyundai-Kia-Connect/kia_uvo custom_components/kia_uvo/sensor.py " "(translation_key 'odometer', DISTANCE, TOTAL_INCREASING, dynamic " "km/mi unit). next/last_service_distance exist but their semantics " "(target vs remaining) are unverified — odometer delta instead." ), tasks=( ANNUAL_SERVICE_ODOMETER, TIRE_ROTATION_ODOMETER, ), ), "tesla_custom": IntegrationSignature( name="Tesla (custom)", verified="2026-07-18 @ alandtse/tesla dev", source=( "alandtse/tesla custom_components/tesla_custom/sensor.py " "TeslaCarOdometer (type='odometer' → entity_id suffix, no " "translation_key; DISTANCE, TOTAL_INCREASING, native miles)." ), tasks=( ANNUAL_SERVICE_ODOMETER, TIRE_ROTATION_ODOMETER, ), ), "renault": IntegrationSignature( name="Renault", verified="2026-07-18 @ home-assistant/core dev", source=( "home-assistant/core homeassistant/components/renault/sensor.py " "(translation_key 'mileage', DISTANCE, TOTAL_INCREASING, km)." ), tasks=( ANNUAL_SERVICE_MILEAGE, TIRE_ROTATION_MILEAGE, ), ), "mbapi2020": IntegrationSignature( name="Mercedes-Benz", verified="2026-07-18 @ ReneNulschDE/mbapi2020 master", source=( "ReneNulschDE/mbapi2020 const.py SENSORS 'odometer' (name " "'Odometer' → entity_id suffix; attributes carry " "serviceintervaldays/distance) — lifetime km counter." ), tasks=( ANNUAL_SERVICE_ODOMETER, TIRE_ROTATION_ODOMETER, ), ), "vw_eu_data_act": IntegrationSignature( name="VW Group (EU Data Act)", verified="2026-07-18 @ mikrohard/hass-vw-eu-data-act main", source=( "mikrohard/hass-vw-eu-data-act data.py CuratedSensor('mileage', " "'Mileage', 'distance', 'km', 'total_increasing') — official EU " "Data Act portal data for VW/Audi/Škoda/SEAT/Cupra/Bentley (the " "unofficial WeConnect APIs were locked down upstream)." ), tasks=( ANNUAL_SERVICE_MILEAGE, TIRE_ROTATION_MILEAGE, ), ), "subaru": IntegrationSignature( name="Subaru", verified="2026-07-18 @ home-assistant/core dev", source=("home-assistant/core homeassistant/components/subaru/sensor.py (key sc.ODOMETER, translation_key 'odometer')."), tasks=( ANNUAL_SERVICE_ODOMETER, TIRE_ROTATION_ODOMETER, ), ), "volvo": IntegrationSignature( name="Volvo", verified="2026-07-18 @ home-assistant/core dev", source=("home-assistant/core homeassistant/components/volvo/sensor.py (key 'odometer', api_field 'odometer')."), tasks=( ANNUAL_SERVICE_ODOMETER, TIRE_ROTATION_ODOMETER, ), ), "polestar_api": IntegrationSignature( name="Polestar", verified="2026-07-19 @ pypolestar/polestar_api main sensor.py", source=( "HACS polestar_api: key 'current_odometer' (native METERS, " "suggested display KILOMETERS — the unit-aware threshold reads " "the display unit)." ), tasks=( ConsumableSignature(("current_odometer",), "Annual Service", "usage_delta", delta_units=15000), ConsumableSignature(("current_odometer",), "Tire Rotation", "usage_delta", delta_units=10000), ), ), "fordpass": IntegrationSignature( name="Ford (FordPass)", verified="2026-07-19 @ itchannel/fordpass-ha master sensor.py", source="HACS fordpass: dict-key 'odometer' sensor (name-style, suffix match).", tasks=( ANNUAL_SERVICE_ODOMETER, TIRE_ROTATION_ODOMETER, ), ), "toyota": IntegrationSignature( name="Toyota Connected", verified="2026-07-19 @ DurgNomis-drol/ha_toyota master sensor.py", source="HACS toyota: tk 'odometer', DISTANCE, TOTAL_INCREASING.", tasks=( ANNUAL_SERVICE_ODOMETER, TIRE_ROTATION_ODOMETER, ), ), "mg_saic": IntegrationSignature( name="MG/SAIC iSMART", verified="2026-09-25 @ townsmcp/mg-saic-ha main sensor.py (HACS default)", source=( "HACS mg_saic (repo moved ad-ha/mg-saic-ha → townsmcp/mg-saic-ha, " "hacs/default #9319; code identical): SAICMGMileageSensor " "'Mileage' (field 'mileage', KILOMETERS, total_increasing; name " "'{brand} {model} Mileage' → suffix _mileage; the sibling " "'Mileage Since Last Charge' does not end in _mileage — no clash)." ), tasks=( ANNUAL_SERVICE_MILEAGE, TIRE_ROTATION_MILEAGE, ), ), "myskoda": IntegrationSignature( name="Škoda (MySkoda)", verified="2026-07-19 @ skodaconnect/homeassistant-myskoda main sensor.py", source=( "HACS myskoda: tk 'mileage' (key 'milage', km, TOTAL_INCREASING); " "tk 'inspection' (DAYS) / 'inspection_in_km' (km) and " "'oil_service_in_days' / 'oil_service_in_km' — the car's own " "maintenance_report *_due_in countdowns (remaining until due). " "The countdown replaces a generic odometer service duty, so no " "editorial 15000 km interval here." ), tasks=( TIRE_ROTATION_MILEAGE, ConsumableSignature(("inspection",), "Annual Service", "duration_left", below_hours=336), ConsumableSignature(("inspection_in_km",), "Annual Service", "value_below", delta_units=1000), ConsumableSignature(("oil_service_in_days",), "Oil Service", "duration_left", below_hours=336), ConsumableSignature(("oil_service_in_km",), "Oil Service", "value_below", delta_units=1000), ), ), "audiconnect": IntegrationSignature( name="Audi Connect", verified="2026-07-19 @ audiconnect/audi_connect_ha master sensor.py", source=( "HACS audiconnect (name-derived entity ids, no translation_key): " "'Mileage' (km, TOTAL_INCREASING); 'Service inspection time' " "(days) / 'Service inspection distance' (km) and 'Oil change " "time' / 'Oil change distance' — VAG API inspectionDue_*/" "oilServiceDue_* remaining-until countdowns (audi_models.py). " "Countdowns replace the generic odometer service duty." ), tasks=( TIRE_ROTATION_MILEAGE, ConsumableSignature(("service_inspection_time",), "Annual Service", "duration_left", below_hours=336), _ANNUAL_SERVICE_INSPECTION_DISTANCE, ConsumableSignature(("oil_change_time",), "Oil Service", "duration_left", below_hours=336), ConsumableSignature(("oil_change_distance",), "Oil Service", "value_below", delta_units=1000), ), ), # The three core Tesla integrations share the same entity pattern: # translation_key = description key (entity.py `_attr_translation_key = # self.key`), odometer in native MILES — the unit-aware threshold # converts. Complements the HACS tesla_custom already covered. "tesla_fleet": IntegrationSignature( name="Tesla Fleet", verified="2026-07-20 @ home-assistant/core dev", source=("core tesla_fleet: key/tk 'vehicle_state_odometer' (TOTAL_INCREASING, MILES, DISTANCE)."), tasks=( ANNUAL_SERVICE_TESLA_ODOMETER, TIRE_ROTATION_TESLA_ODOMETER, ), ), "teslemetry": IntegrationSignature( name="Teslemetry", verified="2026-07-20 @ home-assistant/core dev", source=("core teslemetry: key/tk 'vehicle_state_odometer' (TOTAL_INCREASING, MILES, DISTANCE)."), tasks=( ANNUAL_SERVICE_TESLA_ODOMETER, TIRE_ROTATION_TESLA_ODOMETER, ), ), "tessie": IntegrationSignature( name="Tessie", verified="2026-07-20 @ home-assistant/core dev", source=("core tessie: key/tk 'vehicle_state_odometer' (TOTAL_INCREASING, MILES, DISTANCE)."), tasks=( ANNUAL_SERVICE_TESLA_ODOMETER, TIRE_ROTATION_TESLA_ODOMETER, ), ), "ituran": IntegrationSignature( name="Ituran", verified="2026-07-20 @ home-assistant/core dev", source=("core ituran: tk 'mileage' (KILOMETERS, DISTANCE) — fleet-tracker odometer."), tasks=( ANNUAL_SERVICE_MILEAGE, TIRE_ROTATION_MILEAGE, ), ), "starline": IntegrationSignature( name="StarLine", verified="2026-07-20 @ home-assistant/core dev", source=("core starline: tk 'mileage' (KILOMETERS, TOTAL_INCREASING) — alarm-system odometer."), tasks=( ANNUAL_SERVICE_MILEAGE, TIRE_ROTATION_MILEAGE, ), ), "bosch_ebike": IntegrationSignature( name="Bosch eBike", verified="2026-07-20 @ Phil-Barker/hass-bosch-ebike + marq24/ha-bosch-ebike-flow main (HACS default)", source=( "HACS bosch_ebike (both forks share the domain and the " "'total_distance' key/tk, KILOMETERS, TOTAL_INCREASING) — the " "eBike's odometer. Chain lubrication every ~250 km (bicycle " "maintenance standard) and a drivetrain service every ~2,000 km " "(Bosch eBike's service-interval guidance). Odometer delta " "re-baselines on completion, like the car duties." ), tasks=( BIKE_CHAIN_TOTAL_DISTANCE, BIKE_SERVICE_TOTAL_DISTANCE, ), ), "stromer": IntegrationSignature( name="Stromer eBike", verified="2026-07-20 @ CoMPaTech/stromer main sensor.py (HACS default)", source=( "HACS stromer: tk 'total_distance' (KILOMETERS, TOTAL_INCREASING) " "— the eBike's odometer. Same drivetrain duties as Bosch eBike." ), tasks=( BIKE_CHAIN_TOTAL_DISTANCE, BIKE_SERVICE_TOTAL_DISTANCE, ), ), # --- Round 14 (2026-09-25) -------------------------------------------- "stellantis_vehicles": IntegrationSignature( name="Stellantis (Peugeot/Citroën/DS/Opel/Fiat…)", verified="2026-09-25 @ andreadegiovine/homeassistant-stellantis-vehicles develop", source=( "HACS stellantis_vehicles const.py SENSORS_DEFAULT (sensor.py sets " "translation_key = key): 'mileage' (odometer.mileage, KILOMETERS, " "TOTAL_INCREASING), 'mileage_before_maintenance' (maintenance." "mileageBeforeMaintenance, KILOMETERS remaining) and " "'days_before_maintenance' (maintenance.daysBeforeMaintenance, " "DURATION DAYS remaining) — the car's own service countdowns, so " "no editorial 15000 km interval (myskoda precedent)." ), tasks=( TIRE_ROTATION_MILEAGE, ConsumableSignature(("days_before_maintenance",), "Annual Service", "duration_left", below_hours=336), ConsumableSignature(("mileage_before_maintenance",), "Annual Service", "value_below", delta_units=1000), ), ), "cardata": IntegrationSignature( name="BMW CarData (bmw-cardata-ha)", verified="2026-09-25 @ kvanbiesen/bmw-cardata-ha main", source=( "HACS cardata (not in the default store): descriptor " "'vehicle.vehicle.travelledDistance' titled 'Vehicle mileage' " "(descriptor_titles.py; entity.py names '{vehicle} {title}', no " "translation_key → suffix _vehicle_mileage); sensor.py forces " "TOTAL_INCREASING for DESC_TRAVELLED_DISTANCE; the unit is the " "stream's own (units.py normalises to km/mi) — the unit-aware " "threshold converts." ), tasks=( ConsumableSignature(("vehicle_mileage",), "Annual Service", "usage_delta", delta_units=15000), ConsumableSignature(("vehicle_mileage",), "Tire Rotation", "usage_delta", delta_units=10000), ), ), "bavariandata": IntegrationSignature( name="BavarianData (BMW CarData)", verified="2026-09-25 @ JustChr/BavarianData main", source=( "HACS bavariandata: catalogue descriptor 'vehicle.vehicle." "travelledDistance' (descriptor_metadata.py: distance, " "total_increasing, km) → keys.translation_key() = " "'vehicle_vehicle_travelleddistance' (entity.py). Cars streaming " "'vehicle.vehicle.mileage' instead get an uncatalogued name-only " "sensor without a fixed unit — deliberately not matched." ), tasks=( ConsumableSignature(("vehicle_vehicle_travelleddistance",), "Annual Service", "usage_delta", delta_units=15000), ConsumableSignature(("vehicle_vehicle_travelleddistance",), "Tire Rotation", "usage_delta", delta_units=10000), ), ), "uconnect": IntegrationSignature( name="Uconnect (Jeep/Fiat/Chrysler/Dodge/Ram/Alfa Romeo)", verified="2026-09-25 @ hass-uconnect/hass-uconnect main", source=( "HACS uconnect sensor.py (no translation_key; name '{make} " "{nickname|model} ' → entity_id suffix): 'Odometer' (key " "odometer, TOTAL_INCREASING, API unit km/mi), 'Distance to " "Service' (distanceToService, remaining), 'Days till service " "needed' (daysToService, DAYS remaining) and 'Oil Life' (key " "oil_level = py_uconnect oilLevel, PERCENTAGE remaining). The " "service countdowns replace the generic odometer service duty." ), tasks=( TIRE_ROTATION_ODOMETER, ConsumableSignature(("days_till_service_needed",), "Annual Service", "duration_left", below_hours=336), ANNUAL_SERVICE_DISTANCE_TO_SERVICE, ConsumableSignature(("oil_life",), "Oil Service", "percent_left"), ), ), "porscheconnect": IntegrationSignature( name="Porsche Connect", verified="2026-09-25 @ CJNE/ha-porscheconnect main", source=( "HACS porscheconnect sensor.py: tk 'mileage' (MILEAGE.kilometers, " "TOTAL_INCREASING), 'main_service_range'/'main_service_time' " "('Next service in', km resp. DAYS remaining) and " "'oil_service_range'/'oil_service_time' ('Next oil change in'). " "The car's own countdowns replace the generic odometer service " "duty; the intermediate-service pair is left out (no duty name)." ), tasks=( TIRE_ROTATION_MILEAGE, ConsumableSignature(("main_service_time",), "Annual Service", "duration_left", below_hours=336), ConsumableSignature(("main_service_range",), "Annual Service", "value_below", delta_units=1000), ConsumableSignature(("oil_service_time",), "Oil Service", "duration_left", below_hours=336), ConsumableSignature(("oil_service_range",), "Oil Service", "value_below", delta_units=1000), ), ), "nissan_connect": IntegrationSignature( name="Nissan Connect", verified="2026-09-25 @ dan-r/HomeAssistant-NissanConnect main", source=( "HACS nissan_connect sensor.py OdometerSensor (_attr_translation_key " "'odometer', DISTANCE, TOTAL_INCREASING, native KILOMETERS with " "suggested MILES for imperial accounts)." ), tasks=( ANNUAL_SERVICE_ODOMETER, TIRE_ROTATION_ODOMETER, ), ), "smartcar": IntegrationSignature( name="Smartcar", verified="2026-09-25 @ wbyoung/smartcar main", source=( "HACS smartcar sensor.py: 'Odometer' (key odometer, " "odometer-traveleddistance, DISTANCE, TOTAL_INCREASING, native km " "with imperial conversion; has_entity_name, no translation_key → " "suffix _odometer). 'Engine Oil Life' deliberately NOT matched: " "the API's lifeRemaining is passed through without the ×100 the " "sibling percent sensors apply — its scale is unverified." ), tasks=( ANNUAL_SERVICE_ODOMETER, TIRE_ROTATION_ODOMETER, ), ), "lucidmotors": IntegrationSignature( name="Lucid Motors", verified="2026-09-25 @ borski/ha-lucidmotors main", source=( "HACS lucidmotors sensor.py: key 'odometer_km' (state.chassis) with " "translation_key 'mileage', DISTANCE, KILOMETERS — the lifetime " "odometer (declared MEASUREMENT, but only ever grows)." ), tasks=( ANNUAL_SERVICE_MILEAGE, TIRE_ROTATION_MILEAGE, ), ), "abrp": IntegrationSignature( name="ABRP (A Better Routeplanner)", verified="2026-09-25 @ MichelFR/ha-abrp main", source=("HACS abrp sensor.py: tk 'odometer' (telemetry odometer_km, KILOMETERS, DISTANCE, TOTAL_INCREASING)."), tasks=( ANNUAL_SERVICE_ODOMETER, TIRE_ROTATION_ODOMETER, ), ), # Separate domain from pypolestar's polestar_api above. "polestar": IntegrationSignature( name="Unofficial Polestar", verified="2026-09-25 @ kildahldev/unofficial-polestar-api main", source=( "HACS polestar (kildahldev; domain 'polestar', not polestar_api) " "sensor.py, name-based ids (no translation_key): 'Odometer' " "(odometer_km, KILOMETERS, TOTAL_INCREASING), 'Distance to " "service' (health.distance_to_service_km, remaining) and 'Days to " "service' (health.days_to_service, unit 'd'). Countdowns replace " "the generic odometer service duty." ), tasks=( TIRE_ROTATION_ODOMETER, _ANNUAL_SERVICE_DAYS_TO_SERVICE, ANNUAL_SERVICE_DISTANCE_TO_SERVICE, ), ), "specialized_turbo": IntegrationSignature( name="Specialized Turbo eBike", verified="2026-09-25 @ home-assistant/core dev", source=( "core specialized_turbo sensor.py (new in 2026.9): tk 'odometer' " "(motor.odometer_km, KILOMETERS, DISTANCE, TOTAL_INCREASING). Same " "drivetrain duties as Bosch eBike." ), tasks=( BIKE_CHAIN_ODOMETER, ConsumableSignature(("odometer",), "Bike Service", "usage_delta", delta_units=2000), ), ), "cowboy": IntegrationSignature( name="Cowboy eBike", verified="2026-09-25 @ elsbrock/cowboy-ha main", source=( "HACS cowboy sensor.py: tk 'total_distance' (KILOMETERS, DISTANCE, " "TOTAL_INCREASING). Cowboy bikes run a carbon belt drive — no " "chain to lubricate, so only the drivetrain service interval." ), tasks=(BIKE_SERVICE_TOTAL_DISTANCE,), ), "ha_bosch_ebike": IntegrationSignature( name="Bosch eBike (Smart System & eBike System 2)", verified="2026-09-25 @ Xunil99/ha-bosch-ebike main", source=( "HACS ha_bosch_ebike (separate domain from bosch_ebike) sensor.py " "BoschServiceDueSensor: tk 'service_due_in_km' (km remaining to " "the dealer-set/overridden service odometer) and " "'service_due_in_days' (unit 'd', days remaining) — created for " "every bike, BES2 included. tk 'odometer' (km, driveUnit.odometer " "/ 1000) drives the chain duty; translation_keys_authoritative keeps " "the suffix fallback from also claiming next_service_odometer and " "last_ride_start_odometer (their own translation_keys)." ), translation_keys_authoritative=True, tasks=( ConsumableSignature(("service_due_in_days",), "Bike Service", "duration_left", below_hours=336), ConsumableSignature(("service_due_in_km",), "Bike Service", "value_below", delta_units=100), BIKE_CHAIN_ODOMETER, ), ), # ─── Round 15 part 2 (2026-09-27): HACS car integrations ───────────── "volkswagencarnet": IntegrationSignature( name="Volkswagen (WeConnect)", verified="2026-09-27 @ robinostlund/homeassistant-volkswagencarnet master (8b6524c) + volkswagencarnet v5.5.1", source=( "HACS volkswagencarnet sensor.py: has_entity_name, entity name = " "the library instrument name, no translation_key → entity-id " "suffixes. volkswagencarnet vw_dashboard.py instruments " "'Service inspection days' (unit 'd', DURATION; " "vw_vehicle.service_inspection = 'time left for service " "inspection') / 'Service inspection distance' (km left) and 'Oil " "inspection days' / 'Oil inspection distance' (combustion engines " "only) — the car's own countdowns, as for Audi and Škoda; " "'Odometer' (km, TOTAL_INCREASING) drives the tire rotation." ), tasks=( TIRE_ROTATION_ODOMETER, _ANNUAL_SERVICE_INSPECTION_DAYS, _ANNUAL_SERVICE_INSPECTION_DISTANCE, _OIL_SERVICE_INSPECTION_DAYS, _OIL_SERVICE_INSPECTION_DISTANCE, ), ), "smarthashtag": IntegrationSignature( name="Smart #1 / #3 (Hello Smart)", verified="2026-09-27 @ DasBasti/SmartHashtag main (cf8cd46)", source=( "HACS smarthashtag sensor_groups/maintenance.py (translation_key " "on every description): 'odometer' (km, TOTAL_INCREASING), " "'days_to_service' (DURATION, 'd') and 'distance_to_service' " "(km) — pysmarthashtag maintenance.daysToService / " "distanceToService, remaining. The service sensors are DISABLED " "by default and share the display name 'Service due in' (entity " "ids get _2/_3 suffixes) → matched by translation_key only " "(translation_keys_authoritative)." ), translation_keys_authoritative=True, tasks=( TIRE_ROTATION_ODOMETER, _ANNUAL_SERVICE_DAYS_TO_SERVICE, ANNUAL_SERVICE_DISTANCE_TO_SERVICE, ), ), "fordconnect_query": IntegrationSignature( name="FordConnect Query", verified="2026-09-27 @ marq24/ha-fordconnect-query main (27ef561)", source=( "HACS fordconnect_query: translation_key = the tag key (entity.py) " "and entity_id sensor.fcq__; const_tags.py 'oil' = " "metrics.oilLifeRemaining (PERCENTAGE, only created when the " "vehicle reports it — combustion engines) → Oil Service at the " "household floor; 'odometer' (km, TOTAL_INCREASING) → the " "FordPass pair (annual service + tire rotation)." ), translation_keys_authoritative=True, tasks=( ANNUAL_SERVICE_ODOMETER, TIRE_ROTATION_ODOMETER, ConsumableSignature(("oil",), "Oil Service", "percent_left"), ), ), # ─── Round 16 (2026-10-03): HACS cars, car alarms and the UK MOT ──────── "byd_vehicle": IntegrationSignature( name="BYD", verified="2026-10-03 @ jkaberg/hass-byd-vehicle main (0f0cf3f)", source=( "HACS byd_vehicle (custom repository) sensor.py BydSensor: _attr_translation_key = " "description.key on every sensor (every other platform sets one too). tk 'total_mileage' " "(realtime total_mileage, KILOMETERS, DISTANCE, TOTAL_INCREASING, enabled; en 'Odometer' → " "entity id …_odometer) — the lifetime odometer → the usual 15,000 km service / 10,000 km " "tire-rotation pair; no service countdown exists. translation_keys_authoritative keeps the " "suffix fallback off the twins 'total_mileage_v2' and 'energy_cumulative_total_mileage' " "(own keys, DIAGNOSTIC, disabled by default)." ), translation_keys_authoritative=True, tasks=( ConsumableSignature(("total_mileage",), "Annual Service", "usage_delta", delta_units=15000), ConsumableSignature(("total_mileage",), "Tire Rotation", "usage_delta", delta_units=10000), ), ), "dvla": IntegrationSignature( name="DVLA Vehicle Enquiry (UK)", verified="2026-10-03 @ jampez77/DVLA-Vehicle-Enquiry-Service main (739ebee)", source=( "HACS dvla sensor.py: 'motExpiryDate' ('MOT Expiry Date', device_class DATE) — entity_id " "forced to sensor.dvla__motexpirydate (lowercased), no translation_key, no " "has_entity_name, so the original name 'MOT Expiry Date' slugifies differently: both " "'motexpirydate' and 'mot_expiry_date' are keys. Always created: without an MOT date in " "the DVLA record (a car under three years old) update_from_coordinator falls back to the " "first-registration month + 3 years — the first MOT. → Roadworthiness Test (the car " "templates' duty) 30 days ahead; the record's next expiry date clears the trigger after " "the test. Skipped: 'taxDueDate' (vehicle tax, not maintenance) and 'motStatus' / " "'taxStatus' (status)." ), tasks=(ConsumableSignature(("motexpirydate", "mot_expiry_date"), "Roadworthiness Test", "due_date", days_before=30),), ), "pandora_cas": IntegrationSignature( name="Pandora Car Alarm System", verified="2026-10-03 @ alryaz/hass-pandora-cas master (ca2194d) + pandora-cas 0.0.16; turbulator/pandora-cas master (1392544)", source=( "Two HACS forks share the domain. alryaz/hass-pandora-cas: " "PandoraCASEntityDescription.__post_init__ sets translation_key = key on every " "description, entity_id sensor._. sensor.py 'mileage' ('Mileage', " "KILOMETERS, DISTANCE, state_class TOTAL — the alarm unit's odometer) → Tire Rotation; " "'days_to_maintenance' ('Days to Maintenance', DURATION, UnitOfTime.DAYS, DIAGNOSTIC; " "CurrentState.can_days_to_maintenance = the CAN bus's 'CAN_days_to_maintenance') → Annual " "Service two weeks ahead — the car's own countdown, as for Škoda and Stellantis (the entity " "is unavailable while the car's CAN bus does not report it, entity.py " "update_native_value). translation_keys_authoritative keeps 'can_mileage' / " "'can_mileage_by_battery' / 'can_mileage_to_empty' (own keys) off the mileage duty. " "turbulator/pandora-cas (older, no translation keys): ENTITY_CONFIGS 'mileage' → " "sensor._mileage (suffix match); it has no maintenance countdown. Skipped: " "'remaining_engine_runtime' (the remote-start timer)." ), translation_keys_authoritative=True, tasks=( TIRE_ROTATION_MILEAGE, ConsumableSignature(("days_to_maintenance",), "Annual Service", "duration_left", below_hours=336), ), ), "vag_connect": IntegrationSignature( name="VW Group Connect (vag_connect)", verified="2026-10-03 @ its-me-prash/vwgroup-connect-ha main (3f18940)", source=( "HACS vag_connect (Volkswagen / Audi / Škoda / SEAT / CUPRA / Porsche incl. North America) " "sensor.py SENSOR_DESCRIPTIONS: every description sets translation_key (= key); data " "sensors spawn only once their value arrives (hide_empty_entities, default on). " "'service_due_in_days' (unit 'd', days remaining) and 'service_km' (KILOMETERS remaining — " "vw_eu inspectionDue_km, skoda inspectionDueInKm, porsche MAIN_SERVICE_RANGE) → Annual " "Service; 'oil_service_due_in_days' / 'oil_service_km' (condition 'combustion') → Oil " "Service — the car's own countdowns replace the editorial odometer service, as for Škoda " "and Audi; 'odometer_km' (KILOMETERS, TOTAL_INCREASING) → Tire Rotation. " "translation_keys_authoritative plus the longer-key guard keep the 'oil_service_*' entities " "off the 'service_*' keys. Skipped: the DATE twins 'service_due_at' / 'oil_service_at' " "(the same countdowns as dates) and 'reminder_technical_inspection' (date-or-status mix)." ), translation_keys_authoritative=True, tasks=( ConsumableSignature(("odometer_km",), "Tire Rotation", "usage_delta", delta_units=10000), ConsumableSignature(("service_due_in_days",), "Annual Service", "duration_left", below_hours=336), ConsumableSignature(("service_km",), "Annual Service", "value_below", delta_units=1000), ConsumableSignature(("oil_service_due_in_days",), "Oil Service", "duration_left", below_hours=336), ConsumableSignature(("oil_service_km",), "Oil Service", "value_below", delta_units=1000), ), ), "pycupra": IntegrationSignature( name="CUPRA / SEAT (PyCupra)", verified="2026-10-03 @ WulfgarW/homeassistant-pycupra main (6187999) + pycupra 0.2.37", source=( "HACS pycupra: entity name = f'{vehicle_name} {instrument.name}' (no has_entity_name, no " "translation_key → entity-id suffixes). The instruments come from the pinned pycupra " "library (>=0.2.36; 0.2.37 dashboard.py) and exist only where the car supports them " "(Instrument.setup → is__supported): 'Odometer' (km), 'Service inspection days' " "(unit 'd', maintenance.inspectionDueDays — 'time left until service inspection'), " "'Service inspection distance' (km, inspectionDueKm), 'Oil inspection days' / 'Oil " "inspection distance' (oilServiceDueDays / oilServiceDueKm, only where reported) — the " "volkswagencarnet instruments, names and semantics, so the same duty objects." ), tasks=( TIRE_ROTATION_ODOMETER, _ANNUAL_SERVICE_INSPECTION_DAYS, _ANNUAL_SERVICE_INSPECTION_DISTANCE, _OIL_SERVICE_INSPECTION_DAYS, _OIL_SERVICE_INSPECTION_DISTANCE, ), ), }