• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

net-daemon / netdaemon / 13218430318

08 Feb 2025 06:58PM UTC coverage: 83.697% (-0.04%) from 83.737%
13218430318

push

github

web-flow
Strongly-type EntityState.Attributes (#1251)

For those working with base entities (such as the IHaRegistry API), the virtual property/covariant return override approach isn't helpful. The base property will always return a `Dictionary<string, object>`, which can be useful to iterate through. Instead, we should have strongly-typed derived class _hide_ the base property, so users who are just working with pure entities get a useful key/value pair, and derived users get strongly-typed info.

Co-authored-by: Tomas Hellström <tomas.hellstrom@yahoo.se>

842 of 1139 branches covered (73.92%)

Branch coverage included in aggregate %.

2 of 4 new or added lines in 2 files covered. (50.0%)

1 existing line in 1 file now uncovered.

3337 of 3854 relevant lines covered (86.59%)

919.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.4
/src/HassModel/NetDeamon.HassModel/Entities/Entity.cs
1
namespace NetDaemon.HassModel.Entities;
2

3
/// <summary>Represents a Home Assistant entity with its state, changes and services</summary>
4
public record Entity : IEntityCore
5
{
6
    /// <summary>
7
    /// The IHAContext
8
    /// </summary>
9
    public IHaContext HaContext { get; }
177✔
10

11
    /// <summary>
12
    /// Entity id being handled by this entity
13
    /// </summary>
14
    public string EntityId { get; }
304✔
15

16
    /// <summary>
17
    /// Creates a new instance of a Entity class
18
    /// </summary>
19
    /// <param name="haContext">The Home Assistant context associated with this Entity</param>
20
    /// <param name="entityId">The id of this Entity</param>
21
    public Entity(IHaContext haContext, string entityId)
125✔
22
    {
23
        HaContext = haContext;
125✔
24
        EntityId = entityId;
125✔
25
    }
125✔
26

27
    /// <summary>Copy constructor from IEntityCore</summary>
28
    public Entity(IEntityCore entity)
15✔
29
    {
30
        ArgumentNullException.ThrowIfNull(entity);
15✔
31
        HaContext = entity.HaContext;
15✔
32
        EntityId = entity.EntityId;
15✔
33
    }
15✔
34

35
    /// <summary>
36
    /// Area name of entity
37
    /// </summary>
38
    public string? Area => Registration?.Area?.Name;
1!
39

40
    /// <summary>The current state of this Entity</summary>
41
    public string? State => EntityState?.State;
4!
42

43
    /// <summary>
44
    /// The current Attributes of this Entity
45
    /// </summary>
NEW
46
    public Dictionary<string, object>? Attributes => EntityState?.Attributes;
×
47

48
    /// <summary>
49
    /// The full state of this Entity
50
    /// </summary>
51
    public virtual EntityState? EntityState => HaContext.GetState(EntityId);
134✔
52

53
    /// <summary>
54
    /// Observable that emits all state changes, including attribute changes.<br/>
55
    /// Use <see cref="System.ObservableExtensions.Subscribe{T}(System.IObservable{T})"/> to subscribe to the returned observable and receive state changes.
56
    /// </summary>
57
    /// <example>
58
    /// <code>
59
    /// bedroomLight.StateAllChanges()
60
    ///     .Where(s =&gt; s.Old?.Attributes?.Brightness &lt; 128
61
    ///              &amp;&amp; s.New?.Attributes?.Brightness &gt;= 128)
62
    ///     .Subscribe(e =&gt; HandleBrightnessOverHalf());
63
    /// </code>
64
    /// </example>
65
    public virtual IObservable<StateChange> StateAllChanges() =>
66
        HaContext.StateAllChanges().Where(e => e.Entity.EntityId == EntityId);
65✔
67

68
    /// <summary>
69
    /// Observable that emits state changes where New.State != Old.State<br/>
70
    /// Use <see cref="System.ObservableExtensions.Subscribe{T}(System.IObservable{T})"/> to subscribe to the returned observable and receive state changes.
71
    /// </summary>
72
    /// <example>
73
    /// <code>
74
    /// disabledLight.StateChanges()
75
    ///    .Where(s =&gt; s.New?.State == "on")
76
    ///    .Subscribe(e =&gt; e.Entity.TurnOff());
77
    /// </code>
78
    /// </example>
79
    public virtual IObservable<StateChange> StateChanges() =>
80
        StateAllChanges().StateChangesOnly();
1✔
81

82
    /// <summary>
83
    /// Calls a service using this entity as the target
84
    /// </summary>
85
    /// <param name="service">Name of the service to call. If the Domain of the service is the same as the domain of the Entity it can be omitted</param>
86
    /// <param name="data">Data to provide</param>
87
    public virtual void CallService(string service, object? data = null)
88
    {
89
        EntityExtensions.CallService(this, service, data);
1✔
90
    }
1✔
91

92
    /// <summary>
93
    /// Data from the HA Entity Registry, Like Device, Area and Labels regarding this Entity
94
    /// </summary>
95
    public EntityRegistration? Registration => HaContext.GetEntityRegistration(EntityId);
1✔
96
}
97

98
/// <summary>Represents a Home Assistant entity with its state, changes and services</summary>
99
public abstract record Entity<TEntity, TEntityState, TAttributes> : Entity
100
    where TEntity : Entity<TEntity, TEntityState, TAttributes>
101
    where TEntityState : EntityState<TAttributes>
102
    where TAttributes : class
103
{
104
    /// <summary>Copy constructor from IEntityCore</summary>
105
    protected Entity(IEntityCore entity) : base(entity)
6✔
106
    { }
6✔
107

108
    /// <summary>Constructor from haContext and entityId</summary>
109
    protected Entity(IHaContext haContext, string entityId) : base(haContext, entityId)
18✔
110
    { }
18✔
111

112
    /// <inheritdoc />
113
    public new virtual TAttributes? Attributes => EntityState?.Attributes;
8!
114

115
    /// <inheritdoc />
116
    public override TEntityState? EntityState => MapState(base.EntityState);
69✔
117

118
    /// <inheritdoc />
119
    public override IObservable<StateChange<TEntity, TEntityState>> StateAllChanges() =>
120
        base.StateAllChanges().Select(e => new StateChange<TEntity, TEntityState>((TEntity)this,
34✔
121
            Entities.EntityState.Map<TEntityState>(e.Old),
34✔
122
            Entities.EntityState.Map<TEntityState>(e.New)));
34✔
123

124
    /// <inheritdoc />
125
    public override IObservable<StateChange<TEntity, TEntityState>> StateChanges() => StateAllChanges().StateChangesOnly();
2✔
126

127
    private static TEntityState? MapState(EntityState? state) => Entities.EntityState.Map<TEntityState>(state);
69✔
128
}
129

130
/// <summary>Represents a Home Assistant entity with its state, changes and services</summary>
131
public record Entity<TAttributes> : Entity<Entity<TAttributes>, EntityState<TAttributes>, TAttributes>
132
    where TAttributes : class
133
{
134
    // This type is needed because the base type has a recursive type parameter so it can not be used as a return value
135

136
    /// <summary>Copy constructor from IEntityCore</summary>
137
    public Entity(IEntityCore entity) : base(entity) { }
2✔
138

139
    /// <summary>Constructor from haContext and entityId</summary>
140
    public Entity(IHaContext haContext, string entityId) : base(haContext, entityId) { }
×
141
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc