Skip to content

State Extension Methods

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

By default, all entity states are sent to your automations of type HaEntityState which derives from HaEntityState<string, JsonElement>. This means that if you need to get a non-string object from the state, or a value from the JSON attributes, you need to take extra steps to get the values. Fortunately, HaKafkaNet has several extension methods to make working with states easier, and IHaEntityProvider also has several for retrieving typed states from Home Assistant directly.

HaEntityState extensions

When working with an HaEntityState use these methods for retrieving strongly typed values.

public static T? GetState<T>(this HaEntityState state)
public static T? GetStateEnum<T>(this HaEntityState state) where T: struct, Enum
public static T? GetAttributes<T>(this HaEntityState state)
public static T? GetFromAttributes<T>(this HaEntityState state, string key)

The GetState<T> and GetStateEnum<T> methods will return you the State property parsed into type you specify. If parsing fails, null is returned.

The GetAttributes<T> method will return you the entire Attributes object parsed into a type of your choosing. You can use one of the built-in types or create your own.

The GetFromAttributes<T> method is for those times you have a an entity from an integration not natively supported and you want to retrieve a single value from the attributes without needing to create a model to parse all the attributes.

Bad()

For a myriad of reasons, entities go off-line or become unresponsive. The Bad() method is for quickly identifying those scenarios. The following Boolean assignments are functionally equivalent.

    var entity = await _services.EntityProvider.GetEntity("domain.id");

    bool badEasy = entity.Bad();
    bool badHard = 
        entity is null ||
        entity.State is null ||
        entity.State.Equals("unknown", StringComparison.OrdinalIgnoreCase) ||
        entity.State.Equals("unavailable", StringComparison.OrdinalIgnoreCase);

HaEntityStateChange Extensions

State changes passed to your automations are of type HaEntityStateChange which contains old and new states for an entity where those states are of type HaEntityState which have string State and JsonElement Attributes properties. The methods described below will convert the HaEntityStateChange to a type containing more usefule types for the State and Attribues properties.

The first three methods allow you to convert to any types you like. Use these methods if none of the other built-in methods fit your needs.

public static HaEntityStateChange<HaEntityState<Tstate,JsonElement>> ToStateTyped<Tstate>(this HaEntityStateChange change) 
public static HaEntityStateChange<HaEntityState<string,Tatt>> ToAttributeTyped<Tatt>(this HaEntityStateChange change)
public static HaEntityStateChange<HaEntityState<Tstate,Tatt>> ToTyped<Tstate, Tatt>(this HaEntityStateChange change)
  • If you only care about the State property and don't need information from the attributes, use the ToStateTyped<Tstate> method.
  • If you only care about the Attributes and not the state, use the ToAttributeTyped<Tatt> method.
  • If you wish to convert both the State property and the Attributes properties, us the ToTyped<Tstate, Tatt> method.

Fortunately, there are other extension methods to handle most scenarios. Below will detail several categories of method for you to choose from.

Enumerations

Many entities will have their State property set to a specific subset of values. HaKafkaNet has several built in. They include OnOff, BatteryState, and SunState. For sun states, there is a singular method to use which combines the state with a type for sun attributes. The others have both a generic and non-generic method. Use the generic method when you want to also change the Attributes property type.

    public static HaEntityStateChange<HaEntityState<OnOff, JsonElement>> ToOnOff(this HaEntityStateChange change)
    public static HaEntityStateChange<HaEntityState<OnOff, T>> ToOnOff<T>(this HaEntityStateChange change)

    public static HaEntityStateChange<HaEntityState<BatteryState, JsonElement>> ToBatteryState(this HaEntityStateChange change)
    public static HaEntityStateChange<HaEntityState<BatteryState, T>> ToBatteryState<T>(this HaEntityStateChange change)

    public static HaEntityStateChange<HaEntityState<SunState, SunAttributes>> ToSun(this HaEntityStateChange change)

The ToOnOff method is suitable for contact and presence sensors, switches, helper toggle entities, and more. The following statements are semantically identical.

        if (stateChange.New.State.Equals("on", StringComparison.OrdinalIgnoreCase))
        { /* do work */}

        var onOffState = stateChange.ToOnOff();
        if (onOffState.New.State == OnOff.On) 
        { /* do work */}

The OnOff and BatteryState enumerations also have values representing "unknown" and "unavailable" for those times when an entity goes offline.

Numbers and Dates

