Initial framework of the power service

This commit is contained in:
2019-10-13 09:18:57 -04:00
parent 5c3901951e
commit 43ae1061f8
16 changed files with 565 additions and 0 deletions

33
Power/Power.sln Normal file
View File

@@ -0,0 +1,33 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28705.295
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Service", "Service\Service.csproj", "{914B9DB9-3BCD-4B55-8289-2E59D6CA96BA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{63142748-213D-4BD3-9A15-8E5C405718B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{63142748-213D-4BD3-9A15-8E5C405718B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{63142748-213D-4BD3-9A15-8E5C405718B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{63142748-213D-4BD3-9A15-8E5C405718B4}.Release|Any CPU.Build.0 = Release|Any CPU
{39E5DE26-CD8D-47AF-AB94-6ACB0AF24EA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{39E5DE26-CD8D-47AF-AB94-6ACB0AF24EA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{39E5DE26-CD8D-47AF-AB94-6ACB0AF24EA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{39E5DE26-CD8D-47AF-AB94-6ACB0AF24EA1}.Release|Any CPU.Build.0 = Release|Any CPU
{914B9DB9-3BCD-4B55-8289-2E59D6CA96BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{914B9DB9-3BCD-4B55-8289-2E59D6CA96BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{914B9DB9-3BCD-4B55-8289-2E59D6CA96BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{914B9DB9-3BCD-4B55-8289-2E59D6CA96BA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7E56B149-7297-42A1-9607-6B611D7EB09B}
EndGlobalSection
EndGlobal

30
Power/Service/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,30 @@
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (remote console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "publish",
"program": "dotnet",
"args": [
"/home/ckaczor/Power/Service/Power.Service.dll"
],
"cwd": "/home/ckaczor/Power/Service",
"stopAtEntry": false,
"console": "internalConsole",
"pipeTransport": {
"pipeCwd": "${workspaceFolder}",
"pipeProgram": "C:\\Program Files (x86)\\PuTTY\\PLINK.EXE",
"pipeArgs": [
"root@172.23.10.6"
],
"debuggerPath": "/home/ckaczor/vsdbg/vsdbg"
},
"internalConsoleOptions": "openOnSessionStart"
}
]
}

35
Power/Service/.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,35 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Service.csproj"
],
"problemMatcher": "$msCompile",
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "publish",
"type": "shell",
"presentation": {
"reveal": "always",
"panel": "shared"
},
"options": {
"cwd": "${workspaceFolder}"
},
"windows": {
"command": "${cwd}\\publish.bat"
},
"problemMatcher": [],
"group": "build"
}
]
}

View File

@@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Mvc;
namespace ChrisKaczor.HomeMonitor.Power.Service.Controllers
{
[Route("[controller]")]
[ApiController]
public class ReadingsController : ControllerBase
{
}
}

16
Power/Service/Dockerfile Normal file
View File

@@ -0,0 +1,16 @@
FROM mcr.microsoft.com/dotnet/core/aspnet:3.0.0-preview7-buster-slim AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/core/sdk:3.0.100-preview7-buster AS build
WORKDIR /src
COPY ["./Service.csproj", "./"]
RUN dotnet restore "Service.csproj"
COPY . .
WORKDIR "/src"
RUN dotnet publish "Service.csproj" -c Release -o /app
FROM base AS final
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "ChrisKaczor.HomeMonitor.Power.Service.dll"]

View File

@@ -0,0 +1,30 @@
using JetBrains.Annotations;
using System.Text.Json.Serialization;
namespace ChrisKaczor.HomeMonitor.Power.Service.Models
{
[UsedImplicitly]
public class PowerChannel
{
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("ch")]
public long ChannelNumber { get; set; }
[JsonPropertyName("eImp_Ws")]
public long ImportedEnergy { get; set; }
[JsonPropertyName("eExp_Ws")]
public long ExportedEnergy { get; set; }
[JsonPropertyName("p_W")]
public long RealPower { get; set; }
[JsonPropertyName("q_VAR")]
public long ReactivePower { get; set; }
[JsonPropertyName("v_V")]
public double Voltage { get; set; }
}
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace ChrisKaczor.HomeMonitor.Power.Service.Models
{
public class PowerSample
{
[JsonPropertyName("sensorId")]
public string SensorId { get; set; }
[JsonPropertyName("timestamp")]
public DateTimeOffset Timestamp { get; set; }
[JsonPropertyName("channels")]
public PowerChannel[] Channels { get; set; }
[JsonPropertyName("cts")]
public Dictionary<string, double>[] CurrentTransformers { get; set; }
}
}

View File

@@ -0,0 +1,81 @@
using ChrisKaczor.HomeMonitor.Power.Service.Models;
using JetBrains.Annotations;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using RestSharp;
using System;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace ChrisKaczor.HomeMonitor.Power.Service
{
[UsedImplicitly]
public class PowerReader : IHostedService
{
private readonly IConfiguration _configuration;
private HubConnection _hubConnection;
private Timer _readTimer;
public PowerReader(IConfiguration configuration)
{
_configuration = configuration;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_readTimer = new Timer(OnTimer, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
if (!string.IsNullOrEmpty(_configuration["Hub:Power"]))
_hubConnection = new HubConnectionBuilder().WithUrl(_configuration["Hub:Power"]).Build();
return Task.CompletedTask;
}
private void OnTimer(object state)
{
var client = new RestClient(_configuration["Power:Host"]);
var request = new RestRequest("current-sample", Method.GET);
request.AddHeader("Authorization", _configuration["Power:AuthorizationHeader"]);
var response = client.Execute(request);
var sample = JsonSerializer.Deserialize<PowerSample>(response.Content);
Console.WriteLine(sample.Channels[2].Type + " " + sample.Channels[2].RealPower);
Console.WriteLine(sample.Channels[3].Type + " " + sample.Channels[3].RealPower);
if (_hubConnection == null)
return;
try
{
if (_hubConnection.State == HubConnectionState.Disconnected)
_hubConnection.StartAsync().Wait();
_hubConnection.InvokeAsync("SendLatestSample", response.Content).Wait();
}
catch (Exception exception)
{
WriteLog($"Hub exception: {exception}");
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_readTimer.Dispose();
_hubConnection?.StopAsync(cancellationToken).Wait(cancellationToken);
return Task.CompletedTask;
}
private static void WriteLog(string message)
{
Console.WriteLine(message);
}
}
}

22
Power/Service/Program.cs Normal file
View File

@@ -0,0 +1,22 @@
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
namespace ChrisKaczor.HomeMonitor.Power.Service
{
public static class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
private static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
return WebHost.CreateDefaultBuilder(args).ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddEnvironmentVariables();
}).UseStartup<Startup>();
}
}
}

View File

@@ -0,0 +1,13 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"Service": {
"commandName": "Project",
"launchUrl": "values",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:5001"
}
}
}

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage>
<TargetFramework>netcoreapp3.0</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
<AssemblyName>ChrisKaczor.HomeMonitor.Power.Service</AssemblyName>
<RootNamespace>ChrisKaczor.HomeMonitor.Power.Service</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="3.0.0" />
<PackageReference Include="RestSharp" Version="106.6.10" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup>
<ActiveDebugProfile>Service</ActiveDebugProfile>
</PropertyGroup>
</Project>

