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 + }