Remove ambiclimate integration (#116410)
This commit is contained in:
parent
2b6dd59cfc
commit
b3008b074e
19 changed files with 0 additions and 778 deletions
|
@ -64,7 +64,6 @@ omit =
|
|||
homeassistant/components/alarmdecoder/sensor.py
|
||||
homeassistant/components/alpha_vantage/sensor.py
|
||||
homeassistant/components/amazon_polly/*
|
||||
homeassistant/components/ambiclimate/climate.py
|
||||
homeassistant/components/ambient_station/__init__.py
|
||||
homeassistant/components/ambient_station/binary_sensor.py
|
||||
homeassistant/components/ambient_station/entity.py
|
||||
|
|
|
@ -65,7 +65,6 @@ homeassistant.components.alexa.*
|
|||
homeassistant.components.alpha_vantage.*
|
||||
homeassistant.components.amazon_polly.*
|
||||
homeassistant.components.amberelectric.*
|
||||
homeassistant.components.ambiclimate.*
|
||||
homeassistant.components.ambient_network.*
|
||||
homeassistant.components.ambient_station.*
|
||||
homeassistant.components.amcrest.*
|
||||
|
|
|
@ -88,8 +88,6 @@ build.json @home-assistant/supervisor
|
|||
/tests/components/alexa/ @home-assistant/cloud @ochlocracy @jbouwh
|
||||
/homeassistant/components/amberelectric/ @madpilot
|
||||
/tests/components/amberelectric/ @madpilot
|
||||
/homeassistant/components/ambiclimate/ @danielhiversen
|
||||
/tests/components/ambiclimate/ @danielhiversen
|
||||
/homeassistant/components/ambient_network/ @thomaskistler
|
||||
/tests/components/ambient_network/ @thomaskistler
|
||||
/homeassistant/components/ambient_station/ @bachya
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
"""Support for Ambiclimate devices."""
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv, issue_registry as ir
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import config_flow
|
||||
from .const import DOMAIN
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_CLIENT_ID): cv.string,
|
||||
vol.Required(CONF_CLIENT_SECRET): cv.string,
|
||||
}
|
||||
)
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
PLATFORMS = [Platform.CLIMATE]
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up Ambiclimate components."""
|
||||
if DOMAIN not in config:
|
||||
return True
|
||||
|
||||
conf = config[DOMAIN]
|
||||
|
||||
config_flow.register_flow_implementation(
|
||||
hass, conf[CONF_CLIENT_ID], conf[CONF_CLIENT_SECRET]
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Ambiclimate from a config entry."""
|
||||
ir.async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
DOMAIN,
|
||||
breaks_in_ha_version="2024.4.0",
|
||||
is_fixable=False,
|
||||
severity=ir.IssueSeverity.WARNING,
|
||||
translation_key="integration_removed",
|
||||
translation_placeholders={
|
||||
"entries": "/config/integrations/integration/ambiclimate",
|
||||
},
|
||||
)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
return True
|
|
@ -1,211 +0,0 @@
|
|||
"""Support for Ambiclimate ac."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import ambiclimate
|
||||
from ambiclimate import AmbiclimateDevice
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
ClimateEntity,
|
||||
ClimateEntityFeature,
|
||||
HVACMode,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_NAME,
|
||||
ATTR_TEMPERATURE,
|
||||
CONF_CLIENT_ID,
|
||||
CONF_CLIENT_SECRET,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.storage import Store
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .const import (
|
||||
ATTR_VALUE,
|
||||
DOMAIN,
|
||||
SERVICE_COMFORT_FEEDBACK,
|
||||
SERVICE_COMFORT_MODE,
|
||||
SERVICE_TEMPERATURE_MODE,
|
||||
STORAGE_KEY,
|
||||
STORAGE_VERSION,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SEND_COMFORT_FEEDBACK_SCHEMA = vol.Schema(
|
||||
{vol.Required(ATTR_NAME): cv.string, vol.Required(ATTR_VALUE): cv.string}
|
||||
)
|
||||
|
||||
SET_COMFORT_MODE_SCHEMA = vol.Schema({vol.Required(ATTR_NAME): cv.string})
|
||||
|
||||
SET_TEMPERATURE_MODE_SCHEMA = vol.Schema(
|
||||
{vol.Required(ATTR_NAME): cv.string, vol.Required(ATTR_VALUE): cv.string}
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the Ambiclimate device."""
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up the Ambiclimate device from config entry."""
|
||||
config = entry.data
|
||||
websession = async_get_clientsession(hass)
|
||||
store = Store[dict[str, Any]](hass, STORAGE_VERSION, STORAGE_KEY)
|
||||
token_info = await store.async_load()
|
||||
|
||||
oauth = ambiclimate.AmbiclimateOAuth(
|
||||
config[CONF_CLIENT_ID],
|
||||
config[CONF_CLIENT_SECRET],
|
||||
config["callback_url"],
|
||||
websession,
|
||||
)
|
||||
|
||||
try:
|
||||
token_info = await oauth.refresh_access_token(token_info)
|
||||
except ambiclimate.AmbiclimateOauthError:
|
||||
token_info = None
|
||||
|
||||
if not token_info:
|
||||
_LOGGER.error("Failed to refresh access token")
|
||||
return
|
||||
|
||||
await store.async_save(token_info)
|
||||
|
||||
data_connection = ambiclimate.AmbiclimateConnection(
|
||||
oauth, token_info=token_info, websession=websession
|
||||
)
|
||||
|
||||
if not await data_connection.find_devices():
|
||||
_LOGGER.error("No devices found")
|
||||
return
|
||||
|
||||
tasks = [
|
||||
asyncio.create_task(heater.update_device_info())
|
||||
for heater in data_connection.get_devices()
|
||||
]
|
||||
await asyncio.wait(tasks)
|
||||
|
||||
async_add_entities(
|
||||
(AmbiclimateEntity(heater, store) for heater in data_connection.get_devices()),
|
||||
True,
|
||||
)
|
||||
|
||||
async def send_comfort_feedback(service: ServiceCall) -> None:
|
||||
"""Send comfort feedback."""
|
||||
device_name = service.data[ATTR_NAME]
|
||||
device = data_connection.find_device_by_room_name(device_name)
|
||||
if device:
|
||||
await device.set_comfort_feedback(service.data[ATTR_VALUE])
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_COMFORT_FEEDBACK,
|
||||
send_comfort_feedback,
|
||||
schema=SEND_COMFORT_FEEDBACK_SCHEMA,
|
||||
)
|
||||
|
||||
async def set_comfort_mode(service: ServiceCall) -> None:
|
||||
"""Set comfort mode."""
|
||||
device_name = service.data[ATTR_NAME]
|
||||
device = data_connection.find_device_by_room_name(device_name)
|
||||
if device:
|
||||
await device.set_comfort_mode()
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_COMFORT_MODE, set_comfort_mode, schema=SET_COMFORT_MODE_SCHEMA
|
||||
)
|
||||
|
||||
async def set_temperature_mode(service: ServiceCall) -> None:
|
||||
"""Set temperature mode."""
|
||||
device_name = service.data[ATTR_NAME]
|
||||
device = data_connection.find_device_by_room_name(device_name)
|
||||
if device:
|
||||
await device.set_temperature_mode(service.data[ATTR_VALUE])
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_TEMPERATURE_MODE,
|
||||
set_temperature_mode,
|
||||
schema=SET_TEMPERATURE_MODE_SCHEMA,
|
||||
)
|
||||
|
||||
|
||||
class AmbiclimateEntity(ClimateEntity):
|
||||
"""Representation of a Ambiclimate Thermostat device."""
|
||||
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_target_temperature_step = 1
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, heater: AmbiclimateDevice, store: Store[dict[str, Any]]) -> None:
|
||||
"""Initialize the thermostat."""
|
||||
self._heater = heater
|
||||
self._store = store
|
||||
self._attr_unique_id = heater.device_id
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, self.unique_id)}, # type: ignore[arg-type]
|
||||
manufacturer="Ambiclimate",
|
||||
name=heater.name,
|
||||
)
|
||||
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set new target temperature."""
|
||||
if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
|
||||
return
|
||||
await self._heater.set_target_temperature(temperature)
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Set new target hvac mode."""
|
||||
if hvac_mode == HVACMode.HEAT:
|
||||
await self._heater.turn_on()
|
||||
return
|
||||
if hvac_mode == HVACMode.OFF:
|
||||
await self._heater.turn_off()
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Retrieve latest state."""
|
||||
try:
|
||||
token_info = await self._heater.control.refresh_access_token()
|
||||
except ambiclimate.AmbiclimateOauthError:
|
||||
_LOGGER.error("Failed to refresh access token")
|
||||
return
|
||||
|
||||
if token_info:
|
||||
await self._store.async_save(token_info)
|
||||
|
||||
data = await self._heater.update_device()
|
||||
self._attr_min_temp = self._heater.get_min_temp()
|
||||
self._attr_max_temp = self._heater.get_max_temp()
|
||||
self._attr_target_temperature = data.get("target_temperature")
|
||||
self._attr_current_temperature = data.get("temperature")
|
||||
self._attr_current_humidity = data.get("humidity")
|
||||
self._attr_hvac_mode = (
|
||||
HVACMode.HEAT if data.get("power", "").lower() == "on" else HVACMode.OFF
|
||||
)
|
|
@ -1,160 +0,0 @@
|
|||
"""Config flow for Ambiclimate."""
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import web
|
||||
import ambiclimate
|
||||
|
||||
from homeassistant.components.http import KEY_HASS, HomeAssistantView
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.network import get_url
|
||||
from homeassistant.helpers.storage import Store
|
||||
|
||||
from .const import (
|
||||
AUTH_CALLBACK_NAME,
|
||||
AUTH_CALLBACK_PATH,
|
||||
DOMAIN,
|
||||
STORAGE_KEY,
|
||||
STORAGE_VERSION,
|
||||
)
|
||||
|
||||
DATA_AMBICLIMATE_IMPL = "ambiclimate_flow_implementation"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@callback
|
||||
def register_flow_implementation(
|
||||
hass: HomeAssistant, client_id: str, client_secret: str
|
||||
) -> None:
|
||||
"""Register a ambiclimate implementation.
|
||||
|
||||
client_id: Client id.
|
||||
client_secret: Client secret.
|
||||
"""
|
||||
hass.data.setdefault(DATA_AMBICLIMATE_IMPL, {})
|
||||
|
||||
hass.data[DATA_AMBICLIMATE_IMPL] = {
|
||||
CONF_CLIENT_ID: client_id,
|
||||
CONF_CLIENT_SECRET: client_secret,
|
||||
}
|
||||
|
||||
|
||||
class AmbiclimateFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize flow."""
|
||||
self._registered_view = False
|
||||
self._oauth = None
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle external yaml configuration."""
|
||||
self._async_abort_entries_match()
|
||||
|
||||
config = self.hass.data.get(DATA_AMBICLIMATE_IMPL, {})
|
||||
|
||||
if not config:
|
||||
_LOGGER.debug("No config")
|
||||
return self.async_abort(reason="missing_configuration")
|
||||
|
||||
return await self.async_step_auth()
|
||||
|
||||
async def async_step_auth(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a flow start."""
|
||||
self._async_abort_entries_match()
|
||||
|
||||
errors = {}
|
||||
|
||||
if user_input is not None:
|
||||
errors["base"] = "follow_link"
|
||||
|
||||
if not self._registered_view:
|
||||
self._generate_view()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="auth",
|
||||
description_placeholders={
|
||||
"authorization_url": await self._get_authorize_url(),
|
||||
"cb_url": self._cb_url(),
|
||||
},
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_code(self, code: str | None = None) -> ConfigFlowResult:
|
||||
"""Received code for authentication."""
|
||||
self._async_abort_entries_match()
|
||||
|
||||
if await self._get_token_info(code) is None:
|
||||
return self.async_abort(reason="access_token")
|
||||
|
||||
config = self.hass.data[DATA_AMBICLIMATE_IMPL].copy()
|
||||
config["callback_url"] = self._cb_url()
|
||||
|
||||
return self.async_create_entry(title="Ambiclimate", data=config)
|
||||
|
||||
async def _get_token_info(self, code: str | None) -> dict[str, Any] | None:
|
||||
oauth = self._generate_oauth()
|
||||
try:
|
||||
token_info = await oauth.get_access_token(code)
|
||||
except ambiclimate.AmbiclimateOauthError:
|
||||
_LOGGER.exception("Failed to get access token")
|
||||
return None
|
||||
|
||||
store = Store[dict[str, Any]](self.hass, STORAGE_VERSION, STORAGE_KEY)
|
||||
await store.async_save(token_info)
|
||||
|
||||
return token_info # type: ignore[no-any-return]
|
||||
|
||||
def _generate_view(self) -> None:
|
||||
self.hass.http.register_view(AmbiclimateAuthCallbackView())
|
||||
self._registered_view = True
|
||||
|
||||
def _generate_oauth(self) -> ambiclimate.AmbiclimateOAuth:
|
||||
config = self.hass.data[DATA_AMBICLIMATE_IMPL]
|
||||
clientsession = async_get_clientsession(self.hass)
|
||||
callback_url = self._cb_url()
|
||||
|
||||
return ambiclimate.AmbiclimateOAuth(
|
||||
config.get(CONF_CLIENT_ID),
|
||||
config.get(CONF_CLIENT_SECRET),
|
||||
callback_url,
|
||||
clientsession,
|
||||
)
|
||||
|
||||
def _cb_url(self) -> str:
|
||||
return f"{get_url(self.hass, prefer_external=True)}{AUTH_CALLBACK_PATH}"
|
||||
|
||||
async def _get_authorize_url(self) -> str:
|
||||
oauth = self._generate_oauth()
|
||||
return oauth.get_authorize_url() # type: ignore[no-any-return]
|
||||
|
||||
|
||||
class AmbiclimateAuthCallbackView(HomeAssistantView):
|
||||
"""Ambiclimate Authorization Callback View."""
|
||||
|
||||
requires_auth = False
|
||||
url = AUTH_CALLBACK_PATH
|
||||
name = AUTH_CALLBACK_NAME
|
||||
|
||||
async def get(self, request: web.Request) -> str:
|
||||
"""Receive authorization token."""
|
||||
if (code := request.query.get("code")) is None:
|
||||
return "No code"
|
||||
hass = request.app[KEY_HASS]
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": "code"}, data=code
|
||||
)
|
||||
)
|
||||
return "OK!"
|
|
@ -1,15 +0,0 @@
|
|||
"""Constants used by the Ambiclimate component."""
|
||||
|
||||
DOMAIN = "ambiclimate"
|
||||
|
||||
ATTR_VALUE = "value"
|
||||
|
||||
SERVICE_COMFORT_FEEDBACK = "send_comfort_feedback"
|
||||
SERVICE_COMFORT_MODE = "set_comfort_mode"
|
||||
SERVICE_TEMPERATURE_MODE = "set_temperature_mode"
|
||||
|
||||
STORAGE_KEY = "ambiclimate_auth"
|
||||
STORAGE_VERSION = 1
|
||||
|
||||
AUTH_CALLBACK_NAME = "api:ambiclimate"
|
||||
AUTH_CALLBACK_PATH = "/api/ambiclimate"
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"services": {
|
||||
"set_comfort_mode": "mdi:auto-mode",
|
||||
"send_comfort_feedback": "mdi:thermometer-checked",
|
||||
"set_temperature_mode": "mdi:thermometer"
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"domain": "ambiclimate",
|
||||
"name": "Ambiclimate",
|
||||
"codeowners": ["@danielhiversen"],
|
||||
"config_flow": true,
|
||||
"dependencies": ["http"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/ambiclimate",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["ambiclimate"],
|
||||
"requirements": ["Ambiclimate==0.2.1"]
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
# Describes the format for available services for ambiclimate
|
||||
|
||||
set_comfort_mode:
|
||||
fields:
|
||||
name:
|
||||
required: true
|
||||
example: Bedroom
|
||||
selector:
|
||||
text:
|
||||
|
||||
send_comfort_feedback:
|
||||
fields:
|
||||
name:
|
||||
required: true
|
||||
example: Bedroom
|
||||
selector:
|
||||
text:
|
||||
value:
|
||||
required: true
|
||||
example: bit_warm
|
||||
selector:
|
||||
text:
|
||||
|
||||
set_temperature_mode:
|
||||
fields:
|
||||
name:
|
||||
required: true
|
||||
example: Bedroom
|
||||
selector:
|
||||
text:
|
||||
value:
|
||||
required: true
|
||||
example: 22
|
||||
selector:
|
||||
text:
|
|
@ -1,68 +0,0 @@
|
|||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"auth": {
|
||||
"title": "Authenticate Ambiclimate",
|
||||
"description": "Please follow this [link]({authorization_url}) and **Allow** access to your Ambiclimate account, then come back and press **Submit** below.\n(Make sure the specified callback URL is {cb_url})"
|
||||
}
|
||||
},
|
||||
"create_entry": {
|
||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||
},
|
||||
"error": {
|
||||
"no_token": "Not authenticated with Ambiclimate",
|
||||
"follow_link": "Please follow the link and authenticate before pressing Submit"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
|
||||
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
||||
"access_token": "Unknown error generating an access token."
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"integration_removed": {
|
||||
"title": "The Ambiclimate integration has been deprecated and will be removed",
|
||||
"description": "All Ambiclimate services will be terminated, effective March 31, 2024, as Ambi Labs winds down business operations, and the Ambiclimate integration will be removed from Home Assistant.\n\nTo resolve this issue, please remove the integration entries from your Home Assistant setup. [Click here to see your existing Logi Circle integration entries]({entries})."
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"set_comfort_mode": {
|
||||
"name": "Set comfort mode",
|
||||
"description": "Enables comfort mode on your AC.",
|
||||
"fields": {
|
||||
"name": {
|
||||
"name": "Device name",
|
||||
"description": "String with device name."
|
||||
}
|
||||
}
|
||||
},
|
||||
"send_comfort_feedback": {
|
||||
"name": "Send comfort feedback",
|
||||
"description": "Sends feedback for comfort mode.",
|
||||
"fields": {
|
||||
"name": {
|
||||
"name": "[%key:component::ambiclimate::services::set_comfort_mode::fields::name::name%]",
|
||||
"description": "[%key:component::ambiclimate::services::set_comfort_mode::fields::name::description%]"
|
||||
},
|
||||
"value": {
|
||||
"name": "Comfort value",
|
||||
"description": "Send any of the following comfort values: too_hot, too_warm, bit_warm, comfortable, bit_cold, too_cold, freezing."
|
||||
}
|
||||
}
|
||||
},
|
||||
"set_temperature_mode": {
|
||||
"name": "Set temperature mode",
|
||||
"description": "Enables temperature mode on your AC.",
|
||||
"fields": {
|
||||
"name": {
|
||||
"name": "[%key:component::ambiclimate::services::set_comfort_mode::fields::name::name%]",
|
||||
"description": "[%key:component::ambiclimate::services::set_comfort_mode::fields::name::description%]"
|
||||
},
|
||||
"value": {
|
||||
"name": "Temperature",
|
||||
"description": "Target value in celsius."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -41,7 +41,6 @@ FLOWS = {
|
|||
"aladdin_connect",
|
||||
"alarmdecoder",
|
||||
"amberelectric",
|
||||
"ambiclimate",
|
||||
"ambient_network",
|
||||
"ambient_station",
|
||||
"analytics_insights",
|
||||
|
|
|
@ -238,12 +238,6 @@
|
|||
"config_flow": true,
|
||||
"iot_class": "cloud_polling"
|
||||
},
|
||||
"ambiclimate": {
|
||||
"name": "Ambiclimate",
|
||||
"integration_type": "hub",
|
||||
"config_flow": true,
|
||||
"iot_class": "cloud_polling"
|
||||
},
|
||||
"ambient_network": {
|
||||
"name": "Ambient Weather Network",
|
||||
"integration_type": "service",
|
||||
|
|
10
mypy.ini
10
mypy.ini
|
@ -411,16 +411,6 @@ disallow_untyped_defs = true
|
|||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.ambiclimate.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
disallow_subclassing_any = true
|
||||
disallow_untyped_calls = true
|
||||
disallow_untyped_decorators = true
|
||||
disallow_untyped_defs = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.ambient_network.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
|
|
|
@ -15,9 +15,6 @@ AIOSomecomfort==0.0.25
|
|||
# homeassistant.components.adax
|
||||
Adax-local==0.1.5
|
||||
|
||||
# homeassistant.components.ambiclimate
|
||||
Ambiclimate==0.2.1
|
||||
|
||||
# homeassistant.components.blinksticklight
|
||||
BlinkStick==1.2.0
|
||||
|
||||
|
|
|
@ -15,9 +15,6 @@ AIOSomecomfort==0.0.25
|
|||
# homeassistant.components.adax
|
||||
Adax-local==0.1.5
|
||||
|
||||
# homeassistant.components.ambiclimate
|
||||
Ambiclimate==0.2.1
|
||||
|
||||
# homeassistant.components.doorbird
|
||||
DoorBirdPy==2.1.0
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
"""Tests for the Ambiclimate component."""
|
|
@ -1,140 +0,0 @@
|
|||
"""Tests for the Ambiclimate config flow."""
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import ambiclimate
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.ambiclimate import config_flow
|
||||
from homeassistant.components.http import KEY_HASS
|
||||
from homeassistant.config import async_process_ha_core_config
|
||||
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import AbortFlow, FlowResultType
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import aiohttp
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def init_config_flow(hass):
|
||||
"""Init a configuration flow."""
|
||||
await async_process_ha_core_config(
|
||||
hass,
|
||||
{"external_url": "https://example.com"},
|
||||
)
|
||||
await async_setup_component(hass, "http", {})
|
||||
|
||||
config_flow.register_flow_implementation(hass, "id", "secret")
|
||||
flow = config_flow.AmbiclimateFlowHandler()
|
||||
|
||||
flow.hass = hass
|
||||
return flow
|
||||
|
||||
|
||||
async def test_abort_if_no_implementation_registered(hass: HomeAssistant) -> None:
|
||||
"""Test we abort if no implementation is registered."""
|
||||
flow = config_flow.AmbiclimateFlowHandler()
|
||||
flow.hass = hass
|
||||
|
||||
result = await flow.async_step_user()
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "missing_configuration"
|
||||
|
||||
|
||||
async def test_abort_if_already_setup(hass: HomeAssistant) -> None:
|
||||
"""Test we abort if Ambiclimate is already setup."""
|
||||
flow = await init_config_flow(hass)
|
||||
|
||||
MockConfigEntry(domain=config_flow.DOMAIN).add_to_hass(hass)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
config_flow.DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
)
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
with pytest.raises(AbortFlow):
|
||||
result = await flow.async_step_code()
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_full_flow_implementation(hass: HomeAssistant) -> None:
|
||||
"""Test registering an implementation and finishing flow works."""
|
||||
config_flow.register_flow_implementation(hass, None, None)
|
||||
flow = await init_config_flow(hass)
|
||||
|
||||
result = await flow.async_step_user()
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "auth"
|
||||
assert (
|
||||
result["description_placeholders"]["cb_url"]
|
||||
== "https://example.com/api/ambiclimate"
|
||||
)
|
||||
|
||||
url = result["description_placeholders"]["authorization_url"]
|
||||
assert "https://api.ambiclimate.com/oauth2/authorize" in url
|
||||
assert "client_id=id" in url
|
||||
assert "response_type=code" in url
|
||||
assert "redirect_uri=https%3A%2F%2Fexample.com%2Fapi%2Fambiclimate" in url
|
||||
|
||||
with patch("ambiclimate.AmbiclimateOAuth.get_access_token", return_value="test"):
|
||||
result = await flow.async_step_code("123ABC")
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "Ambiclimate"
|
||||
assert result["data"]["callback_url"] == "https://example.com/api/ambiclimate"
|
||||
assert result["data"][CONF_CLIENT_SECRET] == "secret"
|
||||
assert result["data"][CONF_CLIENT_ID] == "id"
|
||||
|
||||
with patch("ambiclimate.AmbiclimateOAuth.get_access_token", return_value=None):
|
||||
result = await flow.async_step_code("123ABC")
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
|
||||
with patch(
|
||||
"ambiclimate.AmbiclimateOAuth.get_access_token",
|
||||
side_effect=ambiclimate.AmbiclimateOauthError(),
|
||||
):
|
||||
result = await flow.async_step_code("123ABC")
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
|
||||
|
||||
async def test_abort_invalid_code(hass: HomeAssistant) -> None:
|
||||
"""Test if no code is given to step_code."""
|
||||
config_flow.register_flow_implementation(hass, None, None)
|
||||
flow = await init_config_flow(hass)
|
||||
|
||||
with patch("ambiclimate.AmbiclimateOAuth.get_access_token", return_value=None):
|
||||
result = await flow.async_step_code("invalid")
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "access_token"
|
||||
|
||||
|
||||
async def test_already_setup(hass: HomeAssistant) -> None:
|
||||
"""Test when already setup."""
|
||||
MockConfigEntry(domain=config_flow.DOMAIN).add_to_hass(hass)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
config_flow.DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_view(hass: HomeAssistant) -> None:
|
||||
"""Test view."""
|
||||
hass.config_entries.flow.async_init = AsyncMock()
|
||||
|
||||
request = aiohttp.MockRequest(
|
||||
b"", query_string="code=test_code", mock_source="test"
|
||||
)
|
||||
request.app = {KEY_HASS: hass}
|
||||
view = config_flow.AmbiclimateAuthCallbackView()
|
||||
assert await view.get(request) == "OK!"
|
||||
|
||||
request = aiohttp.MockRequest(b"", query_string="", mock_source="test")
|
||||
request.app = {KEY_HASS: hass}
|
||||
view = config_flow.AmbiclimateAuthCallbackView()
|
||||
assert await view.get(request) == "No code"
|
|
@ -1,44 +0,0 @@
|
|||
"""Tests for the Ambiclimate integration."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.ambiclimate import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.fixture(name="disable_platforms")
|
||||
async def disable_platforms_fixture(hass):
|
||||
"""Disable ambiclimate platforms."""
|
||||
with patch("homeassistant.components.ambiclimate.PLATFORMS", []):
|
||||
yield
|
||||
|
||||
|
||||
async def test_repair_issue(
|
||||
hass: HomeAssistant,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
disable_platforms,
|
||||
) -> None:
|
||||
"""Test the Ambiclimate configuration entry loading handles the repair."""
|
||||
config_entry = MockConfigEntry(
|
||||
title="Example 1",
|
||||
domain=DOMAIN,
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
assert issue_registry.async_get_issue(DOMAIN, DOMAIN)
|
||||
|
||||
# Remove the entry
|
||||
await hass.config_entries.async_remove(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Ambiclimate does not implement unload
|
||||
assert config_entry.state is ConfigEntryState.FAILED_UNLOAD
|
||||
assert issue_registry.async_get_issue(DOMAIN, DOMAIN)
|
Loading…
Add table
Reference in a new issue