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

net-daemon / netdaemon / 13404352133

16 Feb 2025 01:39PM UTC coverage: 83.694% (-0.04%) from 83.734%
13404352133

push

github

web-flow
Upgrade nuget packages (#1265)

842 of 1139 branches covered (73.92%)

Branch coverage included in aggregate %.

3336 of 3853 relevant lines covered (86.58%)

1083.44 hits per line

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

80.8
/src/Runtime/NetDaemon.Runtime/Internal/NetDaemonRuntime.cs
1
using System.Reactive.Linq;
2
using NetDaemon.AppModel;
3
using NetDaemon.HassModel;
4

5
namespace NetDaemon.Runtime.Internal;
6

7
internal class NetDaemonRuntime(IHomeAssistantRunner homeAssistantRunner,
18✔
8
        IOptions<HomeAssistantSettings> settings,
18✔
9
        IServiceProvider serviceProvider,
18✔
10
        ILogger<NetDaemonRuntime> logger,
18✔
11
        ICacheManager cacheManager)
18✔
12
    : IRuntime
13
{
14
    private const string Version = "local build";
15
    private const int TimeoutInSeconds = 5;
16

17
    private readonly HomeAssistantSettings _haSettings = settings.Value;
18✔
18

19
    private IAppModelContext? _applicationModelContext;
20
    private CancellationToken? _stoppingToken;
21
    private CancellationTokenSource? _runnerCancellationSource;
22

23
    public bool IsConnected;
24

25
    // These internals are used primarily for testing purposes
26
    internal IReadOnlyCollection<IApplication> ApplicationInstances =>
27
        _applicationModelContext?.Applications ?? [];
1!
28

29
    private readonly TaskCompletionSource _startedAndConnected = new();
18✔
30

31
    private Task _runnerTask = Task.CompletedTask;
18✔
32

33
    public async Task StartAsync(CancellationToken stoppingToken)
34
    {
35
        logger.LogInformation("Starting NetDaemon runtime version {Version}.", Version);
18✔
36

37
        _stoppingToken = stoppingToken;
18✔
38

39
        homeAssistantRunner.OnConnect
18✔
40
            .Select(async c => await OnHomeAssistantClientConnected(c, stoppingToken).ConfigureAwait(false))
19✔
41
            .Subscribe();
18✔
42
        homeAssistantRunner.OnDisconnect
18✔
43
            .Select(async s => await OnHomeAssistantClientDisconnected(s).ConfigureAwait(false))
12✔
44
            .Subscribe();
18✔
45
        try
46
        {
47
            _runnerCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken);
18✔
48

49
            _runnerTask = homeAssistantRunner.RunAsync(
18✔
50
                _haSettings.Host,
18✔
51
                _haSettings.Port,
18✔
52
                _haSettings.Ssl,
18✔
53
                _haSettings.Token,
18✔
54
                _haSettings.WebsocketPath,
18✔
55
                TimeSpan.FromSeconds(TimeoutInSeconds),
18✔
56
                _runnerCancellationSource.Token);
18✔
57

58
            // make sure we cancel the task if the stoppingToken is cancelled
59
            stoppingToken.Register(() =>
18✔
60
            {
18✔
61
                _startedAndConnected.TrySetCanceled();
×
62
            });
18✔
63
            // Make sure we only return after the connection is made and initialization is ready
64
            await _startedAndConnected.Task;
18✔
65
        }
16✔
66
        catch (OperationCanceledException)
×
67
        {
68
            // Ignore and just stop
69
        }
×
70
    }
16✔
71

72
    private async Task OnHomeAssistantClientConnected(
73
        IHomeAssistantConnection haConnection,
74
        CancellationToken cancelToken)
75
    {
76
        try
77
        {
78
            logger.LogInformation("Successfully connected to Home Assistant");
19✔
79

80
            if (_applicationModelContext is not null)
19!
81
            {
82
                // Something wrong with unloading and disposing apps on restart of HA, we need to prevent apps loading multiple times
83
                logger.LogWarning("Applications were not successfully disposed during restart, skippin loading apps again");
×
84
                return;
×
85
            }
86

87
            IsConnected = true;
19✔
88

89
            await cacheManager.InitializeAsync(cancelToken).ConfigureAwait(false);
19✔
90

91
            await LoadNewAppContextAsync(haConnection, cancelToken);
17✔
92

93
            _startedAndConnected.TrySetResult();
17✔
94
        }
17✔
95
        catch (Exception ex)
2✔
96
        {
97
            if (!_startedAndConnected.Task.IsCompleted)
2✔
98
            {
99
                // This means this was the first time we connected and StartAsync is still awaiting _startedAndConnected
100
                // By setting the exception on the task it will propagate up.
101
                _startedAndConnected.SetException(ex);
1✔
102
            }
103
            else
104
            {
105
                // There is none waiting for this event handler to finish so we need to Log the exception here
106
                logger.LogCritical(ex, "Error re-initializing after reconnect to Home Assistant");
1✔
107
            }
108
        }
2✔
109
    }
19✔
110

111
    private async Task LoadNewAppContextAsync(IHomeAssistantConnection haConnection, CancellationToken cancelToken)
112
    {
113
        var appModel = serviceProvider.GetService<IAppModel>();
17✔
114
        if (appModel == null) return;
17!
115

116
        _applicationModelContext = await appModel.LoadNewApplicationContext(CancellationToken.None).ConfigureAwait(false);
17✔
117

118
        // Handle state change for apps if registered
119
        var appStateHandler = serviceProvider.GetService<IHandleHomeAssistantAppStateUpdates>();
17✔
120
        if (appStateHandler == null) return;
26✔
121

122
        await appStateHandler.InitializeAsync(haConnection, _applicationModelContext);
8✔
123
    }
17✔
124

125
    private async Task OnHomeAssistantClientDisconnected(DisconnectReason reason)
126
    {
127
        if (_stoppingToken?.IsCancellationRequested == true || reason == DisconnectReason.Client)
12!
128
        {
129
            logger.LogInformation("HassClient disconnected cause of user stopping");
4✔
130
        }
131
        else
132
        {
133
            var reasonString = reason switch
8!
134
            {
8✔
135
                DisconnectReason.Remote => "home assistant closed the connection",
8✔
136
                DisconnectReason.Error => "unknown error, set loglevel to debug to view details",
×
137
                DisconnectReason.Unauthorized => "token not authorized",
×
138
                DisconnectReason.NotReady => "home assistant not ready yet",
×
139
                _ => "unknown error"
×
140
            };
8✔
141
            logger.LogInformation("Home Assistant disconnected due to {Reason}",
8✔
142
                reasonString );
8✔
143
        }
144

145
        try
146
        {
147
            await DisposeApplicationsAsync().ConfigureAwait(false);
12✔
148
        }
12✔
149
        catch (Exception e)
×
150
        {
151
            logger.LogError(e, "Error disposing applications");
×
152
        }
×
153
        IsConnected = false;
12✔
154

155
        if (reason == DisconnectReason.Unauthorized)
12!
156
        {
157
            // We will exit the runtime if the token is unauthorized to avoid hammering the server
158
            _startedAndConnected.SetResult();
×
159
        }
160
    }
12✔
161

162
    private async Task DisposeApplicationsAsync()
163
    {
164
        if (_applicationModelContext is not null)
21✔
165
        {
166
            await _applicationModelContext.DisposeAsync();
11✔
167

168
            _applicationModelContext = null;
11✔
169
        }
170
    }
21✔
171

172
    private volatile bool _isDisposed;
173
    public async ValueTask DisposeAsync()
174
    {
175
        if (_isDisposed) return;
13✔
176
        _isDisposed = true;
9✔
177

178
        await DisposeApplicationsAsync().ConfigureAwait(false);
9✔
179
        if (_runnerCancellationSource is not null)
9✔
180
            await _runnerCancellationSource.CancelAsync();
9✔
181
        try
182
        {
183
            await _runnerTask.ConfigureAwait(false);
9✔
184
        }
6✔
185
        catch (OperationCanceledException) { }
6✔
186
        _runnerCancellationSource?.Dispose();
9!
187
    }
11✔
188
}
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