Add device of different domain in custom component

Hi everyone,

I built a custom component which is working fine, but I would like for every entity created with that custom component to also create an input_boolean and I can’t seem to figure out the best way to do this.

I managed to create input_booleans by calling asyncio.gather.bootstrap.async_setup_component(...) but this only works if I have no other input_booleans declared in my config, which I do. I also tried hass.config_entries.async_forward_entry_setup but input_boolean does not implement async_setup_entry.

Does anyone have hints or know examples of how to create input_booleans from a custom component when the input_boolean domain is already set up?

Thanks.

No dev, but did you try with async_setup from here.

https://github.com/home-assistant/core/blob/faf21e1e1a40f66dc024609456333c533ce01bb4/homeassistant/components/input_boolean/init.py#L84

Well, now I feel stupid.
I didn’t think this was the approptiate function because I thought this would set up input_boolean as a whole and could not be called multiple times but after finding the correct format for the config this actually worked. Thank you very much, @Burningstone.

1 Like

For future refrence: This did - in fact - not work completely.
The entities showed up in HA but when I tried to interact with them, I got an error that the entity could not be found except for the last one added.
So it seems that each call to async_setup de-registered all previously registered entities.

My current solution is to get the input_boolean platform via homeassistant.helpers.entity_platform.async_get_platforms and then call async_add_entities which seems to work so far. If the platform doesn’t exist, I create it.

I am looking at something similar, just with input_number. Could you share your code on what you did to get it to work?

Sure. It boils down to this:

If you have other input_numbers in your configuration, the input_number platform will already be set up but if you don’t have any, you will have to set it up yourself.
In __init__.py for my integration I added

from .helper import get_platform, create_platform

def setup_platform(hass, name):
    platform = get_platform(hass, name)

    if platform is None:
        create_platform(hass, name)

And from async_setup I call setup_platform(hass, 'input_boolean') so now I can be sure, that the platform is set up.
The referenced helper.py looks like this:

from asyncio import gather

from homeassistant import bootstrap
from homeassistant.helpers.entity_platform import async_get_platforms


def get_platform(hass, name):
    platform_list = async_get_platforms(hass, name)

    for platform in platform_list:
        if platform.domain == name:
            return platform

    return None


def create_platform(hass, name):
    gather(
        bootstrap.async_setup_component(
            hass,
            name,
            {}
        )
    )

And finally in the platform file (<platform>.py) I declare and call a method:

from homeassistant.components.input_boolean import InputBoolean
from .helper import get_platform

async def create_input_boolean(hass, name, name_technical):
    input_boolean_name = 'input_boolean'

    data = {
        'id': name_technical,
        'name': name,
        'icon': 'mdi:youtube'
    }

    input_boolean_platform = get_platform(hass, input_boolean_name)

    input_boolean = InputBoolean(data)
    input_boolean.entity_id = input_boolean_name + '.' + name_technical

    await input_boolean_platform.async_add_entities([input_boolean], True)

I have even added automations referencing the new input_boolean this way.

It might not be pretty and if someone knows a better/cleaner/best practice way to do this I am eager to hear it, but this works for me and I am happy with it.

1 Like

Excellent, thanks for sharing. I will give it a go with input_number, and see how it plays out.

I feel like I should clarify my file/folder structure:

.
β”œβ”€β”€ custom_components
β”‚   β”œβ”€β”€ youtube
β”‚       └── __init__.py
β”‚   └── auxiliary
β”‚       β”œβ”€β”€ helper.py
β”‚       └── youtube.py

My custom component is getting channel infos from youtube, hence the name. I wanted the new entities to actually have the entity id youtube.<name> and for that I needed to introduce an auxiliare custom component which declares the actual platform and processes the config entries.
So watch out for the import statements; I edited them to make sense in the context of my previous post but they don’t align with the folder structure.
I hope this information doesn’t confuse you more than it helps. :grimacing:

Thanks for clarifying. No it helps. I’ll ask if I get confused, when I start implementing this in my project. :laughing:

I made most of it work for now, but was wondering how do you keep the state of the input_boolean entities updated? I know you can switch it from Lovelace or a Service, but isn’t the entity somehow linked to the other stuff you create?