Several types of sensors expose a value type in the state property. This includes illumination sensors, power meters, and more.

    public static HaEntityStateChange<HaEntityState<int?, JsonElement>> ToIntTyped(this HaEntityStateChange change)
    public static HaEntityStateChange<HaEntityState<int?, T>> ToIntTyped<T>(this HaEntityStateChange change)
    
    public static HaEntityStateChange<HaEntityState<double?, JsonElement>> ToDoubleTyped(this HaEntityStateChange change)
    public static HaEntityStateChange<HaEntityState<double?, T>> ToDoubleTyped<T>(this HaEntityStateChange change)

    public static HaEntityStateChange<HaEntityState<float?, JsonElement>> ToFloatTyped(this HaEntityStateChange change)
    public static HaEntityStateChange<HaEntityState<float?, T>> ToFloatTyped<T>(this HaEntityStateChange change)

    public static HaEntityStateChange<HaEntityState<DateTime?, JsonElement>> ToDateTimeTyped(this HaEntityStateChange change)
    public static HaEntityStateChange<HaEntityState<DateTime?, T>> ToDateTimeTyped<T>(this HaEntityStateChange change)

Similar to the enumeration methods, these have both a non-generic and generic method. The generic version is for converting the attributes. As an example, the following two if statements are semantically identical:

        if (int.TryParse(stateChange.New.State, out int stateValue) && stateValue > myThreshold)
        { /* do work */}

        var intState = stateChange.ToIntTyped();
        if (intState.New.State > myThreshold)
        { /* do work */}

Lights and Scene Controllers

There are three more methods for working with lights and scene controllers.

    public static HaEntityStateChange<HaEntityState<OnOff, LightModel>> ToLight(this HaEntityStateChange change)
    public static HaEntityStateChange<HaEntityState<OnOff, ColorLightModel>> ToColorLight(this HaEntityStateChange change)
    
    public static HaEntityStateChange<HaEntityState<DateTime?, SceneControllerEvent>> ToSceneControllerEvent(this HaEntityStateChange change)

For clarity only, the following 2 lines are functionally equivalent.

        var lightState1 = stateChange.ToTyped<OnOff, LightModel>();
        var lightState2 = stateChange.ToLight()

The method for scene controllers is particularly useful. The following example is for demonstration purposes to show possibilities. It assumes that your automation is listening to all the button entities of a scene controller. For brevity, error handling is omitted. See Scene Controllers for more information.

public Task Execute(HaEntityStateChange stateChange, CancellationToken cancellationToken)
{
    var sceneState = stateChange.ToSceneControllerEvent();
    var key = sceneState.New.Attributes?.GetKeyPress();
    var btn = stateChange.EntityId.Last();
    return (btn,key) switch
    {
        {btn: '1', key: KeyPress.KeyPressed }   => _services.Api.Toggle("light.light_1"),
        {btn: '1', key: KeyPress.KeyHeldDown }  => _services.Api.LightTurnOn(new LightTurnOnModel(){EntityId = ["lightlight_1"], BrightnessStepPct = 10}),
        {btn: '1', key: KeyPress.KeyPressed2x } => _services.Api.LightTurnOn(new LightTurnOnModel(){EntityId = ["lightlight_1"], BrightnessStepPct = -25}),
        {btn: '2', key: KeyPress.KeyPressed }   => _services.Api.Toggle("light.light_2"),
        {btn: '2', key: KeyPress.KeyHeldDown }  => _services.Api.LightTurnOn(new LightTurnOnModel(){EntityId = ["lightlight_2"], BrightnessStepPct = 10}),
        {btn: '2', key: KeyPress.KeyPressed2x } => _services.Api.LightTurnOn(new LightTurnOnModel(){EntityId = ["lightlight_2"], BrightnessStepPct = -25}),
        _ => Task.CompletedTask // unassigned
    };
}

Geolocation

There are three methods to help with types related to geolocation

    public static HaEntityStateChange<HaEntityState<string, DeviceTrackerModel>> ToDeviceTracker(this HaEntityStateChange change)
    public static HaEntityStateChange<HaEntityState<string, PersonModel>> ToPerson(this HaEntityStateChange change)
    public static HaEntityStateChange<HaEntityState<int, ZoneModel>> ToZone(this HaEntityStateChange change)

Calendars

There is one method for working with calendars.

public static HaEntityStateChange<HaEntityState<OnOff, CalendarModel>> ToCalendar(this HaEntityStateChange change)

Caution: When working with scheduled automations and calendars, be sure to set IsReschedulable to true. See Durable Automations and Schedulable automations for more details.

Home Assistant automations

If you have native home assistant automations, and you want to run an automation based on changes to that automation, you can add automation as a domain in your kafka integraion filter, and trigger HaKafkaNet automations from them.

public static HaEntityStateChange<HaEntityState<OnOff, HaAutomationModel>> ToHaAutomation(this HaEntityStateChange change)