add logging and running as a service

pull/9/head
sleevezipper 4 years ago
parent a5820a5911
commit 2590bd50b0

5
.gitignore vendored

@ -19,4 +19,7 @@ obj/
!.vscode/extensions.json
*.code-workspace
# End of https://www.toptal.com/developers/gitignore/api/vscode,dotnetcore
# End of https://www.toptal.com/developers/gitignore/api/vscode,dotnetcore
# ignore logs
logs/

@ -7,6 +7,7 @@ using Microsoft.Extensions.Logging;
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Client.Options;
using Serilog;
namespace hass_workstation_service.Communication
{
@ -39,8 +40,7 @@ namespace hass_workstation_service.Communication
this._mqttClient = factory.CreateMqttClient();
// connect to the broker
this._mqttClient.ConnectAsync(options);
var result = this._mqttClient.ConnectAsync(options).Result;
// configure what happens on disconnect
this._mqttClient.UseDisconnectedHandler(async e =>
{
@ -51,9 +51,9 @@ namespace hass_workstation_service.Communication
{
await this._mqttClient.ConnectAsync(options, CancellationToken.None);
}
catch
catch (Exception ex)
{
_logger.LogError("Reconnecting failed");
_logger.LogError(ex, "Reconnecting failed");
}
});
}

@ -2,10 +2,12 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.IO.IsolatedStorage;
using System.Reflection;
using System.Text.Json;
using hass_workstation_service.Communication;
using hass_workstation_service.Domain.Sensors;
using Microsoft.Extensions.Configuration;
using Serilog;
namespace hass_workstation_service.Data
{
@ -28,27 +30,55 @@ namespace hass_workstation_service.Data
public async void ReadSettings()
{
IsolatedStorageFileStream stream = this._fileStorage.OpenFile("configured-sensors.json", FileMode.OpenOrCreate);
List<ConfiguredSensor> sensors = await JsonSerializer.DeserializeAsync<List<ConfiguredSensor>>(stream);
String filePath = stream.GetType().GetField("m_FullPath",
BindingFlags.Instance | BindingFlags.NonPublic).GetValue(stream).ToString();
Console.WriteLine(filePath);
Log.Logger.Information($"reading configured sensors from: {filePath}");
List<ConfiguredSensor> sensors = new List<ConfiguredSensor>();
if (stream.Length > 0)
{
sensors = await JsonSerializer.DeserializeAsync<List<ConfiguredSensor>>(stream);
}
foreach (ConfiguredSensor configuredSensor in sensors)
{
AbstractSensor sensor;
#pragma warning disable IDE0066
#pragma warning disable IDE0066
switch (configuredSensor.Type)
{
case "UserNotificationStateSensor":
sensor = new UserNotificationStateSensor(_publisher, configuredSensor.Name, configuredSensor.Id);
break;
case "DummySensor":
sensor = new DummySensor(_publisher, configuredSensor.Name, configuredSensor.Id);
break;
default:
throw new InvalidOperationException("unsupported sensor type in config");
}
this.ConfiguredSensors.Add(sensor);
}
stream.Close();
}
public async void WriteSettings()
{
IsolatedStorageFileStream stream = this._fileStorage.OpenFile("configured-sensors.json", FileMode.OpenOrCreate);
Log.Logger.Information($"writing configured sensors to: {stream.Name}");
List<ConfiguredSensor> configuredSensorsToSave = new List<ConfiguredSensor>();
foreach (AbstractSensor sensor in this.ConfiguredSensors)
{
configuredSensorsToSave.Add(new ConfiguredSensor() { Id = sensor.Id, Name = sensor.Name, Type = sensor.GetType().Name });
}
await JsonSerializer.SerializeAsync(stream, configuredSensorsToSave);
stream.Close();
}
public void AddConfiguredSensor(AbstractSensor sensor)
{
this.ConfiguredSensors.Add(sensor);
WriteSettings();
}
}
}

@ -0,0 +1,42 @@
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using hass_workstation_service.Communication;
namespace hass_workstation_service.Domain.Sensors
{
public class DummySensor : AbstractSensor
{
private readonly Random _random;
public DummySensor(MqttPublisher publisher, string name = "Dummy")
{
this.Id = Guid.NewGuid();
this.Name = name;
this.Publisher = publisher;
this._random = new Random();
}
public DummySensor(MqttPublisher publisher, string name, Guid id)
{
this.Id = id;
this.Name = name;
this.Publisher = publisher;
this._random = new Random();
}
public override AutoDiscoveryConfigModel GetAutoDiscoveryConfig()
{
return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new AutoDiscoveryConfigModel()
{
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/sensor/{this.Name}/state"
});
}
public override string GetState()
{
return _random.Next(0, 100).ToString();
}
}
}