Let me explain a bit what I try:
I have a Camera Entity, and this has an attached value that can be between0 and 100. I want to create an input_number entity for each camera, so that I can set this value. That is easy, and I could actually just create and automation for that. The thing is that the value can also be changed from outside HA, and then I need the value of the input_number to catch that. This work already for my sensors and switches, but I am not sure where to register the state change for an input_number.

My use case is that I track the latest video in a Youtube channel/playlist and get a notification when a new video is posted. Also, I can mark the youtube-entity as watched so that it doesn’t show up in my dashboard anymore.
I could do that as an attribute within the entity but this information would be lost when I restart HA, so I create an input_boolean and its state is persisted be the recorder.
For each youtube-entity I automatically create an automation which is triggered when a new video gets posted and has two actions:

  1. Send a notification
  2. Switch the respective input_boolean to off

Your use case is a little bit more tricky. The first thing that comes to mind is to create a sensor which watches the state of your value (the one from outside HA) and also create an automation which sets the value of your input_number when the sensor value changes and is different from your input_number value.
You could do all that in your custom_component, I think. :slight_smile:

Here is how I create an automation:

from homeassistant.components.automation import _async_process_config as setup_automation

async def create_automation(hass, name, name_technical):
    data = OrderedDict(
        [
            ('automation', [
                OrderedDict(
                    [
                        ('alias', name),
                        ('trigger', [
                            OrderedDict(
                                [
                                    ('platform', 'state'),
                                    ('entity_id', [
                                        'youtube.' + name_technical
                                    ]
                                     )
                                ]
                            )
                        ]
                         ),
                        ('action', [
                            OrderedDict(
                                [
                                    ('service', 'input_boolean.turn_off'),
                                    ('entity_id', ['input_boolean.' + name_technical])
                                ]
                            ),
                            OrderedDict(
                                [
                                    ('service', 'notify.mobile_app'),
                                    ('data', OrderedDict(
                                        [
                                            ('title', name),
                                            ('message',
                                             Template("{{ states('youtube." + name_technical + "') }}")),
                                            ('data', OrderedDict(
                                                [
                                                    ('importance', 'default'),
                                                    ('channel', 'Info'),
                                                    ('tag', 'YoutubeVideo'),
                                                    ('color', 'green')
                                                ]
                                            )
                                             )
                                        ]
                                    )
                                     )
                                ]
                            )
                        ]
                         ),
                        ('mode', 'single'),
                        ('max_exceeded', 'WARNING'),
                        ('max', 10)]
                )
            ]
             )
        ]
    )

    await setup_automation(hass, data, hass.data["automation"])

Thanks. I already have the value available as data, so I can track changes.

Your solution is good creative thinking, I just wish that we could create these helper entities the same way we can create sensors and switches, that would make things more streamlined.

Hi, I’ve been trying to implement this in a custom component (automatically setting up an input_boolean), but I confess I’m a little lost…when you say:

" And from async_setup I call setup_platform(hass, 'input_boolean') so now I can be sure, that the platform is set up."

I guess this is another statement somewhere? Sorry I’m not a great coder so it’s not intuitive what that would look like or where one would call it from…is your project on Github somewhere so I can see the whole context?

Thanks,

Brandon

I’m not actually doing it like this anymore. I wrote an auxiliary custom_component with a helper class which lets me create input_booleans, input_datetimes, input_numbers, input_texts, timers and automations. It’s not on Github because then I’d feel obliged to clean it up and make it understandable but maybe these snippets can lead you in the right direction:

from homeassistant.components.input_boolean import async_setup as setup_input_boolean
from homeassistant.util import slugify

async def create_input_boolean(name: str, icon=None, hass: HomeAssistant):
    data = {
        'name': name
    }

    if icon:
        data['icon'] = icon

    internal_name = slugify(name)

    ALL_INPUT_BOOLEANS[internal_name] = data

    for entity_id in list(filter(lambda eid: 'input_boolean' == eid.split('.')[0], hass.states.async_entity_ids())):
        hass.states.async_remove(entity_id)

    await setup_input_boolean(hass, {'input_boolean': ALL_INPUT_BOOLEANS})

