Add config flow to fibaro (#65203)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
00b53502fb
commit
e844c2380a
20 changed files with 565 additions and 157 deletions
11
.coveragerc
11
.coveragerc
|
@ -335,7 +335,16 @@ omit =
|
||||||
homeassistant/components/faa_delays/binary_sensor.py
|
homeassistant/components/faa_delays/binary_sensor.py
|
||||||
homeassistant/components/fastdotcom/*
|
homeassistant/components/fastdotcom/*
|
||||||
homeassistant/components/ffmpeg/camera.py
|
homeassistant/components/ffmpeg/camera.py
|
||||||
homeassistant/components/fibaro/*
|
homeassistant/components/fibaro/__init__.py
|
||||||
|
homeassistant/components/fibaro/binary_sensor.py
|
||||||
|
homeassistant/components/fibaro/climate.py
|
||||||
|
homeassistant/components/fibaro/cover.py
|
||||||
|
homeassistant/components/fibaro/light.py
|
||||||
|
homeassistant/components/fibaro/lock.py
|
||||||
|
homeassistant/components/fibaro/scene.py
|
||||||
|
homeassistant/components/fibaro/sensor.py
|
||||||
|
homeassistant/components/fibaro/switch.py
|
||||||
|
homeassistant/components/filesize/sensor.py
|
||||||
homeassistant/components/fints/sensor.py
|
homeassistant/components/fints/sensor.py
|
||||||
homeassistant/components/fireservicerota/__init__.py
|
homeassistant/components/fireservicerota/__init__.py
|
||||||
homeassistant/components/fireservicerota/binary_sensor.py
|
homeassistant/components/fireservicerota/binary_sensor.py
|
||||||
|
|
|
@ -306,6 +306,8 @@ tests/components/faa_delays/* @ntilley905
|
||||||
homeassistant/components/fan/* @home-assistant/core
|
homeassistant/components/fan/* @home-assistant/core
|
||||||
tests/components/fan/* @home-assistant/core
|
tests/components/fan/* @home-assistant/core
|
||||||
homeassistant/components/fastdotcom/* @rohankapoorcom
|
homeassistant/components/fastdotcom/* @rohankapoorcom
|
||||||
|
homeassistant/components/fibaro/* @rappenze
|
||||||
|
tests/components/fibaro/* @rappenze
|
||||||
homeassistant/components/file/* @fabaff
|
homeassistant/components/file/* @fabaff
|
||||||
tests/components/file/* @fabaff
|
tests/components/file/* @fabaff
|
||||||
homeassistant/components/filesize/* @gjohansson-ST
|
homeassistant/components/filesize/* @gjohansson-ST
|
||||||
|
|
|
@ -3,10 +3,13 @@ from __future__ import annotations
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from fiblary3.client.v4.client import Client as FibaroClient, StateHandler
|
from fiblary3.client.v4.client import Client as FibaroClient, StateHandler
|
||||||
|
from fiblary3.common.exceptions import HTTPException
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ARMED,
|
ATTR_ARMED,
|
||||||
ATTR_BATTERY_LEVEL,
|
ATTR_BATTERY_LEVEL,
|
||||||
|
@ -17,16 +20,17 @@ from homeassistant.const import (
|
||||||
CONF_URL,
|
CONF_URL,
|
||||||
CONF_USERNAME,
|
CONF_USERNAME,
|
||||||
CONF_WHITE_VALUE,
|
CONF_WHITE_VALUE,
|
||||||
EVENT_HOMEASSISTANT_STOP,
|
|
||||||
Platform,
|
Platform,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import discovery
|
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
from homeassistant.util import convert, slugify
|
from homeassistant.util import convert, slugify
|
||||||
|
|
||||||
|
from .const import CONF_IMPORT_PLUGINS, DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
ATTR_CURRENT_POWER_W = "current_power_w"
|
ATTR_CURRENT_POWER_W = "current_power_w"
|
||||||
|
@ -37,8 +41,7 @@ CONF_DIMMING = "dimming"
|
||||||
CONF_GATEWAYS = "gateways"
|
CONF_GATEWAYS = "gateways"
|
||||||
CONF_PLUGINS = "plugins"
|
CONF_PLUGINS = "plugins"
|
||||||
CONF_RESET_COLOR = "reset_color"
|
CONF_RESET_COLOR = "reset_color"
|
||||||
DOMAIN = "fibaro"
|
FIBARO_CONTROLLER = "fibaro_controller"
|
||||||
FIBARO_CONTROLLERS = "fibaro_controllers"
|
|
||||||
FIBARO_DEVICES = "fibaro_devices"
|
FIBARO_DEVICES = "fibaro_devices"
|
||||||
PLATFORMS = [
|
PLATFORMS = [
|
||||||
Platform.BINARY_SENSOR,
|
Platform.BINARY_SENSOR,
|
||||||
|
@ -102,11 +105,14 @@ GATEWAY_CONFIG = vol.Schema(
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
|
vol.All(
|
||||||
|
cv.deprecated(DOMAIN),
|
||||||
{
|
{
|
||||||
DOMAIN: vol.Schema(
|
DOMAIN: vol.Schema(
|
||||||
{vol.Required(CONF_GATEWAYS): vol.All(cv.ensure_list, [GATEWAY_CONFIG])}
|
{vol.Required(CONF_GATEWAYS): vol.All(cv.ensure_list, [GATEWAY_CONFIG])}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
),
|
||||||
extra=vol.ALLOW_EXTRA,
|
extra=vol.ALLOW_EXTRA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -116,21 +122,19 @@ class FibaroController:
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
"""Initialize the Fibaro controller."""
|
"""Initialize the Fibaro controller."""
|
||||||
|
|
||||||
self._client = FibaroClient(
|
self._client = FibaroClient(
|
||||||
config[CONF_URL], config[CONF_USERNAME], config[CONF_PASSWORD]
|
config[CONF_URL], config[CONF_USERNAME], config[CONF_PASSWORD]
|
||||||
)
|
)
|
||||||
self._scene_map = None
|
self._scene_map = None
|
||||||
# Whether to import devices from plugins
|
# Whether to import devices from plugins
|
||||||
self._import_plugins = config[CONF_PLUGINS]
|
self._import_plugins = config[CONF_IMPORT_PLUGINS]
|
||||||
self._device_config = config[CONF_DEVICE_CONFIG]
|
|
||||||
self._room_map = None # Mapping roomId to room object
|
self._room_map = None # Mapping roomId to room object
|
||||||
self._device_map = None # Mapping deviceId to device object
|
self._device_map = None # Mapping deviceId to device object
|
||||||
self.fibaro_devices = None # List of devices by type
|
self.fibaro_devices = None # List of devices by type
|
||||||
self._callbacks = {} # Update value callbacks by deviceId
|
self._callbacks = {} # Update value callbacks by deviceId
|
||||||
self._state_handler = None # Fiblary's StateHandler object
|
self._state_handler = None # Fiblary's StateHandler object
|
||||||
self._excluded_devices = config[CONF_EXCLUDE]
|
|
||||||
self.hub_serial = None # Unique serial number of the hub
|
self.hub_serial = None # Unique serial number of the hub
|
||||||
|
self.name = None # The friendly name of the hub
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
"""Start the communication with the Fibaro controller."""
|
"""Start the communication with the Fibaro controller."""
|
||||||
|
@ -138,6 +142,7 @@ class FibaroController:
|
||||||
login = self._client.login.get()
|
login = self._client.login.get()
|
||||||
info = self._client.info.get()
|
info = self._client.info.get()
|
||||||
self.hub_serial = slugify(info.serialNumber)
|
self.hub_serial = slugify(info.serialNumber)
|
||||||
|
self.name = slugify(info.hcName)
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
_LOGGER.error("Can't connect to Fibaro HC. Please check URL")
|
_LOGGER.error("Can't connect to Fibaro HC. Please check URL")
|
||||||
return False
|
return False
|
||||||
|
@ -152,6 +157,23 @@ class FibaroController:
|
||||||
self._read_scenes()
|
self._read_scenes()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def connect_with_error_handling(self) -> None:
|
||||||
|
"""Translate connect errors to easily differentiate auth and connect failures.
|
||||||
|
|
||||||
|
When there is a better error handling in the used library this can be improved.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
connected = self.connect()
|
||||||
|
if not connected:
|
||||||
|
raise FibaroConnectFailed("Connect status is false")
|
||||||
|
except HTTPException as http_ex:
|
||||||
|
if http_ex.details == "Forbidden":
|
||||||
|
raise FibaroAuthFailed from http_ex
|
||||||
|
|
||||||
|
raise FibaroConnectFailed from http_ex
|
||||||
|
except Exception as ex:
|
||||||
|
raise FibaroConnectFailed from ex
|
||||||
|
|
||||||
def enable_state_handler(self):
|
def enable_state_handler(self):
|
||||||
"""Start StateHandler thread for monitoring updates."""
|
"""Start StateHandler thread for monitoring updates."""
|
||||||
self._state_handler = StateHandler(self._client, self._on_state_change)
|
self._state_handler = StateHandler(self._client, self._on_state_change)
|
||||||
|
@ -299,16 +321,11 @@ class FibaroController:
|
||||||
device.ha_id = (
|
device.ha_id = (
|
||||||
f"{slugify(room_name)}_{slugify(device.name)}_{device.id}"
|
f"{slugify(room_name)}_{slugify(device.name)}_{device.id}"
|
||||||
)
|
)
|
||||||
if (
|
if device.enabled and (
|
||||||
device.enabled
|
|
||||||
and (
|
|
||||||
"isPlugin" not in device
|
"isPlugin" not in device
|
||||||
or (not device.isPlugin or self._import_plugins)
|
or (not device.isPlugin or self._import_plugins)
|
||||||
)
|
|
||||||
and device.ha_id not in self._excluded_devices
|
|
||||||
):
|
):
|
||||||
device.mapped_type = self._map_device_to_type(device)
|
device.mapped_type = self._map_device_to_type(device)
|
||||||
device.device_config = self._device_config.get(device.ha_id, {})
|
|
||||||
else:
|
else:
|
||||||
device.mapped_type = None
|
device.mapped_type = None
|
||||||
if (dtype := device.mapped_type) is None:
|
if (dtype := device.mapped_type) is None:
|
||||||
|
@ -357,40 +374,79 @@ class FibaroController:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def setup(hass: HomeAssistant, base_config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistant, base_config: ConfigType) -> bool:
|
||||||
"""Set up the Fibaro Component."""
|
"""Migrate configuration from configuration.yaml."""
|
||||||
|
if DOMAIN not in base_config:
|
||||||
|
return True
|
||||||
gateways = base_config[DOMAIN][CONF_GATEWAYS]
|
gateways = base_config[DOMAIN][CONF_GATEWAYS]
|
||||||
hass.data[FIBARO_CONTROLLERS] = {}
|
if gateways is None:
|
||||||
|
|
||||||
def stop_fibaro(event):
|
|
||||||
"""Stop Fibaro Thread."""
|
|
||||||
_LOGGER.info("Shutting down Fibaro connection")
|
|
||||||
for controller in hass.data[FIBARO_CONTROLLERS].values():
|
|
||||||
controller.disable_state_handler()
|
|
||||||
|
|
||||||
hass.data[FIBARO_DEVICES] = {}
|
|
||||||
for platform in PLATFORMS:
|
|
||||||
hass.data[FIBARO_DEVICES][platform] = []
|
|
||||||
|
|
||||||
for gateway in gateways:
|
|
||||||
controller = FibaroController(gateway)
|
|
||||||
if controller.connect():
|
|
||||||
hass.data[FIBARO_CONTROLLERS][controller.hub_serial] = controller
|
|
||||||
for platform in PLATFORMS:
|
|
||||||
hass.data[FIBARO_DEVICES][platform].extend(
|
|
||||||
controller.fibaro_devices[platform]
|
|
||||||
)
|
|
||||||
|
|
||||||
if hass.data[FIBARO_CONTROLLERS]:
|
|
||||||
for platform in PLATFORMS:
|
|
||||||
discovery.load_platform(hass, platform, DOMAIN, {}, base_config)
|
|
||||||
for controller in hass.data[FIBARO_CONTROLLERS].values():
|
|
||||||
controller.enable_state_handler()
|
|
||||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_fibaro)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# check if already configured
|
||||||
|
if hass.config_entries.async_entries(DOMAIN):
|
||||||
|
return True
|
||||||
|
|
||||||
|
for gateway in gateways:
|
||||||
|
# prepare new config based on configuration.yaml
|
||||||
|
conf = {
|
||||||
|
CONF_URL: gateway[CONF_URL],
|
||||||
|
CONF_USERNAME: gateway[CONF_USERNAME],
|
||||||
|
CONF_PASSWORD: gateway[CONF_PASSWORD],
|
||||||
|
CONF_IMPORT_PLUGINS: gateway[CONF_PLUGINS],
|
||||||
|
}
|
||||||
|
|
||||||
|
# import into config flow based configuration
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_IMPORT}, data=conf
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _init_controller(data: dict[str, Any]) -> FibaroController:
|
||||||
|
"""Validate the user input allows us to connect to fibaro."""
|
||||||
|
controller = FibaroController(data)
|
||||||
|
controller.connect_with_error_handling()
|
||||||
|
return controller
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Set up the Fibaro Component."""
|
||||||
|
try:
|
||||||
|
controller = await hass.async_add_executor_job(_init_controller, entry.data)
|
||||||
|
except FibaroConnectFailed as connect_ex:
|
||||||
|
raise ConfigEntryNotReady(
|
||||||
|
f"Could not connect to controller at {entry.data[CONF_URL]}"
|
||||||
|
) from connect_ex
|
||||||
|
except FibaroAuthFailed:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
data: dict[str, Any] = {}
|
||||||
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = data
|
||||||
|
data[FIBARO_CONTROLLER] = controller
|
||||||
|
devices = data[FIBARO_DEVICES] = {}
|
||||||
|
for platform in PLATFORMS:
|
||||||
|
devices[platform] = [*controller.fibaro_devices[platform]]
|
||||||
|
|
||||||
|
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||||
|
|
||||||
|
controller.enable_state_handler()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Unload a config entry."""
|
||||||
|
_LOGGER.info("Shutting down Fibaro connection")
|
||||||
|
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
|
|
||||||
|
hass.data[DOMAIN][entry.entry_id][FIBARO_CONTROLLER].disable_state_handler()
|
||||||
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
|
||||||
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
class FibaroDevice(Entity):
|
class FibaroDevice(Entity):
|
||||||
"""Representation of a Fibaro device entity."""
|
"""Representation of a Fibaro device entity."""
|
||||||
|
@ -519,3 +575,11 @@ class FibaroDevice(Entity):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
|
|
||||||
|
class FibaroConnectFailed(HomeAssistantError):
|
||||||
|
"""Error to indicate we cannot connect to fibaro home center."""
|
||||||
|
|
||||||
|
|
||||||
|
class FibaroAuthFailed(HomeAssistantError):
|
||||||
|
"""Error to indicate that authentication failed on fibaro home center."""
|
||||||
|
|
|
@ -2,16 +2,16 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import (
|
from homeassistant.components.binary_sensor import (
|
||||||
DOMAIN,
|
ENTITY_ID_FORMAT,
|
||||||
BinarySensorDeviceClass,
|
BinarySensorDeviceClass,
|
||||||
BinarySensorEntity,
|
BinarySensorEntity,
|
||||||
)
|
)
|
||||||
from homeassistant.const import CONF_DEVICE_CLASS, CONF_ICON
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|
||||||
|
|
||||||
from . import FIBARO_DEVICES, FibaroDevice
|
from . import FIBARO_DEVICES, FibaroDevice
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
"com.fibaro.floodSensor": ["Flood", "mdi:water", "flood"],
|
"com.fibaro.floodSensor": ["Flood", "mdi:water", "flood"],
|
||||||
|
@ -28,20 +28,18 @@ SENSOR_TYPES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config: ConfigType,
|
entry: ConfigEntry,
|
||||||
add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
discovery_info: DiscoveryInfoType | None = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Perform the setup for Fibaro controller devices."""
|
"""Perform the setup for Fibaro controller devices."""
|
||||||
if discovery_info is None:
|
async_add_entities(
|
||||||
return
|
|
||||||
|
|
||||||
add_entities(
|
|
||||||
[
|
[
|
||||||
FibaroBinarySensor(device)
|
FibaroBinarySensor(device)
|
||||||
for device in hass.data[FIBARO_DEVICES]["binary_sensor"]
|
for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES][
|
||||||
|
"binary_sensor"
|
||||||
|
]
|
||||||
],
|
],
|
||||||
True,
|
True,
|
||||||
)
|
)
|
||||||
|
@ -54,9 +52,8 @@ class FibaroBinarySensor(FibaroDevice, BinarySensorEntity):
|
||||||
"""Initialize the binary_sensor."""
|
"""Initialize the binary_sensor."""
|
||||||
self._state = None
|
self._state = None
|
||||||
super().__init__(fibaro_device)
|
super().__init__(fibaro_device)
|
||||||
self.entity_id = f"{DOMAIN}.{self.ha_id}"
|
self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id)
|
||||||
stype = None
|
stype = None
|
||||||
devconf = fibaro_device.device_config
|
|
||||||
if fibaro_device.type in SENSOR_TYPES:
|
if fibaro_device.type in SENSOR_TYPES:
|
||||||
stype = fibaro_device.type
|
stype = fibaro_device.type
|
||||||
elif fibaro_device.baseType in SENSOR_TYPES:
|
elif fibaro_device.baseType in SENSOR_TYPES:
|
||||||
|
@ -67,9 +64,6 @@ class FibaroBinarySensor(FibaroDevice, BinarySensorEntity):
|
||||||
else:
|
else:
|
||||||
self._device_class = None
|
self._device_class = None
|
||||||
self._icon = None
|
self._icon = None
|
||||||
# device_config overrides:
|
|
||||||
self._device_class = devconf.get(CONF_DEVICE_CLASS, self._device_class)
|
|
||||||
self._icon = devconf.get(CONF_ICON, self._icon)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def icon(self):
|
def icon(self):
|
||||||
|
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.climate import ClimateEntity
|
from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateEntity
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
HVAC_MODE_AUTO,
|
HVAC_MODE_AUTO,
|
||||||
HVAC_MODE_COOL,
|
HVAC_MODE_COOL,
|
||||||
|
@ -17,12 +17,13 @@ from homeassistant.components.climate.const import (
|
||||||
SUPPORT_PRESET_MODE,
|
SUPPORT_PRESET_MODE,
|
||||||
SUPPORT_TARGET_TEMPERATURE,
|
SUPPORT_TARGET_TEMPERATURE,
|
||||||
)
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|
||||||
|
|
||||||
from . import FIBARO_DEVICES, FibaroDevice
|
from . import FIBARO_DEVICES, FibaroDevice
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
PRESET_RESUME = "resume"
|
PRESET_RESUME = "resume"
|
||||||
PRESET_MOIST = "moist"
|
PRESET_MOIST = "moist"
|
||||||
|
@ -98,18 +99,17 @@ HA_OPMODES_HVAC = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config: ConfigType,
|
entry: ConfigEntry,
|
||||||
add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
discovery_info: DiscoveryInfoType | None = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Perform the setup for Fibaro controller devices."""
|
"""Perform the setup for Fibaro controller devices."""
|
||||||
if discovery_info is None:
|
async_add_entities(
|
||||||
return
|
[
|
||||||
|
FibaroThermostat(device)
|
||||||
add_entities(
|
for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES]["climate"]
|
||||||
[FibaroThermostat(device) for device in hass.data[FIBARO_DEVICES]["climate"]],
|
],
|
||||||
True,
|
True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ class FibaroThermostat(FibaroDevice, ClimateEntity):
|
||||||
self._op_mode_device = None
|
self._op_mode_device = None
|
||||||
self._fan_mode_device = None
|
self._fan_mode_device = None
|
||||||
self._support_flags = 0
|
self._support_flags = 0
|
||||||
self.entity_id = f"climate.{self.ha_id}"
|
self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id)
|
||||||
self._hvac_support = []
|
self._hvac_support = []
|
||||||
self._preset_support = []
|
self._preset_support = []
|
||||||
self._fan_support = []
|
self._fan_support = []
|
||||||
|
|
81
homeassistant/components/fibaro/config_flow.py
Normal file
81
homeassistant/components/fibaro/config_flow.py
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
"""Config flow for Fibaro integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
|
from . import FibaroAuthFailed, FibaroConnectFailed, FibaroController
|
||||||
|
from .const import CONF_IMPORT_PLUGINS, DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_URL): str,
|
||||||
|
vol.Required(CONF_USERNAME): str,
|
||||||
|
vol.Required(CONF_PASSWORD): str,
|
||||||
|
vol.Optional(CONF_IMPORT_PLUGINS, default=False): bool,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _connect_to_fibaro(data: dict[str, Any]) -> FibaroController:
|
||||||
|
"""Validate the user input allows us to connect to fibaro."""
|
||||||
|
controller = FibaroController(data)
|
||||||
|
controller.connect_with_error_handling()
|
||||||
|
return controller
|
||||||
|
|
||||||
|
|
||||||
|
async def _validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
"""Validate the user input allows us to connect.
|
||||||
|
|
||||||
|
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
|
||||||
|
"""
|
||||||
|
controller = await hass.async_add_executor_job(_connect_to_fibaro, data)
|
||||||
|
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Successfully connected to fibaro home center %s with name %s",
|
||||||
|
controller.hub_serial,
|
||||||
|
controller.name,
|
||||||
|
)
|
||||||
|
return {"serial_number": controller.hub_serial, "name": controller.name}
|
||||||
|
|
||||||
|
|
||||||
|
class FibaroConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Handle a config flow for Fibaro."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
async def async_step_user(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> FlowResult:
|
||||||
|
"""Handle the initial step."""
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
try:
|
||||||
|
info = await _validate_input(self.hass, user_input)
|
||||||
|
except FibaroConnectFailed:
|
||||||
|
errors["base"] = "cannot_connect"
|
||||||
|
except FibaroAuthFailed:
|
||||||
|
errors["base"] = "invalid_auth"
|
||||||
|
else:
|
||||||
|
await self.async_set_unique_id(info["serial_number"])
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
return self.async_create_entry(title=info["name"], data=user_input)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_import(self, import_config: ConfigType | None) -> FlowResult:
|
||||||
|
"""Import a config entry."""
|
||||||
|
return await self.async_step_user(import_config)
|
4
homeassistant/components/fibaro/const.py
Normal file
4
homeassistant/components/fibaro/const.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
"""Constants for the Fibaro integration."""
|
||||||
|
|
||||||
|
DOMAIN = "fibaro"
|
||||||
|
CONF_IMPORT_PLUGINS = "import_plugins"
|
|
@ -4,28 +4,29 @@ from __future__ import annotations
|
||||||
from homeassistant.components.cover import (
|
from homeassistant.components.cover import (
|
||||||
ATTR_POSITION,
|
ATTR_POSITION,
|
||||||
ATTR_TILT_POSITION,
|
ATTR_TILT_POSITION,
|
||||||
DOMAIN,
|
ENTITY_ID_FORMAT,
|
||||||
CoverEntity,
|
CoverEntity,
|
||||||
)
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|
||||||
|
|
||||||
from . import FIBARO_DEVICES, FibaroDevice
|
from . import FIBARO_DEVICES, FibaroDevice
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config: ConfigType,
|
entry: ConfigEntry,
|
||||||
add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
discovery_info: DiscoveryInfoType | None = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Fibaro covers."""
|
"""Set up the Fibaro covers."""
|
||||||
if discovery_info is None:
|
async_add_entities(
|
||||||
return
|
[
|
||||||
|
FibaroCover(device)
|
||||||
add_entities(
|
for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES]["cover"]
|
||||||
[FibaroCover(device) for device in hass.data[FIBARO_DEVICES]["cover"]], True
|
],
|
||||||
|
True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,7 +36,7 @@ class FibaroCover(FibaroDevice, CoverEntity):
|
||||||
def __init__(self, fibaro_device):
|
def __init__(self, fibaro_device):
|
||||||
"""Initialize the Vera device."""
|
"""Initialize the Vera device."""
|
||||||
super().__init__(fibaro_device)
|
super().__init__(fibaro_device)
|
||||||
self.entity_id = f"{DOMAIN}.{self.ha_id}"
|
self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def bound(position):
|
def bound(position):
|
||||||
|
|
|
@ -8,19 +8,19 @@ from homeassistant.components.light import (
|
||||||
ATTR_BRIGHTNESS,
|
ATTR_BRIGHTNESS,
|
||||||
ATTR_HS_COLOR,
|
ATTR_HS_COLOR,
|
||||||
ATTR_WHITE_VALUE,
|
ATTR_WHITE_VALUE,
|
||||||
DOMAIN,
|
ENTITY_ID_FORMAT,
|
||||||
SUPPORT_BRIGHTNESS,
|
SUPPORT_BRIGHTNESS,
|
||||||
SUPPORT_COLOR,
|
SUPPORT_COLOR,
|
||||||
SUPPORT_WHITE_VALUE,
|
SUPPORT_WHITE_VALUE,
|
||||||
LightEntity,
|
LightEntity,
|
||||||
)
|
)
|
||||||
from homeassistant.const import CONF_WHITE_VALUE
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|
||||||
import homeassistant.util.color as color_util
|
import homeassistant.util.color as color_util
|
||||||
|
|
||||||
from . import CONF_COLOR, CONF_DIMMING, CONF_RESET_COLOR, FIBARO_DEVICES, FibaroDevice
|
from . import FIBARO_DEVICES, FibaroDevice
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
|
||||||
def scaleto255(value):
|
def scaleto255(value):
|
||||||
|
@ -40,18 +40,18 @@ def scaleto100(value):
|
||||||
return max(0, min(100, ((value * 100.0) / 255.0)))
|
return max(0, min(100, ((value * 100.0) / 255.0)))
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config: ConfigType,
|
entry: ConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
discovery_info: DiscoveryInfoType | None = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Perform the setup for Fibaro controller devices."""
|
"""Perform the setup for Fibaro controller devices."""
|
||||||
if discovery_info is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
[FibaroLight(device) for device in hass.data[FIBARO_DEVICES]["light"]], True
|
[
|
||||||
|
FibaroLight(device)
|
||||||
|
for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES]["light"]
|
||||||
|
],
|
||||||
|
True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -67,8 +67,7 @@ class FibaroLight(FibaroDevice, LightEntity):
|
||||||
self._update_lock = asyncio.Lock()
|
self._update_lock = asyncio.Lock()
|
||||||
self._white = 0
|
self._white = 0
|
||||||
|
|
||||||
devconf = fibaro_device.device_config
|
self._reset_color = False
|
||||||
self._reset_color = devconf.get(CONF_RESET_COLOR, False)
|
|
||||||
supports_color = (
|
supports_color = (
|
||||||
"color" in fibaro_device.properties
|
"color" in fibaro_device.properties
|
||||||
or "colorComponents" in fibaro_device.properties
|
or "colorComponents" in fibaro_device.properties
|
||||||
|
@ -91,15 +90,15 @@ class FibaroLight(FibaroDevice, LightEntity):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Configuration can override default capability detection
|
# Configuration can override default capability detection
|
||||||
if devconf.get(CONF_DIMMING, supports_dimming):
|
if supports_dimming:
|
||||||
self._supported_flags |= SUPPORT_BRIGHTNESS
|
self._supported_flags |= SUPPORT_BRIGHTNESS
|
||||||
if devconf.get(CONF_COLOR, supports_color):
|
if supports_color:
|
||||||
self._supported_flags |= SUPPORT_COLOR
|
self._supported_flags |= SUPPORT_COLOR
|
||||||
if devconf.get(CONF_WHITE_VALUE, supports_white_v):
|
if supports_white_v:
|
||||||
self._supported_flags |= SUPPORT_WHITE_VALUE
|
self._supported_flags |= SUPPORT_WHITE_VALUE
|
||||||
|
|
||||||
super().__init__(fibaro_device)
|
super().__init__(fibaro_device)
|
||||||
self.entity_id = f"{DOMAIN}.{self.ha_id}"
|
self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def brightness(self):
|
def brightness(self):
|
||||||
|
|
|
@ -1,26 +1,27 @@
|
||||||
"""Support for Fibaro locks."""
|
"""Support for Fibaro locks."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from homeassistant.components.lock import DOMAIN, LockEntity
|
from homeassistant.components.lock import ENTITY_ID_FORMAT, LockEntity
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|
||||||
|
|
||||||
from . import FIBARO_DEVICES, FibaroDevice
|
from . import FIBARO_DEVICES, FibaroDevice
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config: ConfigType,
|
entry: ConfigEntry,
|
||||||
add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
discovery_info: DiscoveryInfoType | None = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Fibaro locks."""
|
"""Set up the Fibaro locks."""
|
||||||
if discovery_info is None:
|
async_add_entities(
|
||||||
return
|
[
|
||||||
|
FibaroLock(device)
|
||||||
add_entities(
|
for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES]["lock"]
|
||||||
[FibaroLock(device) for device in hass.data[FIBARO_DEVICES]["lock"]], True
|
],
|
||||||
|
True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,7 +32,7 @@ class FibaroLock(FibaroDevice, LockEntity):
|
||||||
"""Initialize the Fibaro device."""
|
"""Initialize the Fibaro device."""
|
||||||
self._state = False
|
self._state = False
|
||||||
super().__init__(fibaro_device)
|
super().__init__(fibaro_device)
|
||||||
self.entity_id = f"{DOMAIN}.{self.ha_id}"
|
self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id)
|
||||||
|
|
||||||
def lock(self, **kwargs):
|
def lock(self, **kwargs):
|
||||||
"""Lock the device."""
|
"""Lock the device."""
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
"name": "Fibaro",
|
"name": "Fibaro",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/fibaro",
|
"documentation": "https://www.home-assistant.io/integrations/fibaro",
|
||||||
"requirements": ["fiblary3==0.1.8"],
|
"requirements": ["fiblary3==0.1.8"],
|
||||||
"codeowners": [],
|
"codeowners": ["@rappenze"],
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
|
"config_flow": true,
|
||||||
"loggers": ["fiblary3"]
|
"loggers": ["fiblary3"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,25 +4,26 @@ from __future__ import annotations
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from homeassistant.components.scene import Scene
|
from homeassistant.components.scene import Scene
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|
||||||
|
|
||||||
from . import FIBARO_DEVICES, FibaroDevice
|
from . import FIBARO_DEVICES, FibaroDevice
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config: ConfigType,
|
entry: ConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
discovery_info: DiscoveryInfoType | None = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Perform the setup for Fibaro scenes."""
|
"""Perform the setup for Fibaro scenes."""
|
||||||
if discovery_info is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
[FibaroScene(scene) for scene in hass.data[FIBARO_DEVICES]["scene"]], True
|
[
|
||||||
|
FibaroScene(scene)
|
||||||
|
for scene in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES]["scene"]
|
||||||
|
],
|
||||||
|
True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,12 @@ from __future__ import annotations
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
DOMAIN,
|
ENTITY_ID_FORMAT,
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
SensorEntity,
|
SensorEntity,
|
||||||
SensorStateClass,
|
SensorStateClass,
|
||||||
)
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONCENTRATION_PARTS_PER_MILLION,
|
CONCENTRATION_PARTS_PER_MILLION,
|
||||||
ENERGY_KILO_WATT_HOUR,
|
ENERGY_KILO_WATT_HOUR,
|
||||||
|
@ -19,10 +20,10 @@ from homeassistant.const import (
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|
||||||
from homeassistant.util import convert
|
from homeassistant.util import convert
|
||||||
|
|
||||||
from . import FIBARO_DEVICES, FibaroDevice
|
from . import FIBARO_DEVICES, FibaroDevice
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
"com.fibaro.temperatureSensor": [
|
"com.fibaro.temperatureSensor": [
|
||||||
|
@ -54,25 +55,21 @@ SENSOR_TYPES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config: ConfigType,
|
entry: ConfigEntry,
|
||||||
add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
discovery_info: DiscoveryInfoType | None = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Fibaro controller devices."""
|
"""Set up the Fibaro controller devices."""
|
||||||
if discovery_info is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
entities: list[SensorEntity] = []
|
entities: list[SensorEntity] = []
|
||||||
for device in hass.data[FIBARO_DEVICES]["sensor"]:
|
for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES]["sensor"]:
|
||||||
entities.append(FibaroSensor(device))
|
entities.append(FibaroSensor(device))
|
||||||
for device_type in ("cover", "light", "switch"):
|
for device_type in ("cover", "light", "switch"):
|
||||||
for device in hass.data[FIBARO_DEVICES][device_type]:
|
for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES][device_type]:
|
||||||
if "energy" in device.interfaces:
|
if "energy" in device.interfaces:
|
||||||
entities.append(FibaroEnergySensor(device))
|
entities.append(FibaroEnergySensor(device))
|
||||||
|
|
||||||
add_entities(entities, True)
|
async_add_entities(entities, True)
|
||||||
|
|
||||||
|
|
||||||
class FibaroSensor(FibaroDevice, SensorEntity):
|
class FibaroSensor(FibaroDevice, SensorEntity):
|
||||||
|
@ -83,7 +80,7 @@ class FibaroSensor(FibaroDevice, SensorEntity):
|
||||||
self.current_value = None
|
self.current_value = None
|
||||||
self.last_changed_time = None
|
self.last_changed_time = None
|
||||||
super().__init__(fibaro_device)
|
super().__init__(fibaro_device)
|
||||||
self.entity_id = f"{DOMAIN}.{self.ha_id}"
|
self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id)
|
||||||
if fibaro_device.type in SENSOR_TYPES:
|
if fibaro_device.type in SENSOR_TYPES:
|
||||||
self._unit = SENSOR_TYPES[fibaro_device.type][1]
|
self._unit = SENSOR_TYPES[fibaro_device.type][1]
|
||||||
self._icon = SENSOR_TYPES[fibaro_device.type][2]
|
self._icon = SENSOR_TYPES[fibaro_device.type][2]
|
||||||
|
@ -139,7 +136,7 @@ class FibaroEnergySensor(FibaroDevice, SensorEntity):
|
||||||
def __init__(self, fibaro_device):
|
def __init__(self, fibaro_device):
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
super().__init__(fibaro_device)
|
super().__init__(fibaro_device)
|
||||||
self.entity_id = f"{DOMAIN}.{self.ha_id}_energy"
|
self.entity_id = ENTITY_ID_FORMAT.format(f"{self.ha_id}_energy")
|
||||||
self._attr_name = f"{fibaro_device.friendly_name} Energy"
|
self._attr_name = f"{fibaro_device.friendly_name} Energy"
|
||||||
self._attr_unique_id = f"{fibaro_device.unique_id_str}_energy"
|
self._attr_unique_id = f"{fibaro_device.unique_id_str}_energy"
|
||||||
|
|
||||||
|
|
22
homeassistant/components/fibaro/strings.json
Normal file
22
homeassistant/components/fibaro/strings.json
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"url": "URL in the format http://HOST/api/",
|
||||||
|
"username": "[%key:common::config_flow::data::username%]",
|
||||||
|
"password": "[%key:common::config_flow::data::password%]",
|
||||||
|
"import_plugins": "Import entities from fibaro plugins?"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,27 +1,28 @@
|
||||||
"""Support for Fibaro switches."""
|
"""Support for Fibaro switches."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from homeassistant.components.switch import DOMAIN, SwitchEntity
|
from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchEntity
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|
||||||
from homeassistant.util import convert
|
from homeassistant.util import convert
|
||||||
|
|
||||||
from . import FIBARO_DEVICES, FibaroDevice
|
from . import FIBARO_DEVICES, FibaroDevice
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config: ConfigType,
|
entry: ConfigEntry,
|
||||||
add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
discovery_info: DiscoveryInfoType | None = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Fibaro switches."""
|
"""Set up the Fibaro switches."""
|
||||||
if discovery_info is None:
|
async_add_entities(
|
||||||
return
|
[
|
||||||
|
FibaroSwitch(device)
|
||||||
add_entities(
|
for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES]["switch"]
|
||||||
[FibaroSwitch(device) for device in hass.data[FIBARO_DEVICES]["switch"]], True
|
],
|
||||||
|
True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ class FibaroSwitch(FibaroDevice, SwitchEntity):
|
||||||
"""Initialize the Fibaro device."""
|
"""Initialize the Fibaro device."""
|
||||||
self._state = False
|
self._state = False
|
||||||
super().__init__(fibaro_device)
|
super().__init__(fibaro_device)
|
||||||
self.entity_id = f"{DOMAIN}.{self.ha_id}"
|
self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id)
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
"""Turn device on."""
|
"""Turn device on."""
|
||||||
|
|
22
homeassistant/components/fibaro/translations/en.json
Normal file
22
homeassistant/components/fibaro/translations/en.json
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Device is already configured"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Failed to connect",
|
||||||
|
"invalid_auth": "Invalid authentication",
|
||||||
|
"unknown": "Unexpected error"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"url": "URL in the format http://HOST/api/",
|
||||||
|
"import_plugins": "Import entities from fibaro plugins?",
|
||||||
|
"password": "Password",
|
||||||
|
"username": "Username"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -97,6 +97,7 @@ FLOWS = {
|
||||||
"evil_genius_labs",
|
"evil_genius_labs",
|
||||||
"ezviz",
|
"ezviz",
|
||||||
"faa_delays",
|
"faa_delays",
|
||||||
|
"fibaro",
|
||||||
"filesize",
|
"filesize",
|
||||||
"fireservicerota",
|
"fireservicerota",
|
||||||
"fivem",
|
"fivem",
|
||||||
|
|
|
@ -433,6 +433,9 @@ faadelays==0.0.7
|
||||||
# homeassistant.components.feedreader
|
# homeassistant.components.feedreader
|
||||||
feedparser==6.0.2
|
feedparser==6.0.2
|
||||||
|
|
||||||
|
# homeassistant.components.fibaro
|
||||||
|
fiblary3==0.1.8
|
||||||
|
|
||||||
# homeassistant.components.fivem
|
# homeassistant.components.fivem
|
||||||
fivem-api==0.1.2
|
fivem-api==0.1.2
|
||||||
|
|
||||||
|
|
1
tests/components/fibaro/__init__.py
Normal file
1
tests/components/fibaro/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"""Tests for the Fibaro integration."""
|
204
tests/components/fibaro/test_config_flow.py
Normal file
204
tests/components/fibaro/test_config_flow.py
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
"""Test the Fibaro config flow."""
|
||||||
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
|
from fiblary3.common.exceptions import HTTPException
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.components.fibaro import DOMAIN
|
||||||
|
from homeassistant.components.fibaro.const import CONF_IMPORT_PLUGINS
|
||||||
|
from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME
|
||||||
|
|
||||||
|
TEST_SERIALNUMBER = "HC2-111111"
|
||||||
|
TEST_NAME = "my_fibaro_home_center"
|
||||||
|
TEST_URL = "http://192.168.1.1/api/"
|
||||||
|
TEST_USERNAME = "user"
|
||||||
|
TEST_PASSWORD = "password"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="fibaro_client", autouse=True)
|
||||||
|
def fibaro_client_fixture():
|
||||||
|
"""Mock common methods and attributes of fibaro client."""
|
||||||
|
info_mock = Mock()
|
||||||
|
info_mock.get.return_value = Mock(serialNumber=TEST_SERIALNUMBER, hcName=TEST_NAME)
|
||||||
|
|
||||||
|
array_mock = Mock()
|
||||||
|
array_mock.list.return_value = []
|
||||||
|
|
||||||
|
with patch("fiblary3.client.v4.client.Client.__init__", return_value=None,), patch(
|
||||||
|
"fiblary3.client.v4.client.Client.info",
|
||||||
|
info_mock,
|
||||||
|
create=True,
|
||||||
|
), patch("fiblary3.client.v4.client.Client.rooms", array_mock, create=True,), patch(
|
||||||
|
"fiblary3.client.v4.client.Client.devices",
|
||||||
|
array_mock,
|
||||||
|
create=True,
|
||||||
|
), patch(
|
||||||
|
"fiblary3.client.v4.client.Client.scenes",
|
||||||
|
array_mock,
|
||||||
|
create=True,
|
||||||
|
):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_user_initiated_success(hass):
|
||||||
|
"""Successful flow manually initialized by the user."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
login_mock = Mock()
|
||||||
|
login_mock.get.return_value = Mock(status=True)
|
||||||
|
with patch("fiblary3.client.v4.client.Client.login", login_mock, create=True):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_URL: TEST_URL,
|
||||||
|
CONF_USERNAME: TEST_USERNAME,
|
||||||
|
CONF_PASSWORD: TEST_PASSWORD,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == TEST_NAME
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_URL: TEST_URL,
|
||||||
|
CONF_USERNAME: TEST_USERNAME,
|
||||||
|
CONF_PASSWORD: TEST_PASSWORD,
|
||||||
|
CONF_IMPORT_PLUGINS: False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_user_initiated_connect_failure(hass):
|
||||||
|
"""Connect failure in flow manually initialized by the user."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
login_mock = Mock()
|
||||||
|
login_mock.get.return_value = Mock(status=False)
|
||||||
|
with patch("fiblary3.client.v4.client.Client.login", login_mock, create=True):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_URL: TEST_URL,
|
||||||
|
CONF_USERNAME: TEST_USERNAME,
|
||||||
|
CONF_PASSWORD: TEST_PASSWORD,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {"base": "cannot_connect"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_user_initiated_auth_failure(hass):
|
||||||
|
"""Authentication failure in flow manually initialized by the user."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
login_mock = Mock()
|
||||||
|
login_mock.get.side_effect = HTTPException(details="Forbidden")
|
||||||
|
with patch("fiblary3.client.v4.client.Client.login", login_mock, create=True):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_URL: TEST_URL,
|
||||||
|
CONF_USERNAME: TEST_USERNAME,
|
||||||
|
CONF_PASSWORD: TEST_PASSWORD,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {"base": "invalid_auth"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_user_initiated_unknown_failure_1(hass):
|
||||||
|
"""Unknown failure in flow manually initialized by the user."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
login_mock = Mock()
|
||||||
|
login_mock.get.side_effect = HTTPException(details="Any")
|
||||||
|
with patch("fiblary3.client.v4.client.Client.login", login_mock, create=True):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_URL: TEST_URL,
|
||||||
|
CONF_USERNAME: TEST_USERNAME,
|
||||||
|
CONF_PASSWORD: TEST_PASSWORD,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {"base": "cannot_connect"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_user_initiated_unknown_failure_2(hass):
|
||||||
|
"""Unknown failure in flow manually initialized by the user."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_URL: TEST_URL,
|
||||||
|
CONF_USERNAME: TEST_USERNAME,
|
||||||
|
CONF_PASSWORD: TEST_PASSWORD,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {"base": "cannot_connect"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_import(hass):
|
||||||
|
"""Test for importing config from configuration.yaml."""
|
||||||
|
login_mock = Mock()
|
||||||
|
login_mock.get.return_value = Mock(status=True)
|
||||||
|
with patch("fiblary3.client.v4.client.Client.login", login_mock, create=True):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_IMPORT},
|
||||||
|
data={
|
||||||
|
CONF_URL: TEST_URL,
|
||||||
|
CONF_USERNAME: TEST_USERNAME,
|
||||||
|
CONF_PASSWORD: TEST_PASSWORD,
|
||||||
|
CONF_IMPORT_PLUGINS: False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == TEST_NAME
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_URL: TEST_URL,
|
||||||
|
CONF_USERNAME: TEST_USERNAME,
|
||||||
|
CONF_PASSWORD: TEST_PASSWORD,
|
||||||
|
CONF_IMPORT_PLUGINS: False,
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue