mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 22:27:07 +00:00
Add hardware revision support to homekit (#63336)
This commit is contained in:
parent
7c6297db86
commit
5c8271552a
@ -26,6 +26,7 @@ from homeassistant.const import (
|
|||||||
ATTR_BATTERY_LEVEL,
|
ATTR_BATTERY_LEVEL,
|
||||||
ATTR_DEVICE_ID,
|
ATTR_DEVICE_ID,
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
|
ATTR_HW_VERSION,
|
||||||
ATTR_MANUFACTURER,
|
ATTR_MANUFACTURER,
|
||||||
ATTR_MODEL,
|
ATTR_MODEL,
|
||||||
ATTR_SW_VERSION,
|
ATTR_SW_VERSION,
|
||||||
@ -911,6 +912,8 @@ class HomeKit:
|
|||||||
config[ATTR_MODEL] = device_entry.model
|
config[ATTR_MODEL] = device_entry.model
|
||||||
if device_entry.sw_version:
|
if device_entry.sw_version:
|
||||||
config[ATTR_SW_VERSION] = device_entry.sw_version
|
config[ATTR_SW_VERSION] = device_entry.sw_version
|
||||||
|
if device_entry.hw_version:
|
||||||
|
config[ATTR_HW_VERSION] = device_entry.hw_version
|
||||||
if device_entry.config_entries:
|
if device_entry.config_entries:
|
||||||
first_entry = list(device_entry.config_entries)[0]
|
first_entry = list(device_entry.config_entries)[0]
|
||||||
if entry := self.hass.config_entries.async_get_entry(first_entry):
|
if entry := self.hass.config_entries.async_get_entry(first_entry):
|
||||||
|
@ -15,6 +15,7 @@ from homeassistant.const import (
|
|||||||
ATTR_BATTERY_LEVEL,
|
ATTR_BATTERY_LEVEL,
|
||||||
ATTR_DEVICE_CLASS,
|
ATTR_DEVICE_CLASS,
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
|
ATTR_HW_VERSION,
|
||||||
ATTR_MANUFACTURER,
|
ATTR_MANUFACTURER,
|
||||||
ATTR_MODEL,
|
ATTR_MODEL,
|
||||||
ATTR_SERVICE,
|
ATTR_SERVICE,
|
||||||
@ -43,6 +44,7 @@ from .const import (
|
|||||||
BRIDGE_SERIAL_NUMBER,
|
BRIDGE_SERIAL_NUMBER,
|
||||||
CHAR_BATTERY_LEVEL,
|
CHAR_BATTERY_LEVEL,
|
||||||
CHAR_CHARGING_STATE,
|
CHAR_CHARGING_STATE,
|
||||||
|
CHAR_HARDWARE_REVISION,
|
||||||
CHAR_STATUS_LOW_BATTERY,
|
CHAR_STATUS_LOW_BATTERY,
|
||||||
CONF_FEATURE_LIST,
|
CONF_FEATURE_LIST,
|
||||||
CONF_LINKED_BATTERY_CHARGING_SENSOR,
|
CONF_LINKED_BATTERY_CHARGING_SENSOR,
|
||||||
@ -59,6 +61,7 @@ from .const import (
|
|||||||
MAX_MODEL_LENGTH,
|
MAX_MODEL_LENGTH,
|
||||||
MAX_SERIAL_LENGTH,
|
MAX_SERIAL_LENGTH,
|
||||||
MAX_VERSION_LENGTH,
|
MAX_VERSION_LENGTH,
|
||||||
|
SERV_ACCESSORY_INFO,
|
||||||
SERV_BATTERY_SERVICE,
|
SERV_BATTERY_SERVICE,
|
||||||
SERVICE_HOMEKIT_RESET_ACCESSORY,
|
SERVICE_HOMEKIT_RESET_ACCESSORY,
|
||||||
TYPE_FAUCET,
|
TYPE_FAUCET,
|
||||||
@ -74,7 +77,7 @@ from .util import (
|
|||||||
async_show_setup_message,
|
async_show_setup_message,
|
||||||
cleanup_name_for_homekit,
|
cleanup_name_for_homekit,
|
||||||
convert_to_float,
|
convert_to_float,
|
||||||
format_sw_version,
|
format_version,
|
||||||
validate_media_player_features,
|
validate_media_player_features,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -256,7 +259,7 @@ class HomeAccessory(Accessory):
|
|||||||
domain = split_entity_id(entity_id)[0].replace("_", " ")
|
domain = split_entity_id(entity_id)[0].replace("_", " ")
|
||||||
|
|
||||||
if self.config.get(ATTR_MANUFACTURER) is not None:
|
if self.config.get(ATTR_MANUFACTURER) is not None:
|
||||||
manufacturer = self.config[ATTR_MANUFACTURER]
|
manufacturer = str(self.config[ATTR_MANUFACTURER])
|
||||||
elif self.config.get(ATTR_INTEGRATION) is not None:
|
elif self.config.get(ATTR_INTEGRATION) is not None:
|
||||||
manufacturer = self.config[ATTR_INTEGRATION].replace("_", " ").title()
|
manufacturer = self.config[ATTR_INTEGRATION].replace("_", " ").title()
|
||||||
elif domain:
|
elif domain:
|
||||||
@ -264,16 +267,19 @@ class HomeAccessory(Accessory):
|
|||||||
else:
|
else:
|
||||||
manufacturer = MANUFACTURER
|
manufacturer = MANUFACTURER
|
||||||
if self.config.get(ATTR_MODEL) is not None:
|
if self.config.get(ATTR_MODEL) is not None:
|
||||||
model = self.config[ATTR_MODEL]
|
model = str(self.config[ATTR_MODEL])
|
||||||
elif domain:
|
elif domain:
|
||||||
model = domain.title()
|
model = domain.title()
|
||||||
else:
|
else:
|
||||||
model = MANUFACTURER
|
model = MANUFACTURER
|
||||||
sw_version = None
|
sw_version = None
|
||||||
if self.config.get(ATTR_SW_VERSION) is not None:
|
if self.config.get(ATTR_SW_VERSION) is not None:
|
||||||
sw_version = format_sw_version(self.config[ATTR_SW_VERSION])
|
sw_version = format_version(self.config[ATTR_SW_VERSION])
|
||||||
if sw_version is None:
|
if sw_version is None:
|
||||||
sw_version = __version__
|
sw_version = __version__
|
||||||
|
hw_version = None
|
||||||
|
if self.config.get(ATTR_HW_VERSION) is not None:
|
||||||
|
hw_version = format_version(self.config[ATTR_HW_VERSION])
|
||||||
|
|
||||||
self.set_info_service(
|
self.set_info_service(
|
||||||
manufacturer=manufacturer[:MAX_MANUFACTURER_LENGTH],
|
manufacturer=manufacturer[:MAX_MANUFACTURER_LENGTH],
|
||||||
@ -281,6 +287,13 @@ class HomeAccessory(Accessory):
|
|||||||
serial_number=serial_number[:MAX_SERIAL_LENGTH],
|
serial_number=serial_number[:MAX_SERIAL_LENGTH],
|
||||||
firmware_revision=sw_version[:MAX_VERSION_LENGTH],
|
firmware_revision=sw_version[:MAX_VERSION_LENGTH],
|
||||||
)
|
)
|
||||||
|
if hw_version:
|
||||||
|
serv_info = self.get_service(SERV_ACCESSORY_INFO)
|
||||||
|
char = self.driver.loader.get_char(CHAR_HARDWARE_REVISION)
|
||||||
|
serv_info.add_characteristic(char)
|
||||||
|
serv_info.configure_char(CHAR_HARDWARE_REVISION, value=hw_version)
|
||||||
|
self.iid_manager.assign(char)
|
||||||
|
char.broker = self
|
||||||
|
|
||||||
self.category = category
|
self.category = category
|
||||||
self.entity_id = entity_id
|
self.entity_id = entity_id
|
||||||
|
@ -166,6 +166,7 @@ CHAR_CONTACT_SENSOR_STATE = "ContactSensorState"
|
|||||||
CHAR_COOLING_THRESHOLD_TEMPERATURE = "CoolingThresholdTemperature"
|
CHAR_COOLING_THRESHOLD_TEMPERATURE = "CoolingThresholdTemperature"
|
||||||
CHAR_CURRENT_AMBIENT_LIGHT_LEVEL = "CurrentAmbientLightLevel"
|
CHAR_CURRENT_AMBIENT_LIGHT_LEVEL = "CurrentAmbientLightLevel"
|
||||||
CHAR_CURRENT_DOOR_STATE = "CurrentDoorState"
|
CHAR_CURRENT_DOOR_STATE = "CurrentDoorState"
|
||||||
|
CHAR_CURRENT_FAN_STATE = "CurrentFanState"
|
||||||
CHAR_CURRENT_HEATING_COOLING = "CurrentHeatingCoolingState"
|
CHAR_CURRENT_HEATING_COOLING = "CurrentHeatingCoolingState"
|
||||||
CHAR_CURRENT_HUMIDIFIER_DEHUMIDIFIER = "CurrentHumidifierDehumidifierState"
|
CHAR_CURRENT_HUMIDIFIER_DEHUMIDIFIER = "CurrentHumidifierDehumidifierState"
|
||||||
CHAR_CURRENT_POSITION = "CurrentPosition"
|
CHAR_CURRENT_POSITION = "CurrentPosition"
|
||||||
@ -176,6 +177,7 @@ CHAR_CURRENT_TILT_ANGLE = "CurrentHorizontalTiltAngle"
|
|||||||
CHAR_CURRENT_VISIBILITY_STATE = "CurrentVisibilityState"
|
CHAR_CURRENT_VISIBILITY_STATE = "CurrentVisibilityState"
|
||||||
CHAR_DEHUMIDIFIER_THRESHOLD_HUMIDITY = "RelativeHumidityDehumidifierThreshold"
|
CHAR_DEHUMIDIFIER_THRESHOLD_HUMIDITY = "RelativeHumidityDehumidifierThreshold"
|
||||||
CHAR_FIRMWARE_REVISION = "FirmwareRevision"
|
CHAR_FIRMWARE_REVISION = "FirmwareRevision"
|
||||||
|
CHAR_HARDWARE_REVISION = "HardwareRevision"
|
||||||
CHAR_HEATING_THRESHOLD_TEMPERATURE = "HeatingThresholdTemperature"
|
CHAR_HEATING_THRESHOLD_TEMPERATURE = "HeatingThresholdTemperature"
|
||||||
CHAR_HUE = "Hue"
|
CHAR_HUE = "Hue"
|
||||||
CHAR_HUMIDIFIER_THRESHOLD_HUMIDITY = "RelativeHumidityHumidifierThreshold"
|
CHAR_HUMIDIFIER_THRESHOLD_HUMIDITY = "RelativeHumidityHumidifierThreshold"
|
||||||
|
@ -92,6 +92,11 @@ from .const import (
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
NUMBERS_ONLY_RE = re.compile(r"[^\d.]+")
|
||||||
|
VERSION_RE = re.compile(r"([0-9]+)(\.[0-9]+)?(\.[0-9]+)?")
|
||||||
|
|
||||||
|
|
||||||
MAX_PORT = 65535
|
MAX_PORT = 65535
|
||||||
VALID_VIDEO_CODECS = [VIDEO_CODEC_LIBX264, VIDEO_CODEC_H264_OMX, AUDIO_CODEC_COPY]
|
VALID_VIDEO_CODECS = [VIDEO_CODEC_LIBX264, VIDEO_CODEC_H264_OMX, AUDIO_CODEC_COPY]
|
||||||
VALID_AUDIO_CODECS = [AUDIO_CODEC_OPUS, VIDEO_CODEC_COPY]
|
VALID_AUDIO_CODECS = [AUDIO_CODEC_OPUS, VIDEO_CODEC_COPY]
|
||||||
@ -412,9 +417,11 @@ def get_aid_storage_fullpath_for_entry_id(hass: HomeAssistant, entry_id: str):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def format_sw_version(version):
|
def format_version(version):
|
||||||
"""Extract the version string in a format homekit can consume."""
|
"""Extract the version string in a format homekit can consume."""
|
||||||
match = re.search(r"([0-9]+)(\.[0-9]+)?(\.[0-9]+)?", str(version).replace("-", "."))
|
split_ver = str(version).replace("-", ".")
|
||||||
|
num_only = NUMBERS_ONLY_RE.sub("", split_ver)
|
||||||
|
match = VERSION_RE.search(num_only)
|
||||||
if match:
|
if match:
|
||||||
return match.group(0)
|
return match.group(0)
|
||||||
return None
|
return None
|
||||||
|
@ -19,6 +19,7 @@ from homeassistant.components.homekit.const import (
|
|||||||
BRIDGE_NAME,
|
BRIDGE_NAME,
|
||||||
BRIDGE_SERIAL_NUMBER,
|
BRIDGE_SERIAL_NUMBER,
|
||||||
CHAR_FIRMWARE_REVISION,
|
CHAR_FIRMWARE_REVISION,
|
||||||
|
CHAR_HARDWARE_REVISION,
|
||||||
CHAR_MANUFACTURER,
|
CHAR_MANUFACTURER,
|
||||||
CHAR_MODEL,
|
CHAR_MODEL,
|
||||||
CHAR_NAME,
|
CHAR_NAME,
|
||||||
@ -33,6 +34,7 @@ from homeassistant.const import (
|
|||||||
ATTR_BATTERY_CHARGING,
|
ATTR_BATTERY_CHARGING,
|
||||||
ATTR_BATTERY_LEVEL,
|
ATTR_BATTERY_LEVEL,
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
|
ATTR_HW_VERSION,
|
||||||
ATTR_MANUFACTURER,
|
ATTR_MANUFACTURER,
|
||||||
ATTR_MODEL,
|
ATTR_MODEL,
|
||||||
ATTR_SERVICE,
|
ATTR_SERVICE,
|
||||||
@ -215,6 +217,36 @@ async def test_accessory_with_missing_basic_service_info(hass, hk_driver):
|
|||||||
assert serv.get_characteristic(CHAR_MODEL).value == "Sensor"
|
assert serv.get_characteristic(CHAR_MODEL).value == "Sensor"
|
||||||
assert serv.get_characteristic(CHAR_SERIAL_NUMBER).value == entity_id
|
assert serv.get_characteristic(CHAR_SERIAL_NUMBER).value == entity_id
|
||||||
assert serv.get_characteristic(CHAR_FIRMWARE_REVISION).value == hass_version
|
assert serv.get_characteristic(CHAR_FIRMWARE_REVISION).value == hass_version
|
||||||
|
assert isinstance(acc.to_HAP(), dict)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_accessory_with_hardware_revision(hass, hk_driver):
|
||||||
|
"""Test HomeAccessory class with hardware revision."""
|
||||||
|
entity_id = "sensor.accessory"
|
||||||
|
hass.states.async_set(entity_id, "on")
|
||||||
|
acc = HomeAccessory(
|
||||||
|
hass,
|
||||||
|
hk_driver,
|
||||||
|
"Home Accessory",
|
||||||
|
entity_id,
|
||||||
|
3,
|
||||||
|
{
|
||||||
|
ATTR_MODEL: None,
|
||||||
|
ATTR_MANUFACTURER: None,
|
||||||
|
ATTR_SW_VERSION: None,
|
||||||
|
ATTR_HW_VERSION: "1.2.3",
|
||||||
|
ATTR_INTEGRATION: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
acc.driver = hk_driver
|
||||||
|
serv = acc.get_service(SERV_ACCESSORY_INFO)
|
||||||
|
assert serv.get_characteristic(CHAR_NAME).value == "Home Accessory"
|
||||||
|
assert serv.get_characteristic(CHAR_MANUFACTURER).value == "Home Assistant Sensor"
|
||||||
|
assert serv.get_characteristic(CHAR_MODEL).value == "Sensor"
|
||||||
|
assert serv.get_characteristic(CHAR_SERIAL_NUMBER).value == entity_id
|
||||||
|
assert serv.get_characteristic(CHAR_FIRMWARE_REVISION).value == hass_version
|
||||||
|
assert serv.get_characteristic(CHAR_HARDWARE_REVISION).value == "1.2.3"
|
||||||
|
assert isinstance(acc.to_HAP(), dict)
|
||||||
|
|
||||||
|
|
||||||
async def test_battery_service(hass, hk_driver, caplog):
|
async def test_battery_service(hass, hk_driver, caplog):
|
||||||
|
@ -1104,6 +1104,7 @@ async def test_homekit_finds_linked_batteries(
|
|||||||
device_entry = device_reg.async_get_or_create(
|
device_entry = device_reg.async_get_or_create(
|
||||||
config_entry_id=config_entry.entry_id,
|
config_entry_id=config_entry.entry_id,
|
||||||
sw_version="0.16.0",
|
sw_version="0.16.0",
|
||||||
|
hw_version="2.34",
|
||||||
model="Powerwall 2",
|
model="Powerwall 2",
|
||||||
manufacturer="Tesla",
|
manufacturer="Tesla",
|
||||||
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
||||||
@ -1152,6 +1153,7 @@ async def test_homekit_finds_linked_batteries(
|
|||||||
"manufacturer": "Tesla",
|
"manufacturer": "Tesla",
|
||||||
"model": "Powerwall 2",
|
"model": "Powerwall 2",
|
||||||
"sw_version": "0.16.0",
|
"sw_version": "0.16.0",
|
||||||
|
"hw_version": "2.34",
|
||||||
"platform": "test",
|
"platform": "test",
|
||||||
"linked_battery_charging_sensor": "binary_sensor.powerwall_battery_charging",
|
"linked_battery_charging_sensor": "binary_sensor.powerwall_battery_charging",
|
||||||
"linked_battery_sensor": "sensor.powerwall_battery",
|
"linked_battery_sensor": "sensor.powerwall_battery",
|
||||||
|
@ -32,7 +32,7 @@ from homeassistant.components.homekit.util import (
|
|||||||
cleanup_name_for_homekit,
|
cleanup_name_for_homekit,
|
||||||
convert_to_float,
|
convert_to_float,
|
||||||
density_to_air_quality,
|
density_to_air_quality,
|
||||||
format_sw_version,
|
format_version,
|
||||||
state_needs_accessory_mode,
|
state_needs_accessory_mode,
|
||||||
temperature_to_homekit,
|
temperature_to_homekit,
|
||||||
temperature_to_states,
|
temperature_to_states,
|
||||||
@ -343,13 +343,17 @@ async def test_port_is_available_skips_existing_entries(hass):
|
|||||||
async_find_next_available_port(hass, 65530)
|
async_find_next_available_port(hass, 65530)
|
||||||
|
|
||||||
|
|
||||||
async def test_format_sw_version():
|
async def test_format_version():
|
||||||
"""Test format_sw_version method."""
|
"""Test format_version method."""
|
||||||
assert format_sw_version("soho+3.6.8+soho-release-rt120+10") == "3.6.8"
|
assert format_version("soho+3.6.8+soho-release-rt120+10") == "3.6.8"
|
||||||
assert format_sw_version("undefined-undefined-1.6.8") == "1.6.8"
|
assert format_version("undefined-undefined-1.6.8") == "1.6.8"
|
||||||
assert format_sw_version("56.0-76060") == "56.0.76060"
|
assert format_version("56.0-76060") == "56.0.76060"
|
||||||
assert format_sw_version(3.6) == "3.6"
|
assert format_version(3.6) == "3.6"
|
||||||
assert format_sw_version("unknown") is None
|
assert format_version("AK001-ZJ100") == "001.100"
|
||||||
|
assert format_version("HF-LPB100-") == "100"
|
||||||
|
assert format_version("AK001-ZJ2149") == "001.2149"
|
||||||
|
assert format_version("0.1") == "0.1"
|
||||||
|
assert format_version("unknown") is None
|
||||||
|
|
||||||
|
|
||||||
async def test_accessory_friendly_name():
|
async def test_accessory_friendly_name():
|
||||||
|
Loading…
x
Reference in New Issue
Block a user