-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #9 from timia2109/feature/prod-templates
Feature/prod templates
- Loading branch information
Showing
11 changed files
with
251 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; } = []; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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!; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,4 +5,4 @@ | |
main?version={{ date.to_string date.now "%s" }} | ||
{{ else }} | ||
{{ state | string.downcase }} | ||
{{ end }} | ||
{{ end }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |