Skip to content

Entity States

Leonard Sperry edited this page Nov 10, 2024 · 5 revisions

V10.1

HaKafkaNet is driven by state changes published by Home Assistant. Here's an example of a typical state from a light pushed by Home Assistant into Kafka:

{
    "attributes": {
        "brightness": 41,
        "color_mode": "brightness",
        "friendly_name": "Office Lights ",
        "supported_color_modes": [
            "brightness"
        ],
        "supported_features": 32
    },
    "context": {
        "id": "01HQ41SDCE9VZ1KVH1E9E6Q5YG",
        "parent_id": null,
        "user_id": "[redacted]"
    },
    "entity_id": "light.office_lights",
    "last_changed": "2024-02-20T15:02:21.297491-05:00",
    "last_updated": "2024-02-20T15:02:21.297491-05:00",
    "state": "on"
}

The top level properties and context properties will always be sent. The state will be a string, but could represent a number depending on the entity. For example power meters or plugs with power reporting capabilities will typically put a decimal number in the state. The attributes sent are completely up to the integration which exposes the device and its entities.

HaEntityStateChange<T>

Non-generic automations are sent an HaStateChange<HaEntityState>.

Generic automations are sent HaStateChange<HaEntityState<Tstate,Tatt>>

public record HaEntityStateChange<T>
{
    public required EventTiming EventTiming { get; set;}
    public required string EntityId { get; set; }
    public T? Old { get ; set; }
    public required T New { get ; set; }
}

The Old and New properties will represent states as exposed by Home Assistant and shown above. The value of Old will always represent what was previously stored in the cache or null if it was not found in the cache. The New property will always represent the state of the entity being read from Kafka.

See Event Timings for more information on the EventTiming property.

HaEntityState<Tstate, Tattributes>

The instances of HaEntityState sent to non-generic automations derive from HaEntityState<string, JsonElement> where the generic properties represent the state and attributes respectively. Here is a simplified definition of HaEntityState<Tstate, Tattributes>.

public record HaEntityState<Tstate, Tattributes> 
{    
    [JsonPropertyName("entity_id")]
    public virtual required string EntityId { get; init; }

    [JsonPropertyName("last_changed")]
    public DateTime LastChanged { get; init; }
    
    [JsonPropertyName("last_updated")]
    public DateTime LastUpdated { get; init; }
    
    [JsonPropertyName("context")]
    public HaEventContext? Context { get; init; }

    [JsonPropertyName("state")]
    public virtual required Tstate State { get; init; }

    [JsonPropertyName("attributes")]
    public Tattributes? Attributes { get; init; }
}

Type conversion

There are several State extension methods to help with converting the raw HaEntityState into one that is strongly-typed for your needs, for example ToColorLight() returns HaEntityState<OnOff, ColorLightModel>. Under the hood, this conversion is done with an explicit cast.

HaEntityState rawState = null; // from an entity provider or passed into an automation
HaEntityState onOffState = (HaEntityState<OnOff, ColorLightModel>)rawState;

The helper methods are there so you don't need to remember all the combinations of Tstate/Tatt, and there are helper methods for you to choose the types you want also.

Note: Type conversion for entities returned from the Updating Entity Provider are not done with a hard cast. However, the considerations are very similar to what is described here.

Choosing the right type

The cast operation can throw an exception if the conversion cannot happen.

Imagine if you wanted an HaEntityState<float, JsonElement> for a temperature sensor, but the sensor is offline, and so its "state" property in Home Assistant is "unavailable". In that case "unavailable" cannot be converted into a float and so an exception will be thrown.

That is why the ToFloatTyped() method returns HaEntityState<float?, JsonElement>>. Since the State property is nullable, instead of throwing an exception, it sets State to null.

Enums

Several built-in enums account for this possibility. For example, the OnOff enumeration, commonly used for switches and contact sensors, has values that represent "unknown" and "unavailable". Therfore, the ToOnOff() method returns HaEntityState<OnOff, JsonElement>. Notice that OnOff is not OnOff?.

Note: When creating your own enums, for example when using a drop-down helper entity, it is recommended that the first two values be Unknown and Unavailable if you will be using them as the state of an entity.

When to choose a nullable type

In most cases, for non-enum value types, you likely want to choose a nullable type, especially when using the IUpdatingEntityProvider. The one exception to this may be when creating a generic automation and you have TriggerOnBadState set to true for your AutomationMetaData. When using a generic automation, you can choose to implement IFallbackExecution which has a method that will be called when conversion fails. In this case, if you implemented IAutomation<float?, JsonElement> and the state was "unavailable", the conversion wouldn't fail, and the fallback method would not be called.