Skip to content

Commit

Permalink
Merge pull request #9 from timia2109/feature/prod-templates
Browse files Browse the repository at this point in the history
Feature/prod templates
  • Loading branch information
timia2109 authored Apr 15, 2024
2 parents f9db9ed + d625721 commit 257461f
Show file tree
Hide file tree
Showing 11 changed files with 251 additions and 42 deletions.
14 changes: 14 additions & 0 deletions HomeAssistant/Calendar/HassAppointmentStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using DisplayUtil.Template;
using Scriban.Runtime;

namespace DisplayUtil.HomeAssistant.Calendar;

public class HassAppointmentStore : ITemplateExtender
{
public HassEvent[] Appointments { get; set; } = [];

public void Enrich(ScriptObject scriptObject, EnrichScope scope)
{
scriptObject.Add("appointments", Appointments);
}
}
22 changes: 22 additions & 0 deletions HomeAssistant/Calendar/HassCalendarJob.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using DisplayUtil.Utils;

namespace DisplayUtil.HomeAssistant.Calendar;

/// <summary>
/// Job to sync HassAppointment
/// </summary>
internal class HassCalendarJob(
IServiceScopeFactory scopeFactory,
ILogger<HassCalendarJob> logger
) : TimedScopedService(scopeFactory, logger)
{
protected override TimeSpan InitTimeout => TimeSpan.FromSeconds(5);

protected override TimeSpan Delay => TimeSpan.FromHours(1);

protected override async Task TriggerAsync(IServiceProvider serviceProvider)
{
var worker = serviceProvider.GetRequiredService<HassCalendarWorker>();
await worker.RefreshAsync();
}
}
6 changes: 6 additions & 0 deletions HomeAssistant/Calendar/HassCalendarSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace DisplayUtil.HomeAssistant.Calendar;

public record HassCalendarSettings
{
public string[] CalendarEntities { get; init; } = [];
}
107 changes: 107 additions & 0 deletions HomeAssistant/Calendar/HassCalendarWorker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using System.Text.Json.Serialization;
using Microsoft.Extensions.Options;
using NetDaemon.Client;
using NetDaemon.Client.HomeAssistant.Model;

namespace DisplayUtil.HomeAssistant.Calendar;

public partial class HassCalendarWorker(
IHomeAssistantConnection client,
HassAppointmentStore store,
IOptions<HassCalendarSettings> options,
ILogger<HassCalendarWorker> logger
)
{
private readonly ILogger _logger = logger;

public async Task RefreshAsync()
{
var appointments = await FetchAsync(CancellationToken.None);
store.Appointments = appointments ?? [];
}

public async Task<HassEvent[]?> FetchAsync(CancellationToken cancellation)
{
LogFetch();

if (client is null)
{
LogNoConnection();
return null;
}

var command = new GetEventsPayload
{
ServiceData = new
{
end_date_time = DateTime.Now + TimeSpan.FromDays(7)
},
Target = new HassTarget
{
EntityIds = options.Value.CalendarEntities
}
};

try
{
var response = await client.SendCommandAndReturnResponseAsync
<GetEventsPayload, GetEventsResponse>(command, cancellation);

return response?.Events?.ToArray();
}
catch (Exception e)
{
LogError(e);
return null;
}
}

[LoggerMessage(LogLevel.Information, "Fetching Appointments")]
private partial void LogFetch();

[LoggerMessage(LogLevel.Warning, "No HASS Connection")]
private partial void LogNoConnection();

[LoggerMessage(LogLevel.Warning, "Error fetching appointments")]
private partial void LogError(Exception e);

}

internal record GetEventsPayload : CommandMessage
{
public GetEventsPayload()
{
Type = "call_service";
}

[JsonPropertyName("domain")] public string Domain => "calendar";

[JsonPropertyName("service")] public string Service => "get_events";

[JsonPropertyName("service_data")] public object? ServiceData { get; init; }

[JsonPropertyName("target")] public HassTarget? Target { get; init; }

[JsonPropertyName("return_response")] public bool ReturnResponse => true;
}

internal record GetEventsResponse : CommandMessage
{
[JsonPropertyName("response")] public Dictionary<string, HassEvents> Response { get; init; } = null!;

public IEnumerable<HassEvent> Events => Response.Values
.SelectMany(e => e.Event)
.OrderBy(e => e.Start);
}

public record HassEvents
{
[JsonPropertyName("events")] public HassEvent[] Event { get; init; } = null!;
}

public record HassEvent
{
[JsonPropertyName("start")] public DateTime Start { get; init; }
[JsonPropertyName("end")] public DateTime End { get; init; }
[JsonPropertyName("summary")] public string Summary { get; init; } = null!;
}
18 changes: 15 additions & 3 deletions HomeAssistant/HassExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using DisplayUtil.HomeAssistant.Calendar;
using DisplayUtil.Template;
using DisplayUtil.Utils;
using NetDaemon.Client.Extensions;
Expand All @@ -8,11 +9,11 @@ namespace DisplayUtil.HomeAssistant;

