Files
HomeAssistantVS/custom_components/maintenance_supporter/helpers/signatures/cars.py
T

659 lines
32 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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} <name>' → 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_<vin>_<tag>; 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_<registration>_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.<device_id>_<key>. 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.<pandora_id>_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_<attr>_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,
),
),
}