diff --git a/README.md b/README.md index 2e1433b..38ab09f 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,28 @@ _Virtual_ is a component that provides virtual entities for _Home Assistant_. ![icon](images/virtual-icon.png) + +# !!!BREAKING CHANGES!!! + +Version 0.9 supports adding virtual devices using _config flow_. By default it +will move your existing devices into a single file `virtual.yaml`. If you +**DO NOT** want this behaviour add this to your `virtual` configuration. + +```yaml +virtual: + yaml_config: True +``` + + # Table Of Contents * [**Virtual devices for Home Assistant**](#virtual-devices-for-home-assistant) +* [!!!BREAKING CHANGES!!!](#breaking-changes) * [Table Of Contents](#table-of-contents) * [Introduction](#introduction) * [Notes](#notes) - * [Version 0.8?](#version-08) + * [Version 0.8 Documentation](#version-08-documentation) * [New Features in 0.9.0](#new-features-in-090) * [Config Flow](#config-flow) * [What pieces are done](#what-pieces-are-done) @@ -22,9 +36,10 @@ _Virtual_ is a component that provides virtual entities for _Home Assistant_. * [Installation](#installation) * [HACS](#hacs) * [Component Configuration](#component-configuration) - * [Availability](#availability) - * [Persistence](#persistence) -* [Platforms](#platforms) +* [Entity Configuration](#entity-configuration) + * [Common Attributes](#common-attributes) + * [Availability](#availability) + * [Persistence](#persistence) * [Switches](#switches) * [Binary Sensors](#binary-sensors) * [Sensors](#sensors) @@ -34,9 +49,11 @@ _Virtual_ is a component that provides virtual entities for _Home Assistant_. * [Covers](#covers) * [Valves](#valves) * [Device Tracking](#device-tracking) +* [Old Style Entity Configuration](#old-style-entity-configuration) * [Services](#services) + # Introduction Virtual provides virtual components for testing Home Assistant systems. @@ -46,7 +63,7 @@ Wherever you see `/config` in this README it refers to your home-assistant configuration directory. For me, for example, it's `/home/steve/ha` that is mapped to `/config` inside my docker container. -## Version 0.8? +## Version 0.8 Documentation **This documentation is for the 0.9.x version, you can find the 0.8.x version** [here](https://github.com/twrecked/hass-virtual/tree/version-0.8.x#readme). @@ -141,6 +158,19 @@ development branches this is the easiest way to install. # Component Configuration +- `yaml_config`; set to `True` to enable backwards compatability, set to `False` + to disable it. The default is `False`. + +For example, this enable backwards compatability. + +```yaml +virtual: + yaml_config: True +``` + + +# Entity Configuration + All component configuration is done through a _yaml_ file. There is a single file per integration instance. The default file, created on upgrade, is `/config/virtual.yaml`. An empty file looks like this: @@ -227,7 +257,9 @@ Living Room Multi Sensor: class: temperature ``` -## Availability +## Common Attributes + +### Availability By default, all devices are market as available. As shown below in each domain, adding `initial_availability: false` to configuration can override default and @@ -236,8 +268,7 @@ the `virtual.set_available` with value `true` or `false`. This is fully optional and `initial_availability` is not required to be set. - -## Persistence +### Persistence By default, all device states are persistent. You can change this behaviour with the `persistent` configuration option. @@ -252,8 +283,6 @@ Test Switch: initial_value: on ``` -# Platforms - ## Switches To add a virtual switch use the following: @@ -263,7 +292,6 @@ Test Switch: - platform: switch ``` - ## Binary Sensors To add a virtual binary_sensor use the following. It supports all standard classes. @@ -278,7 +306,6 @@ Test Binary Sensor: Use the `virtual.turn_on`, `virtual.turn_off` and `virtual.toggle` services to manipulate the binary sensors. - ## Sensors To add a virtual sensor use the following: @@ -323,7 +350,6 @@ Only `name` is required. _Note; *white_value is deprecated and will be removed in future releases._ - ## Locks To add a virtual lock use the following: @@ -346,7 +372,6 @@ Test Lock: - `jamming_test`: optional, default `0` tries; any positive value will result in a jamming failure approximately once per `jamming_test` tries - ## Fans To add a virtual fan use the following: @@ -367,7 +392,6 @@ You only need one of `speed` or `speed_count`. - `direction`; if `True` then fan can run in 2 directions - `oscillate`; if `True` then fan can be set to oscillate - ## Covers To add a virtual cover use the following: @@ -385,7 +409,6 @@ the cover is emulated with timed events, and the timing can be controlled with - `open_close_duration`: The time it take to go from fully open to fully closed, or back - `open_close_tick`: The update interval when opening and closing - ## Valves To add a virtual valve use the following: @@ -403,7 +426,6 @@ the valve is emulated with timed events, and the timing can be controlled with - `open_close_duration`: The time it take to go from fully open to fully closed, or back - `open_close_tick`: The update interval when opening and closing - ## Device Tracking To add a virtual device tracker use the following: @@ -421,6 +443,12 @@ Test Device_Tracker: Use the `virtual.move` service to change device locations. + +# Old Style Entity Configuration + +For now; look at [the 0.8](https://github.com/twrecked/hass-virtual/tree/version-0.8.x?tab=readme-ov-file#component-configuration) documentation. + + # Services The component provides the following services: @@ -469,3 +497,4 @@ This service will turn off a binary sensor. - `gps`; GPS coordinates Move a device tracker. You use one of the parameters. + diff --git a/changelog b/changelog index b947eea..ef7ea0f 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,5 @@ +0.9.0b14: + Restore previous YAML config support. 0.9.0b13: Refactored cover to provide valve support. Made `version and device` optional in the yaml config diff --git a/custom_components/virtual/__init__.py b/custom_components/virtual/__init__.py index aa6b8fc..cbc894a 100644 --- a/custom_components/virtual/__init__.py +++ b/custom_components/virtual/__init__.py @@ -30,12 +30,17 @@ from .cfg import BlendedCfg, UpgradeCfg -__version__ = '0.9.0b13' +__version__ = '0.9.0b14' _LOGGER = logging.getLogger(__name__) -# Purely to quieten down the checks. -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({}) +CONFIG_SCHEMA = vol.Schema({ + COMPONENT_DOMAIN: vol.Schema({ + vol.Optional(CONF_YAML_CONFIG, default=False): cv.boolean, + }), + }, + extra=vol.ALLOW_EXTRA, +) SERVICE_AVAILABILE = 'set_available' SERVICE_SCHEMA = vol.Schema({ @@ -55,43 +60,68 @@ Platform.VALVE ] +async def async_setup(hass, config): + """Set up a virtual components. -async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Set up a virtual component. + This uses the old mechanism and has to be enabled with 'yaml_config: True' """ - hass.data[COMPONENT_DOMAIN] = {} - hass.data[COMPONENT_SERVICES] = {} - - # See if we have already imported the data. If we haven't then do it now. - config_entry = _async_find_matching_config_entry(hass) - if not config_entry: - _LOGGER.debug('importing a YAML setup') - hass.async_create_task( - hass.config_entries.flow.async_init( - COMPONENT_DOMAIN, - context={CONF_SOURCE: SOURCE_IMPORT}, - data=config + # Set up hass data if necessary + if COMPONENT_DOMAIN not in hass.data: + hass.data[COMPONENT_DOMAIN] = {} + hass.data[COMPONENT_SERVICES] = {} + hass.data[COMPONENT_CONFIG] = {} + + # See if yaml support was enabled. + if not config.get(COMPONENT_DOMAIN, {}).get(CONF_YAML_CONFIG, False): + + # New style. We import old config if needed. + _LOGGER.debug("setting up new virtual components") + hass.data[COMPONENT_CONFIG][CONF_YAML_CONFIG] = False + + # See if we have already imported the data. If we haven't then do it now. + config_entry = _async_find_matching_config_entry(hass) + if not config_entry: + _LOGGER.debug('importing a YAML setup') + hass.async_create_task( + hass.config_entries.flow.async_init( + COMPONENT_DOMAIN, + context={CONF_SOURCE: SOURCE_IMPORT}, + data=config + ) + ) + + async_create_issue( + hass, + HOMEASSISTANT_DOMAIN, + f"deprecated_yaml_{COMPONENT_DOMAIN}", + is_fixable=False, + issue_domain=COMPONENT_DOMAIN, + severity=IssueSeverity.WARNING, + translation_key="deprecated_yaml", + translation_placeholders={ + "domain": COMPONENT_DOMAIN, + "integration_title": "Virtual", + }, ) - ) - - async_create_issue( - hass, - HOMEASSISTANT_DOMAIN, - f"deprecated_yaml_{COMPONENT_DOMAIN}", - is_fixable=False, - issue_domain=COMPONENT_DOMAIN, - severity=IssueSeverity.WARNING, - translation_key="deprecated_yaml", - translation_placeholders={ - "domain": COMPONENT_DOMAIN, - "integration_title": "Virtual", - }, - ) - - return True - - _LOGGER.debug('ignoring a YAML setup') + + else: + _LOGGER.debug('ignoring a YAML setup') + + else: + + # Original style. We just use the entities as now. + _LOGGER.debug("setting up old virtual components") + hass.data[COMPONENT_CONFIG][CONF_YAML_CONFIG] = True + + @verify_domain_control(hass, COMPONENT_DOMAIN) + async def async_virtual_service_set_available(call) -> None: + """Call virtual service handler.""" + _LOGGER.info("{} service called".format(call.service)) + await async_virtual_set_availability_service(hass, call) + + hass.services.async_register(COMPONENT_DOMAIN, SERVICE_AVAILABILE, async_virtual_service_set_available) + return True @@ -111,6 +141,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if COMPONENT_DOMAIN not in hass.data: hass.data[COMPONENT_DOMAIN] = {} hass.data[COMPONENT_SERVICES] = {} + hass.data[COMPONENT_CONFIG] = {} # Get the config. _LOGGER.debug(f"creating new cfg") diff --git a/custom_components/virtual/binary_sensor.py b/custom_components/virtual/binary_sensor.py index 8efc585..5d47971 100644 --- a/custom_components/virtual/binary_sensor.py +++ b/custom_components/virtual/binary_sensor.py @@ -43,18 +43,7 @@ }) -async def async_setup_entry( - hass: HomeAssistant, - entry: ConfigEntry, - async_add_entities: Callable[[list], None], -) -> None: - _LOGGER.debug("setting up the entries...") - - entities = [] - for entity in get_entity_configs(hass, entry.data[ATTR_GROUP_NAME], PLATFORM_DOMAIN): - entity = BINARY_SENSOR_SCHEMA(entity) - entities.append(VirtualBinarySensor(entity)) - async_add_entities(entities) +def setup_services(hass: HomeAssistant) -> None: async def async_virtual_service(call): """Call virtual service handler.""" @@ -68,7 +57,7 @@ async def async_virtual_service(call): # Build up services... if not hasattr(hass.data[COMPONENT_SERVICES], PLATFORM_DOMAIN): - _LOGGER.debug("installing handlers") + _LOGGER.debug("installing binary_service handlers") hass.data[COMPONENT_SERVICES][PLATFORM_DOMAIN] = 'installed' hass.services.async_register( COMPONENT_DOMAIN, SERVICE_ON, async_virtual_service, schema=SERVICE_SCHEMA, @@ -81,12 +70,36 @@ async def async_virtual_service(call): ) +async def async_setup_platform(hass, config, async_add_entities, _discovery_info=None): + if hass.data[COMPONENT_CONFIG].get(CONF_YAML_CONFIG, False): + _LOGGER.debug("setting up old config...") + + sensors = [VirtualBinarySensor(config, True)] + async_add_entities(sensors, True) + setup_services(hass) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: Callable[[list], None], +) -> None: + _LOGGER.debug("setting up the entries...") + + entities = [] + for entity in get_entity_configs(hass, entry.data[ATTR_GROUP_NAME], PLATFORM_DOMAIN): + entity = BINARY_SENSOR_SCHEMA(entity) + entities.append(VirtualBinarySensor(entity, False)) + async_add_entities(entities) + setup_services(hass) + + class VirtualBinarySensor(VirtualEntity, BinarySensorEntity): """An implementation of a Virtual Binary Sensor.""" - def __init__(self, config): + def __init__(self, config, old_style: bool): """Initialize a Virtual Binary Sensor.""" - super().__init__(config, PLATFORM_DOMAIN) + super().__init__(config, PLATFORM_DOMAIN, old_style) self._attr_device_class = config.get(CONF_CLASS) @@ -103,7 +116,7 @@ def _restore_state(self, state, config): self._attr_is_on = state.state.lower() == STATE_ON def _update_attributes(self): - super()._update_attributes(); + super()._update_attributes() self._attr_extra_state_attributes.update({ name: value for name, value in ( (ATTR_DEVICE_CLASS, self._attr_device_class), diff --git a/custom_components/virtual/const.py b/custom_components/virtual/const.py index e2a338c..4dc9ee3 100644 --- a/custom_components/virtual/const.py +++ b/custom_components/virtual/const.py @@ -2,6 +2,7 @@ COMPONENT_DOMAIN = "virtual" COMPONENT_SERVICES = "virtual-services" +COMPONENT_CONFIG = "virtual-config" COMPONENT_MANUFACTURER = "twrecked" COMPONENT_MODEL = "virtual" @@ -24,6 +25,7 @@ CONF_OPEN_CLOSE_DURATION = "open_close_duration" CONF_OPEN_CLOSE_TICK = "open_close_tick" CONF_PERSISTENT = "persistent" +CONF_YAML_CONFIG = "yaml_config" DEFAULT_AVAILABILITY = True DEFAULT_PERSISTENT = True diff --git a/custom_components/virtual/cover.py b/custom_components/virtual/cover.py index 9dc3836..e0e62db 100644 --- a/custom_components/virtual/cover.py +++ b/custom_components/virtual/cover.py @@ -44,6 +44,14 @@ })) +async def async_setup_platform(hass, config, async_add_entities, _discovery_info=None): + if hass.data[COMPONENT_CONFIG].get(CONF_YAML_CONFIG, False): + _LOGGER.debug("setting up old config...") + + sensors = [VirtualCover(config, True)] + async_add_entities(sensors, True) + + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, @@ -54,16 +62,16 @@ async def async_setup_entry( entities = [] for entity in get_entity_configs(hass, entry.data[ATTR_GROUP_NAME], PLATFORM_DOMAIN): entity = COVER_SCHEMA(entity) - entities.append(VirtualCover(entity)) + entities.append(VirtualCover(entity, False)) async_add_entities(entities) class VirtualCover(VirtualOpenableEntity, CoverEntity): """Representation of a Virtual cover.""" - def __init__(self, config): + def __init__(self, config, old_style : bool): """Initialize the Virtual cover device.""" - super().__init__(config, PLATFORM_DOMAIN) + super().__init__(config, PLATFORM_DOMAIN, old_style) self._attr_supported_features = CoverEntityFeature( CoverEntityFeature.OPEN | diff --git a/custom_components/virtual/device_tracker.py b/custom_components/virtual/device_tracker.py index 711e21a..e024285 100644 --- a/custom_components/virtual/device_tracker.py +++ b/custom_components/virtual/device_tracker.py @@ -3,8 +3,10 @@ """ +import aiofiles import logging import voluptuous as vol +import json from collections.abc import Callable import homeassistant.helpers.config_validation as cv @@ -18,10 +20,12 @@ from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_LATITUDE, - ATTR_LONGITUDE + ATTR_LONGITUDE, + CONF_DEVICES ) from homeassistant.core import HomeAssistant from homeassistant.helpers.config_validation import PLATFORM_SCHEMA +from homeassistant.helpers.event import async_track_state_change_event from . import get_entity_from_domain, get_entity_configs from .const import * @@ -35,9 +39,14 @@ CONF_LOCATION = 'location' CONF_GPS = 'gps' DEFAULT_DEVICE_TRACKER_VALUE = 'home' +DEFAULT_LOCATION = 'home' + +STATE_FILE = "/config/.storage/virtual.restore_state" + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_DEVICES, default=[]): cv.ensure_list +}) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(virtual_schema(DEFAULT_DEVICE_TRACKER_VALUE, { -})) DEVICE_TRACKER_SCHEMA = vol.Schema(virtual_schema(DEFAULT_DEVICE_TRACKER_VALUE, { })) @@ -52,6 +61,91 @@ }, }) +tracker_states = {} + +async def _async_load_json(file_name): + try: + async with aiofiles.open(file_name, 'r') as state_file: + contents = await state_file.read() + return json.loads(contents) + except Exception as e: + return {} + + +def _write_state(): + global tracker_states + try: + with open(STATE_FILE, 'w') as f: + json.dump(tracker_states, f) + except: + pass + +def _state_changed(event): + entity_id = event.data.get('entity_id', None) + new_state = event.data.get('new_state', None) + if entity_id is None or new_state is None: + _LOGGER.info(f'state changed error') + return + + # update database + _LOGGER.info(f"moving {entity_id} to {new_state.state}") + global tracker_states + tracker_states[entity_id] = new_state.state + _write_state() + + +def _shutting_down(event): + _LOGGER.info(f'shutting down {event}') + _write_state() + + +async def async_setup_scanner(hass, config, async_see, _discovery_info=None): + """Set up the virtual tracker.""" + + if not hass.data[COMPONENT_CONFIG].get(CONF_YAML_CONFIG, False): + return True + _LOGGER.debug("setting up old device trackers...") + + # Read in the last known states. + old_tracker_states = await _async_load_json(STATE_FILE) + + new_tracker_states = {} + for device in config[CONF_DEVICES]: + if not isinstance(device, dict): + device = { + CONF_NAME: device, + } + + name = device.get(CONF_NAME, 'unknown') + location = device.get(CONF_LOCATION, DEFAULT_LOCATION) + peristent = device.get(CONF_PERSISTENT, DEFAULT_PERSISTENT) + entity_id = f"{PLATFORM_DOMAIN}.{name}" + + if peristent: + location = old_tracker_states.get(entity_id, location) + new_tracker_states[entity_id] = location + _LOGGER.info(f"setting persistent {entity_id} to {location}") + else: + _LOGGER.info(f"setting ephemeral {entity_id} to {location}") + + see_args = { + "dev_id": name, + "source_type": COMPONENT_DOMAIN, + "location_name": location, + } + hass.async_create_task(async_see(**see_args)) + + # Start listening if there are persistent entities. + global tracker_states + tracker_states = new_tracker_states + if tracker_states: + async_track_state_change_event(hass, tracker_states.keys(), _state_changed) + hass.bus.async_listen("homeassistant_stop", _shutting_down) + else: + _write_state() + + return True + async def async_setup_entry( hass: HomeAssistant, diff --git a/custom_components/virtual/entity.py b/custom_components/virtual/entity.py index dc8f3b4..770d539 100644 --- a/custom_components/virtual/entity.py +++ b/custom_components/virtual/entity.py @@ -49,36 +49,49 @@ class VirtualEntity(RestoreEntity): # Are we saving/restoring this entity _persistent: bool = True - def __init__(self, config, domain): + def __init__(self, config, domain, old_style : bool = False): """Initialize an Virtual Sensor.""" _LOGGER.debug(f"creating-virtual-{domain}={config}") self._config = config self._persistent = config.get(CONF_PERSISTENT) - # Build name, entity id and unique id. We do this because historically - # the non-domain piece of the entity_id was prefixed with virtual_ so - # we build the pieces manually to make sure. - self._attr_name = config.get(CONF_NAME) - - self.entity_id = config.get(ATTR_ENTITY_ID) - if self.entity_id == "NOTYET": - if self._attr_name.startswith("+"): + if old_style: + # Build name, entity id and unique id. We do this because historically + # the non-domain piece of the entity_id was prefixed with virtual_ so + # we build the pieces manually to make sure. + self._attr_name = config.get(CONF_NAME) + if self._attr_name.startswith("!"): self._attr_name = self._attr_name[1:] - self.entity_id = f'{domain}.{COMPONENT_DOMAIN}_{slugify(self._attr_name)}' - else: self.entity_id = f'{domain}.{slugify(self._attr_name)}' - - self._attr_unique_id = config.get(ATTR_UNIQUE_ID, None) - if self._attr_unique_id == "NOTYET": + else: + self.entity_id = f'{domain}.{COMPONENT_DOMAIN}_{slugify(self._attr_name)}' self._attr_unique_id = slugify(self._attr_name) - if config.get(ATTR_DEVICE_ID) != "NOTYET": - _LOGGER.debug("setting up device info") - self._attr_device_info = DeviceInfo( - identifiers={(COMPONENT_DOMAIN, config.get(ATTR_DEVICE_ID))}, - manufacturer=COMPONENT_MANUFACTURER, - model=COMPONENT_MODEL, - ) + else: + # Build name, entity id and unique id. We do this because historically + # the non-domain piece of the entity_id was prefixed with virtual_ so + # we build the pieces manually to make sure. + self._attr_name = config.get(CONF_NAME) + + self.entity_id = config.get(ATTR_ENTITY_ID) + if self.entity_id == "NOTYET": + if self._attr_name.startswith("+"): + self._attr_name = self._attr_name[1:] + self.entity_id = f'{domain}.{COMPONENT_DOMAIN}_{slugify(self._attr_name)}' + else: + self.entity_id = f'{domain}.{slugify(self._attr_name)}' + + self._attr_unique_id = config.get(ATTR_UNIQUE_ID, None) + if self._attr_unique_id == "NOTYET": + self._attr_unique_id = slugify(self._attr_name) + + if config.get(ATTR_DEVICE_ID) != "NOTYET": + _LOGGER.debug("setting up device info") + self._attr_device_info = DeviceInfo( + identifiers={(COMPONENT_DOMAIN, config.get(ATTR_DEVICE_ID))}, + manufacturer=COMPONENT_MANUFACTURER, + model=COMPONENT_MODEL, + ) _LOGGER.info(f'VirtualEntity {self._attr_name} created') @@ -129,10 +142,10 @@ class VirtualOpenableEntity(VirtualEntity): future we will need to rethink this. """ - def __init__(self, config, domain): + def __init__(self, config, domain, old_style: bool): """Initialize the Virtual openable device.""" _LOGGER.debug(f"creating-virtual-openable-{domain}={config}") - super().__init__(config, domain) + super().__init__(config, domain, old_style) self._attr_device_class = config.get(CONF_CLASS) self._open_close_duration = config.get(CONF_OPEN_CLOSE_DURATION) diff --git a/custom_components/virtual/fan.py b/custom_components/virtual/fan.py index e60cf84..cbb9647 100644 --- a/custom_components/virtual/fan.py +++ b/custom_components/virtual/fan.py @@ -23,6 +23,8 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.config_validation import (PLATFORM_SCHEMA) +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from . import get_entity_configs from .const import * @@ -55,6 +57,19 @@ FAN_SCHEMA = vol.Schema(BASE_SCHEMA) +async def async_setup_platform( + hass: HomeAssistant, + config: ConfigType, + async_add_entities: AddEntitiesCallback, + _discovery_info: DiscoveryInfoType | None = None, +) -> None: + if hass.data[COMPONENT_CONFIG].get(CONF_YAML_CONFIG, False): + _LOGGER.debug("setting up old config...") + + fans = [VirtualFan(config, True)] + async_add_entities(fans, True) + + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, @@ -65,16 +80,16 @@ async def async_setup_entry( entities = [] for entity in get_entity_configs(hass, entry.data[ATTR_GROUP_NAME], PLATFORM_DOMAIN): entity = FAN_SCHEMA(entity) - entities.append(VirtualFan(entity)) + entities.append(VirtualFan(entity, False)) async_add_entities(entities) class VirtualFan(VirtualEntity, FanEntity): """A demonstration fan component.""" - def __init__(self, config): + def __init__(self, config, old_style: bool): """Initialize the entity.""" - super().__init__(config, PLATFORM_DOMAIN) + super().__init__(config, PLATFORM_DOMAIN, old_style) # Modes if supported self._attr_preset_modes = config.get(CONF_MODES) diff --git a/custom_components/virtual/light.py b/custom_components/virtual/light.py index 8c04b46..39489a6 100644 --- a/custom_components/virtual/light.py +++ b/custom_components/virtual/light.py @@ -27,6 +27,8 @@ from homeassistant.const import STATE_ON from homeassistant.core import HomeAssistant from homeassistant.helpers.config_validation import PLATFORM_SCHEMA +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from . import get_entity_configs from .const import * @@ -81,6 +83,19 @@ LIGHT_SCHEMA = vol.Schema(BASE_SCHEMA) +async def async_setup_platform( + hass: HomeAssistant, + config: ConfigType, + async_add_entities: AddEntitiesCallback, + _discovery_info: DiscoveryInfoType | None = None, +) -> None: + if hass.data[COMPONENT_CONFIG].get(CONF_YAML_CONFIG, False): + _LOGGER.debug("setting up old config...") + + lights = [VirtualLight(config, True)] + async_add_entities(lights, True) + + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, @@ -91,15 +106,15 @@ async def async_setup_entry( entities = [] for entity in get_entity_configs(hass, entry.data[ATTR_GROUP_NAME], PLATFORM_DOMAIN): entity = LIGHT_SCHEMA(entity) - entities.append(VirtualLight(entity)) + entities.append(VirtualLight(entity, False)) async_add_entities(entities) class VirtualLight(VirtualEntity, LightEntity): - def __init__(self, config): + def __init__(self, config, old_style: bool): """Initialize a Virtual light.""" - super().__init__(config, PLATFORM_DOMAIN) + super().__init__(config, PLATFORM_DOMAIN, old_style) self._attr_supported_features = LightEntityFeature(0) self._attr_supported_color_modes = set() diff --git a/custom_components/virtual/lock.py b/custom_components/virtual/lock.py index c07b54a..6307ffa 100644 --- a/custom_components/virtual/lock.py +++ b/custom_components/virtual/lock.py @@ -20,7 +20,9 @@ from homeassistant.const import STATE_LOCKED from homeassistant.core import HomeAssistant from homeassistant.helpers.config_validation import PLATFORM_SCHEMA +from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import track_point_in_time +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from . import get_entity_configs from .const import * @@ -48,6 +50,19 @@ })) +async def async_setup_platform( + hass: HomeAssistant, + config: ConfigType, + async_add_entities: AddEntitiesCallback, + _discovery_info: DiscoveryInfoType | None = None, +) -> None: + if hass.data[COMPONENT_CONFIG].get(CONF_YAML_CONFIG, False): + _LOGGER.debug("setting up old config...") + + locks = [VirtualLock(hass, config, True)] + async_add_entities(locks, True) + + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, @@ -58,16 +73,16 @@ async def async_setup_entry( entities = [] for entity in get_entity_configs(hass, entry.data[ATTR_GROUP_NAME], PLATFORM_DOMAIN): entity = LOCK_SCHEMA(entity) - entities.append(VirtualLock(hass, entity)) + entities.append(VirtualLock(hass, entity, False)) async_add_entities(entities) class VirtualLock(VirtualEntity, LockEntity): """Representation of a Virtual lock.""" - def __init__(self, hass, config): + def __init__(self, hass, config, old_style: bool): """Initialize the Virtual lock device.""" - super().__init__(config, PLATFORM_DOMAIN) + super().__init__(config, PLATFORM_DOMAIN, old_style) self._hass = hass self._change_time = config.get(CONF_CHANGE_TIME) diff --git a/custom_components/virtual/manifest.json b/custom_components/virtual/manifest.json index df13d0c..4cbb19a 100644 --- a/custom_components/virtual/manifest.json +++ b/custom_components/virtual/manifest.json @@ -8,5 +8,5 @@ "documentation": "https://github.com/twrecked/hass-virtual/blob/master/README.md", "iot_class": "local_push", "issue_tracker": "https://github.com/twrecked/hass-virtual/issues", - "version": "0.9.0b13" + "version": "0.9.0b14" } diff --git a/custom_components/virtual/sensor.py b/custom_components/virtual/sensor.py index 56c4c29..38f824b 100644 --- a/custom_components/virtual/sensor.py +++ b/custom_components/virtual/sensor.py @@ -13,7 +13,6 @@ SensorDeviceClass ) from homeassistant.config_entries import ConfigEntry -from homeassistant.helpers.config_validation import PLATFORM_SCHEMA from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_DEVICE_CLASS, @@ -35,7 +34,10 @@ UnitOfVolume, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers.config_validation import PLATFORM_SCHEMA from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from . import get_entity_from_domain, get_entity_configs from .const import * @@ -93,18 +95,7 @@ } -async def async_setup_entry( - hass: HomeAssistant, - entry: ConfigEntry, - async_add_entities: Callable[[list], None], -) -> None: - _LOGGER.debug("setting up the entries...") - - entities = [] - for entity in get_entity_configs(hass, entry.data[ATTR_GROUP_NAME], PLATFORM_DOMAIN): - entity = SENSOR_SCHEMA(entity) - entities.append(VirtualSensor(entity)) - async_add_entities(entities) +def setup_services(hass: HomeAssistant) -> None: async def async_virtual_service(call): """Call virtual service handler.""" @@ -120,12 +111,41 @@ async def async_virtual_service(call): ) +async def async_setup_platform( + hass: HomeAssistant, + config: ConfigType, + async_add_entities: AddEntitiesCallback, + _discovery_info: DiscoveryInfoType | None = None, +) -> None: + if hass.data[COMPONENT_CONFIG].get(CONF_YAML_CONFIG, False): + _LOGGER.debug("setting up old config...") + + sensors = [VirtualSensor(config, True)] + async_add_entities(sensors, True) + setup_services(hass) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: Callable[[list], None], +) -> None: + _LOGGER.debug("setting up the entries...") + + entities = [] + for entity in get_entity_configs(hass, entry.data[ATTR_GROUP_NAME], PLATFORM_DOMAIN): + entity = SENSOR_SCHEMA(entity) + entities.append(VirtualSensor(entity, False)) + async_add_entities(entities) + setup_services(hass) + + class VirtualSensor(VirtualEntity, Entity): """An implementation of a Virtual Sensor.""" - def __init__(self, config): + def __init__(self, config, old_style: bool): """Initialize an Virtual Sensor.""" - super().__init__(config, PLATFORM_DOMAIN) + super().__init__(config, PLATFORM_DOMAIN, old_style) self._attr_device_class = config.get(CONF_CLASS) diff --git a/custom_components/virtual/switch.py b/custom_components/virtual/switch.py index a323a25..5c0ac66 100644 --- a/custom_components/virtual/switch.py +++ b/custom_components/virtual/switch.py @@ -14,12 +14,14 @@ SwitchEntity, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.helpers.config_validation import (PLATFORM_SCHEMA) from homeassistant.const import ( ATTR_DEVICE_CLASS, STATE_ON, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers.config_validation import (PLATFORM_SCHEMA) +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from . import get_entity_configs from .const import * @@ -40,6 +42,19 @@ })) +async def async_setup_platform( + hass: HomeAssistant, + config: ConfigType, + async_add_entities: AddEntitiesCallback, + _discovery_info: DiscoveryInfoType | None = None, +) -> None: + if hass.data[COMPONENT_CONFIG].get(CONF_YAML_CONFIG, False): + _LOGGER.debug("setting up old config...") + + switches = [VirtualSwitch(config, True)] + async_add_entities(switches, True) + + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, @@ -50,16 +65,16 @@ async def async_setup_entry( entities = [] for entity in get_entity_configs(hass, entry.data[ATTR_GROUP_NAME], PLATFORM_DOMAIN): entity = SWITCH_SCHEMA(entity) - entities.append(VirtualSwitch(entity)) + entities.append(VirtualSwitch(entity, False)) async_add_entities(entities) class VirtualSwitch(VirtualEntity, SwitchEntity): """Representation of a Virtual switch.""" - def __init__(self, config): + def __init__(self, config, old_style : bool): """Initialize the Virtual switch device.""" - super().__init__(config, PLATFORM_DOMAIN) + super().__init__(config, PLATFORM_DOMAIN, old_style) self._attr_device_class = config.get(CONF_CLASS) diff --git a/custom_components/virtual/valve.py b/custom_components/virtual/valve.py index 94afb14..84f5ab0 100644 --- a/custom_components/virtual/valve.py +++ b/custom_components/virtual/valve.py @@ -17,6 +17,8 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.config_validation import PLATFORM_SCHEMA +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from . import get_entity_configs from .const import * @@ -44,6 +46,19 @@ })) +async def async_setup_platform( + hass: HomeAssistant, + config: ConfigType, + async_add_entities: AddEntitiesCallback, + _discovery_info: DiscoveryInfoType | None = None, +) -> None: + if hass.data[COMPONENT_CONFIG].get(CONF_YAML_CONFIG, False): + _LOGGER.debug("setting up old config...") + + sensors = [VirtualValve(config, True)] + async_add_entities(sensors, True) + + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, @@ -54,15 +69,15 @@ async def async_setup_entry( entities = [] for entity in get_entity_configs(hass, entry.data[ATTR_GROUP_NAME], PLATFORM_DOMAIN): entity = VALVE_SCHEMA(entity) - entities.append(VirtualValve(entity)) + entities.append(VirtualValve(entity, False)) async_add_entities(entities) class VirtualValve(VirtualOpenableEntity, ValveEntity): - def __init__(self, config): + def __init__(self, config, old_style: bool): """Initialize the Virtual valve device.""" - super().__init__(config, PLATFORM_DOMAIN) + super().__init__(config, PLATFORM_DOMAIN, old_style) self._attr_supported_features = ValveEntityFeature( ValveEntityFeature.OPEN |