Skip to content

Tutorial: Creating Automations

Leonard Sperry edited this page Apr 2, 2024 · 13 revisions

After setting up your environment, the easiest way to start making automations is to use the IAutomationRegistry interface.

Add a class to your project that implements IAutomationRegistry. Inject any services you may need for executing actions in your automations. The IHaServices interface gives access to Home Assistant. Also, inject either IAutomationFactory or the IAutomationBuilder or both.

Next, in the Register method, create automations and register them with the registrar. Here is a small example with a few automations to get you started:

IAutomationRegistry example

using HaKafkaNet;

namespace MyHome;

public class TutorialRegistry : IAutomationRegistry
{
    readonly IAutomationFactory _factory;
    readonly IAutomationBuilder _builder;
    readonly IHaServices _services;

    public TutorialRegistry (IAutomationFactory factory, IAutomationBuilder builder, IHaServices services)
    {
        _factory = factory;
        _builder = builder;
        _services = services;
    }

    public void Register(IRegistrar reg)
    {
        var turnOffFrontPorch = _factory.DurableAutoOff("light.front_porch", TimeSpan.FromHours(2));
        var turnOnBackPorchWithMotion = _factory.LightOnMotion("binary_sensor.back_porch", "light.back_porch");
        var turnOffBackPorch = _factory.LightOffOnNoMotion("binary_sensor.back_porch", "light.back_porch", TimeSpan.FromMinutes(5));
        
        reg.Register(turnOnBackPorchWithMotion);
        reg.RegisterMultiple(turnOffBackPorch, turnOffFrontPorch);

        var frontPorchAtSunset = _builder.CreateSunAutomation(SunEventType.Set)
            .WithOffset(TimeSpan.FromMinutes(-15))
            .WithExecution(ct => _services.Api.TurnOn("light.front_porch"))
            .Build();
        reg.Register(frontPorchAtSunset);
    }
}

There, you just created your first 4 automations. Only one registry is needed, but you can have as many as you want to structure your code by area or any category you choose. Here's a breakdown of the four automations created above:

  1. EntityAutoOff - This is a durable prebuilt automation that will turn off any entity after a specified amount of time after it turns on.
  2. LightOnMotion - Another prebuilt automation that ties motion sensors and lights together
  3. LightOffOnNoMotion - The counterpart to LightOnMotion
  4. Sunset automation created using the builder with fluent syntax. It has an offset to run 15 minutes before sunset. There is also a similar factory method available.

HaKafkaNet is driven by state changes being sent from home assistant. In those state-change events, an entity will be sent in the form of <domain>.<entity name>. You can see that in the above example with entities such as "binary_sensor.back_porch".

The IRegistrar has several overloads for registering automations. If the automations are of the same type, you can register multiple at the same time. This is great for setting up a bunch of similar automations.

Tip: you can define entity id's as constants and expose them to your test classes to help prevent typos.

Tip: There are extension methods for entity states and the services to get strongly typed access to entities. Also check out the utility classes documentation.

From Scratch Example

The examples above are great for quickly creating simple automations, but what about when you want something a little more? The following example shows how you can create an automation from scratch with more complex logic. This automation will have the following requirements:

  • When motion is detected in the entryway by the front door:
    • If I am home, turn on the entryway light
    • Otherwise, send a notification to my phone

To detect if we are home, this example will use a single person entity. Of course, you could use any entity's state for your own logic. After we create this automation, we will modify it to add metadata, then we will modify it for use in multiple rooms.

using HaKafkaNet;
namespace MyHome.Dev;

public class MotionBehaviorTutorial : IAutomation
{
    readonly IHaServices _services;
    public MotionBehaviorTutorial(IHaServices services)
    {
        _services = services;    
    }

    public IEnumerable<string> TriggerEntityIds() => ["binary_sensor.entryway_motion"];

