Skip to content

Commit

Permalink
Config flow (#72)
Browse files Browse the repository at this point in the history
* Start on the config flow.

* Got data import working.

* Binary and sensors working.

* Added switches.

* Tidy some constants.

* Bump the revision...

* Enable light support.

* Enable lock support.

* Enable fan support.

* Enable device_tracker support.

* Move into subgroup so we can support multiple virtual integrations.

* Move into subgroup so we can support multiple virtual integrations.

Add config flow translations.

* Allow groups to be deleted.

* Allow device trackers to be moved.

* Better (but still not great) device tracker handling.

* Fix global class variable issue.

* Fix global class variable issue.

* Fix global class variable issue.

* Fix global class variable issue.

* First (very) early draft of the docs.

* First (very) early draft of the docs.

* Fix services.

* Fix services.

* Fix services.

* Merge in cover changes.

* Tweak cover merge.

* Fix actions.

* Fix errors reports by validation.
  • Loading branch information
twrecked authored Nov 9, 2023
1 parent 0eacb77 commit ddeda0d
Show file tree
Hide file tree
Showing 22 changed files with 1,290 additions and 297 deletions.
1 change: 0 additions & 1 deletion .github/workflows/hacs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ jobs:
name: HACS Validation
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v2"
- name: HACS Validation
uses: "hacs/action@main"
with:
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/hass.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ on:

jobs:
validate:
name: HASS Validation
name: hassfest Validation
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v2"
- uses: "home-assistant/actions/hassfest@master"
- uses: "actions/checkout@v2"
- uses: "home-assistant/actions/hassfest@master"
86 changes: 79 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,84 @@
# hass-virtual
### Virtual Components for Home Assistant
![icon](images/virtual-icon.png)

## Virtual Components for Home Assistant

Virtual components for testing Home Assistant systems.

## Version 0.8
## Version 0.8?

**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).

## New Features in 0.9.0

### Config Flow

### **Breaking Changes**
Finally. After sitting on it for far too long I decided to do the work I
needed to, this integration now acts much like every integration, splitting
down by entity, device and integration.

I've added persistent support to `binary_sensor`, `fan`, `light`, `lock`,
`sensor`, `switch`, `cover` and `device_tracker`. The persistent saving of state is
turned *on* by default. If you do not want this set `persistent: False` in the
entity configuration.
This means a lot of this documentation is now out of date, I will upgrade it
when all the changes have been finalised, for now I will just add a quick note
inline.

#### What pieces are done

- _upgrade_; the code will upgrade a _0.8_ build to the _config flow_ system.
Your current configuration will be moved into 1 file, `virtual.yaml`. This
file contains all your virtual devices. Edit this file to add any type of
device.
- _configuration_; the settings are still available you only need to edit them
in one place, `virtual.yaml`. The layout should be obvious after the
upgrade.
- _device groupings_; for example, a motion detector can have a motion
detection entity and a battery entity, upgraded devices will have a one to
one relationship. For example, the following will create a motion device
with 2 entities. If you don't provide a name for an entity the system will
provide a default.
- _multiple integration instances_; you can group virtual devices, each group
will use a different configuration file

```yaml
Mezzaine Motion:
- platform: binary_sensor
initial_value: 'off'
class: motion
- platform: sensor
initial_value: 98
class: battery
```
#### What you need to be wary of
- _device trackers_; the upgrade process is a little more complicated if you
have device trackers, because of the way _virtual_ created the old devices
you will end up with duplicates entries, you can fix it by running the
following steps
1. do the upgrade
2. comment out device virtual device trackers from `device_trackers.yaml`
and `known_devices.yaml`
3. restart _Home Assistant_
4. delete the virtual integration
5. add back the virtual integration in accepting the defaults

#### What pieces need doing

- _reload/reconfigure_; this somewhat works, but I need to deal with orphans
when devices are turned off
- _documentation; the configuration is handled differently now

#### What if it goes wrong?

For now I recommend leaving your old configuration in place so you can revert
back to a _0.8_ release if you encounter an issue. _Home Assistant_ will
complain about the config but it's ok to ignore it.

If you do encounter and issue if you can turn on debug an create an issue that
would be great.


## Version 0.9

