Skip to content

Commit

Permalink
Merge e9ff6f8 into d9c2ec8
Browse files Browse the repository at this point in the history
  • Loading branch information
helto4real committed Jun 30, 2020
2 parents d9c2ec8 + e9ff6f8 commit f78c12d
Show file tree
Hide file tree
Showing 10 changed files with 2,426 additions and 150 deletions.
207 changes: 106 additions & 101 deletions exampleapps/apps/_EntityExtensions.cs

Large diffs are not rendered by default.

2,175 changes: 2,175 additions & 0 deletions exampleapps/apps/_EntityExtensionsRx.cs

Large diffs are not rendered by default.

12 changes: 7 additions & 5 deletions exampleapps/apps/test2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@
using System;
using System.Reactive.Linq;
using System.Collections.Generic;
// using Netdaemon.Generated.Extensions;
public class BatteryManager : NetDaemonRxApp
using Netdaemon.Generated.Reactive;
public class BatteryManager : GeneratedAppBase //NetDaemonRxApp
// public class BatteryManager : NetDaemonRxApp
{
// private ISchedulerResult _schedulerResult;
private int numberOfRuns = 0;

public string? HelloWorldSecret { get; set; }
public override async Task InitializeAsync()
{
// Remote.Tvrummet.TurnOn(new {activity="TV"});
// Log(Remote.Tvrummet.State);
// Log(Remote.Tvrummet.Area);

// SetState("sensor.testing", "on", new { attributeobject = new { aobject = "hello" } });
RunEvery(TimeSpan.FromSeconds(5), () => SetAttribute("Time", DateTime.Now));
Log("Hello");
Log("Hello {name}", "Tomas");
// RunEvery(TimeSpan.FromSeconds(5), () => Log("Hello world!"));
// RunDaily("13:00:00", () => Log("Hello world!"));
// RunIn(TimeSpan.FromSeconds(5), () => Entity("light.tomas_rum").TurnOn());
Expand Down
2 changes: 1 addition & 1 deletion src/App/NetDaemon.App/Common/ExtensionMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public static dynamic ToDynamic(this (string name, object val)[] attributeNameVa
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
internal static ExpandoObject ToExpandoObject(this object obj)
public static ExpandoObject ToExpandoObject(this object obj)
{
// Null-check

Expand Down
34 changes: 20 additions & 14 deletions src/App/NetDaemon.App/Common/Reactive/RxEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,14 @@ public interface ISetState
/// </summary>
public class RxEntity : ICanTurnOnAndOff, ISetState, IObserve
{
private readonly INetDaemonReactive _daemonRxApp;
private readonly IEnumerable<string> _entityIds;
/// <summary>
/// The protected daemon app instance
/// </summary>
protected readonly INetDaemonReactive DaemonRxApp;
/// <summary>
/// Entity ids being handled by the RxEntity
/// </summary>
protected readonly IEnumerable<string> EntityIds;

/// <summary>
/// Constructor
Expand All @@ -74,16 +80,16 @@ public class RxEntity : ICanTurnOnAndOff, ISetState, IObserve
/// <param name="entityIds">Unique entity id:s</param>
public RxEntity(INetDaemonReactive daemon, IEnumerable<string> entityIds)
{
_daemonRxApp = daemon;
_entityIds = entityIds;
DaemonRxApp = daemon;
EntityIds = entityIds;
}

/// <inheritdoc/>
public IObservable<(EntityState Old, EntityState New)> StateAllChanges
{
get
{
return _daemonRxApp.StateAllChanges.Where(f => _entityIds.Contains(f.New.EntityId));
return DaemonRxApp.StateAllChanges.Where(f => EntityIds.Contains(f.New.EntityId));
}
}

Expand All @@ -92,17 +98,17 @@ public RxEntity(INetDaemonReactive daemon, IEnumerable<string> entityIds)
{
get
{
return _daemonRxApp.StateChanges.Where(f => _entityIds.Contains(f.New.EntityId) && f.New?.State != f.Old?.State);
return DaemonRxApp.StateChanges.Where(f => EntityIds.Contains(f.New.EntityId) && f.New?.State != f.Old?.State);
}
}

/// <inheritdoc/>
public void SetState(dynamic state, dynamic? attributes = null)
{
foreach (var entityId in _entityIds)
foreach (var entityId in EntityIds)
{
var domain = GetDomainFromEntity(entityId);
_daemonRxApp.SetState(entityId, state, attributes);
DaemonRxApp.SetState(entityId, state, attributes);
}
}

Expand Down Expand Up @@ -131,10 +137,10 @@ internal static string GetDomainFromEntity(string entity)
/// <param name="data">Data to provide</param>
public void CallService(string service, dynamic? data = null)
{
if (_entityIds is null || _entityIds is object && _entityIds.Count() == 0)
if (EntityIds is null || EntityIds is object && EntityIds.Count() == 0)
return;

foreach (var entityId in _entityIds!)
foreach (var entityId in EntityIds!)
{
var serviceData = new FluentExpandoObject();

Expand All @@ -154,13 +160,13 @@ public void CallService(string service, dynamic? data = null)

serviceData["entity_id"] = entityId;

_daemonRxApp.CallService(domain, service, serviceData);
DaemonRxApp.CallService(domain, service, serviceData);
}
}

private void CallServiceOnEntity(string service, dynamic? attributes = null)
{
if (_entityIds is null || _entityIds is object && _entityIds.Count() == 0)
if (EntityIds is null || EntityIds is object && EntityIds.Count() == 0)
return;

dynamic? data = null;
Expand All @@ -173,7 +179,7 @@ private void CallServiceOnEntity(string service, dynamic? attributes = null)
data = attributes;
}

foreach (var entityId in _entityIds!)
foreach (var entityId in EntityIds!)
{
var serviceData = new FluentExpandoObject();

Expand All @@ -187,7 +193,7 @@ private void CallServiceOnEntity(string service, dynamic? attributes = null)

serviceData["entity_id"] = entityId;

_daemonRxApp.CallService(domain, service, serviceData);
DaemonRxApp.CallService(domain, service, serviceData);
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,14 @@ public IHttpHandler Http
.AddConsole();
});


public async Task<IEnumerable<HassServiceDomain>> GetAllServices()
{
this._cancelToken.ThrowIfCancellationRequested();

return await _hassClient.GetServices();
}

public void CallService(string domain, string service, dynamic? data = null)
{
this._cancelToken.ThrowIfCancellationRequested();
Expand Down
128 changes: 103 additions & 25 deletions src/DaemonRunner/DaemonRunner/Service/App/CodeGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using JoySoftware.HomeAssistant.Client;
using JoySoftware.HomeAssistant.NetDaemon.Daemon.Config;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
Expand Down Expand Up @@ -64,6 +66,7 @@ public class CodeGenerator
// Add the classes implementing the specific entities
foreach (var domain in GetDomainsFromEntities(entities))
{

if (_FluentApiMapper.ContainsKey(domain))
{
var classDeclaration = $@"public partial class {domain.ToCamelCase()}Entities
Expand Down Expand Up @@ -100,11 +103,16 @@ public class CodeGenerator
return code.NormalizeWhitespace(indentation: " ", eol: "\n").ToFullString();
}

public string? GenerateCodeRx(string nameSpace, IEnumerable<string> entities)
public string? GenerateCodeRx(string nameSpace, IEnumerable<string> entities, IEnumerable<HassServiceDomain> services)
{
var code = SyntaxFactory.CompilationUnit();

// Add Usings statements
code = code.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System")));
code = code.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Collections.Generic")));
code = code.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Dynamic")));
code = code.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Linq")));
code = code.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("JoySoftware.HomeAssistant.NetDaemon.Common")));
code = code.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("JoySoftware.HomeAssistant.NetDaemon.Common.Reactive")));

// Add namespace
Expand All @@ -122,22 +130,93 @@ public class CodeGenerator

foreach (var domain in domains)
{
if (_FluentApiMapper.ContainsKey(domain))
var camelCaseDomain = domain.ToCamelCase();
var property = $@"public {camelCaseDomain}Entities {camelCaseDomain} => new {camelCaseDomain}Entities(this);";
var propertyDeclaration = CSharpSyntaxTree.ParseText(property).GetRoot().ChildNodes().OfType<PropertyDeclarationSyntax>().FirstOrDefault();
extensionClass = extensionClass.AddMembers(propertyDeclaration);
}
namespaceDeclaration = namespaceDeclaration.AddMembers(extensionClass);

foreach (var domain in GetDomainsFromEntities(entities))
{
var classDeclaration = $@"public partial class {domain.ToCamelCase()}Entity : RxEntity
{{
public string EntityId => EntityIds.First();
public string? Area => DaemonRxApp.State(EntityId)?.Area;
public dynamic? Attribute => DaemonRxApp.State(EntityId)?.Attribute;
public DateTime LastChanged => DaemonRxApp.State(EntityId).LastChanged;
public DateTime LastUpdated => DaemonRxApp.State(EntityId).LastUpdated;
public dynamic? State => DaemonRxApp.State(EntityId)?.State;
public {domain.ToCamelCase()}Entity(INetDaemonReactive daemon, IEnumerable<string> entityIds) : base(daemon, entityIds)
{{
}}
}}";
var entityClass = CSharpSyntaxTree.ParseText(classDeclaration).GetRoot().ChildNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();

// var entityIdProperty = $@"public string EntityId => EntityIds.First();";
// var entityIdPropertyDeclaration = CSharpSyntaxTree.ParseText(entityIdProperty).GetRoot().ChildNodes().OfType<PropertyDeclarationSyntax>().FirstOrDefault();
// entityClass = entityClass.AddMembers(entityIdPropertyDeclaration);

// var stateProperty = $@"public EntityState? State => DaemonRxApp.State(EntityId)?.State;";
// var statePropertyDeclaration = CSharpSyntaxTree.ParseText(stateProperty).GetRoot().ChildNodes().OfType<PropertyDeclarationSyntax>().FirstOrDefault();
// entityClass = entityClass.AddMembers(statePropertyDeclaration);

// They allready have default implementation
var skipServices = new string[] {"turn_on", "turn_off", "toggle"};

foreach (var s in services.Where(n => n.Domain == domain).SelectMany(n => n.Services))
{
var camelCaseDomain = domain.ToCamelCase();
var property = $@"public {camelCaseDomain}Entities {camelCaseDomain} => new {camelCaseDomain}Entities(this);";
var propertyDeclaration = CSharpSyntaxTree.ParseText(property).GetRoot().ChildNodes().OfType<PropertyDeclarationSyntax>().FirstOrDefault();
extensionClass = extensionClass.AddMembers(propertyDeclaration);
if (s.Service is null)
continue;

var name = s.Service[(s.Service.IndexOf(".") + 1)..];

if (Array.IndexOf(skipServices, name) >=0)
continue;

// Quick check to make sure the name is a valid C# identifier. Should really check to make
// sure it doesn't collide with a reserved keyword as well.
if (!char.IsLetter(name[0]) && (name[0] != '_'))
{
name = "s_" + name;
}
var hasEntityId = s.Fields.Count(c => c.Field == "entity_id") > 0? true : false;
var entityAssignmentStatement = hasEntityId? @"serviceData[""entity_id""] = EntityId;" : "";
var methodCode = $@"public void {name.ToCamelCase()}(dynamic? data=null)
{{
var serviceData = new FluentExpandoObject();
if (data is ExpandoObject)
{{
serviceData.CopyFrom(data);
}}
else if (data is object)
{{
var expObject = ((object)data).ToExpandoObject();
serviceData.CopyFrom(expObject);
}}
{entityAssignmentStatement}
DaemonRxApp.CallService(""{domain}"", ""{s.Service}"", serviceData);
}}
";
var methodDeclaration = CSharpSyntaxTree.ParseText(methodCode).GetRoot().ChildNodes().OfType<MethodDeclarationSyntax>().FirstOrDefault();
entityClass = entityClass.AddMembers(methodDeclaration);
}
namespaceDeclaration = namespaceDeclaration.AddMembers(entityClass);

}
namespaceDeclaration = namespaceDeclaration.AddMembers(extensionClass);

// Add the classes implementing the specific entities
foreach (var domain in GetDomainsFromEntities(entities))
{
if (_FluentApiMapper.ContainsKey(domain))
{
var classDeclaration = $@"public partial class {domain.ToCamelCase()}Entities

var classDeclaration = $@"public partial class {domain.ToCamelCase()}Entities
{{
private readonly NetDaemonRxApp _app;
Expand All @@ -146,24 +225,23 @@ public class CodeGenerator
_app = app;
}}
}}";
var entityClass = CSharpSyntaxTree.ParseText(classDeclaration).GetRoot().ChildNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();
foreach (var entity in entities.Where(n => n.StartsWith(domain)))
var entityClass = CSharpSyntaxTree.ParseText(classDeclaration).GetRoot().ChildNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();
foreach (var entity in entities.Where(n => n.StartsWith(domain)))
{

var name = entity[(entity.IndexOf(".") + 1)..];
// Quick check to make sure the name is a valid C# identifier. Should really check to make
// sure it doesn't collide with a reserved keyword as well.
if (!char.IsLetter(name[0]) && (name[0] != '_'))
{

var name = entity[(entity.IndexOf(".") + 1)..];
// Quick check to make sure the name is a valid C# identifier. Should really check to make
// sure it doesn't collide with a reserved keyword as well.
if (!char.IsLetter(name[0]) && (name[0] != '_'))
{
name = "e_" + name;
}

var propertyCode = $@"public RxEntity {name.ToCamelCase()} => _app.Entity(""{entity}"");";
var propDeclaration = CSharpSyntaxTree.ParseText(propertyCode).GetRoot().ChildNodes().OfType<PropertyDeclarationSyntax>().FirstOrDefault();
entityClass = entityClass.AddMembers(propDeclaration);
name = "e_" + name;
}
namespaceDeclaration = namespaceDeclaration.AddMembers(entityClass);

var propertyCode = $@"public {domain.ToCamelCase()}Entity {name.ToCamelCase()} => new {domain.ToCamelCase()}Entity(_app, new string[] {{""{entity}""}});";
var propDeclaration = CSharpSyntaxTree.ParseText(propertyCode).GetRoot().ChildNodes().OfType<PropertyDeclarationSyntax>().FirstOrDefault();
entityClass = entityClass.AddMembers(propDeclaration);
}
namespaceDeclaration = namespaceDeclaration.AddMembers(entityClass);
}

code = code.AddMembers(namespaceDeclaration);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ public static IEnumerable<MetadataReference> GetDefaultReferences()
MetadataReference.CreateFromFile(typeof(Console).Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.ComponentModel.DataAnnotations.DisplayAttribute).Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.Linq.Enumerable).Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.ComponentModel.INotifyPropertyChanged).Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.Linq.Expressions.DynamicExpression).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Microsoft.Extensions.Logging.Abstractions.NullLogger).Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly.Location),
Expand Down
7 changes: 4 additions & 3 deletions src/DaemonRunner/DaemonRunner/Service/RunnerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,12 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
var codeGen = new CodeGenerator();
var source = codeGen.GenerateCode("Netdaemon.Generated.Extensions",
_daemonHost.State.Select(n => n.EntityId).Distinct());

System.IO.File.WriteAllText(System.IO.Path.Combine(sourceFolder!, "_EntityExtensions.cs"), source);


var services = await _daemonHost.GetAllServices();
var sourceRx = codeGen.GenerateCodeRx("Netdaemon.Generated.Reactive",
_daemonHost.State.Select(n => n.EntityId).Distinct());
_daemonHost.State.Select(n => n.EntityId).Distinct(), services);

System.IO.File.WriteAllText(System.IO.Path.Combine(sourceFolder!, "_EntityExtensionsRx.cs"), sourceRx);
}
Expand Down
2 changes: 1 addition & 1 deletion tests/NetDaemon.Daemon.Tests/Reactive/RxAppTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public async Task NewEventShouldCallFunction()
public async Task NewEventMissingDataAttributeShouldReturnNull()
{
// ARRANGE
var daemonTask = await GetConnectedNetDaemonTask();
var daemonTask = await GetConnectedNetDaemonTask(200);
string? missingAttribute = "has initial value";

// ACT
Expand Down

0 comments on commit f78c12d

Please sign in to comment.