659 lines
32 KiB
Python
659 lines
32 KiB
Python
"""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,
|
||
),
|
||
),
|
||
}
|