"""Support for Velbus devices.""" import asyncio import logging import velbus import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_PORT, CONF_NAME from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType from .const import DOMAIN _LOGGER = logging.getLogger(__name__) VELBUS_MESSAGE = "velbus.message" CONFIG_SCHEMA = vol.Schema( {DOMAIN: vol.Schema({vol.Required(CONF_PORT): cv.string})}, extra=vol.ALLOW_EXTRA ) COMPONENT_TYPES = ["switch", "sensor", "binary_sensor", "cover", "climate"] async def async_setup(hass, config): """Set up the Velbus platform.""" # Import from the configuration file if needed if DOMAIN not in config: return True port = config[DOMAIN].get(CONF_PORT) data = {} if port: data = {CONF_PORT: port, CONF_NAME: "Velbus import"} hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=data ) ) return True async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): """Establish connection with velbus.""" hass.data.setdefault(DOMAIN, {}) def callback(): modules = controller.get_modules() discovery_info = {"cntrl": controller} for category in COMPONENT_TYPES: discovery_info[category] = [] for module in modules: for channel in range(1, module.number_of_channels() + 1): for category in COMPONENT_TYPES: if category in module.get_categories(channel): discovery_info[category].append( (module.get_module_address(), channel) ) hass.data[DOMAIN][entry.entry_id] = discovery_info for category in COMPONENT_TYPES: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, category) ) try: controller = velbus.Controller(entry.data[CONF_PORT]) controller.scan(callback) except velbus.util.VelbusException as err: _LOGGER.error("An error occurred: %s", err) raise ConfigEntryNotReady def syn_clock(self, service=None): try: controller.sync_clock() except velbus.util.VelbusException as err: _LOGGER.error("An error occurred: %s", err) hass.services.async_register(DOMAIN, "sync_clock", syn_clock, schema=vol.Schema({})) return True async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): """Remove the velbus connection.""" await asyncio.wait( [ hass.config_entries.async_forward_entry_unload(entry, component) for component in COMPONENT_TYPES ] ) hass.data[DOMAIN][entry.entry_id]["cntrl"].stop() hass.data[DOMAIN].pop(entry.entry_id) if not hass.data[DOMAIN]: hass.data.pop(DOMAIN) return True class VelbusEntity(Entity): """Representation of a Velbus entity.""" def __init__(self, module, channel): """Initialize a Velbus entity.""" self._module = module self._channel = channel @property def unique_id(self): """Get unique ID.""" serial = 0 if self._module.serial == 0: serial = self._module.get_module_address() else: serial = self._module.serial return f"{serial}-{self._channel}" @property def name(self): """Return the display name of this entity.""" return self._module.get_name(self._channel) @property def should_poll(self): """Disable polling.""" return False async def async_added_to_hass(self): """Add listener for state changes.""" self._module.on_status_update(self._channel, self._on_update) def _on_update(self, state): self.schedule_update_ha_state() @property def device_info(self): """Return the device info.""" return { "identifiers": { (DOMAIN, self._module.get_module_address(), self._module.serial) }, "name": "{} {}".format( self._module.get_module_address(), self._module.get_module_name() ), "manufacturer": "Velleman", "model": self._module.get_module_name(), "sw_version": "{}.{}-{}".format( self._module.memory_map_version, self._module.build_year, self._module.build_week, ), }