The tricky part is, that setup_input_boolean() needs a list of ALL input_booleans or else you will lose the ones already present (I think… it’s been a while). When my custom component gets initialized I save all input_booleans from the config to ALL_INPUT_BOOLEANS and just add my custom input_booleans to that list.
Before the call to setup_input_boolean() I remove all entities from the domain (otherwise they just become dead inside HA, if I recall correctly) and re-add them along with my custom ones.
Instead of saving the input_booleans from the config when the component get initialized, you might as well save the entity_ids of the entities you remove while removing them and populate ALL_INPUT_BOOLEANS on the fly. (Well, not actually the entity_ids but just the names without the domain.)
I Hope you can follow my ideas - it’s working pretty well for me.

@Quitschibo: I’m also struggling with this, it seems like a very natural path to take when designing a custom integratio to also include the inputs to it and not only it’s states. I would be grateful if you also could share your input_number and maybe show how you use it as well.

Is the automation part you describe still what you use? I need to get that in place as well.

This is my basic file structure:

.
β”œβ”€β”€ custom_components
β”‚   β”œβ”€β”€ custom_component
β”‚       └── __init__.py
β”‚   └── auxiliary
β”‚       β”œβ”€β”€ __init__.py
β”‚       β”œβ”€β”€ helper.py
β”‚       └── custom_component.py

This is auxiliary/__init__.py:

from logging import getLogger

from voluptuous import Required, Schema

from homeassistant.components.automation import EVENT_AUTOMATION_RELOADED
from homeassistant.const import CONF_ENTITY_ID, CONF_STATE, EVENT_HOMEASSISTANT_STARTED
from homeassistant.core import HomeAssistant, Event
from homeassistant.helpers.config_validation import string
from .helper import create_automations, create_entities_and_automations, CONFIG_INPUT_BOOLEAN, COMPONENT_INPUT_BOOLEAN, \
    CONFIG_INPUT_DATETIME, COMPONENT_INPUT_DATETIME, CONFIG_INPUT_NUMBER, COMPONENT_INPUT_NUMBER, CONFIG_INPUT_TEXT, \
    COMPONENT_INPUT_TEXT, CONFIG_TIMER, COMPONENT_TIMER

DOMAIN = 'auxiliary'

SCHEMA_SET_STATE = Schema(
    {
        Required(CONF_ENTITY_ID): string,
        Required(CONF_STATE): string
    }
)

_LOGGER = getLogger(__name__)


async def async_setup(hass: HomeAssistant, config: dict):
    CONFIG_INPUT_BOOLEAN.update(config.get(COMPONENT_INPUT_BOOLEAN, {}))
    CONFIG_INPUT_DATETIME.update(config.get(COMPONENT_INPUT_DATETIME, {}))
    CONFIG_INPUT_NUMBER.update(config.get(COMPONENT_INPUT_NUMBER, {}))
    CONFIG_INPUT_TEXT.update(config.get(COMPONENT_INPUT_TEXT, {}))
    CONFIG_TIMER.update(config.get(COMPONENT_TIMER, {}))

    async def handle_home_assistant_started_event(event: Event):
        await create_entities_and_automations(hass)

    async def handle_automation_reload_event(event: Event):
        await create_automations(hass)

    hass.bus.async_listen(EVENT_HOMEASSISTANT_STARTED, handle_home_assistant_started_event)
    hass.bus.async_listen(EVENT_AUTOMATION_RELOADED, handle_automation_reload_event)

    return True

and this is auxiliary/helper.py:

from collections import OrderedDict
from datetime import timedelta
from logging import getLogger

from homeassistant.components.automation import async_setup as setup_automation, \
    _async_process_config as add_automation, AutomationConfig
from homeassistant.components.input_boolean import async_setup as setup_input_boolean
from homeassistant.components.input_datetime import async_setup as setup_input_datetime
from homeassistant.components.input_number import async_setup as setup_input_number
from homeassistant.components.input_text import async_setup as setup_input_text
from homeassistant.components.timer import async_setup as setup_timer
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity_platform import async_get_platforms
from homeassistant.util import slugify

_LOGGER = getLogger(__name__)

COMPONENT_AUTOMATION = 'automation'
COMPONENT_INPUT_BOOLEAN = 'input_boolean'
COMPONENT_INPUT_DATETIME = 'input_datetime'
COMPONENT_INPUT_NUMBER = 'input_number'
COMPONENT_INPUT_TEXT = 'input_text'
COMPONENT_TIMER = 'timer'

CUSTOM_ENTITY_COMPONENTS = {}

SETUP_FUNCTION = {
    COMPONENT_AUTOMATION: setup_automation
}