@ -9,7 +9,7 @@ namespace hass_workstation_service.Domain.Sensors
{
public UserNotificationStateSensor(MqttPublisher publisher, string name = "NotificationState")
{
this.Id = new Guid();
this.Id = Guid.NewGuid();
this.Name = name;
this.Publisher = publisher;
}

@ -4,28 +4,69 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using hass_workstation_service.Communication;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;
using MQTTnet.Client.Options;
using hass_workstation_service.Data;
using System.Linq;
using System.Diagnostics;
using System.Threading.Tasks;
using hass_workstation_service.ServiceHost;
using Serilog;
using Serilog.Formatting.Compact;
using System.IO.IsolatedStorage;
using System.Reflection;
using System.IO;
namespace hass_workstation_service
{
public class Program
{
public static void Main(string[] args)
public static async Task Main(string[] args)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.File(new RenderedCompactJsonFormatter(), "logs/log.ndjson")
.CreateLogger();
try
{
CreateHostBuilder(args).Build().Run();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var isService = !(Debugger.IsAttached || args.Contains("--console"));
if (isService)
{
await CreateHostBuilder(args).RunAsServiceAsync();
}
else
{
await CreateHostBuilder(args).RunConsoleAsync();
}
}
else
{
// we only support MS Windows for now
throw new NotImplementedException("Your platform is not yet supported");
}
}
else
catch (Exception ex)
{
// we only support MS Windows for now
throw new NotImplementedException("Your platform is not yet supported");
Log.Fatal(ex, "Application start-up failed");
}
finally
{
Log.CloseAndFlush();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog()
.ConfigureAppConfiguration((hostingContext, config) =>
{
config
.SetBasePath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))
.AddJsonFile("appsettings.json");
})
.ConfigureServices((hostContext, services) =>
{
IConfiguration configuration = hostContext.Configuration;

@ -0,0 +1,64 @@
using Microsoft.Extensions.Hosting;
using System;
using System.ServiceProcess;
using System.Threading;
using System.Threading.Tasks;
namespace hass_workstation_service.ServiceHost
{
public class ServiceBaseLifetime : ServiceBase, IHostLifetime
{
private IHostApplicationLifetime ApplicationLifetime { get; }
private readonly TaskCompletionSource<object> _delayStart = new TaskCompletionSource<object>();
public ServiceBaseLifetime(IHostApplicationLifetime applicationLifetime)
{
ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
}
public Task WaitForStartAsync(CancellationToken cancellationToken)
{
cancellationToken.Register(() => _delayStart.TrySetCanceled());
ApplicationLifetime.ApplicationStopping.Register(Stop);
new Thread(Run).Start(); // Otherwise this would block and prevent IHost.StartAsync from finishing.
return _delayStart.Task;
}
private void Run()
{
try
{
Run(this); // This blocks until the service is stopped.
_delayStart.TrySetException(new InvalidOperationException("Stopped without starting"));
}
catch (Exception ex)
{
_delayStart.TrySetException(ex);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
Stop();
return Task.CompletedTask;
}
// Called by base.Run when the service is ready to start.
protected override void OnStart(string[] args)
{
_delayStart.TrySetResult(null);
base.OnStart(args);
}
// Called by base.Stop. This may be called multiple times by service Stop, ApplicationStopping, and StopAsync.
// That's OK because StopApplication uses a CancellationTokenSource and prevents any recursion.
protected override void OnStop()
{
ApplicationLifetime.StopApplication();
base.OnStop();
}
}
}

@ -0,0 +1,21 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace hass_workstation_service.ServiceHost
{
public static class ServiceBaseLifetimeHostExtensions
{
public static IHostBuilder UseServiceBaseLifetime(this IHostBuilder hostBuilder)
{
return hostBuilder.ConfigureServices((hostContext, services) => services.AddSingleton<IHostLifetime, ServiceBaseLifetime>());
}
public static Task RunAsServiceAsync(this IHostBuilder hostBuilder, CancellationToken cancellationToken = default)
{
return hostBuilder.UseServiceBaseLifetime().Build().RunAsync(cancellationToken);
}
}
}

@ -31,10 +31,16 @@ namespace hass_workstation_service
{
while (!_mqttPublisher.IsConnected)
{
_logger.LogInformation("Connecting to MQTT broker...");
_logger.LogInformation($"Connecting to MQTT broker...");
await Task.Delay(2000);
}
_logger.LogInformation("Connected. Sending auto discovery messages.");
// if there are no configured sensors we add a dummy sensor
if (_configuredSensorsService.ConfiguredSensors.Count == 0)
{
_configuredSensorsService.AddConfiguredSensor(new DummySensor(_mqttPublisher));
_configuredSensorsService.AddConfiguredSensor(new UserNotificationStateSensor(_mqttPublisher));
}
foreach (AbstractSensor sensor in _configuredSensorsService.ConfiguredSensors)
{
await sensor.PublishAutoDiscoveryConfigAsync();

@ -1,11 +1,4 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"MqttBroker": {
"Host": "192.168.2.6",
"Username": "tester",

@ -1,9 +1,7 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
"MqttBroker": {
"Host": "192.168.2.6",
"Username": "tester",
"Password": "tester"
}
}

@ -9,5 +9,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.10" />
<PackageReference Include="MQTTnet" Version="3.0.13" />
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
<PackageReference Include="System.ServiceProcess.ServiceController" Version="5.0.0" />
</ItemGroup>
</Project>

Loading…
Cancel
Save