My AppDaemon Apps

Hi everyone

I’ve started using Home Assistant and AppDaemon about a year ago, maybe a bit more and learned a lot from the community here. Now I want to give something back and share my setup and apps in the hope that others can get some inspirations or help for their smart home.

I’m no programmer, this is only a hobby of mine and there will probably be bugs or bad code practice everywhere in my setup. I try to advance, fix bugs and apply better code practices as much as I can. If you have any suggestions or found any errors please let me know, I’m open for criticism.

The apps and setup is inspired a lot by the setup of @bachya , I would like to thank him for sharing his amazing setup, it helped me a lot learning about Home Assistant, AppDaemon and Python. I also got some nice ideas from the setup of @eifinger ,thanks aswell for sharing your config. I would also like to thank @ReneTode for helping me out when I started using AppDaemon.

In the future, I would love to actively contribute to the Home Assistant development and would be happy if some of you could maybe point me in the right direction to get started with this.

Github Burningstone Smart Home

5 Likes

Very cool automations and good documentation. I can learn a thing or two like using voluptuous for appdaemon^^

Good stuff, and very well documented. You should go ahead and add this to the appdaemon-apps topic on GitHub as well: https://github.com/topics/appdaemon-apps

Thanks. I can do a quick write-up on voluptuous if you are interested. It saved me quite some headaches.

2 Likes

Thanks, I did add it to the appdaemon-apps topic now.

1 Like

I would love that!

So quick introduction to voluptuous. It’s a library which can check incoming data, transform the data (if needed) and reject incorrect data. You can install it through pip.
In voluptuous you create a schema and the data is then checked against this schema. If the data doesn’t match the schema, it will throw an error. A schema can have required and optional arguments. You can check the input against some included functions like str (string) or int (integer) or you can build your own validation functions.

Now let’s get to a real life example from my appbase.py module modified for demo purpose.

first we import the library:

import voluptuous as vol

then we create the schema with vol.Schema():

APP_SCHEMA = vol.Schema({
    vol.Required('module'): str,
    vol.Required('class'): str,
    vol.Optional('disabled_states'): vol.Schema({
        vol.Optional('presence'): str,
        vol.Optional('days'): str,
    }),
}, extra=vol.ALLOW_EXTRA)

the above schema starts with ‘{’ for a dictionary, then a required key named ‘module’ with a value which needs to be a string. Then another required key named ‘class’, which also needs a string as value. Afterwards follows an optional key named ‘disabled_states’ for which we define another schema with the optional keys ‘presence’ and ‘days’. The ‘extra=vol.ALLOW_EXTRA’ at the end is there to allow additional key, value pairs in the dictionary.

So now our schema requires the yaml file to looke like this, where the disabled_states dictionary is optional:

vacuum_app:
  module: a string
  class: a string
  disabled_states:
    presence: a string
    days: a string

Now that we created our schema, we need to add it to the app and check it.

class AppBase(Hass):
    """Define a base automation object."""

    APP_SCHEMA = APP_SCHEMA
    
    def initialize(self) -> None:
        """Initialize."""

        # Check if the app configuration is correct:
        try:
            self.APP_SCHEMA(self.args)
        except vol.Invalid as err:
            self.error(f"Invalid format: {err}", level='ERROR')
            return

Here we try if the config is valid against the schema with self.APP_SCHEMA(self.args) and we except a vol.Invalid error if the config is invalid. Then we define the error message and ‘return’ to exit the function.

Now we can take it a step further and define our own validation function to use in the voluptuous schema. For this we create a separate module and just import it when needed. Here is another modified example from my voluptuous_helper.py file, which checks if a given string is an home assistant entity-id:

from typing import Any, Sequence, TypeVar, Union

import voluptuous as vol

def entity_id(value: Any) -> str:
    value = str(value).lower()
    if '.' in value:
        return value

    raise vol.Invalid(f"Invalid entity-id: {value}")

this function just checks if there is a dot in the provided value, if there is a dot the function returns the value otherwise it raises a voluptuous error with the message we define. This error will later be shown in our error log if the config doesn’t match the schema.

Now we can use this function in our schema. First import the module we created import voluptuous_helper as vol_help. The new schema:

APP_SCHEMA = vol.Schema({
    vol.Required('module'): str,
    vol.Required('class'): str,
    vol.Optional('motion_sensor'): vol_help.entity_id,
    vol.Optional('disabled_states'): vol.Schema({
        vol.Optional('presence'): str,
        vol.Optional('days'): str,
    }),
}, extra=vol.ALLOW_EXTRA)

with the line vol.Optional('motion_sensor'): vol_help.entity_id we added an optional key named ‘motion_sensor’ which needs to be an entity_id.

I hope this helps you get the concept of voluptuous. I think it’s awesome and saves me a lot of code like:

if 'motion_sensor' in self.args:
    do something
else:
    self.log("No motion sensor specified")

and because I included it in my base app, every app will automatically be checked against the schema defined there and the schema can then be extended for each app individually. As I said, I’m no programmer and there might be better ways to do this but for me this works out pretty well.

5 Likes

“I’m no programmer” - You should be! There’s a lot of thought, detail and documentation in your code. Great work.

@Burningstone, here is an extension to your voluptuous_helper module:

class existing_entity_id(object):
    def __init__(self, hass):
        self._hass = hass

    def __call__(self, value: Any) -> str:
        value = str(value).lower()
        if '.' not in value:
            raise vol.Invalid(f'Invalid entity-id: {value}')
        if not self._hass.entity_exists(value):
            raise vol.Invalid(f'Entity-id {value} does not exist')
        return value

You’d then move the scheme within the init function, so that you can pass hass instance to the validation function:

    def initialize(self):
        APP_SCHEMA = vol.Schema({
            vol.Required('module'): str,
            vol.Required('class'): str,
            vol.Optional('entity_id'): vol_help.existing_entity_id(self)
        }, extra=vol.ALLOW_EXTRA)

        try:
            config = APP_SCHEMA(self.args)
        except vol.Invalid as err:
            self.error(f"Invalid format: {err}", level='ERROR')
            return

And it will not only check the format, but also typos (if the entity actually exist).

3 Likes

Excellent extension! I will incorporate this into my system as soon as I restructured my network.
Do you have any Github Repo to check out? Might get some more ideas from your setup :slight_smile:

Cool. My Github is https://github.com/bruxy70
This appdeamon is under Venetian-Blinds-Control. I did not include that one in HACS, I think it is quite specific. But I plan making a video on the topic.
There are few other repos for custom integrations that are available through HACS, I think the garbage collection is the most popular.

2 Likes