AUTOMATIONS = []

# all input_* entities in HA (config + custom)
CONFIG_INPUT_BOOLEAN = {}
CONFIG_INPUT_DATETIME = {}
CONFIG_INPUT_NUMBER = {}
CONFIG_INPUT_TEXT = {}
CONFIG_TIMER = {}

# save whether custom inputs were declared, so they can be added to HA
CUSTOM_INPUT_BOOLEAN = False
CUSTOM_INPUT_DATETIME = False
CUSTOM_INPUT_NUMBER = False
CUSTOM_INPUT_TEXT = False
CUSTOM_TIMER = False


def add_host(device_id: str, host: Entity):
    HOSTS[device_id] = host


def get_host(device_id: str) -> Entity:
    return HOSTS.get(device_id, None)


async def _get_platform(hass: HomeAssistant, domain: str):
    platform_list = async_get_platforms(hass, domain)

    for platform in platform_list:
        if platform.domain == domain:
            return platform

    if domain not in CUSTOM_ENTITY_COMPONENTS:
        await SETUP_FUNCTION[domain](hass, {})

        CUSTOM_ENTITY_COMPONENTS[domain] = EntityComponent(
            _LOGGER, domain, hass, timedelta(seconds=86400)
        )

    return CUSTOM_ENTITY_COMPONENTS[domain]


async def create_input_boolean(name: str, icon=None) -> str:
    data = {
        'name': name
    }

    if icon:
        data['icon'] = icon

    internal_name = slugify(name)

    CONFIG_INPUT_BOOLEAN[internal_name] = data

    global CUSTOM_INPUT_BOOLEAN
    CUSTOM_INPUT_BOOLEAN = True

    return 'input_boolean.{}'.format(internal_name)


async def create_input_datetime(name: str, has_date: bool, has_time: bool, initial=None,
                                icon=None) -> str:
    data = {
        'name': name,
        'has_date': has_date,
        'has_time': has_time
    }

    if initial:
        data['initial'] = initial

    if icon:
        data['icon'] = icon

    internal_name = slugify(name)

    CONFIG_INPUT_DATETIME[internal_name] = OrderedDict(data)

    global CUSTOM_INPUT_DATETIME
    CUSTOM_INPUT_DATETIME = True

    return 'input_datetime.{}'.format(internal_name)


async def create_input_number(name: str, _min: int, _max: int, step: int, mode: str,
                              unit_of_measurement: str, icon=None) -> str:
    data = {
        'name': name,
        'min': _min,
        'max': _max,
        'step': step,
        'mode': mode,
        'unit_of_measurement': unit_of_measurement
    }

    if icon:
        data['icon'] = icon

    internal_name = slugify(name)

    CONFIG_INPUT_NUMBER[internal_name] = data

    global CUSTOM_INPUT_NUMBER
    CUSTOM_INPUT_NUMBER = True

    return 'input_number.{}'.format(internal_name)


async def create_input_text(name: str, _min=0, _max=100, initial=None,
                            pattern='', mode='text', icon=None) -> str:
    data = {
        'name': name,
        'min': _min,
        'max': _max,
        'initial': initial,
        'pattern': pattern,
        'mode': mode
    }

    if icon:
        data['icon'] = icon

    internal_name = slugify(name)

    CONFIG_INPUT_TEXT[internal_name] = data

    global CUSTOM_INPUT_TEXT
    CUSTOM_INPUT_TEXT = True

    return 'input_text.{}'.format(internal_name)


async def create_timer(name: str, duration='00:00:00') -> str:
    data = {
        'name': name,
        'duration': duration
    }

    internal_name = slugify(name)

    CONFIG_TIMER[internal_name] = data

    global CUSTOM_TIMER
    CUSTOM_TIMER = True

    return 'timer.{}'.format(internal_name)