50
Power/Service/Startup.cs Normal file
View File

@@ -0,0 +1,50 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.IO.Compression;
namespace ChrisKaczor.HomeMonitor.Power.Service
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddHostedService<PowerReader>();
services.Configure<GzipCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Optimal;
});
services.AddResponseCompression(options =>
{
options.Providers.Add<GzipCompressionProvider>();
options.EnableForHttps = true;
});
services.AddCors(o => o.AddPolicy("CorsPolicy", builder => builder.AllowAnyMethod().AllowAnyHeader().AllowCredentials().WithOrigins("http://localhost:4200")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
}
public void Configure(IApplicationBuilder applicationBuilder, IWebHostEnvironment environment)
{
if (environment.IsDevelopment())
applicationBuilder.UseDeveloperExceptionPage();
applicationBuilder.UseCors("CorsPolicy");
applicationBuilder.UseResponseCompression();
applicationBuilder.UseRouting();
applicationBuilder.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
}
}

View File

@@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"Hub": {
"Power": "http://hub-server/power"
}
}

View File

@@ -0,0 +1,43 @@
name: $(Rev:r)
trigger:
batch: 'true'
branches:
include:
- master
paths:
include:
- Power/Service
pool:
name: Hosted Ubuntu 1604
steps:
- task: Docker@0
displayName: 'Build an image'
inputs:
containerregistrytype: 'Container Registry'
dockerRegistryConnection: 'Docker Hub'
dockerFile: 'Power/Service/Dockerfile'
imageName: 'ckaczor/home-monitor-power-service:$(Build.BuildNumber)'
includeLatestTag: true
- task: Docker@0
displayName: 'Push an image'
inputs:
containerregistrytype: 'Container Registry'
dockerRegistryConnection: 'Docker Hub'
action: 'Push an image'
imageName: 'ckaczor/home-monitor-power-service:$(Build.BuildNumber)'
includeLatestTag: true
- task: Bash@3
inputs:
targetType: 'inline'
script: 'sed -i s/#BUILD_BUILDNUMBER#/$BUILD_BUILDNUMBER/ Power/Service/deploy/manifest.yaml'
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: 'Power/Service/deploy/manifest.yaml'
ArtifactName: 'Manifest'
publishLocation: 'Container'

