mirror of
https://github.com/ckaczor/HomeMonitor.git
synced 2026-01-13 17:22:54 -05:00
Add power database support
This commit is contained in:
@@ -1,11 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace ChrisKaczor.HomeMonitor.Power.Service.Controllers
|
||||
{
|
||||
[Route("[controller]")]
|
||||
[ApiController]
|
||||
public class ReadingsController : ControllerBase
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
28
Power/Service/Controllers/StatusController.cs
Normal file
28
Power/Service/Controllers/StatusController.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using ChrisKaczor.HomeMonitor.Power.Service.Data;
|
||||
using ChrisKaczor.HomeMonitor.Power.Service.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ChrisKaczor.HomeMonitor.Power.Service.Controllers
|
||||
{
|
||||
[Route("[controller]")]
|
||||
[ApiController]
|
||||
public class StatusController : ControllerBase
|
||||
{
|
||||
private readonly Database _database;
|
||||
|
||||
public StatusController(Database database)
|
||||
{
|
||||
_database = database;
|
||||
}
|
||||
|
||||
[HttpGet("history-grouped")]
|
||||
public async Task<ActionResult<List<PowerStatusGrouped>>> GetHistoryGrouped(DateTimeOffset start, DateTimeOffset end, int bucketMinutes = 2)
|
||||
{
|
||||
return (await _database.GetStatusHistoryGrouped(start, end, bucketMinutes)).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
91
Power/Service/Data/Database.cs
Normal file
91
Power/Service/Data/Database.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using ChrisKaczor.HomeMonitor.Power.Service.Models;
|
||||
using Dapper;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ChrisKaczor.HomeMonitor.Power.Service.Data
|
||||
{
|
||||
public class Database
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public Database(IConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public void EnsureDatabase()
|
||||
{
|
||||
var connectionStringBuilder = new SqlConnectionStringBuilder
|
||||
{
|
||||
DataSource = $"{_configuration["Power:Database:Host"]},{_configuration["Power:Database:Port"]}",
|
||||
UserID = _configuration["Power:Database:User"],
|
||||
Password = _configuration["Power:Database:Password"],
|
||||
InitialCatalog = "master"
|
||||
};
|
||||
|
||||
using var connection = new SqlConnection(connectionStringBuilder.ConnectionString);
|
||||
|
||||
var command = new SqlCommand { Connection = connection };
|
||||
|
||||
connection.Open();
|
||||
|
||||
// Check to see if the database exists
|
||||
command.CommandText = $"SELECT CAST(1 as bit) from sys.databases WHERE name='{_configuration["Power:Database:Name"]}'";
|
||||
var databaseExists = (bool?)command.ExecuteScalar();
|
||||
|
||||
// Create database if needed
|
||||
if (!databaseExists.GetValueOrDefault(false))
|
||||
{
|
||||
command.CommandText = $"CREATE DATABASE {_configuration["Power:Database:Name"]}";
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
// Switch to the database now that we're sure it exists
|
||||
connection.ChangeDatabase(_configuration["Power:Database:Name"]);
|
||||
|
||||
var schema = ResourceReader.GetString("ChrisKaczor.HomeMonitor.Power.Service.Data.Resources.Schema.sql");
|
||||
|
||||
// Make sure the database is up to date
|
||||
command.CommandText = schema;
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
private SqlConnection CreateConnection()
|
||||
{
|
||||
var connectionStringBuilder = new SqlConnectionStringBuilder
|
||||
{
|
||||
DataSource = $"{_configuration["Power:Database:Host"]},{_configuration["Power:Database:Port"]}",
|
||||
UserID = _configuration["Power:Database:User"],
|
||||
Password = _configuration["Power:Database:Password"],
|
||||
InitialCatalog = _configuration["Power:Database:Name"]
|
||||
};
|
||||
|
||||
var connection = new SqlConnection(connectionStringBuilder.ConnectionString);
|
||||
connection.Open();
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
public void StorePowerData(PowerStatus powerStatus)
|
||||
{
|
||||
using var connection = CreateConnection();
|
||||
|
||||
var query = ResourceReader.GetString("ChrisKaczor.HomeMonitor.Power.Service.Data.Resources.CreateStatus.sql");
|
||||
|
||||
connection.Query(query, powerStatus);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<PowerStatusGrouped>> GetStatusHistoryGrouped(DateTimeOffset start, DateTimeOffset end, int bucketMinutes)
|
||||
{
|
||||
await using var connection = CreateConnection();
|
||||
|
||||
var query = ResourceReader.GetString("ChrisKaczor.HomeMonitor.Power.Service.Data.Resources.GetStatusHistoryGrouped.sql");
|
||||
|
||||
return await connection.QueryAsync<PowerStatusGrouped>(query, new { Start = start, End = end, BucketMinutes = bucketMinutes });
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Power/Service/Data/Resources/CreateStatus.sql
Normal file
2
Power/Service/Data/Resources/CreateStatus.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
INSERT INTO Status (Timestamp, Generation, Consumption)
|
||||
VALUES (@Timestamp, @Generation, @Consumption)
|
||||
14
Power/Service/Data/Resources/GetStatusHistoryGrouped.sql
Normal file
14
Power/Service/Data/Resources/GetStatusHistoryGrouped.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
SELECT Bucket,
|
||||
AVG(Generation) AS AverageGeneration,
|
||||
AVG(Consumption) AS AverageConsumption
|
||||
FROM (
|
||||
SELECT CAST(FORMAT(Timestamp, 'yyyy-MM-ddTHH:') +
|
||||
RIGHT('00' + CAST(DATEPART(MINUTE, Timestamp) / @BucketMinutes * @BucketMinutes AS VARCHAR), 2)
|
||||
+ ':00+00:00' AS DATETIMEOFFSET) AS Bucket,
|
||||
Generation,
|
||||
Consumption
|
||||
FROM Status
|
||||
WHERE Timestamp BETWEEN @Start AND @End
|
||||
) AS Data
|
||||
GROUP BY Bucket
|
||||
ORDER BY Bucket
|
||||
9
Power/Service/Data/Resources/Schema.sql
Normal file
9
Power/Service/Data/Resources/Schema.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
IF NOT EXISTS(SELECT 1 FROM sys.tables WHERE name = 'Status')
|
||||
CREATE TABLE Status
|
||||
(
|
||||
Timestamp datetimeoffset NOT NULL
|
||||
CONSTRAINT status_pk
|
||||
PRIMARY KEY,
|
||||
Generation int NOT NULL,
|
||||
Consumption int NOT NULL
|
||||
);
|
||||
@@ -1,10 +1,12 @@
|
||||
using JetBrains.Annotations;
|
||||
using System;
|
||||
|
||||
namespace ChrisKaczor.HomeMonitor.Power.Service.Models
|
||||
{
|
||||
[PublicAPI]
|
||||
public class PowerStatus
|
||||
{
|
||||
public DateTimeOffset Timestamp { get; set; } = DateTimeOffset.UtcNow;
|
||||
public long Generation { get; set; }
|
||||
public long Consumption { get; set; }
|
||||
}
|
||||
|
||||
13
Power/Service/Models/PowerStatusGrouped.cs
Normal file
13
Power/Service/Models/PowerStatusGrouped.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using JetBrains.Annotations;
|
||||
using System;
|
||||
|
||||
namespace ChrisKaczor.HomeMonitor.Power.Service.Models
|
||||
{
|
||||
[PublicAPI]
|
||||
public class PowerStatusGrouped
|
||||
{
|
||||
public DateTimeOffset Bucket { get; set; }
|
||||
public long AverageGeneration { get; set; }
|
||||
public long AverageConsumption { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using ChrisKaczor.HomeMonitor.Power.Service.Models;
|
||||
using ChrisKaczor.HomeMonitor.Power.Service.Data;
|
||||
using ChrisKaczor.HomeMonitor.Power.Service.Models;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
@@ -16,13 +17,15 @@ namespace ChrisKaczor.HomeMonitor.Power.Service
|
||||
public class PowerReader : IHostedService
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly Database _database;
|
||||
|
||||
private HubConnection _hubConnection;
|
||||
private Timer _readTimer;
|
||||
|
||||
public PowerReader(IConfiguration configuration)
|
||||
public PowerReader(IConfiguration configuration, Database database)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_database = database;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
@@ -54,6 +57,8 @@ namespace ChrisKaczor.HomeMonitor.Power.Service
|
||||
|
||||
var status = new PowerStatus { Generation = generation.RealPower, Consumption = consumption.RealPower };
|
||||
|
||||
_database.StorePowerData(status);
|
||||
|
||||
var json = JsonSerializer.Serialize(status);
|
||||
|
||||
Console.WriteLine(json);
|
||||
|
||||
22
Power/Service/ResourceReader.cs
Normal file
22
Power/Service/ResourceReader.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace ChrisKaczor.HomeMonitor.Power.Service
|
||||
{
|
||||
public static class ResourceReader
|
||||
{
|
||||
public static string GetString(string resourceName)
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
using var stream = assembly.GetManifestResourceStream(resourceName);
|
||||
|
||||
if (stream == null)
|
||||
throw new Exception($"Resource {resourceName} not found in {assembly.FullName}. Valid resources are: {string.Join(", ", assembly.GetManifestResourceNames())}.");
|
||||
|
||||
using var reader = new StreamReader(stream);
|
||||
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,22 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Data\Resources\CreateStatus.sql" />
|
||||
<None Remove="Data\Resources\GetStatusHistoryGrouped.sql" />
|
||||
<None Remove="Data\Resources\Schema.sql" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Data\Resources\CreateStatus.sql" />
|
||||
<EmbeddedResource Include="Data\Resources\GetStatusHistoryGrouped.sql" />
|
||||
<EmbeddedResource Include="Data\Resources\Schema.sql" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.0.30" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="1.0.19269.1" />
|
||||
<PackageReference Include="RestSharp" Version="106.6.10" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using ChrisKaczor.HomeMonitor.Power.Service.Data;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.ResponseCompression;
|
||||
@@ -12,6 +13,8 @@ namespace ChrisKaczor.HomeMonitor.Power.Service
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddTransient<Database>();
|
||||
|
||||
services.AddHostedService<PowerReader>();
|
||||
|
||||
services.Configure<GzipCompressionProviderOptions>(options =>
|
||||
@@ -35,6 +38,9 @@ namespace ChrisKaczor.HomeMonitor.Power.Service
|
||||
if (environment.IsDevelopment())
|
||||
applicationBuilder.UseDeveloperExceptionPage();
|
||||
|
||||
var database = applicationBuilder.ApplicationServices.GetService<Database>();
|
||||
database.EnsureDatabase();
|
||||
|
||||
applicationBuilder.UseCors("CorsPolicy");
|
||||
|
||||
applicationBuilder.UseResponseCompression();
|
||||
|
||||
Reference in New Issue
Block a user