async def create_entities_and_automations(hass: HomeAssistant):
    if CUSTOM_INPUT_BOOLEAN:
        for entity_id in list(
                filter(lambda eid: COMPONENT_INPUT_BOOLEAN == eid.split('.')[0], hass.states.async_entity_ids())):
            hass.states.async_remove(entity_id)

        await setup_input_boolean(hass, {COMPONENT_INPUT_BOOLEAN: CONFIG_INPUT_BOOLEAN})

    if CUSTOM_INPUT_DATETIME:
        for entity_id in list(
                filter(lambda eid: COMPONENT_INPUT_DATETIME == eid.split('.')[0], hass.states.async_entity_ids())):
            hass.states.async_remove(entity_id)

        await setup_input_datetime(hass, {COMPONENT_INPUT_DATETIME: CONFIG_INPUT_DATETIME})

    if CUSTOM_INPUT_NUMBER:
        for entity_id in list(
                filter(lambda eid: COMPONENT_INPUT_NUMBER == eid.split('.')[0], hass.states.async_entity_ids())):
            hass.states.async_remove(entity_id)

        await setup_input_number(hass, {COMPONENT_INPUT_NUMBER: CONFIG_INPUT_NUMBER})

    if CUSTOM_INPUT_TEXT:
        for entity_id in list(
                filter(lambda eid: COMPONENT_INPUT_TEXT == eid.split('.')[0], hass.states.async_entity_ids())):
            hass.states.async_remove(entity_id)

        await setup_input_text(hass, {COMPONENT_INPUT_TEXT: CONFIG_INPUT_TEXT})

    if CUSTOM_TIMER:
        for entity_id in list(
                filter(lambda eid: COMPONENT_TIMER == eid.split('.')[0], hass.states.async_entity_ids())):
            hass.states.async_remove(entity_id)

        await setup_timer(hass, {COMPONENT_TIMER: CONFIG_TIMER})

    await create_automations(hass)


async def create_automation(data: dict):
    automation = AutomationConfig(data)
    raw_config = dict(data)
    automation.raw_config = raw_config
    AUTOMATIONS.append(automation)


async def create_automations(hass: HomeAssistant):
    platform = await _get_platform(hass, "automation")

    data = {
        'automation': AUTOMATIONS
    }

    await add_automation(hass, OrderedDict(data), platform)

and custom_component/__init__.py (in this specific case reminder/__init__.py):

import logging
from datetime import timedelta

from homeassistant.helpers.entity_component import EntityComponent
from .const import DOMAIN, SERVICE_DONE

ENTITY_ID_FORMAT = DOMAIN + ".{}"

_LOGGER = logging.getLogger(__name__)

SCAN_INTERVAL = timedelta(seconds=86400)


async def async_setup(hass, config):
    component = hass.data[DOMAIN] = EntityComponent(
        _LOGGER, DOMAIN, hass, SCAN_INTERVAL
    )

    await component.async_setup(config)

    component.async_register_entity_service(
        SERVICE_DONE, {}, "done",
    )

    return True


async def async_setup_entry(hass, entry):
    return await hass.data[DOMAIN].async_setup_entry(entry)


async def async_unload_entry(hass, entry):
    return await hass.data[DOMAIN].async_unload_entry(entry)

and an example for auxiliary/custom_component.py (in this specific case reminder.py). This is a reminder component I wrote which heavily utilizes helper.py:

#!/usr/bin/env python
# -*- coding: utf-8 -*
from collections import OrderedDict
from datetime import datetime
from logging import getLogger

from voluptuous import Optional, Required, Schema

from homeassistant.const import CONF_NAME, CONF_ICON
from homeassistant.core import HomeAssistant
from homeassistant.helpers.config_validation import string, positive_int
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.template import Template
from homeassistant.util import slugify
from .helper import create_input_datetime, create_input_number, create_automation

CONF_FREQUENCY = 'frequency'
CONF_NOTIFY = 'notify'

_LOGGER = getLogger(__name__)