## Table Of Contents
1. [Notes](#Notes)
Expand All @@ -37,6 +105,8 @@ Many thanks to:

[![JetBrains](/images/jetbrains.svg)](https://www.jetbrains.com/?from=hass-aarlo)

* Icon from [iconscout](https://iconscout.com) by [twitter-inc](https://iconscout.com/contributors/twitter-inc)


## Installation

Expand All @@ -49,6 +119,8 @@ development branches this is the easiest way to install.

## Component Configuration

**This is no longer valid, all config is in `virtual.yaml`.**

Add the following to your `configuration.yaml` to enable the component:

```yaml
Expand Down
2 changes: 2 additions & 0 deletions changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
0.9.0a1: move to config flow and proper integration support

0.8.0b1: move into beta...
0.8.0a3: save device tracker location
0.8.0a2: added locking/unlocking state to locks
Expand Down
138 changes: 126 additions & 12 deletions custom_components/virtual/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,158 @@
import logging
import voluptuous as vol
from distutils import util

import homeassistant.helpers.config_validation as cv
import homeassistant.helpers.device_registry as dr
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA
from homeassistant.helpers.service import verify_domain_control
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.helpers.typing import ConfigType
from homeassistant.config_entries import ConfigEntry, SOURCE_IMPORT
from homeassistant.const import ATTR_ENTITY_ID, CONF_SOURCE, Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError

from .const import (
COMPONENT_DOMAIN,
COMPONENT_SERVICES
)
from .const import *
from .cfg import BlendedCfg, UpgradeCfg


__version__ = '0.8.0b1'
__version__ = '0.9.0a1'

_LOGGER = logging.getLogger(__name__)

# Purely to quieten down the checks.
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({})

SERVICE_AVAILABILE = 'set_available'
SERVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required('value'): cv.boolean,
})

VIRTUAL_PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.COVER,
Platform.DEVICE_TRACKER,
Platform.FAN,
Platform.LIGHT,
Platform.LOCK,
Platform.SENSOR,
Platform.SWITCH
]


async def async_setup(hass, config):
"""Set up a virtual components."""
def setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up a virtual component.
"""

hass.data[COMPONENT_DOMAIN] = {}
hass.data[COMPONENT_SERVICES] = {}
_LOGGER.debug('setup')

# 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
)
)
return True

_LOGGER.debug('ignoring a YAML setup')
return True


@callback
def _async_find_matching_config_entry(hass):
""" If we have anything in config_entries for virtual we consider it
configured and will ignore the YAML.
"""
for entry in hass.config_entries.async_entries(COMPONENT_DOMAIN):
return entry


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
_LOGGER.debug(f'async setup {entry.data}')

# Set up hass data if necessary
if COMPONENT_DOMAIN not in hass.data:
hass.data[COMPONENT_DOMAIN] = {}
hass.data[COMPONENT_SERVICES] = {}

# Get the config.
_LOGGER.debug(f"creating new cfg")
vcfg = BlendedCfg(entry.data)
vcfg.load()

# create the devices.
_LOGGER.debug("creating the devices")
for device in vcfg.devices:
_LOGGER.debug(f"creating-device={device}")
await _async_get_or_create_virtual_device_in_registry(hass, entry, device)

# Update the component data.
hass.data[COMPONENT_DOMAIN].update({
entry.data[ATTR_GROUP_NAME]: {
ATTR_ENTITIES: vcfg.entities,
ATTR_FILE_NAME: entry.data[ATTR_FILE_NAME]
}
})
_LOGGER.debug(f"update hass data {hass.data[COMPONENT_DOMAIN]}")

# Create the entities.
_LOGGER.debug("creating the entities")
await hass.config_entries.async_forward_entry_setups(entry, VIRTUAL_PLATFORMS)

# Install service handler.
@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))
_LOGGER.info(f"{call.service} service called")
await async_virtual_set_availability_service(hass, call)

hass.services.async_register(COMPONENT_DOMAIN, SERVICE_AVAILABILE, async_virtual_service_set_available)
if not hasattr(hass.data[COMPONENT_SERVICES], COMPONENT_DOMAIN):
_LOGGER.debug("installing handlers")
hass.data[COMPONENT_SERVICES][COMPONENT_DOMAIN] = 'installed'
hass.services.async_register(COMPONENT_DOMAIN, SERVICE_AVAILABILE, async_virtual_service_set_available)

return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
_LOGGER.debug(f"unloading virtual group {entry.data[ATTR_GROUP_NAME]}")
# _LOGGER.debug(f"before hass={hass.data[COMPONENT_DOMAIN]}")
unload_ok = await hass.config_entries.async_unload_platforms(entry, VIRTUAL_PLATFORMS)
if unload_ok:
bcfg = BlendedCfg(entry.data)
bcfg.delete()
hass.data[COMPONENT_DOMAIN].pop(entry.data[ATTR_GROUP_NAME])
# _LOGGER.debug(f"after hass={hass.data[COMPONENT_DOMAIN]}")

return unload_ok


async def _async_get_or_create_virtual_device_in_registry(
hass: HomeAssistant, entry: ConfigEntry, device
) -> None:
device_registry = dr.async_get(hass)
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(COMPONENT_DOMAIN, device[ATTR_DEVICE_ID])},
manufacturer=COMPONENT_MANUFACTURER,
model=COMPONENT_MODEL,
name=device[CONF_NAME],
sw_version=__version__
)


def get_entity_configs(hass, group_name, domain):
return hass.data.get(COMPONENT_DOMAIN, {}).get(group_name, {}).get(ATTR_ENTITIES, {}).get(domain, [])


def get_entity_from_domain(hass, domain, entity_id):
component = hass.data.get(domain)
if component is None:
Expand All @@ -64,4 +178,4 @@ async def async_virtual_set_availability_service(hass, call):
value = bool(util.strtobool(value))
domain = entity_id.split(".")[0]
_LOGGER.info("{} set_avilable(value={})".format(entity_id, value))
get_entity_from_domain(hass, domain, entity_id).set_available(value)
get_entity_from_domain(hass, domain, entity_id).set_available(value)
Loading

0 comments on commit ddeda0d

Please sign in to comment.