Skip to content

Commit

Permalink
Netdaemon status change and integration bugfix (#277)
Browse files Browse the repository at this point in the history
* Tests and netdaemon.status bugfix

* bugfixes and code cleanup
  • Loading branch information
helto4real committed Jan 9, 2021
1 parent 32e76c6 commit d412b1a
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 31 deletions.
11 changes: 5 additions & 6 deletions src/App/NetDaemon.App/Common/NetDaemonAppBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,26 +144,25 @@ public async Task RestoreAppStateAsync()
bool isDisabled = Storage.__IsDisabled ?? false;
var appInfo = Daemon!.State.FirstOrDefault(s => s.EntityId == EntityId);
var appState = appInfo?.State as string;

if (isDisabled)
{
IsEnabled = false;
if (appState == "on")
if (appState == "on" || appInfo is null)
{
dynamic serviceData = new FluentExpandoObject();
serviceData.entity_id = EntityId;
await Daemon.SetStateAsync(EntityId, "off").ConfigureAwait(false);
await Daemon!.SetStateAsync(EntityId, "off").ConfigureAwait(false);
}
return;
}
else
{
IsEnabled = true;
if (appState == "off")
if (appState == "off" || appInfo is null)
{
dynamic serviceData = new FluentExpandoObject();
serviceData.entity_id = EntityId;
await Daemon.SetStateAsync(EntityId, "on").ConfigureAwait(false);
await Daemon!.SetStateAsync(EntityId, "on").ConfigureAwait(false);
}
return;
}
Expand All @@ -178,7 +177,7 @@ public async Task RestoreAppStateAsync()
/// <inheritdoc/>
[SuppressMessage("", "CA1065")]
public IEnumerable<string> EntityIds => Daemon?.State.Select(n => n.EntityId) ??
throw new NetDaemonNullReferenceException("Deamon not expected to be null");
throw new NetDaemonNullReferenceException("Daemon not expected to be null");

/// <summary>
/// Instance to Daemon service
Expand Down
4 changes: 2 additions & 2 deletions src/Daemon/NetDaemon.Daemon/Daemon/IInstanceDaemonApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
namespace NetDaemon.Daemon
{
/// <summary>
/// Interface for objects implementing the InstanceDeamonApps features
/// Interface for objects implementing the InstanceDaemonApps features
/// </summary>
public interface IInstanceDaemonApp
{
/// <summary>
/// Number of instanced deamonapps
/// Number of instanced daemonapps
/// </summary>
int Count { get; }

Expand Down
38 changes: 22 additions & 16 deletions src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ public class NetDaemonHost : INetDaemonHost, IAsyncDisposable
private CancellationToken _cancelToken;

private CancellationTokenSource? _cancelTokenSource;
private bool _hasNetDaemonIntegration;

internal bool HasNetDaemonIntegration;

public IServiceProvider? ServiceProvider { get; }

Expand Down Expand Up @@ -232,7 +233,7 @@ public async ValueTask DisposeAsync()
Logger.LogTrace("Instance NetDaemonHost Disposed");
}

public void EnableApplicationDiscoveryService()
private void EnableApplicationDiscoveryService()
{
// For service call reload_apps we do just that... reload the fucking apps yay :)
ListenCompanionServiceCall("reload_apps", async (_) => await ReloadAllApps().ConfigureAwait(false));
Expand Down Expand Up @@ -532,7 +533,7 @@ internal async Task ConnectToHAIntegration()
var x = await _hassClient.GetApiCall<NetDaemonInfo>("netdaemon/info").ConfigureAwait(false);
if (x is not null)
{
_hasNetDaemonIntegration = true;
HasNetDaemonIntegration = true;
return;
}
}
Expand Down Expand Up @@ -586,12 +587,15 @@ public async Task SetDaemonStateAsync(int numberOfLoadedApps, int numberOfRunnin
{
_cancelToken.ThrowIfCancellationRequested();

await SetStateAsync(
"netdaemon.status",
await SetStateAndWaitForResponseAsync(
"sensor.netdaemon_status",
"Connected", // State will always be connected, otherwise state could not be set.
("number_of_loaded_apps", numberOfLoadedApps),
("number_of_running_apps", numberOfRunningApps),
("version", GetType().Assembly.GetName().Version?.ToString() ?? "N/A")).ConfigureAwait(false);
new
{
number_of_loaded_apps = numberOfLoadedApps,
number_of_running_apps = numberOfRunningApps,
version = GetType().Assembly.GetName().Version?.ToString() ?? "N/A",
}, false).ConfigureAwait(false);
}

public EntityState? SetState(string entityId, dynamic state, dynamic? attributes = null, bool waitForResponse = false)
Expand All @@ -606,12 +610,12 @@ public async Task SetDaemonStateAsync(int numberOfLoadedApps, int numberOfRunnin
}
else
{
return SetStateDynamicAsync(entityId, state, attributes, true).Result;
return SetStateAndWaitForResponseAsync(entityId, state, attributes, true).Result;
}
}

private readonly string[] _supportedDomains = new string[] { "binary_sensor", "sensor", "switch" };
public async Task<EntityState?> SetStateDynamicAsync(string entityId, dynamic state,
public async Task<EntityState?> SetStateAndWaitForResponseAsync(string entityId, dynamic state,
dynamic? attributes, bool waitForResponse)
{
_cancelToken.ThrowIfCancellationRequested();
Expand All @@ -623,11 +627,12 @@ public async Task SetDaemonStateAsync(int numberOfLoadedApps, int numberOfRunnin
try
{
// Use expando object as all other methods
if (_hasNetDaemonIntegration &&
if (HasNetDaemonIntegration &&
_supportedDomains.Contains(entityId.Split('.')[0]))
{
var service = InternalState.ContainsKey(entityId) ? "entity_update" : "entity_create";
// We have an integration that will help persist
await CallServiceAsync("netdaemon", "entity_create",
await CallServiceAsync("netdaemon", service,
new
{
entity_id = entityId,
Expand Down Expand Up @@ -689,10 +694,11 @@ public async Task SetDaemonStateAsync(int numberOfLoadedApps, int numberOfRunnin
{
// Use expando object as all other methods
dynamic dynAttributes = attributes.ToDynamic();
if (_hasNetDaemonIntegration)
if (HasNetDaemonIntegration)
{
var service = InternalState.ContainsKey(entityId) ? "entity_update" : "entity_create";
// We have an integration that will help persist
await CallServiceAsync("netdaemon", "entity_create",
await CallServiceAsync("netdaemon", service,
new
{
entity_id = entityId,
Expand Down Expand Up @@ -1281,7 +1287,7 @@ private async Task HandleAsyncSetState(CancellationToken cancellationToken)
(string entityId, dynamic state, dynamic? attributes)
= await _setStateMessageChannel.Reader.ReadAsync(cancellationToken).ConfigureAwait(false);

await SetStateDynamicAsync(entityId, state, attributes, false).ConfigureAwait(false);
await SetStateAndWaitForResponseAsync(entityId, state, attributes, false).ConfigureAwait(false);

hasLoggedError = false;
}
Expand Down Expand Up @@ -1536,6 +1542,6 @@ private async Task PostExternalEvent(ExternalEventBase ev)
await callbackTaskList.WhenAll(_cancelToken).ConfigureAwait(false);
}

public bool HomeAssistantHasNetDaemonIntegration() => _hasNetDaemonIntegration;
public bool HomeAssistantHasNetDaemonIntegration() => HasNetDaemonIntegration;
}
}
18 changes: 18 additions & 0 deletions src/DevelopmentApps/apps/DebugApp/DebugApp.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Reactive.Linq;
using System.Threading.Tasks;
using NetDaemon.Common;
using NetDaemon.Common.Reactive;

Expand Down Expand Up @@ -26,5 +28,21 @@ public void CallMeFromHass(dynamic data)
{
Log("A call from hass! {data}", data);
}

[HomeAssistantServiceCall]
public async Task Testing(dynamic data)
{
Log("Wait for a update");
try
{
Entity("input_select.who_cooks").StateChanges.Timeout(TimeSpan.FromSeconds(20)).Take(1).Wait();
Log("State changed as expected");
}
catch (System.Exception)
{
Log("We had timeout");
}
await Task.Delay(10);
}
}
}
20 changes: 14 additions & 6 deletions src/Fakes/NetDaemon.Fakes/HassClientMock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,24 @@ public HassClientMock()
Setup(x => x.SetState(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<object?>())).Returns<string, string, object>(
(entityId, state, attributes) =>
{
var fluentAttr = (FluentExpandoObject)attributes;
var attrib = new Dictionary<string, object>();
foreach (var attr in (IDictionary<string, object>)fluentAttr)
attrib[attr.Key] = attr.Value;
var fluentAttr = attributes.ToExpandoObject();
if (fluentAttr is not null)
{
var attrib = new Dictionary<string, object>();
foreach (var attr in (IDictionary<string, object>)fluentAttr)
attrib[attr.Key] = attr.Value;
return Task.FromResult(new HassState
{
EntityId = entityId,
State = state,
Attributes = attrib
});
}
return Task.FromResult(new HassState
{
EntityId = entityId,
State = state,
Attributes = attrib
Attributes = null
});
}
);
Expand Down
104 changes: 103 additions & 1 deletion tests/NetDaemon.Daemon.Tests/Daemon/NetDaemonHostTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
using Moq;
using System;
using System.Dynamic;
using System.Threading;
using System.Threading.Tasks;
using NetDaemon.Common;
using Xunit;
using NetDaemon.Daemon.Fakes;
using NetDaemon.Daemon.Tests.DaemonRunner.App;
using System.Diagnostics.CodeAnalysis;
using System.Collections.Generic;
using NetDaemon.Common.Reactive;

namespace NetDaemon.Daemon.Tests.Daemon
{
Expand Down Expand Up @@ -409,6 +410,92 @@ public async Task SetStateShouldCallCorrectFunction()
DefaultHassClientMock.Verify(n => n.SetState("sensor.any_sensor", "on", expObj));
}

[Fact]
public async Task SetStateWaitForResultShouldCallCorrectFunction()
{
var (dynObj, expObj) = GetDynamicObject(
("attr", "value")
);
var entity = await DefaultDaemonHost.SetStateAndWaitForResponseAsync("sensor.any_sensor", "on", dynObj, true).ConfigureAwait(false);

DefaultHassClientMock.Verify(n => n.SetState("sensor.any_sensor", "on", expObj));
}

[Fact]
public async Task SetStateDynamicNullReturnOfGetStateShouldCallCorrectFunction()
{
var (dynObj, expObj) = GetDynamicObject(
("attr", "value")
);
DefaultDaemonHost.HasNetDaemonIntegration = true;
DefaultHassClientMock.Setup(n => n.GetState(It.IsAny<string>())).Returns(Task.FromResult<HassState?>(null));
var entity = await DefaultDaemonHost.SetStateAndWaitForResponseAsync("sensor.any_sensor", "on", new { attr = "value" }, true).ConfigureAwait(false);

DefaultHassClientMock.Verify(n => n.CallService("netdaemon", "entity_create",
It.IsAny<object>(), true), Times.Once);

DefaultHassClientMock.Verify(n => n.GetState("sensor.any_sensor"));
Assert.Null(entity);
}

[Fact]
public async Task SetStateDynamicShouldCallCorrectFunction()
{
var (dynObj, expObj) = GetDynamicObject(
("attr", "value")
);
DefaultDaemonHost.HasNetDaemonIntegration = true;
DefaultHassClientMock.Setup(n => n.GetState(It.IsAny<string>())).Returns(Task.FromResult<HassState?>(new HassState()));
var entity = await DefaultDaemonHost.SetStateAndWaitForResponseAsync("sensor.any_sensor", "on", new { attr = "value" }, true).ConfigureAwait(false);

DefaultHassClientMock.Verify(n => n.CallService("netdaemon", "entity_create",
It.IsAny<object>(), true), Times.Once);

DefaultHassClientMock.Verify(n => n.GetState("sensor.any_sensor"));
Assert.NotNull(entity);
}

private class MyTestApp : NetDaemonRxApp { }
[Fact]
public void SortByDependecyTest()
{
// ARRANGE
var apps = new List<INetDaemonAppBase>
{
new MyTestApp()
{
Id = "test",
Dependencies = new List<string> { "dependent_app" },
},
new MyTestApp()
{
Id = "dependent_app",
Dependencies = new List<string>(),
}
};
// ACT
var sorted = NetDaemonHost.SortByDependency(apps);

// ASSERT
Assert.Equal(2, sorted.Count);
Assert.Equal("dependent_app", sorted[0].Id);
}
[Fact]
public async Task SetStateDynamicWithNoWaitShouldCallCorrectFunction()
{
var (dynObj, expObj) = GetDynamicObject(
("attr", "value")
);
DefaultDaemonHost.HasNetDaemonIntegration = true;
var entity = await DefaultDaemonHost.SetStateAndWaitForResponseAsync("sensor.any_sensor", "on", new { attr = "value" }, false).ConfigureAwait(false);

DefaultHassClientMock.Verify(n => n.CallService("netdaemon", "entity_create",
It.IsAny<object>(), false), Times.Once);

DefaultHassClientMock.Verify(n => n.GetState("sensor.any_sensor"), Times.Never);
Assert.Null(entity);
}

[Fact]
public async Task SetStateShouldReturnCorrectData()
{
Expand All @@ -420,6 +507,21 @@ public async Task SetStateShouldReturnCorrectData()
DefaultHassClientMock.Verify(n => n.SetState("sensor.any_sensor", "on", expObj));
}

[Fact]
public async Task GetServicesShouldCallCorrectFunctions()
{
var services = await DefaultDaemonHost.GetAllServices().ConfigureAwait(false);

DefaultHassClientMock.Verify(n => n.GetServices(), Times.Once);
}
[Fact]
public async Task SetDaemonStateAsyncShouldCallCorrectFunctions()
{
await DefaultDaemonHost.SetDaemonStateAsync(5, 2).ConfigureAwait(false);

DefaultHassClientMock.Verify(n => n.SetState("sensor.netdaemon_status", "Connected", It.IsAny<object>()), Times.Once);
}

[Fact]
public async Task DelayStateChangeShouldReturnTrue()
{
Expand Down

0 comments on commit d412b1a

Please sign in to comment.