async def async_setup_platform(hass: HomeAssistant, config, async_add_entities, discovery_info=None):
    name = config[CONF_NAME]
    notify = config.get(CONF_NOTIFY)
    icon = config.get(CONF_ICON)

    letzte_ausfuehrung_name = name + " Letzte AusfΓΌhrung"
    letzte_ausfuehrung_input_datetime_name = 'input_datetime.' + slugify(letzte_ausfuehrung_name)

    erinnern_um_name = name + " Erinnern um"
    erinnern_um_input_datetime_name = 'input_datetime.' + slugify(erinnern_um_name)

    frequenz_name = name + ' Frequenz'
    frequenz_input_number_name = 'input_number.' + slugify(frequenz_name)

    pause_name = name + ' Pause'
    pause_input_number_name = 'input_number.' + slugify(pause_name)

    await create_input_datetime(letzte_ausfuehrung_name, True, False, icon=icon)
    await create_input_datetime(erinnern_um_name, False, True, icon=icon)
    await create_input_number(frequenz_name, 1, 99999, 1, 'box', 'Tage', icon=icon)
    await create_input_number(pause_name, 1, 99999, 1, 'box', 'Tage', icon=icon)
    await create_automation_for_notification(name, letzte_ausfuehrung_input_datetime_name,
                                             erinnern_um_input_datetime_name,
                                             frequenz_input_number_name, pause_input_number_name, notify)
    await create_automation_for_notification_action(name)

    async_add_entities([ReminderEntity(name, notify, letzte_ausfuehrung_input_datetime_name,
                                       erinnern_um_input_datetime_name, frequenz_input_number_name, pause_input_number_name)], False)


async def create_automation_for_notification(name: str, letzte_ausfuehrung_name: str,
                                             erinnern_um_name: str,
                                             frequenz_name: str,
                                             pause_name: str,
                                             notify_list: list):
    internal_name = slugify(name)

    notification_list = []

    for notify in notify_list:
        notification_list.append(
            {
                'service': 'notify.{}'.format(notify),
                'data': {
                    'title': name,
                    'message': Template(
                        "{% set tage = (((as_timestamp(now().date()) - state_attr('" + letzte_ausfuehrung_name + "', 'timestamp')) | int /60/60/24) | round(0)) - (states('" + frequenz_name + "') | int) %} " + name + " ausstehend seit {{ 'heute.' if tage == 0 else 'gestern.' if tage == 1 else tage | string + ' Tagen.' }}"),
                    'data': {
                        'importance': 'default',
                        'channel': 'Information',
                        'tag': internal_name + 'Notification',
                        'color': 'green',
                        'actions': [
                            {
                                'action': internal_name + 'Done',
                                'title': 'Erledigt'
                            }
                        ]
                    }
                }
            }
        )

    data = {
        'alias': name,
        'trigger': [
            {
                'platform': 'time',
                'at': [erinnern_um_name]
            }
        ],
        'condition': [
            {
                'condition': 'template',
                'value_template': Template(
                    "{{ (((as_timestamp(now().date()) - state_attr('" + letzte_ausfuehrung_name + "', 'timestamp')) | int /60/60/24) | round(0)) >= (states('" + frequenz_name + "') | int) and ((((as_timestamp(now().date()) - state_attr('" + letzte_ausfuehrung_name + "', 'timestamp')) | int /60/60/24) | round(0)) - (states('" + frequenz_name + "') | int)) % (states('" + pause_name + "') | int) == 0 }}")
            }
        ],
        'action': notification_list,
        'mode': 'single',
        'max_exceeded': 'WARNING',
        'max': 10,
        'trace': {
            'stored_traces': 5
        }
    }

    await create_automation(OrderedDict(data))


async def create_automation_for_notification_action(name: str):
    internal_name = slugify(name)

    data = {
        'alias': name + ' erledigt',
        'trigger': [
            {
                'platform': 'event',
                'event_type': ['mobile_app_notification_action'],
                'event_data': {
                    'action': internal_name + 'Done'
                }
            }
        ],
        'action': [
            {
                'service': 'reminder.done',
                'entity_id': 'reminder.{}'.format(internal_name)
            }
        ],
        'mode': 'single',
        'max_exceeded': 'WARNING',
        'max': 10,
        'trace': {
            'stored_traces': 5
        }
    }

    await create_automation(OrderedDict(data))


class ReminderEntity(Entity):
    def __init__(self, name: str, notify: list, letzte_ausfuhrung_input_datetime: str, erinnern_um_input_datetime: str,
                 frequenz_input_number: str, pause_input_number: str):
        self._name = name
        self._notify = notify
        self._letzte_ausfuhrung_input_datetime = letzte_ausfuhrung_input_datetime
        self._erinnern_um_input_datetime = erinnern_um_input_datetime
        self._frequenz_input_number = frequenz_input_number
        self._pause_input_number = pause_input_number

    async def async_update(self):
        pass

    @property
    def name(self):
        return self._name

    @property
    def device_state_attributes(self):
        return {
            'last_execution': self._letzte_ausfuhrung_input_datetime,
            'remind_at': self._erinnern_um_input_datetime,
            'frequency': self._frequenz_input_number,
            'pause': self._pause_input_number
        }

    async def done(self):
        if self._notify:
            data = {
                'message': 'clear_notification',
                'data': {
                    'tag': slugify(self._name) + 'Notification'
                }
            }

            for notify in self._notify:
                await self.hass.services.async_call('notify', notify, data)

        data = {
            'entity_id': self._letzte_ausfuhrung_input_datetime,
            'date': datetime.now().strftime("%Y-%m-%d")
            # 'time': datetime.now().strftime("%H:%M")
        }

        await self.hass.services.async_call('input_datetime', 'set_datetime', data)

