mirror of
https://github.com/ckaczor/HomeMonitor.git
synced 2026-01-13 17:22:54 -05:00
Initial commit from private
This commit is contained in:
9
.dockerignore
Normal file
9
.dockerignore
Normal file
@@ -0,0 +1,9 @@
|
||||
.dockerignore
|
||||
.env
|
||||
.git
|
||||
.gitignore
|
||||
.vs
|
||||
.vscode
|
||||
*/bin
|
||||
*/obj
|
||||
**/.toolstarget
|
||||
63
.gitattributes
vendored
Normal file
63
.gitattributes
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
||||
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
bin/
|
||||
log/
|
||||
obj/
|
||||
.vs/
|
||||
|
||||
Private/
|
||||
17
Dockerfile-HubService
Normal file
17
Dockerfile-HubService
Normal file
@@ -0,0 +1,17 @@
|
||||
FROM microsoft/dotnet:2.2-aspnetcore-runtime-stretch-slim AS base
|
||||
WORKDIR /app
|
||||
EXPOSE 80
|
||||
|
||||
FROM microsoft/dotnet:2.2-sdk-stretch AS build
|
||||
WORKDIR /src
|
||||
COPY ["Hub/Service/Service.csproj", "Hub/Service/"]
|
||||
COPY ["Weather/Models/Models.csproj", "Weather/Models/"]
|
||||
RUN dotnet restore "Hub/Service/Service.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/Hub/Service"
|
||||
RUN dotnet publish "Service.csproj" -c Release -o /app
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=build /app .
|
||||
ENTRYPOINT ["dotnet", "Hub.Service.dll"]
|
||||
16
Dockerfile-WeatherSerialReader
Normal file
16
Dockerfile-WeatherSerialReader
Normal file
@@ -0,0 +1,16 @@
|
||||
FROM mcr.microsoft.com/dotnet/core/aspnet:3.0.0-preview6-buster-slim-arm32v7 AS base
|
||||
WORKDIR /app
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:3.0.100-preview6-buster AS build
|
||||
WORKDIR /src
|
||||
COPY ["Weather/SerialReader/SerialReader.csproj", "Weather/SerialReader/"]
|
||||
COPY ["Weather/Models/Models.csproj", "Weather/Models/"]
|
||||
RUN dotnet restore -r linux-arm "Weather/SerialReader/SerialReader.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/Weather/SerialReader"
|
||||
RUN dotnet publish -r linux-arm --self-contained=false "SerialReader.csproj" -c Release -o /app
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=build /app .
|
||||
ENTRYPOINT ["dotnet", "Weather.SerialReader.dll"]
|
||||
17
Dockerfile-WeatherService
Normal file
17
Dockerfile-WeatherService
Normal file
@@ -0,0 +1,17 @@
|
||||
FROM microsoft/dotnet:2.2-aspnetcore-runtime-stretch-slim AS base
|
||||
WORKDIR /app
|
||||
EXPOSE 80
|
||||
|
||||
FROM microsoft/dotnet:2.2-sdk-stretch AS build
|
||||
WORKDIR /src
|
||||
COPY ["Weather/Service/Service.csproj", "Weather/Service/"]
|
||||
COPY ["Weather/Models/Models.csproj", "Weather/Models/"]
|
||||
RUN dotnet restore "Weather/Service/Service.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/Weather/Service"
|
||||
RUN dotnet publish "Service.csproj" -c Release -o /app
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=build /app .
|
||||
ENTRYPOINT ["dotnet", "Weather.Service.dll"]
|
||||
31
Hub/Hub.sln
Normal file
31
Hub/Hub.sln
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.28711.60
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Service", "Service\Service.csproj", "{11E9A9F4-9348-402E-8ADF-942F66965D32}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models", "..\Weather\Models\Models.csproj", "{7DE44178-B63E-4CC4-88AE-B322274EDE26}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{11E9A9F4-9348-402E-8ADF-942F66965D32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{11E9A9F4-9348-402E-8ADF-942F66965D32}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{11E9A9F4-9348-402E-8ADF-942F66965D32}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{11E9A9F4-9348-402E-8ADF-942F66965D32}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7DE44178-B63E-4CC4-88AE-B322274EDE26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7DE44178-B63E-4CC4-88AE-B322274EDE26}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7DE44178-B63E-4CC4-88AE-B322274EDE26}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7DE44178-B63E-4CC4-88AE-B322274EDE26}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3A02B8D9-C20F-42A8-BEFD-F67E6BF56F16}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
3
Hub/Hub.sln.DotSettings.user
Normal file
3
Hub/Hub.sln.DotSettings.user
Normal file
@@ -0,0 +1,3 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/AnalysisEnabled/@EntryValue">SOLUTION</s:String>
|
||||
<s:Int64 x:Key="/Default/CodeStyle/Naming/CSharpAutoNaming/AutoNamingCompletedVersion/@EntryValue">2</s:Int64></wpf:ResourceDictionary>
|
||||
33
Hub/Service/.vscode/launch.json
vendored
Normal file
33
Hub/Service/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": ".NET Core Launch (web)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
"program": "${workspaceFolder}/bin/Debug/netcoreapp3.0/Service.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"stopAtEntry": false,
|
||||
"launchBrowser": {
|
||||
"enabled": false
|
||||
},
|
||||
"env": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/Views": "${workspaceFolder}/Views"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": ".NET Core Attach",
|
||||
"type": "coreclr",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}"
|
||||
}
|
||||
]
|
||||
}
|
||||
35
Hub/Service/.vscode/tasks.json
vendored
Normal file
35
Hub/Service/.vscode/tasks.json
vendored
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
42
Hub/Service/Controllers/ValuesController.cs
Normal file
42
Hub/Service/Controllers/ValuesController.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Hub.Service.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class ValuesController : ControllerBase
|
||||
{
|
||||
// GET api/values
|
||||
[HttpGet]
|
||||
public ActionResult<IEnumerable<string>> Get()
|
||||
{
|
||||
return new[] { "value1", "value2" };
|
||||
}
|
||||
|
||||
// GET api/values/5
|
||||
[HttpGet("{id}")]
|
||||
public ActionResult<string> Get(int id)
|
||||
{
|
||||
return "value";
|
||||
}
|
||||
|
||||
// POST api/values
|
||||
[HttpPost]
|
||||
public void Post([FromBody] string value)
|
||||
{
|
||||
}
|
||||
|
||||
// PUT api/values/5
|
||||
[HttpPut("{id}")]
|
||||
public void Put(int id, [FromBody] string value)
|
||||
{
|
||||
}
|
||||
|
||||
// DELETE api/values/5
|
||||
[HttpDelete("{id}")]
|
||||
public void Delete(int id)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Hub/Service/Hubs/WeatherHub.cs
Normal file
19
Hub/Service/Hubs/WeatherHub.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Hub.Service.Hubs
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class WeatherHub : Microsoft.AspNetCore.SignalR.Hub
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public async Task SendLatestReading(string message)
|
||||
{
|
||||
Console.WriteLine(message);
|
||||
|
||||
await Clients.Others.SendAsync("LatestReading", message);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Hub/Service/Program.cs
Normal file
18
Hub/Service/Program.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
namespace Hub.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).UseStartup<Startup>();
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Hub/Service/Properties/launchSettings.json
Normal file
30
Hub/Service/Properties/launchSettings.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:53872",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "api/values",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"Service": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "api/values",
|
||||
"applicationUrl": "http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Hub/Service/Service.csproj
Normal file
21
Hub/Service/Service.csproj
Normal file
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
|
||||
<AssemblyName>Hub.Service</AssemblyName>
|
||||
<RootNamespace>Hub.Service</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Weather\Models\Models.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
9
Hub/Service/Service.csproj.user
Normal file
9
Hub/Service/Service.csproj.user
Normal 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>
|
||||
30
Hub/Service/Startup.cs
Normal file
30
Hub/Service/Startup.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Hub.Service.Hubs;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Hub.Service
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
|
||||
|
||||
services.AddSignalR().AddJsonProtocol(options => { options.PayloadSerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver(); });
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder applicationBuilder, IHostingEnvironment environment)
|
||||
{
|
||||
if (environment.IsDevelopment())
|
||||
{
|
||||
applicationBuilder.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
applicationBuilder.UseSignalR(routes => { routes.MapHub<WeatherHub>("/weatherHub"); });
|
||||
|
||||
applicationBuilder.UseMvc();
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Hub/Service/appsettings.Development.json
Normal file
9
Hub/Service/appsettings.Development.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Hub/Service/appsettings.json
Normal file
8
Hub/Service/appsettings.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
44
Hub/Service/deploy/service.yaml
Normal file
44
Hub/Service/deploy/service.yaml
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
kind: Deployment
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: hub-service
|
||||
namespace: home-monitor
|
||||
labels:
|
||||
app: hub-service
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: hub-service
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: hub-service
|
||||
spec:
|
||||
containers:
|
||||
- name: hub-service
|
||||
image: ckaczor/home-monitor-hub-service:latest
|
||||
terminationMessagePath: "/dev/termination-log"
|
||||
terminationMessagePolicy: File
|
||||
imagePullPolicy: Always
|
||||
securityContext:
|
||||
privileged: true
|
||||
restartPolicy: Always
|
||||
terminationGracePeriodSeconds: 30
|
||||
dnsPolicy: ClusterFirst
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: kubernetes
|
||||
schedulerName: default-scheduler
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: hub-service
|
||||
spec:
|
||||
ports:
|
||||
- name: client
|
||||
port: 80
|
||||
selector:
|
||||
app: hub-service
|
||||
type: ClusterIP
|
||||
317
Weather/Arduino/Weather.ino
Normal file
317
Weather/Arduino/Weather.ino
Normal file
@@ -0,0 +1,317 @@
|
||||
#include <Wire.h> // I2C needed for sensors
|
||||
#include "SparkFunMPL3115A2.h" // Pressure sensor - Search "SparkFun MPL3115" and install from Library Manager
|
||||
#include "SparkFun_Si7021_Breakout_Library.h" // Humidity sensor - Search "SparkFun Si7021" and install from Library Manager
|
||||
#include <SoftwareSerial.h> // Needed for GPS
|
||||
#include <TinyGPS++.h> // GPS parsing - Available from https://github.com/mikalhart/TinyGPSPlus
|
||||
|
||||
static const int RXPin = 5; // GPS is attached to pin 5 (RX into GPS)
|
||||
static const int TXPin = 4; // GPS is attached to pin 4 (TX from GPS)
|
||||
|
||||
TinyGPSPlus gps; // GPS module
|
||||
SoftwareSerial ss(RXPin, TXPin); // Software serial port for GPS
|
||||
MPL3115A2 pressureSensor; // Instance of the pressure sensor
|
||||
Weather humiditySensor; // Instance of the humidity sensor
|
||||
|
||||
// Digital I/O pins
|
||||
const byte WSPEED = 3; // Wind speed switch
|
||||
const byte RAIN = 2; // Rain switch
|
||||
const byte STAT1 = 7; // Blue status light
|
||||
const byte STAT2 = 8; // Green status light
|
||||
const byte GPS_PWRCTL = 6; // Pulling this pin low puts GPS to sleep but maintains RTC and RAM
|
||||
|
||||
// Analog I/O pins
|
||||
const byte REFERENCE_3V3 = A3; // 3.3V reference
|
||||
const byte LIGHT = A1; // Light level
|
||||
const byte BATT = A2; // Battery level
|
||||
const byte WDIR = A0; // Wind direction
|
||||
|
||||
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
|
||||
// Global Variables
|
||||
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
|
||||
|
||||
long lastSecond; // The millis counter to see when a second rolls by
|
||||
long lastWindCheck = 0; // Time of the last wind check
|
||||
int winddir = 0; // Instantaneous wind direction [0-360]
|
||||
float windspeedmph = 0; // Instantaneous wind speed [mph]
|
||||
float humidity = 0; // Instantaneous humidity [%]
|
||||
float tempH = 0; // Instantaneous temperature from humidity sensor [F]
|
||||
float tempP = 0; // Instantaneous temperature from pressure sensor [F]
|
||||
float pressure = 0; // Instantaneous pressure [pascals]
|
||||
|
||||
float batt_lvl = 0; // Battery level [Analog value from 0 to 1023]
|
||||
float light_lvl = 0; // Light level [Analog value from 0 to 1023]
|
||||
|
||||
// volatiles are subject to modification by IRQs
|
||||
volatile unsigned long raintime, rainlast, raininterval, rain;
|
||||
volatile long lastWindIRQ = 0;
|
||||
volatile byte windClicks = 0;
|
||||
|
||||
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
|
||||
|
||||
//Interrupt routines (these are called by the hardware interrupts, not by the main code)
|
||||
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
|
||||
void rainIRQ()
|
||||
// Count rain gauge bucket tips as they occur
|
||||
// Activated by the magnet and reed switch in the rain gauge, attached to input D2
|
||||
{
|
||||
raintime = millis(); // grab current time
|
||||
raininterval = raintime - rainlast; // calculate interval between this and last event
|
||||
|
||||
if (raininterval > 10) // ignore switch-bounce glitches less than 10mS after initial edge
|
||||
{
|
||||
rain += 0.011; //Each dump is 0.011" of water
|
||||
rainlast = raintime; // set up for next event
|
||||
}
|
||||
}
|
||||
|
||||
void wspeedIRQ()
|
||||
// Activated by the magnet in the anemometer (2 ticks per rotation), attached to input D3
|
||||
{
|
||||
if (millis() - lastWindIRQ > 10) // Ignore switch-bounce glitches less than 10ms (142MPH max reading) after the reed switch closes
|
||||
{
|
||||
lastWindIRQ = millis(); //Grab the current time
|
||||
windClicks++; //There is 1.492MPH for each click per second.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(9600);
|
||||
|
||||
Serial.println("Board starting");
|
||||
|
||||
ss.begin(9600); //Begin listening to GPS over software serial at 9600. This should be the default baud of the module.
|
||||
|
||||
pinMode(STAT1, OUTPUT); //Status LED Blue
|
||||
pinMode(STAT2, OUTPUT); //Status LED Green
|
||||
|
||||
pinMode(GPS_PWRCTL, OUTPUT);
|
||||
digitalWrite(GPS_PWRCTL, HIGH); //Pulling this pin low puts GPS to sleep but maintains RTC and RAM
|
||||
|
||||
pinMode(WSPEED, INPUT_PULLUP); // input from wind meters windspeed sensor
|
||||
pinMode(RAIN, INPUT_PULLUP); // input from wind meters rain gauge sensor
|
||||
|
||||
pinMode(REFERENCE_3V3, INPUT);
|
||||
pinMode(LIGHT, INPUT);
|
||||
|
||||
//Configure the pressure sensor
|
||||
pressureSensor.begin(); // Get sensor online
|
||||
pressureSensor.setModeBarometer(); // Measure pressure in Pascals from 20 to 110 kPa
|
||||
pressureSensor.setOversampleRate(7); // Set Oversample to the recommended 128
|
||||
pressureSensor.enableEventFlags(); // Enable all three pressure and temp event flags
|
||||
|
||||
//Configure the humidity sensor
|
||||
humiditySensor.begin();
|
||||
|
||||
lastSecond = millis();
|
||||
|
||||
// attach external interrupt pins to IRQ functions
|
||||
attachInterrupt(0, rainIRQ, FALLING);
|
||||
attachInterrupt(1, wspeedIRQ, FALLING);
|
||||
|
||||
// turn on interrupts
|
||||
interrupts();
|
||||
|
||||
Serial.println("Board ready");
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
//Keep track of which minute it is
|
||||
if (millis() - lastSecond >= 1000)
|
||||
{
|
||||
digitalWrite(STAT1, HIGH); //Blink stat LED
|
||||
|
||||
lastSecond += 1000;
|
||||
|
||||
//Go calc all the various sensors
|
||||
calcWeather();
|
||||
|
||||
//Report all readings every second
|
||||
printWeather();
|
||||
|
||||
digitalWrite(STAT1, LOW); //Turn off stat LED
|
||||
}
|
||||
|
||||
smartdelay(800); //Wait 1 second, and gather GPS data
|
||||
}
|
||||
|
||||
//While we delay for a given amount of time, gather GPS data
|
||||
static void smartdelay(unsigned long ms)
|
||||
{
|
||||
unsigned long start = millis();
|
||||
do
|
||||
{
|
||||
while (ss.available())
|
||||
gps.encode(ss.read());
|
||||
} while (millis() - start < ms);
|
||||
}
|
||||
|
||||
|
||||
//Calculates each of the variables that wunderground is expecting
|
||||
void calcWeather()
|
||||
{
|
||||
//Calc the wind speed and direction every second for 120 second to get 2 minute average
|
||||
float currentSpeed = get_wind_speed();
|
||||
windspeedmph = currentSpeed; //update global variable for windspeed when using the printWeather() function
|
||||
//float currentSpeed = random(5); //For testing
|
||||
int currentDirection = get_wind_direction();
|
||||
|
||||
//Calc winddir
|
||||
winddir = get_wind_direction();
|
||||
|
||||
//Calc windspeed
|
||||
//windspeedmph = get_wind_speed(); //This is calculated in the main loop on line 196
|
||||
|
||||
//Calc humidity
|
||||
humidity = humiditySensor.getRH();
|
||||
tempH = humiditySensor.readTempF();
|
||||
|
||||
//Calc tempf from pressure sensor
|
||||
tempP = pressureSensor.readTempF();
|
||||
|
||||
//Calc pressure
|
||||
pressure = pressureSensor.readPressure();
|
||||
|
||||
//Calc light level
|
||||
light_lvl = get_light_level();
|
||||
|
||||
//Calc battery level
|
||||
batt_lvl = get_battery_level();
|
||||
}
|
||||
|
||||
//Returns the voltage of the light sensor based on the 3.3V rail
|
||||
//This allows us to ignore what VCC might be (an Arduino plugged into USB has VCC of 4.5 to 5.2V)
|
||||
float get_light_level()
|
||||
{
|
||||
float operatingVoltage = analogRead(REFERENCE_3V3);
|
||||
|
||||
float lightSensor = analogRead(LIGHT);
|
||||
|
||||
operatingVoltage = 3.3 / operatingVoltage; //The reference voltage is 3.3V
|
||||
|
||||
lightSensor = operatingVoltage * lightSensor;
|
||||
|
||||
return (lightSensor);
|
||||
}
|
||||
|
||||
//Returns the voltage of the raw pin based on the 3.3V rail
|
||||
//This allows us to ignore what VCC might be (an Arduino plugged into USB has VCC of 4.5 to 5.2V)
|
||||
//Battery level is connected to the RAW pin on Arduino and is fed through two 5% resistors:
|
||||
//3.9K on the high side (R1), and 1K on the low side (R2)
|
||||
float get_battery_level()
|
||||
{
|
||||
float operatingVoltage = analogRead(REFERENCE_3V3);
|
||||
|
||||
float rawVoltage = analogRead(BATT);
|
||||
|
||||
operatingVoltage = 3.30 / operatingVoltage; //The reference voltage is 3.3V
|
||||
|
||||
rawVoltage = operatingVoltage * rawVoltage; //Convert the 0 to 1023 int to actual voltage on BATT pin
|
||||
|
||||
rawVoltage *= 4.90; //(3.9k+1k)/1k - multiple BATT voltage by the voltage divider to get actual system voltage
|
||||
|
||||
return (rawVoltage);
|
||||
}
|
||||
|
||||
//Returns the instataneous wind speed
|
||||
float get_wind_speed()
|
||||
{
|
||||
float deltaTime = millis() - lastWindCheck; //750ms
|
||||
|
||||
deltaTime /= 1000.0; //Covert to seconds
|
||||
|
||||
float windSpeed = (float)windClicks / deltaTime; //3 / 0.750s = 4
|
||||
|
||||
windClicks = 0; //Reset and start watching for new wind
|
||||
lastWindCheck = millis();
|
||||
|
||||
windSpeed *= 1.492; //4 * 1.492 = 5.968MPH
|
||||
|
||||
/* Serial.println();
|
||||
Serial.print("Windspeed:");
|
||||
Serial.println(windSpeed);*/
|
||||
|
||||
return (windSpeed);
|
||||
}
|
||||
|
||||
//Read the wind direction sensor, return heading in degrees
|
||||
int get_wind_direction()
|
||||
{
|
||||
unsigned int adc;
|
||||
|
||||
adc = analogRead(WDIR); // get the current reading from the sensor
|
||||
|
||||
// The following table is ADC readings for the wind direction sensor output, sorted from low to high.
|
||||
// Each threshold is the midpoint between adjacent headings. The output is degrees for that ADC reading.
|
||||
// Note that these are not in compass degree order! See Weather Meters datasheet for more information.
|
||||
|
||||
if (adc < 380) return (113);
|
||||
if (adc < 393) return (68);
|
||||
if (adc < 414) return (90);
|
||||
if (adc < 456) return (158);
|
||||
if (adc < 508) return (135);
|
||||
if (adc < 551) return (203);
|
||||
if (adc < 615) return (180);
|
||||
if (adc < 680) return (23);
|
||||
if (adc < 746) return (45);
|
||||
if (adc < 801) return (248);
|
||||
if (adc < 833) return (225);
|
||||
if (adc < 878) return (338);
|
||||
if (adc < 913) return (0);
|
||||
if (adc < 940) return (293);
|
||||
if (adc < 967) return (315);
|
||||
if (adc < 990) return (270);
|
||||
return (-1); // error, disconnected?
|
||||
}
|
||||
|
||||
|
||||
//Prints the various variables directly to the port
|
||||
//I don't like the way this function is written but Arduino doesn't support floats under sprintf
|
||||
void printWeather()
|
||||
{
|
||||
//Serial.println();
|
||||
Serial.print("$,winddir=");
|
||||
Serial.print(winddir);
|
||||
Serial.print(",windspeedmph=");
|
||||
Serial.print(windspeedmph, 1);
|
||||
Serial.print(",humidity=");
|
||||
Serial.print(humidity, 1);
|
||||
Serial.print(",tempH=");
|
||||
Serial.print(tempH, 1);
|
||||
Serial.print(",tempP=");
|
||||
Serial.print(tempP, 1);
|
||||
Serial.print(",rain=");
|
||||
Serial.print(rain, 2);
|
||||
Serial.print(",pressure=");
|
||||
Serial.print(pressure, 2);
|
||||
Serial.print(",batt_lvl=");
|
||||
Serial.print(batt_lvl, 2);
|
||||
Serial.print(",light_lvl=");
|
||||
Serial.print(light_lvl, 2);
|
||||
|
||||
Serial.print(",lat=");
|
||||
Serial.print(gps.location.lat(), 6);
|
||||
Serial.print(",lng=");
|
||||
Serial.print(gps.location.lng(), 6);
|
||||
Serial.print(",altitude=");
|
||||
Serial.print(gps.altitude.meters());
|
||||
Serial.print(",sats=");
|
||||
Serial.print(gps.satellites.value());
|
||||
|
||||
char sz[32];
|
||||
Serial.print(",date=");
|
||||
sprintf(sz, "%02d/%02d/%02d", gps.date.month(), gps.date.day(), gps.date.year());
|
||||
Serial.print(sz);
|
||||
|
||||
Serial.print(",time=");
|
||||
sprintf(sz, "%02d:%02d:%02d", gps.time.hour(), gps.time.minute(), gps.time.second());
|
||||
Serial.print(sz);
|
||||
|
||||
Serial.print(",");
|
||||
Serial.println("#");
|
||||
|
||||
}
|
||||
|
||||
|
||||
11
Weather/Arduino/makefile
Normal file
11
Weather/Arduino/makefile
Normal file
@@ -0,0 +1,11 @@
|
||||
weather:
|
||||
mkdir -p bin/
|
||||
arduino-cli compile -b arduino:avr:uno Weather.ino -o bin/Weather
|
||||
|
||||
install:
|
||||
sudo service weather stop
|
||||
arduino-cli upload -b arduino:avr:uno -p /dev/ttyACM0 -i bin/Weather
|
||||
sudo service weather start
|
||||
|
||||
clean:
|
||||
rm -rf bin/
|
||||
5
Weather/Arduino/publish.bat
Normal file
5
Weather/Arduino/publish.bat
Normal file
@@ -0,0 +1,5 @@
|
||||
SET REMOTE=ckaczor@172.23.10.6
|
||||
|
||||
plink %REMOTE% mkdir -p Weather/Arduino
|
||||
|
||||
pscp -v -r makefile Weather.ino %REMOTE%:Weather/Arduino
|
||||
15
Weather/Models/Models.csproj
Normal file
15
Weather/Models/Models.csproj
Normal file
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<AssemblyName>Weather.Models</AssemblyName>
|
||||
<RootNamespace>Weather.Models</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
121
Weather/Models/WeatherMessageData.cs
Normal file
121
Weather/Models/WeatherMessageData.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Weather.Models
|
||||
{
|
||||
public enum MessageType
|
||||
{
|
||||
Text,
|
||||
Data
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public enum WindDirection
|
||||
{
|
||||
None = -1,
|
||||
North = 0,
|
||||
East = 90,
|
||||
South = 180,
|
||||
West = 270,
|
||||
NorthEast = 45,
|
||||
SouthEast = 135,
|
||||
SouthWest = 225,
|
||||
NorthWest = 315,
|
||||
NorthNorthEast = 23,
|
||||
EastNorthEast = 68,
|
||||
EastSouthEast = 113,
|
||||
SouthSouthEast = 158,
|
||||
SouthSouthWest = 203,
|
||||
WestSouthWest = 248,
|
||||
WestNorthWest = 293,
|
||||
NorthNorthWest = 338
|
||||
}
|
||||
|
||||
public class WeatherMessage
|
||||
{
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public MessageType Type { get; set; }
|
||||
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public WindDirection WindDirection { get; set; }
|
||||
|
||||
public double WindSpeed { get; set; }
|
||||
|
||||
public double Humidity { get; set; }
|
||||
|
||||
public double HumidityTemperature { get; set; }
|
||||
|
||||
public double Rain { get; set; }
|
||||
|
||||
public double Pressure { get; set; }
|
||||
|
||||
public double PressureTemperature { get; set; }
|
||||
|
||||
public double BatteryLevel { get; set; }
|
||||
|
||||
public double LightLevel { get; set; }
|
||||
|
||||
public double Latitude { get; set; }
|
||||
|
||||
public double Longitude { get; set; }
|
||||
|
||||
public double Altitude { get; set; }
|
||||
|
||||
public int SatelliteCount { get; set; }
|
||||
|
||||
public DateTimeOffset GpsTimestamp { get; set; }
|
||||
|
||||
public string Message { get; set; }
|
||||
|
||||
public WeatherMessage()
|
||||
{
|
||||
Type = MessageType.Text;
|
||||
Timestamp = DateTimeOffset.UtcNow;
|
||||
}
|
||||
|
||||
public WeatherMessage(string message)
|
||||
{
|
||||
Type = MessageType.Data;
|
||||
Timestamp = DateTimeOffset.UtcNow;
|
||||
|
||||
var messageParts = message.Split(',').ToList();
|
||||
|
||||
messageParts.RemoveAt(0);
|
||||
messageParts.RemoveAt(messageParts.Count - 1);
|
||||
|
||||
var messageValues = messageParts.Select(m => m.Split('=')).ToDictionary(a => a[0], a => a[1]);
|
||||
|
||||
WindDirection = Enum.Parse<WindDirection>(messageValues[@"winddir"]);
|
||||
WindSpeed = double.Parse(messageValues[@"windspeedmph"]);
|
||||
Humidity = double.Parse(messageValues[@"humidity"]);
|
||||
HumidityTemperature = double.Parse(messageValues[@"tempH"]);
|
||||
Rain = double.Parse(messageValues[@"rain"]);
|
||||
Pressure = double.Parse(messageValues[@"pressure"]);
|
||||
PressureTemperature = double.Parse(messageValues[@"tempP"]);
|
||||
BatteryLevel = double.Parse(messageValues[@"batt_lvl"]);
|
||||
LightLevel = double.Parse(messageValues[@"light_lvl"]);
|
||||
Latitude = double.Parse(messageValues[@"lat"]);
|
||||
Longitude = double.Parse(messageValues[@"lng"]);
|
||||
Altitude = double.Parse(messageValues[@"altitude"]);
|
||||
SatelliteCount = int.Parse(messageValues[@"sats"]);
|
||||
|
||||
DateTimeOffset.TryParseExact($"{messageValues[@"date"]} {messageValues[@"time"]}", "MM/dd/yyyy HH:mm:ss", null, DateTimeStyles.None, out var gpsTimestamp);
|
||||
GpsTimestamp = gpsTimestamp;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static WeatherMessage Parse(string message)
|
||||
{
|
||||
if (message.StartsWith("$") && message.EndsWith("#"))
|
||||
return new WeatherMessage(message);
|
||||
|
||||
return new WeatherMessage { Message = message };
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Weather/SerialReader/.vscode/launch.json
vendored
Normal file
30
Weather/SerialReader/.vscode/launch.json
vendored
Normal 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/Weather/SerialReader/SerialReader.dll"
|
||||
],
|
||||
"cwd": "/home/ckaczor/Weather/SerialReader",
|
||||
"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
Weather/SerialReader/.vscode/tasks.json
vendored
Normal file
35
Weather/SerialReader/.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"build",
|
||||
"${workspaceFolder}/SerialReader.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"
|
||||
}
|
||||
]
|
||||
}
|
||||
174
Weather/SerialReader/Program.cs
Normal file
174
Weather/SerialReader/Program.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
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 Weather.Models;
|
||||
|
||||
namespace Weather.SerialReader
|
||||
{
|
||||
internal static class Program
|
||||
{
|
||||
private static IConfiguration _configuration;
|
||||
|
||||
private static readonly CancellationTokenSource CancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
private static DateTime _lastLogTime = DateTime.MinValue;
|
||||
private static long _messageCount;
|
||||
private static bool _boardStarting;
|
||||
|
||||
private static void Main()
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
private static void ReadSerial(SerialPort serialPort, IModel model)
|
||||
{
|
||||
while (!CancellationTokenSource.Token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
_messageCount++;
|
||||
|
||||
if ((DateTime.Now - _lastLogTime).TotalMinutes < 1)
|
||||
continue;
|
||||
|
||||
WriteLog($"Number of messages received since {_lastLogTime} = {_messageCount}");
|
||||
|
||||
_lastLogTime = DateTime.Now;
|
||||
_messageCount = 0;
|
||||
}
|
||||
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 = ports.FirstOrDefault(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Weather/SerialReader/Properties/launchSettings.json
Normal file
12
Weather/SerialReader/Properties/launchSettings.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"profiles": {
|
||||
"SerialReader": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": false,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "http://localhost:62648/"
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Weather/SerialReader/SerialReader.csproj
Normal file
27
Weather/SerialReader/SerialReader.csproj
Normal file
@@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage>
|
||||
<AssemblyName>Weather.SerialReader</AssemblyName>
|
||||
<RootNamespace>Weather.SerialReader</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.0.0-preview6.19304.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="3.0.0-preview6.19304.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.0-preview6.19304.6" />
|
||||
<PackageReference Include="RabbitMQ.Client" Version="5.1.0" />
|
||||
<PackageReference Include="System.IO.Ports" Version="4.6.0-preview5.19224.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Models\Models.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
6
Weather/SerialReader/SerialReader.csproj.user
Normal file
6
Weather/SerialReader/SerialReader.csproj.user
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ActiveDebugProfile>IIS Express</ActiveDebugProfile>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
5
Weather/SerialReader/SerialReader.sln.DotSettings.user
Normal file
5
Weather/SerialReader/SerialReader.sln.DotSettings.user
Normal file
@@ -0,0 +1,5 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/AutoDetectedNamingRules/=Locals/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/AutoDetectedNamingRules/=EnumMember/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/AutoDetectedNamingRules/=Property/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:Int64 x:Key="/Default/CodeStyle/Naming/CSharpAutoNaming/AutoNamingCompletedVersion/@EntryValue">2</s:Int64></wpf:ResourceDictionary>
|
||||
11
Weather/SerialReader/appsettings.json
Normal file
11
Weather/SerialReader/appsettings.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"Weather": {
|
||||
"Port": {
|
||||
"Prefix": "/dev/ttyACM",
|
||||
"BaudRate": 9600
|
||||
},
|
||||
"Queue": {
|
||||
"Name": "weather"
|
||||
}
|
||||
}
|
||||
}
|
||||
68
Weather/SerialReader/deploy/queue.yaml
Normal file
68
Weather/SerialReader/deploy/queue.yaml
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
kind: StatefulSet
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: weather-queue
|
||||
namespace: home-monitor
|
||||
labels:
|
||||
app: weather-queue
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: weather-queue
|
||||
serviceName: weather-queue
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: weather-queue
|
||||
spec:
|
||||
containers:
|
||||
- name: weather-queue
|
||||
image: rabbitmq:3.7.16-management-alpine
|
||||
terminationMessagePath: "/dev/termination-log"
|
||||
terminationMessagePolicy: File
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: RABBITMQ_DEFAULT_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: weather-queue-credentials
|
||||
key: username
|
||||
- name: RABBITMQ_DEFAULT_PASS
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: weather-queue-credentials
|
||||
key: password
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /var/lib/rabbitmq
|
||||
restartPolicy: Always
|
||||
terminationGracePeriodSeconds: 30
|
||||
dnsPolicy: ClusterFirst
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: weather
|
||||
schedulerName: default-scheduler
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: data
|
||||
spec:
|
||||
accessModes: [ "ReadWriteOnce" ]
|
||||
storageClassName: local-path
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: weather-queue
|
||||
spec:
|
||||
ports:
|
||||
- name: client
|
||||
port: 5672
|
||||
- name: http
|
||||
port: 15672
|
||||
selector:
|
||||
app: weather-queue
|
||||
type: ClusterIP
|
||||
45
Weather/SerialReader/deploy/service.yaml
Normal file
45
Weather/SerialReader/deploy/service.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
kind: Deployment
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: weather-serial-reader
|
||||
namespace: home-monitor
|
||||
labels:
|
||||
app: weather-serial-reader
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: weather-serial-reader
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: weather-serial-reader
|
||||
spec:
|
||||
containers:
|
||||
- name: weather-serial-reader
|
||||
image: ckaczor/home-monitor-weather-serialreader:latest
|
||||
terminationMessagePath: "/dev/termination-log"
|
||||
terminationMessagePolicy: File
|
||||
imagePullPolicy: Always
|
||||
securityContext:
|
||||
privileged: true
|
||||
env:
|
||||
- name: Weather__Queue__Host
|
||||
value: weather-queue
|
||||
- name: Weather__Queue__User
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: weather-queue-credentials
|
||||
key: username
|
||||
- name: Weather__Queue__Password
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: weather-queue-credentials
|
||||
key: password
|
||||
restartPolicy: Always
|
||||
terminationGracePeriodSeconds: 30
|
||||
dnsPolicy: ClusterFirst
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: weather
|
||||
schedulerName: default-scheduler
|
||||
30
Weather/Service/.vscode/launch.json
vendored
Normal file
30
Weather/Service/.vscode/launch.json
vendored
Normal 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/Weather/Service/Weather.Service.dll"
|
||||
],
|
||||
"cwd": "/home/ckaczor/Weather/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
Weather/Service/.vscode/tasks.json
vendored
Normal file
35
Weather/Service/.vscode/tasks.json
vendored
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
31
Weather/Service/Controllers/ValuesController.cs
Normal file
31
Weather/Service/Controllers/ValuesController.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Weather.Service.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class ValuesController : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public ActionResult<IEnumerable<string>> Get()
|
||||
{
|
||||
return new[] { "value1", "value2" };
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public ActionResult<string> Get(int id)
|
||||
{
|
||||
return "value";
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public void Post([FromBody] string value) { }
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public void Put(int id, [FromBody] string value) { }
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public void Delete(int id) { }
|
||||
}
|
||||
}
|
||||
81
Weather/Service/Data/Database.cs
Normal file
81
Weather/Service/Data/Database.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using Dapper;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Npgsql;
|
||||
using Weather.Models;
|
||||
|
||||
namespace Weather.Service.Data
|
||||
{
|
||||
public class Database
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public Database(IConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public void EnsureDatabase()
|
||||
{
|
||||
var connectionStringBuilder = new NpgsqlConnectionStringBuilder
|
||||
{
|
||||
Host = _configuration["Weather:Database:Host"],
|
||||
Username = _configuration["Weather:Database:User"],
|
||||
Password = _configuration["Weather:Database:Password"],
|
||||
Database = "postgres"
|
||||
};
|
||||
|
||||
using (var connection = new NpgsqlConnection(connectionStringBuilder.ConnectionString))
|
||||
{
|
||||
var command = new NpgsqlCommand { Connection = connection };
|
||||
|
||||
connection.Open();
|
||||
|
||||
// Check to see if the database exists
|
||||
command.CommandText = $"SELECT TRUE from pg_database WHERE datname='{_configuration["Weather:Database:Name"]}'";
|
||||
var databaseExists = (bool?)command.ExecuteScalar();
|
||||
|
||||
// Create database if needed
|
||||
if (!databaseExists.GetValueOrDefault(false))
|
||||
{
|
||||
command.CommandText = $"CREATE DATABASE {_configuration["Weather:Database:Name"]}";
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
// Switch to the database now that we're sure it exists
|
||||
connection.ChangeDatabase(_configuration["Weather:Database:Name"]);
|
||||
|
||||
var schema = ResourceReader.GetString("Weather.Service.Data.Resources.Schema.sql");
|
||||
|
||||
// Make sure the database is up to date
|
||||
command.CommandText = schema;
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
|
||||
private NpgsqlConnection CreateConnection()
|
||||
{
|
||||
var connectionStringBuilder = new NpgsqlConnectionStringBuilder
|
||||
{
|
||||
Host = _configuration["Weather:Database:Host"],
|
||||
Username = _configuration["Weather:Database:User"],
|
||||
Password = _configuration["Weather:Database:Password"],
|
||||
Database = _configuration["Weather:Database:Name"]
|
||||
};
|
||||
|
||||
var connection = new NpgsqlConnection(connectionStringBuilder.ConnectionString);
|
||||
connection.Open();
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
public void StoreWeatherData(WeatherMessage weatherMessage)
|
||||
{
|
||||
using (var connection = CreateConnection())
|
||||
{
|
||||
var query = ResourceReader.GetString("Weather.Service.Data.Resources.CreateReading.sql");
|
||||
|
||||
connection.Execute(query, weatherMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
6
Weather/Service/Data/Resources/CreateReading.sql
Normal file
6
Weather/Service/Data/Resources/CreateReading.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
INSERT INTO weather_reading (timestamp, wind_direction, wind_speed, humidity, humidity_temperature, rain, pressure,
|
||||
pressure_temperature, battery_level, light_level, latitude, longitude, altitude,
|
||||
satellite_count, gps_timestamp)
|
||||
VALUES (:timestamp, :windDirection, :windSpeed, :humidity, :humidityTemperature, :rain, :pressure, :pressureTemperature,
|
||||
:batteryLevel, :lightLevel, :latitude, :longitude, :altitude, :satelliteCount, :gpsTimestamp)
|
||||
ON CONFLICT DO NOTHING
|
||||
24
Weather/Service/Data/Resources/Schema.sql
Normal file
24
Weather/Service/Data/Resources/Schema.sql
Normal file
@@ -0,0 +1,24 @@
|
||||
CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS weather_reading
|
||||
(
|
||||
timestamp timestamptz NOT NULL
|
||||
CONSTRAINT weather_reading_pk
|
||||
PRIMARY KEY,
|
||||
wind_direction int NOT NULL,
|
||||
wind_speed double precision NOT NULL,
|
||||
humidity double precision NOT NULL,
|
||||
humidity_temperature double precision NOT NULL,
|
||||
rain double precision NOT NULL,
|
||||
pressure double precision NOT NULL,
|
||||
pressure_temperature double precision NOT NULL,
|
||||
battery_level double precision NOT NULL,
|
||||
light_level double precision NOT NULL,
|
||||
latitude double precision NOT NULL,
|
||||
longitude double precision NOT NULL,
|
||||
altitude double precision NOT NULL,
|
||||
satellite_count double precision NOT NULL,
|
||||
gps_timestamp timestamptz NOT NULL
|
||||
);
|
||||
|
||||
SELECT create_hypertable('weather_reading', 'timestamp', if_not_exists => TRUE);
|
||||
119
Weather/Service/MessageHandler.cs
Normal file
119
Weather/Service/MessageHandler.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Newtonsoft.Json;
|
||||
using RabbitMQ.Client;
|
||||
using RabbitMQ.Client.Events;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Weather.Models;
|
||||
using Weather.Service.Data;
|
||||
|
||||
namespace Weather.Service
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class MessageHandler : IHostedService
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly Database _database;
|
||||
|
||||
private IConnection _queueConnection;
|
||||
private IModel _queueModel;
|
||||
|
||||
private HubConnection _hubConnection;
|
||||
|
||||
private static DateTime _lastLogTime = DateTime.MinValue;
|
||||
private static long _messageCount;
|
||||
|
||||
public MessageHandler(IConfiguration configuration, Database database)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_database = database;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
WriteLog("MessageHandler: Start");
|
||||
|
||||
var factory = new ConnectionFactory
|
||||
{
|
||||
HostName = _configuration["Weather:Queue:Host"],
|
||||
UserName = _configuration["Weather:Queue:User"],
|
||||
Password = _configuration["Weather:Queue:Password"]
|
||||
};
|
||||
|
||||
_queueConnection = factory.CreateConnection();
|
||||
_queueModel = _queueConnection.CreateModel();
|
||||
|
||||
_queueModel.QueueDeclare(_configuration["Weather:Queue:Name"], true, false, false, null);
|
||||
|
||||
var consumer = new EventingBasicConsumer(_queueModel);
|
||||
consumer.Received += DeviceEventHandler;
|
||||
|
||||
_queueModel.BasicConsume(_configuration["Weather:Queue:Name"], true, consumer);
|
||||
|
||||
_hubConnection = new HubConnectionBuilder().WithUrl(_configuration["Hub:Weather"]).Build();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
WriteLog("MessageHandler: Stop");
|
||||
|
||||
_hubConnection.StopAsync(cancellationToken).Wait(cancellationToken);
|
||||
|
||||
_queueModel.Close();
|
||||
_queueConnection.Close();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void DeviceEventHandler(object model, BasicDeliverEventArgs eventArgs)
|
||||
{
|
||||
var body = eventArgs.Body;
|
||||
var message = Encoding.UTF8.GetString(body);
|
||||
|
||||
_messageCount++;
|
||||
|
||||
if ((DateTime.Now - _lastLogTime).TotalMinutes >= 1)
|
||||
{
|
||||
WriteLog($"Number of messages received since {_lastLogTime} = {_messageCount}");
|
||||
|
||||
_lastLogTime = DateTime.Now;
|
||||
_messageCount = 0;
|
||||
}
|
||||
|
||||
var weatherMessage = JsonConvert.DeserializeObject<WeatherMessage>(message);
|
||||
|
||||
if (weatherMessage.Type == MessageType.Text)
|
||||
{
|
||||
WriteLog(weatherMessage.Message);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_database.StoreWeatherData(weatherMessage);
|
||||
|
||||
try
|
||||
{
|
||||
if (_hubConnection.State == HubConnectionState.Disconnected)
|
||||
_hubConnection.StartAsync().Wait();
|
||||
|
||||
_hubConnection.InvokeAsync("SendLatestReading", message).Wait();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
WriteLog($"Hub exception: {exception}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteLog(string message)
|
||||
{
|
||||
Console.WriteLine(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Weather/Service/Program.cs
Normal file
22
Weather/Service/Program.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Weather.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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Weather/Service/Properties/launchSettings.json
Normal file
13
Weather/Service/Properties/launchSettings.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"Service": {
|
||||
"commandName": "Project",
|
||||
"launchUrl": "api/values",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "http://localhost:5001"
|
||||
}
|
||||
}
|
||||
}
|
||||
26
Weather/Service/ResourceReader.cs
Normal file
26
Weather/Service/ResourceReader.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Weather.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
Weather/Service/Service.csproj
Normal file
33
Weather/Service/Service.csproj
Normal file
@@ -0,0 +1,33 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
|
||||
<AssemblyName>Weather.Service</AssemblyName>
|
||||
<RootNamespace>Weather.Service</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Data\Resources\CreateReading.sql" />
|
||||
<None Remove="Data\Resources\Schema.sql" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Data\Resources\CreateReading.sql" />
|
||||
<EmbeddedResource Include="Data\Resources\Schema.sql" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="1.60.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="1.1.0" />
|
||||
<PackageReference Include="Npgsql" Version="4.0.7" />
|
||||
<PackageReference Include="RabbitMQ.Client" Version="5.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Models\Models.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
9
Weather/Service/Service.csproj.user
Normal file
9
Weather/Service/Service.csproj.user
Normal 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>
|
||||
31
Weather/Service/Startup.cs
Normal file
31
Weather/Service/Startup.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Weather.Service.Data;
|
||||
|
||||
namespace Weather.Service
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddTransient<Database>();
|
||||
|
||||
services.AddHostedService<MessageHandler>();
|
||||
|
||||
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder applicationBuilder, IHostingEnvironment environment)
|
||||
{
|
||||
if (environment.IsDevelopment())
|
||||
applicationBuilder.UseDeveloperExceptionPage();
|
||||
|
||||
var database = applicationBuilder.ApplicationServices.GetService<Database>();
|
||||
database.EnsureDatabase();
|
||||
|
||||
applicationBuilder.UseMvc();
|
||||
}
|
||||
}
|
||||
}
|
||||
10
Weather/Service/appsettings.Development.json
Normal file
10
Weather/Service/appsettings.Development.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
18
Weather/Service/appsettings.json
Normal file
18
Weather/Service/appsettings.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
},
|
||||
"Weather": {
|
||||
"Database": {
|
||||
"Name": "weather"
|
||||
},
|
||||
"Queue": {
|
||||
"Name": "weather"
|
||||
}
|
||||
},
|
||||
"Hub": {
|
||||
"Weather": "http://hub-server/weatherHub"
|
||||
}
|
||||
}
|
||||
68
Weather/Service/deploy/database.yaml
Normal file
68
Weather/Service/deploy/database.yaml
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
kind: StatefulSet
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: weather-database
|
||||
namespace: home-monitor
|
||||
labels:
|
||||
app: weather-database
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: weather-database
|
||||
serviceName: weather-database
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: weather-database
|
||||
spec:
|
||||
containers:
|
||||
- name: weather-database
|
||||
image: timescale/timescaledb
|
||||
terminationMessagePath: "/dev/termination-log"
|
||||
terminationMessagePolicy: File
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: POSTGRES_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: weather-database-credentials
|
||||
key: username
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: weather-database-credentials
|
||||
key: password
|
||||
- name: POSTGRES_DB
|
||||
value: weather
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /var/lib/postgresql/data
|
||||
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: weather-database
|
||||
spec:
|
||||
ports:
|
||||
- name: client
|
||||
port: 5432
|
||||
selector:
|
||||
app: weather-database
|
||||
type: ClusterIP
|
||||
59
Weather/Service/deploy/service.yaml
Normal file
59
Weather/Service/deploy/service.yaml
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
kind: Deployment
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: weather-service
|
||||
namespace: home-monitor
|
||||
labels:
|
||||
app: weather-service
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: weather-service
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: weather-service
|
||||
spec:
|
||||
containers:
|
||||
- name: weather-service
|
||||
image: ckaczor/home-monitor-weather-service:latest
|
||||
terminationMessagePath: "/dev/termination-log"
|
||||
terminationMessagePolicy: File
|
||||
imagePullPolicy: Always
|
||||
securityContext:
|
||||
privileged: true
|
||||
env:
|
||||
- name: Weather__Queue__Host
|
||||
value: weather-queue
|
||||
- name: Weather__Queue__User
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: weather-queue-credentials
|
||||
key: username
|
||||
- name: Weather__Queue__Password
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: weather-queue-credentials
|
||||
key: password
|
||||
- name: Weather__Database__Host
|
||||
value: weather-database
|
||||
- name: Weather__Database__User
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: weather-database-credentials
|
||||
key: username
|
||||
- name: Weather__Database__Password
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: weather-database-credentials
|
||||
key: password
|
||||
- name: Hub__Weather
|
||||
value: http://hub-service/weatherHub
|
||||
restartPolicy: Always
|
||||
terminationGracePeriodSeconds: 30
|
||||
dnsPolicy: ClusterFirst
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: kubernetes
|
||||
schedulerName: default-scheduler
|
||||
37
Weather/Weather.sln
Normal file
37
Weather/Weather.sln
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.28705.295
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SerialReader", "SerialReader\SerialReader.csproj", "{63142748-213D-4BD3-9A15-8E5C405718B4}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models", "Models\Models.csproj", "{39E5DE26-CD8D-47AF-AB94-6ACB0AF24EA1}"
|
||||
EndProject
|
||||
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
|
||||
7
Weather/Weather.sln.DotSettings
Normal file
7
Weather/Weather.sln.DotSettings
Normal file
@@ -0,0 +1,7 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_SINGLE_LINE_ARRAY_INITIALIZER_BRACES/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=appsettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
5
Weather/Weather.sln.DotSettings.user
Normal file
5
Weather/Weather.sln.DotSettings.user
Normal file
@@ -0,0 +1,5 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/AutoDetectedNamingRules/=Locals/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/AutoDetectedNamingRules/=EnumMember/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/AutoDetectedNamingRules/=Property/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:Int64 x:Key="/Default/CodeStyle/Naming/CSharpAutoNaming/AutoNamingCompletedVersion/@EntryValue">2</s:Int64></wpf:ResourceDictionary>
|
||||
Reference in New Issue
Block a user