Skip to content

Automation Types

Leonard Sperry edited this page Mar 22, 2024 · 20 revisions

There are two ways of creating automations. You can either use the IAutomationRegistry interface or write individual automations. This page covers the later of those two methods. When writing individual automations, simply create a class and implement one of the interface options below.

Currently, there are 3 main automation types: Simple Automations which implement IAutomation and two types which implement the IDelayableAutomation interface. Those types implement the IConditionalAutomation and ISchedulableAutomation interfaces. All of the above interfaces implement the IAutomationBase interface:

IAutomationBase

public interface IAutomationBase
{
    EventTiming EventTimings { get => EventTiming.PostStartup; }
    IEnumerable<string> TriggerEntityIds();    
}

EventTimings

The EventTimings property tells the framework which events you care about. As you can see, it has a default implementation that only returns events after the application starts. However, at startup, the framework will read all messages from the topic which may or may not be compacted. This means that it will handle the latest entity state for all entities even if your application was not running when the events were created. If the topic has not compacted, it might handle older events. The EventTimings property will allow you to handle those events if you choose. For example your automation might have a line that looks like this:

public EventTiming EventTimings { get => 
    EventTiming.PreStartupSameAsLastCached | EventTiming.PreStartupPostLastCached | EventTiming.PostStartup; 
}

This would ensure that your automation would know the current state of an entity even if your application was down when the entity changed state. You may want to use this kind of option if, for example, you restarted your application at sunset, and you want to make sure lights get set appropriately.

See here for an example of how to consume pre-startup events.

TriggerEntityIds

All state changes for all entities with an ID that matches any of the ID's returned by this method will cause the Execute method of your automation to run, assuming the event timing matches as described above.

IAutomation

This is the primary interface used to trigger all calls into your automations. In fact, all automations end up being wrapped in an automation which implements this interface. In addition to the two members above, it exposes the Execute method.

You can add as many implementations of IAutomation as you need. All of them will be discovered at startup and will be registered as a singleton into the DI container. If you would like to have multiple instances of your automation, you must decorate your class with the ExcludeFromDiscoveryAttribute and register them via an IAutomationRegistry. See Tutorial for a detailed explanation.

public interface IAutomation
{
    Task Execute(HaEntityStateChange stateChange, CancellationToken cancellationToken);
}

Execute

Assuming the event timing and entity id of the state change matches your automation, the Execute method will be called with a HaEntityStateChange model and a CancelationToken. The state change object has 3 properties: EntityId, Old, and New. The Old and New properties represent the state of an entity at different points in time. The New property will always represent the current message being handled by the framework, and the Old will always represent what was previously stored in the cache. There is a possibility that Old will be null if it wasn't in the cache.

Note: The stateChange parameter does not have strongly typed properties for your entities. See State Extension Methods for ways of easily converting to strongly typed states.

IDelayableAutomation

Both the IConditionalAutomation and ISchedulableAutomation implement the IDelayableAutomation interface.

public interface IDelayableAutomation : IAutomationBase
{
    bool ShouldExecutePastEvents { get => false; }
    bool ShouldExecuteOnContinueError { get => false; }

    Task<bool> ContinuesToBeTrue(HaEntityStateChange haEntityStateChange, CancellationToken cancellationToken);
    Task Execute(CancellationToken cancellationToken);
}

All delayable automations are wrapped in an automation that implements the IAutomation interface which are in turn wrapped as discussed above. Instead of an Execute method, a delayable automation responds to state changes through the ContinuesToBeTrue method.

ContinuesToBeTrue

Internally, HaKafkaNet wraps your IConditionalAutomation as an IAutomation. Whereas in the IAutomation, the Execute method was called for all state changes, in the IConditionalAutomation the ContinuesToBeTrue method is called. If the method ever returns false, the delayed execution is canceled.

Execute

When the Execute method is called, it happens at some amount of time after a state change occurred and there could have been multiple state changes during that time. Therefore, there is no single state change that relates to the execution and no HaEntityStateChange object is passed in. After this method executes, your automation will be in a state as if ContinuesToBeTrue had returned false, meaning that the next time ContinuesToBeTrue returns true, your automation will be scheduled again.

ShouldExecutePastEvents

This method does not refer to pre-start up events. Where scheduling is concerned there could be times where the calculated schedule is before DateTime.Now. This property relates to that. Like in the example above regarding restarting your system at sunset. Perhaps your system was down before sunset, then sunset occurred, then your app started while handling pre-start up events, and your automation says to execute at sunset, which is now in the past. Set this to true if you want to handle that event.

ShouldExecuteOnContinueError

Unlike simple automations that fire immediately upon a state change, delayed automations execute at some scheduled time, and multiple state changes could have triggered. If one succeeds and schedules your automation to run, then another fails, what do you want to do? This property answers that question. 

IConditionalAutomation

The conditional automation is intended to ensure a state remains constant for a duration of time before it is executed. In addition to the members exposed by IDelayableAutomation it exposes one more.

public interface IConditionalAutomation
{
    TimeSpan For{ get; }
}

You interpret this interface as follows :

If condition ContinuesToBeTrue' for Foramount of time, thenExecute`.

For

This represents the amount of time to delay execution after the first time that ContinuesToBeTrue returns true.

Note, if ContinuesToBeTrue always returns true your automation would execute on an interval of the For time plus the time between state changes. If you do not want this behavior, it is recommended to recognize the state affected by Execute.

Note: if For returns TimeSpan.MinValue the automation will not run; the same as if ContinuesToBeTrue returned false.

See here for an example of using IConditionalAutomation

ISchedulableAutomation

Unlike the conditional automation that wants some state to exist for an amount of time before it triggers, the schedulable automation is intended for when information about scheduling exists in the state change itself. For example, when the sun.sun state changes, it tells you when the next sun rise/set/etc. will occur. You could also use the LastUpdatedDate property of an entity, which is used by the pre-built durable automations.

In addition to the IDelayableAutomation members, it exposes two more:

public interface ISchedulableAutomation : IDelayableAutomation
{
    bool IsReschedulable { get; }
    DateTime? GetNextScheduled();
}

IsReschedulable

When the sun entity reports state, its sunrise information won't change for roughly 24 hours. So, there is no need to reschedule and this property is false for all the sun based automations. However, if you created an automation based on a calendar, things would be different. Let's say an event on your calendar is scheduled in 4 hours. Your automation then schedules it, then another event is added to the calendar, but it is only 2 hours away. In that case, you would want to reschedule it by setting this to true.

GetNextScheduled

Unlike the conditional automation that uses a TimeSpan, the schedulable automation uses a DateTime. Upon a state change, if this automation is not already running or IsReschedulable is true, this method will be called after ContinuesToBeTrue is called.

Note: If this method returns null, the automation will not be scheduled; the same as if ContinuesToBeTrue returned false.

Scheduling is a complex topic. For more ideas on how you should schedule automations see Scheduling