    public async Task Execute(HaEntityStateChange stateChange, CancellationToken cancellationToken)
    {
        var homeState = await _services.EntityProvider.GetPersonEntity("person.name", cancellationToken);
        var isHome = homeState?.IsHome() ?? false;

        if (isHome)
            await _services.Api.TurnOn("light.entry_way");
        else
            await _services.Api.NotifyGroupOrDevice(
                "device_tracker.my_phone", "Motion was detected in the entry way", cancellationToken);
    }
}

In this example, we implemented the IAutomation interface and its two members. See Automation Types for other types you can implement. At this point, nothing else is required. At startup, the framework will inspect all loaded types. If they implement any of the supported interfaces, they will be registered as a singleton. If we want, we could also unit test this automation using any test framework of our choosing.

Adding Metadata

On the dashboard, this automation would be shown. Its "source" would be listed as "discovered" indicating that it was found through discovery at startup. Its name would default to GetType().Name, which for this example would be MotionBehaviorTutorial. If we want a better display, especially if we intend on reusing the automation, we should also implement the IAutomationMeta and its one member.

    public AutomationMetaData GetMetaData() =>
        new()
        {
            Name = "Entryway Motion",
            Description = "Turn on entryway light if we're home, otherwise notify",
            AdditionalEntitiesToTrack = ["light.entry_way"]
        };

At startup, the framework will wrap your automation in a wrapper which will call the GetMetaData() method exactly one time. The same is true for TriggerEntityIds(). Now when we visit the dashboard, it will render with more useful information. Additionally, if you implement a System Monitor, any entities listed in the AdditionalEntitiesToTrack property will be checked to make sure they are responsive.

Reusing custom built automations

The above automation works great for single use automations. However, we may want to use this automation for other parts of our home. First, we need to decorate the class with the ExcludeFromDiscovery attribute. This will tell the discovery process to ignore it and not create a singleton. Next we need to prepare it to accept different entities. Here is the same class with the necessary modifications.

using HaKafkaNet;
namespace MyHome.Dev;

[ExcludeFromDiscovery]
public class MotionBehaviorTuttorial : IAutomation, IAutomationMeta
{
    readonly string _motion, _light;
    readonly IHaServices _services;
    public MotionBehaviorTuttorial(string motion, string light, IHaServices services)
    {
        _motion = motion;
        _light = light;
        _services = services;    
    }

    public IEnumerable<string> TriggerEntityIds() => [_motion];

    public async Task Execute(HaEntityStateChange stateChange, CancellationToken cancellationToken)
    {
        var homeState = await _services.EntityProvider.GetEntityState("input_boolean.am_home", cancellationToken);
        var isHome = homeState?.State == "on";

        if (isHome)
            await _services.Api.TurnOn(_light);
        else
            await _services.Api.NotifyGroupOrDevice(
                "device_tracker.my_phone", $"Motion was detected by {_motion}", cancellationToken);
    }

    public AutomationMetaData GetMetaData() =>
        new()
        {
            Name = $"Motion Behavior{_motion}",
            Description = $"Turn on {_light} if we're home, otherwise notify",
            AdditionalEntitiesToTrack = [_light]
        };
}

Some of the messaging could be improved, but for demonstration purposes this works. Now what we need is a way to construct it for use in our registry. We could create a helper method in the registry and construct it directly. Alternatively, to use it in multiple registries, we can create a factory extension method.

static class FactoryExtensions
{
    public static IAutomation CreateMotionBehavior(this IAutomationFactory factory, string motion, string light)
        => new MotionBehaviorTuttorial(motion, light, factory.Services);
}

Now we can use this method from any of our registries and register it with the registrar.

If you've read this far, you may also find the advanced tutorial beneficial.

For some inspiration of what is possible, here are a few links from the author's home set up.

  • Garage service - This service tries to make sure the garage is closed. Unfortunately, sometimes tilt sensors can be unreliable and contact sensors with moving doors can be hard to adjust (especially if a little snow gets trapped under the door).
  • Dynamically Set lights - This automation uses readings from roof mounted solar panels and dynamically sets lights using a parabolic curve to calculate brightness so that when they come on, they come on gently.