From 111537504926a5df2e3de951eec170fb09fa734f Mon Sep 17 00:00:00 2001 From: Giovanni Condello Date: Sun, 18 Oct 2026 18:49:33 +0200 Subject: [PATCH] feat: gate HA entities on the vehicle model configuration Publish door, boot, tyre pressure and interior temperature entities only when the vehicle model configuration declares them (DOOR, BOOT, J17, INTEMP), and unpublish them otherwise. This removes the rear door entities on 2-door vehicles such as the MG Cyberster (DOOR=1100). Read S35 as an integer when detecting a sunroof: the value 2 alone does not denote a sunroof. WINDOW, BONNUT, BATTERY and EXTEMP are deliberately ignored: real vehicles report 0 for them while still sending the data (e.g. the MG4 reports WINDOW=0000). Add 18 real model configurations as test fixtures. --- src/integrations/home_assistant/discovery.py | 122 +- src/vehicle_info.py | 49 +- .../vehicle_model_configurations.json | 2601 +++++++++++++++++ tests/test_ha_discovery_feature_gating.py | 105 + tests/test_vehicle_info_features.py | 103 + 5 files changed, 2914 insertions(+), 66 deletions(-) create mode 100644 tests/fixtures/vehicle_model_configurations.json create mode 100644 tests/test_ha_discovery_feature_gating.py create mode 100644 tests/test_vehicle_info_features.py diff --git a/src/integrations/home_assistant/discovery.py b/src/integrations/home_assistant/discovery.py index 0fa3e51..5c53cc5 100644 --- a/src/integrations/home_assistant/discovery.py +++ b/src/integrations/home_assistant/discovery.py @@ -503,6 +503,9 @@ def __publish_location_sensors(self) -> None: ) def __publish_windows_sensors(self) -> None: + # Window sensors are always published. The WINDOW model configuration code + # cannot be used to gate them: e.g. the MG4 reports WINDOW=0000 while sending + # state for all 4 windows. self.__unpublish_ha_discovery_message("switch", "Window driver") self.__unpublish_ha_discovery_message("switch", "Window passenger") self.__unpublish_ha_discovery_message("switch", "Window rear left") @@ -604,34 +607,23 @@ def __publish_lights_sensors(self) -> None: ) def __publish_tyres_sensors(self) -> None: - self._publish_sensor( - mqtt_topics.TYRES_FRONT_LEFT_PRESSURE, - "Tyres front left pressure", - device_class="pressure", - unit_of_measurement="bar", - icon="mdi:tire", - ) - self._publish_sensor( - mqtt_topics.TYRES_FRONT_RIGHT_PRESSURE, - "Tyres front right pressure", - device_class="pressure", - unit_of_measurement="bar", - icon="mdi:tire", - ) - self._publish_sensor( - mqtt_topics.TYRES_REAR_LEFT_PRESSURE, - "Tyres rear left pressure", - device_class="pressure", - unit_of_measurement="bar", - icon="mdi:tire", - ) - self._publish_sensor( - mqtt_topics.TYRES_REAR_RIGHT_PRESSURE, - "Tyres rear right pressure", - device_class="pressure", - unit_of_measurement="bar", - icon="mdi:tire", - ) + tyres = [ + (mqtt_topics.TYRES_FRONT_LEFT_PRESSURE, "Tyres front left pressure"), + (mqtt_topics.TYRES_FRONT_RIGHT_PRESSURE, "Tyres front right pressure"), + (mqtt_topics.TYRES_REAR_LEFT_PRESSURE, "Tyres rear left pressure"), + (mqtt_topics.TYRES_REAR_RIGHT_PRESSURE, "Tyres rear right pressure"), + ] + for topic, name in tyres: + if self.__vin_info.has_tyre_pressure: + self._publish_sensor( + topic, + name, + device_class="pressure", + unit_of_measurement="bar", + icon="mdi:tire", + ) + else: + self.__unpublish_ha_discovery_message("sensor", name) def __publish_doors_sensors(self) -> None: self._publish_lock( @@ -644,36 +636,33 @@ def __publish_doors_sensors(self) -> None: state_locked="False", state_unlocked="True", ) - self._publish_binary_sensor( - mqtt_topics.DOORS_DRIVER, - "Door driver", - device_class="door", - icon="mdi:car-door", - ) - self._publish_binary_sensor( - mqtt_topics.DOORS_PASSENGER, - "Door passenger", - device_class="door", - icon="mdi:car-door", - ) - self._publish_binary_sensor( - mqtt_topics.DOORS_REAR_LEFT, - "Door rear left", - device_class="door", - icon="mdi:car-door", - ) - self._publish_binary_sensor( - mqtt_topics.DOORS_REAR_RIGHT, - "Door rear right", - device_class="door", - icon="mdi:car-door", - ) - self._publish_binary_sensor( - mqtt_topics.DOORS_BONNET, "Bonnet", device_class="door", icon="mdi:car-door" - ) - self._publish_binary_sensor( - mqtt_topics.DOORS_BOOT, "Boot", device_class="door", icon="mdi:car-door" - ) + doors = [ + (mqtt_topics.DOORS_DRIVER, "Door driver", self.__vin_info.has_driver_door), + ( + mqtt_topics.DOORS_PASSENGER, + "Door passenger", + self.__vin_info.has_passenger_door, + ), + ( + mqtt_topics.DOORS_REAR_LEFT, + "Door rear left", + self.__vin_info.has_rear_left_door, + ), + ( + mqtt_topics.DOORS_REAR_RIGHT, + "Door rear right", + self.__vin_info.has_rear_right_door, + ), + (mqtt_topics.DOORS_BONNET, "Bonnet", True), + (mqtt_topics.DOORS_BOOT, "Boot", self.__vin_info.has_boot_status), + ] + for topic, name, supported in doors: + if supported: + self._publish_binary_sensor( + topic, name, device_class="door", icon="mdi:car-door" + ) + else: + self.__unpublish_ha_discovery_message("binary_sensor", name) def __publish_gateway_sensors(self) -> None: options = [m.value for m in RefreshMode] @@ -793,13 +782,16 @@ def __publish_climate_sensors(self) -> None: self.__publish_remote_ac() self.__publish_heated_seats() self.__publish_back_window_heat() - self._publish_sensor( - mqtt_topics.CLIMATE_INTERIOR_TEMPERATURE, - "Interior temperature", - device_class="temperature", - state_class="measurement", - unit_of_measurement="°C", - ) + if self.__vin_info.has_interior_temperature: + self._publish_sensor( + mqtt_topics.CLIMATE_INTERIOR_TEMPERATURE, + "Interior temperature", + device_class="temperature", + state_class="measurement", + unit_of_measurement="°C", + ) + else: + self.__unpublish_ha_discovery_message("sensor", "Interior temperature") self._publish_sensor( mqtt_topics.CLIMATE_EXTERIOR_TEMPERATURE, "Exterior temperature", diff --git a/src/vehicle_info.py b/src/vehicle_info.py index e98ffbb..73e58a2 100644 --- a/src/vehicle_info.py +++ b/src/vehicle_info.py @@ -78,6 +78,21 @@ def __get_property_by_code(self, property_name: str) -> str | None: normalized_property_name = str(property_name).strip().upper() return self.__properties_by_code.get(normalized_property_name) + def __get_int_property_by_code(self, property_name: str) -> int: + # Absent or non-numeric codes read as 0, i.e. "feature not available" + try: + return int(self.__get_property_by_code(property_name) or 0) + except ValueError: + return 0 + + def __is_feature_enabled(self, property_name: str) -> bool: + return self.__get_int_property_by_code(property_name) != 0 + + def __get_mask_by_code(self, property_name: str, index: int) -> bool: + # 4-char positional mask: driver, passenger, rear left, rear right + mask = self.__get_property_by_code(property_name) or "" + return len(mask) == 4 and mask[index] == "1" + @property def is_ev(self) -> bool: return not self.series.startswith("ZP22") @@ -88,7 +103,39 @@ def has_fossil_fuel(self) -> bool: @property def has_sunroof(self) -> bool: - return self.__get_property_by_code("S35") != "0" + # The value 2 alone does not denote a sunroof + return self.__get_int_property_by_code("S35") & ~2 != 0 + + @property + def has_driver_door(self) -> bool: + return self.__get_mask_by_code("DOOR", 0) + + @property + def has_passenger_door(self) -> bool: + return self.__get_mask_by_code("DOOR", 1) + + @property + def has_rear_left_door(self) -> bool: + return self.__get_mask_by_code("DOOR", 2) + + @property + def has_rear_right_door(self) -> bool: + return self.__get_mask_by_code("DOOR", 3) + + # WINDOW, BONNUT, BATTERY and EXTEMP are deliberately not used: real vehicles + # report 0 for them while still sending the data (e.g. WINDOW=0000 on the MG4) + + @property + def has_boot_status(self) -> bool: + return self.__is_feature_enabled("BOOT") + + @property + def has_tyre_pressure(self) -> bool: + return self.__is_feature_enabled("J17") + + @property + def has_interior_temperature(self) -> bool: + return self.__is_feature_enabled("INTEMP") @property def has_on_off_heated_seats(self) -> bool: diff --git a/tests/fixtures/vehicle_model_configurations.json b/tests/fixtures/vehicle_model_configurations.json new file mode 100644 index 0000000..13ee103 --- /dev/null +++ b/tests/fixtures/vehicle_model_configurations.json @@ -0,0 +1,2601 @@ +[ + { + "model": "ZS EV", + "series": "ZS EV S", + "model_year": "2021", + "source": "https://github.com/townsmcp/mg-saic-ha/issues/100", + "configuration": [ + { + "itemCode": "J17", + "itemName": "Tire", + "itemValue": "1" + }, + { + "itemCode": "Q00", + "itemName": "Airbag", + "itemValue": "1" + }, + { + "itemCode": "Q01", + "itemName": "FAirbag", + "itemValue": "0" + }, + { + "itemCode": "Q09", + "itemName": "AirbagS", + "itemValue": "0" + }, + { + "itemCode": "S35", + "itemName": "Sunroof", + "itemValue": "3" + }, + { + "itemCode": "S61", + "itemName": "Remote control", + "itemValue": "1" + }, + { + "itemCode": "T11", + "itemName": "AC", + "itemValue": "1" + }, + { + "itemCode": "EPS", + "itemName": "EPS", + "itemValue": "1" + }, + { + "itemCode": "SA64", + "itemName": "SA64", + "itemValue": "0111110000000000001000000100101000000010100000000000000000000110" + }, + { + "itemCode": "BONNUT", + "itemName": "BONNUT", + "itemValue": "1" + }, + { + "itemCode": "DOOR", + "itemName": "DOOR", + "itemValue": "1111" + }, + { + "itemCode": "BOOT", + "itemName": "BOOT", + "itemValue": "1" + }, + { + "itemCode": "ENGINE", + "itemName": "ENGINE", + "itemValue": "1" + }, + { + "itemCode": "EV", + "itemName": "EV", + "itemValue": "0" + }, + { + "itemCode": "HeatedSeat", + "itemName": "HeatedSeat", + "itemValue": "0" + }, + { + "itemCode": "KEYPOS", + "itemName": "KEYPOS", + "itemValue": "1" + }, + { + "itemCode": "ENERGY", + "itemName": "ENERGY", + "itemValue": "1" + }, + { + "itemCode": "BATTERY", + "itemName": "BATTERY", + "itemValue": "1" + }, + { + "itemCode": "INTEMP", + "itemName": "INTEMP", + "itemValue": "1" + }, + { + "itemCode": "EXTEMP", + "itemName": "EXTEMP", + "itemValue": "1" + }, + { + "itemCode": "WINDOW", + "itemName": "WINDOW", + "itemValue": "0000" + }, + { + "itemCode": "LRD", + "itemName": "LRD", + "itemValue": "0" + }, + { + "itemCode": "BTKEY", + "itemName": "BTKEY", + "itemValue": "1" + }, + { + "itemCode": "BType", + "itemName": "Battery", + "itemValue": "1" + }, + { + "itemCode": "SBH", + "itemName": "SBH", + "itemValue": "1" + }, + { + "itemCode": "BHI", + "itemName": "BHI", + "itemValue": "0" + }, + { + "itemCode": "ROV", + "itemName": "ROV", + "itemValue": "25857" + } + ] + }, + { + "model": "EH32 X7", + "series": "EH32 S", + "model_year": "2023", + "source": "https://github.com/townsmcp/mg-saic-ha/issues/137", + "configuration": [ + { + "itemCode": "J17", + "itemName": "Tire", + "itemValue": "1" + }, + { + "itemCode": "Q00", + "itemName": "Airbag", + "itemValue": "1" + }, + { + "itemCode": "Q01", + "itemName": "FAirbag", + "itemValue": "1" + }, + { + "itemCode": "Q09", + "itemName": "AirbagS", + "itemValue": "1" + }, + { + "itemCode": "S35", + "itemName": "Sunroof", + "itemValue": "0" + }, + { + "itemCode": "S61", + "itemName": "Remote control", + "itemValue": "1" + }, + { + "itemCode": "T11", + "itemName": "AC", + "itemValue": "1" + }, + { + "itemCode": "EPS", + "itemName": "EPS", + "itemValue": "1" + }, + { + "itemCode": "SA64", + "itemName": "SA64", + "itemValue": "0111110000000000001000000100101000000010100000000000000000000110" + }, + { + "itemCode": "BONNUT", + "itemName": "BONNUT", + "itemValue": "1" + }, + { + "itemCode": "DOOR", + "itemName": "DOOR", + "itemValue": "1111" + }, + { + "itemCode": "BOOT", + "itemName": "BOOT", + "itemValue": "1" + }, + { + "itemCode": "ENGINE", + "itemName": "ENGINE", + "itemValue": "1" + }, + { + "itemCode": "EV", + "itemName": "EV", + "itemValue": "0" + }, + { + "itemCode": "HeatedSeat", + "itemName": "HeatedSeat", + "itemValue": "1" + }, + { + "itemCode": "KEYPOS", + "itemName": "KEYPOS", + "itemValue": "0" + }, + { + "itemCode": "ENERGY", + "itemName": "ENERGY", + "itemValue": "1" + }, + { + "itemCode": "BATTERY", + "itemName": "BATTERY", + "itemValue": "1" + }, + { + "itemCode": "INTEMP", + "itemName": "INTEMP", + "itemValue": "1" + }, + { + "itemCode": "EXTEMP", + "itemName": "EXTEMP", + "itemValue": "1" + }, + { + "itemCode": "WINDOW", + "itemName": "WINDOW", + "itemValue": "0000" + }, + { + "itemCode": "LRD", + "itemName": "LRD", + "itemValue": "0" + }, + { + "itemCode": "BTKEY", + "itemName": "BTKEY", + "itemValue": "1" + }, + { + "itemCode": "BType", + "itemName": "Battery", + "itemValue": "1" + }, + { + "itemCode": "SBH", + "itemName": "SBH", + "itemValue": "1" + }, + { + "itemCode": "BHI", + "itemName": "BHI", + "itemValue": "1" + }, + { + "itemCode": "ROV", + "itemName": "ROV", + "itemValue": "25857" + } + ] + }, + { + "model": "Cyberster", + "series": "EC32", + "model_year": "2024", + "source": "https://github.com/townsmcp/mg-saic-ha/issues/14", + "configuration": [ + { + "itemCode": "J17", + "itemName": "Tire", + "itemValue": "1" + }, + { + "itemCode": "Q00", + "itemName": "Airbag", + "itemValue": "1" + }, + { + "itemCode": "Q01", + "itemName": "FAirbag", + "itemValue": "1" + }, + { + "itemCode": "Q09", + "itemName": "AirbagS", + "itemValue": "1" + }, + { + "itemCode": "S35", + "itemName": "Sunroof", + "itemValue": "0" + }, + { + "itemCode": "S61", + "itemName": "Remote control", + "itemValue": "1" + }, + { + "itemCode": "T11", + "itemName": "AC", + "itemValue": "1" + }, + { + "itemCode": "EPS", + "itemName": "EPS", + "itemValue": "1" + }, + { + "itemCode": "SA64", + "itemName": "SA64", + "itemValue": "0111110000000000001000000100101000000010100000000000000000000110" + }, + { + "itemCode": "BONNUT", + "itemName": "BONNUT", + "itemValue": "1" + }, + { + "itemCode": "DOOR", + "itemName": "DOOR", + "itemValue": "1100" + }, + { + "itemCode": "BOOT", + "itemName": "BOOT", + "itemValue": "1" + }, + { + "itemCode": "ENGINE", + "itemName": "ENGINE", + "itemValue": "1" + }, + { + "itemCode": "EV", + "itemName": "EV", + "itemValue": "0" + }, + { + "itemCode": "HeatedSeat", + "itemName": "HeatedSeat", + "itemValue": "1" + }, + { + "itemCode": "KEYPOS", + "itemName": "KEYPOS", + "itemValue": "1" + }, + { + "itemCode": "ENERGY", + "itemName": "ENERGY", + "itemValue": "1" + }, + { + "itemCode": "BATTERY", + "itemName": "BATTERY", + "itemValue": "1" + }, + { + "itemCode": "INTEMP", + "itemName": "INTEMP", + "itemValue": "1" + }, + { + "itemCode": "EXTEMP", + "itemName": "EXTEMP", + "itemValue": "1" + }, + { + "itemCode": "WINDOW", + "itemName": "WINDOW", + "itemValue": "0000" + }, + { + "itemCode": "LRD", + "itemName": "LRD", + "itemValue": "1" + }, + { + "itemCode": "BTKEY", + "itemName": "BTKEY", + "itemValue": "0" + }, + { + "itemCode": "BType", + "itemName": "Battery", + "itemValue": "1" + }, + { + "itemCode": "SBH", + "itemName": "SBH", + "itemValue": "1" + }, + { + "itemCode": "BHI", + "itemName": "BHI", + "itemValue": "1" + }, + { + "itemCode": "ROV", + "itemName": "ROV", + "itemValue": "25858" + } + ] + }, + { + "model": "EH32 X7", + "series": "EH32 L", + "model_year": "2023", + "source": "https://github.com/townsmcp/mg-saic-ha/issues/151", + "configuration": [ + { + "itemCode": "J17", + "itemName": "Tire", + "itemValue": "1" + }, + { + "itemCode": "Q00", + "itemName": "Airbag", + "itemValue": "1" + }, + { + "itemCode": "Q01", + "itemName": "FAirbag", + "itemValue": "1" + }, + { + "itemCode": "Q09", + "itemName": "AirbagS", + "itemValue": "1" + }, + { + "itemCode": "S35", + "itemName": "Sunroof", + "itemValue": "0" + }, + { + "itemCode": "S61", + "itemName": "Remote control", + "itemValue": "1" + }, + { + "itemCode": "T11", + "itemName": "AC", + "itemValue": "1" + }, + { + "itemCode": "EPS", + "itemName": "EPS", + "itemValue": "1" + }, + { + "itemCode": "SA64", + "itemName": "SA64", + "itemValue": "0111110000000000001000000100101000000010100000000000000000000110" + }, + { + "itemCode": "BONNUT", + "itemName": "BONNUT", + "itemValue": "1" + }, + { + "itemCode": "DOOR", + "itemName": "DOOR", + "itemValue": "1111" + }, + { + "itemCode": "BOOT", + "itemName": "BOOT", + "itemValue": "1" + }, + { + "itemCode": "ENGINE", + "itemName": "ENGINE", + "itemValue": "1" + }, + { + "itemCode": "EV", + "itemName": "EV", + "itemValue": "0" + }, + { + "itemCode": "HeatedSeat", + "itemName": "HeatedSeat", + "itemValue": "0" + }, + { + "itemCode": "KEYPOS", + "itemName": "KEYPOS", + "itemValue": "0" + }, + { + "itemCode": "ENERGY", + "itemName": "ENERGY", + "itemValue": "1" + }, + { + "itemCode": "BATTERY", + "itemName": "BATTERY", + "itemValue": "1" + }, + { + "itemCode": "INTEMP", + "itemName": "INTEMP", + "itemValue": "1" + }, + { + "itemCode": "EXTEMP", + "itemName": "EXTEMP", + "itemValue": "1" + }, + { + "itemCode": "WINDOW", + "itemName": "WINDOW", + "itemValue": "0000" + }, + { + "itemCode": "LRD", + "itemName": "LRD", + "itemValue": "0" + }, + { + "itemCode": "BTKEY", + "itemName": "BTKEY", + "itemValue": "1" + }, + { + "itemCode": "BType", + "itemName": "Battery", + "itemValue": "2" + }, + { + "itemCode": "SBH", + "itemName": "SBH", + "itemValue": "1" + }, + { + "itemCode": "BHI", + "itemName": "BHI", + "itemValue": "1" + }, + { + "itemCode": "ROV", + "itemName": "ROV", + "itemValue": "25857" + } + ] + }, + { + "model": "HS Super Hybrid", + "series": "AS33P S", + "model_year": "2025", + "source": "https://github.com/townsmcp/mg-saic-ha/issues/198", + "configuration": [ + { + "itemCode": "J17", + "itemName": "Tire", + "itemValue": "1" + }, + { + "itemCode": "Q00", + "itemName": "Airbag", + "itemValue": "0" + }, + { + "itemCode": "S35", + "itemName": "Sunroof", + "itemValue": "0" + }, + { + "itemCode": "S61", + "itemName": "Remote control", + "itemValue": "1" + }, + { + "itemCode": "T11", + "itemName": "AC", + "itemValue": "1" + }, + { + "itemCode": "EPS", + "itemName": "EPS", + "itemValue": "0" + }, + { + "itemCode": "SA64", + "itemName": "SA64", + "itemValue": "0111110000000000001000000100101000000010100000000000000000000110" + }, + { + "itemCode": "BONNUT", + "itemName": "BONNUT", + "itemValue": "1" + }, + { + "itemCode": "DOOR", + "itemName": "DOOR", + "itemValue": "1111" + }, + { + "itemCode": "BOOT", + "itemName": "BOOT", + "itemValue": "1" + }, + { + "itemCode": "ENGINE", + "itemName": "ENGINE", + "itemValue": "0" + }, + { + "itemCode": "HeatedSeat", + "itemName": "HeatedSeat", + "itemValue": "1" + }, + { + "itemCode": "HEATEDSEAT2", + "itemName": "HEATEDSEAT2", + "itemValue": "0" + }, + { + "itemCode": "KEYPOS", + "itemName": "KEYPOS", + "itemValue": "0" + }, + { + "itemCode": "ENERGY", + "itemName": "ENERGY", + "itemValue": "1" + }, + { + "itemCode": "BATTERY", + "itemName": "BATTERY", + "itemValue": "0" + }, + { + "itemCode": "INTEMP", + "itemName": "INTEMP", + "itemValue": "1" + }, + { + "itemCode": "EXTEMP", + "itemName": "EXTEMP", + "itemValue": "0" + }, + { + "itemCode": "WINDOW", + "itemName": "WINDOW", + "itemValue": "1111" + }, + { + "itemCode": "LRD", + "itemName": "LRD", + "itemValue": "1" + }, + { + "itemCode": "BTKEY", + "itemName": "BTKEY", + "itemValue": "0" + }, + { + "itemCode": "BTKNOTIFY", + "itemName": "BTKNOTIFY", + "itemValue": "1" + }, + { + "itemCode": "BType", + "itemName": "Battery", + "itemValue": "2" + }, + { + "itemCode": "SBH", + "itemName": "SBH", + "itemValue": "1" + }, + { + "itemCode": "BHI", + "itemName": "BHI", + "itemValue": "1" + }, + { + "itemCode": "TR", + "itemName": "TR", + "itemValue": "3" + }, + { + "itemCode": "SWH", + "itemName": "SWH", + "itemValue": "0" + }, + { + "itemCode": "UB", + "itemName": "UB", + "itemValue": "0" + }, + { + "itemCode": "SS", + "itemName": "SS", + "itemValue": "0" + }, + { + "itemCode": "SC", + "itemName": "SC", + "itemValue": "5" + }, + { + "itemCode": "ROV", + "itemName": "ROV", + "itemValue": "25858" + } + ] + }, + { + "model": "MG S9 PHEV", + "series": "xxxxx L", + "model_year": "2025", + "source": "https://github.com/townsmcp/mg-saic-ha/issues/204", + "configuration": [ + { + "itemCode": "J17", + "itemName": "Tire", + "itemValue": "1" + }, + { + "itemCode": "Q00", + "itemName": "Airbag", + "itemValue": "1" + }, + { + "itemCode": "Q01", + "itemName": "FAirbag", + "itemValue": "1" + }, + { + "itemCode": "Q09", + "itemName": "AirbagS", + "itemValue": "1" + }, + { + "itemCode": "S35", + "itemName": "Sunroof", + "itemValue": "3" + }, + { + "itemCode": "S61", + "itemName": "Remote control", + "itemValue": "1" + }, + { + "itemCode": "T11", + "itemName": "AC", + "itemValue": "1" + }, + { + "itemCode": "EPS", + "itemName": "EPS", + "itemValue": "1" + }, + { + "itemCode": "BONNUT", + "itemName": "BONNUT", + "itemValue": "1" + }, + { + "itemCode": "DOOR", + "itemName": "DOOR", + "itemValue": "1111" + }, + { + "itemCode": "BOOT", + "itemName": "BOOT", + "itemValue": "1" + }, + { + "itemCode": "ENGINE", + "itemName": "ENGINE", + "itemValue": "0" + }, + { + "itemCode": "HeatedSeat", + "itemName": "HeatedSeat", + "itemValue": "2" + }, + { + "itemCode": "KEYPOS", + "itemName": "KEYPOS", + "itemValue": "1" + }, + { + "itemCode": "ENERGY", + "itemName": "ENERGY", + "itemValue": "1" + }, + { + "itemCode": "BATTERY", + "itemName": "BATTERY", + "itemValue": "1" + }, + { + "itemCode": "INTEMP", + "itemName": "INTEMP", + "itemValue": "1" + }, + { + "itemCode": "EXTEMP", + "itemName": "EXTEMP", + "itemValue": "0" + }, + { + "itemCode": "WINDOW", + "itemName": "WINDOW", + "itemValue": "1111" + }, + { + "itemCode": "LRD", + "itemName": "LRD", + "itemValue": "0" + }, + { + "itemCode": "BTKEY", + "itemName": "BTKEY", + "itemValue": "2" + }, + { + "itemCode": "BType", + "itemName": "Battery", + "itemValue": "2" + }, + { + "itemCode": "SBH", + "itemName": "SBH", + "itemValue": "1" + }, + { + "itemCode": "BHI", + "itemName": "BHI", + "itemValue": "1" + }, + { + "itemCode": "SWH", + "itemName": "SWH", + "itemValue": "0" + }, + { + "itemCode": "UB", + "itemName": "UB", + "itemValue": "1" + }, + { + "itemCode": "SL", + "itemName": "SL", + "itemValue": "1_2_5" + }, + { + "itemCode": "DF", + "itemName": "DF", + "itemValue": "9" + }, + { + "itemCode": "RWH", + "itemName": "RWH", + "itemValue": "9" + }, + { + "itemCode": "SC", + "itemName": "SC", + "itemValue": "5" + }, + { + "itemCode": "ROV", + "itemName": "ROV", + "itemValue": "25858" + } + ] + }, + { + "model": "MG3", + "series": "ZP22 EU", + "model_year": "2023", + "source": "https://github.com/townsmcp/mg-saic-ha/issues/206", + "configuration": [ + { + "itemCode": "J17", + "itemName": "Tire", + "itemValue": "1" + }, + { + "itemCode": "Q00", + "itemName": "Airbag", + "itemValue": "1" + }, + { + "itemCode": "S35", + "itemName": "Sunroof", + "itemValue": "0" + }, + { + "itemCode": "S61", + "itemName": "Remote control", + "itemValue": "1" + }, + { + "itemCode": "T11", + "itemName": "AC", + "itemValue": "1" + }, + { + "itemCode": "EPS", + "itemName": "EPS", + "itemValue": "1" + }, + { + "itemCode": "BONNUT", + "itemName": "BONNUT", + "itemValue": "0" + }, + { + "itemCode": "DOOR", + "itemName": "DOOR", + "itemValue": "1111" + }, + { + "itemCode": "BOOT", + "itemName": "BOOT", + "itemValue": "1" + }, + { + "itemCode": "ENGINE", + "itemName": "ENGINE", + "itemValue": "1" + }, + { + "itemCode": "HeatedSeat", + "itemName": "HeatedSeat", + "itemValue": "2" + }, + { + "itemCode": "KEYPOS", + "itemName": "KEYPOS", + "itemValue": "1" + }, + { + "itemCode": "ENERGY", + "itemName": "ENERGY", + "itemValue": "1" + }, + { + "itemCode": "BATTERY", + "itemName": "BATTERY", + "itemValue": "1" + }, + { + "itemCode": "INTEMP", + "itemName": "INTEMP", + "itemValue": "1" + }, + { + "itemCode": "EXTEMP", + "itemName": "EXTEMP", + "itemValue": "0" + }, + { + "itemCode": "WINDOW", + "itemName": "WINDOW", + "itemValue": "1000" + }, + { + "itemCode": "LRD", + "itemName": "LRD", + "itemValue": "0" + }, + { + "itemCode": "BTKEY", + "itemName": "BTKEY", + "itemValue": "0" + }, + { + "itemCode": "BType", + "itemName": "Battery", + "itemValue": "0" + }, + { + "itemCode": "SBH", + "itemName": "SBH", + "itemValue": "0" + }, + { + "itemCode": "BHI", + "itemName": "BHI", + "itemValue": "0" + }, + { + "itemCode": "SWH", + "itemName": "SWH", + "itemValue": "0" + }, + { + "itemCode": "SS", + "itemName": "SS", + "itemValue": "0" + }, + { + "itemCode": "SC", + "itemName": "SC", + "itemValue": "1" + }, + { + "itemCode": "ROV", + "itemName": "ROV", + "itemValue": "REDACTED" + } + ] + }, + { + "model": "EH32 X8", + "series": "EH32 L", + "model_year": "2023", + "source": "https://github.com/townsmcp/mg-saic-ha/issues/23", + "configuration": [ + { + "itemCode": "J17", + "itemName": "Tire", + "itemValue": "1" + }, + { + "itemCode": "Q00", + "itemName": "Airbag", + "itemValue": "1" + }, + { + "itemCode": "Q01", + "itemName": "FAirbag", + "itemValue": "1" + }, + { + "itemCode": "Q09", + "itemName": "AirbagS", + "itemValue": "1" + }, + { + "itemCode": "S35", + "itemName": "Sunroof", + "itemValue": "0" + }, + { + "itemCode": "S61", + "itemName": "Remote control", + "itemValue": "1" + }, + { + "itemCode": "T11", + "itemName": "AC", + "itemValue": "1" + }, + { + "itemCode": "EPS", + "itemName": "EPS", + "itemValue": "1" + }, + { + "itemCode": "SA64", + "itemName": "SA64", + "itemValue": "0111110000000000001000000100101000000010100000000000000000000110" + }, + { + "itemCode": "BONNUT", + "itemName": "BONNUT", + "itemValue": "1" + }, + { + "itemCode": "DOOR", + "itemName": "DOOR", + "itemValue": "1111" + }, + { + "itemCode": "BOOT", + "itemName": "BOOT", + "itemValue": "1" + }, + { + "itemCode": "ENGINE", + "itemName": "ENGINE", + "itemValue": "1" + }, + { + "itemCode": "EV", + "itemName": "EV", + "itemValue": "0" + }, + { + "itemCode": "HeatedSeat", + "itemName": "HeatedSeat", + "itemValue": "0" + }, + { + "itemCode": "KEYPOS", + "itemName": "KEYPOS", + "itemValue": "0" + }, + { + "itemCode": "ENERGY", + "itemName": "ENERGY", + "itemValue": "1" + }, + { + "itemCode": "BATTERY", + "itemName": "BATTERY", + "itemValue": "1" + }, + { + "itemCode": "INTEMP", + "itemName": "INTEMP", + "itemValue": "1" + }, + { + "itemCode": "EXTEMP", + "itemName": "EXTEMP", + "itemValue": "1" + }, + { + "itemCode": "WINDOW", + "itemName": "WINDOW", + "itemValue": "0000" + }, + { + "itemCode": "LRD", + "itemName": "LRD", + "itemValue": "1" + }, + { + "itemCode": "BTKEY", + "itemName": "BTKEY", + "itemValue": "1" + }, + { + "itemCode": "BType", + "itemName": "Battery", + "itemValue": "2" + }, + { + "itemCode": "SBH", + "itemName": "SBH", + "itemValue": "1" + }, + { + "itemCode": "BHI", + "itemName": "BHI", + "itemValue": "1" + }, + { + "itemCode": "ROV", + "itemName": "ROV", + "itemValue": "25857" + } + ] + }, + { + "model": "MG4 EV URBAN", + "series": "AH4EM L", + "model_year": "2025", + "source": "https://github.com/townsmcp/mg-saic-ha/issues/243", + "configuration": [ + { + "itemCode": "J17", + "itemName": "Tire", + "itemValue": "1" + }, + { + "itemCode": "Q00", + "itemName": "Airbag", + "itemValue": "0" + }, + { + "itemCode": "Q01", + "itemName": "FAirbag", + "itemValue": "1" + }, + { + "itemCode": "Q09", + "itemName": "AirbagS", + "itemValue": "1" + }, + { + "itemCode": "S35", + "itemName": "Sunroof", + "itemValue": "0" + }, + { + "itemCode": "S61", + "itemName": "Remote control", + "itemValue": "1" + }, + { + "itemCode": "T11", + "itemName": "AC", + "itemValue": "1" + }, + { + "itemCode": "EPS", + "itemName": "EPS", + "itemValue": "0" + }, + { + "itemCode": "SA64", + "itemName": "SA64", + "itemValue": "0111110000000000001000000100101000000010100000000000000000000110" + }, + { + "itemCode": "BONNUT", + "itemName": "BONNUT", + "itemValue": "0" + }, + { + "itemCode": "DOOR", + "itemName": "DOOR", + "itemValue": "1111" + }, + { + "itemCode": "BOOT", + "itemName": "BOOT", + "itemValue": "1" + }, + { + "itemCode": "ENGINE", + "itemName": "ENGINE", + "itemValue": "0" + }, + { + "itemCode": "EV", + "itemName": "EV", + "itemValue": "0" + }, + { + "itemCode": "HeatedSeat", + "itemName": "HeatedSeat", + "itemValue": "0" + }, + { + "itemCode": "KEYPOS", + "itemName": "KEYPOS", + "itemValue": "0" + }, + { + "itemCode": "ENERGY", + "itemName": "ENERGY", + "itemValue": "1" + }, + { + "itemCode": "BATTERY", + "itemName": "BATTERY", + "itemValue": "0" + }, + { + "itemCode": "INTEMP", + "itemName": "INTEMP", + "itemValue": "1" + }, + { + "itemCode": "EXTEMP", + "itemName": "EXTEMP", + "itemValue": "0" + }, + { + "itemCode": "WINDOW", + "itemName": "WINDOW", + "itemValue": "1111" + }, + { + "itemCode": "LRD", + "itemName": "LRD", + "itemValue": "0" + }, + { + "itemCode": "BTKEY", + "itemName": "BTKEY", + "itemValue": "2" + }, + { + "itemCode": "BType", + "itemName": "Battery", + "itemValue": "2" + }, + { + "itemCode": "SBH", + "itemName": "SBH", + "itemValue": "1" + }, + { + "itemCode": "BHI", + "itemName": "BHI", + "itemValue": "1" + }, + { + "itemCode": "TR", + "itemName": "TR", + "itemValue": "1" + }, + { + "itemCode": "SWH", + "itemName": "SWH", + "itemValue": "1" + }, + { + "itemCode": "BTKNOTIFY", + "itemName": "BTKNOTIFY", + "itemValue": "1" + }, + { + "itemCode": "SS", + "itemName": "SS", + "itemValue": "0" + }, + { + "itemCode": "SC", + "itemName": "SC", + "itemValue": "5" + }, + { + "itemCode": "CL", + "itemName": "CL", + "itemValue": "1" + }, + { + "itemCode": "ROV", + "itemName": "ROV", + "itemValue": "25859" + } + ] + }, + { + "model": "MG3", + "series": "ZP22", + "model_year": "2025", + "source": "https://github.com/townsmcp/mg-saic-ha/issues/258", + "configuration": [ + { + "itemCode": "J17", + "itemName": "Tire", + "itemValue": "1" + }, + { + "itemCode": "Q00", + "itemName": "Airbag", + "itemValue": "1" + }, + { + "itemCode": "Q01", + "itemName": "FAirbag", + "itemValue": "0" + }, + { + "itemCode": "Q09", + "itemName": "AirbagS", + "itemValue": "0" + }, + { + "itemCode": "S35", + "itemName": "Sunroof", + "itemValue": "0" + }, + { + "itemCode": "S61", + "itemName": "Remote control", + "itemValue": "1" + }, + { + "itemCode": "T11", + "itemName": "AC", + "itemValue": "1" + }, + { + "itemCode": "EPS", + "itemName": "EPS", + "itemValue": "1" + }, + { + "itemCode": "SA64", + "itemName": "SA64", + "itemValue": "0111110000000000001000000100101000000010100000000000000000000110" + }, + { + "itemCode": "BONNUT", + "itemName": "BONNUT", + "itemValue": "1" + }, + { + "itemCode": "DOOR", + "itemName": "DOOR", + "itemValue": "1111" + }, + { + "itemCode": "BOOT", + "itemName": "BOOT", + "itemValue": "1" + }, + { + "itemCode": "ENGINE", + "itemName": "ENGINE", + "itemValue": "1" + }, + { + "itemCode": "HeatedSeat", + "itemName": "HeatedSeat", + "itemValue": "2" + }, + { + "itemCode": "KEYPOS", + "itemName": "KEYPOS", + "itemValue": "1" + }, + { + "itemCode": "ENERGY", + "itemName": "ENERGY", + "itemValue": "1" + }, + { + "itemCode": "BATTERY", + "itemName": "BATTERY", + "itemValue": "1" + }, + { + "itemCode": "INTEMP", + "itemName": "INTEMP", + "itemValue": "1" + }, + { + "itemCode": "EXTEMP", + "itemName": "EXTEMP", + "itemValue": "0" + }, + { + "itemCode": "WINDOW", + "itemName": "WINDOW", + "itemValue": "1000" + }, + { + "itemCode": "LRD", + "itemName": "LRD", + "itemValue": "0" + }, + { + "itemCode": "BTKEY", + "itemName": "BTKEY", + "itemValue": "0" + }, + { + "itemCode": "BType", + "itemName": "Battery", + "itemValue": "0" + }, + { + "itemCode": "SBH", + "itemName": "SBH", + "itemValue": "0" + }, + { + "itemCode": "BHI", + "itemName": "BHI", + "itemValue": "0" + }, + { + "itemCode": "SWH", + "itemName": "SWH", + "itemValue": "0" + }, + { + "itemCode": "ROV", + "itemName": "ROV", + "itemValue": "25857" + } + ] + }, + { + "model": "MG3", + "series": "ZP22 EU", + "model_year": "2023", + "source": "https://github.com/townsmcp/mg-saic-ha/issues/318", + "configuration": [ + { + "itemCode": "J17", + "itemName": "Tire", + "itemValue": "1" + }, + { + "itemCode": "Q00", + "itemName": "Airbag", + "itemValue": "1" + }, + { + "itemCode": "S35", + "itemName": "Sunroof", + "itemValue": "0" + }, + { + "itemCode": "S61", + "itemName": "Remote control", + "itemValue": "1" + }, + { + "itemCode": "T11", + "itemName": "AC", + "itemValue": "1" + }, + { + "itemCode": "EPS", + "itemName": "EPS", + "itemValue": "1" + }, + { + "itemCode": "BONNUT", + "itemName": "BONNUT", + "itemValue": "0" + }, + { + "itemCode": "DOOR", + "itemName": "DOOR", + "itemValue": "1111" + }, + { + "itemCode": "BOOT", + "itemName": "BOOT", + "itemValue": "1" + }, + { + "itemCode": "ENGINE", + "itemName": "ENGINE", + "itemValue": "1" + }, + { + "itemCode": "HeatedSeat", + "itemName": "HeatedSeat", + "itemValue": "2" + }, + { + "itemCode": "KEYPOS", + "itemName": "KEYPOS", + "itemValue": "1" + }, + { + "itemCode": "ENERGY", + "itemName": "ENERGY", + "itemValue": "1" + }, + { + "itemCode": "BATTERY", + "itemName": "BATTERY", + "itemValue": "1" + }, + { + "itemCode": "INTEMP", + "itemName": "INTEMP", + "itemValue": "1" + }, + { + "itemCode": "EXTEMP", + "itemName": "EXTEMP", + "itemValue": "0" + }, + { + "itemCode": "WINDOW", + "itemName": "WINDOW", + "itemValue": "1000" + }, + { + "itemCode": "LRD", + "itemName": "LRD", + "itemValue": "0" + }, + { + "itemCode": "BTKEY", + "itemName": "BTKEY", + "itemValue": "0" + }, + { + "itemCode": "BType", + "itemName": "Battery", + "itemValue": "0" + }, + { + "itemCode": "SBH", + "itemName": "SBH", + "itemValue": "0" + }, + { + "itemCode": "BHI", + "itemName": "BHI", + "itemValue": "0" + }, + { + "itemCode": "SWH", + "itemName": "SWH", + "itemValue": "0" + }, + { + "itemCode": "SS", + "itemName": "SS", + "itemValue": "0" + }, + { + "itemCode": "SC", + "itemName": "SC", + "itemValue": "1" + }, + { + "itemCode": "ROV", + "itemName": "ROV", + "itemValue": "25857" + } + ] + }, + { + "model": "MG5 Electric", + "series": "EP22 UK", + "model_year": "2022", + "source": "https://github.com/townsmcp/mg-saic-ha/issues/401", + "configuration": [ + { + "itemCode": "J17", + "itemName": "Tire", + "itemValue": "1" + }, + { + "itemCode": "Q00", + "itemName": "Airbag", + "itemValue": "1" + }, + { + "itemCode": "Q01", + "itemName": "FAirbag", + "itemValue": "1" + }, + { + "itemCode": "Q09", + "itemName": "AirbagS", + "itemValue": "1" + }, + { + "itemCode": "S35", + "itemName": "Sunroof", + "itemValue": "0" + }, + { + "itemCode": "S61", + "itemName": "Remote control", + "itemValue": "1" + }, + { + "itemCode": "T11", + "itemName": "AC", + "itemValue": "1" + }, + { + "itemCode": "EPS", + "itemName": "EPS", + "itemValue": "1" + }, + { + "itemCode": "SA64", + "itemName": "SA64", + "itemValue": "0111110000000000001000000100101000000010100000000000000000000110" + }, + { + "itemCode": "BONNUT", + "itemName": "BONNUT", + "itemValue": "1" + }, + { + "itemCode": "DOOR", + "itemName": "DOOR", + "itemValue": "1111" + }, + { + "itemCode": "BOOT", + "itemName": "BOOT", + "itemValue": "1" + }, + { + "itemCode": "ENGINE", + "itemName": "ENGINE", + "itemValue": "1" + }, + { + "itemCode": "EV", + "itemName": "EV", + "itemValue": "0" + }, + { + "itemCode": "HeatedSeat", + "itemName": "HeatedSeat", + "itemValue": "0" + }, + { + "itemCode": "KEYPOS", + "itemName": "KEYPOS", + "itemValue": "1" + }, + { + "itemCode": "ENERGY", + "itemName": "ENERGY", + "itemValue": "0" + }, + { + "itemCode": "BATTERY", + "itemName": "BATTERY", + "itemValue": "1" + }, + { + "itemCode": "INTEMP", + "itemName": "INTEMP", + "itemValue": "1" + }, + { + "itemCode": "EXTEMP", + "itemName": "EXTEMP", + "itemValue": "1" + }, + { + "itemCode": "WINDOW", + "itemName": "WINDOW", + "itemValue": "0000" + }, + { + "itemCode": "LRD", + "itemName": "LRD", + "itemValue": "1" + }, + { + "itemCode": "BTKEY", + "itemName": "BTKEY", + "itemValue": "0" + }, + { + "itemCode": "BType", + "itemName": "Battery", + "itemValue": "1" + }, + { + "itemCode": "SBH", + "itemName": "SBH", + "itemValue": "0" + }, + { + "itemCode": "BHI", + "itemName": "BHI", + "itemValue": "0" + }, + { + "itemCode": "ROV", + "itemName": "ROV", + "itemValue": "25857" + } + ] + }, + { + "model": "ZS EV", + "series": "ZS EV L", + "model_year": "2021", + "source": "https://github.com/townsmcp/mg-saic-ha/issues/43", + "configuration": [ + { + "itemCode": "J17", + "itemName": "Tire pressure monitoring system", + "itemValue": "1" + }, + { + "itemCode": "Q00", + "itemName": "Regular airbags", + "itemValue": "1" + }, + { + "itemCode": "Q01", + "itemName": "Front-seat airbags", + "itemValue": "0" + }, + { + "itemCode": "Q09", + "itemName": "Airbag switch", + "itemValue": "0" + }, + { + "itemCode": "S35", + "itemName": "Sun Roof", + "itemValue": "0" + }, + { + "itemCode": "S61", + "itemName": "Remote control", + "itemValue": "1" + }, + { + "itemCode": "T11", + "itemName": "Air conditioning", + "itemValue": "1" + }, + { + "itemCode": "EPS", + "itemName": "Electric Power Steering", + "itemValue": "1" + }, + { + "itemCode": "SA64", + "itemName": "Security alert", + "itemValue": "0111110000000000000010000100100100000010100000000000000000000110" + }, + { + "itemCode": "BONNUT", + "itemName": "Bonnut Status", + "itemValue": "1" + }, + { + "itemCode": "DOOR", + "itemName": "Door Status", + "itemValue": "1111" + }, + { + "itemCode": "BOOT", + "itemName": "Boot Status", + "itemValue": "1" + }, + { + "itemCode": "ENGINE", + "itemName": "Engine Status", + "itemValue": "1" + }, + { + "itemCode": "EV", + "itemName": "Electric Vehicle", + "itemValue": "0" + }, + { + "itemCode": "HeatedSeat", + "itemName": "HeatedSeat", + "itemValue": "0" + }, + { + "itemCode": "KEYPOS", + "itemName": "Key Position", + "itemValue": "1" + }, + { + "itemCode": "ENERGY", + "itemName": "Energy state", + "itemValue": "1" + }, + { + "itemCode": "BATTERY", + "itemName": "Battery Voltage", + "itemValue": "1" + }, + { + "itemCode": "INTEMP", + "itemName": "Interior Temperature", + "itemValue": "1" + }, + { + "itemCode": "EXTEMP", + "itemName": "Exterior Temperature", + "itemValue": "1" + }, + { + "itemCode": "WINDOW", + "itemName": "Window Status", + "itemValue": "0000" + }, + { + "itemCode": "LRD", + "itemName": "Left-Right Driving", + "itemValue": "1" + }, + { + "itemCode": "BTKEY", + "itemName": "BLE CarKey", + "itemValue": "0" + }, + { + "itemCode": "BTYPE", + "itemName": "Battery Type", + "itemValue": "2" + }, + { + "itemCode": "ROV", + "itemName": "ROV", + "itemValue": "25857" + } + ] + }, + { + "model": "ZS", + "series": "ZS32 EU", + "model_year": "2024", + "source": "https://github.com/townsmcp/mg-saic-ha/issues/441", + "configuration": [ + { + "itemCode": "J17", + "itemName": "Tire", + "itemValue": "1" + }, + { + "itemCode": "Q00", + "itemName": "Airbag", + "itemValue": "1" + }, + { + "itemCode": "S35", + "itemName": "Sunroof", + "itemValue": "0" + }, + { + "itemCode": "S61", + "itemName": "Remote control", + "itemValue": "1" + }, + { + "itemCode": "T11", + "itemName": "AC", + "itemValue": "1" + }, + { + "itemCode": "EPS", + "itemName": "EPS", + "itemValue": "1" + }, + { + "itemCode": "BONNUT", + "itemName": "BONNUT", + "itemValue": "1" + }, + { + "itemCode": "DOOR", + "itemName": "DOOR", + "itemValue": "1111" + }, + { + "itemCode": "BOOT", + "itemName": "BOOT", + "itemValue": "1" + }, + { + "itemCode": "ENGINE", + "itemName": "ENGINE", + "itemValue": "1" + }, + { + "itemCode": "EV", + "itemName": "EV", + "itemValue": "0" + }, + { + "itemCode": "HeatedSeat", + "itemName": "HeatedSeat", + "itemValue": "2" + }, + { + "itemCode": "KEYPOS", + "itemName": "KEYPOS", + "itemValue": "1" + }, + { + "itemCode": "ENERGY", + "itemName": "ENERGY", + "itemValue": "1" + }, + { + "itemCode": "BATTERY", + "itemName": "BATTERY", + "itemValue": "1" + }, + { + "itemCode": "INTEMP", + "itemName": "INTEMP", + "itemValue": "1" + }, + { + "itemCode": "EXTEMP", + "itemName": "EXTEMP", + "itemValue": "0" + }, + { + "itemCode": "WINDOW", + "itemName": "WINDOW", + "itemValue": "1000" + }, + { + "itemCode": "LRD", + "itemName": "LRD", + "itemValue": "0" + }, + { + "itemCode": "BTKEY", + "itemName": "BTKEY", + "itemValue": "0" + }, + { + "itemCode": "BType", + "itemName": "Battery", + "itemValue": "0" + }, + { + "itemCode": "SBH", + "itemName": "SBH", + "itemValue": "0" + }, + { + "itemCode": "BHI", + "itemName": "BHI", + "itemValue": "0" + }, + { + "itemCode": "SWH", + "itemName": "SWH", + "itemValue": "0" + }, + { + "itemCode": "SS", + "itemName": "SS", + "itemValue": "0" + }, + { + "itemCode": "ROV", + "itemName": "ROV", + "itemValue": "25857" + } + ] + }, + { + "model": "MG4", + "series": "EH32 L", + "model_year": "2024", + "source": "https://github.com/townsmcp/mg-saic-ha/issues/75", + "configuration": [ + { + "itemCode": "J17", + "itemName": "Tire pressure monitoring system", + "itemValue": "1" + }, + { + "itemCode": "Q00", + "itemName": "Regular airbags", + "itemValue": "1" + }, + { + "itemCode": "Q01", + "itemName": "Front-seat airbags", + "itemValue": "1" + }, + { + "itemCode": "Q09", + "itemName": "Airbag switch", + "itemValue": "1" + }, + { + "itemCode": "S35", + "itemName": "Sun Roof", + "itemValue": "0" + }, + { + "itemCode": "S61", + "itemName": "Remote control", + "itemValue": "1" + }, + { + "itemCode": "T11", + "itemName": "Air conditioning", + "itemValue": "1" + }, + { + "itemCode": "EPS", + "itemName": "Electric Power Steering", + "itemValue": "1" + }, + { + "itemCode": "SA64", + "itemName": "Security alert", + "itemValue": "0111110000000000001000000100101000000010100000000000000000000110" + }, + { + "itemCode": "BONNUT", + "itemName": "Bonnut Status", + "itemValue": "1" + }, + { + "itemCode": "DOOR", + "itemName": "Door Status", + "itemValue": "1111" + }, + { + "itemCode": "BOOT", + "itemName": "Boot Status", + "itemValue": "1" + }, + { + "itemCode": "ENGINE", + "itemName": "Engine Status", + "itemValue": "1" + }, + { + "itemCode": "EV", + "itemName": "Electric Vehicle", + "itemValue": "0" + }, + { + "itemCode": "HeatedSeat", + "itemName": "HeatedSeat", + "itemValue": "0" + }, + { + "itemCode": "KEYPOS", + "itemName": "Key Position", + "itemValue": "0" + }, + { + "itemCode": "ENERGY", + "itemName": "Energy state", + "itemValue": "1" + }, + { + "itemCode": "BATTERY", + "itemName": "Battery Voltage", + "itemValue": "1" + }, + { + "itemCode": "INTEMP", + "itemName": "Interior Temperature", + "itemValue": "1" + }, + { + "itemCode": "EXTEMP", + "itemName": "Exterior Temperature", + "itemValue": "1" + }, + { + "itemCode": "WINDOW", + "itemName": "Window Status", + "itemValue": "0000" + }, + { + "itemCode": "LRD", + "itemName": "Left-Right Driving", + "itemValue": "1" + }, + { + "itemCode": "BTKEY", + "itemName": "Bluetooth Key", + "itemValue": "1" + }, + { + "itemCode": "BTYPE", + "itemName": "Battery Type", + "itemValue": "1" + }, + { + "itemCode": "ROV", + "itemName": "ROV", + "itemValue": "25857" + } + ] + }, + { + "model": "AS33 UK", + "series": "AS33 UK", + "model_year": "2024", + "source": "https://github.com/townsmcp/mg-saic-ha/issues/91", + "configuration": [ + { + "itemCode": "J17", + "itemName": "Tire", + "itemValue": "1" + }, + { + "itemCode": "Q00", + "itemName": "Airbag", + "itemValue": "1" + }, + { + "itemCode": "Q01", + "itemName": "FAirbag", + "itemValue": "1" + }, + { + "itemCode": "Q09", + "itemName": "AirbagS", + "itemValue": "1" + }, + { + "itemCode": "S35", + "itemName": "Sunroof", + "itemValue": "0" + }, + { + "itemCode": "S61", + "itemName": "Remote control", + "itemValue": "1" + }, + { + "itemCode": "T11", + "itemName": "AC", + "itemValue": "1" + }, + { + "itemCode": "EPS", + "itemName": "EPS", + "itemValue": "1" + }, + { + "itemCode": "SA64", + "itemName": "SA64", + "itemValue": "0111110000000000001000000100101000000010100000000000000000000110" + }, + { + "itemCode": "BONNUT", + "itemName": "BONNUT", + "itemValue": "1" + }, + { + "itemCode": "DOOR", + "itemName": "DOOR", + "itemValue": "1111" + }, + { + "itemCode": "BOOT", + "itemName": "BOOT", + "itemValue": "1" + }, + { + "itemCode": "ENGINE", + "itemName": "ENGINE", + "itemValue": "1" + }, + { + "itemCode": "EV", + "itemName": "EV", + "itemValue": "1" + }, + { + "itemCode": "HeatedSeat", + "itemName": "HeatedSeat", + "itemValue": "1" + }, + { + "itemCode": "KEYPOS", + "itemName": "KEYPOS", + "itemValue": "0" + }, + { + "itemCode": "ENERGY", + "itemName": "ENERGY", + "itemValue": "1" + }, + { + "itemCode": "BATTERY", + "itemName": "BATTERY", + "itemValue": "1" + }, + { + "itemCode": "INTEMP", + "itemName": "INTEMP", + "itemValue": "1" + }, + { + "itemCode": "EXTEMP", + "itemName": "EXTEMP", + "itemValue": "1" + }, + { + "itemCode": "WINDOW", + "itemName": "WINDOW", + "itemValue": "0000" + }, + { + "itemCode": "LRD", + "itemName": "LRD", + "itemValue": "1" + }, + { + "itemCode": "BTKEY", + "itemName": "BTKEY", + "itemValue": "0" + }, + { + "itemCode": "BType", + "itemName": "Battery", + "itemValue": "0" + }, + { + "itemCode": "SBH", + "itemName": "SBH", + "itemValue": "0" + }, + { + "itemCode": "BHI", + "itemName": "BHI", + "itemValue": "0" + }, + { + "itemCode": "SWH", + "itemName": "SWH", + "itemValue": "0" + }, + { + "itemCode": "ROV", + "itemName": "ROV", + "itemValue": "25858" + } + ] + }, + { + "model": "ZS EV", + "series": "ZS EV S", + "model_year": "2021", + "source": "https://github.com/townsmcp/mg-saic-ha/issues/98", + "configuration": [ + { + "itemCode": "J17", + "itemName": "Tire", + "itemValue": "1" + }, + { + "itemCode": "Q00", + "itemName": "Airbag", + "itemValue": "1" + }, + { + "itemCode": "Q01", + "itemName": "FAirbag", + "itemValue": "0" + }, + { + "itemCode": "Q09", + "itemName": "AirbagS", + "itemValue": "0" + }, + { + "itemCode": "S35", + "itemName": "Sunroof", + "itemValue": "3" + }, + { + "itemCode": "S61", + "itemName": "Remote control", + "itemValue": "1" + }, + { + "itemCode": "T11", + "itemName": "AC", + "itemValue": "1" + }, + { + "itemCode": "EPS", + "itemName": "EPS", + "itemValue": "1" + }, + { + "itemCode": "SA64", + "itemName": "SA64", + "itemValue": "0111110000000000001000000100101000000010100000000000000000000110" + }, + { + "itemCode": "BONNUT", + "itemName": "BONNUT", + "itemValue": "1" + }, + { + "itemCode": "DOOR", + "itemName": "DOOR", + "itemValue": "1111" + }, + { + "itemCode": "BOOT", + "itemName": "BOOT", + "itemValue": "1" + }, + { + "itemCode": "ENGINE", + "itemName": "ENGINE", + "itemValue": "1" + }, + { + "itemCode": "EV", + "itemName": "EV", + "itemValue": "0" + }, + { + "itemCode": "HeatedSeat", + "itemName": "HeatedSeat", + "itemValue": "0" + }, + { + "itemCode": "KEYPOS", + "itemName": "KEYPOS", + "itemValue": "1" + }, + { + "itemCode": "ENERGY", + "itemName": "ENERGY", + "itemValue": "1" + }, + { + "itemCode": "INTEMP", + "itemName": "INTEMP", + "itemValue": "1" + }, + { + "itemCode": "EXTEMP", + "itemName": "EXTEMP", + "itemValue": "1" + }, + { + "itemCode": "WINDOW", + "itemName": "WIN\n> DOW", + "itemValue": "0000" + }, + { + "itemCode": "LRD", + "itemName": "LRD", + "itemValue": "0" + }, + { + "itemCode": "BTKEY", + "itemName": "BTKEY", + "itemValue": "1" + }, + { + "itemCode": "SBH", + "itemName": "SBH", + "itemValue": "1" + }, + { + "itemCode": "BHI", + "itemName": "BHI", + "itemValue": "0" + } + ] + }, + { + "model": "MG4 Electric", + "series": "EH32 S", + "model_year": "2022", + "source": "gateway API capture (maintainer's vehicle)", + "configuration": [ + { + "itemCode": "J17", + "itemName": "Tire", + "itemValue": "1" + }, + { + "itemCode": "Q00", + "itemName": "Airbag", + "itemValue": "1" + }, + { + "itemCode": "Q01", + "itemName": "FAirbag", + "itemValue": "1" + }, + { + "itemCode": "Q09", + "itemName": "AirbagS", + "itemValue": "1" + }, + { + "itemCode": "S35", + "itemName": "Sunroof", + "itemValue": "0" + }, + { + "itemCode": "S61", + "itemName": "Remote control", + "itemValue": "1" + }, + { + "itemCode": "T11", + "itemName": "AC", + "itemValue": "1" + }, + { + "itemCode": "EPS", + "itemName": "EPS", + "itemValue": "1" + }, + { + "itemCode": "SA64", + "itemName": "SA64", + "itemValue": "0111110000000000001000000100101000000010100000000000000000000110" + }, + { + "itemCode": "BONNUT", + "itemName": "BONNUT", + "itemValue": "1" + }, + { + "itemCode": "DOOR", + "itemName": "DOOR", + "itemValue": "1111" + }, + { + "itemCode": "BOOT", + "itemName": "BOOT", + "itemValue": "1" + }, + { + "itemCode": "ENGINE", + "itemName": "ENGINE", + "itemValue": "1" + }, + { + "itemCode": "EV", + "itemName": "EV", + "itemValue": "0" + }, + { + "itemCode": "HeatedSeat", + "itemName": "HeatedSeat", + "itemValue": "1" + }, + { + "itemCode": "KEYPOS", + "itemName": "KEYPOS", + "itemValue": "0" + }, + { + "itemCode": "ENERGY", + "itemName": "ENERGY", + "itemValue": "1" + }, + { + "itemCode": "BATTERY", + "itemName": "BATTERY", + "itemValue": "1" + }, + { + "itemCode": "INTEMP", + "itemName": "INTEMP", + "itemValue": "1" + }, + { + "itemCode": "EXTEMP", + "itemName": "EXTEMP", + "itemValue": "1" + }, + { + "itemCode": "WINDOW", + "itemName": "WINDOW", + "itemValue": "0000" + }, + { + "itemCode": "LRD", + "itemName": "LRD", + "itemValue": "0" + }, + { + "itemCode": "BTKEY", + "itemName": "BTKEY", + "itemValue": "1" + }, + { + "itemCode": "BType", + "itemName": "Battery", + "itemValue": "1" + }, + { + "itemCode": "SBH", + "itemName": "SBH", + "itemValue": "1" + }, + { + "itemCode": "BHI", + "itemName": "BHI", + "itemValue": "1" + }, + { + "itemCode": "ROV", + "itemName": "ROV", + "itemValue": "25857" + } + ] + } +] diff --git a/tests/test_ha_discovery_feature_gating.py b/tests/test_ha_discovery_feature_gating.py new file mode 100644 index 0000000..010a7ce --- /dev/null +++ b/tests/test_ha_discovery_feature_gating.py @@ -0,0 +1,105 @@ +from __future__ import annotations + +from apscheduler.schedulers.blocking import BlockingScheduler +import pytest +from saic_ismart_client_ng.api.vehicle.schema import ( + VehicleModelConfiguration, + VinInfo, +) + +from configuration import Configuration +from integrations.home_assistant.discovery import HomeAssistantDiscovery +from vehicle import RefreshMode, VehicleState +from vehicle_info import VehicleInfo + +from .common_mocks import VIN +from .mocks import MessageCapturingConsolePublisher + +ALL_FEATURES = { + "DOOR": "1111", + "BOOT": "1", + "J17": "1", + "INTEMP": "1", +} + +GATED_ENTITIES = [ + ("binary_sensor", "door_driver"), + ("binary_sensor", "door_passenger"), + ("binary_sensor", "door_rear_left"), + ("binary_sensor", "door_rear_right"), + ("binary_sensor", "boot"), + ("sensor", "tyres_front_left_pressure"), + ("sensor", "tyres_front_right_pressure"), + ("sensor", "tyres_rear_left_pressure"), + ("sensor", "tyres_rear_right_pressure"), + ("sensor", "interior_temperature"), +] + +# Codes that real vehicles report as 0 while still sending the data +UNGATED_ENTITIES = [ + ("binary_sensor", "window_driver"), + ("binary_sensor", "window_passenger"), + ("binary_sensor", "window_rear_left"), + ("binary_sensor", "window_rear_right"), + ("binary_sensor", "bonnet"), + ("sensor", "auxiliary_battery_voltage"), + ("sensor", "exterior_temperature"), +] + + +def _publish_discovery(codes: dict[str, str]) -> dict[str, str]: + config = Configuration() + config.anonymized_publishing = False + config.ha_discovery_prefix = "homeassistant" + publisher = MessageCapturingConsolePublisher(config) + vin_info = VinInfo() + vin_info.vin = VIN + vin_info.series = "EH32 S" + vin_info.vehicleModelConfiguration = [ + VehicleModelConfiguration(itemCode=code, itemName=code, itemValue=value) + for code, value in codes.items() + ] + vehicle_info = VehicleInfo(vin_info, None) + vehicle_state = VehicleState( + publisher, BlockingScheduler(), f"/vehicles/{VIN}", vehicle_info + ) + vehicle_state.refresh_period_active = 30 + vehicle_state.refresh_period_inactive = 120 + vehicle_state.refresh_period_after_shutdown = 60 + vehicle_state.refresh_period_inactive_grace = 600 + vehicle_state.refresh_mode = RefreshMode.PERIODIC + discovery = HomeAssistantDiscovery(vehicle_state, vehicle_info, config) + assert discovery.publish_ha_discovery_messages() + return publisher.map + + +def _topic(component: str, name: str) -> str: + return f"homeassistant/{component}/{VIN}_mg/{VIN}_{name}/config" + + +@pytest.mark.parametrize(("component", "name"), GATED_ENTITIES) +def test_entity_published_when_supported(component: str, name: str) -> None: + published = _publish_discovery(ALL_FEATURES) + assert published[_topic(component, name)] != "" + + +@pytest.mark.parametrize(("component", "name"), GATED_ENTITIES) +def test_entity_unpublished_when_code_absent(component: str, name: str) -> None: + published = _publish_discovery({}) + assert published[_topic(component, name)] == "" + + +@pytest.mark.parametrize(("component", "name"), UNGATED_ENTITIES) +def test_entity_published_even_when_code_disabled(component: str, name: str) -> None: + published = _publish_discovery( + {"WINDOW": "0000", "BONNUT": "0", "BATTERY": "0", "EXTEMP": "0"} + ) + assert published[_topic(component, name)] != "" + + +def test_door_mask_gates_single_entities() -> None: + published = _publish_discovery({"DOOR": "1100"}) + assert published[_topic("binary_sensor", "door_driver")] != "" + assert published[_topic("binary_sensor", "door_passenger")] != "" + assert published[_topic("binary_sensor", "door_rear_left")] == "" + assert published[_topic("binary_sensor", "door_rear_right")] == "" diff --git a/tests/test_vehicle_info_features.py b/tests/test_vehicle_info_features.py new file mode 100644 index 0000000..d23273d --- /dev/null +++ b/tests/test_vehicle_info_features.py @@ -0,0 +1,103 @@ +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +import pytest +from saic_ismart_client_ng.api.vehicle.schema import ( + VehicleModelConfiguration, + VinInfo, +) + +from vehicle_info import VehicleInfo + +from .common_mocks import VIN + +# Real configurations shared by users in public issue reports +REAL_CONFIGURATIONS: list[dict[str, Any]] = json.loads( + ( + Path(__file__).parent / "fixtures" / "vehicle_model_configurations.json" + ).read_text() +) + + +def _vehicle_info(**codes: str) -> VehicleInfo: + vin_info = VinInfo() + vin_info.vin = VIN + vin_info.vehicleModelConfiguration = [ + VehicleModelConfiguration(itemCode=code, itemName=code, itemValue=value) + for code, value in codes.items() + ] + return VehicleInfo(vin_info, None) + + +def _real_vehicle_info(fixture: dict[str, Any]) -> VehicleInfo: + vin_info = VinInfo() + vin_info.vin = VIN + vin_info.series = fixture["series"] + vin_info.modelName = fixture["model"] + vin_info.vehicleModelConfiguration = [ + VehicleModelConfiguration(**item) for item in fixture["configuration"] + ] + return VehicleInfo(vin_info, None) + + +@pytest.mark.parametrize( + ("value", "expected"), + [("0", False), ("1", True), ("2", False), ("3", True), ("X", False)], +) +def test_has_sunroof(value: str, expected: bool) -> None: + assert _vehicle_info(S35=value).has_sunroof is expected + + +def test_absent_codes_disable_features() -> None: + vehicle_info = _vehicle_info() + assert not vehicle_info.has_sunroof + assert not vehicle_info.has_driver_door + assert not vehicle_info.has_boot_status + assert not vehicle_info.has_tyre_pressure + assert not vehicle_info.has_interior_temperature + + +def test_door_mask_is_positional() -> None: + vehicle_info = _vehicle_info(DOOR="1100") + assert vehicle_info.has_driver_door + assert vehicle_info.has_passenger_door + assert not vehicle_info.has_rear_left_door + assert not vehicle_info.has_rear_right_door + + +@pytest.mark.parametrize("mask", ["1", "111", "11111", ""]) +def test_door_mask_with_wrong_length_disables_all(mask: str) -> None: + vehicle_info = _vehicle_info(DOOR=mask) + assert not vehicle_info.has_driver_door + assert not vehicle_info.has_rear_right_door + + +@pytest.mark.parametrize( + "fixture", + REAL_CONFIGURATIONS, + ids=[f"{c['model']}-{c['source'].rsplit('/', 1)[-1]}" for c in REAL_CONFIGURATIONS], +) +def test_real_configurations(fixture: dict[str, Any]) -> None: + vehicle_info = _real_vehicle_info(fixture) + is_two_door = fixture["series"] == "EC32" # MG Cyberster + assert vehicle_info.has_driver_door + assert vehicle_info.has_passenger_door + assert vehicle_info.has_rear_left_door is not is_two_door + assert vehicle_info.has_rear_right_door is not is_two_door + assert vehicle_info.has_boot_status + assert vehicle_info.has_tyre_pressure + assert vehicle_info.has_interior_temperature + + +def test_real_configurations_sunroof() -> None: + with_sunroof = { + c["source"] for c in REAL_CONFIGURATIONS if _real_vehicle_info(c).has_sunroof + } + assert with_sunroof == { + "https://github.com/townsmcp/mg-saic-ha/issues/100", # ZS EV, S35=3 + "https://github.com/townsmcp/mg-saic-ha/issues/98", # ZS EV, S35=3 + "https://github.com/townsmcp/mg-saic-ha/issues/204", # MG S9 PHEV, S35=3 + }