View File

@@ -0,0 +1,144 @@
---
kind: StatefulSet
apiVersion: apps/v1
metadata:
name: power-database
namespace: home-monitor
labels:
app: power-database
spec:
replicas: 1
selector:
matchLabels:
app: power-database
serviceName: power-database
template:
metadata:
labels:
app: power-database
spec:
containers:
- name: power-database
image: mcr.microsoft.com/mssql/server
terminationMessagePath: "/dev/termination-log"
terminationMessagePolicy: File
imagePullPolicy: IfNotPresent
env:
- name: SA_PASSWORD
valueFrom:
secretKeyRef:
name: power-database-credentials
key: password
- name: ACCEPT_EULA
value: "Y"
- name: MSSQL_PID
value: Express
- name: TZ
value: America/New_York
volumeMounts:
- name: data
mountPath: /var/opt/mssql
restartPolicy: Always
terminationGracePeriodSeconds: 30
dnsPolicy: ClusterFirst
nodeSelector:
kubernetes.io/hostname: kubernetes
schedulerName: default-scheduler
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: local-path
resources:
requests:
storage: 4Gi
---
kind: Service
apiVersion: v1
metadata:
name: power-database
spec:
ports:
- name: client
port: 1433
selector:
app: power-database
type: LoadBalancer
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: power-service
namespace: home-monitor
labels:
app: power-service
spec:
replicas: 1
selector:
matchLabels:
app: power-service
template:
metadata:
labels:
app: power-service
spec:
containers:
- name: power-service
image: ckaczor/home-monitor-power-service:#BUILD_BUILDNUMBER#
terminationMessagePath: "/dev/termination-log"
terminationMessagePolicy: File
imagePullPolicy: Always
securityContext:
privileged: true
env:
- name: Power__Database__Host
value: power-database
- name: Power__Database__User
valueFrom:
secretKeyRef:
name: power-database-credentials
key: username
- name: Power__Database__Password
valueFrom:
secretKeyRef:
name: power-database-credentials
key: password
- name: Hub__Power
value: http://hub-service/power
restartPolicy: Always
terminationGracePeriodSeconds: 30
dnsPolicy: ClusterFirst
nodeSelector:
kubernetes.io/hostname: kubernetes
schedulerName: default-scheduler
---
kind: Service
apiVersion: v1
metadata:
name: power-service
spec:
ports:
- name: client
port: 80
selector:
app: power-service
type: ClusterIP
---
kind: Ingress
apiVersion: extensions/v1beta1
metadata:
name: power
namespace: home-monitor
annotations:
kubernetes.io/ingress.class: traefik
nginx.ingress.kubernetes.io/ssl-redirect: 'false'
traefik.frontend.rule.type: PathPrefixStrip
spec:
rules:
- http:
paths:
- path: "/api/power"
backend:
serviceName: power-service
servicePort: 80