From a52fbd028209ffcdca1fa3e3ee35888261715101 Mon Sep 17 00:00:00 2001 From: Chris Kaczor Date: Mon, 14 Jun 2021 15:51:47 -0400 Subject: [PATCH] Convert serial reader from console to web API --- .../Controllers/HealthController.cs | 27 +++ Weather/SerialReader/Program.cs | 165 ++--------------- .../Properties/launchSettings.json | 3 +- Weather/SerialReader/SerialReader.cs | 170 ++++++++++++++++++ Weather/SerialReader/SerialReader.csproj | 4 +- Weather/SerialReader/SerialReader.csproj.user | 5 +- Weather/SerialReader/Startup.cs | 34 ++++ Weather/Weather.sln | 2 +- 8 files changed, 251 insertions(+), 159 deletions(-) create mode 100644 Weather/SerialReader/Controllers/HealthController.cs create mode 100644 Weather/SerialReader/SerialReader.cs create mode 100644 Weather/SerialReader/Startup.cs diff --git a/Weather/SerialReader/Controllers/HealthController.cs b/Weather/SerialReader/Controllers/HealthController.cs new file mode 100644 index 0000000..299481e --- /dev/null +++ b/Weather/SerialReader/Controllers/HealthController.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Mvc; +using System; + +namespace ChrisKaczor.HomeMonitor.Weather.SerialReader.Controllers +{ + [Route("[controller]")] + [ApiController] + public class HealthController : ControllerBase + { + private readonly TimeSpan _checkTimeSpan = TimeSpan.FromSeconds(5); + + [HttpGet("ready")] + public IActionResult Ready() + { + return SerialReader.BoardStarted ? Ok() : Conflict(); + } + + [HttpGet("health")] + public IActionResult Health() + { + var lastReading = SerialReader.LastReading; + var timeSinceLastReading = DateTimeOffset.UtcNow - lastReading; + + return timeSinceLastReading <= _checkTimeSpan ? Ok(lastReading) : BadRequest(lastReading); + } + } +} diff --git a/Weather/SerialReader/Program.cs b/Weather/SerialReader/Program.cs index 940b1a4..f9e9641 100644 --- a/Weather/SerialReader/Program.cs +++ b/Weather/SerialReader/Program.cs @@ -1,163 +1,20 @@ -using ChrisKaczor.HomeMonitor.Weather.Models; -using Microsoft.Extensions.Configuration; -using Newtonsoft.Json; -using RabbitMQ.Client; -using System; -using System.IO.Ports; -using System.Linq; -using System.Text; -using System.Threading; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; namespace ChrisKaczor.HomeMonitor.Weather.SerialReader { - internal static class Program + public static class Program { - private static IConfiguration _configuration; - private static bool _boardStarting; - - private static readonly CancellationTokenSource CancellationTokenSource = new CancellationTokenSource(); - - private static void Main() + public static void Main(string[] args) { - WriteLog("Starting"); - - Console.CancelKeyPress += OnCancelKeyPress; - - _configuration = new ConfigurationBuilder() - .AddJsonFile("appsettings.json", false, false) - .AddEnvironmentVariables() - .Build(); - - var baudRate = int.Parse(_configuration["Weather:Port:BaudRate"]); - - while (!CancellationTokenSource.Token.IsCancellationRequested) - { - try - { - WriteLog("Starting main loop"); - - Thread.Sleep(1000); - - var port = GetPort(); - - if (port == null) - continue; - - using var serialPort = new SerialPort(port, baudRate) { NewLine = "\r\n" }; - - WriteLog("Opening serial port"); - - serialPort.Open(); - - _boardStarting = false; - - var factory = new ConnectionFactory - { - HostName = _configuration["Weather:Queue:Host"], - UserName = _configuration["Weather:Queue:User"], - Password = _configuration["Weather:Queue:Password"] - }; - - WriteLog("Connecting to queue server"); - - using var connection = factory.CreateConnection(); - using var model = connection.CreateModel(); - - WriteLog("Declaring queue"); - - model.QueueDeclare(_configuration["Weather:Queue:Name"], true, false, false, null); - - WriteLog("Starting serial read loop"); - - ReadSerial(serialPort, model); - } - catch (Exception exception) - { - WriteLog($"Exception: {exception}"); - } - } - - WriteLog("Exiting"); + CreateHostBuilder(args).Build().Run(); } - private static void ReadSerial(SerialPort serialPort, IModel model) - { - while (!CancellationTokenSource.Token.IsCancellationRequested) - { - try + private static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => { - var message = serialPort.ReadLine(); - - if (!_boardStarting) - { - _boardStarting = message.Contains("Board starting"); - - if (_boardStarting) - WriteLog("Board starting"); - } - - if (!_boardStarting) - { - WriteLog($"Message received but board not starting: {message}"); - continue; - } - - WriteLog($"Message received: {message}"); - - var weatherMessage = WeatherMessage.Parse(message); - - var messageString = JsonConvert.SerializeObject(weatherMessage); - - var body = Encoding.UTF8.GetBytes(messageString); - - var properties = model.CreateBasicProperties(); - - properties.Persistent = true; - - model.BasicPublish(string.Empty, _configuration["Weather:Queue:Name"], properties, body); - } - catch (TimeoutException) - { - WriteLog("Serial port read timeout"); - } - } - } - - private static void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args) - { - args.Cancel = true; - CancellationTokenSource.Cancel(); - } - - private static string GetPort() - { - var portPrefix = _configuration["Weather:Port:Prefix"]; - - while (!CancellationTokenSource.Token.IsCancellationRequested) - { - WriteLog($"Checking for port starting with: {portPrefix}"); - - var ports = SerialPort.GetPortNames(); - - var port = Array.Find(ports, p => p.StartsWith(portPrefix)); - - if (port != null) - { - WriteLog($"Port found: {port}"); - return port; - } - - WriteLog("Port not found - waiting"); - - Thread.Sleep(1000); - } - - return null; - } - - private static void WriteLog(string message) - { - Console.WriteLine(message); - } + webBuilder.UseStartup(); + }); } -} \ No newline at end of file +} diff --git a/Weather/SerialReader/Properties/launchSettings.json b/Weather/SerialReader/Properties/launchSettings.json index 2c4feb9..041e8a7 100644 --- a/Weather/SerialReader/Properties/launchSettings.json +++ b/Weather/SerialReader/Properties/launchSettings.json @@ -1,11 +1,12 @@ { + "$schema": "http://json.schemastore.org/launchsettings.json", "profiles": { "SerialReader": { "commandName": "Project", - "launchBrowser": false, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, + "dotnetRunMessages": "true", "applicationUrl": "http://localhost:62648/" } } diff --git a/Weather/SerialReader/SerialReader.cs b/Weather/SerialReader/SerialReader.cs new file mode 100644 index 0000000..4815f92 --- /dev/null +++ b/Weather/SerialReader/SerialReader.cs @@ -0,0 +1,170 @@ +using ChrisKaczor.HomeMonitor.Weather.Models; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Newtonsoft.Json; +using RabbitMQ.Client; +using System; +using System.IO.Ports; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace ChrisKaczor.HomeMonitor.Weather.SerialReader +{ + public class SerialReader : IHostedService + { + private readonly IConfiguration _configuration; + + public static bool BoardStarted { get; private set; } + public static DateTimeOffset LastReading { get; private set; } + + private CancellationToken _cancellationToken; + + public SerialReader(IConfiguration configuration) + { + _configuration = configuration; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _cancellationToken = cancellationToken; + + Task.Run(Execute, cancellationToken); + + return Task.CompletedTask; + } + + private void Execute() + { + var baudRate = int.Parse(_configuration["Weather:Port:BaudRate"]); + + while (!_cancellationToken.IsCancellationRequested) + { + try + { + WriteLog("Starting main loop"); + + Thread.Sleep(1000); + + var port = GetPort(); + + if (port == null) + continue; + + using var serialPort = new SerialPort(port, baudRate) { NewLine = "\r\n" }; + + WriteLog("Opening serial port"); + + serialPort.Open(); + + BoardStarted = false; + + var factory = new ConnectionFactory + { + HostName = _configuration["Weather:Queue:Host"], + UserName = _configuration["Weather:Queue:User"], + Password = _configuration["Weather:Queue:Password"] + }; + + WriteLog("Connecting to queue server"); + + using var connection = factory.CreateConnection(); + using var model = connection.CreateModel(); + + WriteLog("Declaring queue"); + + model.QueueDeclare(_configuration["Weather:Queue:Name"], true, false, false, null); + + WriteLog("Starting serial read loop"); + + ReadSerial(serialPort, model); + } + catch (Exception exception) + { + WriteLog($"Exception: {exception}"); + } + } + } + + private void ReadSerial(SerialPort serialPort, IModel model) + { + while (!_cancellationToken.IsCancellationRequested) + { + try + { + var message = serialPort.ReadLine(); + + if (!BoardStarted) + { + BoardStarted = message.Contains("Board started"); + + if (BoardStarted) + WriteLog("Board started"); + } + + if (!BoardStarted) + { + WriteLog($"Message received but board not started: {message}"); + continue; + } + + LastReading = DateTimeOffset.UtcNow; + + WriteLog($"Message received: {message}"); + + var weatherMessage = WeatherMessage.Parse(message); + + var messageString = JsonConvert.SerializeObject(weatherMessage); + + var body = Encoding.UTF8.GetBytes(messageString); + + var properties = model.CreateBasicProperties(); + + properties.Persistent = true; + + model.BasicPublish(string.Empty, _configuration["Weather:Queue:Name"], properties, body); + } + catch (TimeoutException) + { + WriteLog("Serial port read timeout"); + } + } + } + + private string GetPort() + { + var portPrefix = _configuration["Weather:Port:Prefix"]; + + while (!_cancellationToken.IsCancellationRequested) + { + WriteLog($"Checking for port starting with: {portPrefix}"); + + var ports = SerialPort.GetPortNames(); + + var port = Array.Find(ports, p => p.StartsWith(portPrefix)); + + if (port != null) + { + WriteLog($"Port found: {port}"); + return port; + } + + WriteLog("Port not found - waiting"); + + Thread.Sleep(1000); + } + + return null; + } + + private static void WriteLog(string message) + { + Console.WriteLine(message); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +} diff --git a/Weather/SerialReader/SerialReader.csproj b/Weather/SerialReader/SerialReader.csproj index 5036310..bea55d8 100644 --- a/Weather/SerialReader/SerialReader.csproj +++ b/Weather/SerialReader/SerialReader.csproj @@ -1,4 +1,4 @@ - + Exe @@ -22,4 +22,4 @@ - + \ No newline at end of file diff --git a/Weather/SerialReader/SerialReader.csproj.user b/Weather/SerialReader/SerialReader.csproj.user index cff74a9..b6b25d3 100644 --- a/Weather/SerialReader/SerialReader.csproj.user +++ b/Weather/SerialReader/SerialReader.csproj.user @@ -1,6 +1,9 @@  + + ProjectDebugger + - IIS Express + SerialReader \ No newline at end of file diff --git a/Weather/SerialReader/Startup.cs b/Weather/SerialReader/Startup.cs new file mode 100644 index 0000000..b37733e --- /dev/null +++ b/Weather/SerialReader/Startup.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace ChrisKaczor.HomeMonitor.Weather.SerialReader +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + + services.AddHostedService(); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} diff --git a/Weather/Weather.sln b/Weather/Weather.sln index 3885ce5..83e76f3 100644 --- a/Weather/Weather.sln +++ b/Weather/Weather.sln @@ -9,7 +9,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models", "Models\Models.csp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Service", "Service\Service.csproj", "{914B9DB9-3BCD-4B55-8289-2E59D6CA96BA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModelsTests", "ModelsTests\ModelsTests.csproj", "{F83A3E8C-3A03-4264-B136-1FBE569A9411}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ModelsTests", "ModelsTests\ModelsTests.csproj", "{F83A3E8C-3A03-4264-B136-1FBE569A9411}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution