From 20e18b8e56b6a300e18dc1938e7d634ea87c3dc4 Mon Sep 17 00:00:00 2001 From: KiraPC Date: Mon, 16 Dec 2024 14:31:22 +0100 Subject: [PATCH 1/4] add new config option host to workaround 403 on .com APIs --- custom_components/switchbotremote/__init__.py | 2 +- .../switchbotremote/client/__init__.py | 6 +- .../switchbotremote/client/client.py | 10 ++- .../switchbotremote/config_flow.py | 79 ++++++++++++------- 4 files changed, 62 insertions(+), 35 deletions(-) diff --git a/custom_components/switchbotremote/__init__.py b/custom_components/switchbotremote/__init__.py index fce3c3f..71f262b 100644 --- a/custom_components/switchbotremote/__init__.py +++ b/custom_components/switchbotremote/__init__.py @@ -32,7 +32,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: entry.add_update_listener(update_listener) - switchbot = SwitchBot(token=entry.data["token"], secret=entry.data["secret"]) + switchbot = SwitchBot(token=entry.data["token"], secret=entry.data["secret"], host=entry.data["host"]) remotes = await hass.async_add_executor_job(switchbot.remotes) _LOGGER.debug(f"Configuring remotes: {remotes}") diff --git a/custom_components/switchbotremote/client/__init__.py b/custom_components/switchbotremote/client/__init__.py index 56f47fc..f33b161 100644 --- a/custom_components/switchbotremote/client/__init__.py +++ b/custom_components/switchbotremote/client/__init__.py @@ -1,7 +1,7 @@ import uuid from typing import List -from .client import SwitchBotClient +from .client import SwitchBotClient, switchbot_host from .remote import Remote from homeassistant.exceptions import ServiceValidationError @@ -10,8 +10,8 @@ class SwitchBot: - def __init__(self, token: str, secret: str): - self.client = SwitchBotClient(token, secret, nonce=str(uuid.uuid4())) + def __init__(self, token: str, secret: str, host=switchbot_host): + self.client = SwitchBotClient(token, secret, nonce=str(uuid.uuid4()), host=host) def remotes(self) -> List[Remote]: response = self.client.get("devices") diff --git a/custom_components/switchbotremote/client/client.py b/custom_components/switchbotremote/client/client.py index f972b4c..93edbcc 100644 --- a/custom_components/switchbotremote/client/client.py +++ b/custom_components/switchbotremote/client/client.py @@ -12,13 +12,15 @@ from homeassistant.exceptions import HomeAssistantError _LOGGER = logging.getLogger(__name__) -switchbot_host = "https://api.switch-bot.com/v1.1" +switchbot_host = "https://api.switch-bot.com" +api_version = "v1.1" MAX_TRIES = 5 DELAY_BETWEEN_TRIES_MS = 500 class SwitchBotClient: - def __init__(self, token: str, secret: str, nonce: str): + def __init__(self, token: str, secret: str, nonce: str, host=switchbot_host): + self._host = host self._token = token self._secret = secret self._nonce = nonce @@ -45,7 +47,7 @@ def headers(self): return headers def __request(self, method: str, path: str, **kwargs) -> Any: - url = f"{switchbot_host}/{path}" + url = f"{self._host}/{api_version}/{path}" _LOGGER.debug(f"Calling service {url}") response = request(method, url, headers=self.headers, **kwargs) @@ -63,7 +65,7 @@ def __request(self, method: str, path: str, **kwargs) -> Any: _LOGGER.debug(f"Call service {url} OK") return response_in_json - + def request(self, method: str, path: str, maxNumberOfTrials: int = MAX_TRIES, delayMSBetweenTrials: int = DELAY_BETWEEN_TRIES_MS, **kwargs) -> Any: """Try to send the request. If the server returns a 500 Internal error status, will retry until it succeeds or it passes a threshold of max number of tries. diff --git a/custom_components/switchbotremote/config_flow.py b/custom_components/switchbotremote/config_flow.py index 79a2cb2..04e42ab 100644 --- a/custom_components/switchbotremote/config_flow.py +++ b/custom_components/switchbotremote/config_flow.py @@ -7,43 +7,40 @@ import voluptuous as vol from homeassistant import config_entries +from homeassistant.components.climate.const import HVACMode from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import FlowResult -from homeassistant.exceptions import HomeAssistantError +from homeassistant.exceptions import ConfigEntryAuthFailed, HomeAssistantError from homeassistant.helpers.selector import selector -from homeassistant.components.climate.const import HVACMode -from homeassistant.exceptions import ConfigEntryAuthFailed -from .client import SwitchBot +from .client import SwitchBot from .const import ( - DOMAIN, - CLASS_BY_TYPE, - AIR_CONDITIONER_CLASS, - FAN_CLASS, - LIGHT_CLASS, - MEDIA_CLASS, CAMERA_CLASS, - VACUUM_CLASS, - WATER_HEATER_CLASS, - OTHERS_CLASS, - - CONF_POWER_SENSOR, - CONF_TEMPERATURE_SENSOR, + CLASS_BY_TYPE, + CONF_CUSTOMIZE_COMMANDS, CONF_HUMIDITY_SENSOR, - CONF_TEMP_MIN, + CONF_HVAC_MODES, + CONF_OFF_COMMAND, + CONF_ON_COMMAND, + CONF_OVERRIDE_OFF_COMMAND, + CONF_POWER_SENSOR, CONF_TEMP_MAX, + CONF_TEMP_MIN, CONF_TEMP_STEP, - CONF_HVAC_MODES, - CONF_CUSTOMIZE_COMMANDS, - CONF_WITH_SPEED, - CONF_WITH_ION, - CONF_WITH_TIMER, + CONF_TEMPERATURE_SENSOR, CONF_WITH_BRIGHTNESS, + CONF_WITH_ION, + CONF_WITH_SPEED, CONF_WITH_TEMPERATURE, - CONF_ON_COMMAND, - CONF_OFF_COMMAND, - CONF_OVERRIDE_OFF_COMMAND, + CONF_WITH_TIMER, + DOMAIN, + FAN_CLASS, + LIGHT_CLASS, + MEDIA_CLASS, + OTHERS_CLASS, + VACUUM_CLASS, + WATER_HEATER_CLASS, ) DEFAULT_HVAC_MODES = [ @@ -68,6 +65,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema( { + vol.Required("host", default="https://api.switch-bot.com"): str, vol.Required("name"): str, vol.Required("token"): str, vol.Required("secret"): str, @@ -126,7 +124,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]: - switchbot = SwitchBot(token=data["token"], secret=data["secret"]) + switchbot = SwitchBot(token=data["token"], secret=data["secret"], host=data["host"]) try: remotes = await hass.async_add_executor_job(switchbot.remotes) @@ -147,6 +145,30 @@ def async_get_options_flow(config_entry): """Get options flow for this handler.""" return OptionsFlowHandler(config_entry) + async def async_step_reconfigure(self, user_input: dict[str, Any] | None = None): + if user_input is not None: + name = user_input["name"] + uniq_id = f"switchbot_remote_{name}" + await self.async_set_unique_id(uniq_id) + return self.async_update_reload_and_abort( + self._get_reconfigure_entry(), + data_updates=user_input, + ) + + old_entry = self._get_reconfigure_entry() + + return self.async_show_form( + step_id="reconfigure", + data_schema=vol.Schema( + { + vol.Required("host", default=old_entry.data['host'] or "https://api.switch-bot.com"): str, + vol.Required("name", default=old_entry.data['name']): str, + vol.Required("token", default=old_entry.data['token']): str, + vol.Required("secret", default=old_entry.data['secret']): str, + } + ) + ) + async def async_step_user(self, user_input: dict[str, Any] | None = None) -> FlowResult: """Handle the initial step.""" errors: dict[str, str] = {} @@ -178,7 +200,10 @@ def __init__(self, config_entry: config_entries.ConfigEntry) -> None: self.data = config_entry.data self.sb = SwitchBot( - token=self.data["token"], secret=self.data["secret"]) + token=self.data["token"], + secret=self.data["secret"], + host=self.data["host"] or "https://api.switch-bot.com" + ) self.discovered_devices = [] self.selected_device = None From f6d1e8c9fb19ac0b90d083deeb030f1b6f015a40 Mon Sep 17 00:00:00 2001 From: KiraPC Date: Mon, 16 Dec 2024 14:38:49 +0100 Subject: [PATCH 2/4] fix issue of gett new host param --- custom_components/switchbotremote/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/switchbotremote/config_flow.py b/custom_components/switchbotremote/config_flow.py index 04e42ab..47a0a0d 100644 --- a/custom_components/switchbotremote/config_flow.py +++ b/custom_components/switchbotremote/config_flow.py @@ -161,7 +161,7 @@ async def async_step_reconfigure(self, user_input: dict[str, Any] | None = None) step_id="reconfigure", data_schema=vol.Schema( { - vol.Required("host", default=old_entry.data['host'] or "https://api.switch-bot.com"): str, + vol.Required("host", default=old_entry.data.get('host', "https://api.switch-bot.com")): str, vol.Required("name", default=old_entry.data['name']): str, vol.Required("token", default=old_entry.data['token']): str, vol.Required("secret", default=old_entry.data['secret']): str, From 1b76d4d6ea11555e25424f9ed2d2215ba780ac53 Mon Sep 17 00:00:00 2001 From: KiraPC Date: Tue, 17 Dec 2024 11:30:11 +0100 Subject: [PATCH 3/4] get host key from config entry optionally --- custom_components/switchbotremote/__init__.py | 8 ++++++-- custom_components/switchbotremote/config_flow.py | 8 ++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/custom_components/switchbotremote/__init__.py b/custom_components/switchbotremote/__init__.py index 71f262b..10eda4a 100644 --- a/custom_components/switchbotremote/__init__.py +++ b/custom_components/switchbotremote/__init__.py @@ -5,7 +5,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant -from .client import SwitchBot +from .client import SwitchBot, switchbot_host from .const import DOMAIN from homeassistant.helpers import ( @@ -32,7 +32,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: entry.add_update_listener(update_listener) - switchbot = SwitchBot(token=entry.data["token"], secret=entry.data["secret"], host=entry.data["host"]) + switchbot = SwitchBot( + token=entry.data["token"], + secret=entry.data["secret"], + host=entry.data.get("host", switchbot_host) + ) remotes = await hass.async_add_executor_job(switchbot.remotes) _LOGGER.debug(f"Configuring remotes: {remotes}") diff --git a/custom_components/switchbotremote/config_flow.py b/custom_components/switchbotremote/config_flow.py index 47a0a0d..08723c1 100644 --- a/custom_components/switchbotremote/config_flow.py +++ b/custom_components/switchbotremote/config_flow.py @@ -13,7 +13,7 @@ from homeassistant.exceptions import ConfigEntryAuthFailed, HomeAssistantError from homeassistant.helpers.selector import selector -from .client import SwitchBot +from .client import SwitchBot, switchbot_host from .const import ( AIR_CONDITIONER_CLASS, CAMERA_CLASS, @@ -65,7 +65,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema( { - vol.Required("host", default="https://api.switch-bot.com"): str, + vol.Required("host", default=switchbot_host): str, vol.Required("name"): str, vol.Required("token"): str, vol.Required("secret"): str, @@ -161,7 +161,7 @@ async def async_step_reconfigure(self, user_input: dict[str, Any] | None = None) step_id="reconfigure", data_schema=vol.Schema( { - vol.Required("host", default=old_entry.data.get('host', "https://api.switch-bot.com")): str, + vol.Required("host", default=old_entry.data.get('host', switchbot_host)): str, vol.Required("name", default=old_entry.data['name']): str, vol.Required("token", default=old_entry.data['token']): str, vol.Required("secret", default=old_entry.data['secret']): str, @@ -202,7 +202,7 @@ def __init__(self, config_entry: config_entries.ConfigEntry) -> None: self.sb = SwitchBot( token=self.data["token"], secret=self.data["secret"], - host=self.data["host"] or "https://api.switch-bot.com" + host=self.data["host"] or switchbot_host ) self.discovered_devices = [] self.selected_device = None From 86b0c7446554199a888a36d606c02d18d3469d16 Mon Sep 17 00:00:00 2001 From: KiraPC Date: Tue, 17 Dec 2024 14:42:56 +0100 Subject: [PATCH 4/4] get host key from config entry optionally v2 --- custom_components/switchbotremote/config_flow.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/custom_components/switchbotremote/config_flow.py b/custom_components/switchbotremote/config_flow.py index 08723c1..00ba7a4 100644 --- a/custom_components/switchbotremote/config_flow.py +++ b/custom_components/switchbotremote/config_flow.py @@ -124,7 +124,11 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]: - switchbot = SwitchBot(token=data["token"], secret=data["secret"], host=data["host"]) + switchbot = SwitchBot( + token=data["token"], + secret=data["secret"], + host=data.get("host", switchbot_host) + ) try: remotes = await hass.async_add_executor_job(switchbot.remotes) @@ -202,7 +206,7 @@ def __init__(self, config_entry: config_entries.ConfigEntry) -> None: self.sb = SwitchBot( token=self.data["token"], secret=self.data["secret"], - host=self.data["host"] or switchbot_host + host=self.data.get("host", switchbot_host) ) self.discovered_devices = [] self.selected_device = None