diff --git a/DeviceStatus/Service/Service.sln b/DeviceStatus/Service/Service.sln new file mode 100644 index 0000000..28595d2 --- /dev/null +++ b/DeviceStatus/Service/Service.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Service", "Service.csproj", "{AC509ACF-B729-408E-B9A1-83EE811A2FA0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AC509ACF-B729-408E-B9A1-83EE811A2FA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AC509ACF-B729-408E-B9A1-83EE811A2FA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC509ACF-B729-408E-B9A1-83EE811A2FA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AC509ACF-B729-408E-B9A1-83EE811A2FA0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A50962D3-359A-49D1-ADEB-8D49FAECF950} + EndGlobalSection +EndGlobal diff --git a/Environment/Environment.sln b/Environment/Environment.sln new file mode 100644 index 0000000..8736251 --- /dev/null +++ b/Environment/Environment.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34330.188 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Service", "Service\Service.csproj", "{AAF49637-5EAB-4D0F-A3EB-4421456DF709}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AAF49637-5EAB-4D0F-A3EB-4421456DF709}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AAF49637-5EAB-4D0F-A3EB-4421456DF709}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AAF49637-5EAB-4D0F-A3EB-4421456DF709}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AAF49637-5EAB-4D0F-A3EB-4421456DF709}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {AF8B4FE8-1A8C-4189-8A16-BFD216D13047} + EndGlobalSection +EndGlobal diff --git a/Environment/Environment.sln.DotSettings b/Environment/Environment.sln.DotSettings new file mode 100644 index 0000000..86c4878 --- /dev/null +++ b/Environment/Environment.sln.DotSettings @@ -0,0 +1,195 @@ + + <?xml version="1.0" encoding="utf-16"?> +<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> + <TypePattern DisplayName="Non-reorderable types"> + <TypePattern.Match> + <Or> + <And> + <Kind Is="Interface" /> + <Or> + <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> + <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> + </Or> + </And> + <Kind Is="Struct" /> + <HasAttribute Name="JetBrains.Annotations.NoReorderAttribute" /> + <HasAttribute Name="JetBrains.Annotations.NoReorder" /> + </Or> + </TypePattern.Match> + </TypePattern> + <TypePattern DisplayName="xUnit.net Test Classes" RemoveRegions="All"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <HasMember> + <And> + <Kind Is="Method" /> + <HasAttribute Name="Xunit.FactAttribute" Inherited="True" /> + <HasAttribute Name="Xunit.TheoryAttribute" Inherited="True" /> + </And> + </HasMember> + </And> + </TypePattern.Match> + <Entry DisplayName="Setup/Teardown Methods"> + <Entry.Match> + <Or> + <Kind Is="Constructor" /> + <And> + <Kind Is="Method" /> + <ImplementsInterface Name="System.IDisposable" /> + </And> + </Or> + </Entry.Match> + <Entry.SortBy> + <Kind Order="Constructor" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="All other members" /> + <Entry DisplayName="Test Methods" Priority="100"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <HasAttribute Name="Xunit.FactAttribute" /> + <HasAttribute Name="Xunit.TheoryAttribute" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> + <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <Or> + <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.TestFixtureSourceAttribute" Inherited="True" /> + <HasMember> + <And> + <Kind Is="Method" /> + <HasAttribute Name="NUnit.Framework.TestAttribute" /> + <HasAttribute Name="NUnit.Framework.TestCaseAttribute" /> + <HasAttribute Name="NUnit.Framework.TestCaseSourceAttribute" /> + </And> + </HasMember> + </Or> + </And> + </TypePattern.Match> + <Entry DisplayName="Setup/Teardown Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <Or> + <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.TestFixtureSetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.TestFixtureTearDownAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.OneTimeSetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.OneTimeTearDownAttribute" Inherited="True" /> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="All other members" /> + <Entry DisplayName="Test Methods" Priority="100"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <HasAttribute Name="NUnit.Framework.TestAttribute" /> + <HasAttribute Name="NUnit.Framework.TestCaseAttribute" /> + <HasAttribute Name="NUnit.Framework.TestCaseSourceAttribute" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> + <TypePattern DisplayName="Default Pattern"> + <Entry DisplayName="Public Delegates" Priority="100"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Delegate" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Public Enums" Priority="100"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Enum" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Static Fields and Constants"> + <Entry.Match> + <Or> + <Kind Is="Constant" /> + <And> + <Kind Is="Field" /> + <Static /> + </And> + </Or> + </Entry.Match> + <Entry.SortBy> + <Kind Order="Constant Field" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Fields"> + <Entry.Match> + <And> + <Kind Is="Field" /> + <Not> + <Static /> + </Not> + </And> + </Entry.Match> + <Entry.SortBy> + <Readonly /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Constructors"> + <Entry.Match> + <Kind Is="Constructor" /> + </Entry.Match> + <Entry.SortBy> + <Static /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Properties, Indexers"> + <Entry.Match> + <Or> + <Kind Is="Property" /> + <Kind Is="Indexer" /> + </Or> + </Entry.Match> + </Entry> + <Entry DisplayName="Interface Implementations" Priority="100"> + <Entry.Match> + <And> + <Kind Is="Member" /> + <ImplementsInterface /> + </And> + </Entry.Match> + <Entry.SortBy> + <ImplementsInterface Immediate="True" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="All other members" /> + <Entry DisplayName="Nested Types"> + <Entry.Match> + <Kind Is="Type" /> + </Entry.Match> + </Entry> + </TypePattern> +</Patterns> + True \ No newline at end of file diff --git a/Environment/Service/Data/Database.cs b/Environment/Service/Data/Database.cs new file mode 100644 index 0000000..75b8e59 --- /dev/null +++ b/Environment/Service/Data/Database.cs @@ -0,0 +1,51 @@ +using System.Reflection; +using Dapper; +using DbUp; +using Microsoft.Data.SqlClient; + +namespace ChrisKaczor.HomeMonitor.Environment.Service.Data; + +public class Database(IConfiguration configuration) +{ + private string GetConnectionString() + { + var connectionStringBuilder = new SqlConnectionStringBuilder + { + DataSource = configuration["Environment:Database:Host"], + UserID = configuration["Environment:Database:User"], + Password = configuration["Environment:Database:Password"], + InitialCatalog = configuration["Environment:Database:Name"], + TrustServerCertificate = bool.Parse(configuration["Environment:Database:TrustServerCertificate"] ?? "false") + }; + + return connectionStringBuilder.ConnectionString; + } + + public void EnsureDatabase() + { + var connectionString = GetConnectionString(); + + DbUp.EnsureDatabase.For.SqlDatabase(connectionString); + + var upgradeEngine = DeployChanges.To.SqlDatabase(connectionString).WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), s => s.Contains(".Schema.")).LogToConsole().Build(); + + upgradeEngine.PerformUpgrade(); + } + + private SqlConnection CreateConnection() + { + var connection = new SqlConnection(GetConnectionString()); + connection.Open(); + + return connection; + } + + public async Task StoreMessageAsync(Message message) + { + await using var connection = CreateConnection(); + + var query = ResourceReader.GetString("ChrisKaczor.HomeMonitor.Environment.Service.Data.Queries.CreateReading.sql"); + + await connection.QueryAsync(query, message); + } +} \ No newline at end of file diff --git a/Environment/Service/Data/Queries/CreateReading.sql b/Environment/Service/Data/Queries/CreateReading.sql new file mode 100644 index 0000000..06ecd86 --- /dev/null +++ b/Environment/Service/Data/Queries/CreateReading.sql @@ -0,0 +1,25 @@ +BEGIN TRANSACTION + +INSERT Reading + (Timestamp, Name, Model, Temperature, Pressure, Humidity, Luminance, GasResistance, ColorTemperature, AirQualityIndex) +SELECT + @Timestamp, + @Name, + @Model, + @Temperature, + @Pressure, + @Humidity, + @Luminance, + @GasResistance, + @ColorTemperature, + @AirQualityIndex +WHERE NOT EXISTS + ( + SELECT + 1 + FROM + Reading WITH (UPDLOCK, SERIALIZABLE) + WHERE Timestamp = @Timestamp AND Name = @Name AND Model = @Model + ) + +COMMIT TRANSACTION \ No newline at end of file diff --git a/Environment/Service/Data/Schema/1-Initial Schema.sql b/Environment/Service/Data/Schema/1-Initial Schema.sql new file mode 100644 index 0000000..a24e750 --- /dev/null +++ b/Environment/Service/Data/Schema/1-Initial Schema.sql @@ -0,0 +1,14 @@ +CREATE TABLE Reading +( + Timestamp datetimeoffset NOT NULL, + Name nvarchar(50) NOT NULL, + Model nvarchar(50) NOT NULL, + Temperature decimal(5, 2) NOT NULL, + Pressure decimal(6, 2) NOT NULL, + Humidity decimal(5, 2) NOT NULL, + Luminance int NOT NULL, + GasResistance int NOT NULL, + ColorTemperature int NOT NULL, + AirQualityIndex decimal(4, 1) NOT NULL, + CONSTRAINT reading_pk PRIMARY KEY (Timestamp, Name, Model) +); diff --git a/Environment/Service/Dockerfile b/Environment/Service/Dockerfile new file mode 100644 index 0000000..e7c8478 --- /dev/null +++ b/Environment/Service/Dockerfile @@ -0,0 +1,17 @@ +FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS base +WORKDIR /app +EXPOSE 80 + +FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine 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 +RUN apk add --no-cache tzdata +WORKDIR /app +COPY --from=build /app . +ENTRYPOINT ["dotnet", "ChrisKaczor.HomeMonitor.Environment.Service.dll"] \ No newline at end of file diff --git a/Environment/Service/Environment.http b/Environment/Service/Environment.http new file mode 100644 index 0000000..f257474 --- /dev/null +++ b/Environment/Service/Environment.http @@ -0,0 +1,6 @@ +@Environment_HostAddress = http://localhost:5234 + +GET {{Environment_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/Environment/Service/Message.cs b/Environment/Service/Message.cs new file mode 100644 index 0000000..d1cfb12 --- /dev/null +++ b/Environment/Service/Message.cs @@ -0,0 +1,32 @@ +using System.Text.Json.Serialization; + +namespace ChrisKaczor.HomeMonitor.Environment.Service; + +public class Message +{ + [JsonPropertyName("model")] + public required string Model { get; set; } + + [JsonPropertyName("nickname")] + public required string Name { get; set; } + + [JsonPropertyName("readings")] + public required Readings Readings { get; set; } + + [JsonPropertyName("timestamp")] + public required DateTimeOffset Timestamp { get; set; } + + public decimal AirQualityIndex => Readings.AirQualityIndex; + + public decimal ColorTemperature => Readings.ColorTemperature; + + public decimal GasResistance => Readings.GasResistance; + + public decimal Humidity => Readings.Humidity; + + public decimal Luminance => Readings.Luminance; + + public decimal Pressure => Readings.Pressure; + + public decimal Temperature => Readings.Temperature; +} \ No newline at end of file diff --git a/Environment/Service/MessageHandler.cs b/Environment/Service/MessageHandler.cs new file mode 100644 index 0000000..61dabcc --- /dev/null +++ b/Environment/Service/MessageHandler.cs @@ -0,0 +1,68 @@ +using System.Text.Json; +using ChrisKaczor.HomeMonitor.Environment.Service.Data; +using MQTTnet; +using MQTTnet.Client; + +namespace ChrisKaczor.HomeMonitor.Environment.Service; + +public class MessageHandler : IHostedService +{ + private readonly IConfiguration _configuration; + private readonly Database _database; + private readonly IMqttClient _mqttClient; + + private readonly MqttFactory _mqttFactory; + private readonly string _topic; + + public MessageHandler(IConfiguration configuration, Database database) + { + _configuration = configuration; + _database = database; + + _database.EnsureDatabase(); + + _topic = _configuration["Mqtt:Topic"] ?? string.Empty; + + if (string.IsNullOrEmpty(_topic)) + throw new InvalidOperationException("Topic not set"); + + _mqttFactory = new MqttFactory(); + _mqttClient = _mqttFactory.CreateMqttClient(); + + _mqttClient.ApplicationMessageReceivedAsync += OnApplicationMessageReceivedAsync; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer(_configuration["Mqtt:Server"]).Build(); + await _mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); + + var mqttSubscribeOptions = _mqttFactory.CreateSubscribeOptionsBuilder().WithTopicFilter($"{_topic}/#").Build(); + await _mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + await _mqttClient.DisconnectAsync(new MqttClientDisconnectOptionsBuilder().Build(), CancellationToken.None); + } + + private async Task OnApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs arg) + { + var topic = arg.ApplicationMessage.Topic; + var payload = arg.ApplicationMessage.ConvertPayloadToString(); + + WriteLog($"Topic: {topic} = {payload}"); + + var message = JsonSerializer.Deserialize(payload); + + if (message == null) + return; + + await _database.StoreMessageAsync(message); + } + + private static void WriteLog(string message) + { + Console.WriteLine(message); + } +} \ No newline at end of file diff --git a/Environment/Service/Program.cs b/Environment/Service/Program.cs new file mode 100644 index 0000000..47788ad --- /dev/null +++ b/Environment/Service/Program.cs @@ -0,0 +1,26 @@ +using ChrisKaczor.HomeMonitor.Environment.Service.Data; + +namespace ChrisKaczor.HomeMonitor.Environment.Service; + +public static class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + builder.Configuration.AddEnvironmentVariables(); + + builder.Services.AddControllers(); + + builder.Services.AddTransient(); + builder.Services.AddHostedService(); + + var app = builder.Build(); + + app.UseAuthorization(); + + app.MapControllers(); + + app.Run(); + } +} \ No newline at end of file diff --git a/Environment/Service/Properties/launchSettings.json b/Environment/Service/Properties/launchSettings.json new file mode 100644 index 0000000..2ca9931 --- /dev/null +++ b/Environment/Service/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "Environment": { + "commandName": "Project", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/Environment/Service/Readings.cs b/Environment/Service/Readings.cs new file mode 100644 index 0000000..c1f4919 --- /dev/null +++ b/Environment/Service/Readings.cs @@ -0,0 +1,29 @@ +using System.Text.Json.Serialization; +using JetBrains.Annotations; + +namespace ChrisKaczor.HomeMonitor.Environment.Service; + +[UsedImplicitly] +public class Readings +{ + [JsonPropertyName("aqi")] + public decimal AirQualityIndex { get; set; } + + [JsonPropertyName("color_temperature")] + public decimal ColorTemperature { get; set; } + + [JsonPropertyName("gas_resistance")] + public decimal GasResistance { get; set; } + + [JsonPropertyName("humidity")] + public decimal Humidity { get; set; } + + [JsonPropertyName("luminance")] + public decimal Luminance { get; set; } + + [JsonPropertyName("pressure")] + public decimal Pressure { get; set; } + + [JsonPropertyName("temperature")] + public decimal Temperature { get; set; } +} \ No newline at end of file diff --git a/Environment/Service/ResourceReader.cs b/Environment/Service/ResourceReader.cs new file mode 100644 index 0000000..f62f576 --- /dev/null +++ b/Environment/Service/ResourceReader.cs @@ -0,0 +1,19 @@ +using System.Reflection; + +namespace ChrisKaczor.HomeMonitor.Environment.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(); + } +} \ No newline at end of file diff --git a/Environment/Service/Service.csproj b/Environment/Service/Service.csproj new file mode 100644 index 0000000..821e2cf --- /dev/null +++ b/Environment/Service/Service.csproj @@ -0,0 +1,33 @@ + + + + net8.0 + enable + enable + false + ChrisKaczor.HomeMonitor.Environment.Service + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Environment/Service/appsettings.Development.json b/Environment/Service/appsettings.Development.json new file mode 100644 index 0000000..196c711 --- /dev/null +++ b/Environment/Service/appsettings.Development.json @@ -0,0 +1,20 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "Mqtt": { + "Server": "172.23.10.3" + }, + "Environment": { + "Database": { + "Host": "localhost", + "User": "sa", + "Password": "newpassword", + "Name": "Environment", + "TrustServerCertificate": true + } + } +} diff --git a/Environment/Service/appsettings.json b/Environment/Service/appsettings.json new file mode 100644 index 0000000..5566089 --- /dev/null +++ b/Environment/Service/appsettings.json @@ -0,0 +1,22 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Mqtt": { + "Server": "mosquitto", + "Topic": "enviro" + }, + "Environment": { + "Database": { + "Host": "", + "User": "", + "Password": "", + "Name": "Environment", + "TrustServerCertificate": false + } + } +} diff --git a/Environment/Service/deploy/azure-pipelines.yml b/Environment/Service/deploy/azure-pipelines.yml new file mode 100644 index 0000000..aab7031 --- /dev/null +++ b/Environment/Service/deploy/azure-pipelines.yml @@ -0,0 +1,72 @@ +name: $(Rev:r) + +pr: none + +trigger: + batch: 'true' + branches: + include: + - master + paths: + include: + - Environment/Service + +stages: +- stage: Build + jobs: + - job: Build + pool: + vmImage: 'ubuntu-latest' + + steps: + - task: Docker@0 + displayName: 'Build an image' + inputs: + containerregistrytype: 'Container Registry' + dockerRegistryConnection: 'Docker Hub' + dockerFile: 'Environment/Service/Dockerfile' + imageName: 'ckaczor/home-monitor-environment-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-environment-service:$(Build.BuildNumber)' + includeLatestTag: true + + - task: Bash@3 + inputs: + targetType: 'inline' + script: 'sed -i s/#BUILD_BUILDNUMBER#/$BUILD_BUILDNUMBER/ Environment/Service/deploy/manifest.yaml' + + - task: PublishBuildArtifacts@1 + inputs: + PathtoPublish: 'Environment/Service/deploy/manifest.yaml' + ArtifactName: 'Manifest' + publishLocation: 'Container' + +- stage: Deploy + jobs: + - job: Deploy + pool: + vmImage: 'ubuntu-latest' + steps: + - task: DownloadBuildArtifacts@0 + inputs: + artifactName: 'Manifest' + buildType: 'current' + downloadType: 'single' + downloadPath: '$(System.ArtifactsDirectory)' + - task: Kubernetes@1 + inputs: + connectionType: 'Kubernetes Service Connection' + kubernetesServiceEndpoint: 'Kubernetes' + namespace: 'home-monitor' + command: 'apply' + useConfigurationFile: true + configuration: '$(System.ArtifactsDirectory)/Manifest/manifest.yaml' + secretType: 'dockerRegistry' + containerRegistryType: 'Container Registry' \ No newline at end of file diff --git a/Environment/Service/deploy/manifest.yaml b/Environment/Service/deploy/manifest.yaml new file mode 100644 index 0000000..8799fe9 --- /dev/null +++ b/Environment/Service/deploy/manifest.yaml @@ -0,0 +1,158 @@ +--- +kind: StatefulSet +apiVersion: apps/v1 +metadata: + name: environment-database + namespace: home-monitor + labels: + app: environment-database +spec: + replicas: 1 + selector: + matchLabels: + app: environment-database + serviceName: environment-database + template: + metadata: + labels: + app: environment-database + spec: + containers: + - name: environment-database + image: mcr.microsoft.com/mssql/server + terminationMessagePath: "/dev/termination-log" + terminationMessagePolicy: File + imagePullPolicy: IfNotPresent + env: + - name: SA_PASSWORD + valueFrom: + secretKeyRef: + name: environment-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: environment-database +spec: + ports: + - name: client + port: 1433 + selector: + app: environment-database + type: LoadBalancer +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: environment-service + namespace: home-monitor + labels: + app: environment-service +spec: + replicas: 1 + selector: + matchLabels: + app: environment-service + template: + metadata: + labels: + app: environment-service + spec: + containers: + - name: environment-service + image: ckaczor/home-monitor-environment-service:#BUILD_BUILDNUMBER# + terminationMessagePath: "/dev/termination-log" + terminationMessagePolicy: File + imagePullPolicy: Always + securityContext: + privileged: true + env: + - name: Environment__Database__Host + value: environment-database + - name: Environment__Database__User + valueFrom: + secretKeyRef: + name: environment-database-credentials + key: username + - name: Environment__Database__Password + valueFrom: + secretKeyRef: + name: environment-database-credentials + key: password + - name: Hub__Environment + value: http://hub-service/environment + restartPolicy: Always + terminationGracePeriodSeconds: 30 + dnsPolicy: ClusterFirst + nodeSelector: + kubernetes.io/hostname: kubernetes + schedulerName: default-scheduler +--- +kind: Service +apiVersion: v1 +metadata: + name: environment-service +spec: + ports: + - name: client + port: 80 + selector: + app: environment-service + type: ClusterIP +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + annotations: + kubernetes.io/ingress.class: traefik + creationTimestamp: null + name: environment + namespace: home-monitor +spec: + routes: + - kind: Rule + match: PathPrefix(`/api/environment`) + middlewares: + - name: api-environment + namespace: home-monitor + services: + - kind: Service + name: environment-service + namespace: home-monitor + port: 80 +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + creationTimestamp: null + name: api-environment + namespace: home-monitor +spec: + stripPrefix: + prefixes: + - /api/environment diff --git a/Hub/Service/Hubs/EnvironmentHub.cs b/Hub/Service/Hubs/EnvironmentHub.cs new file mode 100644 index 0000000..3bc108c --- /dev/null +++ b/Hub/Service/Hubs/EnvironmentHub.cs @@ -0,0 +1,19 @@ +using JetBrains.Annotations; +using Microsoft.AspNetCore.SignalR; +using System; +using System.Threading.Tasks; + +namespace ChrisKaczor.HomeMonitor.Hub.Service.Hubs +{ + [UsedImplicitly] + public class EnvironmentHub : Microsoft.AspNetCore.SignalR.Hub + { + [UsedImplicitly] + public async Task SendLatestReading(string message) + { + Console.WriteLine(message); + + await Clients.Others.SendAsync("LatestReading", message); + } + } +} \ No newline at end of file diff --git a/Hub/Service/Startup.cs b/Hub/Service/Startup.cs index 8fe3dc5..8b69e99 100644 --- a/Hub/Service/Startup.cs +++ b/Hub/Service/Startup.cs @@ -34,6 +34,7 @@ namespace ChrisKaczor.HomeMonitor.Hub.Service endpoints.MapHub("/weather"); endpoints.MapHub("/power"); endpoints.MapHub("/device-status"); + endpoints.MapHub("/environment"); endpoints.MapDefaultControllerRoute(); }); } diff --git a/Weather/Service/Service.sln b/Weather/Service/Service.sln new file mode 100644 index 0000000..abcaf41 --- /dev/null +++ b/Weather/Service/Service.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Service", "Service.csproj", "{8F323902-4BFE-4A92-ADDC-534F3DF4B497}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8F323902-4BFE-4A92-ADDC-534F3DF4B497}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F323902-4BFE-4A92-ADDC-534F3DF4B497}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F323902-4BFE-4A92-ADDC-534F3DF4B497}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F323902-4BFE-4A92-ADDC-534F3DF4B497}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {07D80FFE-643C-44E1-9BD3-1F5FD92C0527} + EndGlobalSection +EndGlobal