Add hardware revision support to homekit (#63336)

This commit is contained in:
J. Nick Koston 2022-01-04 05:19:12 -10:00 committed by GitHub
parent 7c6297db86
commit 5c8271552a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 77 additions and 14 deletions

View File

@ -26,6 +26,7 @@ from homeassistant.const import (
ATTR_BATTERY_LEVEL,
ATTR_DEVICE_ID,
ATTR_ENTITY_ID,
ATTR_HW_VERSION,
ATTR_MANUFACTURER,
ATTR_MODEL,
ATTR_SW_VERSION,
@ -911,6 +912,8 @@ class HomeKit:
config[ATTR_MODEL] = device_entry.model
if 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:
first_entry = list(device_entry.config_entries)[0]
if entry := self.hass.config_entries.async_get_entry(first_entry):

View File

@ -15,6 +15,7 @@ from homeassistant.const import (
ATTR_BATTERY_LEVEL,
ATTR_DEVICE_CLASS,
ATTR_ENTITY_ID,
ATTR_HW_VERSION,
ATTR_MANUFACTURER,
ATTR_MODEL,
ATTR_SERVICE,
@ -43,6 +44,7 @@ from .const import (
BRIDGE_SERIAL_NUMBER,
CHAR_BATTERY_LEVEL,
CHAR_CHARGING_STATE,
CHAR_HARDWARE_REVISION,
CHAR_STATUS_LOW_BATTERY,
CONF_FEATURE_LIST,
CONF_LINKED_BATTERY_CHARGING_SENSOR,
@ -59,6 +61,7 @@ from .const import (
MAX_MODEL_LENGTH,
MAX_SERIAL_LENGTH,
MAX_VERSION_LENGTH,
SERV_ACCESSORY_INFO,
SERV_BATTERY_SERVICE,
SERVICE_HOMEKIT_RESET_ACCESSORY,
TYPE_FAUCET,
@ -74,7 +77,7 @@ from .util import (
async_show_setup_message,
cleanup_name_for_homekit,
convert_to_float,
format_sw_version,
format_version,
validate_media_player_features,
)
@ -256,7 +259,7 @@ class HomeAccessory(Accessory):
domain = split_entity_id(entity_id)[0].replace("_", " ")
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:
manufacturer = self.config[ATTR_INTEGRATION].replace("_", " ").title()
elif domain:
@ -264,16 +267,19 @@ class HomeAccessory(Accessory):
else:
manufacturer = MANUFACTURER
if self.config.get(ATTR_MODEL) is not None:
model = self.config[ATTR_MODEL]
model = str(self.config[ATTR_MODEL])
elif domain:
model = domain.title()
else:
model = MANUFACTURER
sw_version = 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:
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(
manufacturer=manufacturer[:MAX_MANUFACTURER_LENGTH],
@ -281,6 +287,13 @@ class HomeAccessory(Accessory):
serial_number=serial_number[:MAX_SERIAL_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.entity_id = entity_id

View File

@ -166,6 +166,7 @@ CHAR_CONTACT_SENSOR_STATE = "ContactSensorState"
CHAR_COOLING_THRESHOLD_TEMPERATURE = "CoolingThresholdTemperature"
CHAR_CURRENT_AMBIENT_LIGHT_LEVEL = "CurrentAmbientLightLevel"
CHAR_CURRENT_DOOR_STATE = "CurrentDoorState"
CHAR_CURRENT_FAN_STATE = "CurrentFanState"
CHAR_CURRENT_HEATING_COOLING = "CurrentHeatingCoolingState"
CHAR_CURRENT_HUMIDIFIER_DEHUMIDIFIER = "CurrentHumidifierDehumidifierState"
CHAR_CURRENT_POSITION = "CurrentPosition"
@ -176,6 +177,7 @@ CHAR_CURRENT_TILT_ANGLE = "CurrentHorizontalTiltAngle"
CHAR_CURRENT_VISIBILITY_STATE = "CurrentVisibilityState"
CHAR_DEHUMIDIFIER_THRESHOLD_HUMIDITY = "RelativeHumidityDehumidifierThreshold"
CHAR_FIRMWARE_REVISION = "FirmwareRevision"
CHAR_HARDWARE_REVISION = "HardwareRevision"
CHAR_HEATING_THRESHOLD_TEMPERATURE = "HeatingThresholdTemperature"
CHAR_HUE = "Hue"
CHAR_HUMIDIFIER_THRESHOLD_HUMIDITY = "RelativeHumidityHumidifierThreshold"

View File

@ -92,6 +92,11 @@ from .const import (
_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
VALID_VIDEO_CODECS = [VIDEO_CODEC_LIBX264, VIDEO_CODEC_H264_OMX, AUDIO_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."""
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:
return match.group(0)
return None

View File

@ -19,6 +19,7 @@ from homeassistant.components.homekit.const import (
BRIDGE_NAME,
BRIDGE_SERIAL_NUMBER,
CHAR_FIRMWARE_REVISION,
CHAR_HARDWARE_REVISION,
CHAR_MANUFACTURER,
CHAR_MODEL,
CHAR_NAME,
@ -33,6 +34,7 @@ from homeassistant.const import (
ATTR_BATTERY_CHARGING,
ATTR_BATTERY_LEVEL,
ATTR_ENTITY_ID,
ATTR_HW_VERSION,
ATTR_MANUFACTURER,
ATTR_MODEL,
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_SERIAL_NUMBER).value == entity_id
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):

View File

@ -1104,6 +1104,7 @@ async def test_homekit_finds_linked_batteries(
device_entry = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
sw_version="0.16.0",
hw_version="2.34",
model="Powerwall 2",
manufacturer="Tesla",
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",
"model": "Powerwall 2",
"sw_version": "0.16.0",
"hw_version": "2.34",
"platform": "test",
"linked_battery_charging_sensor": "binary_sensor.powerwall_battery_charging",
"linked_battery_sensor": "sensor.powerwall_battery",

View File

@ -32,7 +32,7 @@ from homeassistant.components.homekit.util import (
cleanup_name_for_homekit,
convert_to_float,
density_to_air_quality,
format_sw_version,
format_version,
state_needs_accessory_mode,
temperature_to_homekit,
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 def test_format_sw_version():
"""Test format_sw_version method."""
assert format_sw_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_sw_version("56.0-76060") == "56.0.76060"
assert format_sw_version(3.6) == "3.6"
assert format_sw_version("unknown") is None
async def test_format_version():
"""Test format_version method."""
assert format_version("soho+3.6.8+soho-release-rt120+10") == "3.6.8"
assert format_version("undefined-undefined-1.6.8") == "1.6.8"
assert format_version("56.0-76060") == "56.0.76060"
assert format_version(3.6) == "3.6"
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():