From 01116d1ff5b5ea66bba6d73ad4089a2c38f03ffe Mon Sep 17 00:00:00 2001 From: InTheDaylight14 <67400055+InTheDaylight14@users.noreply.github.com> Date: Thu, 8 Dec 2022 23:50:32 -0500 Subject: [PATCH] feat: add valet mode switch (#405) closes #355 --- custom_components/tesla_custom/manifest.json | 2 +- custom_components/tesla_custom/switch.py | 37 +++++++++++++++ poetry.lock | 8 ++-- pyproject.toml | 2 +- tests/mock_data/car.py | 2 +- tests/test_switch.py | 48 ++++++++++++++++++++ 6 files changed, 92 insertions(+), 7 deletions(-) diff --git a/custom_components/tesla_custom/manifest.json b/custom_components/tesla_custom/manifest.json index 0143a087..c8f7affb 100644 --- a/custom_components/tesla_custom/manifest.json +++ b/custom_components/tesla_custom/manifest.json @@ -5,7 +5,7 @@ "documentation": "https://github.com/alandtse/tesla/wiki", "issue_tracker": "https://github.com/alandtse/tesla/issues", "requirements": [ - "teslajsonpy==3.3.0" + "teslajsonpy==3.4.1" ], "codeowners": [ "@alandtse" diff --git a/custom_components/tesla_custom/switch.py b/custom_components/tesla_custom/switch.py index 2c9e91da..7a7016d6 100644 --- a/custom_components/tesla_custom/switch.py +++ b/custom_components/tesla_custom/switch.py @@ -25,6 +25,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entitie entities.append(TeslaCarSentryMode(hass, car, coordinator)) entities.append(TeslaCarPolling(hass, car, coordinator)) entities.append(TeslaCarCharger(hass, car, coordinator)) + entities.append(TeslaCarValetMode(hass, car, coordinator)) async_add_entities(entities, True) @@ -173,3 +174,39 @@ async def async_turn_off(self, **kwargs): """Send the off command.""" await self._car.set_sentry_mode(False) await self.async_update_ha_state() + + +class TeslaCarValetMode(TeslaCarEntity, SwitchEntity): + """Representation of a Tesla car valet mode switch.""" + + def __init__( + self, + hass: HomeAssistant, + car: TeslaCar, + coordinator: TeslaDataUpdateCoordinator, + ) -> None: + """Initialize valet mode switch entity.""" + super().__init__(hass, car, coordinator) + self.type = "valet mode" + self._attr_icon = "mdi:room-service" + + @property + def is_on(self): + """Return valet mode state.""" + return self._car.is_valet_mode + + async def async_turn_on(self, **kwargs): + """Send the on command.""" + if self._car._vehicle_data.get("vehicle_state", {}).get("valet_pin_needed"): + _LOGGER.debug("Pin required for valet mode, set pin in vehicle or app.") + else: + await self._car.valet_mode(True) + await self.async_update_ha_state() + + async def async_turn_off(self, **kwargs): + """Send the off command.""" + if self._car._vehicle_data.get("vehicle_state", {}).get("valet_pin_needed"): + _LOGGER.debug("Pin required for valet mode, set pin in vehicle or app.") + else: + await self._car.valet_mode(False) + await self.async_update_ha_state() diff --git a/poetry.lock b/poetry.lock index 113dcff1..79855285 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1506,7 +1506,7 @@ tests = ["pytest", "pytest-cov"] [[package]] name = "teslajsonpy" -version = "3.3.0" +version = "3.4.1" description = "A library to work with Tesla API." category = "main" optional = false @@ -1678,7 +1678,7 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "39913e7746e29d1b89ca54649e2919e1bba0f83925b52e2bceff6ba63bc08bd0" +content-hash = "8c8d7db623863241944928cd489ba210a169fa21d270293f7d7eea493d295320" [metadata.files] aiohttp = [ @@ -2927,8 +2927,8 @@ termcolor = [ {file = "termcolor-2.1.1.tar.gz", hash = "sha256:67cee2009adc6449c650f6bcf3bdeed00c8ba53a8cda5362733c53e0a39fb70b"}, ] teslajsonpy = [ - {file = "teslajsonpy-3.3.0-py3-none-any.whl", hash = "sha256:2eb94c71c3d672adf4acde29a4b92a0aa9c5fe6c1749282ad97dc660675aeaf3"}, - {file = "teslajsonpy-3.3.0.tar.gz", hash = "sha256:1b314badd75346149b188d904faa77d04407311a0d5d6343eebce4c19d7cec7e"}, + {file = "teslajsonpy-3.4.1-py3-none-any.whl", hash = "sha256:ef80d16460f347e582e331246311ad8973bbdbdf4bc1002b32c1cfb55da0fa6f"}, + {file = "teslajsonpy-3.4.1.tar.gz", hash = "sha256:6ff768e522def412a96b2c0cda295b91fbdc309aecad75815bd3e57067b41a75"}, ] text-unidecode = [ {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, diff --git a/pyproject.toml b/pyproject.toml index 386a4b94..5cc25c44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ license = "Apache-2.0" [tool.poetry.dependencies] python = "^3.10" -teslajsonpy = "^3.3.0" +teslajsonpy = "^3.4.1" [tool.poetry.dev-dependencies] homeassistant = ">=2021.10.0" diff --git a/tests/mock_data/car.py b/tests/mock_data/car.py index 3d4c1f44..2454d1bc 100644 --- a/tests/mock_data/car.py +++ b/tests/mock_data/car.py @@ -246,7 +246,7 @@ "tpms_soft_warning_rl": False, "tpms_soft_warning_rr": False, "valet_mode": False, - "valet_pin_needed": True, + "valet_pin_needed": False, "vehicle_name": "My Model S", "sentry_mode": True, "sentry_mode_available": True, diff --git a/tests/test_switch.py b/tests/test_switch.py index 3e615944..cc995bad 100644 --- a/tests/test_switch.py +++ b/tests/test_switch.py @@ -27,6 +27,9 @@ async def test_registry_entries(hass: HomeAssistant) -> None: entry = entity_registry.async_get("switch.my_model_s_sentry_mode") assert entry.unique_id == f"{car_mock_data.VIN.lower()}_sentry_mode" + entry = entity_registry.async_get("switch.my_model_s_valet_mode") + assert entry.unique_id == f"{car_mock_data.VIN.lower()}_valet_mode" + async def test_enabled_by_default(hass: HomeAssistant) -> None: """Tests devices are registered in the entity registry.""" @@ -45,6 +48,9 @@ async def test_enabled_by_default(hass: HomeAssistant) -> None: entry = entity_registry.async_get("switch.my_model_s_sentry_mode") assert not entry.disabled + entry = entity_registry.async_get("switch.my_model_s_valet_mode") + assert not entry.disabled + async def test_disabled_by_default(hass: HomeAssistant) -> None: """Tests devices are disabled by default when appropriate.""" @@ -150,3 +156,45 @@ async def test_sentry_mode(hass: HomeAssistant) -> None: blocking=True, ) mock_set_sentry_mode.assert_awaited_with(False) + + +async def test_valet_mode(hass: HomeAssistant) -> None: + """Tests car valet mode switch.""" + car_mock_data.VEHICLE_DATA["vehicle_state"]["sentry_mode_available"] = True + await setup_platform(hass, SWITCH_DOMAIN) + + with patch("teslajsonpy.car.TeslaCar.valet_mode") as mock_valet_mode: + # Test switch on + assert await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.my_model_s_valet_mode"}, + blocking=True, + ) + mock_valet_mode.assert_awaited_once_with(True) + # Test switch off + assert await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "switch.my_model_s_valet_mode"}, + blocking=True, + ) + mock_valet_mode.assert_awaited_with(False) + + with patch("teslajsonpy.car.TeslaCar.valet_mode") as mock_pin_required: + # Test pin required + car_mock_data.VEHICLE_DATA["vehicle_state"]["valet_pin_needed"] = True + assert await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.my_model_s_valet_mode"}, + blocking=True, + ) + mock_pin_required.assert_not_awaited() + assert await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "switch.my_model_s_valet_mode"}, + blocking=True, + ) + mock_pin_required.assert_not_awaited()