and finally a config entry (reminder.yaml) for this custom component could look like this:

- platform: auxiliary
  name: Blumen gießen
  notify:
    - mobile_app_1
  icon: mdi:flower

- platform: auxiliary
  name: Haus saugen
  notify:
    - mobile_app_1
    - mobile_app_2
  icon: mdi:home-modern

I hope this helps to showcase how I made it work. Sometimes I have to adapt to changes in Home Assistant but this has been stable for a long time now and I built 7 custom components this way.

1 Like

Thanks alot. I see tons of useful code here that I will try out

I sthought I swould start out easy by just cpying your examples but I fail, probably on something obvious but…

Your example has
β”œβ”€β”€ custom_components
β”‚ β”œβ”€β”€ custom_component
β”‚ └── init.py
β”‚ └── auxiliary
β”‚ β”œβ”€β”€ init.py
β”‚ β”œβ”€β”€ helper.py
β”‚ └── custom_component.py

and then you have the reminder implementation. Is that really called custom_component.py or?

Where do the reminder.yaml go? I tried to put it in top dir and added this to configuration.yaml:
reminder: !reminder.yaml

That didn’t make me happier :slight_smile:

And finally, you have custom_component with _init.py only. And auxiliary with custom_component.py
Why? Is that a design pattern of ha?

Ok, I see how this might be confusing. I started with a generic layout and then went into specific implementations. So this is a generic file structure as to my previous post:

.
β”œβ”€β”€ custom_components
β”‚   β”œβ”€β”€ custom_component
β”‚       └── __init__.py
β”‚   └── auxiliary
β”‚       β”œβ”€β”€ __init__.py
β”‚       β”œβ”€β”€ helper.py
β”‚       └── custom_component.py

But applied to the custom component reminder it would look like this:

.
β”œβ”€β”€ custom_components
β”‚   β”œβ”€β”€ reminder
β”‚       β”œβ”€β”€ __init__.py
β”‚       β”œβ”€β”€ const.py
β”‚       β”œβ”€β”€ manifest.json
β”‚       └── services.yaml
β”‚   └── auxiliary
β”‚       β”œβ”€β”€ __init__.py
β”‚       β”œβ”€β”€ helper.py
β”‚       └── reminder.py

I omitted const.py, manifest.json and services.yaml earlier because they are not specific for my setup, but they look like this:

const.py:

DOMAIN = "reminder"

SERVICE_DONE = "done"

manifest.json:

{
  "domain": "reminder",
  "name": "Reminder",
  "documentation": "",
  "codeowners": [],
  "version": "0.0.1"
}

services.yaml:

done:
  description: Markiert die Erinnerung als erledigt und setzt das Tagesdatum als Erledigungsdatum
  fields:
    entity_id:
      description: Die zu markierende EntitΓ€t.

When you change the names of the files and folders, Home Assistant should be able to pick up the component reminder form the custom components folder. The config entry should be ok then.

I’ll also edit my last answer a little to make it more consistent.

Thanks, that was my thought as well (a mix btw generic and specific)

I also added a manifest.json to the auxiliary directory.
The entry in configuration.yaml

reminder:
  - platform: auxiliary
    name: Haus saugen
    notify:
      - mobile_app_1
      - mobile_app_2
    icon: mdi:home-modern

Then I restarted and created a card in Lovelace with the newly created inputs/sensors like this:
image

For some reason I also had to change some of the german letters to get it to work in my instance (double-s and ΓΌ) in a couple of places causing the mixed-language above. Probably something wrong in my locale.

Anyway, got it to work, now I have to see how to incooperate it into my own integration and HACS in a good way. I think HACS has a requirement of beeing one directory only…