public static class HassExtension
{
private const string _section = "HomeAssistant";

public static IHostApplicationBuilder AddHassSupport(this IHostApplicationBuilder builder)
{
var settings = builder.ConfigureAndGet<HomeAssistantSettings>(
"HomeAssistant"
);
var settings = builder.ConfigureAndGet<HomeAssistantSettings>(_section);

if (settings is null
|| settings.Host is null
Expand All @@ -35,6 +36,17 @@ public static IHostApplicationBuilder AddHassSupport(this IHostApplicationBuilde
// Background Connection
builder.Services.AddHostedService<HassHostedService>();

var calendarSettings = builder.ConfigureAndGet<HassCalendarSettings>(
_section
);
if (calendarSettings?.CalendarEntities.Length == 0) return builder;

builder.Services
.AddSingleton<HassAppointmentStore>()
.AddSingleton<ITemplateExtender>(s => s.GetRequiredService<HassAppointmentStore>())
.AddScoped<HassCalendarWorker>()
.AddHostedService<HassCalendarJob>();

return builder;
}
}
44 changes: 12 additions & 32 deletions MqttExport/MqttExportJob.cs
Original file line number Diff line number Diff line change
@@ -1,44 +1,24 @@
using DisplayUtil.Utils;
using Microsoft.Extensions.Options;
using NetDaemon.HassModel;

namespace DisplayUtil.MqttExport;

/// <summary>
/// Job to publish the URI to MQTT
/// </summary>
public class MqttExportJob(
IServiceScopeFactory scopeFactory,
IOptions<MqttSettings> optionsSnapshot
) : IHostedService
ILogger<MqttExportJob> logger,
IOptions<MqttSettings> options
) : TimedScopedService(scopeFactory, logger)
{
private static readonly TimeSpan InitTimeout = TimeSpan.FromSeconds(5);
private CancellationTokenSource? _cancellationTokenSource;
protected override TimeSpan InitTimeout => TimeSpan.FromSeconds(5);
protected override TimeSpan Delay => options.Value.RefreshInterval!.Value;

private async Task RunAsync(CancellationToken cancellation)
protected override async Task TriggerAsync(IServiceProvider serviceProvider)
{
await Task.Delay(InitTimeout);

while (!cancellation.IsCancellationRequested)
{
await using (var scope = scopeFactory.CreateAsyncScope())
{
var renderer = scope.ServiceProvider
.GetRequiredService<MqttUrlRenderer>();
await renderer.RenderUrlAndPublish();
}

await Task.Delay(optionsSnapshot.Value.RefreshInterval!.Value,
cancellation);
}
}

public Task StartAsync(CancellationToken cancellationToken)
{
_cancellationTokenSource = new CancellationTokenSource();
_ = RunAsync(_cancellationTokenSource.Token);
return Task.CompletedTask;
}

public Task StopAsync(CancellationToken cancellationToken)
{
_cancellationTokenSource?.Cancel();
return Task.CompletedTask;
var renderer = serviceProvider.GetRequiredService<MqttUrlRenderer>();
await renderer.RenderUrlAndPublish();
}
}
5 changes: 1 addition & 4 deletions MqttExport/MqttUrlRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,8 @@ public async Task<Uri> GetMqttTemplateUriAsync()
? providerId
: providerId[0..query];

var uriBuilder = new UriBuilder
var uriBuilder = new UriBuilder(settings.ServerHostName!)
{
Port = 80,
Scheme = "http",
Host = settings.ServerHostName,
Path = EspUtilitiesInitExtension.CompressedImageRoute
};

Expand Down
2 changes: 1 addition & 1 deletion Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:7133;http://localhost:5296",
"applicationUrl": "https://localhost:7133;http://0.0.0.0:5296",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
Expand Down
2 changes: 1 addition & 1 deletion Resources/screens/_media_player.sbntxt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
end
}}
<VBox Gap="2">
<Defaults TextSize="30" IconHeight="30" />

<Border Y="2" />
<Padding All="2" />

Expand Down
2 changes: 1 addition & 1 deletion Resources/screens/export_screen.sbntxt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
main?version={{ date.to_string date.now "%s" }}
{{ else }}
{{ state | string.downcase }}
{{ end }}
{{ end }}
71 changes: 71 additions & 0 deletions Utils/TimedScopedService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@

using System.Diagnostics;

namespace DisplayUtil.Utils;

/// <summary>
/// Helper class to handle scheduled background tasks
/// </summary>
public abstract partial class TimedScopedService(
IServiceScopeFactory scopeFactory,
ILogger logger
) : IHostedService
{
private readonly ILogger _logger = logger;
private CancellationTokenSource _cancellationTokenSource = new();

protected abstract TimeSpan InitTimeout { get; }
protected abstract TimeSpan Delay { get; }
protected CancellationToken CancellationToken => _cancellationTokenSource.Token;

protected abstract Task TriggerAsync(IServiceProvider serviceProvider);

private async Task RunJobAsync()
{
LogTrigger();
await using var scope = scopeFactory.CreateAsyncScope();
var stopwatch = Stopwatch.StartNew();
try
{
await TriggerAsync(scope.ServiceProvider);
stopwatch.Stop();
LogTriggered(stopwatch.ElapsedMilliseconds);
}
catch (Exception ex)
{
LogError(ex);
}
}

private async Task RunAsync(CancellationToken cancellationToken)
{
await Task.Delay(InitTimeout, cancellationToken);

while (!cancellationToken.IsCancellationRequested)
{
await RunJobAsync();
await Task.Delay(Delay, cancellationToken);
}
}

public Task StartAsync(CancellationToken cancellationToken)
{
_ = RunAsync(_cancellationTokenSource.Token);
return Task.CompletedTask;
}

public Task StopAsync(CancellationToken cancellationToken)
{
_cancellationTokenSource?.Cancel();
return Task.CompletedTask;
}

[LoggerMessage(LogLevel.Information, "Trigger task")]
private partial void LogTrigger();

[LoggerMessage(LogLevel.Information, "Successfully triggered in {ms}ms")]
private partial void LogTriggered(long ms);

[LoggerMessage(LogLevel.Error, "Error on trigger")]
private partial void LogError(Exception ex);
}

0 comments on commit 257461f

Please sign in to comment.