mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Add Home Connect integration (#29214)
This commit is contained in:
parent
6b9eed5a70
commit
86d410d863
@ -299,6 +299,7 @@ omit =
|
|||||||
homeassistant/components/hitron_coda/device_tracker.py
|
homeassistant/components/hitron_coda/device_tracker.py
|
||||||
homeassistant/components/hive/*
|
homeassistant/components/hive/*
|
||||||
homeassistant/components/hlk_sw16/*
|
homeassistant/components/hlk_sw16/*
|
||||||
|
homeassistant/components/home_connect/*
|
||||||
homeassistant/components/homematic/*
|
homeassistant/components/homematic/*
|
||||||
homeassistant/components/homematic/climate.py
|
homeassistant/components/homematic/climate.py
|
||||||
homeassistant/components/homematic/cover.py
|
homeassistant/components/homematic/cover.py
|
||||||
|
@ -163,6 +163,7 @@ homeassistant/components/hikvisioncam/* @fbradyirl
|
|||||||
homeassistant/components/hisense_aehw4a1/* @bannhead
|
homeassistant/components/hisense_aehw4a1/* @bannhead
|
||||||
homeassistant/components/history/* @home-assistant/core
|
homeassistant/components/history/* @home-assistant/core
|
||||||
homeassistant/components/hive/* @Rendili @KJonline
|
homeassistant/components/hive/* @Rendili @KJonline
|
||||||
|
homeassistant/components/home_connect/* @DavidMStraub
|
||||||
homeassistant/components/homeassistant/* @home-assistant/core
|
homeassistant/components/homeassistant/* @home-assistant/core
|
||||||
homeassistant/components/homekit/* @bdraco
|
homeassistant/components/homekit/* @bdraco
|
||||||
homeassistant/components/homekit_controller/* @Jc2k
|
homeassistant/components/homekit_controller/* @Jc2k
|
||||||
|
106
homeassistant/components/home_connect/__init__.py
Normal file
106
homeassistant/components/home_connect/__init__.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
"""Support for BSH Home Connect appliances."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from requests import HTTPError
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import config_entry_oauth2_flow, config_validation as cv
|
||||||
|
from homeassistant.util import Throttle
|
||||||
|
|
||||||
|
from . import api, config_flow
|
||||||
|
from .const import DOMAIN, OAUTH2_AUTHORIZE, OAUTH2_TOKEN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SCAN_INTERVAL = timedelta(minutes=1)
|
||||||
|
|
||||||
|
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 = ["binary_sensor", "sensor", "switch"]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass: HomeAssistant, config: dict) -> bool:
|
||||||
|
"""Set up Home Connect component."""
|
||||||
|
hass.data[DOMAIN] = {}
|
||||||
|
|
||||||
|
if DOMAIN not in config:
|
||||||
|
return True
|
||||||
|
|
||||||
|
config_flow.OAuth2FlowHandler.async_register_implementation(
|
||||||
|
hass,
|
||||||
|
config_entry_oauth2_flow.LocalOAuth2Implementation(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
config[DOMAIN][CONF_CLIENT_ID],
|
||||||
|
config[DOMAIN][CONF_CLIENT_SECRET],
|
||||||
|
OAUTH2_AUTHORIZE,
|
||||||
|
OAUTH2_TOKEN,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Set up Home Connect from a config entry."""
|
||||||
|
implementation = await config_entry_oauth2_flow.async_get_config_entry_implementation(
|
||||||
|
hass, entry
|
||||||
|
)
|
||||||
|
|
||||||
|
hc_api = api.ConfigEntryAuth(hass, entry, implementation)
|
||||||
|
|
||||||
|
hass.data[DOMAIN][entry.entry_id] = hc_api
|
||||||
|
|
||||||
|
await update_all_devices(hass, entry)
|
||||||
|
|
||||||
|
for component in PLATFORMS:
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Unload a config entry."""
|
||||||
|
unload_ok = all(
|
||||||
|
await asyncio.gather(
|
||||||
|
*[
|
||||||
|
hass.config_entries.async_forward_entry_unload(entry, component)
|
||||||
|
for component in PLATFORMS
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if unload_ok:
|
||||||
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
|
||||||
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
|
@Throttle(SCAN_INTERVAL)
|
||||||
|
async def update_all_devices(hass, entry):
|
||||||
|
"""Update all the devices."""
|
||||||
|
data = hass.data[DOMAIN]
|
||||||
|
hc_api = data[entry.entry_id]
|
||||||
|
try:
|
||||||
|
await hass.async_add_executor_job(hc_api.get_devices)
|
||||||
|
for device_dict in hc_api.devices:
|
||||||
|
await hass.async_add_executor_job(device_dict["device"].initialize)
|
||||||
|
except HTTPError as err:
|
||||||
|
_LOGGER.warning("Cannot update devices: %s", err.response.status_code)
|
372
homeassistant/components/home_connect/api.py
Normal file
372
homeassistant/components/home_connect/api.py
Normal file
@ -0,0 +1,372 @@
|
|||||||
|
"""API for Home Connect bound to HASS OAuth."""
|
||||||
|
|
||||||
|
from asyncio import run_coroutine_threadsafe
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import homeconnect
|
||||||
|
from homeconnect.api import HomeConnectError
|
||||||
|
|
||||||
|
from homeassistant import config_entries, core
|
||||||
|
from homeassistant.const import DEVICE_CLASS_TIMESTAMP, TIME_SECONDS, UNIT_PERCENTAGE
|
||||||
|
from homeassistant.helpers import config_entry_oauth2_flow
|
||||||
|
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
BSH_ACTIVE_PROGRAM,
|
||||||
|
BSH_POWER_OFF,
|
||||||
|
BSH_POWER_STANDBY,
|
||||||
|
SIGNAL_UPDATE_ENTITIES,
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigEntryAuth(homeconnect.HomeConnectAPI):
|
||||||
|
"""Provide Home Connect authentication tied to an OAuth2 based config entry."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: core.HomeAssistant,
|
||||||
|
config_entry: config_entries.ConfigEntry,
|
||||||
|
implementation: config_entry_oauth2_flow.AbstractOAuth2Implementation,
|
||||||
|
):
|
||||||
|
"""Initialize Home Connect Auth."""
|
||||||
|
self.hass = hass
|
||||||
|
self.config_entry = config_entry
|
||||||
|
self.session = config_entry_oauth2_flow.OAuth2Session(
|
||||||
|
hass, config_entry, implementation
|
||||||
|
)
|
||||||
|
super().__init__(self.session.token)
|
||||||
|
self.devices = []
|
||||||
|
|
||||||
|
def refresh_tokens(self) -> dict:
|
||||||
|
"""Refresh and return new Home Connect tokens using Home Assistant OAuth2 session."""
|
||||||
|
run_coroutine_threadsafe(
|
||||||
|
self.session.async_ensure_token_valid(), self.hass.loop
|
||||||
|
).result()
|
||||||
|
|
||||||
|
return self.session.token
|
||||||
|
|
||||||
|
def get_devices(self):
|
||||||
|
"""Get a dictionary of devices."""
|
||||||
|
appl = self.get_appliances()
|
||||||
|
devices = []
|
||||||
|
for app in appl:
|
||||||
|
if app.type == "Dryer":
|
||||||
|
device = Dryer(self.hass, app)
|
||||||
|
elif app.type == "Washer":
|
||||||
|
device = Washer(self.hass, app)
|
||||||
|
elif app.type == "Dishwasher":
|
||||||
|
device = Dishwasher(self.hass, app)
|
||||||
|
elif app.type == "FridgeFreezer":
|
||||||
|
device = FridgeFreezer(self.hass, app)
|
||||||
|
elif app.type == "Oven":
|
||||||
|
device = Oven(self.hass, app)
|
||||||
|
elif app.type == "CoffeeMaker":
|
||||||
|
device = CoffeeMaker(self.hass, app)
|
||||||
|
elif app.type == "Hood":
|
||||||
|
device = Hood(self.hass, app)
|
||||||
|
elif app.type == "Hob":
|
||||||
|
device = Hob(self.hass, app)
|
||||||
|
else:
|
||||||
|
_LOGGER.warning("Appliance type %s not implemented.", app.type)
|
||||||
|
continue
|
||||||
|
devices.append({"device": device, "entities": device.get_entity_info()})
|
||||||
|
self.devices = devices
|
||||||
|
return devices
|
||||||
|
|
||||||
|
|
||||||
|
class HomeConnectDevice:
|
||||||
|
"""Generic Home Connect device."""
|
||||||
|
|
||||||
|
# for some devices, this is instead BSH_POWER_STANDBY
|
||||||
|
# see https://developer.home-connect.com/docs/settings/power_state
|
||||||
|
power_off_state = BSH_POWER_OFF
|
||||||
|
|
||||||
|
def __init__(self, hass, appliance):
|
||||||
|
"""Initialize the device class."""
|
||||||
|
self.hass = hass
|
||||||
|
self.appliance = appliance
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
"""Fetch the info needed to initialize the device."""
|
||||||
|
try:
|
||||||
|
self.appliance.get_status()
|
||||||
|
except (HomeConnectError, ValueError):
|
||||||
|
_LOGGER.debug("Unable to fetch appliance status. Probably offline.")
|
||||||
|
try:
|
||||||
|
self.appliance.get_settings()
|
||||||
|
except (HomeConnectError, ValueError):
|
||||||
|
_LOGGER.debug("Unable to fetch settings. Probably offline.")
|
||||||
|
try:
|
||||||
|
program_active = self.appliance.get_programs_active()
|
||||||
|
except (HomeConnectError, ValueError):
|
||||||
|
_LOGGER.debug("Unable to fetch active programs. Probably offline.")
|
||||||
|
program_active = None
|
||||||
|
if program_active and "key" in program_active:
|
||||||
|
self.appliance.status[BSH_ACTIVE_PROGRAM] = {"value": program_active["key"]}
|
||||||
|
self.appliance.listen_events(callback=self.event_callback)
|
||||||
|
|
||||||
|
def event_callback(self, appliance):
|
||||||
|
"""Handle event."""
|
||||||
|
_LOGGER.debug("Update triggered on %s", appliance.name)
|
||||||
|
_LOGGER.debug(self.appliance.status)
|
||||||
|
dispatcher_send(self.hass, SIGNAL_UPDATE_ENTITIES, appliance.haId)
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceWithPrograms(HomeConnectDevice):
|
||||||
|
"""Device with programs."""
|
||||||
|
|
||||||
|
PROGRAMS = []
|
||||||
|
|
||||||
|
def get_programs_available(self):
|
||||||
|
"""Get the available programs."""
|
||||||
|
return self.PROGRAMS
|
||||||
|
|
||||||
|
def get_program_switches(self):
|
||||||
|
"""Get a dictionary with info about program switches.
|
||||||
|
|
||||||
|
There will be one switch for each program.
|
||||||
|
"""
|
||||||
|
programs = self.get_programs_available()
|
||||||
|
return [{"device": self, "program_name": p["name"]} for p in programs]
|
||||||
|
|
||||||
|
def get_program_sensors(self):
|
||||||
|
"""Get a dictionary with info about program sensors.
|
||||||
|
|
||||||
|
There will be one of the four types of sensors for each
|
||||||
|
device.
|
||||||
|
"""
|
||||||
|
sensors = {
|
||||||
|
"Remaining Program Time": (None, None, DEVICE_CLASS_TIMESTAMP, 1),
|
||||||
|
"Duration": (TIME_SECONDS, "mdi:update", None, 1),
|
||||||
|
"Program Progress": (UNIT_PERCENTAGE, "mdi:progress-clock", None, 1),
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"device": self,
|
||||||
|
"desc": k,
|
||||||
|
"unit": unit,
|
||||||
|
"key": "BSH.Common.Option.{}".format(k.replace(" ", "")),
|
||||||
|
"icon": icon,
|
||||||
|
"device_class": device_class,
|
||||||
|
"sign": sign,
|
||||||
|
}
|
||||||
|
for k, (unit, icon, device_class, sign) in sensors.items()
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceWithDoor(HomeConnectDevice):
|
||||||
|
"""Device that has a door sensor."""
|
||||||
|
|
||||||
|
def get_door_entity(self):
|
||||||
|
"""Get a dictionary with info about the door binary sensor."""
|
||||||
|
return {
|
||||||
|
"device": self,
|
||||||
|
"desc": "Door",
|
||||||
|
"device_class": "door",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Dryer(DeviceWithDoor, DeviceWithPrograms):
|
||||||
|
"""Dryer class."""
|
||||||
|
|
||||||
|
PROGRAMS = [
|
||||||
|
{"name": "LaundryCare.Dryer.Program.Cotton"},
|
||||||
|
{"name": "LaundryCare.Dryer.Program.Synthetic"},
|
||||||
|
{"name": "LaundryCare.Dryer.Program.Mix"},
|
||||||
|
{"name": "LaundryCare.Dryer.Program.Blankets"},
|
||||||
|
{"name": "LaundryCare.Dryer.Program.BusinessShirts"},
|
||||||
|
{"name": "LaundryCare.Dryer.Program.DownFeathers"},
|
||||||
|
{"name": "LaundryCare.Dryer.Program.Hygiene"},
|
||||||
|
{"name": "LaundryCare.Dryer.Program.Jeans"},
|
||||||
|
{"name": "LaundryCare.Dryer.Program.Outdoor"},
|
||||||
|
{"name": "LaundryCare.Dryer.Program.SyntheticRefresh"},
|
||||||
|
{"name": "LaundryCare.Dryer.Program.Towels"},
|
||||||
|
{"name": "LaundryCare.Dryer.Program.Delicates"},
|
||||||
|
{"name": "LaundryCare.Dryer.Program.Super40"},
|
||||||
|
{"name": "LaundryCare.Dryer.Program.Shirts15"},
|
||||||
|
{"name": "LaundryCare.Dryer.Program.Pillow"},
|
||||||
|
{"name": "LaundryCare.Dryer.Program.AntiShrink"},
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_entity_info(self):
|
||||||
|
"""Get a dictionary with infos about the associated entities."""
|
||||||
|
door_entity = self.get_door_entity()
|
||||||
|
program_sensors = self.get_program_sensors()
|
||||||
|
program_switches = self.get_program_switches()
|
||||||
|
return {
|
||||||
|
"binary_sensor": [door_entity],
|
||||||
|
"switch": program_switches,
|
||||||
|
"sensor": program_sensors,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Dishwasher(DeviceWithDoor, DeviceWithPrograms):
|
||||||
|
"""Dishwasher class."""
|
||||||
|
|
||||||
|
PROGRAMS = [
|
||||||
|
{"name": "Dishcare.Dishwasher.Program.Auto1"},
|
||||||
|
{"name": "Dishcare.Dishwasher.Program.Auto2"},
|
||||||
|
{"name": "Dishcare.Dishwasher.Program.Auto3"},
|
||||||
|
{"name": "Dishcare.Dishwasher.Program.Eco50"},
|
||||||
|
{"name": "Dishcare.Dishwasher.Program.Quick45"},
|
||||||
|
{"name": "Dishcare.Dishwasher.Program.Intensiv70"},
|
||||||
|
{"name": "Dishcare.Dishwasher.Program.Normal65"},
|
||||||
|
{"name": "Dishcare.Dishwasher.Program.Glas40"},
|
||||||
|
{"name": "Dishcare.Dishwasher.Program.GlassCare"},
|
||||||
|
{"name": "Dishcare.Dishwasher.Program.NightWash"},
|
||||||
|
{"name": "Dishcare.Dishwasher.Program.Quick65"},
|
||||||
|
{"name": "Dishcare.Dishwasher.Program.Normal45"},
|
||||||
|
{"name": "Dishcare.Dishwasher.Program.Intensiv45"},
|
||||||
|
{"name": "Dishcare.Dishwasher.Program.AutoHalfLoad"},
|
||||||
|
{"name": "Dishcare.Dishwasher.Program.IntensivPower"},
|
||||||
|
{"name": "Dishcare.Dishwasher.Program.MagicDaily"},
|
||||||
|
{"name": "Dishcare.Dishwasher.Program.Super60"},
|
||||||
|
{"name": "Dishcare.Dishwasher.Program.Kurz60"},
|
||||||
|
{"name": "Dishcare.Dishwasher.Program.ExpressSparkle65"},
|
||||||
|
{"name": "Dishcare.Dishwasher.Program.MachineCare"},
|
||||||
|
{"name": "Dishcare.Dishwasher.Program.SteamFresh"},
|
||||||
|
{"name": "Dishcare.Dishwasher.Program.MaximumCleaning"},
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_entity_info(self):
|
||||||
|
"""Get a dictionary with infos about the associated entities."""
|
||||||
|
door_entity = self.get_door_entity()
|
||||||
|
program_sensors = self.get_program_sensors()
|
||||||
|
program_switches = self.get_program_switches()
|
||||||
|
return {
|
||||||
|
"binary_sensor": [door_entity],
|
||||||
|
"switch": program_switches,
|
||||||
|
"sensor": program_sensors,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Oven(DeviceWithDoor, DeviceWithPrograms):
|
||||||
|
"""Oven class."""
|
||||||
|
|
||||||
|
PROGRAMS = [
|
||||||
|
{"name": "Cooking.Oven.Program.HeatingMode.PreHeating"},
|
||||||
|
{"name": "Cooking.Oven.Program.HeatingMode.HotAir"},
|
||||||
|
{"name": "Cooking.Oven.Program.HeatingMode.TopBottomHeating"},
|
||||||
|
{"name": "Cooking.Oven.Program.HeatingMode.PizzaSetting"},
|
||||||
|
{"name": "Cooking.Oven.Program.Microwave.600Watt"},
|
||||||
|
]
|
||||||
|
|
||||||
|
power_off_state = BSH_POWER_STANDBY
|
||||||
|
|
||||||
|
def get_entity_info(self):
|
||||||
|
"""Get a dictionary with infos about the associated entities."""
|
||||||
|
door_entity = self.get_door_entity()
|
||||||
|
program_sensors = self.get_program_sensors()
|
||||||
|
program_switches = self.get_program_switches()
|
||||||
|
return {
|
||||||
|
"binary_sensor": [door_entity],
|
||||||
|
"switch": program_switches,
|
||||||
|
"sensor": program_sensors,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Washer(DeviceWithDoor, DeviceWithPrograms):
|
||||||
|
"""Washer class."""
|
||||||
|
|
||||||
|
PROGRAMS = [
|
||||||
|
{"name": "LaundryCare.Washer.Program.Cotton"},
|
||||||
|
{"name": "LaundryCare.Washer.Program.Cotton.CottonEco"},
|
||||||
|
{"name": "LaundryCare.Washer.Program.EasyCare"},
|
||||||
|
{"name": "LaundryCare.Washer.Program.Mix"},
|
||||||
|
{"name": "LaundryCare.Washer.Program.DelicatesSilk"},
|
||||||
|
{"name": "LaundryCare.Washer.Program.Wool"},
|
||||||
|
{"name": "LaundryCare.Washer.Program.Sensitive"},
|
||||||
|
{"name": "LaundryCare.Washer.Program.Auto30"},
|
||||||
|
{"name": "LaundryCare.Washer.Program.Auto40"},
|
||||||
|
{"name": "LaundryCare.Washer.Program.Auto60"},
|
||||||
|
{"name": "LaundryCare.Washer.Program.Chiffon"},
|
||||||
|
{"name": "LaundryCare.Washer.Program.Curtains"},
|
||||||
|
{"name": "LaundryCare.Washer.Program.DarkWash"},
|
||||||
|
{"name": "LaundryCare.Washer.Program.Dessous"},
|
||||||
|
{"name": "LaundryCare.Washer.Program.Monsoon"},
|
||||||
|
{"name": "LaundryCare.Washer.Program.Outdoor"},
|
||||||
|
{"name": "LaundryCare.Washer.Program.PlushToy"},
|
||||||
|
{"name": "LaundryCare.Washer.Program.ShirtsBlouses"},
|
||||||
|
{"name": "LaundryCare.Washer.Program.SportFitness"},
|
||||||
|
{"name": "LaundryCare.Washer.Program.Towels"},
|
||||||
|
{"name": "LaundryCare.Washer.Program.WaterProof"},
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_entity_info(self):
|
||||||
|
"""Get a dictionary with infos about the associated entities."""
|
||||||
|
door_entity = self.get_door_entity()
|
||||||
|
program_sensors = self.get_program_sensors()
|
||||||
|
program_switches = self.get_program_switches()
|
||||||
|
return {
|
||||||
|
"binary_sensor": [door_entity],
|
||||||
|
"switch": program_switches,
|
||||||
|
"sensor": program_sensors,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CoffeeMaker(DeviceWithPrograms):
|
||||||
|
"""Coffee maker class."""
|
||||||
|
|
||||||
|
PROGRAMS = [
|
||||||
|
{"name": "ConsumerProducts.CoffeeMaker.Program.Beverage.Espresso"},
|
||||||
|
{"name": "ConsumerProducts.CoffeeMaker.Program.Beverage.EspressoMacchiato"},
|
||||||
|
{"name": "ConsumerProducts.CoffeeMaker.Program.Beverage.Coffee"},
|
||||||
|
{"name": "ConsumerProducts.CoffeeMaker.Program.Beverage.Cappuccino"},
|
||||||
|
{"name": "ConsumerProducts.CoffeeMaker.Program.Beverage.LatteMacchiato"},
|
||||||
|
{"name": "ConsumerProducts.CoffeeMaker.Program.Beverage.CaffeLatte"},
|
||||||
|
{"name": "ConsumerProducts.CoffeeMaker.Program.CoffeeWorld.Americano"},
|
||||||
|
{"name": "ConsumerProducts.CoffeeMaker.Program.Beverage.EspressoDoppio"},
|
||||||
|
{"name": "ConsumerProducts.CoffeeMaker.Program.CoffeeWorld.FlatWhite"},
|
||||||
|
{"name": "ConsumerProducts.CoffeeMaker.Program.CoffeeWorld.Galao"},
|
||||||
|
{"name": "ConsumerProducts.CoffeeMaker.Program.Beverage.MilkFroth"},
|
||||||
|
{"name": "ConsumerProducts.CoffeeMaker.Program.Beverage.WarmMilk"},
|
||||||
|
{"name": "ConsumerProducts.CoffeeMaker.Program.Beverage.Ristretto"},
|
||||||
|
{"name": "ConsumerProducts.CoffeeMaker.Program.CoffeeWorld.Cortado"},
|
||||||
|
]
|
||||||
|
|
||||||
|
power_off_state = BSH_POWER_STANDBY
|
||||||
|
|
||||||
|
def get_entity_info(self):
|
||||||
|
"""Get a dictionary with infos about the associated entities."""
|
||||||
|
program_sensors = self.get_program_sensors()
|
||||||
|
program_switches = self.get_program_switches()
|
||||||
|
return {"switch": program_switches, "sensor": program_sensors}
|
||||||
|
|
||||||
|
|
||||||
|
class Hood(DeviceWithPrograms):
|
||||||
|
"""Hood class."""
|
||||||
|
|
||||||
|
PROGRAMS = [
|
||||||
|
{"name": "Cooking.Common.Program.Hood.Automatic"},
|
||||||
|
{"name": "Cooking.Common.Program.Hood.Venting"},
|
||||||
|
{"name": "Cooking.Common.Program.Hood.DelayedShutOff"},
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_entity_info(self):
|
||||||
|
"""Get a dictionary with infos about the associated entities."""
|
||||||
|
program_sensors = self.get_program_sensors()
|
||||||
|
program_switches = self.get_program_switches()
|
||||||
|
return {"switch": program_switches, "sensor": program_sensors}
|
||||||
|
|
||||||
|
|
||||||
|
class FridgeFreezer(DeviceWithDoor):
|
||||||
|
"""Fridge/Freezer class."""
|
||||||
|
|
||||||
|
def get_entity_info(self):
|
||||||
|
"""Get a dictionary with infos about the associated entities."""
|
||||||
|
door_entity = self.get_door_entity()
|
||||||
|
return {"binary_sensor": [door_entity]}
|
||||||
|
|
||||||
|
|
||||||
|
class Hob(DeviceWithPrograms):
|
||||||
|
"""Hob class."""
|
||||||
|
|
||||||
|
PROGRAMS = [{"name": "Cooking.Hob.Program.PowerLevelMode"}]
|
||||||
|
|
||||||
|
def get_entity_info(self):
|
||||||
|
"""Get a dictionary with infos about the associated entities."""
|
||||||
|
program_sensors = self.get_program_sensors()
|
||||||
|
program_switches = self.get_program_switches()
|
||||||
|
return {"switch": program_switches, "sensor": program_sensors}
|
65
homeassistant/components/home_connect/binary_sensor.py
Normal file
65
homeassistant/components/home_connect/binary_sensor.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
"""Provides a binary sensor for Home Connect."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components.binary_sensor import BinarySensorEntity
|
||||||
|
|
||||||
|
from .const import BSH_DOOR_STATE, DOMAIN
|
||||||
|
from .entity import HomeConnectEntity
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
|
"""Set up the Home Connect binary sensor."""
|
||||||
|
|
||||||
|
def get_entities():
|
||||||
|
entities = []
|
||||||
|
hc_api = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
for device_dict in hc_api.devices:
|
||||||
|
entity_dicts = device_dict.get("entities", {}).get("binary_sensor", [])
|
||||||
|
entities += [HomeConnectBinarySensor(**d) for d in entity_dicts]
|
||||||
|
return entities
|
||||||
|
|
||||||
|
async_add_entities(await hass.async_add_executor_job(get_entities), True)
|
||||||
|
|
||||||
|
|
||||||
|
class HomeConnectBinarySensor(HomeConnectEntity, BinarySensorEntity):
|
||||||
|
"""Binary sensor for Home Connect."""
|
||||||
|
|
||||||
|
def __init__(self, device, desc, device_class):
|
||||||
|
"""Initialize the entity."""
|
||||||
|
super().__init__(device, desc)
|
||||||
|
self._device_class = device_class
|
||||||
|
self._state = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
"""Return true if the binary sensor is on."""
|
||||||
|
return bool(self._state)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""Return true if the binary sensor is available."""
|
||||||
|
return self._state is not None
|
||||||
|
|
||||||
|
async def async_update(self):
|
||||||
|
"""Update the binary sensor's status."""
|
||||||
|
state = self.device.appliance.status.get(BSH_DOOR_STATE, {})
|
||||||
|
if not state:
|
||||||
|
self._state = None
|
||||||
|
elif state.get("value") in [
|
||||||
|
"BSH.Common.EnumType.DoorState.Closed",
|
||||||
|
"BSH.Common.EnumType.DoorState.Locked",
|
||||||
|
]:
|
||||||
|
self._state = False
|
||||||
|
elif state.get("value") == "BSH.Common.EnumType.DoorState.Open":
|
||||||
|
self._state = True
|
||||||
|
else:
|
||||||
|
_LOGGER.warning("Unexpected value for HomeConnect door state: %s", state)
|
||||||
|
self._state = None
|
||||||
|
_LOGGER.debug("Updated, new state: %s", self._state)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_class(self):
|
||||||
|
"""Return the device class."""
|
||||||
|
return self._device_class
|
23
homeassistant/components/home_connect/config_flow.py
Normal file
23
homeassistant/components/home_connect/config_flow.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
"""Config flow for Home Connect."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.helpers import config_entry_oauth2_flow
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class OAuth2FlowHandler(
|
||||||
|
config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN
|
||||||
|
):
|
||||||
|
"""Config flow to handle Home Connect OAuth2 authentication."""
|
||||||
|
|
||||||
|
DOMAIN = DOMAIN
|
||||||
|
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_PUSH
|
||||||
|
|
||||||
|
@property
|
||||||
|
def logger(self) -> logging.Logger:
|
||||||
|
"""Return logger."""
|
||||||
|
return logging.getLogger(__name__)
|
16
homeassistant/components/home_connect/const.py
Normal file
16
homeassistant/components/home_connect/const.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""Constants for the Home Connect integration."""
|
||||||
|
|
||||||
|
DOMAIN = "home_connect"
|
||||||
|
|
||||||
|
OAUTH2_AUTHORIZE = "https://api.home-connect.com/security/oauth/authorize"
|
||||||
|
OAUTH2_TOKEN = "https://api.home-connect.com/security/oauth/token"
|
||||||
|
|
||||||
|
BSH_POWER_STATE = "BSH.Common.Setting.PowerState"
|
||||||
|
BSH_POWER_ON = "BSH.Common.EnumType.PowerState.On"
|
||||||
|
BSH_POWER_OFF = "BSH.Common.EnumType.PowerState.Off"
|
||||||
|
BSH_POWER_STANDBY = "BSH.Common.EnumType.PowerState.Standby"
|
||||||
|
BSH_ACTIVE_PROGRAM = "BSH.Common.Root.ActiveProgram"
|
||||||
|
BSH_OPERATION_STATE = "BSH.Common.Status.OperationState"
|
||||||
|
BSH_DOOR_STATE = "BSH.Common.Status.DoorState"
|
||||||
|
|
||||||
|
SIGNAL_UPDATE_ENTITIES = "home_connect.update_entities"
|
67
homeassistant/components/home_connect/entity.py
Normal file
67
homeassistant/components/home_connect/entity.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
"""Home Connect entity base class."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
|
from .api import HomeConnectDevice
|
||||||
|
from .const import DOMAIN, SIGNAL_UPDATE_ENTITIES
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class HomeConnectEntity(Entity):
|
||||||
|
"""Generic Home Connect entity (base class)."""
|
||||||
|
|
||||||
|
def __init__(self, device: HomeConnectDevice, desc: str) -> None:
|
||||||
|
"""Initialize the entity."""
|
||||||
|
self.device = device
|
||||||
|
self.desc = desc
|
||||||
|
self._name = f"{self.device.appliance.name} {desc}"
|
||||||
|
|
||||||
|
async def async_added_to_hass(self):
|
||||||
|
"""Register callbacks."""
|
||||||
|
self.async_on_remove(
|
||||||
|
async_dispatcher_connect(
|
||||||
|
self.hass, SIGNAL_UPDATE_ENTITIES, self._update_callback
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _update_callback(self, ha_id):
|
||||||
|
"""Update data."""
|
||||||
|
if ha_id == self.device.appliance.haId:
|
||||||
|
self.async_entity_update()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
"""No polling needed."""
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the node (used for Entity_ID)."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Return the unique id base on the id returned by Home Connect and the entity name."""
|
||||||
|
return f"{self.device.appliance.haId}-{self.desc}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self):
|
||||||
|
"""Return info about the device."""
|
||||||
|
return {
|
||||||
|
"identifiers": {(DOMAIN, self.device.appliance.haId)},
|
||||||
|
"name": self.device.appliance.name,
|
||||||
|
"manufacturer": self.device.appliance.brand,
|
||||||
|
"model": self.device.appliance.vib,
|
||||||
|
}
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_entity_update(self):
|
||||||
|
"""Update the entity."""
|
||||||
|
_LOGGER.debug("Entity update triggered on %s", self)
|
||||||
|
self.async_schedule_update_ha_state(True)
|
9
homeassistant/components/home_connect/manifest.json
Normal file
9
homeassistant/components/home_connect/manifest.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"domain": "home_connect",
|
||||||
|
"name": "Home Connect",
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/home_connect",
|
||||||
|
"dependencies": ["http"],
|
||||||
|
"codeowners": ["@DavidMStraub"],
|
||||||
|
"requirements": ["homeconnect==0.5"],
|
||||||
|
"config_flow": true
|
||||||
|
}
|
92
homeassistant/components/home_connect/sensor.py
Normal file
92
homeassistant/components/home_connect/sensor.py
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
"""Provides a sensor for Home Connect."""
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.const import DEVICE_CLASS_TIMESTAMP
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .entity import HomeConnectEntity
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
|
"""Set up the Home Connect sensor."""
|
||||||
|
|
||||||
|
def get_entities():
|
||||||
|
"""Get a list of entities."""
|
||||||
|
entities = []
|
||||||
|
hc_api = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
for device_dict in hc_api.devices:
|
||||||
|
entity_dicts = device_dict.get("entities", {}).get("sensor", [])
|
||||||
|
entities += [HomeConnectSensor(**d) for d in entity_dicts]
|
||||||
|
return entities
|
||||||
|
|
||||||
|
async_add_entities(await hass.async_add_executor_job(get_entities), True)
|
||||||
|
|
||||||
|
|
||||||
|
class HomeConnectSensor(HomeConnectEntity):
|
||||||
|
"""Sensor class for Home Connect."""
|
||||||
|
|
||||||
|
def __init__(self, device, desc, key, unit, icon, device_class, sign=1):
|
||||||
|
"""Initialize the entity."""
|
||||||
|
super().__init__(device, desc)
|
||||||
|
self._state = None
|
||||||
|
self._key = key
|
||||||
|
self._unit = unit
|
||||||
|
self._icon = icon
|
||||||
|
self._device_class = device_class
|
||||||
|
self._sign = sign
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
"""Return true if the binary sensor is on."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""Return true if the sensor is available."""
|
||||||
|
return self._state is not None
|
||||||
|
|
||||||
|
async def async_update(self):
|
||||||
|
"""Update the sensos status."""
|
||||||
|
status = self.device.appliance.status
|
||||||
|
if self._key not in status:
|
||||||
|
self._state = None
|
||||||
|
else:
|
||||||
|
if self.device_class == DEVICE_CLASS_TIMESTAMP:
|
||||||
|
if "value" not in status[self._key]:
|
||||||
|
self._state = None
|
||||||
|
elif (
|
||||||
|
self._state is not None
|
||||||
|
and self._sign == 1
|
||||||
|
and self._state < dt_util.utcnow()
|
||||||
|
):
|
||||||
|
# if the date is supposed to be in the future but we're
|
||||||
|
# already past it, set state to None.
|
||||||
|
self._state = None
|
||||||
|
else:
|
||||||
|
seconds = self._sign * float(status[self._key]["value"])
|
||||||
|
self._state = (
|
||||||
|
dt_util.utcnow() + timedelta(seconds=seconds)
|
||||||
|
).isoformat()
|
||||||
|
else:
|
||||||
|
self._state = status[self._key].get("value")
|
||||||
|
_LOGGER.debug("Updated, new state: %s", self._state)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
"""Return the unit of measurement."""
|
||||||
|
return self._unit
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self):
|
||||||
|
"""Return the icon."""
|
||||||
|
return self._icon
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_class(self):
|
||||||
|
"""Return the device class."""
|
||||||
|
return self._device_class
|
15
homeassistant/components/home_connect/strings.json
Normal file
15
homeassistant/components/home_connect/strings.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"pick_implementation": {
|
||||||
|
"title": "Pick Authentication Method"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"missing_configuration": "The Home Connect component is not configured. Please follow the documentation."
|
||||||
|
},
|
||||||
|
"create_entry": {
|
||||||
|
"default": "Successfully authenticated with Home Connect."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
158
homeassistant/components/home_connect/switch.py
Normal file
158
homeassistant/components/home_connect/switch.py
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
"""Provides a switch for Home Connect."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeconnect.api import HomeConnectError
|
||||||
|
|
||||||
|
from homeassistant.components.switch import SwitchEntity
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
BSH_ACTIVE_PROGRAM,
|
||||||
|
BSH_OPERATION_STATE,
|
||||||
|
BSH_POWER_ON,
|
||||||
|
BSH_POWER_STATE,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
|
from .entity import HomeConnectEntity
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
|
"""Set up the Home Connect switch."""
|
||||||
|
|
||||||
|
def get_entities():
|
||||||
|
"""Get a list of entities."""
|
||||||
|
entities = []
|
||||||
|
hc_api = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
for device_dict in hc_api.devices:
|
||||||
|
entity_dicts = device_dict.get("entities", {}).get("switch", [])
|
||||||
|
entity_list = [HomeConnectProgramSwitch(**d) for d in entity_dicts]
|
||||||
|
entity_list += [HomeConnectPowerSwitch(device_dict["device"])]
|
||||||
|
entities += entity_list
|
||||||
|
return entities
|
||||||
|
|
||||||
|
async_add_entities(await hass.async_add_executor_job(get_entities), True)
|
||||||
|
|
||||||
|
|
||||||
|
class HomeConnectProgramSwitch(HomeConnectEntity, SwitchEntity):
|
||||||
|
"""Switch class for Home Connect."""
|
||||||
|
|
||||||
|
def __init__(self, device, program_name):
|
||||||
|
"""Initialize the entity."""
|
||||||
|
desc = " ".join(["Program", program_name.split(".")[-1]])
|
||||||
|
super().__init__(device, desc)
|
||||||
|
self.program_name = program_name
|
||||||
|
self._state = None
|
||||||
|
self._remote_allowed = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
"""Return true if the switch is on."""
|
||||||
|
return bool(self._state)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""Return true if the entity is available."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def async_turn_on(self, **kwargs):
|
||||||
|
"""Start the program."""
|
||||||
|
_LOGGER.debug("Tried to turn on program %s", self.program_name)
|
||||||
|
try:
|
||||||
|
await self.hass.async_add_executor_job(
|
||||||
|
self.device.appliance.start_program, self.program_name
|
||||||
|
)
|
||||||
|
except HomeConnectError as err:
|
||||||
|
_LOGGER.error("Error while trying to start program: %s", err)
|
||||||
|
self.async_entity_update()
|
||||||
|
|
||||||
|
async def async_turn_off(self, **kwargs):
|
||||||
|
"""Stop the program."""
|
||||||
|
_LOGGER.debug("Tried to stop program %s", self.program_name)
|
||||||
|
try:
|
||||||
|
await self.hass.async_add_executor_job(self.device.appliance.stop_program)
|
||||||
|
except HomeConnectError as err:
|
||||||
|
_LOGGER.error("Error while trying to stop program: %s", err)
|
||||||
|
self.async_entity_update()
|
||||||
|
|
||||||
|
async def async_update(self):
|
||||||
|
"""Update the switch's status."""
|
||||||
|
state = self.device.appliance.status.get(BSH_ACTIVE_PROGRAM, {})
|
||||||
|
if state.get("value") == self.program_name:
|
||||||
|
self._state = True
|
||||||
|
else:
|
||||||
|
self._state = False
|
||||||
|
_LOGGER.debug("Updated, new state: %s", self._state)
|
||||||
|
|
||||||
|
|
||||||
|
class HomeConnectPowerSwitch(HomeConnectEntity, SwitchEntity):
|
||||||
|
"""Power switch class for Home Connect."""
|
||||||
|
|
||||||
|
def __init__(self, device):
|
||||||
|
"""Inititialize the entity."""
|
||||||
|
super().__init__(device, "Power")
|
||||||
|
self._state = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
"""Return true if the switch is on."""
|
||||||
|
return bool(self._state)
|
||||||
|
|
||||||
|
async def async_turn_on(self, **kwargs):
|
||||||
|
"""Switch the device on."""
|
||||||
|
_LOGGER.debug("Tried to switch on %s", self.name)
|
||||||
|
try:
|
||||||
|
await self.hass.async_add_executor_job(
|
||||||
|
self.device.appliance.set_setting, BSH_POWER_STATE, BSH_POWER_ON,
|
||||||
|
)
|
||||||
|
except HomeConnectError as err:
|
||||||
|
_LOGGER.error("Error while trying to turn on device: %s", err)
|
||||||
|
self._state = False
|
||||||
|
self.async_entity_update()
|
||||||
|
|
||||||
|
async def async_turn_off(self, **kwargs):
|
||||||
|
"""Switch the device off."""
|
||||||
|
_LOGGER.debug("tried to switch off %s", self.name)
|
||||||
|
try:
|
||||||
|
await self.hass.async_add_executor_job(
|
||||||
|
self.device.appliance.set_setting,
|
||||||
|
BSH_POWER_STATE,
|
||||||
|
self.device.power_off_state,
|
||||||
|
)
|
||||||
|
except HomeConnectError as err:
|
||||||
|
_LOGGER.error("Error while trying to turn off device: %s", err)
|
||||||
|
self._state = True
|
||||||
|
self.async_entity_update()
|
||||||
|
|
||||||
|
async def async_update(self):
|
||||||
|
"""Update the switch's status."""
|
||||||
|
if (
|
||||||
|
self.device.appliance.status.get(BSH_POWER_STATE, {}).get("value")
|
||||||
|
== BSH_POWER_ON
|
||||||
|
):
|
||||||
|
self._state = True
|
||||||
|
elif (
|
||||||
|
self.device.appliance.status.get(BSH_POWER_STATE, {}).get("value")
|
||||||
|
== self.device.power_off_state
|
||||||
|
):
|
||||||
|
self._state = False
|
||||||
|
elif self.device.appliance.status.get(BSH_OPERATION_STATE, {}).get(
|
||||||
|
"value", None
|
||||||
|
) in [
|
||||||
|
"BSH.Common.EnumType.OperationState.Ready",
|
||||||
|
"BSH.Common.EnumType.OperationState.DelayedStart",
|
||||||
|
"BSH.Common.EnumType.OperationState.Run",
|
||||||
|
"BSH.Common.EnumType.OperationState.Pause",
|
||||||
|
"BSH.Common.EnumType.OperationState.ActionRequired",
|
||||||
|
"BSH.Common.EnumType.OperationState.Aborting",
|
||||||
|
"BSH.Common.EnumType.OperationState.Finished",
|
||||||
|
]:
|
||||||
|
self._state = True
|
||||||
|
elif (
|
||||||
|
self.device.appliance.status.get(BSH_OPERATION_STATE, {}).get("value")
|
||||||
|
== "BSH.Common.EnumType.OperationState.Inactive"
|
||||||
|
):
|
||||||
|
self._state = False
|
||||||
|
else:
|
||||||
|
self._state = None
|
||||||
|
_LOGGER.debug("Updated, new state: %s", self._state)
|
@ -50,6 +50,7 @@ FLOWS = [
|
|||||||
"harmony",
|
"harmony",
|
||||||
"heos",
|
"heos",
|
||||||
"hisense_aehw4a1",
|
"hisense_aehw4a1",
|
||||||
|
"home_connect",
|
||||||
"homekit",
|
"homekit",
|
||||||
"homekit_controller",
|
"homekit_controller",
|
||||||
"homematicip_cloud",
|
"homematicip_cloud",
|
||||||
|
@ -721,6 +721,9 @@ home-assistant-frontend==20200427.2
|
|||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
homeassistant-pyozw==0.1.10
|
homeassistant-pyozw==0.1.10
|
||||||
|
|
||||||
|
# homeassistant.components.home_connect
|
||||||
|
homeconnect==0.5
|
||||||
|
|
||||||
# homeassistant.components.homematicip_cloud
|
# homeassistant.components.homematicip_cloud
|
||||||
homematicip==0.10.17
|
homematicip==0.10.17
|
||||||
|
|
||||||
|
@ -302,6 +302,9 @@ home-assistant-frontend==20200427.2
|
|||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
homeassistant-pyozw==0.1.10
|
homeassistant-pyozw==0.1.10
|
||||||
|
|
||||||
|
# homeassistant.components.home_connect
|
||||||
|
homeconnect==0.5
|
||||||
|
|
||||||
# homeassistant.components.homematicip_cloud
|
# homeassistant.components.homematicip_cloud
|
||||||
homematicip==0.10.17
|
homematicip==0.10.17
|
||||||
|
|
||||||
|
1
tests/components/home_connect/__init__.py
Normal file
1
tests/components/home_connect/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for the Home Connect integration."""
|
54
tests/components/home_connect/test_config_flow.py
Normal file
54
tests/components/home_connect/test_config_flow.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
"""Test the Home Connect config flow."""
|
||||||
|
from homeassistant import config_entries, data_entry_flow, setup
|
||||||
|
from homeassistant.components.home_connect.const import (
|
||||||
|
DOMAIN,
|
||||||
|
OAUTH2_AUTHORIZE,
|
||||||
|
OAUTH2_TOKEN,
|
||||||
|
)
|
||||||
|
from homeassistant.helpers import config_entry_oauth2_flow
|
||||||
|
|
||||||
|
CLIENT_ID = "1234"
|
||||||
|
CLIENT_SECRET = "5678"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_full_flow(hass, aiohttp_client, aioclient_mock):
|
||||||
|
"""Check full flow."""
|
||||||
|
assert await setup.async_setup_component(
|
||||||
|
hass,
|
||||||
|
"home_connect",
|
||||||
|
{
|
||||||
|
"home_connect": {"client_id": CLIENT_ID, "client_secret": CLIENT_SECRET},
|
||||||
|
"http": {"base_url": "https://example.com"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
"home_connect", context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
state = config_entry_oauth2_flow._encode_jwt(hass, {"flow_id": result["flow_id"]})
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP
|
||||||
|
assert result["url"] == (
|
||||||
|
f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}"
|
||||||
|
"&redirect_uri=https://example.com/auth/external/callback"
|
||||||
|
f"&state={state}"
|
||||||
|
)
|
||||||
|
|
||||||
|
client = await aiohttp_client(hass.http.app)
|
||||||
|
resp = await client.get(f"/auth/external/callback?code=abcd&state={state}")
|
||||||
|
assert resp.status == 200
|
||||||
|
assert resp.headers["content-type"] == "text/html; charset=utf-8"
|
||||||
|
|
||||||
|
aioclient_mock.post(
|
||||||
|
OAUTH2_TOKEN,
|
||||||
|
json={
|
||||||
|
"refresh_token": "mock-refresh-token",
|
||||||
|
"access_token": "mock-access-token",
|
||||||
|
"type": "Bearer",
|
||||||
|
"expires_in": 60,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||||
|
|
||||||
|
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
Loading…
x
Reference in New Issue
Block a user