Add guards for HomeKit version/names that break apple watches (#67585)
This commit is contained in:
parent
e7ca6b6e38
commit
6d2302b703
5 changed files with 56 additions and 17 deletions
|
@ -274,7 +274,7 @@ class HomeAccessory(Accessory):
|
|||
if self.config.get(ATTR_SW_VERSION) is not None:
|
||||
sw_version = format_version(self.config[ATTR_SW_VERSION])
|
||||
if sw_version is None:
|
||||
sw_version = __version__
|
||||
sw_version = format_version(__version__)
|
||||
hw_version = None
|
||||
if self.config.get(ATTR_HW_VERSION) is not None:
|
||||
hw_version = format_version(self.config[ATTR_HW_VERSION])
|
||||
|
@ -289,7 +289,9 @@ class HomeAccessory(Accessory):
|
|||
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)
|
||||
serv_info.configure_char(
|
||||
CHAR_HARDWARE_REVISION, value=hw_version[:MAX_VERSION_LENGTH]
|
||||
)
|
||||
self.iid_manager.assign(char)
|
||||
char.broker = self
|
||||
|
||||
|
@ -532,7 +534,7 @@ class HomeBridge(Bridge):
|
|||
"""Initialize a Bridge object."""
|
||||
super().__init__(driver, name)
|
||||
self.set_info_service(
|
||||
firmware_revision=__version__,
|
||||
firmware_revision=format_version(__version__),
|
||||
manufacturer=MANUFACTURER,
|
||||
model=BRIDGE_MODEL,
|
||||
serial_number=BRIDGE_SERIAL_NUMBER,
|
||||
|
|
|
@ -100,6 +100,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||
|
||||
NUMBERS_ONLY_RE = re.compile(r"[^\d.]+")
|
||||
VERSION_RE = re.compile(r"([0-9]+)(\.[0-9]+)?(\.[0-9]+)?")
|
||||
MAX_VERSION_PART = 2**32 - 1
|
||||
|
||||
|
||||
MAX_PORT = 65535
|
||||
|
@ -363,7 +364,15 @@ def convert_to_float(state):
|
|||
return None
|
||||
|
||||
|
||||
def cleanup_name_for_homekit(name: str | None) -> str | None:
|
||||
def coerce_int(state: str) -> int:
|
||||
"""Return int."""
|
||||
try:
|
||||
return int(state)
|
||||
except (ValueError, TypeError):
|
||||
return 0
|
||||
|
||||
|
||||
def cleanup_name_for_homekit(name: str | None) -> str:
|
||||
"""Ensure the name of the device will not crash homekit."""
|
||||
#
|
||||
# This is not a security measure.
|
||||
|
@ -371,7 +380,7 @@ def cleanup_name_for_homekit(name: str | None) -> str | None:
|
|||
# UNICODE_EMOJI is also not allowed but that
|
||||
# likely isn't a problem
|
||||
if name is None:
|
||||
return None
|
||||
return "None" # None crashes apple watches
|
||||
return name.translate(HOMEKIT_CHAR_TRANSLATIONS)[:MAX_NAME_LENGTH]
|
||||
|
||||
|
||||
|
@ -420,13 +429,23 @@ def get_aid_storage_fullpath_for_entry_id(hass: HomeAssistant, entry_id: str):
|
|||
)
|
||||
|
||||
|
||||
def _format_version_part(version_part: str) -> str:
|
||||
return str(max(0, min(MAX_VERSION_PART, coerce_int(version_part))))
|
||||
|
||||
|
||||
def format_version(version):
|
||||
"""Extract the version string in a format homekit can consume."""
|
||||
split_ver = str(version).replace("-", ".")
|
||||
split_ver = str(version).replace("-", ".").replace(" ", ".")
|
||||
num_only = NUMBERS_ONLY_RE.sub("", split_ver)
|
||||
if match := VERSION_RE.search(num_only):
|
||||
return match.group(0)
|
||||
return None
|
||||
if (match := VERSION_RE.search(num_only)) is None:
|
||||
return None
|
||||
value = ".".join(map(_format_version_part, match.group(0).split(".")))
|
||||
return None if _is_zero_but_true(value) else value
|
||||
|
||||
|
||||
def _is_zero_but_true(value):
|
||||
"""Zero but true values can crash apple watches."""
|
||||
return convert_to_float(value) == 0
|
||||
|
||||
|
||||
def remove_state_files_for_entry_id(hass: HomeAssistant, entry_id: str):
|
||||
|
|
|
@ -42,7 +42,6 @@ from homeassistant.const import (
|
|||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_UNAVAILABLE,
|
||||
__version__,
|
||||
__version__ as hass_version,
|
||||
)
|
||||
from homeassistant.helpers.event import TRACK_STATE_CHANGE_CALLBACKS
|
||||
|
@ -166,7 +165,9 @@ async def test_home_accessory(hass, hk_driver):
|
|||
serv.get_characteristic(CHAR_SERIAL_NUMBER).value
|
||||
== "light.accessory_that_exceeds_the_maximum_maximum_maximum_maximum"
|
||||
)
|
||||
assert serv.get_characteristic(CHAR_FIRMWARE_REVISION).value == hass_version
|
||||
assert hass_version.startswith(
|
||||
serv.get_characteristic(CHAR_FIRMWARE_REVISION).value
|
||||
)
|
||||
|
||||
hass.states.async_set(entity_id, "on")
|
||||
await hass.async_block_till_done()
|
||||
|
@ -216,7 +217,9 @@ async def test_accessory_with_missing_basic_service_info(hass, hk_driver):
|
|||
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 hass_version.startswith(
|
||||
serv.get_characteristic(CHAR_FIRMWARE_REVISION).value
|
||||
)
|
||||
assert isinstance(acc.to_HAP(), dict)
|
||||
|
||||
|
||||
|
@ -244,7 +247,9 @@ async def test_accessory_with_hardware_revision(hass, hk_driver):
|
|||
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 hass_version.startswith(
|
||||
serv.get_characteristic(CHAR_FIRMWARE_REVISION).value
|
||||
)
|
||||
assert serv.get_characteristic(CHAR_HARDWARE_REVISION).value == "1.2.3"
|
||||
assert isinstance(acc.to_HAP(), dict)
|
||||
|
||||
|
@ -687,7 +692,9 @@ def test_home_bridge(hk_driver):
|
|||
serv = bridge.services[0] # SERV_ACCESSORY_INFO
|
||||
assert serv.display_name == SERV_ACCESSORY_INFO
|
||||
assert serv.get_characteristic(CHAR_NAME).value == BRIDGE_NAME
|
||||
assert serv.get_characteristic(CHAR_FIRMWARE_REVISION).value == __version__
|
||||
assert hass_version.startswith(
|
||||
serv.get_characteristic(CHAR_FIRMWARE_REVISION).value
|
||||
)
|
||||
assert serv.get_characteristic(CHAR_MANUFACTURER).value == MANUFACTURER
|
||||
assert serv.get_characteristic(CHAR_MODEL).value == BRIDGE_MODEL
|
||||
assert serv.get_characteristic(CHAR_SERIAL_NUMBER).value == BRIDGE_SERIAL_NUMBER
|
||||
|
|
|
@ -399,4 +399,4 @@ async def test_empty_name(hass, hk_driver):
|
|||
assert acc.category == 10 # Sensor
|
||||
|
||||
assert acc.char_humidity.value == 20
|
||||
assert acc.display_name is None
|
||||
assert acc.display_name == "None"
|
||||
|
|
|
@ -30,6 +30,7 @@ from homeassistant.components.homekit.util import (
|
|||
async_port_is_available,
|
||||
async_show_setup_message,
|
||||
cleanup_name_for_homekit,
|
||||
coerce_int,
|
||||
convert_to_float,
|
||||
density_to_air_quality,
|
||||
format_version,
|
||||
|
@ -349,13 +350,23 @@ async def test_format_version():
|
|||
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("AK001-ZJ100") == "1.100"
|
||||
assert format_version("HF-LPB100-") == "100"
|
||||
assert format_version("AK001-ZJ2149") == "001.2149"
|
||||
assert format_version("AK001-ZJ2149") == "1.2149"
|
||||
assert format_version("13216407885") == "4294967295" # max value
|
||||
assert format_version("000132 16407885") == "132.16407885"
|
||||
assert format_version("0.1") == "0.1"
|
||||
assert format_version("0") is None
|
||||
assert format_version("unknown") is None
|
||||
|
||||
|
||||
async def test_coerce_int():
|
||||
"""Test coerce_int method."""
|
||||
assert coerce_int("1") == 1
|
||||
assert coerce_int("") == 0
|
||||
assert coerce_int(0) == 0
|
||||
|
||||
|
||||
async def test_accessory_friendly_name():
|
||||
"""Test we provide a helpful friendly name."""
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue