1
mirror of https://github.com/home-assistant/core synced 2024-09-03 08:14:07 +02:00

Prepare for Ihc config flow (#64852)

* Extracting group and extra  info from ihc products

* Make suggested area not optional

* Revert back to assignment expression :=

* Make auto setup show device info for all platforms

* Change code comment to #

* Add return typing

* Remove device_info key without value

* get_manual_configuration typings for everything

* Adding IHCController typings

* Remove "ihc" from unique id

* Remove device_info

* Separator in unique id

* Return typing on ihc_setup

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/ihc/ihcdevice.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/ihc/ihcdevice.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Raise ValueError instead of logging an error

* Update homeassistant/components/ihc/service_functions.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Catch up with dev

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
Jens Østergaard Nielsen 2022-02-11 10:24:31 +01:00 committed by GitHub
parent 43671da7cf
commit a644baf3cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 122 additions and 68 deletions

View File

@ -10,13 +10,18 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import ConfigType
from .auto_setup import autosetup_ihc_products
from .const import CONF_AUTOSETUP, CONF_INFO, DOMAIN, IHC_CONTROLLER
from .const import (
CONF_AUTOSETUP,
CONF_INFO,
DOMAIN,
IHC_CONTROLLER,
IHC_CONTROLLER_INDEX,
)
from .manual_setup import IHC_SCHEMA, get_manual_configuration
from .service_functions import setup_service_functions
_LOGGER = logging.getLogger(__name__)
IHC_INFO = "info"
CONFIG_SCHEMA = vol.Schema(
{DOMAIN: vol.Schema(vol.All(cv.ensure_list, [IHC_SCHEMA]))}, extra=vol.ALLOW_EXTRA
@ -29,7 +34,6 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
for index, controller_conf in enumerate(conf):
if not ihc_setup(hass, config, controller_conf, index):
return False
return True
@ -37,7 +41,7 @@ def ihc_setup(
hass: HomeAssistant,
config: ConfigType,
controller_conf: ConfigType,
controller_id: int,
controller_index: int,
) -> bool:
"""Set up the IHC integration."""
url = controller_conf[CONF_URL]
@ -48,20 +52,20 @@ def ihc_setup(
if not ihc_controller.authenticate():
_LOGGER.error("Unable to authenticate on IHC controller")
return False
controller_id: str = ihc_controller.client.get_system_info()["serial_number"]
# Store controller configuration
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][controller_id] = {
IHC_CONTROLLER: ihc_controller,
CONF_INFO: controller_conf[CONF_INFO],
IHC_CONTROLLER_INDEX: controller_index,
}
if controller_conf[CONF_AUTOSETUP] and not autosetup_ihc_products(
hass, config, ihc_controller, controller_id
):
return False
# Manual configuration
get_manual_configuration(hass, config, controller_conf, controller_id)
# Store controller configuration
ihc_key = f"ihc{controller_id}"
hass.data[ihc_key] = {
IHC_CONTROLLER: ihc_controller,
IHC_INFO: controller_conf[CONF_INFO],
}
# We only want to register the service functions once for the first controller
if controller_id == 0:
if controller_index == 0:
setup_service_functions(hass)
return True

View File

@ -120,19 +120,25 @@ def get_discovery_info(platform_setup, groups, controller_id):
for product_cfg in platform_setup:
products = group.findall(product_cfg[CONF_XPATH])
for product in products:
product_id = int(product.attrib["id"].strip("_"), 0)
nodes = product.findall(product_cfg[CONF_NODE])
for node in nodes:
if "setting" in node.attrib and node.attrib["setting"] == "yes":
continue
ihc_id = int(node.attrib["id"].strip("_"), 0)
name = f"{groupname}_{ihc_id}"
# make the model number look a bit nicer - strip leading _
model = product.get("product_identifier", "").lstrip("_")
device = {
"ihc_id": ihc_id,
"ctrl_id": controller_id,
"product": {
"id": product_id,
"name": product.get("name") or "",
"note": product.get("note") or "",
"position": product.get("position") or "",
"model": model,
"group": groupname,
},
"product_cfg": product_cfg,
}

View File

@ -1,14 +1,15 @@
"""Support for IHC binary sensors."""
from __future__ import annotations
from ihcsdk.ihccontroller import IHCController
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.const import CONF_TYPE
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import IHC_CONTROLLER, IHC_INFO
from .const import CONF_INVERTING
from .const import CONF_INVERTING, DOMAIN, IHC_CONTROLLER
from .ihcdevice import IHCDevice
@ -27,16 +28,13 @@ def setup_platform(
product_cfg = device["product_cfg"]
product = device["product"]
# Find controller that corresponds with device id
ctrl_id = device["ctrl_id"]
ihc_key = f"ihc{ctrl_id}"
info = hass.data[ihc_key][IHC_INFO]
ihc_controller = hass.data[ihc_key][IHC_CONTROLLER]
controller_id = device["ctrl_id"]
ihc_controller: IHCController = hass.data[DOMAIN][controller_id][IHC_CONTROLLER]
sensor = IHCBinarySensor(
ihc_controller,
controller_id,
name,
ihc_id,
info,
product_cfg.get(CONF_TYPE),
product_cfg[CONF_INVERTING],
product,
@ -54,16 +52,16 @@ class IHCBinarySensor(IHCDevice, BinarySensorEntity):
def __init__(
self,
ihc_controller,
name,
ihc_controller: IHCController,
controller_id: str,
name: str,
ihc_id: int,
info: bool,
sensor_type: str,
inverting: bool,
product=None,
) -> None:
"""Initialize the IHC binary sensor."""
super().__init__(ihc_controller, name, ihc_id, info, product)
super().__init__(ihc_controller, controller_id, name, ihc_id, product)
self._state = None
self._sensor_type = sensor_type
self.inverting = inverting

View File

@ -25,6 +25,7 @@ CONF_XPATH = "xpath"
DOMAIN = "ihc"
IHC_CONTROLLER = "controller"
IHC_CONTROLLER_INDEX = "controller_index"
IHC_PLATFORMS = (
Platform.BINARY_SENSOR,
Platform.LIGHT,

View File

@ -1,6 +1,14 @@
"""Implementation of a base class for all IHC devices."""
import logging
from ihcsdk.ihccontroller import IHCController
from homeassistant.helpers.entity import Entity
from .const import CONF_INFO, DOMAIN
_LOGGER = logging.getLogger(__name__)
class IHCDevice(Entity):
"""Base class for all IHC devices.
@ -11,17 +19,33 @@ class IHCDevice(Entity):
"""
def __init__(
self, ihc_controller, name, ihc_id: int, info: bool, product=None
self,
ihc_controller: IHCController,
controller_id: str,
name: str,
ihc_id: int,
product=None,
) -> None:
"""Initialize IHC attributes."""
self.ihc_controller = ihc_controller
self._name = name
self.ihc_id = ihc_id
self.info = info
self.controller_id = controller_id
self.device_id = None
self.suggested_area = None
if product:
self.ihc_name = product["name"]
self.ihc_note = product["note"]
self.ihc_position = product["position"]
self.suggested_area = product["group"] if "group" in product else None
if "id" in product:
product_id = product["id"]
self.device_id = f"{controller_id}_{product_id }"
# this will name the device the same way as the IHC visual application: Product name + position
self.device_name = product["name"]
if self.ihc_position:
self.device_name += f" ({self.ihc_position})"
self.device_model = product["model"]
else:
self.ihc_name = ""
self.ihc_note = ""
@ -29,6 +53,7 @@ class IHCDevice(Entity):
async def async_added_to_hass(self):
"""Add callback for IHC changes."""
_LOGGER.debug("Adding IHC entity notify event: %s", self.ihc_id)
self.ihc_controller.add_notify_event(self.ihc_id, self.on_ihc_change, True)
@property
@ -41,17 +66,26 @@ class IHCDevice(Entity):
"""Return the device name."""
return self._name
@property
def unique_id(self):
"""Return a unique ID."""
return f"{self.controller_id}-{self.ihc_id}"
@property
def extra_state_attributes(self):
"""Return the state attributes."""
if not self.info:
if not self.hass.data[DOMAIN][self.controller_id][CONF_INFO]:
return {}
return {
attributes = {
"ihc_id": self.ihc_id,
"ihc_name": self.ihc_name,
"ihc_note": self.ihc_note,
"ihc_position": self.ihc_position,
}
if len(self.hass.data[DOMAIN]) > 1:
# We only want to show the controller id if we have more than one
attributes["ihc_controller"] = self.controller_id
return attributes
def on_ihc_change(self, ihc_id, value):
"""Handle IHC resource change.

View File

@ -1,6 +1,8 @@
"""Support for IHC lights."""
from __future__ import annotations
from ihcsdk.ihccontroller import IHCController
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
SUPPORT_BRIGHTNESS,
@ -10,8 +12,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import IHC_CONTROLLER, IHC_INFO
from .const import CONF_DIMMABLE, CONF_OFF_ID, CONF_ON_ID
from .const import CONF_DIMMABLE, CONF_OFF_ID, CONF_ON_ID, DOMAIN, IHC_CONTROLLER
from .ihcdevice import IHCDevice
from .util import async_pulse, async_set_bool, async_set_int
@ -31,15 +32,20 @@ def setup_platform(
product_cfg = device["product_cfg"]
product = device["product"]
# Find controller that corresponds with device id
ctrl_id = device["ctrl_id"]
ihc_key = f"ihc{ctrl_id}"
info = hass.data[ihc_key][IHC_INFO]
ihc_controller = hass.data[ihc_key][IHC_CONTROLLER]
controller_id = device["ctrl_id"]
ihc_controller: IHCController = hass.data[DOMAIN][controller_id][IHC_CONTROLLER]
ihc_off_id = product_cfg.get(CONF_OFF_ID)
ihc_on_id = product_cfg.get(CONF_ON_ID)
dimmable = product_cfg[CONF_DIMMABLE]
light = IhcLight(
ihc_controller, name, ihc_id, ihc_off_id, ihc_on_id, info, dimmable, product
ihc_controller,
controller_id,
name,
ihc_id,
ihc_off_id,
ihc_on_id,
dimmable,
product,
)
devices.append(light)
add_entities(devices)
@ -55,17 +61,17 @@ class IhcLight(IHCDevice, LightEntity):
def __init__(
self,
ihc_controller,
name,
ihc_controller: IHCController,
controller_id: str,
name: str,
ihc_id: int,
ihc_off_id: int,
ihc_on_id: int,
info: bool,
dimmable=False,
product=None,
) -> None:
"""Initialize the light."""
super().__init__(ihc_controller, name, ihc_id, info, product)
super().__init__(ihc_controller, controller_id, name, ihc_id, product)
self._ihc_off_id = ihc_off_id
self._ihc_on_id = ihc_on_id
self._brightness = 0

View File

@ -111,7 +111,7 @@ def get_manual_configuration(
hass: HomeAssistant,
config: ConfigType,
controller_conf: ConfigType,
controller_id: int,
controller_id: str,
) -> None:
"""Get manual configuration for IHC devices."""
for platform in IHC_PLATFORMS:

View File

@ -1,6 +1,8 @@
"""Support for IHC sensors."""
from __future__ import annotations
from ihcsdk.ihccontroller import IHCController
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
from homeassistant.const import CONF_UNIT_OF_MEASUREMENT
from homeassistant.core import HomeAssistant
@ -8,7 +10,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util.unit_system import TEMPERATURE_UNITS
from . import IHC_CONTROLLER, IHC_INFO
from .const import DOMAIN, IHC_CONTROLLER
from .ihcdevice import IHCDevice
@ -27,12 +29,10 @@ def setup_platform(
product_cfg = device["product_cfg"]
product = device["product"]
# Find controller that corresponds with device id
ctrl_id = device["ctrl_id"]
ihc_key = f"ihc{ctrl_id}"
info = hass.data[ihc_key][IHC_INFO]
ihc_controller = hass.data[ihc_key][IHC_CONTROLLER]
controller_id = device["ctrl_id"]
ihc_controller: IHCController = hass.data[DOMAIN][controller_id][IHC_CONTROLLER]
unit = product_cfg[CONF_UNIT_OF_MEASUREMENT]
sensor = IHCSensor(ihc_controller, name, ihc_id, info, unit, product)
sensor = IHCSensor(ihc_controller, controller_id, name, ihc_id, unit, product)
devices.append(sensor)
add_entities(devices)
@ -41,10 +41,16 @@ class IHCSensor(IHCDevice, SensorEntity):
"""Implementation of the IHC sensor."""
def __init__(
self, ihc_controller, name, ihc_id: int, info: bool, unit, product=None
self,
ihc_controller: IHCController,
controller_id: str,
name: str,
ihc_id: int,
unit: str,
product=None,
) -> None:
"""Initialize the IHC sensor."""
super().__init__(ihc_controller, name, ihc_id, info, product)
super().__init__(ihc_controller, controller_id, name, ihc_id, product)
self._state = None
self._unit_of_measurement = unit

View File

@ -1,6 +1,4 @@
"""Support for IHC devices."""
import logging
import voluptuous as vol
from homeassistant.core import HomeAssistant
@ -12,6 +10,7 @@ from .const import (
ATTR_VALUE,
DOMAIN,
IHC_CONTROLLER,
IHC_CONTROLLER_INDEX,
SERVICE_PULSE,
SERVICE_SET_RUNTIME_VALUE_BOOL,
SERVICE_SET_RUNTIME_VALUE_FLOAT,
@ -19,9 +18,6 @@ from .const import (
)
from .util import async_pulse, async_set_bool, async_set_float, async_set_int
_LOGGER = logging.getLogger(__name__)
SET_RUNTIME_VALUE_BOOL_SCHEMA = vol.Schema(
{
vol.Required(ATTR_IHC_ID): cv.positive_int,
@ -54,13 +50,17 @@ PULSE_SCHEMA = vol.Schema(
)
def setup_service_functions(hass: HomeAssistant):
def setup_service_functions(hass: HomeAssistant) -> None:
"""Set up the IHC service functions."""
def _get_controller(call):
controller_id = call.data[ATTR_CONTROLLER_ID]
ihc_key = f"ihc{controller_id}"
return hass.data[ihc_key][IHC_CONTROLLER]
controller_index = call.data[ATTR_CONTROLLER_ID]
for controller_id in hass.data[DOMAIN]:
controller_conf = hass.data[DOMAIN][controller_id]
if controller_conf[IHC_CONTROLLER_INDEX] == controller_index:
return controller_conf[IHC_CONTROLLER]
# if not found the controller_index is ouf of range
raise ValueError("The controller index is out of range")
async def async_set_runtime_value_bool(call):
"""Set a IHC runtime bool value service function."""

View File

@ -1,13 +1,14 @@
"""Support for IHC switches."""
from __future__ import annotations
from ihcsdk.ihccontroller import IHCController
from homeassistant.components.switch import SwitchEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import IHC_CONTROLLER, IHC_INFO
from .const import CONF_OFF_ID, CONF_ON_ID
from .const import CONF_OFF_ID, CONF_ON_ID, DOMAIN, IHC_CONTROLLER
from .ihcdevice import IHCDevice
from .util import async_pulse, async_set_bool
@ -27,15 +28,13 @@ def setup_platform(
product_cfg = device["product_cfg"]
product = device["product"]
# Find controller that corresponds with device id
ctrl_id = device["ctrl_id"]
ihc_key = f"ihc{ctrl_id}"
info = hass.data[ihc_key][IHC_INFO]
ihc_controller = hass.data[ihc_key][IHC_CONTROLLER]
controller_id = device["ctrl_id"]
ihc_controller: IHCController = hass.data[DOMAIN][controller_id][IHC_CONTROLLER]
ihc_off_id = product_cfg.get(CONF_OFF_ID)
ihc_on_id = product_cfg.get(CONF_ON_ID)
switch = IHCSwitch(
ihc_controller, name, ihc_id, ihc_off_id, ihc_on_id, info, product
ihc_controller, controller_id, name, ihc_id, ihc_off_id, ihc_on_id, product
)
devices.append(switch)
add_entities(devices)
@ -46,16 +45,16 @@ class IHCSwitch(IHCDevice, SwitchEntity):
def __init__(
self,
ihc_controller,
ihc_controller: IHCController,
controller_id: str,
name: str,
ihc_id: int,
ihc_off_id: int,
ihc_on_id: int,
info: bool,
product=None,
) -> None:
"""Initialize the IHC switch."""
super().__init__(ihc_controller, name, ihc_id, product)
super().__init__(ihc_controller, controller_id, name, ihc_id, product)
self._ihc_off_id = ihc_off_id
self._ihc_on_id = ihc_on_id
self._state = False