From d6e223063b41215582a47a3094aa95ebe9cf874d Mon Sep 17 00:00:00 2001 From: Mark Crossley Date: Tue, 3 Dec 2019 10:55:01 +0000 Subject: [PATCH] b3058 --- .vs/slnx.sqlite | Bin 5185536 -> 5185536 bytes CumulusMX/Api.cs | 14 + CumulusMX/Cumulus.cs | 8 +- CumulusMX/CumulusMX.csproj | 4 +- CumulusMX/DataEditor.cs | 1899 ++++++++++++++++++++------ CumulusMX/DavisWllStation.cs | 506 +++---- CumulusMX/GW1000Station.cs | 261 ++-- CumulusMX/Properties/AssemblyInfo.cs | 6 +- CumulusMX/WeatherStation.cs | 25 + Updates.txt | 17 + 10 files changed, 1949 insertions(+), 791 deletions(-) diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite index becfeafd8cf85bd306e4fba81e37687174744b37..d78f042efc01fd7781a5ea293a80aafe3f8bdfa3 100644 GIT binary patch delta 98502 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 Z=T#w=1!Cc&SA`ucU?ENB>`lE{|G!UtP4WN$ delta 105771 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 diff --git a/CumulusMX/Api.cs b/CumulusMX/Api.cs index 39bf6cb7..b8c074bf 100644 --- a/CumulusMX/Api.cs +++ b/CumulusMX/Api.cs @@ -134,6 +134,16 @@ public bool EditData() case "alltimerecordslogfile.json": return this.JsonResponse(dataEditor.GetAllTimeRecLogFile()); + + case "monthlyrecords.json": + return this.JsonResponse(dataEditor.GetMonthlyRecData()); + + case "monthlyrecordsdayfile.json": + return this.JsonResponse(dataEditor.GetMonthlyRecDayFile()); + + case "monthlyrecordslogfile.json": + return this.JsonResponse(dataEditor.GetMonthlyRecLogFile()); + } throw new KeyNotFoundException("Key Not Found: " + lastSegment); @@ -192,6 +202,10 @@ public bool EditData() case "alltime": return this.JsonResponse(dataEditor.EditAllTimeRecs(this)); + + case "monthly": + return this.JsonResponse(dataEditor.EditMonthlyRecs(this)); + } throw new KeyNotFoundException("Key Not Found: " + lastSegment); diff --git a/CumulusMX/Cumulus.cs b/CumulusMX/Cumulus.cs index 3df27823..1a350ea4 100644 --- a/CumulusMX/Cumulus.cs +++ b/CumulusMX/Cumulus.cs @@ -29,8 +29,8 @@ namespace CumulusMX public class Cumulus { ///////////////////////////////// - public string Version = "3.2.1"; - public string Build = "3057"; + public string Version = "3.2.2"; + public string Build = "3058"; ///////////////////////////////// private static string appGuid = "57190d2e-7e45-4efb-8c09-06a176cef3f3"; @@ -1291,9 +1291,9 @@ public Cumulus(int HTTPport, int WSport) LogMessage("RainDayThreshold=" + RainDayThreshold.ToString("F3")); LogMessage("Offsets and Multipliers:"); LogMessage("PO=" + PressOffset.ToString("F3") + " TO=" + TempOffset.ToString("F3") + " HO=" + HumOffset + " WDO=" + WindDirOffset + " ITO=" + - InTempoffset.ToString("F3") + "SO=" + SolarOffset.ToString("F3") + " UVO=" + UVOffset.ToString("F3")); + InTempoffset.ToString("F3") + " SO=" + SolarOffset.ToString("F3") + " UVO=" + UVOffset.ToString("F3")); LogMessage("PM=" + PressMult.ToString("F3") + " WSM=" + WindSpeedMult.ToString("F3") + " WGM=" + WindGustMult.ToString("F3") + " TM=" + TempMult.ToString("F3") + " TM2=" + TempMult2.ToString("F3") + - " HM=" + HumMult.ToString("F3") + " HM2=" + HumMult2.ToString("F3") + " RM=" + RainMult.ToString("F3") + "SM=" + SolarMult.ToString("F3") + " UVM=" + UVMult.ToString("F3")); + " HM=" + HumMult.ToString("F3") + " HM2=" + HumMult2.ToString("F3") + " RM=" + RainMult.ToString("F3") + " SM=" + SolarMult.ToString("F3") + " UVM=" + UVMult.ToString("F3")); LogMessage("Spike removal:"); LogMessage("TD=" + EWtempdiff.ToString("F3") + " GD=" + EWgustdiff.ToString("F3") + " WD=" + EWwinddiff.ToString("F3") + " HD=" + EWhumiditydiff.ToString("F3") + " PD=" + EWpressurediff.ToString("F3")); diff --git a/CumulusMX/CumulusMX.csproj b/CumulusMX/CumulusMX.csproj index 762e8e4a..0a1ac3cc 100644 --- a/CumulusMX/CumulusMX.csproj +++ b/CumulusMX/CumulusMX.csproj @@ -24,8 +24,8 @@ false false true - 3057 - 3.2.1.3057 + 3058 + 3.2.2.3058 false true true diff --git a/CumulusMX/DataEditor.cs b/CumulusMX/DataEditor.cs index 59643de2..1ef8b989 100644 --- a/CumulusMX/DataEditor.cs +++ b/CumulusMX/DataEditor.cs @@ -4,22 +4,18 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Net; -using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; using Unosquare.Labs.EmbedIO; -using SQLite; namespace CumulusMX { internal class DataEditor { - private WeatherStation station; - private Cumulus cumulus; - private WebTags webtags; + private readonly WeatherStation station; + private readonly Cumulus cumulus; + private readonly WebTags webtags; - private List HourRainLog = new List(); + private readonly List hourRainLog = new List(); internal DataEditor(Cumulus cumulus, WeatherStation station, WebTags webtags) { @@ -31,7 +27,7 @@ internal DataEditor(Cumulus cumulus, WeatherStation station, WebTags webtags) //internal string EditRainToday(HttpListenerContext context) internal string EditRainToday(IHttpContext context) { - var InvC = new CultureInfo(""); + var invC = new CultureInfo(""); var request = context.Request; string text; using (var reader = new StreamReader(request.InputStream, request.ContentEncoding)) @@ -39,15 +35,14 @@ internal string EditRainToday(IHttpContext context) text = reader.ReadToEnd(); } - string[] kvPair = text.Split('='); - string key = kvPair[0]; - string raintodaystring = kvPair[1]; + var kvPair = text.Split('='); + var raintodaystring = kvPair[1]; - if (!String.IsNullOrEmpty(raintodaystring)) + if (!string.IsNullOrEmpty(raintodaystring)) { try { - double raintoday = Double.Parse(raintodaystring, CultureInfo.InvariantCulture); + var raintoday = double.Parse(raintodaystring, CultureInfo.InvariantCulture); cumulus.LogMessage("Before rain today edit, raintoday=" + station.RainToday.ToString(cumulus.RainFormat) + " Raindaystart=" + station.raindaystart.ToString(cumulus.RainFormat)); station.RainToday = raintoday; station.raindaystart = station.Raincounter - (station.RainToday / cumulus.RainMult); @@ -59,22 +54,22 @@ internal string EditRainToday(IHttpContext context) } } - var json = "{\"raintoday\":\"" + station.RainToday.ToString(cumulus.RainFormat, InvC) + - "\",\"raincounter\":\"" + station.Raincounter.ToString(cumulus.RainFormat, InvC) + - "\",\"startofdayrain\":\"" + station.raindaystart.ToString(cumulus.RainFormat, InvC) + - "\",\"rainmult\":\"" + cumulus.RainMult.ToString("F3", InvC) + "\"}"; + var json = "{\"raintoday\":\"" + station.RainToday.ToString(cumulus.RainFormat, invC) + + "\",\"raincounter\":\"" + station.Raincounter.ToString(cumulus.RainFormat, invC) + + "\",\"startofdayrain\":\"" + station.raindaystart.ToString(cumulus.RainFormat, invC) + + "\",\"rainmult\":\"" + cumulus.RainMult.ToString("F3", invC) + "\"}"; return json; } internal string GetRainTodayEditData() { - var InvC = new CultureInfo(""); - string step = (cumulus.RainDPlaces == 1 ? "0.1" : "0.01"); - var json = "{\"raintoday\":\"" + station.RainToday.ToString(cumulus.RainFormat, InvC) + - "\",\"raincounter\":\"" + station.Raincounter.ToString(cumulus.RainFormat, InvC) + - "\",\"startofdayrain\":\"" + station.raindaystart.ToString(cumulus.RainFormat, InvC) + - "\",\"rainmult\":\"" + cumulus.RainMult.ToString("F3", InvC) + + var invC = new CultureInfo(""); + var step = (cumulus.RainDPlaces == 1 ? "0.1" : "0.01"); + var json = "{\"raintoday\":\"" + station.RainToday.ToString(cumulus.RainFormat, invC) + + "\",\"raincounter\":\"" + station.Raincounter.ToString(cumulus.RainFormat, invC) + + "\",\"startofdayrain\":\"" + station.raindaystart.ToString(cumulus.RainFormat, invC) + + "\",\"rainmult\":\"" + cumulus.RainMult.ToString("F3", invC) + "\",\"step\":\"" + step + "\"}"; return json; @@ -136,9 +131,8 @@ internal string DeleteDiary(IHttpContext context) internal string GetAllTimeRecData() { - var timeStampFormat = "dd/MM/yy HH:mm"; - var dateStampFormat = "dd/MM/yy"; - var InvC = new CultureInfo(""); + const string timeStampFormat = "dd/MM/yy HH:mm"; + const string dateStampFormat = "dd/MM/yy"; // Records - Temperature values var json = "{\"highTempVal\":\"" + station.alltimerecarray[WeatherStation.AT_hightemp].value.ToString(cumulus.TempFormat) + "\","; json += "\"lowTempVal\":\"" + station.alltimerecarray[WeatherStation.AT_lowtemp].value.ToString(cumulus.TempFormat) + "\","; @@ -205,69 +199,68 @@ internal string GetAllTimeRecData() internal string GetAllTimeRecDayFile() { - var timeStampFormat = "dd/MM/yy HH:mm"; - var dateStampFormat = "dd/MM/yy"; - - int linenum = 0; - string LogFile = cumulus.Datapath + cumulus.GetLogFileName(cumulus.LastUpdateTime); - double highTempVal = -999; - double lowTempVal = 999; - double highDewPtVal = -999; - double lowDewPtVal = 999; - double highAppTempVal = -999; - double lowAppTempVal = 999; - double lowWindChillVal = 999; - double highHeatIndVal = -999; - double highMinTempVal = -999; - double lowMaxTempVal = 999; - double highTempRangeVal = -999; - double lowTempRangeVal = 999; - double highHumVal = -999; - double lowHumVal = 999; - double highBaroVal = -999; - double lowBaroVal = 99999; - double highGustVal = -999; - double highWindVal = -999; - double highWindRunVal = -999; - double highRainRateVal = -999; - double highRainHourVal = -999; - double highRainDayVal = -999; - double highRainMonthVal = -999; - int dryPeriodVal = 0; - int wetPeriodVal = 0; - DateTime highTempTime = new DateTime(1900, 01, 01); - DateTime lowTempTime = highTempTime; - DateTime highDewPtTime = highTempTime; - DateTime lowDewPtTime = highTempTime; - DateTime highAppTempTime = highTempTime; - DateTime lowAppTempTime = highTempTime; - DateTime lowWindChillTime = highTempTime; - DateTime highHeatIndTime = highTempTime; - DateTime highMinTempTime = highTempTime; - DateTime lowMaxTempTime = highTempTime; - DateTime highTempRangeTime = highTempTime; - DateTime lowTempRangeTime = highTempTime; - DateTime highHumTime = highTempTime; - DateTime lowHumTime = highTempTime; - DateTime highBaroTime = highTempTime; - DateTime lowBaroTime = highTempTime; - DateTime highGustTime = highTempTime; - DateTime highWindTime = highTempTime; - DateTime highWindRunTime = highTempTime; - DateTime highRainRateTime = highTempTime; - DateTime highRainHourTime = highTempTime; - DateTime highRainDayTime = highTempTime; - DateTime highRainMonthTime = highTempTime; - DateTime dryPeriodTime = highTempTime; - DateTime wetPeriodTime = highTempTime; - - DateTime thisDate = highTempTime; - double rainThisMonth = 0; - int currentDryPeriod = 0; - int currentWetPeriod = 0; - bool isDryNow = false; - DateTime thisDateDry = highTempTime; - DateTime thisDateWet = highTempTime; + const string timeStampFormat = "dd/MM/yy HH:mm"; + const string dateStampFormat = "dd/MM/yy"; + + var linenum = 0; + var highTempVal = -999.0; + var lowTempVal = 999.0; + var highDewPtVal = highTempVal; + var lowDewPtVal = lowTempVal; + var highAppTempVal = highTempVal; + var lowAppTempVal = lowTempVal; + var lowWindChillVal = lowTempVal; + var highHeatIndVal = highTempVal; + var highMinTempVal = highTempVal; + var lowMaxTempVal = lowTempVal; + var highTempRangeVal = highTempVal; + var lowTempRangeVal = lowTempVal; + var highHumVal = highTempVal; + var lowHumVal = lowTempVal; + var highBaroVal = highTempVal; + var lowBaroVal = 99999.0; + var highGustVal = highTempVal; + var highWindVal = highTempVal; + var highWindRunVal = highTempVal; + var highRainRateVal = highTempVal; + var highRainHourVal = highTempVal; + var highRainDayVal = highTempVal; + var highRainMonthVal = highTempVal; + var dryPeriodVal = 0; + var wetPeriodVal = 0; + var highTempTime = new DateTime(1900, 01, 01); + var lowTempTime = highTempTime; + var highDewPtTime = highTempTime; + var lowDewPtTime = highTempTime; + var highAppTempTime = highTempTime; + var lowAppTempTime = highTempTime; + var lowWindChillTime = highTempTime; + var highHeatIndTime = highTempTime; + var highMinTempTime = highTempTime; + var lowMaxTempTime = highTempTime; + var highTempRangeTime = highTempTime; + var lowTempRangeTime = highTempTime; + var highHumTime = highTempTime; + var lowHumTime = highTempTime; + var highBaroTime = highTempTime; + var lowBaroTime = highTempTime; + var highGustTime = highTempTime; + var highWindTime = highTempTime; + var highWindRunTime = highTempTime; + var highRainRateTime = highTempTime; + var highRainHourTime = highTempTime; + var highRainDayTime = highTempTime; + var highRainMonthTime = highTempTime; + var dryPeriodTime = highTempTime; + var wetPeriodTime = highTempTime; + + var thisDate = highTempTime; + var rainThisMonth = 0.0; + var currentDryPeriod = 0; + var currentWetPeriod = 0; + var isDryNow = false; + var thisDateDry = highTempTime; + var thisDateWet = highTempTime; var json = "{"; double rainThreshold = 0; @@ -281,167 +274,176 @@ internal string GetAllTimeRecDayFile() { try { - string[] dayfile = File.ReadAllLines(cumulus.DayFile); + var dayfile = File.ReadAllLines(cumulus.DayFile); - foreach (string line in dayfile) + foreach (var line in dayfile) { linenum++; var st = new List(Regex.Split(line, CultureInfo.CurrentCulture.TextInfo.ListSeparator)); - if (st.Count > 0) - { - string datestr = st[0]; - DateTime loggedDate = station.ddmmyyStrToDate(datestr); + if (st.Count <= 0) continue; - // This assumes the day file is in date order! - if (thisDate.Month != loggedDate.Month) - { - // monthly rain - if (rainThisMonth > highRainMonthVal) - { - highRainMonthVal = rainThisMonth; - highRainMonthTime = thisDate; - } - // reset the date and counter for a new month - thisDate = loggedDate; - rainThisMonth = 0; - } - // hi temp - if (Double.Parse(st[6]) > highTempVal) - { - highTempVal = Double.Parse(st[6]); - highTempTime = getDateTime(loggedDate, st[7]); - } - // lo temp - if (Double.Parse(st[4]) < lowTempVal) - { - lowTempVal = Double.Parse(st[4]); - lowTempTime = getDateTime(loggedDate, st[5]); - } - // hi dewpt - if (Double.Parse(st[35]) > highDewPtVal) - { - highDewPtVal = Double.Parse(st[35]); - highDewPtTime = getDateTime(loggedDate, st[36]); - } - // lo dewpt - if (Double.Parse(st[37]) < lowDewPtVal) - { - lowDewPtVal = Double.Parse(st[37]); - lowDewPtTime = getDateTime(loggedDate, st[38]); - } - // hi app temp - if (Double.Parse(st[27]) > highAppTempVal) - { - highAppTempVal = Double.Parse(st[27]); - highAppTempTime = getDateTime(loggedDate, st[28]); - } - // lo app temp - if (Double.Parse(st[29]) < lowAppTempVal) - { - lowAppTempVal = Double.Parse(st[29]); - lowAppTempTime = getDateTime(loggedDate, st[30]); - } - // lo wind chill - if (Double.Parse(st[33]) < lowWindChillVal) - { - lowWindChillVal = Double.Parse(st[33]); - lowWindChillTime = getDateTime(loggedDate, st[34]); - } - // hi heat index - if (Double.Parse(st[25]) > highHeatIndVal) - { - highHeatIndVal = Double.Parse(st[25]); - highHeatIndTime = getDateTime(loggedDate, st[26]); - } - // hi min temp - if (Double.Parse(st[4]) > highMinTempVal) - { - highMinTempVal = Double.Parse(st[4]); - highMinTempTime = loggedDate; - } - // lo max temp - if (Double.Parse(st[6]) < lowMaxTempVal) + var datestr = st[0]; + var loggedDate = station.ddmmyyStrToDate(datestr); + double valDbl, valDbl2; + + // This assumes the day file is in date order! + if (thisDate.Month != loggedDate.Month) + { + // monthly rain + if (rainThisMonth > highRainMonthVal) { - lowMaxTempVal = Double.Parse(st[6]); - lowMaxTempTime = loggedDate; + highRainMonthVal = rainThisMonth; + highRainMonthTime = thisDate; } + // reset the date and counter for a new month + thisDate = loggedDate; + rainThisMonth = 0; + } + // hi temp + if (double.TryParse(st[6], out valDbl) && valDbl > highTempVal) + { + highTempVal = valDbl; + highTempTime = GetDateTime(loggedDate, st[7]); + } + // lo temp + if (double.TryParse(st[4], out valDbl) && valDbl < lowTempVal) + { + lowTempVal = valDbl; + lowTempTime = GetDateTime(loggedDate, st[5]); + } + // hi dewpt + if (double.TryParse(st[35], out valDbl) && valDbl > highDewPtVal) + { + highDewPtVal = valDbl; + highDewPtTime = GetDateTime(loggedDate, st[36]); + } + // lo dewpt + if (double.TryParse(st[37], out valDbl) && valDbl < lowDewPtVal) + { + lowDewPtVal = valDbl; + lowDewPtTime = GetDateTime(loggedDate, st[38]); + } + // hi app temp + if (double.TryParse(st[27], out valDbl) && valDbl > highAppTempVal) + { + highAppTempVal = valDbl; + highAppTempTime = GetDateTime(loggedDate, st[28]); + } + // lo app temp + if (double.TryParse(st[29], out valDbl) && valDbl < lowAppTempVal) + { + lowAppTempVal = valDbl; + lowAppTempTime = GetDateTime(loggedDate, st[30]); + } + // lo wind chill + if (double.TryParse(st[33], out valDbl) && valDbl < lowWindChillVal) + { + lowWindChillVal = valDbl; + lowWindChillTime = GetDateTime(loggedDate, st[34]); + } + // hi heat index + if (double.TryParse(st[25], out valDbl) && valDbl > highHeatIndVal) + { + highHeatIndVal = valDbl; + highHeatIndTime = GetDateTime(loggedDate, st[26]); + } + // hi min temp + if (double.TryParse(st[4], out valDbl) && valDbl > highMinTempVal) + { + highMinTempVal = valDbl; + highMinTempTime = loggedDate; + } + // lo max temp + if (double.TryParse(st[6], out valDbl) && valDbl < lowMaxTempVal) + { + lowMaxTempVal = valDbl; + lowMaxTempTime = loggedDate; + } + // temp ranges + if (double.TryParse(st[6], out valDbl) && double.TryParse(st[4], out valDbl2)) + { // hi temp range - if (Double.Parse(st[6]) - Double.Parse(st[4]) > highTempRangeVal) + if ((valDbl - valDbl2) > highTempRangeVal) { - highTempRangeVal = Double.Parse(st[6]) - Double.Parse(st[4]); + highTempRangeVal = valDbl - valDbl2; highTempRangeTime = loggedDate; } // lo temp range - if (Double.Parse(st[6]) - Double.Parse(st[4]) < lowTempRangeVal) + if ((valDbl - valDbl2) < lowTempRangeVal) { - lowTempRangeVal = Double.Parse(st[6]) - Double.Parse(st[4]); + lowTempRangeVal = valDbl - valDbl2; lowTempRangeTime = loggedDate; } - // hi humidity - if (Double.Parse(st[21]) > highHumVal) - { - highHumVal = Double.Parse(st[21]); - highHumTime = getDateTime(loggedDate, st[22]); - } - // lo humidity - if (Double.Parse(st[19]) < lowHumVal) - { - lowHumVal = Double.Parse(st[19]); - lowHumTime = getDateTime(loggedDate, st[20]); - } - // hi baro - if (Double.Parse(st[10]) > highBaroVal) - { - highBaroVal = Double.Parse(st[10]); - highBaroTime = getDateTime(loggedDate, st[11]); - } - // lo baro - if (Double.Parse(st[8]) < lowBaroVal) - { - lowBaroVal = Double.Parse(st[8]); - lowBaroTime = getDateTime(loggedDate, st[9]); - } - // hi gust - if (Double.Parse(st[1]) > highGustVal) - { - highGustVal = Double.Parse(st[1]); - highGustTime = getDateTime(loggedDate, st[3]); - } - // hi wind - if (Double.Parse(st[17]) > highWindVal) - { - highWindVal = Double.Parse(st[17]); - highWindTime = getDateTime(loggedDate, st[18]); - } - // hi wind run - if (Double.Parse(st[16]) > highWindRunVal) - { - highWindRunVal = Double.Parse(st[16]); - highWindRunTime = loggedDate; - } - // hi rain rate - if (Double.Parse(st[12]) > highRainRateVal) - { - highRainRateVal = Double.Parse(st[12]); - highRainRateTime = getDateTime(loggedDate, st[13]); - } - // hi rain hour - if (Double.Parse(st[31]) > highRainHourVal) - { - highRainHourVal = Double.Parse(st[31]); - highRainHourTime = getDateTime(loggedDate, st[32]); - } + } + // hi humidity + if (double.TryParse(st[21], out valDbl) && valDbl > highHumVal) + { + highHumVal = valDbl; + highHumTime = GetDateTime(loggedDate, st[22]); + } + // lo humidity + if (double.TryParse(st[19], out valDbl) && valDbl < lowHumVal) + { + lowHumVal = valDbl; + lowHumTime = GetDateTime(loggedDate, st[20]); + } + // hi baro + if (double.TryParse(st[10], out valDbl) && valDbl > highBaroVal) + { + highBaroVal = valDbl; + highBaroTime = GetDateTime(loggedDate, st[11]); + } + // lo baro + if (double.TryParse(st[8], out valDbl) && valDbl < lowBaroVal) + { + lowBaroVal = valDbl; + lowBaroTime = GetDateTime(loggedDate, st[9]); + } + // hi gust + if (double.TryParse(st[1], out valDbl) && valDbl > highGustVal) + { + highGustVal = valDbl; + highGustTime = GetDateTime(loggedDate, st[3]); + } + // hi wind + if (double.TryParse(st[17], out valDbl) && valDbl > highWindVal) + { + highWindVal = valDbl; + highWindTime = GetDateTime(loggedDate, st[18]); + } + // hi wind run + if (double.TryParse(st[16], out valDbl) && valDbl > highWindRunVal) + { + highWindRunVal = valDbl; + highWindRunTime = loggedDate; + } + // hi rain rate + if (double.TryParse(st[12], out valDbl) && valDbl > highRainRateVal) + { + highRainRateVal = valDbl; + highRainRateTime = GetDateTime(loggedDate, st[13]); + } + // hi rain hour + if (double.TryParse(st[31], out valDbl) && valDbl > highRainHourVal) + { + highRainHourVal = valDbl; + highRainHourTime = GetDateTime(loggedDate, st[32]); + } + if (double.TryParse(st[14], out valDbl)) + { // hi rain day - if (Double.Parse(st[14]) > highRainDayVal) + if (valDbl > highRainDayVal) { - highRainDayVal = Double.Parse(st[14]); + highRainDayVal = valDbl; highRainDayTime = loggedDate; } + // monthly rain - rainThisMonth += Double.Parse(st[14]); + rainThisMonth += valDbl; + // dry/wet period - if (Double.Parse(st[14]) > rainThreshold) + if (valDbl > rainThreshold) { if (isDryNow) { @@ -533,9 +535,9 @@ internal string GetAllTimeRecDayFile() json += "\"longestWetPeriodValDayfile\":\"" + wetPeriodVal.ToString() + "\","; json += "\"longestWetPeriodTimeDayfile\":\"" + wetPeriodTime.ToString(dateStampFormat) + "\"}"; } - catch (Exception E) + catch (Exception e) { - cumulus.LogMessage("Error on line " + linenum + " of " + cumulus.DayFile + ": " + E.Message); + cumulus.LogMessage("Error on line " + linenum + " of " + cumulus.DayFile + ": " + e.Message); } } else @@ -552,8 +554,8 @@ internal string GetAllTimeRecDayFile() internal string GetAllTimeRecLogFile() { - var timeStampFormat = "dd/MM/yy HH:mm"; - var dateStampFormat = "dd/MM/yy"; + const string timeStampFormat = "dd/MM/yy HH:mm"; + const string dateStampFormat = "dd/MM/yy"; var json = "{"; var datefrom = DateTime.Parse(cumulus.RecordsBeganDate); @@ -562,92 +564,86 @@ internal string GetAllTimeRecLogFile() dateto = new DateTime(dateto.Year, dateto.Month, 1, 0, 0, 0); var filedate = datefrom; - string logFile = cumulus.GetLogFileName(filedate); - bool started = false; - bool finished = false; - var entrydate = datefrom; + var logFile = cumulus.GetLogFileName(filedate); + var started = false; + var finished = false; var lastentrydate = datefrom; - var metoDate = datefrom; var currentDay = datefrom; double dayHighTemp = -999; double dayLowTemp = 999; double dayWindRun = 0; - //double hourRain = 0; double dayRain = 0; - bool isDryNow = false; - int currentDryPeriod = 0; - int currentWetPeriod = 0; + var isDryNow = false; + var currentDryPeriod = 0; + var currentWetPeriod = 0; - double rainThreshold = 0; + var rainThreshold = 0.0; if (cumulus.RainDayThreshold > -1) rainThreshold = cumulus.RainDayThreshold; - double highTempVal = -999; - double lowTempVal = 999; - double highDewPtVal = -999; - double lowDewPtVal = 999; - double highAppTempVal = -999; - double lowAppTempVal = 999; - double lowWindChillVal = 999; - double highHeatIndVal = -999; - double highMinTempVal = -999; - double lowMaxTempVal = 999; - double highTempRangeVal = -999; - double lowTempRangeVal = 999; - double highHumVal = -999; - double lowHumVal = 999; - double highBaroVal = -999; - double lowBaroVal = 99999; - double highGustVal = -999; - double highWindVal = -999; - double highWindRunVal = -999; - double highRainRateVal = -999; - double highRainHourVal = -999; - double highRainDayVal = -999; - double highRainMonthVal = -999; - int dryPeriodVal = 0; - int wetPeriodVal = 0; - - DateTime highTempTime = new DateTime(1900, 01, 01); - DateTime lowTempTime = highTempTime; - DateTime highDewPtTime = highTempTime; - DateTime lowDewPtTime = highTempTime; - DateTime highAppTempTime = highTempTime; - DateTime lowAppTempTime = highTempTime; - DateTime lowWindChillTime = highTempTime; - DateTime highHeatIndTime = highTempTime; - DateTime highMinTempTime = highTempTime; - DateTime lowMaxTempTime = highTempTime; - DateTime highTempRangeTime = highTempTime; - DateTime lowTempRangeTime = highTempTime; - DateTime highHumTime = highTempTime; - DateTime lowHumTime = highTempTime; - DateTime highBaroTime = highTempTime; - DateTime lowBaroTime = highTempTime; - DateTime highGustTime = highTempTime; - DateTime highWindTime = highTempTime; - DateTime highWindRunTime = highTempTime; - DateTime highRainRateTime = highTempTime; - DateTime highRainHourTime = highTempTime; - DateTime highRainDayTime = highTempTime; - DateTime highRainMonthTime = highTempTime; - DateTime dryPeriodTime = highTempTime; - DateTime wetPeriodTime = highTempTime; - - DateTime thisDateDry = highTempTime; - DateTime thisDateWet = highTempTime; - - Double lastRainMidnight = 0; - Double rainMidnight = 0; - Double totalRainfall = 0; - - Double outsidetemp, dewpoint, speed, gust, rainrate, raintoday, pressure, chill, heat, apptemp; - int hum; + var highTempVal = -999.0; + var lowTempVal = 999.0; + var highDewPtVal = highTempVal; + var lowDewPtVal = lowTempVal; + var highAppTempVal = highTempVal; + var lowAppTempVal = lowTempVal; + var lowWindChillVal = lowTempVal; + var highHeatIndVal = highTempVal; + var highMinTempVal = highTempVal; + var lowMaxTempVal = lowTempVal; + var highTempRangeVal = highTempVal; + var lowTempRangeVal = lowTempVal; + var highHumVal = highTempVal; + var lowHumVal = lowTempVal; + var highBaroVal = highTempVal; + var lowBaroVal = 99999.0; + var highGustVal = highTempVal; + var highWindVal = highTempVal; + var highWindRunVal = highTempVal; + var highRainRateVal = highTempVal; + var highRainHourVal = highTempVal; + var highRainDayVal = highTempVal; + var highRainMonthVal = highTempVal; + var dryPeriodVal = 0; + var wetPeriodVal = 0; + + var highTempTime = new DateTime(1900, 01, 01); + var lowTempTime = highTempTime; + var highDewPtTime = highTempTime; + var lowDewPtTime = highTempTime; + var highAppTempTime = highTempTime; + var lowAppTempTime = highTempTime; + var lowWindChillTime = highTempTime; + var highHeatIndTime = highTempTime; + var highMinTempTime = highTempTime; + var lowMaxTempTime = highTempTime; + var highTempRangeTime = highTempTime; + var lowTempRangeTime = highTempTime; + var highHumTime = highTempTime; + var lowHumTime = highTempTime; + var highBaroTime = highTempTime; + var lowBaroTime = highTempTime; + var highGustTime = highTempTime; + var highWindTime = highTempTime; + var highWindRunTime = highTempTime; + var highRainRateTime = highTempTime; + var highRainHourTime = highTempTime; + var highRainDayTime = highTempTime; + var highRainMonthTime = highTempTime; + var dryPeriodTime = highTempTime; + var wetPeriodTime = highTempTime; + + var thisDateDry = highTempTime; + var thisDateWet = highTempTime; + + var totalRainfall = 0.0; var watch = System.Diagnostics.Stopwatch.StartNew(); + hourRainLog.Clear(); + while (!finished) { double monthlyRain = 0; @@ -655,21 +651,22 @@ internal string GetAllTimeRecLogFile() if (File.Exists(logFile)) { cumulus.LogDebugMessage($"GetAllTimeRecLogFile: Processing log file - {logFile}"); - int linenum = 0; + var linenum = 0; try { - string[] logfile = File.ReadAllLines(logFile); + var logfile = File.ReadAllLines(logFile); + double valDbl; - foreach (string line in logfile) + foreach (var line in logfile) { // process each record in the file linenum++; //var st = new List(Regex.Split(line, CultureInfo.CurrentCulture.TextInfo.ListSeparator)); // Regex is very expensive, let's assume the separator is always a single character var st = new List(line.Split((CultureInfo.CurrentCulture.TextInfo.ListSeparator)[0])); - entrydate = station.ddmmyyhhmmStrToDate(st[0], st[1]); + var entrydate = station.ddmmyyhhmmStrToDate(st[0], st[1]); // We need to work in meto dates not clock dates for day hi/lows - metoDate = entrydate.AddHours(cumulus.GetHourInc()); + var metoDate = entrydate.AddHours(cumulus.GetHourInc()); if (!started) { @@ -678,48 +675,45 @@ internal string GetAllTimeRecLogFile() started = true; } - outsidetemp = Convert.ToDouble(st[2]); - hum = Convert.ToInt32(st[3]); - dewpoint = Convert.ToDouble(st[4]); - speed = Convert.ToDouble(st[5]); - gust = Convert.ToDouble(st[6]); - rainrate = Convert.ToDouble(st[8]); - raintoday = Convert.ToDouble(st[9]); - pressure = Convert.ToDouble(st[10]); - if (st.Count >= 16) + var outsidetemp = double.Parse(st[2]); + var hum = int.Parse(st[3]); + var dewpoint = double.Parse(st[4]); + var speed = double.Parse(st[5]); + var gust = double.Parse(st[6]); + var rainrate = double.Parse(st[8]); + var raintoday = double.Parse(st[9]); + var pressure = double.Parse(st[10]); + if (st.Count > 15 && double.TryParse(st[15], out valDbl)) { - chill = Convert.ToDouble(st[15]); // low chill - if (chill < lowWindChillVal) + if (valDbl < lowWindChillVal) { - lowWindChillVal = chill; + lowWindChillVal = valDbl; lowWindChillTime = entrydate; } } - if (st.Count >= 17) + if (st.Count > 16 && double.TryParse(st[16], out valDbl)) { - heat = Convert.ToDouble(st[16]); // hi heat - if (heat > highHeatIndVal) + if (valDbl > highHeatIndVal) { - highHeatIndVal = heat; + highHeatIndVal = valDbl; highHeatIndTime = entrydate; } } - if (st.Count >= 22) + if (st.Count > 21 && double.TryParse(st[21], out valDbl)) { - apptemp = Convert.ToDouble(st[21]); // hi appt - if (apptemp > highAppTempVal) + if (valDbl > highAppTempVal) { - highAppTempVal = apptemp; + highAppTempVal = valDbl; highAppTempTime = entrydate; } // lo appt - if (apptemp < lowAppTempVal) + if (valDbl < lowAppTempVal) { - lowAppTempVal = apptemp; + lowAppTempVal = valDbl; lowAppTempTime = entrydate; } } @@ -897,10 +891,10 @@ internal string GetAllTimeRecLogFile() * need to track what the rainfall has been in the last rolling hour * across day rollovers where the count resets */ - AddLastHourRainEntry(entrydate, totalRainfall + raintoday); + AddLastHourRainEntry(entrydate, totalRainfall + dayRain); RemoveOldRainData(entrydate); - var rainThisHour = HourRainLog.Last().raincounter - HourRainLog.First().raincounter; + var rainThisHour = hourRainLog.Last().Raincounter - hourRainLog.First().Raincounter; if (rainThisHour > highRainHourVal) { highRainHourVal = rainThisHour; @@ -908,7 +902,7 @@ internal string GetAllTimeRecLogFile() } lastentrydate = entrydate; - lastRainMidnight = rainMidnight; + //lastRainMidnight = rainMidnight; } } catch (Exception e) @@ -992,9 +986,9 @@ internal string GetAllTimeRecLogFile() return json; } - private DateTime getDateTime(DateTime date, string time) + private static DateTime GetDateTime(DateTime date, string time) { - string[] tim = time.Split(CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator.ToCharArray()[0]); + var tim = time.Split(CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator.ToCharArray()[0]); return new DateTime(date.Year, date.Month, date.Day, int.Parse(tim[0]), int.Parse(tim[1]), 0); } @@ -1002,8 +996,7 @@ internal string EditAllTimeRecs(IHttpContext context) { var request = context.Request; string text; - int result; - string[] dt; + using (var reader = new StreamReader(request.InputStream, request.ContentEncoding)) { @@ -1013,161 +1006,162 @@ internal string EditAllTimeRecs(IHttpContext context) var newData = text.Split('&'); var field = newData[0].Split('=')[1]; var value = newData[1].Split('=')[1]; - result = 1; + var result = 1; try { + string[] dt; switch (field) { case "highTempVal": - station.SetAlltime(WeatherStation.AT_hightemp, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_hightemp].timestamp); + station.SetAlltime(WeatherStation.AT_hightemp, double.Parse(value), station.alltimerecarray[WeatherStation.AT_hightemp].timestamp); break; case "highTempTime": dt = value.Split('+'); station.SetAlltime(WeatherStation.AT_hightemp, station.alltimerecarray[WeatherStation.AT_hightemp].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); break; case "lowTempVal": - station.SetAlltime(WeatherStation.AT_lowtemp, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_lowtemp].timestamp); + station.SetAlltime(WeatherStation.AT_lowtemp, double.Parse(value), station.alltimerecarray[WeatherStation.AT_lowtemp].timestamp); break; case "lowTempTime": dt = value.Split('+'); station.SetAlltime(WeatherStation.AT_lowtemp, station.alltimerecarray[WeatherStation.AT_lowtemp].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); break; case "highDewPointVal": - station.SetAlltime(WeatherStation.AT_highdewpoint, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_highdewpoint].timestamp); + station.SetAlltime(WeatherStation.AT_highdewpoint, double.Parse(value), station.alltimerecarray[WeatherStation.AT_highdewpoint].timestamp); break; case "highDewPointTime": dt = value.Split('+'); station.SetAlltime(WeatherStation.AT_highdewpoint, station.alltimerecarray[WeatherStation.AT_highdewpoint].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); break; case "lowDewPointVal": - station.SetAlltime(WeatherStation.AT_lowdewpoint, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_lowdewpoint].timestamp); + station.SetAlltime(WeatherStation.AT_lowdewpoint, double.Parse(value), station.alltimerecarray[WeatherStation.AT_lowdewpoint].timestamp); break; case "lowDewPointTime": dt = value.Split('+'); station.SetAlltime(WeatherStation.AT_lowdewpoint, station.alltimerecarray[WeatherStation.AT_lowdewpoint].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); break; case "highApparentTempVal": - station.SetAlltime(WeatherStation.AT_highapptemp, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_highapptemp].timestamp); + station.SetAlltime(WeatherStation.AT_highapptemp, double.Parse(value), station.alltimerecarray[WeatherStation.AT_highapptemp].timestamp); break; case "highApparentTempTime": dt = value.Split('+'); station.SetAlltime(WeatherStation.AT_highapptemp, station.alltimerecarray[WeatherStation.AT_highapptemp].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); break; case "lowApparentTempVal": - station.SetAlltime(WeatherStation.AT_lowapptemp, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_lowapptemp].timestamp); + station.SetAlltime(WeatherStation.AT_lowapptemp, double.Parse(value), station.alltimerecarray[WeatherStation.AT_lowapptemp].timestamp); break; case "lowApparentTempTime": dt = value.Split('+'); station.SetAlltime(WeatherStation.AT_lowapptemp, station.alltimerecarray[WeatherStation.AT_lowapptemp].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); break; case "lowWindChillVal": - station.SetAlltime(WeatherStation.AT_lowchill, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_lowchill].timestamp); + station.SetAlltime(WeatherStation.AT_lowchill, double.Parse(value), station.alltimerecarray[WeatherStation.AT_lowchill].timestamp); break; case "lowWindChillTime": dt = value.Split('+'); station.SetAlltime(WeatherStation.AT_lowchill, station.alltimerecarray[WeatherStation.AT_lowchill].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); break; case "highHeatIndexVal": - station.SetAlltime(WeatherStation.AT_highheatindex, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_highheatindex].timestamp); + station.SetAlltime(WeatherStation.AT_highheatindex, double.Parse(value), station.alltimerecarray[WeatherStation.AT_highheatindex].timestamp); break; case "highHeatIndexTime": dt = value.Split('+'); station.SetAlltime(WeatherStation.AT_highheatindex, station.alltimerecarray[WeatherStation.AT_highheatindex].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); break; case "highMinTempVal": - station.SetAlltime(WeatherStation.AT_highmintemp, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_highmintemp].timestamp); + station.SetAlltime(WeatherStation.AT_highmintemp, double.Parse(value), station.alltimerecarray[WeatherStation.AT_highmintemp].timestamp); break; case "highMinTempTime": station.SetAlltime(WeatherStation.AT_highmintemp, station.alltimerecarray[WeatherStation.AT_highmintemp].value, station.ddmmyyStrToDate(value)); break; case "lowMaxTempVal": - station.SetAlltime(WeatherStation.AT_lowmaxtemp, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_lowmaxtemp].timestamp); + station.SetAlltime(WeatherStation.AT_lowmaxtemp, double.Parse(value), station.alltimerecarray[WeatherStation.AT_lowmaxtemp].timestamp); break; case "lowMaxTempTime": station.SetAlltime(WeatherStation.AT_lowmaxtemp, station.alltimerecarray[WeatherStation.AT_lowmaxtemp].value, station.ddmmyyStrToDate(value)); break; case "highDailyTempRangeVal": - station.SetAlltime(WeatherStation.AT_highdailytemprange, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_highdailytemprange].timestamp); + station.SetAlltime(WeatherStation.AT_highdailytemprange, double.Parse(value), station.alltimerecarray[WeatherStation.AT_highdailytemprange].timestamp); break; case "highDailyTempRangeTime": station.SetAlltime(WeatherStation.AT_highdailytemprange, station.alltimerecarray[WeatherStation.AT_highdailytemprange].value, station.ddmmyyStrToDate(value)); break; case "lowDailyTempRangeVal": - station.SetAlltime(WeatherStation.AT_lowdailytemprange, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_lowdailytemprange].timestamp); + station.SetAlltime(WeatherStation.AT_lowdailytemprange, double.Parse(value), station.alltimerecarray[WeatherStation.AT_lowdailytemprange].timestamp); break; case "lowDailyTempRangeTime": station.SetAlltime(WeatherStation.AT_lowdailytemprange, station.alltimerecarray[WeatherStation.AT_lowdailytemprange].value, station.ddmmyyStrToDate(value)); break; case "highHumidityVal": - station.SetAlltime(WeatherStation.AT_highhumidity, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_highhumidity].timestamp); + station.SetAlltime(WeatherStation.AT_highhumidity, double.Parse(value), station.alltimerecarray[WeatherStation.AT_highhumidity].timestamp); break; case "highHumidityTime": dt = value.Split('+'); station.SetAlltime(WeatherStation.AT_highhumidity, station.alltimerecarray[WeatherStation.AT_highhumidity].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); break; case "lowHumidityVal": - station.SetAlltime(WeatherStation.AT_lowhumidity, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_lowhumidity].timestamp); + station.SetAlltime(WeatherStation.AT_lowhumidity, double.Parse(value), station.alltimerecarray[WeatherStation.AT_lowhumidity].timestamp); break; case "lowHumidityTime": dt = value.Split('+'); station.SetAlltime(WeatherStation.AT_lowhumidity, station.alltimerecarray[WeatherStation.AT_lowhumidity].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); break; case "highBarometerVal": - station.SetAlltime(WeatherStation.AT_highpress, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_highpress].timestamp); + station.SetAlltime(WeatherStation.AT_highpress, double.Parse(value), station.alltimerecarray[WeatherStation.AT_highpress].timestamp); break; case "highBarometerTime": dt = value.Split('+'); station.SetAlltime(WeatherStation.AT_highpress, station.alltimerecarray[WeatherStation.AT_highpress].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); break; case "lowBarometerVal": - station.SetAlltime(WeatherStation.AT_lowpress, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_lowpress].timestamp); + station.SetAlltime(WeatherStation.AT_lowpress, double.Parse(value), station.alltimerecarray[WeatherStation.AT_lowpress].timestamp); break; case "lowBarometerTime": dt = value.Split('+'); station.SetAlltime(WeatherStation.AT_lowpress, station.alltimerecarray[WeatherStation.AT_lowpress].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); break; case "highGustVal": - station.SetAlltime(WeatherStation.AT_highgust, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_highgust].timestamp); + station.SetAlltime(WeatherStation.AT_highgust, double.Parse(value), station.alltimerecarray[WeatherStation.AT_highgust].timestamp); break; case "highGustTime": dt = value.Split('+'); station.SetAlltime(WeatherStation.AT_highgust, station.alltimerecarray[WeatherStation.AT_highgust].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); break; case "highWindVal": - station.SetAlltime(WeatherStation.AT_highwind, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_highwind].timestamp); + station.SetAlltime(WeatherStation.AT_highwind, double.Parse(value), station.alltimerecarray[WeatherStation.AT_highwind].timestamp); break; case "highWindTime": dt = value.Split('+'); station.SetAlltime(WeatherStation.AT_highwind, station.alltimerecarray[WeatherStation.AT_highwind].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); break; case "highWindRunVal": - station.SetAlltime(WeatherStation.AT_highwindrun, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_highwindrun].timestamp); + station.SetAlltime(WeatherStation.AT_highwindrun, double.Parse(value), station.alltimerecarray[WeatherStation.AT_highwindrun].timestamp); break; case "highWindRunTime": station.SetAlltime(WeatherStation.AT_highwindrun, station.alltimerecarray[WeatherStation.AT_highwindrun].value, station.ddmmyyStrToDate(value)); break; case "highRainRateVal": - station.SetAlltime(WeatherStation.AT_highrainrate, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_highrainrate].timestamp); + station.SetAlltime(WeatherStation.AT_highrainrate, double.Parse(value), station.alltimerecarray[WeatherStation.AT_highrainrate].timestamp); break; case "highRainRateTime": dt = value.Split('+'); station.SetAlltime(WeatherStation.AT_highrainrate, station.alltimerecarray[WeatherStation.AT_highrainrate].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); break; case "highHourlyRainVal": - station.SetAlltime(WeatherStation.AT_hourlyrain, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_hourlyrain].timestamp); + station.SetAlltime(WeatherStation.AT_hourlyrain, double.Parse(value), station.alltimerecarray[WeatherStation.AT_hourlyrain].timestamp); break; case "highHourlyRainTime": dt = value.Split('+'); station.SetAlltime(WeatherStation.AT_hourlyrain, station.alltimerecarray[WeatherStation.AT_hourlyrain].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); break; case "highDailyRainVal": - station.SetAlltime(WeatherStation.AT_dailyrain, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_dailyrain].timestamp); + station.SetAlltime(WeatherStation.AT_dailyrain, double.Parse(value), station.alltimerecarray[WeatherStation.AT_dailyrain].timestamp); break; case "highDailyRainTime": station.SetAlltime(WeatherStation.AT_dailyrain, station.alltimerecarray[WeatherStation.AT_dailyrain].value, station.ddmmyyStrToDate(value)); break; case "highMonthlyRainVal": - station.SetAlltime(WeatherStation.AT_wetmonth, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_wetmonth].timestamp); + station.SetAlltime(WeatherStation.AT_wetmonth, double.Parse(value), station.alltimerecarray[WeatherStation.AT_wetmonth].timestamp); break; case "highMonthlyRainTime": dt = value.Split('/'); @@ -1175,13 +1169,13 @@ internal string EditAllTimeRecs(IHttpContext context) station.SetAlltime(WeatherStation.AT_wetmonth, station.alltimerecarray[WeatherStation.AT_wetmonth].value, station.ddmmyyStrToDate(datstr)); break; case "longestDryPeriodVal": - station.SetAlltime(WeatherStation.AT_longestdryperiod, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_longestdryperiod].timestamp); + station.SetAlltime(WeatherStation.AT_longestdryperiod, double.Parse(value), station.alltimerecarray[WeatherStation.AT_longestdryperiod].timestamp); break; case "longestDryPeriodTime": station.SetAlltime(WeatherStation.AT_longestdryperiod, station.alltimerecarray[WeatherStation.AT_longestdryperiod].value, station.ddmmyyStrToDate(value)); break; case "longestWetPeriodVal": - station.SetAlltime(WeatherStation.AT_longestwetperiod, Double.Parse(value), station.alltimerecarray[WeatherStation.AT_longestwetperiod].timestamp); + station.SetAlltime(WeatherStation.AT_longestwetperiod, double.Parse(value), station.alltimerecarray[WeatherStation.AT_longestwetperiod].timestamp); break; case "longestWetPeriodTime": station.SetAlltime(WeatherStation.AT_longestwetperiod, station.alltimerecarray[WeatherStation.AT_longestwetperiod].value, station.ddmmyyStrToDate(value)); @@ -1198,82 +1192,1167 @@ internal string EditAllTimeRecs(IHttpContext context) return "{\"result\":\"" + ((result == 1) ? "Success" : "Failed") + "\"}"; } - internal string GetCurrentCond() - { - return "{\"data\":\"" + webtags.GetCurrCondText() + "\"}"; - } - - internal string EditCurrentCond(IHttpContext context) + internal string EditMonthlyRecs(IHttpContext context) { var request = context.Request; string text; - bool result = true; + + using (var reader = new StreamReader(request.InputStream, request.ContentEncoding)) { - text = reader.ReadToEnd(); + text = Uri.UnescapeDataString(reader.ReadToEnd()); } - - result = SetCurrCondText(text); - - return "{\"result\":\"" + (result ? "Success" : "Failed") + "\"}"; - } - - private bool SetCurrCondText(string currCondText) - { - string fileName = cumulus.AppDir + "currentconditions.txt"; + // Eg "name=2-highTempValvalue=134.6&pk=1" + var newData = text.Split('&'); + var monthField = newData[0].Split('=')[1].Split('-'); + var month = int.Parse(monthField[0]); + var field = monthField[1]; + var value = newData[1].Split('=')[1]; + var result = 1; try { - cumulus.LogMessage("Writing current conditions to file..."); - - System.IO.File.WriteAllText(fileName, currCondText); - return true; + string[] dt; + switch (field) + { + case "highTempVal": + station.SetMonthlyAlltime(WeatherStation.AT_hightemp, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_hightemp, month].timestamp); + break; + case "highTempTime": + dt = value.Split('+'); + station.SetMonthlyAlltime(WeatherStation.AT_hightemp, station.monthlyrecarray[WeatherStation.AT_hightemp, month].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); + break; + case "lowTempVal": + station.SetMonthlyAlltime(WeatherStation.AT_lowtemp, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_lowtemp, month].timestamp); + break; + case "lowTempTime": + dt = value.Split('+'); + station.SetMonthlyAlltime(WeatherStation.AT_lowtemp, station.monthlyrecarray[WeatherStation.AT_lowtemp, month].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); + break; + case "highDewPointVal": + station.SetMonthlyAlltime(WeatherStation.AT_highdewpoint, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_highdewpoint, month].timestamp); + break; + case "highDewPointTime": + dt = value.Split('+'); + station.SetMonthlyAlltime(WeatherStation.AT_highdewpoint, station.monthlyrecarray[WeatherStation.AT_highdewpoint, month].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); + break; + case "lowDewPointVal": + station.SetMonthlyAlltime(WeatherStation.AT_lowdewpoint, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_lowdewpoint, month].timestamp); + break; + case "lowDewPointTime": + dt = value.Split('+'); + station.SetMonthlyAlltime(WeatherStation.AT_lowdewpoint, station.monthlyrecarray[WeatherStation.AT_lowdewpoint, month].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); + break; + case "highApparentTempVal": + station.SetMonthlyAlltime(WeatherStation.AT_highapptemp, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_highapptemp, month].timestamp); + break; + case "highApparentTempTime": + dt = value.Split('+'); + station.SetMonthlyAlltime(WeatherStation.AT_highapptemp, station.monthlyrecarray[WeatherStation.AT_highapptemp, month].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); + break; + case "lowApparentTempVal": + station.SetMonthlyAlltime(WeatherStation.AT_lowapptemp, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_lowapptemp, month].timestamp); + break; + case "lowApparentTempTime": + dt = value.Split('+'); + station.SetMonthlyAlltime(WeatherStation.AT_lowapptemp, station.monthlyrecarray[WeatherStation.AT_lowapptemp, month].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); + break; + case "lowWindChillVal": + station.SetMonthlyAlltime(WeatherStation.AT_lowchill, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_lowchill, month].timestamp); + break; + case "lowWindChillTime": + dt = value.Split('+'); + station.SetMonthlyAlltime(WeatherStation.AT_lowchill, station.monthlyrecarray[WeatherStation.AT_lowchill, month].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); + break; + case "highHeatIndexVal": + station.SetMonthlyAlltime(WeatherStation.AT_highheatindex, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_highheatindex, month].timestamp); + break; + case "highHeatIndexTime": + dt = value.Split('+'); + station.SetMonthlyAlltime(WeatherStation.AT_highheatindex, station.monthlyrecarray[WeatherStation.AT_highheatindex, month].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); + break; + case "highMinTempVal": + station.SetMonthlyAlltime(WeatherStation.AT_highmintemp, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_highmintemp, month].timestamp); + break; + case "highMinTempTime": + station.SetMonthlyAlltime(WeatherStation.AT_highmintemp, station.monthlyrecarray[WeatherStation.AT_highmintemp, month].value, station.ddmmyyStrToDate(value)); + break; + case "lowMaxTempVal": + station.SetMonthlyAlltime(WeatherStation.AT_lowmaxtemp, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_lowmaxtemp, month].timestamp); + break; + case "lowMaxTempTime": + station.SetMonthlyAlltime(WeatherStation.AT_lowmaxtemp, station.monthlyrecarray[WeatherStation.AT_lowmaxtemp, month].value, station.ddmmyyStrToDate(value)); + break; + case "highDailyTempRangeVal": + station.SetMonthlyAlltime(WeatherStation.AT_highdailytemprange, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_highdailytemprange, month].timestamp); + break; + case "highDailyTempRangeTime": + station.SetMonthlyAlltime(WeatherStation.AT_highdailytemprange, station.monthlyrecarray[WeatherStation.AT_highdailytemprange, month].value, station.ddmmyyStrToDate(value)); + break; + case "lowDailyTempRangeVal": + station.SetMonthlyAlltime(WeatherStation.AT_lowdailytemprange, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_lowdailytemprange, month].timestamp); + break; + case "lowDailyTempRangeTime": + station.SetMonthlyAlltime(WeatherStation.AT_lowdailytemprange, station.monthlyrecarray[WeatherStation.AT_lowdailytemprange, month].value, station.ddmmyyStrToDate(value)); + break; + case "highHumidityVal": + station.SetMonthlyAlltime(WeatherStation.AT_highhumidity, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_highhumidity, month].timestamp); + break; + case "highHumidityTime": + dt = value.Split('+'); + station.SetMonthlyAlltime(WeatherStation.AT_highhumidity, station.monthlyrecarray[WeatherStation.AT_highhumidity, month].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); + break; + case "lowHumidityVal": + station.SetMonthlyAlltime(WeatherStation.AT_lowhumidity, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_lowhumidity, month].timestamp); + break; + case "lowHumidityTime": + dt = value.Split('+'); + station.SetMonthlyAlltime(WeatherStation.AT_lowhumidity, station.monthlyrecarray[WeatherStation.AT_lowhumidity, month].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); + break; + case "highBarometerVal": + station.SetMonthlyAlltime(WeatherStation.AT_highpress, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_highpress, month].timestamp); + break; + case "highBarometerTime": + dt = value.Split('+'); + station.SetMonthlyAlltime(WeatherStation.AT_highpress, station.monthlyrecarray[WeatherStation.AT_highpress, month].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); + break; + case "lowBarometerVal": + station.SetMonthlyAlltime(WeatherStation.AT_lowpress, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_lowpress, month].timestamp); + break; + case "lowBarometerTime": + dt = value.Split('+'); + station.SetMonthlyAlltime(WeatherStation.AT_lowpress, station.monthlyrecarray[WeatherStation.AT_lowpress, month].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); + break; + case "highGustVal": + station.SetMonthlyAlltime(WeatherStation.AT_highgust, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_highgust, month].timestamp); + break; + case "highGustTime": + dt = value.Split('+'); + station.SetMonthlyAlltime(WeatherStation.AT_highgust, station.monthlyrecarray[WeatherStation.AT_highgust, month].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); + break; + case "highWindVal": + station.SetMonthlyAlltime(WeatherStation.AT_highwind, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_highwind, month].timestamp); + break; + case "highWindTime": + dt = value.Split('+'); + station.SetMonthlyAlltime(WeatherStation.AT_highwind, station.monthlyrecarray[WeatherStation.AT_highwind, month].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); + break; + case "highWindRunVal": + station.SetMonthlyAlltime(WeatherStation.AT_highwindrun, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_highwindrun, month].timestamp); + break; + case "highWindRunTime": + station.SetMonthlyAlltime(WeatherStation.AT_highwindrun, station.monthlyrecarray[WeatherStation.AT_highwindrun, month].value, station.ddmmyyStrToDate(value)); + break; + case "highRainRateVal": + station.SetMonthlyAlltime(WeatherStation.AT_highrainrate, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_highrainrate, month].timestamp); + break; + case "highRainRateTime": + dt = value.Split('+'); + station.SetMonthlyAlltime(WeatherStation.AT_highrainrate, station.monthlyrecarray[WeatherStation.AT_highrainrate, month].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); + break; + case "highHourlyRainVal": + station.SetMonthlyAlltime(WeatherStation.AT_hourlyrain, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_hourlyrain, month].timestamp); + break; + case "highHourlyRainTime": + dt = value.Split('+'); + station.SetMonthlyAlltime(WeatherStation.AT_hourlyrain, station.monthlyrecarray[WeatherStation.AT_hourlyrain, month].value, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); + break; + case "highDailyRainVal": + station.SetMonthlyAlltime(WeatherStation.AT_dailyrain, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_dailyrain, month].timestamp); + break; + case "highDailyRainTime": + station.SetMonthlyAlltime(WeatherStation.AT_dailyrain, station.monthlyrecarray[WeatherStation.AT_dailyrain, month].value, station.ddmmyyStrToDate(value)); + break; + case "highMonthlyRainVal": + station.SetMonthlyAlltime(WeatherStation.AT_wetmonth, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_wetmonth, month].timestamp); + break; + case "highMonthlyRainTime": + dt = value.Split('/'); + var datstr = "01/" + dt[1] + "/" + dt[0].Substring(2, 2); + station.SetMonthlyAlltime(WeatherStation.AT_wetmonth, station.monthlyrecarray[WeatherStation.AT_wetmonth, month].value, station.ddmmyyStrToDate(datstr)); + break; + case "longestDryPeriodVal": + station.SetMonthlyAlltime(WeatherStation.AT_longestdryperiod, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_longestdryperiod, month].timestamp); + break; + case "longestDryPeriodTime": + station.SetMonthlyAlltime(WeatherStation.AT_longestdryperiod, station.monthlyrecarray[WeatherStation.AT_longestdryperiod, month].value, station.ddmmyyStrToDate(value)); + break; + case "longestWetPeriodVal": + station.SetMonthlyAlltime(WeatherStation.AT_longestwetperiod, double.Parse(value), station.monthlyrecarray[WeatherStation.AT_longestwetperiod, month].timestamp); + break; + case "longestWetPeriodTime": + station.SetMonthlyAlltime(WeatherStation.AT_longestwetperiod, station.monthlyrecarray[WeatherStation.AT_longestwetperiod, month].value, station.ddmmyyStrToDate(value)); + break; + default: + result = 0; + break; + } } - catch (Exception e) + catch { - cumulus.LogMessage("Error writing current conditions to file - " + e.Message); - return false; + result = 0; } + return "{\"result\":\"" + ((result == 1) ? "Success" : "Failed") + "\"}"; } - internal class JsonEditRainData - { - public double raintoday { get; set; } - public double raincounter { get; set; } - public double startofdayrain { get; set; } - public double rainmult { get; set; } - } - private void AddLastHourRainEntry(DateTime ts, double rain) + internal string GetMonthlyRecData() { - LastHourRainLog lasthourrain = new LastHourRainLog(ts, rain); + const string timeStampFormat = "dd/MM/yy HH:mm"; + const string dateStampFormat = "dd/MM/yy"; - HourRainLog.Add(lasthourrain); + var json = "{"; + for (var m = 1; m <= 12; m++) + { + // Records - Temperature values + json += $"\"{m}-highTempVal\":\"" + station.monthlyrecarray[WeatherStation.AT_hightemp, m].value.ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-lowTempVal\":\"" + station.monthlyrecarray[WeatherStation.AT_lowtemp, m].value.ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-highDewPointVal\":\"" + station.monthlyrecarray[WeatherStation.AT_highdewpoint, m].value.ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-lowDewPointVal\":\"" + station.monthlyrecarray[WeatherStation.AT_lowdewpoint, m].value.ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-highApparentTempVal\":\"" + station.monthlyrecarray[WeatherStation.AT_highapptemp, m].value.ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-lowApparentTempVal\":\"" + station.monthlyrecarray[WeatherStation.AT_lowapptemp, m].value.ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-lowWindChillVal\":\"" + station.monthlyrecarray[WeatherStation.AT_lowchill, m].value.ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-highHeatIndexVal\":\"" + station.monthlyrecarray[WeatherStation.AT_highheatindex, m].value.ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-highMinTempVal\":\"" + station.monthlyrecarray[WeatherStation.AT_highmintemp, m].value.ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-lowMaxTempVal\":\"" + station.monthlyrecarray[WeatherStation.AT_lowmaxtemp, m].value.ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-highDailyTempRangeVal\":\"" + station.monthlyrecarray[WeatherStation.AT_highdailytemprange, m].value.ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-lowDailyTempRangeVal\":\"" + station.monthlyrecarray[WeatherStation.AT_lowdailytemprange, m].value.ToString(cumulus.TempFormat) + "\","; + // Records - Temperature timestamps + json += $"\"{m}-highTempTime\":\"" + station.monthlyrecarray[WeatherStation.AT_hightemp, m].timestamp.ToString(timeStampFormat) + "\","; + json += $"\"{m}-lowTempTime\":\"" + station.monthlyrecarray[WeatherStation.AT_lowtemp, m].timestamp.ToString(timeStampFormat) + "\","; + json += $"\"{m}-highDewPointTime\":\"" + station.monthlyrecarray[WeatherStation.AT_highdewpoint, m].timestamp.ToString(timeStampFormat) + "\","; + json += $"\"{m}-lowDewPointTime\":\"" + station.monthlyrecarray[WeatherStation.AT_lowdewpoint, m].timestamp.ToString(timeStampFormat) + "\","; + json += $"\"{m}-highApparentTempTime\":\"" + station.monthlyrecarray[WeatherStation.AT_highapptemp, m].timestamp.ToString(timeStampFormat) + "\","; + json += $"\"{m}-lowApparentTempTime\":\"" + station.monthlyrecarray[WeatherStation.AT_lowapptemp, m].timestamp.ToString(timeStampFormat) + "\","; + json += $"\"{m}-lowWindChillTime\":\"" + station.monthlyrecarray[WeatherStation.AT_lowchill, m].timestamp.ToString(timeStampFormat) + "\","; + json += $"\"{m}-highHeatIndexTime\":\"" + station.monthlyrecarray[WeatherStation.AT_highheatindex, m].timestamp.ToString(timeStampFormat) + "\","; + json += $"\"{m}-highMinTempTime\":\"" + station.monthlyrecarray[WeatherStation.AT_highmintemp, m].timestamp.ToString(dateStampFormat) + "\","; + json += $"\"{m}-lowMaxTempTime\":\"" + station.monthlyrecarray[WeatherStation.AT_lowmaxtemp, m].timestamp.ToString(dateStampFormat) + "\","; + json += $"\"{m}-highDailyTempRangeTime\":\"" + station.monthlyrecarray[WeatherStation.AT_highdailytemprange, m].timestamp.ToString(dateStampFormat) + "\","; + json += $"\"{m}-lowDailyTempRangeTime\":\"" + station.monthlyrecarray[WeatherStation.AT_lowdailytemprange, m].timestamp.ToString(dateStampFormat) + "\","; + // Records - Humidity values + json += $"\"{m}-highHumidityVal\":\"" + station.monthlyrecarray[WeatherStation.AT_highhumidity, m].value.ToString(cumulus.HumFormat) + "\","; + json += $"\"{m}-lowHumidityVal\":\"" + station.monthlyrecarray[WeatherStation.AT_lowhumidity, m].value.ToString(cumulus.HumFormat) + "\","; + // Records - Humidity times + json += $"\"{m}-highHumidityTime\":\"" + station.monthlyrecarray[WeatherStation.AT_highhumidity, m].timestamp.ToString(timeStampFormat) + "\","; + json += $"\"{m}-lowHumidityTime\":\"" + station.monthlyrecarray[WeatherStation.AT_lowhumidity, m].timestamp.ToString(timeStampFormat) + "\","; + // Records - Pressure values + json += $"\"{m}-highBarometerVal\":\"" + station.monthlyrecarray[WeatherStation.AT_highpress, m].value.ToString(cumulus.PressFormat) + "\","; + json += $"\"{m}-lowBarometerVal\":\"" + station.monthlyrecarray[WeatherStation.AT_lowpress, m].value.ToString(cumulus.PressFormat) + "\","; + // Records - Pressure times + json += $"\"{m}-highBarometerTime\":\"" + station.monthlyrecarray[WeatherStation.AT_highpress, m].timestamp.ToString(timeStampFormat) + "\","; + json += $"\"{m}-lowBarometerTime\":\"" + station.monthlyrecarray[WeatherStation.AT_lowpress, m].timestamp.ToString(timeStampFormat) + "\","; + // Records - Wind values + json += $"\"{m}-highGustVal\":\"" + station.monthlyrecarray[WeatherStation.AT_highgust, m].value.ToString(cumulus.WindFormat) + "\","; + json += $"\"{m}-highWindVal\":\"" + station.monthlyrecarray[WeatherStation.AT_highwind, m].value.ToString(cumulus.WindFormat) + "\","; + json += $"\"{m}-highWindRunVal\":\"" + station.monthlyrecarray[WeatherStation.AT_highwindrun, m].value.ToString(cumulus.WindRunFormat) + "\","; + // Records - Wind times + json += $"\"{m}-highGustTime\":\"" + station.monthlyrecarray[WeatherStation.AT_highgust, m].timestamp.ToString(timeStampFormat) + "\","; + json += $"\"{m}-highWindTime\":\"" + station.monthlyrecarray[WeatherStation.AT_highwind, m].timestamp.ToString(timeStampFormat) + "\","; + json += $"\"{m}-highWindRunTime\":\"" + station.monthlyrecarray[WeatherStation.AT_highwindrun, m].timestamp.ToString(dateStampFormat) + "\","; + // Records - Rain values + json += $"\"{m}-highRainRateVal\":\"" + station.monthlyrecarray[WeatherStation.AT_highrainrate, m].value.ToString(cumulus.RainFormat) + "\","; + json += $"\"{m}-highHourlyRainVal\":\"" + station.monthlyrecarray[WeatherStation.AT_hourlyrain, m].value.ToString(cumulus.RainFormat) + "\","; + json += $"\"{m}-highDailyRainVal\":\"" + station.monthlyrecarray[WeatherStation.AT_dailyrain, m].value.ToString(cumulus.RainFormat) + "\","; + json += $"\"{m}-highMonthlyRainVal\":\"" + station.monthlyrecarray[WeatherStation.AT_wetmonth, m].value.ToString(cumulus.RainFormat) + "\","; + json += $"\"{m}-longestDryPeriodVal\":\"" + station.monthlyrecarray[WeatherStation.AT_longestdryperiod, m].value.ToString("f0") + "\","; + json += $"\"{m}-longestWetPeriodVal\":\"" + station.monthlyrecarray[WeatherStation.AT_longestwetperiod, m].value.ToString("f0") + "\","; + // Records - Rain times + json += $"\"{m}-highRainRateTime\":\"" + station.monthlyrecarray[WeatherStation.AT_highrainrate, m].timestamp.ToString(timeStampFormat) + "\","; + json += $"\"{m}-highHourlyRainTime\":\"" + station.monthlyrecarray[WeatherStation.AT_hourlyrain, m].timestamp.ToString(timeStampFormat) + "\","; + json += $"\"{m}-highDailyRainTime\":\"" + station.monthlyrecarray[WeatherStation.AT_dailyrain, m].timestamp.ToString(dateStampFormat) + "\","; + json += $"\"{m}-highMonthlyRainTime\":\"" + station.monthlyrecarray[WeatherStation.AT_wetmonth, m].timestamp.ToString("yyyy/MM") + "\","; + json += $"\"{m}-longestDryPeriodTime\":\"" + station.monthlyrecarray[WeatherStation.AT_longestdryperiod, m].timestamp.ToString(dateStampFormat) + "\","; + json += $"\"{m}-longestWetPeriodTime\":\"" + station.monthlyrecarray[WeatherStation.AT_longestwetperiod, m].timestamp.ToString(dateStampFormat) + "\","; + + } + json = json.Remove(json.Length - 1); + json += "}"; + + return json; + } + + internal string GetMonthlyRecDayFile() + { + const string timeStampFormat = "dd/MM/yy HH:mm"; + const string dateStampFormat = "dd/MM/yy"; + + var linenum = 0; + var highTempVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var lowTempVal = new double[] { 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999 }; + var highDewPtVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var lowDewPtVal = new double[] { 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999 }; + var highAppTempVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var lowAppTempVal = new double[] { 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999 }; + var lowWindChillVal = new double[] { 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999 }; + var highHeatIndVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var highMinTempVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var lowMaxTempVal = new double[] { 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999 }; + var highTempRangeVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var lowTempRangeVal = new double[] { 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999 }; + var highHumVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var lowHumVal = new double[] { 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999 }; + var highBaroVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var lowBaroVal = new double[] { 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999 }; + var highGustVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var highWindVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var highWindRunVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var highRainRateVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var highRainHourVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var highRainDayVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var highRainMonthVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var dryPeriodVal = new[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + var wetPeriodVal = new[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + var thisDate = new DateTime(1900, 01, 01); + var highTempTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var lowTempTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highDewPtTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var lowDewPtTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highAppTempTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var lowAppTempTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var lowWindChillTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highHeatIndTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highMinTempTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var lowMaxTempTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highTempRangeTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var lowTempRangeTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highHumTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var lowHumTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highBaroTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var lowBaroTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highGustTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highWindTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highWindRunTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highRainRateTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highRainHourTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highRainDayTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highRainMonthTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var dryPeriodTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var wetPeriodTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + + var rainThisMonth = 0.0; + var currentDryPeriod = 0; + var currentWetPeriod = 0; + var isDryNow = false; + var thisDateDry = thisDate; + var thisDateWet = thisDate; + var json = "{"; + + var rainThreshold = 0.0; + if (cumulus.RainDayThreshold > -1) + rainThreshold = cumulus.RainDayThreshold; + + var watch = System.Diagnostics.Stopwatch.StartNew(); + + // Read the dayfile and extract the records from there + if (File.Exists(cumulus.DayFile)) + { + try + { + var dayfile = File.ReadAllLines(cumulus.DayFile); + + foreach (var line in dayfile) + { + linenum++; + var st = new List(Regex.Split(line, CultureInfo.CurrentCulture.TextInfo.ListSeparator)); + + if (st.Count <= 0) continue; + + var datestr = st[0]; + var loggedDate = station.ddmmyyStrToDate(datestr); + var monthOffset = loggedDate.Month - 1; + double valDbl, valDbl2; + + // This assumes the day file is in date order! + if (thisDate.Month != loggedDate.Month) + { + // monthly rain + if (rainThisMonth > highRainMonthVal[monthOffset]) + { + highRainMonthVal[monthOffset] = rainThisMonth; + highRainMonthTime[monthOffset] = thisDate; + } + // reset the date and counter for a new month + thisDate = loggedDate; + rainThisMonth = 0; + } + // hi temp + if (double.TryParse(st[6], out valDbl) && valDbl > highTempVal[monthOffset]) + { + highTempVal[monthOffset] = valDbl; + highTempTime[monthOffset] = GetDateTime(loggedDate, st[7]); + } + // lo temp + if (double.TryParse(st[4], out valDbl) && valDbl < lowTempVal[monthOffset]) + { + lowTempVal[monthOffset] = valDbl; + lowTempTime[monthOffset] = GetDateTime(loggedDate, st[5]); + } + // hi dewpt + if (double.TryParse(st[35], out valDbl) && valDbl > highDewPtVal[monthOffset]) + { + highDewPtVal[monthOffset] = valDbl; + highDewPtTime[monthOffset] = GetDateTime(loggedDate, st[36]); + } + // lo dewpt + if (double.TryParse(st[37], out valDbl) && valDbl < lowDewPtVal[monthOffset]) + { + lowDewPtVal[monthOffset] = valDbl; + lowDewPtTime[monthOffset] = GetDateTime(loggedDate, st[38]); + } + // hi app temp + if (double.TryParse(st[27], out valDbl) && valDbl > highAppTempVal[monthOffset]) + { + highAppTempVal[monthOffset] = valDbl; + highAppTempTime[monthOffset] = GetDateTime(loggedDate, st[28]); + } + // lo app temp + if (double.TryParse(st[29], out valDbl) && valDbl < lowAppTempVal[monthOffset]) + { + lowAppTempVal[monthOffset] = valDbl; + lowAppTempTime[monthOffset] = GetDateTime(loggedDate, st[30]); + } + // lo wind chill + if (double.TryParse(st[33], out valDbl) && valDbl < lowWindChillVal[monthOffset]) + { + lowWindChillVal[monthOffset] = valDbl; + lowWindChillTime[monthOffset] = GetDateTime(loggedDate, st[34]); + } + // hi heat index + if (double.TryParse(st[25], out valDbl) && valDbl > highHeatIndVal[monthOffset]) + { + highHeatIndVal[monthOffset] = valDbl; + highHeatIndTime[monthOffset] = GetDateTime(loggedDate, st[26]); + } + // hi min temp + if (double.TryParse(st[4], out valDbl) && valDbl > highMinTempVal[monthOffset]) + { + highMinTempVal[monthOffset] = valDbl; + highMinTempTime[monthOffset] = loggedDate; + } + // lo max temp + if (double.TryParse(st[6], out valDbl) && valDbl < lowMaxTempVal[monthOffset]) + { + lowMaxTempVal[monthOffset] = valDbl; + lowMaxTempTime[monthOffset] = loggedDate; + } + // temp ranges + if (double.TryParse(st[6], out valDbl) && double.TryParse(st[4], out valDbl2)) + { + // hi temp range + if ((valDbl - valDbl2) > highTempRangeVal[monthOffset]) + { + highTempRangeVal[monthOffset] = valDbl - valDbl2; + highTempRangeTime[monthOffset] = loggedDate; + } + // lo temp range + if ((valDbl - valDbl2) < lowTempRangeVal[monthOffset]) + { + lowTempRangeVal[monthOffset] = valDbl - valDbl2; + lowTempRangeTime[monthOffset] = loggedDate; + } + } + // hi humidity + if (double.TryParse(st[21], out valDbl) && valDbl > highHumVal[monthOffset]) + { + highHumVal[monthOffset] = valDbl; + highHumTime[monthOffset] = GetDateTime(loggedDate, st[22]); + } + // lo humidity + if (double.TryParse(st[19], out valDbl) && valDbl < lowHumVal[monthOffset]) + { + lowHumVal[monthOffset] = valDbl; + lowHumTime[monthOffset] = GetDateTime(loggedDate, st[20]); + } + // hi baro + if (double.TryParse(st[10], out valDbl) && valDbl > highBaroVal[monthOffset]) + { + highBaroVal[monthOffset] = valDbl; + highBaroTime[monthOffset] = GetDateTime(loggedDate, st[11]); + } + // lo baro + if (double.TryParse(st[8], out valDbl) && valDbl < lowBaroVal[monthOffset]) + { + lowBaroVal[monthOffset] = valDbl; + lowBaroTime[monthOffset] = GetDateTime(loggedDate, st[9]); + } + // hi gust + if (double.TryParse(st[1], out valDbl) && valDbl > highGustVal[monthOffset]) + { + highGustVal[monthOffset] = valDbl; + highGustTime[monthOffset] = GetDateTime(loggedDate, st[3]); + } + // hi wind + if (double.TryParse(st[17], out valDbl) && valDbl > highWindVal[monthOffset]) + { + highWindVal[monthOffset] = valDbl; + highWindTime[monthOffset] = GetDateTime(loggedDate, st[18]); + } + // hi wind run + if (double.TryParse(st[16], out valDbl) && valDbl > highWindRunVal[monthOffset]) + { + highWindRunVal[monthOffset] = valDbl; + highWindRunTime[monthOffset] = loggedDate; + } + // hi rain rate + if (double.TryParse(st[12], out valDbl) && valDbl > highRainRateVal[monthOffset]) + { + highRainRateVal[monthOffset] = valDbl; + highRainRateTime[monthOffset] = GetDateTime(loggedDate, st[13]); + } + // hi rain hour + if (double.TryParse(st[31], out valDbl) && valDbl > highRainHourVal[monthOffset]) + { + highRainHourVal[monthOffset] = valDbl; + highRainHourTime[monthOffset] = GetDateTime(loggedDate, st[32]); + } + if (double.TryParse(st[14], out valDbl)) + { + // hi rain day + if (valDbl > highRainDayVal[monthOffset]) + { + highRainDayVal[monthOffset] = valDbl; + highRainDayTime[monthOffset] = loggedDate; + } + + // monthly rain + rainThisMonth += valDbl; + + // dry/wet period + if (valDbl > rainThreshold) + { + if (isDryNow) + { + currentWetPeriod = 1; + isDryNow = false; + if (currentDryPeriod > dryPeriodVal[monthOffset]) + { + dryPeriodVal[monthOffset] = currentDryPeriod; + dryPeriodTime[monthOffset] = thisDateDry; + } + currentDryPeriod = 0; + } + else + { + currentWetPeriod++; + thisDateWet = loggedDate; + } + } + else + { + if (isDryNow) + { + currentDryPeriod++; + thisDateDry = loggedDate; + } + else + { + currentDryPeriod = 1; + isDryNow = true; + if (currentWetPeriod > wetPeriodVal[monthOffset]) + { + wetPeriodVal[monthOffset] = currentWetPeriod; + wetPeriodTime[monthOffset] = thisDateWet; + } + currentWetPeriod = 0; + } + } + } + } + + for (var i = 0; i < 12; i++) + { + var m = i + 1; + json += $"\"{m}-highTempValDayfile\":\"" + highTempVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-highTempTimeDayfile\":\"" + highTempTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-lowTempValDayfile\":\"" + lowTempVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-lowTempTimeDayfile\":\"" + lowTempTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-highDewPointValDayfile\":\"" + highDewPtVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-highDewPointTimeDayfile\":\"" + highDewPtTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-lowDewPointValDayfile\":\"" + lowDewPtVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-lowDewPointTimeDayfile\":\"" + lowDewPtTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-highApparentTempValDayfile\":\"" + highAppTempVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-highApparentTempTimeDayfile\":\"" + highAppTempTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-lowApparentTempValDayfile\":\"" + lowAppTempVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-lowApparentTempTimeDayfile\":\"" + lowAppTempTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-lowWindChillValDayfile\":\"" + lowWindChillVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-lowWindChillTimeDayfile\":\"" + lowWindChillTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-highHeatIndexValDayfile\":\"" + highHeatIndVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-highHeatIndexTimeDayfile\":\"" + highHeatIndTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-highMinTempValDayfile\":\"" + highMinTempVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-highMinTempTimeDayfile\":\"" + highMinTempTime[i].ToString(dateStampFormat) + "\","; + json += $"\"{m}-lowMaxTempValDayfile\":\"" + lowMaxTempVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-lowMaxTempTimeDayfile\":\"" + lowMaxTempTime[i].ToString(dateStampFormat) + "\","; + json += $"\"{m}-highDailyTempRangeValDayfile\":\"" + highTempRangeVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-highDailyTempRangeTimeDayfile\":\"" + highTempRangeTime[i].ToString(dateStampFormat) + "\","; + json += $"\"{m}-lowDailyTempRangeValDayfile\":\"" + lowTempRangeVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-lowDailyTempRangeTimeDayfile\":\"" + lowTempRangeTime[i].ToString(dateStampFormat) + "\","; + json += $"\"{m}-highHumidityValDayfile\":\"" + highHumVal[i].ToString(cumulus.HumFormat) + "\","; + json += $"\"{m}-highHumidityTimeDayfile\":\"" + highHumTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-lowHumidityValDayfile\":\"" + lowHumVal[i].ToString(cumulus.HumFormat) + "\","; + json += $"\"{m}-lowHumidityTimeDayfile\":\"" + lowHumTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-highBarometerValDayfile\":\"" + highBaroVal[i].ToString(cumulus.PressFormat) + "\","; + json += $"\"{m}-highBarometerTimeDayfile\":\"" + highBaroTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-lowBarometerValDayfile\":\"" + lowBaroVal[i].ToString(cumulus.PressFormat) + "\","; + json += $"\"{m}-lowBarometerTimeDayfile\":\"" + lowBaroTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-highGustValDayfile\":\"" + highGustVal[i].ToString(cumulus.WindFormat) + "\","; + json += $"\"{m}-highGustTimeDayfile\":\"" + highGustTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-highWindValDayfile\":\"" + highWindVal[i].ToString(cumulus.WindFormat) + "\","; + json += $"\"{m}-highWindTimeDayfile\":\"" + highWindTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-highWindRunValDayfile\":\"" + highWindRunVal[i].ToString(cumulus.WindRunFormat) + "\","; + json += $"\"{m}-highWindRunTimeDayfile\":\"" + highWindRunTime[i].ToString(dateStampFormat) + "\","; + json += $"\"{m}-highRainRateValDayfile\":\"" + highRainRateVal[i].ToString(cumulus.RainFormat) + "\","; + json += $"\"{m}-highRainRateTimeDayfile\":\"" + highRainRateTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-highHourlyRainValDayfile\":\"" + highRainHourVal[i].ToString(cumulus.RainFormat) + "\","; + json += $"\"{m}-highHourlyRainTimeDayfile\":\"" + highRainHourTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-highDailyRainValDayfile\":\"" + highRainDayVal[i].ToString(cumulus.RainFormat) + "\","; + json += $"\"{m}-highDailyRainTimeDayfile\":\"" + highRainDayTime[i].ToString(dateStampFormat) + "\","; + json += $"\"{m}-highMonthlyRainValDayfile\":\"" + highRainMonthVal[i].ToString(cumulus.RainFormat) + "\","; + json += $"\"{m}-highMonthlyRainTimeDayfile\":\"" + highRainMonthTime[i].ToString("yyyy/MM") + "\","; + json += $"\"{m}-longestDryPeriodValDayfile\":\"" + dryPeriodVal[i].ToString() + "\","; + json += $"\"{m}-longestDryPeriodTimeDayfile\":\"" + dryPeriodTime[i].ToString(dateStampFormat) + "\","; + json += $"\"{m}-longestWetPeriodValDayfile\":\"" + wetPeriodVal[i].ToString() + "\","; + json += $"\"{m}-longestWetPeriodTimeDayfile\":\"" + wetPeriodTime[i].ToString(dateStampFormat) + "\","; + } + json = json.Remove(json.Length - 1); + json += "}"; + } + catch (Exception e) + { + cumulus.LogMessage("Error on line " + linenum + " of " + cumulus.DayFile + ": " + e.Message); + } + } + else + { + cumulus.LogMessage("Error failed to find day file: " + cumulus.DayFile); + } + + watch.Stop(); + var elapsed = watch.ElapsedMilliseconds; + cumulus.LogDebugMessage($"Monthly recs editor Dayfile load = {elapsed} ms"); + + return json; + } + + internal string GetMonthlyRecLogFile() + { + const string timeStampFormat = "dd/MM/yy HH:mm"; + const string dateStampFormat = "dd/MM/yy"; + + var json = "{"; + var datefrom = DateTime.Parse(cumulus.RecordsBeganDate); + datefrom = new DateTime(datefrom.Year, datefrom.Month, 1, 0, 0, 0); + var dateto = DateTime.Now; + dateto = new DateTime(dateto.Year, dateto.Month, 1, 0, 0, 0); + var filedate = datefrom; + + var logFile = cumulus.GetLogFileName(filedate); + var started = false; + var finished = false; + var lastentrydate = datefrom; + + var currentDay = datefrom; + double dayHighTemp = -999; + double dayLowTemp = 999; + double dayWindRun = 0; + double dayRain = 0; + + var isDryNow = false; + var currentDryPeriod = 0; + var currentWetPeriod = 0; + + var rainThreshold = 0.0; + if (cumulus.RainDayThreshold > -1) + rainThreshold = cumulus.RainDayThreshold; + + var highTempVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var lowTempVal = new double[] { 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999 }; + var highDewPtVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var lowDewPtVal = new double[] { 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999 }; + var highAppTempVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var lowAppTempVal = new double[] { 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999 }; + var lowWindChillVal = new double[] { 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999 }; + var highHeatIndVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var highMinTempVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var lowMaxTempVal = new double[] { 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999 }; + var highTempRangeVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var lowTempRangeVal = new double[] { 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999 }; + var highHumVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var lowHumVal = new double[] { 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999 }; + var highBaroVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var lowBaroVal = new double[] { 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999 }; + var highGustVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var highWindVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var highWindRunVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var highRainRateVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var highRainHourVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var highRainDayVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var highRainMonthVal = new double[] { -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 }; + var dryPeriodVal = new[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + var wetPeriodVal = new[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + var thisDate = new DateTime(1900, 01, 01); + var highTempTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var lowTempTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highDewPtTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var lowDewPtTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highAppTempTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var lowAppTempTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var lowWindChillTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highHeatIndTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highMinTempTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var lowMaxTempTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highTempRangeTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var lowTempRangeTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highHumTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var lowHumTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highBaroTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var lowBaroTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highGustTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highWindTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highWindRunTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highRainRateTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highRainHourTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highRainDayTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var highRainMonthTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var dryPeriodTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + var wetPeriodTime = new[] { thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate, thisDate }; + + var thisDateDry = thisDate; + var thisDateWet = thisDate; + + var totalRainfall = 0.0; + + hourRainLog.Clear(); + + var watch = System.Diagnostics.Stopwatch.StartNew(); + + while (!finished) + { + double monthlyRain = 0; + + if (File.Exists(logFile)) + { + cumulus.LogDebugMessage($"GetMonthlyTimeRecLogFile: Processing log file - {logFile}"); + var linenum = 0; + try + { + var logfile = File.ReadAllLines(logFile); + foreach (var line in logfile) + { + // process each record in the file + linenum++; + //var st = new List(Regex.Split(line, CultureInfo.CurrentCulture.TextInfo.ListSeparator)); + // Regex is very expensive, let's assume the separator is always a single character + var st = new List(line.Split((CultureInfo.CurrentCulture.TextInfo.ListSeparator)[0])); + var entrydate = station.ddmmyyhhmmStrToDate(st[0], st[1]); + // We need to work in meto dates not clock dates for day hi/lows + var metoDate = entrydate.AddHours(cumulus.GetHourInc()); + var monthOffset = metoDate.Month - 1; + double valDbl; + + if (!started) + { + lastentrydate = entrydate; + currentDay = metoDate; + started = true; + } + + var outsidetemp = double.Parse(st[2]); + var hum = double.Parse(st[3]); + var dewpoint = double.Parse(st[4]); + var speed = double.Parse(st[5]); + var gust = double.Parse(st[6]); + var rainrate = double.Parse(st[8]); + var raintoday = double.Parse(st[9]); + var pressure = double.Parse(st[10]); + if (st.Count > 15 && double.TryParse(st[15], out valDbl)) + { + // low chill + if (valDbl < lowWindChillVal[monthOffset]) + { + lowWindChillVal[monthOffset] = valDbl; + lowWindChillTime[monthOffset] = entrydate; + } + + } + if (st.Count > 16 && double.TryParse(st[16], out valDbl)) + { + // hi heat + if (valDbl > highHeatIndVal[monthOffset]) + { + highHeatIndVal[monthOffset] = valDbl; + highHeatIndTime[monthOffset] = entrydate; + } + } + if (st.Count > 21 && double.TryParse(st[21], out valDbl)) + { + // hi appt + if (valDbl > highAppTempVal[monthOffset]) + { + highAppTempVal[monthOffset] = valDbl; + highAppTempTime[monthOffset] = entrydate; + } + // lo appt + if (valDbl < lowAppTempVal[monthOffset]) + { + lowAppTempVal[monthOffset] = valDbl; + lowAppTempTime[monthOffset] = entrydate; + } + } + // hi temp + if (outsidetemp > highTempVal[monthOffset]) + { + highTempVal[monthOffset] = outsidetemp; + highTempTime[monthOffset] = entrydate; + } + // lo temp + if (outsidetemp < lowTempVal[monthOffset]) + { + lowTempVal[monthOffset] = outsidetemp; + lowTempTime[monthOffset] = entrydate; + } + // hi dewpoint + if (dewpoint > highDewPtVal[monthOffset]) + { + highDewPtVal[monthOffset] = dewpoint; + highDewPtTime[monthOffset] = entrydate; + } + // low dewpoint + if (dewpoint < lowDewPtVal[monthOffset]) + { + lowDewPtVal[monthOffset] = dewpoint; + lowDewPtTime[monthOffset] = entrydate; + } + // hi hum + if (hum > highHumVal[monthOffset]) + { + highHumVal[monthOffset] = hum; + highHumTime[monthOffset] = entrydate; + } + // lo hum + if (hum < lowHumVal[monthOffset]) + { + lowHumVal[monthOffset] = hum; + lowHumTime[monthOffset] = entrydate; + } + // hi baro + if (pressure > highBaroVal[monthOffset]) + { + highBaroVal[monthOffset] = pressure; + highBaroTime[monthOffset] = entrydate; + } + // lo hum + if (pressure < lowBaroVal[monthOffset]) + { + lowBaroVal[monthOffset] = pressure; + lowBaroTime[monthOffset] = entrydate; + } + // hi gust + if (gust > highGustVal[monthOffset]) + { + highGustVal[monthOffset] = gust; + highGustTime[monthOffset] = entrydate; + } + // hi wind + if (speed > highWindVal[monthOffset]) + { + highWindVal[monthOffset] = speed; + highWindTime[monthOffset] = entrydate; + } + // hi rain rate + if (rainrate > highRainRateVal[monthOffset]) + { + highRainRateVal[monthOffset] = rainrate; + highRainRateTime[monthOffset] = entrydate; + } + + if (monthlyRain > highRainMonthVal[monthOffset]) + { + highRainMonthVal[monthOffset] = monthlyRain; + highRainMonthTime[monthOffset] = entrydate; + } + + // same meto day + if (currentDay.Day == metoDate.Day && currentDay.Month == metoDate.Month && currentDay.Year == metoDate.Year) + { + if (outsidetemp > dayHighTemp) + dayHighTemp = outsidetemp; + + if (outsidetemp < dayLowTemp) + dayLowTemp = outsidetemp; + + if (dayRain < raintoday) + dayRain = raintoday; + + dayWindRun += entrydate.Subtract(lastentrydate).TotalHours * speed; + } + else // new meto day + { + var lastEntryMonthOffset = currentDay.Month - 1; + if (dayHighTemp < lowMaxTempVal[lastEntryMonthOffset]) + { + lowMaxTempVal[lastEntryMonthOffset] = dayHighTemp; + lowMaxTempTime[lastEntryMonthOffset] = currentDay; + } + if (dayLowTemp > highMinTempVal[lastEntryMonthOffset]) + { + highMinTempVal[lastEntryMonthOffset] = dayLowTemp; + highMinTempTime[lastEntryMonthOffset] = currentDay; + } + if (dayHighTemp - dayLowTemp > highTempRangeVal[lastEntryMonthOffset]) + { + highTempRangeVal[lastEntryMonthOffset] = dayHighTemp - dayLowTemp; + highTempRangeTime[lastEntryMonthOffset] = currentDay; + } + if (dayHighTemp - dayLowTemp < lowTempRangeVal[lastEntryMonthOffset]) + { + lowTempRangeVal[lastEntryMonthOffset] = dayHighTemp - dayLowTemp; + lowTempRangeTime[lastEntryMonthOffset] = currentDay; + } + if (dayWindRun > highWindRunVal[lastEntryMonthOffset]) + { + highWindRunVal[lastEntryMonthOffset] = dayWindRun; + highWindRunTime[lastEntryMonthOffset] = currentDay; + } + if (dayRain > highRainDayVal[lastEntryMonthOffset]) + { + highRainDayVal[lastEntryMonthOffset] = dayRain; + highRainDayTime[lastEntryMonthOffset] = currentDay; + } + monthlyRain += dayRain; + + // dry/wet period + if (dayRain > rainThreshold) + { + if (isDryNow) + { + currentWetPeriod = 1; + isDryNow = false; + if (currentDryPeriod > dryPeriodVal[monthOffset]) + { + dryPeriodVal[monthOffset] = currentDryPeriod; + dryPeriodTime[monthOffset] = thisDateDry; + } + currentDryPeriod = 0; + } + else + { + currentWetPeriod++; + thisDateWet = currentDay; + } + } + else + { + if (isDryNow) + { + currentDryPeriod++; + thisDateDry = currentDay; + } + else + { + currentDryPeriod = 1; + isDryNow = true; + if (currentWetPeriod > wetPeriodVal[monthOffset]) + { + wetPeriodVal[monthOffset] = currentWetPeriod; + wetPeriodTime[monthOffset] = thisDateWet; + } + currentWetPeriod = 0; + } + } + + currentDay = metoDate; + dayHighTemp = outsidetemp; + dayLowTemp = outsidetemp; + dayWindRun = 0; + totalRainfall += dayRain; + dayRain = 0; + } + + // hourly rain + /* + * need to track what the rainfall has been in the last rolling hour + * across day rollovers where the count resets + */ + AddLastHourRainEntry(entrydate, totalRainfall + dayRain); + RemoveOldRainData(entrydate); + + var rainThisHour = hourRainLog.Last().Raincounter - hourRainLog.First().Raincounter; + if (rainThisHour > highRainHourVal[monthOffset]) + { + highRainHourVal[monthOffset] = rainThisHour; + highRainHourTime[monthOffset] = entrydate; + } + + lastentrydate = entrydate; + //lastRainMidnight = rainMidnight; + } + } + catch (Exception e) + { + cumulus.LogMessage($"Error at line {linenum} of {logFile} : {e.Message}"); + cumulus.LogMessage("Please edit the file to correct the error"); + } + } + else + { + cumulus.LogDebugMessage($"GetMonthlyRecLogFile: Log file not found - {logFile}"); + } + if (filedate >= dateto) + { + finished = true; + cumulus.LogDebugMessage("GetMonthlyRecLogFile: Finished processing the log files"); + } + else + { + cumulus.LogDebugMessage($"GetMonthlyRecLogFile: Finished processing log file - {logFile}"); + filedate = filedate.AddMonths(1); + logFile = cumulus.GetLogFileName(filedate); + } + } + for (var i = 0; i < 12; i++) + { + var m = i + 1; + json += $"\"{m}-highTempValLogfile\":\"" + highTempVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-highTempTimeLogfile\":\"" + highTempTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-lowTempValLogfile\":\"" + lowTempVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-lowTempTimeLogfile\":\"" + lowTempTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-highDewPointValLogfile\":\"" + highDewPtVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-highDewPointTimeLogfile\":\"" + highDewPtTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-lowDewPointValLogfile\":\"" + lowDewPtVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-lowDewPointTimeLogfile\":\"" + lowDewPtTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-highApparentTempValLogfile\":\"" + highAppTempVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-highApparentTempTimeLogfile\":\"" + highAppTempTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-lowApparentTempValLogfile\":\"" + lowAppTempVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-lowApparentTempTimeLogfile\":\"" + lowAppTempTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-lowWindChillValLogfile\":\"" + lowWindChillVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-lowWindChillTimeLogfile\":\"" + lowWindChillTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-highHeatIndexValLogfile\":\"" + highHeatIndVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-highHeatIndexTimeLogfile\":\"" + highHeatIndTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-highMinTempValLogfile\":\"" + highMinTempVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-highMinTempTimeLogfile\":\"" + highMinTempTime[i].ToString(dateStampFormat) + "\","; + json += $"\"{m}-lowMaxTempValLogfile\":\"" + lowMaxTempVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-lowMaxTempTimeLogfile\":\"" + lowMaxTempTime[i].ToString(dateStampFormat) + "\","; + json += $"\"{m}-highDailyTempRangeValLogfile\":\"" + highTempRangeVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-highDailyTempRangeTimeLogfile\":\"" + highTempRangeTime[i].ToString(dateStampFormat) + "\","; + json += $"\"{m}-lowDailyTempRangeValLogfile\":\"" + lowTempRangeVal[i].ToString(cumulus.TempFormat) + "\","; + json += $"\"{m}-lowDailyTempRangeTimeLogfile\":\"" + lowTempRangeTime[i].ToString(dateStampFormat) + "\","; + json += $"\"{m}-highHumidityValLogfile\":\"" + highHumVal[i].ToString(cumulus.HumFormat) + "\","; + json += $"\"{m}-highHumidityTimeLogfile\":\"" + highHumTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-lowHumidityValLogfile\":\"" + lowHumVal[i].ToString(cumulus.HumFormat) + "\","; + json += $"\"{m}-lowHumidityTimeLogfile\":\"" + lowHumTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-highBarometerValLogfile\":\"" + highBaroVal[i].ToString(cumulus.PressFormat) + "\","; + json += $"\"{m}-highBarometerTimeLogfile\":\"" + highBaroTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-lowBarometerValLogfile\":\"" + lowBaroVal[i].ToString(cumulus.PressFormat) + "\","; + json += $"\"{m}-lowBarometerTimeLogfile\":\"" + lowBaroTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-highGustValLogfile\":\"" + highGustVal[i].ToString(cumulus.WindFormat) + "\","; + json += $"\"{m}-highGustTimeLogfile\":\"" + highGustTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-highWindValLogfile\":\"" + highWindVal[i].ToString(cumulus.WindFormat) + "\","; + json += $"\"{m}-highWindTimeLogfile\":\"" + highWindTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-highWindRunValLogfile\":\"" + highWindRunVal[i].ToString(cumulus.WindRunFormat) + "\","; + json += $"\"{m}-highWindRunTimeLogfile\":\"" + highWindRunTime[i].ToString(dateStampFormat) + "\","; + json += $"\"{m}-highRainRateValLogfile\":\"" + highRainRateVal[i].ToString(cumulus.RainFormat) + "\","; + json += $"\"{m}-highRainRateTimeLogfile\":\"" + highRainRateTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-highHourlyRainValLogfile\":\"" + highRainHourVal[i].ToString(cumulus.RainFormat) + "\","; + json += $"\"{m}-highHourlyRainTimeLogfile\":\"" + highRainHourTime[i].ToString(timeStampFormat) + "\","; + json += $"\"{m}-highDailyRainValLogfile\":\"" + highRainDayVal[i].ToString(cumulus.RainFormat) + "\","; + json += $"\"{m}-highDailyRainTimeLogfile\":\"" + highRainDayTime[i].ToString(dateStampFormat) + "\","; + json += $"\"{m}-highMonthlyRainValLogfile\":\"" + highRainMonthVal[i].ToString(cumulus.RainFormat) + "\","; + json += $"\"{m}-highMonthlyRainTimeLogfile\":\"" + highRainMonthTime[i].ToString("yyyy/MM") + "\","; + json += $"\"{m}-longestDryPeriodValLogfile\":\"" + dryPeriodVal[i].ToString() + "\","; + json += $"\"{m}-longestDryPeriodTimeLogfile\":\"" + dryPeriodTime[i].ToString(dateStampFormat) + "\","; + json += $"\"{m}-longestWetPeriodValLogfile\":\"" + wetPeriodVal[i].ToString() + "\","; + json += $"\"{m}-longestWetPeriodTimeLogfile\":\"" + wetPeriodTime[i].ToString(dateStampFormat) + "\","; + } + + json = json.Remove(json.Length - 1); + json += "}"; + + watch.Stop(); + var elapsed = watch.ElapsedMilliseconds; + cumulus.LogDebugMessage($"Monthly recs editor Logfiles load = {elapsed} ms"); + + return json; + } + + + internal string GetCurrentCond() + { + return "{\"data\":\"" + webtags.GetCurrCondText() + "\"}"; + } + + internal string EditCurrentCond(IHttpContext context) + { + var request = context.Request; + string text; + using (var reader = new StreamReader(request.InputStream, request.ContentEncoding)) + { + text = reader.ReadToEnd(); + } + + var result = SetCurrCondText(text); + + return "{\"result\":\"" + (result ? "Success" : "Failed") + "\"}"; + } + + private bool SetCurrCondText(string currCondText) + { + var fileName = cumulus.AppDir + "currentconditions.txt"; + try + { + cumulus.LogMessage("Writing current conditions to file..."); + + File.WriteAllText(fileName, currCondText); + return true; + } + catch (Exception e) + { + cumulus.LogMessage("Error writing current conditions to file - " + e.Message); + return false; + } + } + + /* + internal class JsonEditRainData + { + public double raintoday { get; set; } + public double raincounter { get; set; } + public double startofdayrain { get; set; } + public double rainmult { get; set; } + } + */ + + private void AddLastHourRainEntry(DateTime ts, double rain) + { + var lasthourrain = new LastHourRainLog(ts, rain); + + hourRainLog.Add(lasthourrain); } private class LastHourRainLog { - public DateTime timestamp; - public double raincounter; + public readonly DateTime Timestamp; + public readonly double Raincounter; public LastHourRainLog(DateTime ts, double rain) { - timestamp = ts; - raincounter = rain; + Timestamp = ts; + Raincounter = rain; } } private void RemoveOldRainData(DateTime ts) { - DateTime onehourago = ts.AddHours(-1); + var onehourago = ts.AddHours(-1); - if (HourRainLog.Count > 0) + if (hourRainLog.Count <= 0) return; + + // there are entries to consider + while ((hourRainLog.Count > 0) && (hourRainLog.First().Timestamp < onehourago)) { - // there are entries to consider - while ((HourRainLog.Count > 0) && (HourRainLog.First().timestamp < onehourago)) - { - // the oldest entry is older than 1 hour ago, delete it - HourRainLog.RemoveAt(0); - } + // the oldest entry is older than 1 hour ago, delete it + hourRainLog.RemoveAt(0); } } } diff --git a/CumulusMX/DavisWllStation.cs b/CumulusMX/DavisWllStation.cs index fb5b8dd5..3abf20f2 100644 --- a/CumulusMX/DavisWllStation.cs +++ b/CumulusMX/DavisWllStation.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; using System.IO.Ports; +using System.Linq; using System.Timers; using System.Net.Http; using System.Net.Sockets; @@ -17,11 +18,10 @@ internal class DavisWllStation : WeatherStation private string ipaddr; private int port; private int duration; - private System.Timers.Timer tmrRealtime; - private System.Timers.Timer tmrCurrent; - private ServiceBrowser serviceBrowser; - private readonly static object threadSafer = new object(); - private static SemaphoreSlim webReq = new SemaphoreSlim(1); + private readonly System.Timers.Timer tmrRealtime; + private readonly System.Timers.Timer tmrCurrent; + private readonly object threadSafer = new object(); + private static readonly SemaphoreSlim WebReq = new SemaphoreSlim(1); private bool startupDayResetIfRequired = true; public DavisWllStation(Cumulus cumulus) : base(cumulus) @@ -31,6 +31,8 @@ public DavisWllStation(Cumulus cumulus) : base(cumulus) //cumulus.UseDataLogger = false; // WLL does not provide a forecast string, so use the Cumulus forecast cumulus.UseCumulusForecast = true; + // initialise the battery status + TxBatText = "1-NA 2-NA 3-NA 4-NA 5-NA 6-NA 7-NA 8-NA"; cumulus.LogMessage("Station type = Davis WLL"); @@ -41,8 +43,8 @@ public DavisWllStation(Cumulus cumulus) : base(cumulus) // Perform zero-config // If it works - check IP address in config file and set/update if required // If it fails - just use the IP address from config file - string serviceType = "_weatherlinklive._tcp"; - serviceBrowser = new ServiceBrowser(); + const string serviceType = "_weatherlinklive._tcp"; + var serviceBrowser = new ServiceBrowser(); serviceBrowser.ServiceAdded += OnServiceAdded; serviceBrowser.ServiceRemoved += OnServiceRemoved; serviceBrowser.ServiceChanged += OnServiceChanged; @@ -65,10 +67,6 @@ public DavisWllStation(Cumulus cumulus) : base(cumulus) StartLoop(); } - // destructor - ~DavisWllStation() - { - } public override void Start() { @@ -82,14 +80,14 @@ public override void Start() // Create a realtime thread to periodically restart broadcasts GetWllRealtime(null, null); - tmrRealtime.Elapsed += new ElapsedEventHandler(GetWllRealtime); + tmrRealtime.Elapsed += GetWllRealtime; tmrRealtime.Interval = cumulus.WllBroadcastDuration * 1000; tmrRealtime.AutoReset = true; tmrRealtime.Start(); // Create a current conditions thread to poll readings once a minute GetWllCurrent(null, null); - tmrCurrent.Elapsed += new ElapsedEventHandler(GetWllCurrent); + tmrCurrent.Elapsed += GetWllCurrent; tmrCurrent.Interval = 60 * 1000; // Every 60 seconds tmrCurrent.AutoReset = true; tmrCurrent.Start(); @@ -103,16 +101,16 @@ public override void Start() port = 22222; } // Create a broadcast listener - UdpClient udpClient = new UdpClient(); + var udpClient = new UdpClient(); udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, port)); var from = new IPEndPoint(0, 0); - Task t = Task.Run(() => + Task.Run(() => { - while (true) + while (!Program.exitSystem) { - var recvBuffer = udpClient.Receive(ref from); - decodeBroadcast(Encoding.UTF8.GetString(recvBuffer)); + var recvBuffer = udpClient.Receive(ref @from); + DecodeBroadcast(Encoding.UTF8.GetString(recvBuffer)); } }); @@ -147,11 +145,10 @@ public override void portDataReceived(object sender, SerialDataReceivedEventArgs private async void GetWllRealtime(object source, ElapsedEventArgs e) { - string ip; - int retry = 2; + var retry = 2; cumulus.LogDebugMessage("Lock: GetWllRealtime waiting for lock"); - webReq.Wait(); + WebReq.Wait(); cumulus.LogDebugMessage("Lock: GetWllRealtime has the lock"); using (HttpClient client = new HttpClient()) @@ -162,12 +159,14 @@ private async void GetWllRealtime(object source, ElapsedEventArgs e) // Call asynchronous network methods in a try/catch block to handle exceptions try { + string ip; + lock (threadSafer) { ip = ipaddr; } - if (CheckIPValid(ip)) + if (CheckIpValid(ip)) { var urlRealtime = "http://" + ip + "/v1/real_time?duration=" + cumulus.WllBroadcastDuration; @@ -175,9 +174,9 @@ private async void GetWllRealtime(object source, ElapsedEventArgs e) client.DefaultRequestHeaders.Add("Connection", "close"); - HttpResponseMessage response = await client.GetAsync(urlRealtime); + var response = await client.GetAsync(urlRealtime); //response.EnsureSuccessStatusCode(); - string responseBody = await response.Content.ReadAsStringAsync(); + var responseBody = await response.Content.ReadAsStringAsync(); responseBody = responseBody.TrimEnd('\r', '\n'); @@ -185,11 +184,7 @@ private async void GetWllRealtime(object source, ElapsedEventArgs e) //Console.WriteLine($" - Realtime response: {responseBody}"); var jObject = Newtonsoft.Json.Linq.JObject.Parse(responseBody); - string err = (string)jObject["error"]; - if (err == null) - { - err = "OK"; - } + var err = (string)jObject["error"] ?? "OK"; port = (int)jObject["data"]["broadcast_port"]; duration = (int)jObject["data"]["duration"]; cumulus.LogDebugMessage("GET realtime request WLL response Code: " + err + ", Port: " + (string)jObject["data"]["broadcast_port"]); @@ -222,28 +217,28 @@ private async void GetWllRealtime(object source, ElapsedEventArgs e) } while (retry > 0); cumulus.LogDebugMessage("Lock: GetWllRealtime releasing lock"); - webReq.Release(); + WebReq.Release(); } } private async void GetWllCurrent(object source, ElapsedEventArgs e) { string ip; - int retry = 2; + var retry = 2; lock (threadSafer) { ip = ipaddr; } - if (CheckIPValid(ip)) + if (CheckIpValid(ip)) { var urlCurrent = $"http://{ip}/v1/current_conditions"; cumulus.LogDebugMessage("Lock: GetWllCurrent waiting for lock"); - webReq.Wait(); + WebReq.Wait(); cumulus.LogDebugMessage("Lock: GetWllCurrent has the lock"); // The WLL will error if already responding to a request from another device, so add a retry @@ -258,15 +253,15 @@ private async void GetWllCurrent(object source, ElapsedEventArgs e) { client.DefaultRequestHeaders.Add("Connection", "close"); - HttpResponseMessage response = await client.GetAsync(urlCurrent); + var response = await client.GetAsync(urlCurrent); response.EnsureSuccessStatusCode(); - string responseBody = await response.Content.ReadAsStringAsync(); + var responseBody = await response.Content.ReadAsStringAsync(); //Console.WriteLine($" - Current conds response: {responseBody}"); // sanity check if (responseBody.StartsWith("{\"data\":{\"did\":")) { - decodeCurrent(responseBody); + DecodeCurrent(responseBody); if (startupDayResetIfRequired) { DoDayResetIfNeeded(); @@ -295,7 +290,7 @@ private async void GetWllCurrent(object source, ElapsedEventArgs e) } while (retry > 0); cumulus.LogDebugMessage("Lock: GetWllCurrent releasing lock"); - webReq.Release(); + WebReq.Release(); } else { @@ -303,10 +298,8 @@ private async void GetWllCurrent(object source, ElapsedEventArgs e) } } - private void decodeBroadcast(string broadcastJson) + private void DecodeBroadcast(string broadcastJson) { - int txid; - try { cumulus.LogDataMessage("WLL Broadcast: " + broadcastJson); @@ -319,7 +312,7 @@ private void decodeBroadcast(string broadcastJson) var dateTime = DateTime.Now; foreach (var rec in data["conditions"]) { - txid = (int)rec["txid"]; + var txid = (int)rec["txid"]; // Wind /* Available fields: @@ -330,7 +323,7 @@ private void decodeBroadcast(string broadcastJson) */ if (cumulus.WllPrimaryWind == txid) { - if (String.IsNullOrEmpty((string)rec["wind_speed_last"]) || String.IsNullOrEmpty((string)rec["wind_dir_last"])) + if (string.IsNullOrEmpty((string)rec["wind_speed_last"]) || string.IsNullOrEmpty((string)rec["wind_dir_last"])) { cumulus.LogDebugMessage($"WLL broadcast: no valid wind speed found [speed={(string)rec["wind_speed_last"]}, dir= {(string)rec["wind_dir_last"]}] on TxId {txid}"); } @@ -370,36 +363,35 @@ private void decodeBroadcast(string broadcastJson) * rec["rainfall_monthly"] * rec["rainfall_year"]) */ - if (cumulus.WllPrimaryRain == txid) + if (cumulus.WllPrimaryRain != txid) continue; + + if (string.IsNullOrEmpty((string)rec["rainfall_year"]) || string.IsNullOrEmpty((string)rec["rain_rate_last"]) || string.IsNullOrEmpty((string)rec["rain_size"])) + { + cumulus.LogDebugMessage($"WLL broadcast: no valid rainfall found [total={(string)rec["rainfall_year"]}, rate= {(string)rec["rain_rate_last"]}] on TxId {txid}"); + } + else { - if (String.IsNullOrEmpty((string)rec["rainfall_year"]) || String.IsNullOrEmpty((string)rec["rain_rate_last"]) || String.IsNullOrEmpty((string)rec["rain_size"])) + var rain = ConvertRainClicksToUser((double)rec["rainfall_year"], (int)rec["rain_size"]); + var rainrate = ConvertRainClicksToUser((double)rec["rain_rate_last"], (int)rec["rain_size"]); + + if (rainrate < 0) { - cumulus.LogDebugMessage($"WLL broadcast: no valid rainfall found [total={(string)rec["rainfall_year"]}, rate= {(string)rec["rain_rate_last"]}] on TxId {txid}"); + rainrate = 0; } - else - { - double rain = ConvertRainClicksToUser((double)rec["rainfall_year"], (int)rec["rain_size"]); - double rainrate = ConvertRainClicksToUser((double)rec["rain_rate_last"], (int)rec["rain_size"]); - - if (rainrate < 0) - { - rainrate = 0; - } - DoRain(rain, rainrate, dateTime); - } + DoRain(rain, rainrate, dateTime); } } UpdateStatusPanel(DateTime.Now); } catch (Exception exp) { - cumulus.LogDebugMessage("decodeBroadcast(): Exception Caught!"); + cumulus.LogDebugMessage("DecodeBroadcast(): Exception Caught!"); cumulus.LogDebugMessage($"Message :" + exp.Message); } } - private void decodeCurrent(string currentJson) + private void DecodeCurrent(string currentJson) { try { @@ -417,9 +409,10 @@ private void decodeCurrent(string currentJson) foreach (var rec in data["conditions"]) { - int type = (int)rec["data_structure_type"]; + var type = (int)rec["data_structure_type"]; int txid; string idx; + uint batt; switch (type) { @@ -428,6 +421,10 @@ private void decodeCurrent(string currentJson) cumulus.LogDebugMessage($"WLL current: found ISS data on TxId {txid}"); + // Battery + batt = (uint)rec["trans_battery_flag"]; + SetTxBatteryStatus(txid, batt); + // Temperature & Humidity if (cumulus.WllPrimaryTempHum == txid) { @@ -442,7 +439,7 @@ private void decodeCurrent(string currentJson) * "thsw_index": 5.5, // **(°F)** */ - if (String.IsNullOrEmpty((string)rec["temp"]) || (int)rec["temp"] == -99) + if (string.IsNullOrEmpty((string)rec["temp"]) || (int)rec["temp"] == -99) { cumulus.LogDebugMessage($"WLL current: no valid Primary temperature value found [{(string)rec["temp"]}] on TxId {txid}"); } @@ -450,7 +447,7 @@ private void decodeCurrent(string currentJson) { cumulus.LogDebugMessage($"WLL current: using temp/hum data from TxId {txid}"); - if (String.IsNullOrEmpty((string)rec["hum"])) + if (string.IsNullOrEmpty((string)rec["hum"])) { cumulus.LogDebugMessage($"WLL current: no valid Primary humidity value found [{(string)rec["hum"]}] on TxId {txid}"); } @@ -461,7 +458,7 @@ private void decodeCurrent(string currentJson) DoOutdoorTemp(ConvertTempFToUser((double)rec["temp"]), dateTime); - if (String.IsNullOrEmpty((string)rec["dew_point"])) + if (string.IsNullOrEmpty((string)rec["dew_point"])) { cumulus.LogDebugMessage($"WLL current: no valid dewpoint value found [{(string)rec["dew_point"]}] on TxId {txid}"); } @@ -473,7 +470,7 @@ private void decodeCurrent(string currentJson) if (!cumulus.CalculatedWC) { // use wind chill from WLL - if (String.IsNullOrEmpty((string)rec["wind_chill"])) + if (string.IsNullOrEmpty((string)rec["wind_chill"])) { cumulus.LogDebugMessage($"WLL current: no valid wind chill value found [{(string)rec["wind_chill"]}] on TxId {txid}"); } @@ -489,31 +486,29 @@ private void decodeCurrent(string currentJson) } else { // Check for Extra temperature/humidity settings - for (int tempTxId = 1; tempTxId <= 8; tempTxId++) + for (var tempTxId = 1; tempTxId <= 8; tempTxId++) { - if (cumulus.WllExtraTempTx[tempTxId - 1] == txid) + if (cumulus.WllExtraTempTx[tempTxId - 1] != txid) continue; + + if (string.IsNullOrEmpty((string)rec["temp"]) || (int)rec["temp"] == -99) { - if (String.IsNullOrEmpty((string)rec["temp"]) || (int)rec["temp"] == -99) + cumulus.LogDebugMessage($"WLL current: no valid Extra temperature value found [{(string)rec["temp"]}] on TxId {txid}"); + } + else + { + cumulus.LogDebugMessage($"WLL current: using extra temp data from TxId {txid}"); + + DoExtraTemp(ConvertTempFToUser((double)rec["temp"]), tempTxId); + + if (!cumulus.WllExtraHumTx[tempTxId - 1]) continue; + + if (string.IsNullOrEmpty((string)rec["hum"])) { - cumulus.LogDebugMessage($"WLL current: no valid Extra temperature value found [{(string)rec["temp"]}] on TxId {txid}"); + cumulus.LogDebugMessage($"WLL current: no valid Extra humidity value found [{(string)rec["hum"]}] on TxId {txid}"); } else { - cumulus.LogDebugMessage($"WLL current: using extra temp data from TxId {txid}"); - - DoExtraTemp(ConvertTempFToUser((double)rec["temp"]), tempTxId); - - if (cumulus.WllExtraHumTx[tempTxId - 1]) - { - if (String.IsNullOrEmpty((string)rec["hum"])) - { - cumulus.LogDebugMessage($"WLL current: no valid Extra humidity value found [{(string)rec["hum"]}] on TxId {txid}"); - } - else - { - DoExtraHum((int)rec["hum"], tempTxId); - } - } + DoExtraHum((int)rec["hum"], tempTxId); } } } @@ -538,7 +533,7 @@ private void decodeCurrent(string currentJson) * "wind_dir_at_hi_speed_last_10_min":0.0, // gust wind direction over last 10 min **(°degree)** */ - if (String.IsNullOrEmpty((string)rec["wind_speed_last"])) + if (string.IsNullOrEmpty((string)rec["wind_speed_last"])) { cumulus.LogDebugMessage($"WLL current: no wind_speed_last value found [{(string)rec["wind_speed_last"]}] on TxId {txid}"); } @@ -548,7 +543,7 @@ private void decodeCurrent(string currentJson) DoWind((double)rec["wind_speed_last"], (int)rec["wind_dir_last"], (double)rec["wind_speed_avg_last_10_min"], dateTime); - if (String.IsNullOrEmpty((string)rec["wind_speed_avg_last_10_min"])) + if (string.IsNullOrEmpty((string)rec["wind_speed_avg_last_10_min"])) { cumulus.LogDebugMessage("WLL current: no wind speed 10 min average value found [avg=" + (string)rec["wind_speed_avg_last_10_min"] + "] on TxId " + txid); @@ -562,8 +557,8 @@ private void decodeCurrent(string currentJson) { // See if the current speed is higher than the current 10-min max // We can then update the figure before the next LOOP2 packet is read - if (String.IsNullOrEmpty((string)rec["wind_speed_hi_last_10_min"]) || - String.IsNullOrEmpty((string)rec["wind_dir_at_hi_speed_last_10_min"])) + if (string.IsNullOrEmpty((string)rec["wind_speed_hi_last_10_min"]) || + string.IsNullOrEmpty((string)rec["wind_dir_at_hi_speed_last_10_min"])) { cumulus.LogDebugMessage("WLL current: no wind speed 10 min high values found [speed=" + (string)rec["wind_speed_hi_last_10_min"] + @@ -623,9 +618,9 @@ private void decodeCurrent(string currentJson) //DoRain(rain, rainrate, dateTime); - if (String.IsNullOrEmpty((string)rec["rain_storm"]) || - String.IsNullOrEmpty((string)rec["rain_storm_start_at"]) || - String.IsNullOrEmpty((string)rec["rain_size"])) + if (string.IsNullOrEmpty((string)rec["rain_storm"]) || + string.IsNullOrEmpty((string)rec["rain_storm_start_at"]) || + string.IsNullOrEmpty((string)rec["rain_size"])) { cumulus.LogDebugMessage("WLL current: no rain storm values found [speed=" + (string)rec["rain_storm"] + @@ -643,7 +638,7 @@ private void decodeCurrent(string currentJson) if (cumulus.WllPrimaryUV == txid) { - if (String.IsNullOrEmpty((string)rec["uv_index"])) + if (string.IsNullOrEmpty((string)rec["uv_index"])) { cumulus.LogDebugMessage($"WLL current: no valid UV value found [{(string)rec["uv_index"]}] on TxId {txid}"); } @@ -656,7 +651,7 @@ private void decodeCurrent(string currentJson) if (cumulus.WllPrimarySolar == txid) { - if (String.IsNullOrEmpty((string)rec["solar_rad"])) + if (string.IsNullOrEmpty((string)rec["solar_rad"])) { cumulus.LogDebugMessage($"WLL current: no valid Solar value found [{(string)rec["solar_rad"]}] on TxId {txid}"); } @@ -668,166 +663,171 @@ private void decodeCurrent(string currentJson) } break; - case 2: // Leaf/Soil Mositure - /* - * Available fields - * "temp_1":null, // most recent valid soil temp slot 1 **(°F)** - * "temp_2":null, // most recent valid soil temp slot 2 **(°F)** - * "temp_3":null, // most recent valid soil temp slot 3 **(°F)** - * "temp_4":null, // most recent valid soil temp slot 4 **(°F)** - * "moist_soil_1":null, // most recent valid soil moisture slot 1 **(|cb|)** - * "moist_soil_2":null, // most recent valid soil moisture slot 2 **(|cb|)** - * "moist_soil_3":null, // most recent valid soil moisture slot 3 **(|cb|)** - * "moist_soil_4":null, // most recent valid soil moisture slot 4 **(|cb|)** - * "wet_leaf_1":null, // most recent valid leaf wetness slot 1 **(no unit)** - * "wet_leaf_2":null, // most recent valid leaf wetness slot 2 **(no unit)** - * "rx_state":null, // configured radio receiver state **(no unit)** - * "trans_battery_flag":null // transmitter battery status flag **(no unit)** - */ - txid = (int)rec["txid"]; + case 2: // Leaf/Soil Moisture + /* + * Available fields + * "temp_1":null, // most recent valid soil temp slot 1 **(°F)** + * "temp_2":null, // most recent valid soil temp slot 2 **(°F)** + * "temp_3":null, // most recent valid soil temp slot 3 **(°F)** + * "temp_4":null, // most recent valid soil temp slot 4 **(°F)** + * "moist_soil_1":null, // most recent valid soil moisture slot 1 **(|cb|)** + * "moist_soil_2":null, // most recent valid soil moisture slot 2 **(|cb|)** + * "moist_soil_3":null, // most recent valid soil moisture slot 3 **(|cb|)** + * "moist_soil_4":null, // most recent valid soil moisture slot 4 **(|cb|)** + * "wet_leaf_1":null, // most recent valid leaf wetness slot 1 **(no unit)** + * "wet_leaf_2":null, // most recent valid leaf wetness slot 2 **(no unit)** + * "rx_state":null, // configured radio receiver state **(no unit)** + * "trans_battery_flag":null // transmitter battery status flag **(no unit)** + */ + + txid = (int)rec["txid"]; - cumulus.LogDebugMessage($"WLL current: found Leaf/Soil data on TxId {txid}"); + cumulus.LogDebugMessage($"WLL current: found Leaf/Soil data on TxId {txid}"); - // Leaf wetness - if (cumulus.WllExtraLeafTx1 == txid) + // Battery + batt = (uint)rec["trans_battery_flag"]; + SetTxBatteryStatus(txid, batt); + + // Leaf wetness + if (cumulus.WllExtraLeafTx1 == txid) + { + idx = "wet_leaf_" + cumulus.WllExtraLeafIdx1; + if (string.IsNullOrEmpty((string)rec[idx])) { - idx = "wet_leaf_" + cumulus.WllExtraLeafIdx1; - if (String.IsNullOrEmpty((string)rec[idx])) - { - cumulus.LogDebugMessage($"WLL current: no valid leaf wetness #{cumulus.WllExtraLeafIdx1} found [{(string)rec[idx]}] on TxId {txid}"); - } - else - { - DoLeafWetness((double)rec[idx], 1); - } + cumulus.LogDebugMessage($"WLL current: no valid leaf wetness #{cumulus.WllExtraLeafIdx1} found [{(string)rec[idx]}] on TxId {txid}"); } - if (cumulus.WllExtraLeafTx2 == txid) + else { - idx = "wet_leaf_" + cumulus.WllExtraLeafIdx2; - if (String.IsNullOrEmpty((string)rec[idx])) - { - cumulus.LogDebugMessage($"WLL current: no valid leaf wetness #{cumulus.WllExtraLeafIdx2} found [{(string)rec[idx]}] on TxId {txid}"); - } - else - { - DoLeafWetness((double)rec[idx], 2); - } + DoLeafWetness((double)rec[idx], 1); + } + } + if (cumulus.WllExtraLeafTx2 == txid) + { + idx = "wet_leaf_" + cumulus.WllExtraLeafIdx2; + if (string.IsNullOrEmpty((string)rec[idx])) + { + cumulus.LogDebugMessage($"WLL current: no valid leaf wetness #{cumulus.WllExtraLeafIdx2} found [{(string)rec[idx]}] on TxId {txid}"); + } + else + { + DoLeafWetness((double)rec[idx], 2); } + } - // Soil moisture - if (cumulus.WllExtraSoilMoistureTx1 == txid) + // Soil moisture + if (cumulus.WllExtraSoilMoistureTx1 == txid) + { + idx = "moist_soil_" + cumulus.WllExtraSoilMoistureIdx1; + if (string.IsNullOrEmpty((string)rec[idx])) { - idx = "moist_soil_" + cumulus.WllExtraSoilMoistureIdx1; - if (String.IsNullOrEmpty((string)rec[idx])) - { - cumulus.LogDebugMessage($"WLL current: no valid soil moisture #{cumulus.WllExtraSoilMoistureIdx1} found [{(string)rec[idx]}] on TxId {txid}"); - } - else - { - DoSoilMoisture((double)rec[idx], 1); - } + cumulus.LogDebugMessage($"WLL current: no valid soil moisture #{cumulus.WllExtraSoilMoistureIdx1} found [{(string)rec[idx]}] on TxId {txid}"); } - if (cumulus.WllExtraSoilMoistureTx2 == txid) + else { - idx = "moist_soil_" + cumulus.WllExtraSoilMoistureIdx2; - if (String.IsNullOrEmpty((string)rec[idx])) - { - cumulus.LogDebugMessage($"WLL current: no valid soil moisture #{cumulus.WllExtraSoilMoistureIdx2} found [{(string)rec[idx]}] on TxId {txid}"); - } - else - { - DoSoilMoisture((double)rec[idx], 2); - } + DoSoilMoisture((double)rec[idx], 1); } - if (cumulus.WllExtraSoilMoistureTx3 == txid) + } + if (cumulus.WllExtraSoilMoistureTx2 == txid) + { + idx = "moist_soil_" + cumulus.WllExtraSoilMoistureIdx2; + if (string.IsNullOrEmpty((string)rec[idx])) { - idx = "moist_soil_" + cumulus.WllExtraSoilMoistureIdx3; - if (String.IsNullOrEmpty((string)rec[idx])) - { - cumulus.LogDebugMessage($"WLL current: no valid soil moisture #{cumulus.WllExtraSoilMoistureIdx3} found [{(string)rec[idx]}] on TxId {txid}"); - } - else - { - DoSoilMoisture((double)rec[idx], 3); - } + cumulus.LogDebugMessage($"WLL current: no valid soil moisture #{cumulus.WllExtraSoilMoistureIdx2} found [{(string)rec[idx]}] on TxId {txid}"); } - if (cumulus.WllExtraSoilMoistureTx4 == txid) + else { - idx = "moist_soil_" + cumulus.WllExtraSoilMoistureIdx4; - if (String.IsNullOrEmpty((string)rec[idx])) - { - cumulus.LogDebugMessage($"WLL current: no valid soil moisture #{cumulus.WllExtraSoilMoistureIdx4} found [{(string)rec[idx]}] on TxId {txid}"); - } - else - { - DoSoilMoisture((double)rec[idx], 4); - } + DoSoilMoisture((double)rec[idx], 2); + } + } + if (cumulus.WllExtraSoilMoistureTx3 == txid) + { + idx = "moist_soil_" + cumulus.WllExtraSoilMoistureIdx3; + if (string.IsNullOrEmpty((string)rec[idx])) + { + cumulus.LogDebugMessage($"WLL current: no valid soil moisture #{cumulus.WllExtraSoilMoistureIdx3} found [{(string)rec[idx]}] on TxId {txid}"); + } + else + { + DoSoilMoisture((double)rec[idx], 3); + } + } + if (cumulus.WllExtraSoilMoistureTx4 == txid) + { + idx = "moist_soil_" + cumulus.WllExtraSoilMoistureIdx4; + if (string.IsNullOrEmpty((string)rec[idx])) + { + cumulus.LogDebugMessage($"WLL current: no valid soil moisture #{cumulus.WllExtraSoilMoistureIdx4} found [{(string)rec[idx]}] on TxId {txid}"); + } + else + { + DoSoilMoisture((double)rec[idx], 4); } + } - // SoilTemperature - if (cumulus.WllExtraSoilTempTx1 == txid) + // SoilTemperature + if (cumulus.WllExtraSoilTempTx1 == txid) + { + idx = "temp_" + cumulus.WllExtraSoilTempIdx1; + if (string.IsNullOrEmpty((string)rec[idx])) { - idx = "temp_" + cumulus.WllExtraSoilTempIdx1; - if (String.IsNullOrEmpty((string)rec[idx])) - { - cumulus.LogDebugMessage($"WLL current: no valid extra soil temp #{cumulus.WllExtraSoilTempIdx1} found [{(string)rec[idx]}] on TxId {txid}"); - } - else - { - DoSoilTemp(ConvertTempFToUser((double)rec[idx]), 1); - } + cumulus.LogDebugMessage($"WLL current: no valid extra soil temp #{cumulus.WllExtraSoilTempIdx1} found [{(string)rec[idx]}] on TxId {txid}"); } - if (cumulus.WllExtraSoilTempTx2 == txid) + else { - idx = "temp_" + cumulus.WllExtraSoilTempIdx2; - if (String.IsNullOrEmpty((string)rec[idx])) - { - cumulus.LogDebugMessage($"WLL current: no valid extra soil temp #{cumulus.WllExtraSoilTempIdx2} found [{(string)rec[idx]}] on TxId {txid}"); - } - else - { - DoSoilTemp(ConvertTempFToUser((double)rec[idx]), 2); - } + DoSoilTemp(ConvertTempFToUser((double)rec[idx]), 1); } - if (cumulus.WllExtraSoilTempTx3 == txid) + } + if (cumulus.WllExtraSoilTempTx2 == txid) + { + idx = "temp_" + cumulus.WllExtraSoilTempIdx2; + if (string.IsNullOrEmpty((string)rec[idx])) { - idx = "temp_" + cumulus.WllExtraSoilTempIdx3; - if (String.IsNullOrEmpty((string)rec[idx])) - { - cumulus.LogDebugMessage($"WLL current: no valid extra soil temp #{cumulus.WllExtraSoilTempIdx3} found [{(string)rec[idx]}] on TxId {txid}"); - } - else - { - DoSoilTemp(ConvertTempFToUser((double)rec[idx]), 3); - } + cumulus.LogDebugMessage($"WLL current: no valid extra soil temp #{cumulus.WllExtraSoilTempIdx2} found [{(string)rec[idx]}] on TxId {txid}"); } - if (cumulus.WllExtraSoilTempTx4 == txid) + else { - idx = "temp_" + cumulus.WllExtraSoilTempIdx4; - if (String.IsNullOrEmpty((string)rec[idx])) - { - cumulus.LogDebugMessage($"WLL current: no valid extra soil temp #{cumulus.WllExtraSoilTempIdx4} found [{(string)rec[idx]}] on TxId {txid}"); - } - else - { - DoSoilTemp(ConvertTempFToUser((double)rec[idx]), 4); - } + DoSoilTemp(ConvertTempFToUser((double)rec[idx]), 2); + } + } + if (cumulus.WllExtraSoilTempTx3 == txid) + { + idx = "temp_" + cumulus.WllExtraSoilTempIdx3; + if (string.IsNullOrEmpty((string)rec[idx])) + { + cumulus.LogDebugMessage($"WLL current: no valid extra soil temp #{cumulus.WllExtraSoilTempIdx3} found [{(string)rec[idx]}] on TxId {txid}"); + } + else + { + DoSoilTemp(ConvertTempFToUser((double)rec[idx]), 3); + } + } + if (cumulus.WllExtraSoilTempTx4 == txid) + { + idx = "temp_" + cumulus.WllExtraSoilTempIdx4; + if (string.IsNullOrEmpty((string)rec[idx])) + { + cumulus.LogDebugMessage($"WLL current: no valid extra soil temp #{cumulus.WllExtraSoilTempIdx4} found [{(string)rec[idx]}] on TxId {txid}"); + } + else + { + DoSoilTemp(ConvertTempFToUser((double)rec[idx]), 4); } + } - // TODO: Extra Humidity? No type for this on WLL - break; + // TODO: Extra Humidity? No type for this on WLL + break; - case 3: // Barometer - /* - * Available fields: - * rec["bar_sea_level"] - * rec["bar_absolute"] - * rec["bar_trend"] - */ + case 3: // Barometer + /* + * Available fields: + * rec["bar_sea_level"] + * rec["bar_absolute"] + * rec["bar_trend"] + */ cumulus.LogDebugMessage("WLL current: found Baro data"); - if (String.IsNullOrEmpty((string)rec["bar_sea_level"])) + if (string.IsNullOrEmpty((string)rec["bar_sea_level"])) { cumulus.LogDebugMessage($"WLL current: no valid baro reading found [{(string)rec["bar_sea_level"]}]"); } @@ -850,7 +850,7 @@ private void decodeCurrent(string currentJson) cumulus.LogDebugMessage("WLL current: found Indoor temp/hum data"); - if (String.IsNullOrEmpty((string)rec["temp_in"])) + if (string.IsNullOrEmpty((string)rec["temp_in"])) { cumulus.LogDebugMessage($"WLL current: no valid temp-in reading found [{(string)rec["temp_in"]}]"); } @@ -859,7 +859,7 @@ private void decodeCurrent(string currentJson) DoIndoorTemp(ConvertTempFToUser((double)rec["temp_in"])); } - if (String.IsNullOrEmpty((string)rec["hum_in"])) + if (string.IsNullOrEmpty((string)rec["hum_in"])) { cumulus.LogDebugMessage($"WLL current: no valid humidity-in reading found [{(string)rec["hum_in"]}]"); } @@ -869,6 +869,10 @@ private void decodeCurrent(string currentJson) } break; + + default: + cumulus.LogDebugMessage($"WLL current: found an unknown tramsmitter type [{type}]!"); + break; } DoForecast("", false); @@ -901,7 +905,7 @@ private void decodeCurrent(string currentJson) } } - private DateTime FromUnixTime(long unixTime) + private static DateTime FromUnixTime(long unixTime) { // WWL uses UTC ticks, convert to local time var utcTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(unixTime); @@ -965,15 +969,15 @@ private void PrintService(char startChar, ServiceAnnouncement service) lock (threadSafer) { ipaddr = service.Addresses[0].ToString(); - } - if (cumulus.VP2IPAddr != ipaddr) - { - cumulus.LogMessage("WLL IP address changed from " + cumulus.VP2IPAddr + " to " + ipaddr); - cumulus.VP2IPAddr = ipaddr; - } - else - { - cumulus.LogMessage("WLL found at IP address " + ipaddr); + if (cumulus.VP2IPAddr != ipaddr) + { + cumulus.LogMessage("WLL IP address changed from " + cumulus.VP2IPAddr + " to " + ipaddr); + cumulus.VP2IPAddr = ipaddr; + } + else + { + cumulus.LogMessage("WLL found at IP address " + ipaddr); + } } } @@ -1019,23 +1023,39 @@ internal double ConvertRainClicksToUser(double clicks, int size) } } - private Boolean CheckIPValid(String strIP) + private static bool CheckIpValid(string strIp) { - if (String.IsNullOrEmpty(strIP)) + if (string.IsNullOrEmpty(strIp)) return false; // Split string by ".", check that array length is 4 - string[] arrOctets = strIP.Split('.'); + var arrOctets = strIp.Split('.'); if (arrOctets.Length != 4) return false; //Check each substring checking that parses to byte - byte obyte = 0; - foreach (string strOctet in arrOctets) - if (!byte.TryParse(strOctet, out obyte)) - return false; - - return true; + return arrOctets.All(strOctet => byte.TryParse(strOctet, out _)); } + private void SetTxBatteryStatus(int txId, uint status) + { + // Split the string + var delimiters = new[] { ' ', '-' }; + var sl = TxBatText.Split(delimiters); + + TxBatText = ""; + for (var i = 1; i <= 8; i++) + { + TxBatText += i; + if (i == txId) + { + TxBatText += (status == 0 ? "-OK " : "-LOW "); + } + else + { + TxBatText += "-" + sl[(i-1) * 2 + 1] + " "; + } + } + TxBatText = TxBatText.Trim(); + } } } \ No newline at end of file diff --git a/CumulusMX/GW1000Station.cs b/CumulusMX/GW1000Station.cs index 43ef4e1d..2af8b94f 100644 --- a/CumulusMX/GW1000Station.cs +++ b/CumulusMX/GW1000Station.cs @@ -1,12 +1,10 @@ using System; using System.ComponentModel; using System.IO.Ports; -using System.IO; using System.Net.Sockets; using System.Text; using System.Threading; using System.Runtime.InteropServices; -using System.Runtime.Serialization.Formatters.Binary; namespace CumulusMX @@ -14,9 +12,9 @@ namespace CumulusMX internal class GW1000Station : WeatherStation { private readonly string ipaddr; - private static int port = 45000; + private const int AT_port = 45000; private int lastMinute; - bool tenMinuteChanged = true; + private bool tenMinuteChanged = true; private TcpClient socket; private bool connectedOK = false; @@ -120,6 +118,7 @@ private enum CommandRespSize : int ch8 = 1 << 7 } + /* private enum _wh41_ch : UInt16 { ch1 = 15 << 0, @@ -127,8 +126,9 @@ private enum _wh41_ch : UInt16 ch3 = 15 << 8, ch4 = 15 << 12 } + */ - [Flags] private enum _wh51_ch : UInt16 + [Flags] private enum _wh51_ch : UInt32 { ch1 = 1 << 0, ch2 = 1 << 1, @@ -203,7 +203,7 @@ public GW1000Station(Cumulus cumulus) : base(cumulus) ipaddr = cumulus.Gw1000IpAddress; - cumulus.LogMessage("IP address = " + ipaddr + " Port = " + port); + cumulus.LogMessage("IP address = " + ipaddr + " Port = " + AT_port); socket = OpenTcpPort(); connectedOK = socket != null; @@ -222,10 +222,10 @@ public GW1000Station(Cumulus cumulus) : base(cumulus) if (connectedOK) { // Get the firmware version as check we are communicating - GW1000FirmwareVersion = getFirmwareVersion(); + GW1000FirmwareVersion = GetFirmwareVersion(); cumulus.LogMessage($"GW1000 firmware version: {GW1000FirmwareVersion}"); - getSensorIds(); + GetSensorIds(); } timerStartNeeded = true; @@ -255,7 +255,7 @@ private TcpClient OpenTcpPort() cumulus.LogDebugMessage("GW1000 Connect attempt " + attempt); try { - client = new TcpClient(ipaddr, port); + client = new TcpClient(ipaddr, AT_port); if (!client.Connected) { @@ -271,7 +271,7 @@ private TcpClient OpenTcpPort() } // Set the timeout of the underlying stream - if (!(client == null)) + if (client != null) { client.GetStream().ReadTimeout = 2500; cumulus.LogDebugMessage("GW1000 reconnected"); @@ -327,8 +327,11 @@ public override void Start() } finally { - socket.GetStream().WriteByte(10); - socket.Close(); + if (socket != null) + { + socket.GetStream().WriteByte(10); + socket.Close(); + } } } @@ -361,9 +364,9 @@ private void bw_DoStart(object sender, DoWorkEventArgs e) } - private string getFirmwareVersion() + private string GetFirmwareVersion() { - string response = "???"; + var response = "???"; cumulus.LogMessage("Reading firmware version"); var data = DoCommand((byte)Commands.CMD_READ_FIRMWARE_VERSION); @@ -374,7 +377,7 @@ private string getFirmwareVersion() return response; } - private bool getSensorIds() + private bool GetSensorIds() { cumulus.LogMessage("Reading sensor ids"); @@ -398,7 +401,7 @@ private bool getSensorIds() { for (int i = 4; i < data[3]; i += 7) { - printSensorInfo(data, i); + PrintSensorInfo(data, i); } return true; @@ -409,26 +412,26 @@ private bool getSensorIds() } } - private void printSensorInfo(byte[] data, int idx) + private void PrintSensorInfo(byte[] data, int idx) { - var id = convertBigEndianUInt32(data, idx + 1); + var id = ConvertBigEndianUInt32(data, idx + 1); + var type = Enum.GetName(typeof(SensorIds), data[idx]); - string type = Enum.GetName(typeof(SensorIds), data[idx]); if (string.IsNullOrEmpty(type)) { type = $"unknown type = {id}"; } - if (id == 0xFFFFFFFE) - { - cumulus.LogDebugMessage($" - {type} sensor = disabled"); - } - else if (id == 0xFFFFFFFF) - { - cumulus.LogDebugMessage($" - {type} sensor = registering"); - } - else + switch (id) { - cumulus.LogDebugMessage($" - {type} sensor id = {id} signal = {data[idx+5]} battery = {data[idx+6]}"); + case 0xFFFFFFFE: + cumulus.LogDebugMessage($" - {type} sensor = disabled"); + break; + case 0xFFFFFFFF: + cumulus.LogDebugMessage($" - {type} sensor = registering"); + break; + default: + cumulus.LogDebugMessage($" - {type} sensor id = {id} signal = {data[idx+5]} battery = {data[idx+6]}"); + break; } } @@ -467,38 +470,38 @@ public void GetLiveData() Int16 tempInt16; UInt16 tempUint16; UInt32 tempUint32; - int idx = 5; + var idx = 5; var dateTime = DateTime.Now; - ushort size = convertBigEndianUInt16(data, 3); - int chan = 0; + var size = ConvertBigEndianUInt16(data, 3); + int chan; - double windSpeedLast, rainRateLast, rainLast, gustLast; - windSpeedLast = rainRateLast = rainLast = gustLast = 0; - int windDirLast = 0; + double rainRateLast, rainLast, gustLast; + var windSpeedLast = rainRateLast = rainLast = gustLast = 0; + var windDirLast = 0; do { switch (data[idx++]) { case 0x01: //Indoor Temperature (℃) - tempInt16 = convertBigEndianInt16(data, idx); + tempInt16 = ConvertBigEndianInt16(data, idx); DoIndoorTemp(ConvertTempCToUser(tempInt16 / 10.0)); idx += 2; break; case 0x02: //Outdoor Temperature (℃) - tempInt16 = convertBigEndianInt16(data, idx); + tempInt16 = ConvertBigEndianInt16(data, idx); DoOutdoorTemp(ConvertTempCToUser(tempInt16 / 10.0), dateTime); idx += 2; break; case 0x03: //Dew point (℃) - tempInt16 = convertBigEndianInt16(data, idx); + tempInt16 = ConvertBigEndianInt16(data, idx); DoOutdoorDewpoint(ConvertTempCToUser(tempInt16 / 10.0), dateTime); idx += 2; break; case 0x04: //Wind chill (℃) if (!cumulus.CalculatedWC) { - tempInt16 = convertBigEndianInt16(data, idx); + tempInt16 = ConvertBigEndianInt16(data, idx); DoWindChill(ConvertTempFToUser(tempInt16 / 10.0), dateTime); } idx += 2; @@ -519,28 +522,28 @@ public void GetLiveData() idx += 2; break; case 0x09: //Relative Barometric (hpa) - tempUint16 = convertBigEndianUInt16(data, idx); + tempUint16 = ConvertBigEndianUInt16(data, idx); DoPressure(ConvertPressMBToUser(tempUint16 / 10.0), dateTime); DoPressTrend("Pressure trend"); idx += 2; break; case 0x0A: //Wind Direction (360°) - windDirLast = convertBigEndianUInt16(data, idx); + windDirLast = ConvertBigEndianUInt16(data, idx); idx += 2; break; case 0x0B: //Wind Speed (m/s) - windSpeedLast = ConvertWindMSToUser(convertBigEndianUInt16(data, idx) / 10.0); + windSpeedLast = ConvertWindMSToUser(ConvertBigEndianUInt16(data, idx) / 10.0); idx += 2; break; case 0x0C: // Gust speed (m/s) - gustLast = ConvertWindMSToUser(convertBigEndianUInt16(data, idx) / 10.0); + gustLast = ConvertWindMSToUser(ConvertBigEndianUInt16(data, idx) / 10.0); idx += 2; break; case 0x0D: //Rain Event (mm) idx += 2; break; case 0x0E: //Rain Rate (mm/h) - rainRateLast = ConvertRainMMToUser(convertBigEndianUInt16(data, idx) / 10.0); + rainRateLast = ConvertRainMMToUser(ConvertBigEndianUInt16(data, idx) / 10.0); idx += 2; break; case 0x0F: //Rain hour (mm) @@ -556,7 +559,7 @@ public void GetLiveData() idx += 4; break; case 0x13: //Rain Year (mm) - rainLast = ConvertRainMMToUser(convertBigEndianUInt32(data, idx) / 10.0); + rainLast = ConvertRainMMToUser(ConvertBigEndianUInt32(data, idx) / 10.0); idx += 4; break; case 0x14: //Rain Totals (mm) @@ -564,7 +567,7 @@ public void GetLiveData() break; case 0x15: //Light (lux) // convert LUX to W/m2 - approximately! - tempUint32 = (UInt32)(convertBigEndianUInt32(data, idx) * cumulus.LuxToWM2 / 10.0); + tempUint32 = (UInt32)(ConvertBigEndianUInt32(data, idx) * cumulus.LuxToWM2 / 10.0); DoSolarRad((int)tempUint32, dateTime); idx += 4; break; @@ -590,7 +593,7 @@ public void GetLiveData() case 0x20: //Temperature 7(℃) case 0x21: //Temperature 8(℃) chan = data[idx - 1] - 0x1A + 1; - tempInt16 = convertBigEndianInt16(data, idx); + tempInt16 = ConvertBigEndianInt16(data, idx); DoExtraTemp(ConvertTempCToUser(tempInt16 / 10.0), chan); idx += 2; break; @@ -624,7 +627,7 @@ public void GetLiveData() case 0x49: //Soil Temperature16 (℃) chan = data[idx - 1] - 0x2B + 1; chan += (chan - 1); - tempInt16 = convertBigEndianInt16(data, idx); + tempInt16 = ConvertBigEndianInt16(data, idx); DoSoilTemp(ConvertTempCToUser(tempInt16 / 10.0), chan); idx += 2; break; @@ -647,7 +650,7 @@ public void GetLiveData() // figure out the channel number chan = data[idx - 1] - 0x2C + 1; chan += (chan - 1); - DoSoilMoisture((double)data[idx], chan); + DoSoilMoisture(data[idx], chan); idx += 1; break; case 0x4C: //All sensor lowbatt 16 char @@ -660,7 +663,7 @@ public void GetLiveData() idx += 16; break; case 0x2A: //PM2.5 Air Quality Sensor(μg/m3) - tempUint16 = convertBigEndianUInt16(data, idx); + tempUint16 = ConvertBigEndianUInt16(data, idx); DoAirQuality(tempUint16 / 10.0, 1); idx += 2; break; @@ -669,7 +672,7 @@ public void GetLiveData() case 0x4F: //for pm25_ch3 case 0x50: //for pm25_ch4 chan = data[idx - 1] - 0x4D + 1; - tempUint16 = convertBigEndianUInt16(data, idx); + tempUint16 = ConvertBigEndianUInt16(data, idx); DoAirQualityAvg(tempUint16 / 10.0, chan); idx += 2; break; @@ -677,7 +680,7 @@ public void GetLiveData() case 0x52: //PM2.5 ch_3 Air Quality Sensor(μg/m3) case 0x53: //PM2.5 ch_4 Air Quality Sensor(μg/m3) chan = data[idx - 1] - 0x51 + 2; - tempUint16 = convertBigEndianUInt16(data, idx); + tempUint16 = ConvertBigEndianUInt16(data, idx); DoAirQuality(tempUint16 / 10.0, chan); idx += 2; break; @@ -695,15 +698,15 @@ public void GetLiveData() idx += 1; break; case 0x62: //Lightning time (UTC) - tempUint32 = convertBigEndianUInt32(data, idx); - System.DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc); + tempUint32 = ConvertBigEndianUInt32(data, idx); + var dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); dtDateTime = dtDateTime.AddSeconds(tempUint32).ToLocalTime(); //cumulus.LogDebugMessage($"Lightning time={dtDateTime}"); LightningTime = dtDateTime; idx += 4; break; case 0x63: //Lightning strikes today - tempUint32 = convertBigEndianUInt32(data, idx); + tempUint32 = ConvertBigEndianUInt32(data, idx); cumulus.LogDebugMessage($"Lightning power={tempUint32}"); LightningStrikesToday = (int)tempUint32; idx += 4; @@ -769,22 +772,22 @@ public override void portDataReceived(object sender, SerialDataReceivedEventArgs { } - private byte[] DoCommand(byte Command) + private byte[] DoCommand(byte command) { - byte[] buffer = new byte[2028]; + var buffer = new byte[2028]; byte[] data; var bytesRead = 0; - var cmdName = Enum.GetName(typeof(Commands), Command); + var cmdName = Enum.GetName(typeof(Commands), command); - CommandPayload payload = new CommandPayload(Command); - CommTimer tmrComm = new CommTimer(); + var payload = new CommandPayload(command); + var tmrComm = new CommTimer(); - byte[] bytes = payload.Serialise(); + var bytes = payload.Serialise(); try { - NetworkStream stream = socket.GetStream(); + var stream = socket.GetStream(); stream.Write(bytes, 0, bytes.Length); tmrComm.Start(1000); @@ -809,7 +812,7 @@ private byte[] DoCommand(byte Command) } } // Check the response is to our command and checksum is OK - if (buffer[2] != Command || !ChecksumOK(buffer, (int)Enum.Parse(typeof(CommandRespSize), cmdName))) + if (buffer[2] != command || !ChecksumOK(buffer, (int)Enum.Parse(typeof(CommandRespSize), cmdName))) { cumulus.LogMessage($"DoCommand({cmdName}): Invalid response"); cumulus.LogDataMessage("Received 0x" + BitConverter.ToString(buffer, 0, bytesRead - 1)); @@ -844,58 +847,58 @@ private void DoBatteryStatus(byte[] data, int index) BatteryStatus status = (BatteryStatus)RawDeserialize(data, index, typeof(BatteryStatus)); cumulus.LogDebugMessage("battery status..."); - var str = "singles> wh24=" + testBattery1(status.single, (byte)_sig_sen.wh24); - str += " wh25=" + testBattery1(status.single, (byte)_sig_sen.wh25); - str += " wh26=" + testBattery1(status.single, (byte)_sig_sen.wh26); - str += " wh40=" + testBattery1(status.single, (byte)_sig_sen.wh40); + var str = "singles> wh24=" + TestBattery1(status.single, (byte)_sig_sen.wh24); + str += " wh25=" + TestBattery1(status.single, (byte)_sig_sen.wh25); + str += " wh26=" + TestBattery1(status.single, (byte)_sig_sen.wh26); + str += " wh40=" + TestBattery1(status.single, (byte)_sig_sen.wh40); cumulus.LogDebugMessage(str); - str = "wh31> ch1=" + testBattery1(status.wh31, (byte)_wh31_ch.ch1); - str += " ch2=" + testBattery1(status.wh31, (byte)_wh31_ch.ch2); - str += " ch3=" + testBattery1(status.wh31, (byte)_wh31_ch.ch3); - str += " ch4=" + testBattery1(status.wh31, (byte)_wh31_ch.ch4); - str += " ch5=" + testBattery1(status.wh31, (byte)_wh31_ch.ch5); - str += " ch6=" + testBattery1(status.wh31, (byte)_wh31_ch.ch6); - str += " ch7=" + testBattery1(status.wh31, (byte)_wh31_ch.ch7); - str += " ch8=" + testBattery1(status.wh31, (byte)_wh31_ch.ch8); + str = "wh31> ch1=" + TestBattery1(status.wh31, (byte)_wh31_ch.ch1); + str += " ch2=" + TestBattery1(status.wh31, (byte)_wh31_ch.ch2); + str += " ch3=" + TestBattery1(status.wh31, (byte)_wh31_ch.ch3); + str += " ch4=" + TestBattery1(status.wh31, (byte)_wh31_ch.ch4); + str += " ch5=" + TestBattery1(status.wh31, (byte)_wh31_ch.ch5); + str += " ch6=" + TestBattery1(status.wh31, (byte)_wh31_ch.ch6); + str += " ch7=" + TestBattery1(status.wh31, (byte)_wh31_ch.ch7); + str += " ch8=" + TestBattery1(status.wh31, (byte)_wh31_ch.ch8); cumulus.LogDebugMessage(str); - str = "wh41> ch1=" + testBattery2(status.wh41, 0x0F); - str += " ch2=" + testBattery2((UInt16)(status.wh41 >> 4), 0x0F); - str += " ch3=" + testBattery2((UInt16)(status.wh41 >> 8), 0x0F); - str += " ch4=" + testBattery2((UInt16)(status.wh41 >> 12), 0x0F); + str = "wh41> ch1=" + TestBattery2(status.wh41, 0x0F); + str += " ch2=" + TestBattery2((UInt16)(status.wh41 >> 4), 0x0F); + str += " ch3=" + TestBattery2((UInt16)(status.wh41 >> 8), 0x0F); + str += " ch4=" + TestBattery2((UInt16)(status.wh41 >> 12), 0x0F); cumulus.LogDebugMessage(str); - str = "wh51> ch1=" + testBattery1(status.wh51, (byte)_wh51_ch.ch1); - str += " ch2=" + testBattery1(status.wh31, (byte)_wh51_ch.ch2); - str += " ch3=" + testBattery1(status.wh31, (byte)_wh51_ch.ch3); - str += " ch4=" + testBattery1(status.wh31, (byte)_wh51_ch.ch4); - str += " ch5=" + testBattery1(status.wh31, (byte)_wh51_ch.ch5); - str += " ch6=" + testBattery1(status.wh31, (byte)_wh51_ch.ch6); - str += " ch7=" + testBattery1(status.wh31, (byte)_wh51_ch.ch7); - str += " ch8=" + testBattery1(status.wh31, (byte)_wh51_ch.ch8); + str = "wh51> ch1=" + TestBattery1(status.wh51, (byte)_wh51_ch.ch1); + str += " ch2=" + TestBattery1(status.wh31, (byte)_wh51_ch.ch2); + str += " ch3=" + TestBattery1(status.wh31, (byte)_wh51_ch.ch3); + str += " ch4=" + TestBattery1(status.wh31, (byte)_wh51_ch.ch4); + str += " ch5=" + TestBattery1(status.wh31, (byte)_wh51_ch.ch5); + str += " ch6=" + TestBattery1(status.wh31, (byte)_wh51_ch.ch6); + str += " ch7=" + TestBattery1(status.wh31, (byte)_wh51_ch.ch7); + str += " ch8=" + TestBattery1(status.wh31, (byte)_wh51_ch.ch8); cumulus.LogDebugMessage(str); - cumulus.LogDebugMessage("wh57> " + testBattery3(status.wh57)); + cumulus.LogDebugMessage("wh57> " + TestBattery3(status.wh57)); cumulus.LogDebugMessage("wh68> " + (0.02 * status.wh68) + "V"); cumulus.LogDebugMessage("wh80> " + (0.02 * status.wh80) + "V"); - str = "wh55> ch1=" + testBattery3(status.wh55_ch1); - str += " ch2=" + testBattery3(status.wh55_ch2); - str += " ch3=" + testBattery3(status.wh55_ch2); - str += " ch4=" + testBattery3(status.wh55_ch2); + str = "wh55> ch1=" + TestBattery3(status.wh55_ch1); + str += " ch2=" + TestBattery3(status.wh55_ch2); + str += " ch3=" + TestBattery3(status.wh55_ch2); + str += " ch4=" + TestBattery3(status.wh55_ch2); cumulus.LogDebugMessage(str); } - private string testBattery1(byte value, byte mask) + private string TestBattery1(byte value, byte mask) { if ((value & mask) == 0) return "OK"; else return "Low"; } - private string testBattery1(UInt16 value, UInt16 mask) + private string TestBattery1(UInt16 value, UInt16 mask) { if ((value & mask) == 0) return "OK"; @@ -903,7 +906,7 @@ private string testBattery1(UInt16 value, UInt16 mask) return "Low"; } - private string testBattery2(UInt16 value, UInt16 mask) + private string TestBattery2(UInt16 value, UInt16 mask) { if ((value & mask) > 1) return "OK"; @@ -911,7 +914,7 @@ private string testBattery2(UInt16 value, UInt16 mask) return "Low"; } - private string testBattery3(byte value) + private string TestBattery3(byte value) { if (value > 1) return "OK"; @@ -935,7 +938,7 @@ public static object RawDeserialize(byte[] rawData, int position, Type anyType) private struct CommandPayload { private readonly ushort Header; - public byte Command; + private readonly byte Command; private readonly byte Size; //public byte[] Data; private readonly byte Checksum; @@ -943,6 +946,7 @@ private struct CommandPayload //public CommandPayload(byte command, byte[] data) : this() public CommandPayload(byte command) : this() { + //ushort header; this.Header = 0xffff; this.Command = command; this.Size = (byte) (Marshal.SizeOf(typeof(CommandPayload)) - 3); @@ -1033,7 +1037,7 @@ private bool ChecksumOK(byte[] data, int lengthBytes) } else { - size = convertBigEndianUInt16(data, 3); + size = ConvertBigEndianUInt16(data, 3); } byte checksum = (byte)(data[2] + data[3]); @@ -1053,53 +1057,52 @@ private bool ChecksumOK(byte[] data, int lengthBytes) } } - private UInt16 convertBigEndianUInt16(byte[] array, int start) + private static UInt16 ConvertBigEndianUInt16(byte[] array, int start) { return (UInt16)(array[start] << 8 | array[start+1]); } - private Int16 convertBigEndianInt16(byte[] array, int start) + private static Int16 ConvertBigEndianInt16(byte[] array, int start) { return (Int16)((array[start] << 8) + array[start + 1]); } - private UInt32 convertBigEndianUInt32(byte[] array, int start) + private static UInt32 ConvertBigEndianUInt32(byte[] array, int start) { return (UInt32)(array[start++] << 24 | array[start++] << 16 | array[start++] << 8 | array[start]); } private void CheckHighGust(double gust, int gustdir, DateTime timestamp) { - if (gust > RecentMaxGust) - { - if (gust > highgusttoday) - { - highgusttoday = gust; - highgusttodaytime = timestamp; - highgustbearing = gustdir; - WriteTodayFile(timestamp, false); - } - if (gust > HighGustThisMonth) - { - HighGustThisMonth = gust; - HighGustThisMonthTS = timestamp; - WriteMonthIniFile(); - } - if (gust > HighGustThisYear) - { - HighGustThisYear = gust; - HighGustThisYearTS = timestamp; - WriteYearIniFile(); - } - // All time high gust? - if (gust > alltimerecarray[AT_highgust].value) - { - SetAlltime(AT_highgust, gust, timestamp); - } + if (!(gust > RecentMaxGust)) return; - // check for monthly all time records (and set) - CheckMonthlyAlltime(AT_highgust, gust, true, timestamp); + if (gust > highgusttoday) + { + highgusttoday = gust; + highgusttodaytime = timestamp; + highgustbearing = gustdir; + WriteTodayFile(timestamp, false); } + if (gust > HighGustThisMonth) + { + HighGustThisMonth = gust; + HighGustThisMonthTS = timestamp; + WriteMonthIniFile(); + } + if (gust > HighGustThisYear) + { + HighGustThisYear = gust; + HighGustThisYearTS = timestamp; + WriteYearIniFile(); + } + // All time high gust? + if (gust > alltimerecarray[AT_highgust].value) + { + SetAlltime(AT_highgust, gust, timestamp); + } + + // check for monthly all time records (and set) + CheckMonthlyAlltime(AT_highgust, gust, true, timestamp); } diff --git a/CumulusMX/Properties/AssemblyInfo.cs b/CumulusMX/Properties/AssemblyInfo.cs index 9f80d7c8..62593a1e 100644 --- a/CumulusMX/Properties/AssemblyInfo.cs +++ b/CumulusMX/Properties/AssemblyInfo.cs @@ -6,7 +6,7 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("CumulusMX")] -[assembly: AssemblyDescription("Build 3057")] +[assembly: AssemblyDescription("Build 3058")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("CumulusMX")] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("3.2.1.3057")] -[assembly: AssemblyFileVersion("3.2.1.3057")] +[assembly: AssemblyVersion("3.2.2.3058")] +[assembly: AssemblyFileVersion("3.2.2.3058")] diff --git a/CumulusMX/WeatherStation.cs b/CumulusMX/WeatherStation.cs index 31ef7d6e..30d19cef 100644 --- a/CumulusMX/WeatherStation.cs +++ b/CumulusMX/WeatherStation.cs @@ -3816,6 +3816,31 @@ public void SetAlltime(int index, double value, DateTime timestamp) } } + public void SetMonthlyAlltime(int index, double value, DateTime timestamp) + { + lock (monthlyalltimeIniThreadLock) + { + var month = timestamp.Month; + + double oldvalue = monthlyrecarray[index, month].value; + DateTime oldts = monthlyrecarray[index, month].timestamp; + + string s = "Changed monthly record, month = " + month + ": "; + s = s + timestamp.ToString("yyyy-MM-dd") + FormatDateTime(" HH", timestamp) + ":" + FormatDateTime("mm ", timestamp); + s = s + value.ToString("F3") + " \"" + alltimedescs[index] + "\" "; + s = s + FormatDateTime("yyyy-MM-dd", oldts) + FormatDateTime(" HH", oldts) + ":" + FormatDateTime("mm ", oldts); + s = s + oldvalue.ToString("F3"); + + cumulus.LogMessage(s); + + monthlyrecarray[index, month].data_type = index; + monthlyrecarray[index, month].value = value; + + WriteMonthlyAlltimeIniFile(); + } + } + + /// /// Returns the angle from bearing2 to bearing1, in the range -180 to +180 degrees diff --git a/Updates.txt b/Updates.txt index c5117098..f444231b 100644 --- a/Updates.txt +++ b/Updates.txt @@ -1,3 +1,20 @@ +3.2.2 - b3058 +============= +- Implements the missing <#txbattery> web tag for WLL devices +- Fix default website pages header not wrapping on small screens +- Adds Monthly Records editor +- Fixes and improvements to the All Time Records editor + +- Updated files + \CumulusMX.exe + \interface\.html + \web\T.htm + +- New files + \interface\monthlyrecseditor.html + \interface\js\monthlyrecseditor.js + + 3.2.1 - b3057 ============= - Fix for WMR200 stations writing a zero value Apparent Temperature to the log files when retrieving logger data