From 6d3b2625a6d3a2acc878aa837b2ba3bba7b8f864 Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Sun, 9 May 2021 09:46:59 +0100 Subject: [PATCH 1/6] - Fix: Send email failing if email logging is not enabled --- CumulusMX/AlarmSettings.cs | 1 + CumulusMX/Api.cs | 2 +- CumulusMX/EmailSender.cs | 4 ++-- CumulusMX/Properties/AssemblyInfo.cs | 6 +++--- Updates.txt | 5 +++++ 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CumulusMX/AlarmSettings.cs b/CumulusMX/AlarmSettings.cs index d4584052..a629c2a4 100644 --- a/CumulusMX/AlarmSettings.cs +++ b/CumulusMX/AlarmSettings.cs @@ -429,6 +429,7 @@ public string TestEmail(IHttpContext context) } else { + context.Response.StatusCode = 500; return ret; } } diff --git a/CumulusMX/Api.cs b/CumulusMX/Api.cs index 1ef7dd1d..85402e07 100644 --- a/CumulusMX/Api.cs +++ b/CumulusMX/Api.cs @@ -941,7 +941,7 @@ public async Task SettingsSet() case "ftpnow.json": return await this.JsonResponseAsync(stationSettings.FtpNow(this)); case "testemail.json": - return await this.JsonResponseAsync(alarmSettings.TestEmail(this)); + return await this.StringResponseAsync(alarmSettings.TestEmail(this)); } throw new KeyNotFoundException("Key Not Found: " + lastSegment); diff --git a/CumulusMX/EmailSender.cs b/CumulusMX/EmailSender.cs index 2296c9ab..894479a3 100644 --- a/CumulusMX/EmailSender.cs +++ b/CumulusMX/EmailSender.cs @@ -50,7 +50,7 @@ public async void SendEmail(string[] to, string from, string subject, string mes m.Body = bodyBuilder.ToMessageBody(); - using (SmtpClient client = new SmtpClient(cumulus.SmtpOptions.Logging ? new ProtocolLogger("MXdiags/smtp.log") : null)) + using (SmtpClient client = cumulus.SmtpOptions.Logging ? new SmtpClient(new ProtocolLogger("MXdiags/smtp.log")) : new SmtpClient()) { await client.ConnectAsync(cumulus.SmtpOptions.Server, cumulus.SmtpOptions.Port, (MailKit.Security.SecureSocketOptions)cumulus.SmtpOptions.SslOption); @@ -112,7 +112,7 @@ public string SendTestEmail(string[] to, string from, string subject, string mes m.Body = bodyBuilder.ToMessageBody(); - using (SmtpClient client = new SmtpClient(cumulus.SmtpOptions.Logging ? new ProtocolLogger("MXdiags/smtp.log") : null)) + using (SmtpClient client = cumulus.SmtpOptions.Logging ? new SmtpClient(new ProtocolLogger("MXdiags/smtp.log")) : new SmtpClient()) { client.Connect(cumulus.SmtpOptions.Server, cumulus.SmtpOptions.Port, (MailKit.Security.SecureSocketOptions)cumulus.SmtpOptions.SslOption); //client.Connect(cumulus.SmtpOptions.Server, cumulus.SmtpOptions.Port, MailKit.Security.SecureSocketOptions.StartTlsWhenAvailable); diff --git a/CumulusMX/Properties/AssemblyInfo.cs b/CumulusMX/Properties/AssemblyInfo.cs index fdaf5a22..47858ee9 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("Cumulus MX")] -[assembly: AssemblyDescription("Version 3.11.1 - Build 3130")] +[assembly: AssemblyDescription("Version 3.11.2 - Build 3131")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Cumulus MX")] @@ -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.11.1.3130")] -[assembly: AssemblyFileVersion("3.11.1.3130")] +[assembly: AssemblyVersion("3.11.2.3131")] +[assembly: AssemblyFileVersion("3.11.2.3131")] diff --git a/Updates.txt b/Updates.txt index ea9b3fbc..9a6439fc 100644 --- a/Updates.txt +++ b/Updates.txt @@ -1,3 +1,8 @@ +3.11.2 - b3131 +—————————————— +- Fix: Send email failing if email logging is not enabled + + 3.11.1 - b3130 —————————————— - Fix: Fix Test email logging "success" on failure From 5752f8f9f71cf00b0aae1dc5b852b344800909cc Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Mon, 10 May 2021 14:19:29 +0100 Subject: [PATCH 2/6] Finish "sticky" logging settings --- CumulusMX/Cumulus.cs | 9 ++++++--- Updates.txt | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/CumulusMX/Cumulus.cs b/CumulusMX/Cumulus.cs index 65222c42..2032857a 100644 --- a/CumulusMX/Cumulus.cs +++ b/CumulusMX/Cumulus.cs @@ -3505,17 +3505,16 @@ private void ReadIniFile() ProgramOptions.StartupDelaySecs = ini.GetValue("Program", "StartupDelaySecs", 0); ProgramOptions.StartupDelayMaxUptime = ini.GetValue("Program", "StartupDelayMaxUptime", 300); ProgramOptions.WarnMultiple = ini.GetValue("Station", "WarnMultiple", true); + SmtpOptions.Logging = ini.GetValue("SMTP", "Logging", false); if (DebuggingEnabled) { ProgramOptions.DebugLogging = true; ProgramOptions.DataLogging = true; - SmtpOptions.Logging = true; } else { ProgramOptions.DebugLogging = ini.GetValue("Station", "Logging", false); ProgramOptions.DataLogging = ini.GetValue("Station", "DataLogging", false); - SmtpOptions.Logging = ini.GetValue("SMTP", "Logging", false); } ComportName = ini.GetValue("Station", "ComportName", DefaultComportName); @@ -4559,8 +4558,8 @@ internal void WriteIniFile() ini.SetValue("Program", "StartupDelaySecs", ProgramOptions.StartupDelaySecs); ini.SetValue("Program", "StartupDelayMaxUptime", ProgramOptions.StartupDelayMaxUptime); - ini.SetValue("Station", "WarnMultiple", ProgramOptions.WarnMultiple); + ini.SetValue("Station", "WarnMultiple", ProgramOptions.WarnMultiple); ini.SetValue("Station", "Type", StationType); ini.SetValue("Station", "Model", StationModel); @@ -4578,6 +4577,9 @@ internal void WriteIniFile() ini.SetValue("Station", "AvgSpeedMinutes", StationOptions.AvgSpeedMinutes); ini.SetValue("Station", "PeakGustMinutes", StationOptions.PeakGustMinutes); + ini.SetValue("Station", "Logging", ProgramOptions.DebugLogging); + ini.SetValue("Station", "DataLogging", ProgramOptions.DataLogging); + ini.SetValue("Station", "DavisReadReceptionStats", DavisOptions.ReadReceptionStats); ini.SetValue("Station", "DavisSetLoggerInterval", DavisOptions.SetLoggerInterval); ini.SetValue("Station", "UseDavisLoop2", DavisOptions.UseLoop2); @@ -5246,6 +5248,7 @@ internal void WriteIniFile() ini.SetValue("SMTP", "RequiresAuthentication", SmtpOptions.RequiresAuthentication); ini.SetValue("SMTP", "User", SmtpOptions.User); ini.SetValue("SMTP", "Password", SmtpOptions.Password); + ini.SetValue("SMTP", "Logging", SmtpOptions.Logging); // Growing Degree Days ini.SetValue("GrowingDD", "BaseTemperature1", GrowingBase1); diff --git a/Updates.txt b/Updates.txt index 9a6439fc..3c06c8f7 100644 --- a/Updates.txt +++ b/Updates.txt @@ -1,6 +1,20 @@ 3.11.2 - b3131 —————————————— - Fix: Send email failing if email logging is not enabled +- Fix: Solar Rad colour now displays as selected on first load in the Selectachart on the dashboard and default website +- Fix: Handlebars JS script updated due to severe security risk on previous version + +- Change: Setting of ALL logging options via the GUI is now "sticky" - i.e. preserved across runs of Cumulus MX +- Change: Many of the main settings screens now alert to to the first invalid settings in a message, and the tree containing + the error is highlighted in red: + - Program Settings + - Station Settings + - Internet Settings + - Third Party Settings + - Extra Sensor Settings + - Calibration Settings + - NOAA Settings + - MySQL Settings 3.11.1 - b3130 From 2bc05d96ed409e162433314204ebdb4d1ae199dc Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Mon, 10 May 2021 16:18:56 +0100 Subject: [PATCH 3/6] Fix GW1000 variants in autodiscovery --- CumulusMX/GW1000Station.cs | 4 ++-- Updates.txt | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CumulusMX/GW1000Station.cs b/CumulusMX/GW1000Station.cs index d39c3d49..77ba31d6 100644 --- a/CumulusMX/GW1000Station.cs +++ b/CumulusMX/GW1000Station.cs @@ -472,7 +472,7 @@ private Discovery DiscoverGW1000() client.EnableBroadcast = true; client.Send(sendBytes, sendBytes.Length, sendEp); - string[] namesToCheck = { "GW1000A", "WH2650A", "EasyWeather", "AMBWeather", "WS1900A" }; + string[] namesToCheck = { "GW1000", "WH2650", "EasyWeather", "AMBWeather", "WS1900", "WN1900" }; do { @@ -491,7 +491,7 @@ private Discovery DiscoverGW1000() Array.Copy(receivedBytes, 5, macArr, 0, 6); var macHex = BitConverter.ToString(macArr).Replace('-', ':'); - if (namesToCheck.Contains(name.Split('-')[0]) && ipAddr.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries).Length == 4) + if (namesToCheck.Any((name.Split('-')[0]).StartsWith) && ipAddr.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries).Length == 4) { IPAddress ipAddr2; if (IPAddress.TryParse(ipAddr, out ipAddr2)) diff --git a/Updates.txt b/Updates.txt index 3c06c8f7..8f83bcc2 100644 --- a/Updates.txt +++ b/Updates.txt @@ -3,6 +3,7 @@ - Fix: Send email failing if email logging is not enabled - Fix: Solar Rad colour now displays as selected on first load in the Selectachart on the dashboard and default website - Fix: Handlebars JS script updated due to severe security risk on previous version +- Fix: GW1000 for 433, 868, 915 Mhz variants - Change: Setting of ALL logging options via the GUI is now "sticky" - i.e. preserved across runs of Cumulus MX - Change: Many of the main settings screens now alert to to the first invalid settings in a message, and the tree containing From b5c55e8faddb751ca9a822b771503cf47a9dd1f5 Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Mon, 10 May 2021 16:57:21 +0100 Subject: [PATCH 4/6] Changing base units via station setings resets various thresholds etc --- CumulusMX/Cumulus.cs | 53 ++++++++++++++++++++++++++++++++++++ CumulusMX/StationSettings.cs | 25 +++++++++++++---- Updates.txt | 3 +- 3 files changed, 75 insertions(+), 6 deletions(-) diff --git a/CumulusMX/Cumulus.cs b/CumulusMX/Cumulus.cs index 2032857a..a423681f 100644 --- a/CumulusMX/Cumulus.cs +++ b/CumulusMX/Cumulus.cs @@ -1716,6 +1716,59 @@ internal void SetupUnitText() } } + // If the temperature units are changed, reset NOAA thresholds to defaults + internal void ChangeTempUnits() + { + SetupUnitText(); + + NOAAheatingthreshold = Units.Temp == 0 ? 18.3 : 65; + NOAAcoolingthreshold = Units.Temp == 0 ? 18.3 : 65; + NOAAmaxtempcomp1 = Units.Temp == 0 ? 27 : 80; + NOAAmaxtempcomp2 = Units.Temp == 0 ? 0 : 32; + NOAAmintempcomp1 = Units.Temp == 0 ? 0 : 32; + NOAAmintempcomp2 = Units.Temp == 0 ? -18 : 0; + + ChillHourThreshold = Units.Temp == 0 ? 7 : 45; + + GrowingBase1 = Units.Temp == 0 ? 5.0 : 40.0; + GrowingBase2 = Units.Temp == 0 ? 10.0 : 50.0; + + TempChangeAlarm.Units = Units.TempTrendText; + HighTempAlarm.Units = Units.TempText; + LowTempAlarm.Units = Units.TempText; + } + + internal void ChangeRainUnits() + { + SetupUnitText(); + + NOAAraincomp1 = Units.Rain == 0 ? 0.2 : 0.01; + NOAAraincomp2 = Units.Rain == 0 ? 2 : 0.1; + NOAAraincomp3 = Units.Rain == 0 ? 20 : 1; + + HighRainRateAlarm.Units = Units.RainTrendText; + HighRainTodayAlarm.Units = Units.RainText; + } + + internal void ChangePressureUnits() + { + SetupUnitText(); + + FCPressureThreshold = Units.Press == 2 ? 0.00295333727 : 0.1; + + PressChangeAlarm.Units = Units.PressTrendText; + HighPressAlarm.Units = Units.PressText; + LowPressAlarm.Units = Units.PressText; + } + + internal void ChangeWindUnits() + { + SetupUnitText(); + + HighWindAlarm.Units = Units.WindText; + HighGustAlarm.Units = Units.WindText; + } + public void SetFtpLogging(bool isSet) { try diff --git a/CumulusMX/StationSettings.cs b/CumulusMX/StationSettings.cs index 560422ef..ee87c249 100644 --- a/CumulusMX/StationSettings.cs +++ b/CumulusMX/StationSettings.cs @@ -974,11 +974,26 @@ internal string UpdateConfig(IHttpContext context) // Units try { - cumulus.Units.Wind = settings.general.units.wind; - cumulus.Units.Press = settings.general.units.pressure; - cumulus.Units.Temp = settings.general.units.temp; - cumulus.Units.Rain = settings.general.units.rain; - cumulus.SetupUnitText(); + if (cumulus.Units.Wind != settings.general.units.wind) + { + cumulus.Units.Wind = settings.general.units.wind; + cumulus.ChangeWindUnits(); + } + if (cumulus.Units.Press != settings.general.units.pressure) + { + cumulus.Units.Press = settings.general.units.pressure; + cumulus.ChangePressureUnits(); + } + if (cumulus.Units.Temp != settings.general.units.temp) + { + cumulus.Units.Temp = settings.general.units.temp; + cumulus.ChangeTempUnits(); + } + if (cumulus.Units.Rain != settings.general.units.rain) + { + cumulus.Units.Rain = settings.general.units.rain; + cumulus.ChangeRainUnits(); + } cumulus.AirQualityDPlaces = settings.general.units.advanced.airqulaitydp; cumulus.PressDPlaces = settings.general.units.advanced.pressdp; diff --git a/Updates.txt b/Updates.txt index 8f83bcc2..9b974561 100644 --- a/Updates.txt +++ b/Updates.txt @@ -16,7 +16,8 @@ - Calibration Settings - NOAA Settings - MySQL Settings - +- Change: If any of the base units (wind, temp, rain, pressure) are changed via Station Settings (for instance during the initial configuration), then all the + thresholds and base values associated with that unit are reset back to the defaults for the new unit 3.11.1 - b3130 —————————————— From d604f7536412d848d0782789a55f641bc0e39311 Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Tue, 11 May 2021 00:15:11 +0100 Subject: [PATCH 5/6] Add HTTP and MySQL alarms --- CumulusMX/AlarmSettings.cs | 50 ++++++++++++++- CumulusMX/Cumulus.cs | 119 ++++++++++++++++++++++++++++++++++++ CumulusMX/DataStruct.cs | 10 +++ CumulusMX/WeatherStation.cs | 12 +++- CumulusMX/webtags.cs | 25 ++++++++ Updates.txt | 7 ++- 6 files changed, 219 insertions(+), 4 deletions(-) diff --git a/CumulusMX/AlarmSettings.cs b/CumulusMX/AlarmSettings.cs index a629c2a4..5d4d35ea 100644 --- a/CumulusMX/AlarmSettings.cs +++ b/CumulusMX/AlarmSettings.cs @@ -157,7 +157,23 @@ public string GetAlarmSettings() upgradeNotify = cumulus.UpgradeAlarm.Notify, upgradeEmail = cumulus.UpgradeAlarm.Email, upgradeLatches = cumulus.UpgradeAlarm.Latch, - upgradeLatchHrs = cumulus.UpgradeAlarm.LatchHours + upgradeLatchHrs = cumulus.UpgradeAlarm.LatchHours, + + httpStoppedEnabled = cumulus.HttpUploadAlarm.Enabled, + httpStoppedSoundEnabled = cumulus.HttpUploadAlarm.Sound, + httpStoppedSound = cumulus.HttpUploadAlarm.SoundFile, + httpStoppedNotify = cumulus.HttpUploadAlarm.Notify, + httpStoppedEmail = cumulus.HttpUploadAlarm.Email, + httpStoppedLatches = cumulus.HttpUploadAlarm.Latch, + httpStoppedLatchHrs = cumulus.HttpUploadAlarm.LatchHours, + + mySqlStoppedEnabled = cumulus.MySqlUploadAlarm.Enabled, + mySqlStoppedSoundEnabled = cumulus.MySqlUploadAlarm.Sound, + mySqlStoppedSound = cumulus.MySqlUploadAlarm.SoundFile, + mySqlStoppedNotify = cumulus.MySqlUploadAlarm.Notify, + mySqlStoppedEmail = cumulus.MySqlUploadAlarm.Email, + mySqlStoppedLatches = cumulus.MySqlUploadAlarm.Latch, + mySqlStoppedLatchHrs = cumulus.MySqlUploadAlarm.LatchHours }; var email = new JsonAlarmEmail() @@ -342,6 +358,22 @@ public string UpdateAlarmSettings(IHttpContext context) cumulus.UpgradeAlarm.Latch = settings.upgradeLatches; cumulus.UpgradeAlarm.LatchHours = settings.upgradeLatchHrs; + cumulus.HttpUploadAlarm.Enabled = settings.httpStoppedEnabled; + cumulus.HttpUploadAlarm.Sound = settings.httpStoppedSoundEnabled; + cumulus.HttpUploadAlarm.SoundFile = settings.httpStoppedSound; + cumulus.HttpUploadAlarm.Notify = settings.httpStoppedNotify; + cumulus.HttpUploadAlarm.Email = settings.httpStoppedEmail; + cumulus.HttpUploadAlarm.Latch = settings.httpStoppedLatches; + cumulus.HttpUploadAlarm.LatchHours = settings.httpStoppedLatchHrs; + + cumulus.MySqlUploadAlarm.Enabled = settings.mySqlStoppedEnabled; + cumulus.MySqlUploadAlarm.Sound = settings.mySqlStoppedSoundEnabled; + cumulus.MySqlUploadAlarm.SoundFile = settings.mySqlStoppedSound; + cumulus.MySqlUploadAlarm.Notify = settings.mySqlStoppedNotify; + cumulus.MySqlUploadAlarm.Email = settings.mySqlStoppedEmail; + cumulus.MySqlUploadAlarm.Latch = settings.mySqlStoppedLatches; + cumulus.MySqlUploadAlarm.LatchHours = settings.mySqlStoppedLatchHrs; + // validate the from email if (!EmailSender.CheckEmailAddress(result.email.fromEmail.Trim())) { @@ -575,6 +607,22 @@ public class JsonAlarmSettingsData public bool upgradeEmail { get; set; } public bool upgradeLatches { get; set; } public int upgradeLatchHrs { get; set; } + + public bool httpStoppedEnabled { get; set; } + public bool httpStoppedSoundEnabled { get; set; } + public string httpStoppedSound { get; set; } + public bool httpStoppedNotify { get; set; } + public bool httpStoppedEmail { get; set; } + public bool httpStoppedLatches { get; set; } + public int httpStoppedLatchHrs { get; set; } + + public bool mySqlStoppedEnabled { get; set; } + public bool mySqlStoppedSoundEnabled { get; set; } + public string mySqlStoppedSound { get; set; } + public bool mySqlStoppedNotify { get; set; } + public bool mySqlStoppedEmail { get; set; } + public bool mySqlStoppedLatches { get; set; } + public int mySqlStoppedLatchHrs { get; set; } } public class JsonAlarmEmail diff --git a/CumulusMX/Cumulus.cs b/CumulusMX/Cumulus.cs index a423681f..1c7a4122 100644 --- a/CumulusMX/Cumulus.cs +++ b/CumulusMX/Cumulus.cs @@ -619,6 +619,8 @@ public struct MqttSettings public Alarm HighTempAlarm = new Alarm(); public Alarm LowTempAlarm = new Alarm(); public Alarm UpgradeAlarm = new Alarm(); + public Alarm HttpUploadAlarm = new Alarm(); + public Alarm MySqlUploadAlarm = new Alarm(); private const double DEFAULTFCLOWPRESS = 950.0; @@ -1313,6 +1315,8 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) LowTempAlarm.cumulus = this; LowTempAlarm.Units = Units.TempText; UpgradeAlarm.cumulus = this; + HttpUploadAlarm.cumulus = this; + MySqlUploadAlarm.cumulus = this; GetLatestVersion(); @@ -2028,10 +2032,13 @@ internal async void UpdateTwitter() { LogDebugMessage($"Status returned: ({tweet.StatusID}) {tweet.User.Name}, {tweet.Text}, {tweet.CreatedAt}"); } + + HttpUploadAlarm.Triggered = false; } catch (Exception ex) { LogMessage($"UpdateTwitter: {ex.Message}"); + HttpUploadAlarm.Triggered = true; } //if (tweet != null) // Console.WriteLine("Status returned: " + "(" + tweet.StatusID + ")" + tweet.User.Name + ", " + tweet.Text + "\n"); @@ -2100,11 +2107,17 @@ internal async void UpdateWunderground(DateTime timestamp) if (!Wund.RapidFireEnabled || response.StatusCode != HttpStatusCode.OK) { LogDebugMessage("Wunderground: Response = " + response.StatusCode + ": " + responseBodyAsText); + HttpUploadAlarm.Triggered = true; + } + else + { + HttpUploadAlarm.Triggered = false; } } catch (Exception ex) { LogMessage("Wunderground: ERROR - " + ex.Message); + HttpUploadAlarm.Triggered = true; } finally { @@ -2133,10 +2146,19 @@ internal async void UpdateWindy(DateTime timestamp) HttpResponseMessage response = await WindyhttpClient.GetAsync(url); var responseBodyAsText = await response.Content.ReadAsStringAsync(); LogDebugMessage("Windy: Response = " + response.StatusCode + ": " + responseBodyAsText); + if (response.StatusCode != HttpStatusCode.OK) + { + HttpUploadAlarm.Triggered = true; + } + else + { + HttpUploadAlarm.Triggered = false; + } } catch (Exception ex) { LogMessage("Windy: ERROR - " + ex.Message); + HttpUploadAlarm.Triggered = true; } finally { @@ -2165,10 +2187,19 @@ internal async void UpdateWindGuru(DateTime timestamp) HttpResponseMessage response = await WindGuruhttpClient.GetAsync(url); var responseBodyAsText = await response.Content.ReadAsStringAsync(); LogDebugMessage("WindGuru: " + response.StatusCode + ": " + responseBodyAsText); + if (response.StatusCode != HttpStatusCode.OK) + { + HttpUploadAlarm.Triggered = true; + } + else + { + HttpUploadAlarm.Triggered = false; + } } catch (Exception ex) { LogDebugMessage("WindGuru: ERROR - " + ex.Message); + HttpUploadAlarm.Triggered = true; } finally { @@ -2203,12 +2234,23 @@ internal async void UpdateAwekas(DateTime timestamp) var responseBodyAsText = await response.Content.ReadAsStringAsync(); LogDebugMessage("AWEKAS Response code = " + response.StatusCode); LogDataMessage("AWEKAS: Response text = " + responseBodyAsText); + + if (response.StatusCode != HttpStatusCode.OK) + { + HttpUploadAlarm.Triggered = true; + } + else + { + HttpUploadAlarm.Triggered = false; + } //var respJson = JsonConvert.DeserializeObject(responseBodyAsText); var respJson = JsonSerializer.DeserializeFromString(responseBodyAsText); // Check the status response if (respJson.status == 2) + { LogDebugMessage("AWEKAS: Data stored OK"); + } else if (respJson.status == 1) { LogMessage("AWEKAS: Data PARIALLY stored"); @@ -2256,6 +2298,7 @@ internal async void UpdateAwekas(DateTime timestamp) else { LogMessage("AWEKAS: Unknown error"); + HttpUploadAlarm.Triggered = true; } } @@ -2289,6 +2332,7 @@ internal async void UpdateAwekas(DateTime timestamp) catch (Exception ex) { LogMessage("AWEKAS: Exception = " + ex.Message); + HttpUploadAlarm.Triggered = true; } finally { @@ -2324,21 +2368,27 @@ internal async void UpdateWCloud(DateTime timestamp) { case 200: msg = "Success"; + HttpUploadAlarm.Triggered = false; break; case 400: msg = "Bad reuest"; + HttpUploadAlarm.Triggered = true; break; case 401: msg = "Incorrect WID or Key"; + HttpUploadAlarm.Triggered = true; break; case 429: msg = "Too many requests"; + HttpUploadAlarm.Triggered = true; break; case 500: msg = "Server error"; + HttpUploadAlarm.Triggered = true; break; default: msg = "Unknown error"; + HttpUploadAlarm.Triggered = true; break; } LogDebugMessage($"WeatherCloud: Response = {msg} ({response.StatusCode}): {responseBodyAsText}"); @@ -2346,6 +2396,7 @@ internal async void UpdateWCloud(DateTime timestamp) catch (Exception ex) { LogDebugMessage("WeatherCloud: ERROR - " + ex.Message); + HttpUploadAlarm.Triggered = true; } finally { @@ -2381,12 +2432,20 @@ internal async void UpdateOpenWeatherMap(DateTime timestamp) var status = response.StatusCode == HttpStatusCode.NoContent ? "OK" : "Error"; // Returns a 204 reponse for OK! LogDebugMessage($"OpenWeatherMap: Response code = {status} - {response.StatusCode}"); if (response.StatusCode != HttpStatusCode.NoContent) + { LogDataMessage($"OpenWeatherMap: Response data = {responseBodyAsText}"); + HttpUploadAlarm.Triggered = true; + } + else + { + HttpUploadAlarm.Triggered = false; + } } } catch (Exception ex) { LogMessage("OpenWeatherMap: ERROR - " + ex.Message); + HttpUploadAlarm.Triggered = true; } finally { @@ -4361,6 +4420,23 @@ private void ReadIniFile() UpgradeAlarm.Latch = ini.GetValue("Alarms", "UpgradeAlarmLatch", false); UpgradeAlarm.LatchHours = ini.GetValue("Alarms", "UpgradeAlarmLatchHours", 24); + HttpUploadAlarm.Enabled = ini.GetValue("Alarms", "HttpUploadStoppedAlarmSet", false); + HttpUploadAlarm.Sound = ini.GetValue("Alarms", "HttpUploadStoppedAlarmSound", false); + HttpUploadAlarm.SoundFile = ini.GetValue("Alarms", "HttpUploadStoppedAlarmSoundFile", DefaultSoundFile); + HttpUploadAlarm.Notify = ini.GetValue("Alarms", "HttpUploadStoppedAlarmNotify", false); + HttpUploadAlarm.Email = ini.GetValue("Alarms", "HttpUploadStoppedAlarmEmail", false); + HttpUploadAlarm.Latch = ini.GetValue("Alarms", "HttpUploadStoppedAlarmLatch", false); + HttpUploadAlarm.LatchHours = ini.GetValue("Alarms", "HttpUploadStoppedAlarmLatchHours", 24); + + MySqlUploadAlarm.Enabled = ini.GetValue("Alarms", "HttpUploadStoppedAlarmSet", false); + MySqlUploadAlarm.Sound = ini.GetValue("Alarms", "HttpUploadStoppedAlarmSound", false); + MySqlUploadAlarm.SoundFile = ini.GetValue("Alarms", "HttpUploadStoppedAlarmSoundFile", DefaultSoundFile); + MySqlUploadAlarm.Notify = ini.GetValue("Alarms", "HttpUploadStoppedAlarmNotify", false); + MySqlUploadAlarm.Email = ini.GetValue("Alarms", "HttpUploadStoppedAlarmEmail", false); + MySqlUploadAlarm.Latch = ini.GetValue("Alarms", "HttpUploadStoppedAlarmLatch", false); + MySqlUploadAlarm.LatchHours = ini.GetValue("Alarms", "HttpUploadStoppedAlarmLatchHours", 24); + + AlarmFromEmail = ini.GetValue("Alarms", "FromEmail", ""); AlarmDestEmail = ini.GetValue("Alarms", "DestEmail", "").Split(';'); AlarmEmailHtml = ini.GetValue("Alarms", "UseHTML", false); @@ -5125,6 +5201,22 @@ internal void WriteIniFile() ini.SetValue("Alarms", "UpgradeAlarmLatch", UpgradeAlarm.Latch); ini.SetValue("Alarms", "UpgradeAlarmLatchHours", UpgradeAlarm.LatchHours); + ini.SetValue("Alarms", "HttpUploadStoppedAlarmSet", HttpUploadAlarm.Enabled); + ini.SetValue("Alarms", "HttpUploadStoppedAlarmSound", HttpUploadAlarm.Sound); + ini.SetValue("Alarms", "HttpUploadStoppedAlarmSoundFile", HttpUploadAlarm.SoundFile); + ini.SetValue("Alarms", "HttpUploadStoppedAlarmNotify", HttpUploadAlarm.Notify); + ini.SetValue("Alarms", "HttpUploadStoppedAlarmEmail", HttpUploadAlarm.Email); + ini.SetValue("Alarms", "HttpUploadStoppedAlarmLatch", HttpUploadAlarm.Latch); + ini.SetValue("Alarms", "HttpUploadStoppedAlarmLatchHours", HttpUploadAlarm.LatchHours); + + ini.SetValue("Alarms", "HttpUploadStoppedAlarmSet", MySqlUploadAlarm.Enabled); + ini.SetValue("Alarms", "HttpUploadStoppedAlarmSound", MySqlUploadAlarm.Sound); + ini.SetValue("Alarms", "HttpUploadStoppedAlarmSoundFile", MySqlUploadAlarm.SoundFile); + ini.SetValue("Alarms", "HttpUploadStoppedAlarmNotify", MySqlUploadAlarm.Notify); + ini.SetValue("Alarms", "HttpUploadStoppedAlarmEmail", MySqlUploadAlarm.Email); + ini.SetValue("Alarms", "HttpUploadStoppedAlarmLatch", MySqlUploadAlarm.Latch); + ini.SetValue("Alarms", "HttpUploadStoppedAlarmLatchHours", MySqlUploadAlarm.LatchHours); + ini.SetValue("Alarms", "FromEmail", AlarmFromEmail); ini.SetValue("Alarms", "DestEmail", AlarmDestEmail.Join(";")); ini.SetValue("Alarms", "UseHTML", AlarmEmailHtml); @@ -5651,6 +5743,8 @@ private void ReadStringsFile() BatteryLowAlarm.EmailMsg = ini.GetValue("AlarmEmails", "batteryLow", "A low battery condition has been detected."); SpikeAlarm.EmailMsg = ini.GetValue("AlarmEmails", "dataSpike", "A data spike from your weather station has been suppressed."); UpgradeAlarm.EmailMsg = ini.GetValue("AlarmEmails", "upgrade", "An upgrade to Cumulus MX is now available."); + HttpUploadAlarm.EmailMsg = ini.GetValue("AlarmEmails", "httpStopped", "HTTP uploads are failing."); + MySqlUploadAlarm.EmailMsg = ini.GetValue("AlarmEmails", "mySqlStopped", "MySQL uploads are failing."); } @@ -8901,10 +8995,19 @@ public async void UpdatePWSweather(DateTime timestamp) HttpResponseMessage response = await PWShttpClient.GetAsync(URL); var responseBodyAsText = await response.Content.ReadAsStringAsync(); LogDebugMessage("PWS Response: " + response.StatusCode + ": " + responseBodyAsText); + if (response.StatusCode != HttpStatusCode.OK) + { + HttpUploadAlarm.Triggered = true; + } + else + { + HttpUploadAlarm.Triggered = false; + } } catch (Exception ex) { LogDebugMessage("PWS update: " + ex.Message); + HttpUploadAlarm.Triggered = true; } finally { @@ -8932,10 +9035,19 @@ public async void UpdateWOW(DateTime timestamp) HttpResponseMessage response = await WOWhttpClient.GetAsync(URL); var responseBodyAsText = await response.Content.ReadAsStringAsync(); LogDebugMessage("WOW Response: " + response.StatusCode + ": " + responseBodyAsText); + if (response.StatusCode != HttpStatusCode.OK) + { + HttpUploadAlarm.Triggered = true; + } + else + { + HttpUploadAlarm.Triggered = false; + } } catch (Exception ex) { LogDebugMessage("WOW update: " + ex.Message); + HttpUploadAlarm.Triggered = true; } finally { @@ -8961,12 +9073,15 @@ public async Task MySqlCommandAsync(string Cmd, MySqlConnection Connection, stri int aff = await cmd.ExecuteNonQueryAsync(); LogDebugMessage($"{CallingFunction}: MySQL {aff} rows were affected."); } + + MySqlUploadAlarm.Triggered = false; } catch (Exception ex) { LogMessage($"{CallingFunction}: Error encountered during MySQL operation."); LogMessage($"{CallingFunction}: SQL was - \"{Cmd}\""); LogMessage(ex.Message); + MySqlUploadAlarm.Triggered = true; } finally { @@ -9005,12 +9120,15 @@ public Task MySqlCommandSync(List Cmds, MySqlConnection Connection, stri int aff = cmd.ExecuteNonQuery(); LogDebugMessage($"{CallingFunction}: MySQL {aff} rows were affected."); } + + MySqlUploadAlarm.Triggered = false; } catch (Exception ex) { LogMessage($"{CallingFunction}: Error encountered during MySQL operation."); LogMessage($"{CallingFunction}: SQL was - \"{Cmds[i]}\""); LogMessage(ex.Message); + MySqlUploadAlarm.Triggered = true; } } @@ -9028,6 +9146,7 @@ public Task MySqlCommandSync(List Cmds, MySqlConnection Connection, stri { LogMessage($"{CallingFunction}: Error opening MySQL Connection"); LogMessage(e.Message); + MySqlUploadAlarm.Triggered = true; } if (ClearCommands) diff --git a/CumulusMX/DataStruct.cs b/CumulusMX/DataStruct.cs index 67c0c7c0..af1d6996 100644 --- a/CumulusMX/DataStruct.cs +++ b/CumulusMX/DataStruct.cs @@ -23,6 +23,7 @@ public DataStruct(Cumulus cumulus, double outdoorTemp, int outdoorHum, double av bool dataStopped, double stormRain, string stormRainStart, int cloudbase, string cloudbaseUnit, double last24hourRain, bool alarmLowTemp, bool alarmHighTemp, bool alarmTempUp, bool alarmTempDown, bool alarmRain, bool alarmRainRate, bool alarmLowPress, bool alarmHighPress, bool alarmPressUp, bool alarmPressDown, bool alarmGust, bool alarmWind, bool alarmSensor, bool alarmBattery, bool alarmSpike, bool alarmUpgrade, + bool alarmHttp, bool alarmMySql, double feelsLike, double highFeelsLikeToday, string highFeelsLikeTodayTime, double lowFeelsLikeToday, string lowFeelsLikeTodayTime, double highHumidexToday, string highHumidexTodayTime) { @@ -141,6 +142,8 @@ public DataStruct(Cumulus cumulus, double outdoorTemp, int outdoorHum, double av AlarmBattery = alarmBattery; AlarmSpike = alarmSpike; AlarmUpgrade = alarmUpgrade; + AlarmHttp = alarmHttp; + AlarmMySql = alarmMySql; } [IgnoreDataMember] @@ -819,5 +822,12 @@ public string Build [DataMember] public bool AlarmUpgrade { get; set; } + + [DataMember] + public bool AlarmHttp { get; set; } + + [DataMember] + public bool AlarmMySql { get; set; } + } } diff --git a/CumulusMX/WeatherStation.cs b/CumulusMX/WeatherStation.cs index 108c4595..2c8b3a9f 100644 --- a/CumulusMX/WeatherStation.cs +++ b/CumulusMX/WeatherStation.cs @@ -1488,7 +1488,9 @@ public void SecondTimer(object sender, ElapsedEventArgs e) LastDataReadTimestamp.ToString("HH:mm:ss"), DataStopped, StormRain, stormRainStart, CloudBase, cumulus.CloudBaseInFeet ? "ft" : "m", RainLast24Hour, cumulus.LowTempAlarm.Triggered, cumulus.HighTempAlarm.Triggered, cumulus.TempChangeAlarm.UpTriggered, cumulus.TempChangeAlarm.DownTriggered, cumulus.HighRainTodayAlarm.Triggered, cumulus.HighRainRateAlarm.Triggered, cumulus.LowPressAlarm.Triggered, cumulus.HighPressAlarm.Triggered, cumulus.PressChangeAlarm.UpTriggered, cumulus.PressChangeAlarm.DownTriggered, cumulus.HighGustAlarm.Triggered, cumulus.HighWindAlarm.Triggered, - cumulus.SensorAlarm.Triggered, cumulus.BatteryLowAlarm.Triggered, cumulus.SpikeAlarm.Triggered, cumulus.UpgradeAlarm.Triggered, FeelsLike, HiLoToday.HighFeelsLike, HiLoToday.HighFeelsLikeTime.ToString("HH:mm"), HiLoToday.LowFeelsLike, HiLoToday.LowFeelsLikeTime.ToString("HH:mm"), + cumulus.SensorAlarm.Triggered, cumulus.BatteryLowAlarm.Triggered, cumulus.SpikeAlarm.Triggered, cumulus.UpgradeAlarm.Triggered, + cumulus.HttpUploadAlarm.Triggered, cumulus.MySqlUploadAlarm.Triggered, + FeelsLike, HiLoToday.HighFeelsLike, HiLoToday.HighFeelsLikeTime.ToString("HH:mm"), HiLoToday.LowFeelsLike, HiLoToday.LowFeelsLikeTime.ToString("HH:mm"), HiLoToday.HighHumidex, HiLoToday.HighHumidexTime.ToString("HH:mm")); //var json = jss.Serialize(data); @@ -1606,6 +1608,12 @@ private void ClearAlarms() if (cumulus.UpgradeAlarm.Latch && cumulus.UpgradeAlarm.Triggered && DateTime.Now > cumulus.UpgradeAlarm.TriggeredTime.AddHours(cumulus.UpgradeAlarm.LatchHours)) cumulus.UpgradeAlarm.Triggered = false; + if (cumulus.HttpUploadAlarm.Latch && cumulus.HttpUploadAlarm.Triggered && DateTime.Now > cumulus.HttpUploadAlarm.TriggeredTime.AddHours(cumulus.HttpUploadAlarm.LatchHours)) + cumulus.HttpUploadAlarm.Triggered = false; + + if (cumulus.MySqlUploadAlarm.Latch && cumulus.MySqlUploadAlarm.Triggered && DateTime.Now > cumulus.MySqlUploadAlarm.TriggeredTime.AddHours(cumulus.MySqlUploadAlarm.LatchHours)) + cumulus.MySqlUploadAlarm.Triggered = false; + if (cumulus.HighWindAlarm.Latch && cumulus.HighWindAlarm.Triggered && DateTime.Now > cumulus.HighWindAlarm.TriggeredTime.AddHours(cumulus.HighWindAlarm.LatchHours)) cumulus.HighWindAlarm.Triggered = false; @@ -11737,7 +11745,7 @@ internal string GetCurrentData() cumulus.BeaufortDesc(WindAverage), LastDataReadTimestamp.ToString("HH:mm:ss"), DataStopped, StormRain, stormRainStart, CloudBase, cumulus.CloudBaseInFeet ? "ft" : "m", RainLast24Hour, cumulus.LowTempAlarm.Triggered, cumulus.HighTempAlarm.Triggered, cumulus.TempChangeAlarm.UpTriggered, cumulus.TempChangeAlarm.DownTriggered, cumulus.HighRainTodayAlarm.Triggered, cumulus.HighRainRateAlarm.Triggered, cumulus.LowPressAlarm.Triggered, cumulus.HighPressAlarm.Triggered, cumulus.PressChangeAlarm.UpTriggered, cumulus.PressChangeAlarm.DownTriggered, cumulus.HighGustAlarm.Triggered, cumulus.HighWindAlarm.Triggered, - cumulus.SensorAlarm.Triggered, cumulus.BatteryLowAlarm.Triggered, cumulus.SpikeAlarm.Triggered, cumulus.UpgradeAlarm.Triggered, + cumulus.SensorAlarm.Triggered, cumulus.BatteryLowAlarm.Triggered, cumulus.SpikeAlarm.Triggered, cumulus.UpgradeAlarm.Triggered, cumulus.HttpUploadAlarm.Triggered, cumulus.MySqlUploadAlarm.Triggered, FeelsLike, HiLoToday.HighFeelsLike, HiLoToday.HighFeelsLikeTime.ToString("HH:mm:ss"), HiLoToday.LowFeelsLike, HiLoToday.LowFeelsLikeTime.ToString("HH:mm:ss"), HiLoToday.HighHumidex, HiLoToday.HighHumidexTime.ToString("HH:mm:ss")); diff --git a/CumulusMX/webtags.cs b/CumulusMX/webtags.cs index a8ec4a0a..1cb4fd93 100644 --- a/CumulusMX/webtags.cs +++ b/CumulusMX/webtags.cs @@ -3944,6 +3944,27 @@ private string TagUpgradeAlarm(Dictionary tagParams) return "0"; } + private string TagMySqlUploadAlarm(Dictionary tagParams) + { + if (cumulus.MySqlUploadAlarm.Enabled) + { + return cumulus.MySqlUploadAlarm.Triggered ? "1" : "0"; + } + + return "0"; + } + + private string TagHttpUploadAlarm(Dictionary tagParams) + { + if (cumulus.HttpUploadAlarm.Enabled) + { + return cumulus.HttpUploadAlarm.Triggered ? "1" : "0"; + } + + return "0"; + } + + // Monthly highs and lows - values private string TagMonthTempH(Dictionary tagParams) { @@ -5586,6 +5607,7 @@ public void InitialiseWebtags() { "LeafWetness6", TagLeafWetness6 }, { "LeafWetness7", TagLeafWetness7 }, { "LeafWetness8", TagLeafWetness8 }, + { "LowTempAlarm", TagLowTempAlarm }, { "HighTempAlarm", TagHighTempAlarm }, { "TempChangeUpAlarm", TagTempChangeUpAlarm }, @@ -5600,7 +5622,10 @@ public void InitialiseWebtags() { "HighWindSpeedAlarm", TagHighWindSpeedAlarm }, { "BatteryLowAlarm", TagBatteryLowAlarm }, { "DataSpikeAlarm", TagDataSpikeAlarm }, + { "MySqlUploadAlarm", TagMySqlUploadAlarm }, + { "HttpUploadAlarm", TagHttpUploadAlarm }, { "UpgradeAlarm", TagUpgradeAlarm }, + { "RG11RainToday", TagRg11RainToday }, { "RG11RainYest", TagRg11RainYest }, diff --git a/Updates.txt b/Updates.txt index 9b974561..7c30a5ca 100644 --- a/Updates.txt +++ b/Updates.txt @@ -17,7 +17,12 @@ - NOAA Settings - MySQL Settings - Change: If any of the base units (wind, temp, rain, pressure) are changed via Station Settings (for instance during the initial configuration), then all the - thresholds and base values associated with that unit are reset back to the defaults for the new unit + thresholds and base values associated with that unit are reset to the defaults for the new unit +- Change: The main dashboard page now only shows alarm indicators for enabled alarms + +- New: Implements two new alarms for HTTP uploads failing, and MySQL uploads failing +- New: Web tags - <#HttpUploadAlarm>, <#MySqlUploadAlarm> + 3.11.1 - b3130 —————————————— From 52b14c834f97e3c18c87df59c2af84c688989507 Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Sun, 16 May 2021 21:48:49 +0100 Subject: [PATCH 6/6] Final Alarm updates --- .gitattributes | 126 +- CumulusMX/Alarm.cs | 173 + CumulusMX/AlarmSettings.cs | 1224 +- CumulusMX/Cumulus.cs | 20029 +++++++++++++------------- CumulusMX/CumulusMX.csproj | 567 +- CumulusMX/DavisWllStation.cs | 6373 ++++----- CumulusMX/EmailSender.cs | 377 +- CumulusMX/FOStation.cs | 2442 ++-- CumulusMX/GW1000Station.cs | 3691 ++--- CumulusMX/MysqlSettings.cs | 717 +- CumulusMX/NOAAReports.cs | 289 +- CumulusMX/WeatherStation.cs | 24775 +++++++++++++++++---------------- Updates.txt | 3372 ++--- 13 files changed, 32114 insertions(+), 32041 deletions(-) create mode 100644 CumulusMX/Alarm.cs diff --git a/.gitattributes b/.gitattributes index 1ff0c423..d0bfa7a9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,63 +1,63 @@ -############################################################################### -# Set default behavior to automatically normalize line endings. -############################################################################### -* text=auto - -############################################################################### -# Set default behavior for command prompt diff. -# -# This is need for earlier builds of msysgit that does not have it on by -# default for csharp files. -# Note: This is only used by command line -############################################################################### -#*.cs diff=csharp - -############################################################################### -# Set the merge driver for project and solution files -# -# Merging from the command prompt will add diff markers to the files if there -# are conflicts (Merging from VS is not affected by the settings below, in VS -# the diff markers are never inserted). Diff markers may cause the following -# file extensions to fail to load in VS. An alternative would be to treat -# these files as binary and thus will always conflict and require user -# intervention with every merge. To do so, just uncomment the entries below -############################################################################### -#*.sln merge=binary -#*.csproj merge=binary -#*.vbproj merge=binary -#*.vcxproj merge=binary -#*.vcproj merge=binary -#*.dbproj merge=binary -#*.fsproj merge=binary -#*.lsproj merge=binary -#*.wixproj merge=binary -#*.modelproj merge=binary -#*.sqlproj merge=binary -#*.wwaproj merge=binary - -############################################################################### -# behavior for image files -# -# image files are treated as binary by default. -############################################################################### -#*.jpg binary -#*.png binary -#*.gif binary - -############################################################################### -# diff behavior for common document formats -# -# Convert binary document formats to text before diffing them. This feature -# is only available from the command line. Turn it on by uncommenting the -# entries below. -############################################################################### -#*.doc diff=astextplain -#*.DOC diff=astextplain -#*.docx diff=astextplain -#*.DOCX diff=astextplain -#*.dot diff=astextplain -#*.DOT diff=astextplain -#*.pdf diff=astextplain -#*.PDF diff=astextplain -#*.rtf diff=astextplain -#*.RTF diff=astextplain +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +#* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/CumulusMX/Alarm.cs b/CumulusMX/Alarm.cs new file mode 100644 index 00000000..1a5e3f39 --- /dev/null +++ b/CumulusMX/Alarm.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CumulusMX +{ + public class Alarm + { + public Cumulus cumulus { get; set; } + + public bool Enabled { get; set; } + public double Value { get; set; } + public bool Sound { get; set; } + public string SoundFile { get; set; } + + bool triggered; + public bool Triggered + { + get => triggered; + set + { + if (value) + { + triggerCount++; + TriggeredTime = DateTime.Now; + + // do we have a threshold value + if (triggerCount >= TriggerThreshold) + { + // If we were not set before, so we need to send an email? + if (!triggered && Enabled && Email && cumulus.SmtpOptions.Enabled) + { + // Construct the message - preamble, plus values + var msg = cumulus.AlarmEmailPreamble + "\r\n" + string.Format(EmailMsg, Value, Units); + if (!string.IsNullOrEmpty(LastError)) + { + msg += "\r\nLast error: " + LastError; + } + cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); + } + + // If we get a new trigger, record the time + triggered = true; + } + } + else + { + // If the trigger is cleared, check if we should be latching the value + if (Latch) + { + if (DateTime.Now > TriggeredTime.AddHours(LatchHours)) + { + // We are latching, but the latch period has expired, clear the trigger + triggered = false; + triggerCount = 0; + } + } + else + { + // No latch, just clear the trigger + triggered = false; + triggerCount = 0; + } + } + } + } + public DateTime TriggeredTime { get; set; } + public bool Notify { get; set; } + public bool Email { get; set; } + public bool Latch { get; set; } + public int LatchHours { get; set; } + public string EmailMsg { get; set; } + public string Units { get; set; } + public string LastError { get; set; } + int triggerCount = 0; + public int TriggerThreshold { get; set; } + } + + public class AlarmChange : Alarm + { + //public bool changeUp { get; set; } + //public bool changeDown { get; set; } + + bool upTriggered; + public bool UpTriggered + { + get => upTriggered; + set + { + if (value) + { + // If we were not set before, so we need to send an email? + if (!upTriggered && Enabled && Email && cumulus.SmtpOptions.Enabled) + { + // Construct the message - preamble, plus values + var msg = Program.cumulus.AlarmEmailPreamble + "\r\n" + string.Format(EmailMsgUp, Value, Units); + cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); + } + + // If we get a new trigger, record the time + upTriggered = true; + UpTriggeredTime = DateTime.Now; + } + else + { + // If the trigger is cleared, check if we should be latching the value + if (Latch) + { + if (DateTime.Now > UpTriggeredTime.AddHours(LatchHours)) + { + // We are latching, but the latch period has expired, clear the trigger + upTriggered = false; + } + } + else + { + // No latch, just clear the trigger + upTriggered = false; + } + } + } + } + public DateTime UpTriggeredTime { get; set; } + + + bool downTriggered; + public bool DownTriggered + { + get => downTriggered; + set + { + if (value) + { + // If we were not set before, so we need to send an email? + if (!downTriggered && Enabled && Email && cumulus.SmtpOptions.Enabled) + { + // Construct the message - preamble, plus values + var msg = Program.cumulus.AlarmEmailPreamble + "\n" + string.Format(EmailMsgDn, Value, Units); + cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); + } + + // If we get a new trigger, record the time + downTriggered = true; + DownTriggeredTime = DateTime.Now; + } + else + { + // If the trigger is cleared, check if we should be latching the value + if (Latch) + { + if (DateTime.Now > DownTriggeredTime.AddHours(LatchHours)) + { + // We are latching, but the latch period has expired, clear the trigger + downTriggered = false; + } + } + else + { + // No latch, just clear the trigger + downTriggered = false; + } + } + } + } + + public DateTime DownTriggeredTime { get; set; } + + public string EmailMsgUp { get; set; } + public string EmailMsgDn { get; set; } + } +} diff --git a/CumulusMX/AlarmSettings.cs b/CumulusMX/AlarmSettings.cs index 5d4d35ea..060f4c87 100644 --- a/CumulusMX/AlarmSettings.cs +++ b/CumulusMX/AlarmSettings.cs @@ -1,649 +1,575 @@ -using System; -using System.IO; -using System.Net; -using ServiceStack; -using Unosquare.Labs.EmbedIO; - -namespace CumulusMX -{ - public class AlarmSettings - { - private readonly Cumulus cumulus; - - public AlarmSettings(Cumulus cumulus) - { - this.cumulus = cumulus; - } - - public string GetAlarmSettings() - { - //var InvC = new CultureInfo(""); - - var alarmUnits = new JsonAlarmUnits() - { - tempUnits = cumulus.Units.TempText, - pressUnits = cumulus.Units.PressText, - rainUnits = cumulus.Units.RainText, - windUnits = cumulus.Units.WindText - }; - - var data = new JsonAlarmSettingsData() - { - tempBelowEnabled = cumulus.LowTempAlarm.Enabled, - tempBelowVal = cumulus.LowTempAlarm.Value, - tempBelowSoundEnabled = cumulus.LowTempAlarm.Sound, - tempBelowSound = cumulus.LowTempAlarm.SoundFile, - tempBelowNotify = cumulus.LowTempAlarm.Notify, - tempBelowEmail = cumulus.LowTempAlarm.Email, - tempBelowLatches = cumulus.LowTempAlarm.Latch, - tempBelowLatchHrs = cumulus.LowTempAlarm.LatchHours, - - tempAboveEnabled = cumulus.HighTempAlarm.Enabled, - tempAboveVal = cumulus.HighTempAlarm.Value, - tempAboveSoundEnabled = cumulus.HighTempAlarm.Sound, - tempAboveSound = cumulus.HighTempAlarm.SoundFile, - tempAboveNotify = cumulus.HighTempAlarm.Notify, - tempAboveEmail = cumulus.HighTempAlarm.Email, - tempAboveLatches = cumulus.HighTempAlarm.Latch, - tempAboveLatchHrs = cumulus.HighTempAlarm.LatchHours, - - tempChangeEnabled = cumulus.TempChangeAlarm.Enabled, - tempChangeVal = cumulus.TempChangeAlarm.Value, - tempChangeSoundEnabled = cumulus.TempChangeAlarm.Sound, - tempChangeSound = cumulus.TempChangeAlarm.SoundFile, - tempChangeNotify = cumulus.TempChangeAlarm.Notify, - tempChangeEmail = cumulus.TempChangeAlarm.Email, - tempChangeLatches = cumulus.TempChangeAlarm.Latch, - tempChangeLatchHrs = cumulus.TempChangeAlarm.LatchHours, - - pressBelowEnabled = cumulus.LowPressAlarm.Enabled, - pressBelowVal = cumulus.LowPressAlarm.Value, - pressBelowSoundEnabled = cumulus.LowPressAlarm.Sound, - pressBelowSound = cumulus.LowPressAlarm.SoundFile, - pressBelowNotify = cumulus.LowPressAlarm.Notify, - pressBelowEmail = cumulus.LowPressAlarm.Email, - pressBelowLatches = cumulus.LowPressAlarm.Latch, - pressBelowLatchHrs = cumulus.LowPressAlarm.LatchHours, - - pressAboveEnabled = cumulus.HighPressAlarm.Enabled, - pressAboveVal = cumulus.HighPressAlarm.Value, - pressAboveSoundEnabled = cumulus.HighPressAlarm.Sound, - pressAboveSound = cumulus.HighPressAlarm.SoundFile, - pressAboveNotify = cumulus.HighPressAlarm.Notify, - pressAboveEmail = cumulus.HighPressAlarm.Email, - pressAboveLatches = cumulus.HighPressAlarm.Latch, - pressAboveLatchHrs = cumulus.HighPressAlarm.LatchHours, - - pressChangeEnabled = cumulus.PressChangeAlarm.Enabled, - pressChangeVal = cumulus.PressChangeAlarm.Value, - pressChangeSoundEnabled = cumulus.PressChangeAlarm.Sound, - pressChangeSound = cumulus.PressChangeAlarm.SoundFile, - pressChangeNotify = cumulus.PressChangeAlarm.Notify, - pressChangeEmail = cumulus.PressChangeAlarm.Email, - pressChangeLatches = cumulus.PressChangeAlarm.Latch, - pressChangeLatchHrs = cumulus.PressChangeAlarm.LatchHours, - - rainAboveEnabled = cumulus.HighRainTodayAlarm.Enabled, - rainAboveVal = cumulus.HighRainTodayAlarm.Value, - rainAboveSoundEnabled = cumulus.HighRainTodayAlarm.Sound, - rainAboveSound = cumulus.HighRainTodayAlarm.SoundFile, - rainAboveNotify = cumulus.HighRainTodayAlarm.Notify, - rainAboveEmail = cumulus.HighRainTodayAlarm.Email, - rainAboveLatches = cumulus.HighRainTodayAlarm.Latch, - rainAboveLatchHrs = cumulus.HighRainTodayAlarm.LatchHours, - - rainRateAboveEnabled = cumulus.HighRainRateAlarm.Enabled, - rainRateAboveVal = cumulus.HighRainRateAlarm.Value, - rainRateAboveSoundEnabled = cumulus.HighRainRateAlarm.Sound, - rainRateAboveSound = cumulus.HighRainRateAlarm.SoundFile, - rainRateAboveNotify = cumulus.HighRainRateAlarm.Notify, - rainRateAboveEmail = cumulus.HighRainRateAlarm.Email, - rainRateAboveLatches = cumulus.HighRainRateAlarm.Latch, - rainRateAboveLatchHrs = cumulus.HighRainRateAlarm.LatchHours, - - gustAboveEnabled = cumulus.HighGustAlarm.Enabled, - gustAboveVal = cumulus.HighGustAlarm.Value, - gustAboveSoundEnabled = cumulus.HighGustAlarm.Sound, - gustAboveSound = cumulus.HighGustAlarm.SoundFile, - gustAboveNotify = cumulus.HighGustAlarm.Notify, - gustAboveEmail = cumulus.HighGustAlarm.Email, - gustAboveLatches = cumulus.HighGustAlarm.Latch, - gustAboveLatchHrs = cumulus.HighGustAlarm.LatchHours, - - windAboveEnabled = cumulus.HighWindAlarm.Enabled, - windAboveVal = cumulus.HighWindAlarm.Value, - windAboveSoundEnabled = cumulus.HighWindAlarm.Sound, - windAboveSound = cumulus.HighWindAlarm.SoundFile, - windAboveNotify = cumulus.HighWindAlarm.Notify, - windAboveEmail = cumulus.HighWindAlarm.Email, - windAboveLatches = cumulus.HighWindAlarm.Latch, - windAboveLatchHrs = cumulus.HighWindAlarm.LatchHours, - - contactLostEnabled = cumulus.SensorAlarm.Enabled, - contactLostSoundEnabled = cumulus.SensorAlarm.Sound, - contactLostSound = cumulus.SensorAlarm.SoundFile, - contactLostNotify = cumulus.SensorAlarm.Notify, - contactLostEmail = cumulus.SensorAlarm.Email, - contactLostLatches = cumulus.SensorAlarm.Latch, - contactLostLatchHrs = cumulus.SensorAlarm.LatchHours, - - dataStoppedEnabled = cumulus.DataStoppedAlarm.Enabled, - dataStoppedSoundEnabled = cumulus.DataStoppedAlarm.Sound, - dataStoppedSound = cumulus.DataStoppedAlarm.SoundFile, - dataStoppedNotify = cumulus.DataStoppedAlarm.Notify, - dataStoppedEmail = cumulus.DataStoppedAlarm.Email, - dataStoppedLatches = cumulus.DataStoppedAlarm.Latch, - dataStoppedLatchHrs = cumulus.DataStoppedAlarm.LatchHours, - - batteryLowEnabled = cumulus.BatteryLowAlarm.Enabled, - batteryLowSoundEnabled = cumulus.BatteryLowAlarm.Sound, - batteryLowSound = cumulus.BatteryLowAlarm.SoundFile, - batteryLowNotify = cumulus.BatteryLowAlarm.Notify, - batteryLowEmail = cumulus.BatteryLowAlarm.Email, - batteryLowLatches = cumulus.BatteryLowAlarm.Latch, - batteryLowLatchHrs = cumulus.BatteryLowAlarm.LatchHours, - - spikeEnabled = cumulus.SpikeAlarm.Enabled, - spikeSoundEnabled = cumulus.SpikeAlarm.Sound, - spikeSound = cumulus.SpikeAlarm.SoundFile, - spikeNotify = cumulus.SpikeAlarm.Notify, - spikeEmail = cumulus.SpikeAlarm.Email, - spikeLatches = cumulus.SpikeAlarm.Latch, - spikeLatchHrs = cumulus.SpikeAlarm.LatchHours, - - upgradeEnabled = cumulus.UpgradeAlarm.Enabled, - upgradeSoundEnabled = cumulus.UpgradeAlarm.Sound, - upgradeSound = cumulus.UpgradeAlarm.SoundFile, - upgradeNotify = cumulus.UpgradeAlarm.Notify, - upgradeEmail = cumulus.UpgradeAlarm.Email, - upgradeLatches = cumulus.UpgradeAlarm.Latch, - upgradeLatchHrs = cumulus.UpgradeAlarm.LatchHours, - - httpStoppedEnabled = cumulus.HttpUploadAlarm.Enabled, - httpStoppedSoundEnabled = cumulus.HttpUploadAlarm.Sound, - httpStoppedSound = cumulus.HttpUploadAlarm.SoundFile, - httpStoppedNotify = cumulus.HttpUploadAlarm.Notify, - httpStoppedEmail = cumulus.HttpUploadAlarm.Email, - httpStoppedLatches = cumulus.HttpUploadAlarm.Latch, - httpStoppedLatchHrs = cumulus.HttpUploadAlarm.LatchHours, - - mySqlStoppedEnabled = cumulus.MySqlUploadAlarm.Enabled, - mySqlStoppedSoundEnabled = cumulus.MySqlUploadAlarm.Sound, - mySqlStoppedSound = cumulus.MySqlUploadAlarm.SoundFile, - mySqlStoppedNotify = cumulus.MySqlUploadAlarm.Notify, - mySqlStoppedEmail = cumulus.MySqlUploadAlarm.Email, - mySqlStoppedLatches = cumulus.MySqlUploadAlarm.Latch, - mySqlStoppedLatchHrs = cumulus.MySqlUploadAlarm.LatchHours - }; - - var email = new JsonAlarmEmail() - { - fromEmail = cumulus.AlarmFromEmail, - destEmail = cumulus.AlarmDestEmail.Join(";"), - useHtml = cumulus.AlarmEmailHtml - }; - - var retObject = new JsonAlarmSettings() - { - data = data, - units = alarmUnits, - email = email - }; - - return retObject.ToJson(); - } - - public string UpdateAlarmSettings(IHttpContext context) - { - var json = ""; - JsonAlarmSettings result; - JsonAlarmSettingsData settings; - - try - { - var data = new StreamReader(context.Request.InputStream).ReadToEnd(); - - // Start at char 5 to skip the "json:" prefix - json = WebUtility.UrlDecode(data); - - // de-serialize it to the settings structure - //var settings = JsonConvert.DeserializeObject(json); - //var settings = JsonSerializer.DeserializeFromString(json); - - result = json.FromJson(); - settings = result.data; - } - catch (Exception ex) - { - var msg = "Error deserializing Alarm Settings JSON: " + ex.Message; - cumulus.LogMessage(msg); - cumulus.LogDebugMessage("Alarm Data: " + json); - context.Response.StatusCode = 500; - return msg; - } - - try - { - // process the settings - cumulus.LogMessage("Updating Alarm settings"); - - cumulus.LowTempAlarm.Enabled = settings.tempBelowEnabled; - cumulus.LowTempAlarm.Value = settings.tempBelowVal; - cumulus.LowTempAlarm.Sound = settings.tempBelowSoundEnabled; - cumulus.LowTempAlarm.SoundFile = settings.tempBelowSound; - cumulus.LowTempAlarm.Notify = settings.tempBelowNotify; - cumulus.LowTempAlarm.Email = settings.tempBelowEmail; - cumulus.LowTempAlarm.Latch = settings.tempBelowLatches; - cumulus.LowTempAlarm.LatchHours = settings.tempBelowLatchHrs; - - - cumulus.HighTempAlarm.Enabled = settings.tempAboveEnabled; - cumulus.HighTempAlarm.Value = settings.tempAboveVal; - cumulus.HighTempAlarm.Sound = settings.tempAboveSoundEnabled; - cumulus.HighTempAlarm.SoundFile = settings.tempAboveSound; - cumulus.HighTempAlarm.Notify = settings.tempAboveNotify; - cumulus.HighTempAlarm.Email = settings.tempAboveEmail; - cumulus.HighTempAlarm.Latch = settings.tempAboveLatches; - cumulus.HighTempAlarm.LatchHours = settings.tempAboveLatchHrs; - - cumulus.TempChangeAlarm.Enabled = settings.tempChangeEnabled; - cumulus.TempChangeAlarm.Value = settings.tempChangeVal; - cumulus.TempChangeAlarm.Sound = settings.tempChangeSoundEnabled; - cumulus.TempChangeAlarm.SoundFile = settings.tempChangeSound; - cumulus.TempChangeAlarm.Notify = settings.tempChangeNotify; - cumulus.TempChangeAlarm.Email = settings.tempChangeEmail; - cumulus.TempChangeAlarm.Latch = settings.tempChangeLatches; - cumulus.TempChangeAlarm.LatchHours = settings.tempChangeLatchHrs; - - cumulus.LowPressAlarm.Enabled = settings.pressBelowEnabled; - cumulus.LowPressAlarm.Value = settings.pressBelowVal; - cumulus.LowPressAlarm.Sound = settings.pressBelowSoundEnabled; - cumulus.LowPressAlarm.SoundFile = settings.pressBelowSound; - cumulus.LowPressAlarm.Notify = settings.pressBelowNotify; - cumulus.LowPressAlarm.Email = settings.pressBelowEmail; - cumulus.LowPressAlarm.Latch = settings.pressBelowLatches; - cumulus.LowPressAlarm.LatchHours = settings.pressBelowLatchHrs; - - cumulus.HighPressAlarm.Enabled = settings.pressAboveEnabled; - cumulus.HighPressAlarm.Value = settings.pressAboveVal; - cumulus.HighPressAlarm.Sound = settings.pressAboveSoundEnabled; - cumulus.HighPressAlarm.SoundFile = settings.pressAboveSound; - cumulus.HighPressAlarm.Notify = settings.pressAboveNotify; - cumulus.HighPressAlarm.Email = settings.pressAboveEmail; - cumulus.HighPressAlarm.Latch = settings.pressAboveLatches; - cumulus.HighPressAlarm.LatchHours = settings.pressAboveLatchHrs; - - cumulus.PressChangeAlarm.Enabled = settings.pressChangeEnabled; - cumulus.PressChangeAlarm.Value = settings.pressChangeVal; - cumulus.PressChangeAlarm.Sound = settings.pressChangeSoundEnabled; - cumulus.PressChangeAlarm.SoundFile = settings.pressChangeSound; - cumulus.PressChangeAlarm.Notify = settings.pressChangeNotify; - cumulus.PressChangeAlarm.Email = settings.pressChangeEmail; - cumulus.PressChangeAlarm.Latch = settings.pressChangeLatches; - cumulus.PressChangeAlarm.LatchHours = settings.pressChangeLatchHrs; - - cumulus.HighRainTodayAlarm.Enabled = settings.rainAboveEnabled; - cumulus.HighRainTodayAlarm.Value = settings.rainAboveVal; - cumulus.HighRainTodayAlarm.Sound = settings.rainAboveSoundEnabled; - cumulus.HighRainTodayAlarm.SoundFile = settings.rainAboveSound; - cumulus.HighRainTodayAlarm.Notify = settings.rainAboveNotify; - cumulus.HighRainTodayAlarm.Email = settings.rainAboveEmail; - cumulus.HighRainTodayAlarm.Latch = settings.rainAboveLatches; - cumulus.HighRainTodayAlarm.LatchHours = settings.rainAboveLatchHrs; - - cumulus.HighRainRateAlarm.Enabled = settings.rainRateAboveEnabled; - cumulus.HighRainRateAlarm.Value = settings.rainRateAboveVal; - cumulus.HighRainRateAlarm.Sound = settings.rainRateAboveSoundEnabled; - cumulus.HighRainRateAlarm.SoundFile = settings.rainRateAboveSound; - cumulus.HighRainRateAlarm.Notify = settings.rainRateAboveNotify; - cumulus.HighRainRateAlarm.Email = settings.rainRateAboveEmail; - cumulus.HighRainRateAlarm.Latch = settings.rainRateAboveLatches; - cumulus.HighRainRateAlarm.LatchHours = settings.rainRateAboveLatchHrs; - - cumulus.HighGustAlarm.Enabled = settings.gustAboveEnabled; - cumulus.HighGustAlarm.Value = settings.gustAboveVal; - cumulus.HighGustAlarm.Sound = settings.gustAboveSoundEnabled; - cumulus.HighGustAlarm.SoundFile = settings.gustAboveSound; - cumulus.HighGustAlarm.Notify = settings.gustAboveNotify; - cumulus.HighGustAlarm.Email = settings.gustAboveEmail; - cumulus.HighGustAlarm.Latch = settings.gustAboveLatches; - cumulus.HighGustAlarm.LatchHours = settings.gustAboveLatchHrs; - - cumulus.HighWindAlarm.Enabled = settings.windAboveEnabled; - cumulus.HighWindAlarm.Value = settings.windAboveVal; - cumulus.HighWindAlarm.Sound = settings.windAboveSoundEnabled; - cumulus.HighWindAlarm.SoundFile = settings.windAboveSound; - cumulus.HighWindAlarm.Notify = settings.windAboveNotify; - cumulus.HighWindAlarm.Email = settings.windAboveEmail; - cumulus.HighWindAlarm.Latch = settings.windAboveLatches; - cumulus.HighWindAlarm.LatchHours = settings.windAboveLatchHrs; - - cumulus.SensorAlarm.Enabled = settings.contactLostEnabled; - cumulus.SensorAlarm.Sound = settings.contactLostSoundEnabled; - cumulus.SensorAlarm.SoundFile = settings.contactLostSound; - cumulus.SensorAlarm.Notify = settings.contactLostNotify; - cumulus.SensorAlarm.Email = settings.contactLostEmail; - cumulus.SensorAlarm.Latch = settings.contactLostLatches; - cumulus.SensorAlarm.LatchHours = settings.contactLostLatchHrs; - - cumulus.DataStoppedAlarm.Enabled = settings.dataStoppedEnabled; - cumulus.DataStoppedAlarm.Sound = settings.dataStoppedSoundEnabled; - cumulus.DataStoppedAlarm.SoundFile = settings.dataStoppedSound; - cumulus.DataStoppedAlarm.Notify = settings.dataStoppedNotify; - cumulus.DataStoppedAlarm.Email = settings.dataStoppedEmail; - cumulus.DataStoppedAlarm.Latch = settings.dataStoppedLatches; - cumulus.DataStoppedAlarm.LatchHours = settings.dataStoppedLatchHrs; - - cumulus.BatteryLowAlarm.Enabled = settings.batteryLowEnabled; - cumulus.BatteryLowAlarm.Sound = settings.batteryLowSoundEnabled; - cumulus.BatteryLowAlarm.SoundFile = settings.batteryLowSound; - cumulus.BatteryLowAlarm.Notify = settings.batteryLowNotify; - cumulus.BatteryLowAlarm.Email = settings.batteryLowEmail; - cumulus.BatteryLowAlarm.Latch = settings.batteryLowLatches; - cumulus.BatteryLowAlarm.LatchHours = settings.batteryLowLatchHrs; - - cumulus.SpikeAlarm.Enabled = settings.spikeEnabled; - cumulus.SpikeAlarm.Sound = settings.spikeSoundEnabled; - cumulus.SpikeAlarm.SoundFile = settings.spikeSound; - cumulus.SpikeAlarm.Notify = settings.spikeNotify; - cumulus.SpikeAlarm.Email = settings.spikeEmail; - cumulus.SpikeAlarm.Latch = settings.spikeLatches; - cumulus.SpikeAlarm.LatchHours = settings.spikeLatchHrs; - - cumulus.UpgradeAlarm.Enabled = settings.upgradeEnabled; - cumulus.UpgradeAlarm.Sound = settings.upgradeSoundEnabled; - cumulus.UpgradeAlarm.SoundFile = settings.upgradeSound; - cumulus.UpgradeAlarm.Notify = settings.upgradeNotify; - cumulus.UpgradeAlarm.Email = settings.upgradeEmail; - cumulus.UpgradeAlarm.Latch = settings.upgradeLatches; - cumulus.UpgradeAlarm.LatchHours = settings.upgradeLatchHrs; - - cumulus.HttpUploadAlarm.Enabled = settings.httpStoppedEnabled; - cumulus.HttpUploadAlarm.Sound = settings.httpStoppedSoundEnabled; - cumulus.HttpUploadAlarm.SoundFile = settings.httpStoppedSound; - cumulus.HttpUploadAlarm.Notify = settings.httpStoppedNotify; - cumulus.HttpUploadAlarm.Email = settings.httpStoppedEmail; - cumulus.HttpUploadAlarm.Latch = settings.httpStoppedLatches; - cumulus.HttpUploadAlarm.LatchHours = settings.httpStoppedLatchHrs; - - cumulus.MySqlUploadAlarm.Enabled = settings.mySqlStoppedEnabled; - cumulus.MySqlUploadAlarm.Sound = settings.mySqlStoppedSoundEnabled; - cumulus.MySqlUploadAlarm.SoundFile = settings.mySqlStoppedSound; - cumulus.MySqlUploadAlarm.Notify = settings.mySqlStoppedNotify; - cumulus.MySqlUploadAlarm.Email = settings.mySqlStoppedEmail; - cumulus.MySqlUploadAlarm.Latch = settings.mySqlStoppedLatches; - cumulus.MySqlUploadAlarm.LatchHours = settings.mySqlStoppedLatchHrs; - - // validate the from email - if (!EmailSender.CheckEmailAddress(result.email.fromEmail.Trim())) - { - var msg = "ERROR: Invalid Alarm from email address entered"; - cumulus.LogMessage(msg); - context.Response.StatusCode = 500; - return msg; - } - cumulus.AlarmFromEmail = result.email.fromEmail.Trim(); - - // validate the destination email(s) - var emails = result.email.destEmail.Trim().Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); - for (var i = 0; i < emails.Length; i++) - { - emails[i] = emails[i].Trim(); - if (!EmailSender.CheckEmailAddress(emails[i])) - { - var msg = "ERROR: Invalid Alarm destination email address entered"; - cumulus.LogMessage(msg); - context.Response.StatusCode = 500; - return msg; - } - } - cumulus.AlarmDestEmail = emails; - cumulus.AlarmEmailHtml = result.email.useHtml; - - // Save the settings - cumulus.WriteIniFile(); - - context.Response.StatusCode = 200; - } - catch (Exception ex) - { - cumulus.LogMessage("Error processing Alarm settings: " + ex.Message); - cumulus.LogDebugMessage("Alarm Data: " + json); - context.Response.StatusCode = 500; - return ex.Message; - } - return "success"; - } - - public string TestEmail(IHttpContext context) - { - try - { - - var data = new StreamReader(context.Request.InputStream).ReadToEnd(); - - // Start at char 5 to skip the "json:" prefix - var json = WebUtility.UrlDecode(data); - - var result = json.FromJson(); - // process the settings - cumulus.LogMessage("Sending test email..."); - - // validate the from email - if (!EmailSender.CheckEmailAddress(result.fromEmail.Trim())) - { - var msg = "ERROR: Invalid Alarm from email address entered"; - cumulus.LogMessage(msg); - context.Response.StatusCode = 500; - return msg; - } - var from = result.fromEmail.Trim(); - - // validate the destination email(s) - var dest = result.destEmail.Trim().Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); - for (var i = 0; i < dest.Length; i++) - { - dest[i] = dest[i].Trim(); - if (!EmailSender.CheckEmailAddress(dest[i])) - { - var msg = "ERROR: Invalid Alarm destination email address entered"; - cumulus.LogMessage(msg); - context.Response.StatusCode = 500; - return msg; - } - } - - var ret = cumulus.emailer.SendTestEmail(dest, from, "Cumulus MX Test Email", "A test email from Cumulus MX.", result.useHtml); - - if (ret == "OK") - { - cumulus.LogMessage("Test email sent without error"); - } - else - { - context.Response.StatusCode = 500; - return ret; - } - } - catch (Exception ex) - { - cumulus.LogMessage(ex.Message); - context.Response.StatusCode = 500; - return ex.Message; - } - - return "success"; - } - } - - public class JsonAlarmSettingsData - { - public bool tempBelowEnabled { get; set; } - public double tempBelowVal { get; set; } - public bool tempBelowSoundEnabled { get; set; } - public string tempBelowSound { get; set; } - public bool tempBelowNotify { get; set; } - public bool tempBelowEmail { get; set; } - public bool tempBelowLatches { get; set; } - public int tempBelowLatchHrs { get; set; } - - public bool tempAboveEnabled { get; set; } - public double tempAboveVal { get; set; } - public bool tempAboveSoundEnabled { get; set; } - public string tempAboveSound { get; set; } - public bool tempAboveNotify { get; set; } - public bool tempAboveEmail { get; set; } - public bool tempAboveLatches { get; set; } - public int tempAboveLatchHrs { get; set; } - - public bool tempChangeEnabled { get; set; } - public double tempChangeVal { get; set; } - public bool tempChangeSoundEnabled { get; set; } - public string tempChangeSound { get; set; } - public bool tempChangeNotify { get; set; } - public bool tempChangeEmail { get; set; } - public bool tempChangeLatches { get; set; } - public int tempChangeLatchHrs { get; set; } - - public bool pressBelowEnabled { get; set; } - public double pressBelowVal { get; set; } - public bool pressBelowSoundEnabled { get; set; } - public string pressBelowSound { get; set; } - public bool pressBelowNotify { get; set; } - public bool pressBelowEmail { get; set; } - public bool pressBelowLatches { get; set; } - public int pressBelowLatchHrs { get; set; } - - public bool pressAboveEnabled { get; set; } - public double pressAboveVal { get; set; } - public bool pressAboveSoundEnabled { get; set; } - public string pressAboveSound { get; set; } - public bool pressAboveNotify { get; set; } - public bool pressAboveEmail { get; set; } - public bool pressAboveLatches { get; set; } - public int pressAboveLatchHrs { get; set; } - - public bool pressChangeEnabled { get; set; } - public double pressChangeVal { get; set; } - public bool pressChangeSoundEnabled { get; set; } - public string pressChangeSound { get; set; } - public bool pressChangeNotify { get; set; } - public bool pressChangeEmail { get; set; } - public bool pressChangeLatches { get; set; } - public int pressChangeLatchHrs { get; set; } - - public bool rainAboveEnabled { get; set; } - public double rainAboveVal { get; set; } - public bool rainAboveSoundEnabled { get; set; } - public string rainAboveSound { get; set; } - public bool rainAboveNotify { get; set; } - public bool rainAboveEmail { get; set; } - public bool rainAboveLatches { get; set; } - public int rainAboveLatchHrs { get; set; } - - public bool rainRateAboveEnabled { get; set; } - public double rainRateAboveVal { get; set; } - public bool rainRateAboveSoundEnabled { get; set; } - public string rainRateAboveSound { get; set; } - public bool rainRateAboveNotify { get; set; } - public bool rainRateAboveEmail { get; set; } - public bool rainRateAboveLatches { get; set; } - public int rainRateAboveLatchHrs { get; set; } - - public bool gustAboveEnabled { get; set; } - public double gustAboveVal { get; set; } - public bool gustAboveSoundEnabled { get; set; } - public string gustAboveSound { get; set; } - public bool gustAboveNotify { get; set; } - public bool gustAboveEmail { get; set; } - public bool gustAboveLatches { get; set; } - public int gustAboveLatchHrs { get; set; } - - public bool windAboveEnabled { get; set; } - public double windAboveVal { get; set; } - public bool windAboveSoundEnabled { get; set; } - public string windAboveSound { get; set; } - public bool windAboveNotify { get; set; } - public bool windAboveEmail { get; set; } - public bool windAboveLatches { get; set; } - public int windAboveLatchHrs { get; set; } - - public bool contactLostEnabled { get; set; } - public bool contactLostSoundEnabled { get; set; } - public string contactLostSound { get; set; } - public bool contactLostNotify { get; set; } - public bool contactLostEmail { get; set; } - public bool contactLostLatches { get; set; } - public int contactLostLatchHrs { get; set; } - - public bool dataStoppedEnabled { get; set; } - public bool dataStoppedSoundEnabled { get; set; } - public string dataStoppedSound { get; set; } - public bool dataStoppedNotify { get; set; } - public bool dataStoppedEmail { get; set; } - public bool dataStoppedLatches { get; set; } - public int dataStoppedLatchHrs { get; set; } - - public bool batteryLowEnabled { get; set; } - public bool batteryLowSoundEnabled { get; set; } - public string batteryLowSound { get; set; } - public bool batteryLowNotify { get; set; } - public bool batteryLowEmail { get; set; } - public bool batteryLowLatches { get; set; } - public int batteryLowLatchHrs { get; set; } - - public bool spikeEnabled { get; set; } - public bool spikeSoundEnabled { get; set; } - public string spikeSound { get; set; } - public bool spikeNotify { get; set; } - public bool spikeEmail { get; set; } - public bool spikeLatches { get; set; } - public int spikeLatchHrs { get; set; } - - public bool upgradeEnabled { get; set; } - public bool upgradeSoundEnabled { get; set; } - public string upgradeSound { get; set; } - public bool upgradeNotify { get; set; } - public bool upgradeEmail { get; set; } - public bool upgradeLatches { get; set; } - public int upgradeLatchHrs { get; set; } - - public bool httpStoppedEnabled { get; set; } - public bool httpStoppedSoundEnabled { get; set; } - public string httpStoppedSound { get; set; } - public bool httpStoppedNotify { get; set; } - public bool httpStoppedEmail { get; set; } - public bool httpStoppedLatches { get; set; } - public int httpStoppedLatchHrs { get; set; } - - public bool mySqlStoppedEnabled { get; set; } - public bool mySqlStoppedSoundEnabled { get; set; } - public string mySqlStoppedSound { get; set; } - public bool mySqlStoppedNotify { get; set; } - public bool mySqlStoppedEmail { get; set; } - public bool mySqlStoppedLatches { get; set; } - public int mySqlStoppedLatchHrs { get; set; } - } - - public class JsonAlarmEmail - { - public string fromEmail { get; set; } - public string destEmail { get; set; } - public bool useHtml { get; set; } - } - - public class JsonAlarmUnits - { - public string tempUnits { get; set; } - public string pressUnits { get; set; } - public string rainUnits { get; set; } - public string windUnits { get; set; } - } - - public class JsonAlarmSettings - { - public JsonAlarmSettingsData data { get; set; } - public JsonAlarmUnits units { get; set; } - public JsonAlarmEmail email { get; set; } - } -} +using System; +using System.IO; +using System.Net; +using ServiceStack; +using Unosquare.Labs.EmbedIO; + +namespace CumulusMX +{ + public class AlarmSettings + { + private readonly Cumulus cumulus; + + public AlarmSettings(Cumulus cumulus) + { + this.cumulus = cumulus; + } + + public string GetAlarmSettings() + { + //var InvC = new CultureInfo(""); + + var alarmUnits = new JsonAlarmUnits() + { + tempUnits = cumulus.Units.TempText, + pressUnits = cumulus.Units.PressText, + rainUnits = cumulus.Units.RainText, + windUnits = cumulus.Units.WindText + }; + + var data = new JsonAlarmSettingsData() + { + tempBelow = new JsonAlarmValues() + { + Enabled = cumulus.LowTempAlarm.Enabled, + Val = cumulus.LowTempAlarm.Value, + SoundEnabled = cumulus.LowTempAlarm.Sound, + Sound = cumulus.LowTempAlarm.SoundFile, + Notify = cumulus.LowTempAlarm.Notify, + Email = cumulus.LowTempAlarm.Email, + Latches = cumulus.LowTempAlarm.Latch, + LatchHrs = cumulus.LowTempAlarm.LatchHours + }, + tempAbove = new JsonAlarmValues() + { + Enabled = cumulus.HighTempAlarm.Enabled, + Val = cumulus.HighTempAlarm.Value, + SoundEnabled = cumulus.HighTempAlarm.Sound, + Sound = cumulus.HighTempAlarm.SoundFile, + Notify = cumulus.HighTempAlarm.Notify, + Email = cumulus.HighTempAlarm.Email, + Latches = cumulus.HighTempAlarm.Latch, + LatchHrs = cumulus.HighTempAlarm.LatchHours + }, + tempChange = new JsonAlarmValues() + { + Enabled = cumulus.TempChangeAlarm.Enabled, + Val = cumulus.TempChangeAlarm.Value, + SoundEnabled = cumulus.TempChangeAlarm.Sound, + Sound = cumulus.TempChangeAlarm.SoundFile, + Notify = cumulus.TempChangeAlarm.Notify, + Email = cumulus.TempChangeAlarm.Email, + Latches = cumulus.TempChangeAlarm.Latch, + LatchHrs = cumulus.TempChangeAlarm.LatchHours + }, + pressBelow = new JsonAlarmValues() + { + Enabled = cumulus.LowPressAlarm.Enabled, + Val = cumulus.LowPressAlarm.Value, + SoundEnabled = cumulus.LowPressAlarm.Sound, + Sound = cumulus.LowPressAlarm.SoundFile, + Notify = cumulus.LowPressAlarm.Notify, + Email = cumulus.LowPressAlarm.Email, + Latches = cumulus.LowPressAlarm.Latch, + LatchHrs = cumulus.LowPressAlarm.LatchHours + }, + pressAbove = new JsonAlarmValues() + { + Enabled = cumulus.HighPressAlarm.Enabled, + Val = cumulus.HighPressAlarm.Value, + SoundEnabled = cumulus.HighPressAlarm.Sound, + Sound = cumulus.HighPressAlarm.SoundFile, + Notify = cumulus.HighPressAlarm.Notify, + Email = cumulus.HighPressAlarm.Email, + Latches = cumulus.HighPressAlarm.Latch, + LatchHrs = cumulus.HighPressAlarm.LatchHours + }, + pressChange = new JsonAlarmValues() + { + Enabled = cumulus.PressChangeAlarm.Enabled, + Val = cumulus.PressChangeAlarm.Value, + SoundEnabled = cumulus.PressChangeAlarm.Sound, + Sound = cumulus.PressChangeAlarm.SoundFile, + Notify = cumulus.PressChangeAlarm.Notify, + Email = cumulus.PressChangeAlarm.Email, + Latches = cumulus.PressChangeAlarm.Latch, + LatchHrs = cumulus.PressChangeAlarm.LatchHours + }, + rainAbove = new JsonAlarmValues() + { + Enabled = cumulus.HighRainTodayAlarm.Enabled, + Val = cumulus.HighRainTodayAlarm.Value, + SoundEnabled = cumulus.HighRainTodayAlarm.Sound, + Sound = cumulus.HighRainTodayAlarm.SoundFile, + Notify = cumulus.HighRainTodayAlarm.Notify, + Email = cumulus.HighRainTodayAlarm.Email, + Latches = cumulus.HighRainTodayAlarm.Latch, + LatchHrs = cumulus.HighRainTodayAlarm.LatchHours + }, + rainRateAbove = new JsonAlarmValues() + { + Enabled = cumulus.HighRainRateAlarm.Enabled, + Val = cumulus.HighRainRateAlarm.Value, + SoundEnabled = cumulus.HighRainRateAlarm.Sound, + Sound = cumulus.HighRainRateAlarm.SoundFile, + Notify = cumulus.HighRainRateAlarm.Notify, + Email = cumulus.HighRainRateAlarm.Email, + Latches = cumulus.HighRainRateAlarm.Latch, + LatchHrs = cumulus.HighRainRateAlarm.LatchHours + }, + gustAbove = new JsonAlarmValues() + { + Enabled = cumulus.HighGustAlarm.Enabled, + Val = cumulus.HighGustAlarm.Value, + SoundEnabled = cumulus.HighGustAlarm.Sound, + Sound = cumulus.HighGustAlarm.SoundFile, + Notify = cumulus.HighGustAlarm.Notify, + Email = cumulus.HighGustAlarm.Email, + Latches = cumulus.HighGustAlarm.Latch, + LatchHrs = cumulus.HighGustAlarm.LatchHours + }, + windAbove = new JsonAlarmValues() + { + Enabled = cumulus.HighWindAlarm.Enabled, + Val = cumulus.HighWindAlarm.Value, + SoundEnabled = cumulus.HighWindAlarm.Sound, + Sound = cumulus.HighWindAlarm.SoundFile, + Notify = cumulus.HighWindAlarm.Notify, + Email = cumulus.HighWindAlarm.Email, + Latches = cumulus.HighWindAlarm.Latch, + LatchHrs = cumulus.HighWindAlarm.LatchHours + }, + contactLost = new JsonAlarmValues() + { + Enabled = cumulus.SensorAlarm.Enabled, + SoundEnabled = cumulus.SensorAlarm.Sound, + Sound = cumulus.SensorAlarm.SoundFile, + Notify = cumulus.SensorAlarm.Notify, + Email = cumulus.SensorAlarm.Email, + Latches = cumulus.SensorAlarm.Latch, + LatchHrs = cumulus.SensorAlarm.LatchHours + }, + dataStopped = new JsonAlarmValues() + { + Enabled = cumulus.DataStoppedAlarm.Enabled, + SoundEnabled = cumulus.DataStoppedAlarm.Sound, + Sound = cumulus.DataStoppedAlarm.SoundFile, + Notify = cumulus.DataStoppedAlarm.Notify, + Email = cumulus.DataStoppedAlarm.Email, + Latches = cumulus.DataStoppedAlarm.Latch, + LatchHrs = cumulus.DataStoppedAlarm.LatchHours + }, + batteryLow = new JsonAlarmValues() + { + Enabled = cumulus.BatteryLowAlarm.Enabled, + SoundEnabled = cumulus.BatteryLowAlarm.Sound, + Sound = cumulus.BatteryLowAlarm.SoundFile, + Notify = cumulus.BatteryLowAlarm.Notify, + Email = cumulus.BatteryLowAlarm.Email, + Latches = cumulus.BatteryLowAlarm.Latch, + LatchHrs = cumulus.BatteryLowAlarm.LatchHours + }, + spike = new JsonAlarmValues() + { + Enabled = cumulus.SpikeAlarm.Enabled, + SoundEnabled = cumulus.SpikeAlarm.Sound, + Sound = cumulus.SpikeAlarm.SoundFile, + Notify = cumulus.SpikeAlarm.Notify, + Email = cumulus.SpikeAlarm.Email, + Latches = cumulus.SpikeAlarm.Latch, + LatchHrs = cumulus.SpikeAlarm.LatchHours, + Threshold = cumulus.SpikeAlarm.TriggerThreshold + }, + upgrade = new JsonAlarmValues() + { + Enabled = cumulus.UpgradeAlarm.Enabled, + SoundEnabled = cumulus.UpgradeAlarm.Sound, + Sound = cumulus.UpgradeAlarm.SoundFile, + Notify = cumulus.UpgradeAlarm.Notify, + Email = cumulus.UpgradeAlarm.Email, + Latches = cumulus.UpgradeAlarm.Latch, + LatchHrs = cumulus.UpgradeAlarm.LatchHours + }, + httpUpload = new JsonAlarmValues() + { + Enabled = cumulus.HttpUploadAlarm.Enabled, + SoundEnabled = cumulus.HttpUploadAlarm.Sound, + Sound = cumulus.HttpUploadAlarm.SoundFile, + Notify = cumulus.HttpUploadAlarm.Notify, + Email = cumulus.HttpUploadAlarm.Email, + Latches = cumulus.HttpUploadAlarm.Latch, + LatchHrs = cumulus.HttpUploadAlarm.LatchHours, + Threshold = cumulus.HttpUploadAlarm.TriggerThreshold + }, + mySqlUpload = new JsonAlarmValues() + { + Enabled = cumulus.MySqlUploadAlarm.Enabled, + SoundEnabled = cumulus.MySqlUploadAlarm.Sound, + Sound = cumulus.MySqlUploadAlarm.SoundFile, + Notify = cumulus.MySqlUploadAlarm.Notify, + Email = cumulus.MySqlUploadAlarm.Email, + Latches = cumulus.MySqlUploadAlarm.Latch, + LatchHrs = cumulus.MySqlUploadAlarm.LatchHours, + Threshold = cumulus.MySqlUploadAlarm.TriggerThreshold + } + }; + + var email = new JsonAlarmEmail() + { + fromEmail = cumulus.AlarmFromEmail, + destEmail = cumulus.AlarmDestEmail.Join(";"), + useHtml = cumulus.AlarmEmailHtml + }; + + var retObject = new JsonAlarmSettings() + { + data = data, + units = alarmUnits, + email = email + }; + + return retObject.ToJson(); + } + + public string UpdateAlarmSettings(IHttpContext context) + { + var json = ""; + JsonAlarmSettings result; + JsonAlarmSettingsData settings; + + try + { + var data = new StreamReader(context.Request.InputStream).ReadToEnd(); + + // Start at char 5 to skip the "json:" prefix + json = WebUtility.UrlDecode(data); + + // de-serialize it to the settings structure + //var settings = JsonConvert.DeserializeObject(json); + //var settings = JsonSerializer.DeserializeFromString(json); + + result = json.FromJson(); + settings = result.data; + } + catch (Exception ex) + { + var msg = "Error deserializing Alarm Settings JSON: " + ex.Message; + cumulus.LogMessage(msg); + cumulus.LogDebugMessage("Alarm Data: " + json); + context.Response.StatusCode = 500; + return msg; + } + + try + { + // process the settings + cumulus.LogMessage("Updating Alarm settings"); + + cumulus.LowTempAlarm.Enabled = settings.tempBelow.Enabled; + cumulus.LowTempAlarm.Value = settings.tempBelow.Val; + cumulus.LowTempAlarm.Sound = settings.tempBelow.SoundEnabled; + cumulus.LowTempAlarm.SoundFile = settings.tempBelow.Sound; + cumulus.LowTempAlarm.Notify = settings.tempBelow.Notify; + cumulus.LowTempAlarm.Email = settings.tempBelow.Email; + cumulus.LowTempAlarm.Latch = settings.tempBelow.Latches; + cumulus.LowTempAlarm.LatchHours = settings.tempBelow.LatchHrs; + + + cumulus.HighTempAlarm.Enabled = settings.tempAbove.Enabled; + cumulus.HighTempAlarm.Value = settings.tempAbove.Val; + cumulus.HighTempAlarm.Sound = settings.tempAbove.SoundEnabled; + cumulus.HighTempAlarm.SoundFile = settings.tempAbove.Sound; + cumulus.HighTempAlarm.Notify = settings.tempAbove.Notify; + cumulus.HighTempAlarm.Email = settings.tempAbove.Email; + cumulus.HighTempAlarm.Latch = settings.tempAbove.Latches; + cumulus.HighTempAlarm.LatchHours = settings.tempAbove.LatchHrs; + + cumulus.TempChangeAlarm.Enabled = settings.tempChange.Enabled; + cumulus.TempChangeAlarm.Value = settings.tempChange.Val; + cumulus.TempChangeAlarm.Sound = settings.tempChange.SoundEnabled; + cumulus.TempChangeAlarm.SoundFile = settings.tempChange.Sound; + cumulus.TempChangeAlarm.Notify = settings.tempChange.Notify; + cumulus.TempChangeAlarm.Email = settings.tempChange.Email; + cumulus.TempChangeAlarm.Latch = settings.tempChange.Latches; + cumulus.TempChangeAlarm.LatchHours = settings.tempChange.LatchHrs; + + cumulus.LowPressAlarm.Enabled = settings.pressBelow.Enabled; + cumulus.LowPressAlarm.Value = settings.pressBelow.Val; + cumulus.LowPressAlarm.Sound = settings.pressBelow.SoundEnabled; + cumulus.LowPressAlarm.SoundFile = settings.pressBelow.Sound; + cumulus.LowPressAlarm.Notify = settings.pressBelow.Notify; + cumulus.LowPressAlarm.Email = settings.pressBelow.Email; + cumulus.LowPressAlarm.Latch = settings.pressBelow.Latches; + cumulus.LowPressAlarm.LatchHours = settings.pressBelow.LatchHrs; + + cumulus.HighPressAlarm.Enabled = settings.pressAbove.Enabled; + cumulus.HighPressAlarm.Value = settings.pressAbove.Val; + cumulus.HighPressAlarm.Sound = settings.pressAbove.SoundEnabled; + cumulus.HighPressAlarm.SoundFile = settings.pressAbove.Sound; + cumulus.HighPressAlarm.Notify = settings.pressAbove.Notify; + cumulus.HighPressAlarm.Email = settings.pressAbove.Email; + cumulus.HighPressAlarm.Latch = settings.pressAbove.Latches; + cumulus.HighPressAlarm.LatchHours = settings.pressAbove.LatchHrs; + + cumulus.PressChangeAlarm.Enabled = settings.pressChange.Enabled; + cumulus.PressChangeAlarm.Value = settings.pressChange.Val; + cumulus.PressChangeAlarm.Sound = settings.pressChange.SoundEnabled; + cumulus.PressChangeAlarm.SoundFile = settings.pressChange.Sound; + cumulus.PressChangeAlarm.Notify = settings.pressChange.Notify; + cumulus.PressChangeAlarm.Email = settings.pressChange.Email; + cumulus.PressChangeAlarm.Latch = settings.pressChange.Latches; + cumulus.PressChangeAlarm.LatchHours = settings.pressChange.LatchHrs; + + cumulus.HighRainTodayAlarm.Enabled = settings.rainAbove.Enabled; + cumulus.HighRainTodayAlarm.Value = settings.rainAbove.Val; + cumulus.HighRainTodayAlarm.Sound = settings.rainAbove.SoundEnabled; + cumulus.HighRainTodayAlarm.SoundFile = settings.rainAbove.Sound; + cumulus.HighRainTodayAlarm.Notify = settings.rainAbove.Notify; + cumulus.HighRainTodayAlarm.Email = settings.rainAbove.Email; + cumulus.HighRainTodayAlarm.Latch = settings.rainAbove.Latches; + cumulus.HighRainTodayAlarm.LatchHours = settings.rainAbove.LatchHrs; + + cumulus.HighRainRateAlarm.Enabled = settings.rainRateAbove.Enabled; + cumulus.HighRainRateAlarm.Value = settings.rainRateAbove.Val; + cumulus.HighRainRateAlarm.Sound = settings.rainRateAbove.SoundEnabled; + cumulus.HighRainRateAlarm.SoundFile = settings.rainRateAbove.Sound; + cumulus.HighRainRateAlarm.Notify = settings.rainRateAbove.Notify; + cumulus.HighRainRateAlarm.Email = settings.rainRateAbove.Email; + cumulus.HighRainRateAlarm.Latch = settings.rainRateAbove.Latches; + cumulus.HighRainRateAlarm.LatchHours = settings.rainRateAbove.LatchHrs; + + cumulus.HighGustAlarm.Enabled = settings.gustAbove.Enabled; + cumulus.HighGustAlarm.Value = settings.gustAbove.Val; + cumulus.HighGustAlarm.Sound = settings.gustAbove.SoundEnabled; + cumulus.HighGustAlarm.SoundFile = settings.gustAbove.Sound; + cumulus.HighGustAlarm.Notify = settings.gustAbove.Notify; + cumulus.HighGustAlarm.Email = settings.gustAbove.Email; + cumulus.HighGustAlarm.Latch = settings.gustAbove.Latches; + cumulus.HighGustAlarm.LatchHours = settings.gustAbove.LatchHrs; + + cumulus.HighWindAlarm.Enabled = settings.windAbove.Enabled; + cumulus.HighWindAlarm.Value = settings.windAbove.Val; + cumulus.HighWindAlarm.Sound = settings.windAbove.SoundEnabled; + cumulus.HighWindAlarm.SoundFile = settings.windAbove.Sound; + cumulus.HighWindAlarm.Notify = settings.windAbove.Notify; + cumulus.HighWindAlarm.Email = settings.windAbove.Email; + cumulus.HighWindAlarm.Latch = settings.windAbove.Latches; + cumulus.HighWindAlarm.LatchHours = settings.windAbove.LatchHrs; + + cumulus.SensorAlarm.Enabled = settings.contactLost.Enabled; + cumulus.SensorAlarm.Sound = settings.contactLost.SoundEnabled; + cumulus.SensorAlarm.SoundFile = settings.contactLost.Sound; + cumulus.SensorAlarm.Notify = settings.contactLost.Notify; + cumulus.SensorAlarm.Email = settings.contactLost.Email; + cumulus.SensorAlarm.Latch = settings.contactLost.Latches; + cumulus.SensorAlarm.LatchHours = settings.contactLost.LatchHrs; + + cumulus.DataStoppedAlarm.Enabled = settings.dataStopped.Enabled; + cumulus.DataStoppedAlarm.Sound = settings.dataStopped.SoundEnabled; + cumulus.DataStoppedAlarm.SoundFile = settings.dataStopped.Sound; + cumulus.DataStoppedAlarm.Notify = settings.dataStopped.Notify; + cumulus.DataStoppedAlarm.Email = settings.dataStopped.Email; + cumulus.DataStoppedAlarm.Latch = settings.dataStopped.Latches; + cumulus.DataStoppedAlarm.LatchHours = settings.dataStopped.LatchHrs; + + cumulus.BatteryLowAlarm.Enabled = settings.batteryLow.Enabled; + cumulus.BatteryLowAlarm.Sound = settings.batteryLow.SoundEnabled; + cumulus.BatteryLowAlarm.SoundFile = settings.batteryLow.Sound; + cumulus.BatteryLowAlarm.Notify = settings.batteryLow.Notify; + cumulus.BatteryLowAlarm.Email = settings.batteryLow.Email; + cumulus.BatteryLowAlarm.Latch = settings.batteryLow.Latches; + cumulus.BatteryLowAlarm.LatchHours = settings.batteryLow.LatchHrs; + + cumulus.SpikeAlarm.Enabled = settings.spike.Enabled; + cumulus.SpikeAlarm.Sound = settings.spike.SoundEnabled; + cumulus.SpikeAlarm.SoundFile = settings.spike.Sound; + cumulus.SpikeAlarm.Notify = settings.spike.Notify; + cumulus.SpikeAlarm.Email = settings.spike.Email; + cumulus.SpikeAlarm.Latch = settings.spike.Latches; + cumulus.SpikeAlarm.LatchHours = settings.spike.LatchHrs; + cumulus.SpikeAlarm.TriggerThreshold = settings.spike.Threshold; + + cumulus.UpgradeAlarm.Enabled = settings.upgrade.Enabled; + cumulus.UpgradeAlarm.Sound = settings.upgrade.SoundEnabled; + cumulus.UpgradeAlarm.SoundFile = settings.upgrade.Sound; + cumulus.UpgradeAlarm.Notify = settings.upgrade.Notify; + cumulus.UpgradeAlarm.Email = settings.upgrade.Email; + cumulus.UpgradeAlarm.Latch = settings.upgrade.Latches; + cumulus.UpgradeAlarm.LatchHours = settings.upgrade.LatchHrs; + + cumulus.HttpUploadAlarm.Enabled = settings.httpUpload.Enabled; + cumulus.HttpUploadAlarm.Sound = settings.httpUpload.SoundEnabled; + cumulus.HttpUploadAlarm.SoundFile = settings.httpUpload.Sound; + cumulus.HttpUploadAlarm.Notify = settings.httpUpload.Notify; + cumulus.HttpUploadAlarm.Email = settings.httpUpload.Email; + cumulus.HttpUploadAlarm.Latch = settings.httpUpload.Latches; + cumulus.HttpUploadAlarm.LatchHours = settings.httpUpload.LatchHrs; + cumulus.HttpUploadAlarm.TriggerThreshold = settings.httpUpload.Threshold; + + cumulus.MySqlUploadAlarm.Enabled = settings.mySqlUpload.Enabled; + cumulus.MySqlUploadAlarm.Sound = settings.mySqlUpload.SoundEnabled; + cumulus.MySqlUploadAlarm.SoundFile = settings.mySqlUpload.Sound; + cumulus.MySqlUploadAlarm.Notify = settings.mySqlUpload.Notify; + cumulus.MySqlUploadAlarm.Email = settings.mySqlUpload.Email; + cumulus.MySqlUploadAlarm.Latch = settings.mySqlUpload.Latches; + cumulus.MySqlUploadAlarm.LatchHours = settings.mySqlUpload.LatchHrs; + cumulus.MySqlUploadAlarm.TriggerThreshold = settings.mySqlUpload.Threshold; + + // validate the from email + if (!EmailSender.CheckEmailAddress(result.email.fromEmail.Trim())) + { + var msg = "ERROR: Invalid Alarm from email address entered"; + cumulus.LogMessage(msg); + context.Response.StatusCode = 500; + return msg; + } + cumulus.AlarmFromEmail = result.email.fromEmail.Trim(); + + // validate the destination email(s) + var emails = result.email.destEmail.Trim().Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + for (var i = 0; i < emails.Length; i++) + { + emails[i] = emails[i].Trim(); + if (!EmailSender.CheckEmailAddress(emails[i])) + { + var msg = "ERROR: Invalid Alarm destination email address entered"; + cumulus.LogMessage(msg); + context.Response.StatusCode = 500; + return msg; + } + } + cumulus.AlarmDestEmail = emails; + cumulus.AlarmEmailHtml = result.email.useHtml; + + // Save the settings + cumulus.WriteIniFile(); + + context.Response.StatusCode = 200; + } + catch (Exception ex) + { + cumulus.LogMessage("Error processing Alarm settings: " + ex.Message); + cumulus.LogDebugMessage("Alarm Data: " + json); + context.Response.StatusCode = 500; + return ex.Message; + } + return "success"; + } + + public string TestEmail(IHttpContext context) + { + try + { + + var data = new StreamReader(context.Request.InputStream).ReadToEnd(); + + // Start at char 5 to skip the "json:" prefix + var json = WebUtility.UrlDecode(data); + + var result = json.FromJson(); + // process the settings + cumulus.LogMessage("Sending test email..."); + + // validate the from email + if (!EmailSender.CheckEmailAddress(result.fromEmail.Trim())) + { + var msg = "ERROR: Invalid Alarm from email address entered"; + cumulus.LogMessage(msg); + context.Response.StatusCode = 500; + return msg; + } + var from = result.fromEmail.Trim(); + + // validate the destination email(s) + var dest = result.destEmail.Trim().Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + for (var i = 0; i < dest.Length; i++) + { + dest[i] = dest[i].Trim(); + if (!EmailSender.CheckEmailAddress(dest[i])) + { + var msg = "ERROR: Invalid Alarm destination email address entered"; + cumulus.LogMessage(msg); + context.Response.StatusCode = 500; + return msg; + } + } + + var ret = cumulus.emailer.SendTestEmail(dest, from, "Cumulus MX Test Email", "A test email from Cumulus MX.", result.useHtml); + + if (ret == "OK") + { + cumulus.LogMessage("Test email sent without error"); + } + else + { + context.Response.StatusCode = 500; + return ret; + } + } + catch (Exception ex) + { + cumulus.LogMessage(ex.Message); + context.Response.StatusCode = 500; + return ex.Message; + } + + return "success"; + } + } + + public class JsonAlarmSettingsData + { + public JsonAlarmValues tempBelow { get; set; } + public JsonAlarmValues tempAbove { get; set; } + public JsonAlarmValues tempChange { get; set; } + public JsonAlarmValues pressBelow { get; set; } + public JsonAlarmValues pressAbove { get; set; } + public JsonAlarmValues pressChange { get; set; } + public JsonAlarmValues rainAbove { get; set; } + public JsonAlarmValues rainRateAbove { get; set; } + public JsonAlarmValues gustAbove { get; set; } + public JsonAlarmValues windAbove { get; set; } + public JsonAlarmValues contactLost { get; set; } + public JsonAlarmValues dataStopped { get; set; } + public JsonAlarmValues batteryLow { get; set; } + public JsonAlarmValues spike { get; set; } + public JsonAlarmValues upgrade { get; set; } + public JsonAlarmValues httpUpload { get; set; } + public JsonAlarmValues mySqlUpload { get; set; } + } + + public class JsonAlarmValues + { + public bool Enabled { get; set; } + public double Val { get; set; } + public bool SoundEnabled { get; set; } + public string Sound { get; set; } + public bool Notify { get; set; } + public bool Email { get; set; } + public bool Latches { get; set; } + public int LatchHrs { get; set; } + public int Threshold { get; set; } + } + + public class JsonAlarmEmail + { + public string fromEmail { get; set; } + public string destEmail { get; set; } + public bool useHtml { get; set; } + } + + public class JsonAlarmUnits + { + public string tempUnits { get; set; } + public string pressUnits { get; set; } + public string rainUnits { get; set; } + public string windUnits { get; set; } + } + + public class JsonAlarmSettings + { + public JsonAlarmSettingsData data { get; set; } + public JsonAlarmUnits units { get; set; } + public JsonAlarmEmail email { get; set; } + } +} diff --git a/CumulusMX/Cumulus.cs b/CumulusMX/Cumulus.cs index 1c7a4122..8a904c0f 100644 --- a/CumulusMX/Cumulus.cs +++ b/CumulusMX/Cumulus.cs @@ -1,10092 +1,9937 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.IO.Ports; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Sockets; -using System.Net.NetworkInformation; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Security.Authentication; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Timers; -using MySqlConnector; -using FluentFTP; -using LinqToTwitter; -using ServiceStack.Text; -using Unosquare.Labs.EmbedIO; -using Unosquare.Labs.EmbedIO.Modules; -using Unosquare.Labs.EmbedIO.Constants; -using Timer = System.Timers.Timer; -using SQLite; -using Renci.SshNet; -using FluentFTP.Helpers; - -namespace CumulusMX -{ - public class Cumulus - { - ///////////////////////////////// - /// Now derived from app properties - public string Version; - public string Build; - ///////////////////////////////// - - - - public static SemaphoreSlim syncInit = new SemaphoreSlim(1); - - /* - public enum VPRainGaugeTypes - { - MM = 0, - IN = 1 - } - */ - - /* - public enum VPConnTypes - { - Serial = 0, - TCPIP = 1 - } - */ - - public enum PressUnits - { - MB, - HPA, - IN - } - - public enum WindUnits - { - MS, - MPH, - KPH, - KNOTS - } - - public enum TempUnits - { - C, - F - } - - public enum RainUnits - { - MM, - IN - } - - /* - public enum SolarCalcTypes - { - RyanStolzenbach = 0, - Bras = 1 - } - */ - - public enum FtpProtocols - { - FTP = 0, - FTPS = 1, - SFTP = 2 - } - - public enum PrimaryAqSensor - { - Undefined = -1, - AirLinkOutdoor = 0, - Ecowitt1 = 1, - Ecowitt2 = 2, - Ecowitt3 = 3, - Ecowitt4 = 4, - AirLinkIndoor = 5, - EcowittCO2 = 6 - } - - private readonly string[] sshAuthenticationVals = { "password", "psk", "password_psk" }; - - /* - public struct Dataunits - { - public Units.Presss Units.Press; - public Units.Winds Units.Wind; - public tempunits tempunit; - public rainunits rainunit; - } - */ - - /* - public struct CurrentData - { - public double OutdoorTemperature; - public double AvgTempToday; - public double IndoorTemperature; - public double OutdoorDewpoint; - public double WindChill; - public int IndoorHumidity; - public int OutdoorHumidity; - public double Pressure; - public double WindLatest; - public double WindAverage; - public double Recentmaxgust; - public double WindRunToday; - public int Bearing; - public int Avgbearing; - public double RainToday; - public double RainYesterday; - public double RainMonth; - public double RainYear; - public double RainRate; - public double RainLastHour; - public double HeatIndex; - public double Humidex; - public double AppTemp; - public double FeelsLike; - public double TempTrend; - public double PressTrend; - } - */ - - /* - public struct HighLowData - { - public double TodayLow; - public DateTime TodayLowDT; - public double TodayHigh; - public DateTime TodayHighDT; - public double YesterdayLow; - public DateTime YesterdayLowDT; - public double YesterdayHigh; - public DateTime YesterdayHighDT; - public double MonthLow; - public DateTime MonthLowDT; - public double MonthHigh; - public DateTime MonthHighDT; - public double YearLow; - public DateTime YearLowDT; - public double YearHigh; - public DateTime YearHighDT; - } - */ - - public struct TExtraFiles - { - public string local; - public string remote; - public bool process; - public bool binary; - public bool realtime; - public bool endofday; - public bool FTP; - public bool UTF8; - } - - //public Dataunits Units; - - public const int DayfileFields = 52; - - private readonly WeatherStation station; - - internal DavisAirLink airLinkIn; - public int airLinkInLsid; - public string AirLinkInHostName; - internal DavisAirLink airLinkOut; - public int airLinkOutLsid; - public string AirLinkOutHostName; - - public DateTime LastUpdateTime; - - public PerformanceCounter UpTime; - - private readonly WebTags webtags; - private readonly TokenParser tokenParser; - private readonly TokenParser realtimeTokenParser; - private readonly TokenParser customMysqlSecondsTokenParser = new TokenParser(); - private readonly TokenParser customMysqlMinutesTokenParser = new TokenParser(); - private readonly TokenParser customMysqlRolloverTokenParser = new TokenParser(); - - public string CurrentActivity = "Stopped"; - - private static readonly TraceListener FtpTraceListener = new TextWriterTraceListener("ftplog.txt", "ftplog"); - - - public string AirQualityUnitText = "µg/m³"; - public string SoilMoistureUnitText = "cb"; - public string CO2UnitText = "ppm"; - - public volatile int WebUpdating; - - public double WindRoseAngle { get; set; } - - public int NumWindRosePoints { get; set; } - - //public int[] WUnitFact = new[] { 1000, 2237, 3600, 1944 }; - //public int[] TUnitFact = new[] { 1000, 1800 }; - //public int[] TUnitAdd = new[] { 0, 32 }; - //public int[] PUnitFact = new[] { 1000, 1000, 2953 }; - //public int[] PressFact = new[] { 1, 1, 100 }; - //public int[] RUnitFact = new[] { 1000, 39 }; - - public int[] logints = new[] { 1, 5, 10, 15, 20, 30 }; - - //public int UnitMult = 1000; - - public int GraphDays = 31; - - public string Newmoon = "New Moon", - WaxingCrescent = "Waxing Crescent", - FirstQuarter = "First Quarter", - WaxingGibbous = "Waxing Gibbous", - Fullmoon = "Full Moon", - WaningGibbous = "Waning Gibbous", - LastQuarter = "Last Quarter", - WaningCrescent = "Waning Crescent"; - - public string Calm = "Calm", - Lightair = "Light air", - Lightbreeze = "Light breeze", - Gentlebreeze = "Gentle breeze", - Moderatebreeze = "Moderate breeze", - Freshbreeze = "Fresh breeze", - Strongbreeze = "Strong breeze", - Neargale = "Near gale", - Gale = "Gale", - Stronggale = "Strong gale", - Storm = "Storm", - Violentstorm = "Violent storm", - Hurricane = "Hurricane"; - - public string Risingveryrapidly = "Rising very rapidly", - Risingquickly = "Rising quickly", - Rising = "Rising", - Risingslowly = "Rising slowly", - Steady = "Steady", - Fallingslowly = "Falling slowly", - Falling = "Falling", - Fallingquickly = "Falling quickly", - Fallingveryrapidly = "Falling very rapidly"; - - public string[] compassp = { "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW" }; - - public string[] zForecast = - { - "Settled fine", "Fine weather", "Becoming fine", "Fine, becoming less settled", "Fine, possible showers", "Fairly fine, improving", - "Fairly fine, possible showers early", "Fairly fine, showery later", "Showery early, improving", "Changeable, mending", - "Fairly fine, showers likely", "Rather unsettled clearing later", "Unsettled, probably improving", "Showery, bright intervals", - "Showery, becoming less settled", "Changeable, some rain", "Unsettled, short fine intervals", "Unsettled, rain later", "Unsettled, some rain", - "Mostly very unsettled", "Occasional rain, worsening", "Rain at times, very unsettled", "Rain at frequent intervals", "Rain, very unsettled", - "Stormy, may improve", "Stormy, much rain" - }; - - public string[] DavisForecast1 = - { - "FORECAST REQUIRES 3 HRS. OF RECENT DATA", "Mostly cloudy with little temperature change. ", "Mostly cloudy and cooler. ", - "Clearing, cooler and windy. ", "Clearing and cooler. ", "Increasing clouds and cooler. ", - "Increasing clouds with little temperature change. ", "Increasing clouds and warmer. ", - "Mostly clear for 12 to 24 hours with little temperature change. ", "Mostly clear for 6 to 12 hours with little temperature change. ", - "Mostly clear and warmer. ", "Mostly clear for 12 to 24 hours and cooler. ", "Mostly clear for 12 hours with little temperature change. ", - "Mostly clear with little temperature change. ", "Mostly clear and cooler. ", "Partially cloudy, Rain and/or snow possible or continuing. ", - "Partially cloudy, Snow possible or continuing. ", "Partially cloudy, Rain possible or continuing. ", - "Mostly cloudy, Rain and/or snow possible or continuing. ", "Mostly cloudy, Snow possible or continuing. ", - "Mostly cloudy, Rain possible or continuing. ", "Mostly cloudy. ", "Partially cloudy. ", "Mostly clear. ", - "Partly cloudy with little temperature change. ", "Partly cloudy and cooler. ", "Unknown forecast rule." - }; - - public string[] DavisForecast2 = - { - "", "Precipitation possible within 48 hours. ", "Precipitation possible within 24 to 48 hours. ", - "Precipitation possible within 24 hours. ", "Precipitation possible within 12 to 24 hours. ", - "Precipitation possible within 12 hours, possibly heavy at times. ", "Precipitation possible within 12 hours. ", - "Precipitation possible within 6 to 12 hours. ", "Precipitation possible within 6 to 12 hours, possibly heavy at times. ", - "Precipitation possible and windy within 6 hours. ", "Precipitation possible within 6 hours. ", "Precipitation ending in 12 to 24 hours. ", - "Precipitation possibly heavy at times and ending within 12 hours. ", "Precipitation ending within 12 hours. ", - "Precipitation ending within 6 hours. ", "Precipitation likely, possibly heavy at times. ", "Precipitation likely. ", - "Precipitation continuing, possibly heavy at times. ", "Precipitation continuing. " - }; - - public string[] DavisForecast3 = - { - "", "Windy with possible wind shift to the W, SW, or S.", "Possible wind shift to the W, SW, or S.", - "Windy with possible wind shift to the W, NW, or N.", "Possible wind shift to the W, NW, or N.", "Windy.", "Increasing winds." - }; - - public int[,] DavisForecastLookup = - { - {14, 0, 0}, {13, 0, 0}, {12, 0, 0}, {11, 0, 0}, {13, 0, 0}, {25, 0, 0}, {24, 0, 0}, {24, 0, 0}, {10, 0, 0}, {24, 0, 0}, {24, 0, 0}, - {13, 0, 0}, {7, 2, 0}, {24, 0, 0}, {13, 0, 0}, {6, 3, 0}, {13, 0, 0}, {24, 0, 0}, {13, 0, 0}, {6, 6, 0}, {13, 0, 0}, {24, 0, 0}, - {13, 0, 0}, {7, 3, 0}, {10, 0, 6}, {24, 0, 0}, {13, 0, 0}, {7, 6, 6}, {10, 0, 6}, {7, 0, 0}, {24, 0, 0}, {13, 0, 0}, {7, 6, 6}, - {10, 0, 6}, {7, 0, 0}, {24, 0, 0}, {13, 0, 0}, {7, 6, 6}, {24, 0, 0}, {13, 0, 0}, {10, 1, 0}, {10, 0, 0}, {24, 0, 0}, {13, 0, 0}, - {6, 2, 0}, {6, 0, 0}, {24, 0, 0}, {13, 0, 0}, {7, 4, 0}, {24, 0, 0}, {13, 0, 0}, {7, 4, 5}, {24, 0, 0}, {13, 0, 0}, {7, 4, 5}, - {24, 0, 0}, {13, 0, 0}, {7, 7, 0}, {24, 0, 0}, {13, 0, 0}, {7, 7, 5}, {24, 0, 0}, {13, 0, 0}, {7, 4, 5}, {24, 0, 0}, {13, 0, 0}, - {7, 6, 0}, {24, 0, 0}, {13, 0, 0}, {7, 16, 0}, {4, 14, 0}, {24, 0, 0}, {4, 14, 0}, {13, 0, 0}, {4, 14, 0}, {25, 0, 0}, {24, 0, 0}, - {14, 0, 0}, {4, 14, 0}, {13, 0, 0}, {4, 14, 0}, {14, 0, 0}, {24, 0, 0}, {13, 0, 0}, {6, 3, 0}, {2, 18, 0}, {24, 0, 0}, {13, 0, 0}, - {2, 16, 0}, {1, 18, 0}, {1, 16, 0}, {24, 0, 0}, {13, 0, 0}, {5, 9, 0}, {6, 9, 0}, {2, 18, 6}, {24, 0, 0}, {13, 0, 0}, {2, 16, 6}, - {1, 18, 6}, {1, 16, 6}, {24, 0, 0}, {13, 0, 0}, {5, 4, 4}, {6, 4, 4}, {24, 0, 0}, {13, 0, 0}, {5, 10, 4}, {6, 10, 4}, {2, 13, 4}, - {2, 0, 4}, {1, 13, 4}, {1, 0, 4}, {2, 13, 4}, {24, 0, 0}, {13, 0, 0}, {2, 3, 4}, {1, 13, 4}, {1, 3, 4}, {3, 14, 0}, {3, 0, 0}, - {2, 14, 3}, {2, 0, 3}, {3, 0, 0}, {24, 0, 0}, {13, 0, 0}, {1, 6, 5}, {24, 0, 0}, {13, 0, 0}, {5, 5, 5}, {2, 14, 5}, {24, 0, 0}, - {13, 0, 0}, {2, 6, 5}, {2, 11, 0}, {2, 0, 0}, {2, 17, 5}, {24, 0, 0}, {13, 0, 0}, {2, 7, 5}, {1, 17, 5}, {24, 0, 0}, {13, 0, 0}, - {1, 7, 5}, {24, 0, 0}, {13, 0, 0}, {6, 5, 5}, {2, 0, 5}, {2, 17, 5}, {24, 0, 0}, {13, 0, 0}, {2, 15, 5}, {1, 17, 5}, {1, 15, 5}, - {24, 0, 0}, {13, 0, 0}, {5, 10, 5}, {6, 10, 5}, {5, 18, 3}, {24, 0, 0}, {13, 0, 0}, {2, 16, 3}, {1, 18, 3}, {1, 16, 3}, {5, 10, 3}, - {24, 0, 0}, {13, 0, 0}, {5, 10, 4}, {6, 10, 3}, {6, 10, 4}, {24, 0, 0}, {13, 0, 0}, {5, 10, 3}, {6, 10, 3}, {24, 0, 0}, {13, 0, 0}, - {5, 4, 3}, {6, 4, 3}, {2, 12, 3}, {24, 0, 0}, {13, 0, 0}, {2, 8, 3}, {1, 13, 3}, {1, 8, 3}, {2, 18, 0}, {24, 0, 0}, {13, 0, 0}, - {2, 16, 3}, {1, 18, 0}, {1, 16, 0}, {24, 0, 0}, {13, 0, 0}, {2, 5, 5}, {0, 0, 0}, {26, 0, 0}, {26, 0, 0}, {26, 0, 0}, {26, 0, 0}, {26, 0, 0}, {26, 0, 0}, {26, 0, 0} - }; - - // equivalents of Zambretti "dial window" letters A - Z - public int[] riseOptions = { 25, 25, 25, 24, 24, 19, 16, 12, 11, 9, 8, 6, 5, 2, 1, 1, 0, 0, 0, 0, 0, 0 }; - public int[] steadyOptions = { 25, 25, 25, 25, 25, 25, 23, 23, 22, 18, 15, 13, 10, 4, 1, 1, 0, 0, 0, 0, 0, 0 }; - public int[] fallOptions = { 25, 25, 25, 25, 25, 25, 25, 25, 23, 23, 21, 20, 17, 14, 7, 3, 1, 1, 1, 0, 0, 0 }; - - internal int[] FactorsOf60 = { 1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60 }; - - public TimeSpan AvgSpeedTime { get; set; } - - public TimeSpan PeakGustTime { get; set; } - public TimeSpan AvgBearingTime { get; set; } - - public bool UTF8encode { get; set; } - - internal int TempDPlaces = 1; - public string TempFormat; - - internal int WindDPlaces = 1; - internal int WindAvgDPlaces = 1; - public string WindFormat; - public string WindAvgFormat; - - internal int HumDPlaces = 0; - public string HumFormat; - - internal int AirQualityDPlaces = 1; - public string AirQualityFormat; - - public int WindRunDPlaces = 1; - public string WindRunFormat; - - public int RainDPlaces = 1; - public string RainFormat; - - internal int PressDPlaces = 1; - public string PressFormat; - - internal int SunshineDPlaces = 1; - public string SunFormat; - - internal int UVDPlaces = 1; - public string UVFormat; - - public string ETFormat; - - public string ComportName; - public string DefaultComportName; - - //public string IPaddress; - - //public int TCPport; - - //public VPConnTypes VPconntype; - - public string Platform; - - public string dbfile; - public SQLiteConnection LogDB; - - public string diaryfile; - public SQLiteConnection DiaryDB; - - public string Datapath; - - public string ListSeparator; - public char DirectorySeparator; - - public int RolloverHour; - public bool Use10amInSummer; - - public double Latitude; - public double Longitude; - public double Altitude; - - public double RStransfactor = 0.8; - - internal int wsPort; - private readonly bool DebuggingEnabled; - - public SerialPort cmprtRG11; - public SerialPort cmprt2RG11; - - private const int DefaultWebUpdateInterval = 15; - - public int RecordSetTimeoutHrs = 24; - - private const int VP2SERIALCONNECTION = 0; - //private const int VP2USBCONNECTION = 1; - //private const int VP2TCPIPCONNECTION = 2; - - private readonly string twitterKey = "lQiGNdtlYUJ4wS3d7souPw"; - private readonly string twitterSecret = "AoB7OqimfoaSfGQAd47Hgatqdv3YeTTiqpinkje6Xg"; - - public string AlltimeIniFile; - public string Alltimelogfile; - public string MonthlyAlltimeIniFile; - public string MonthlyAlltimeLogFile; - private readonly string logFilePath; - public string DayFileName; - public string YesterdayFile; - public string TodayIniFile; - public string MonthIniFile; - public string YearIniFile; - //private readonly string stringsFile; - private readonly string backupPath; - //private readonly string ExternaldataFile; - public string WebTagFile; - - public bool SynchronisedWebUpdate; - - private List WundList = new List(); - private List WindyList = new List(); - private List PWSList = new List(); - private List WOWList = new List(); - private List OWMList = new List(); - - private List MySqlList = new List(); - - // Calibration settings - /// - /// User value calibration settings - /// - public Calibrations Calib = new Calibrations(); - - /// - /// User extreme limit settings - /// - public Limits Limit = new Limits(); - - /// - /// User spike limit settings - /// - public Spikes Spike = new Spikes(); - - public ProgramOptionsClass ProgramOptions = new ProgramOptionsClass(); - - public StationOptions StationOptions = new StationOptions(); - - public StationUnits Units = new StationUnits(); - - public DavisOptions DavisOptions = new DavisOptions(); - public FineOffsetOptions FineOffsetOptions = new FineOffsetOptions(); - public ImetOptions ImetOptions = new ImetOptions(); - public EasyWeatherOptions EwOptions = new EasyWeatherOptions(); - - public GraphOptions GraphOptions = new GraphOptions(); - - public SelectaChartOptions SelectaChartOptions = new SelectaChartOptions(); - - public DisplayOptions DisplayOptions = new DisplayOptions(); - - public EmailSender emailer; - public EmailSender.SmtpOptions SmtpOptions = new EmailSender.SmtpOptions(); - - public string AlarmEmailPreamble; - public string AlarmEmailSubject; - public string AlarmFromEmail; - public string[] AlarmDestEmail; - public bool AlarmEmailHtml; - - public bool ListWebTags; - - public bool RealtimeEnabled; // The timer is to be started - public bool RealtimeFTPEnabled; // The FTP connection is to be established - private int realtimeFTPRetries; // Count of failed realtime FTP attempts - - // Twitter settings - public WebUploadTwitter Twitter = new WebUploadTwitter(); - - // Wunderground settings - public WebUploadWund Wund = new WebUploadWund(); - - // Windy.com settings - public WebUploadWindy Windy = new WebUploadWindy(); - - // Wind Guru settings - public WebUploadWindGuru WindGuru = new WebUploadWindGuru(); - - // PWS Weather settings - public WebUploadService PWS = new WebUploadService(); - - // WOW settings - public WebUploadService WOW = new WebUploadService(); - - // APRS settings - public WebUploadAprs APRS = new WebUploadAprs(); - - // Awekas settings - public WebUploadAwekas AWEKAS = new WebUploadAwekas(); - - // WeatherCloud settings - public WebUploadWCloud WCloud = new WebUploadWCloud(); - - // OpenWeatherMap settings - public WebUploadService OpenWeatherMap = new WebUploadService(); - - - // MQTT settings - public struct MqttSettings - { - public string Server; - public int Port; - public int IpVersion; - public bool UseTLS; - public string Username; - public string Password; - public bool EnableDataUpdate; - public string UpdateTopic; - public string UpdateTemplate; - public bool UpdateRetained; - public bool EnableInterval; - public int IntervalTime; - public string IntervalTopic; - public string IntervalTemplate; - public bool IntervalRetained; - } - public MqttSettings MQTT; - - // NOAA report settings - public string NOAAname; - public string NOAAcity; - public string NOAAstate; - public double NOAAheatingthreshold; - public double NOAAcoolingthreshold; - public double NOAAmaxtempcomp1; - public double NOAAmaxtempcomp2; - public double NOAAmintempcomp1; - public double NOAAmintempcomp2; - public double NOAAraincomp1; - public double NOAAraincomp2; - public double NOAAraincomp3; - public bool NOAA12hourformat; - public bool NOAAAutoSave; - public bool NOAAAutoFTP; - public bool NOAANeedFTP; - public string NOAAMonthFileFormat; - public string NOAAYearFileFormat; - public string NOAAFTPDirectory; - public string NOAALatestMonthlyReport; - public string NOAALatestYearlyReport; - public bool NOAAUseUTF8; - public bool NOAAUseDotDecimal; - - public double[] NOAATempNorms = new double[13]; - - public double[] NOAARainNorms = new double[13]; - - // Growing Degree Days - public double GrowingBase1; - public double GrowingBase2; - public int GrowingYearStarts; - public bool GrowingCap30C; - - public int TempSumYearStarts; - public double TempSumBase1; - public double TempSumBase2; - - public bool EODfilesNeedFTP; - - public bool IsOSX; - public double CPUtemp = -999; - - // Alarms - public Alarm DataStoppedAlarm = new Alarm(); - public Alarm BatteryLowAlarm = new Alarm(); - public Alarm SensorAlarm = new Alarm(); - public Alarm SpikeAlarm = new Alarm(); - public Alarm HighWindAlarm = new Alarm(); - public Alarm HighGustAlarm = new Alarm(); - public Alarm HighRainRateAlarm = new Alarm(); - public Alarm HighRainTodayAlarm = new Alarm(); - public AlarmChange PressChangeAlarm = new AlarmChange(); - public Alarm HighPressAlarm = new Alarm(); - public Alarm LowPressAlarm = new Alarm(); - public AlarmChange TempChangeAlarm = new AlarmChange(); - public Alarm HighTempAlarm = new Alarm(); - public Alarm LowTempAlarm = new Alarm(); - public Alarm UpgradeAlarm = new Alarm(); - public Alarm HttpUploadAlarm = new Alarm(); - public Alarm MySqlUploadAlarm = new Alarm(); - - - private const double DEFAULTFCLOWPRESS = 950.0; - private const double DEFAULTFCHIGHPRESS = 1050.0; - - private const string ForumDefault = "https://cumulus.hosiene.co.uk/"; - - private const string WebcamDefault = ""; - - private const string DefaultSoundFile = "alarm.mp3"; - private const string DefaultSoundFileOld = "alert.wav"; - - public int RealtimeInterval; - - public string ForecastNotAvailable = "Not available"; - - public WebServer httpServer; - //public WebSocket websock; - - //private Thread httpThread; - - private static readonly HttpClientHandler WUhttpHandler = new HttpClientHandler(); - private readonly HttpClient WUhttpClient = new HttpClient(WUhttpHandler); - - private static readonly HttpClientHandler WindyhttpHandler = new HttpClientHandler(); - private readonly HttpClient WindyhttpClient = new HttpClient(WindyhttpHandler); - - private static readonly HttpClientHandler WindGuruhttpHandler = new HttpClientHandler(); - private readonly HttpClient WindGuruhttpClient = new HttpClient(WindGuruhttpHandler); - - private static readonly HttpClientHandler AwekashttpHandler = new HttpClientHandler(); - private readonly HttpClient AwekashttpClient = new HttpClient(AwekashttpHandler); - - private static readonly HttpClientHandler WCloudhttpHandler = new HttpClientHandler(); - private readonly HttpClient WCloudhttpClient = new HttpClient(WCloudhttpHandler); - - private static readonly HttpClientHandler PWShttpHandler = new HttpClientHandler(); - private readonly HttpClient PWShttpClient = new HttpClient(PWShttpHandler); - - private static readonly HttpClientHandler WOWhttpHandler = new HttpClientHandler(); - private readonly HttpClient WOWhttpClient = new HttpClient(WOWhttpHandler); - - // Custom HTTP - seconds - private static readonly HttpClientHandler customHttpSecondsHandler = new HttpClientHandler(); - private readonly HttpClient customHttpSecondsClient = new HttpClient(customHttpSecondsHandler); - private bool updatingCustomHttpSeconds; - private readonly TokenParser customHttpSecondsTokenParser = new TokenParser(); - internal Timer CustomHttpSecondsTimer; - internal bool CustomHttpSecondsEnabled; - internal string CustomHttpSecondsString; - internal int CustomHttpSecondsInterval; - - // Custom HTTP - minutes - private static readonly HttpClientHandler customHttpMinutesHandler = new HttpClientHandler(); - private readonly HttpClient customHttpMinutesClient = new HttpClient(customHttpMinutesHandler); - private bool updatingCustomHttpMinutes; - private readonly TokenParser customHttpMinutesTokenParser = new TokenParser(); - internal bool CustomHttpMinutesEnabled; - internal string CustomHttpMinutesString; - internal int CustomHttpMinutesInterval; - internal int CustomHttpMinutesIntervalIndex; - - // Custom HTTP - rollover - private static readonly HttpClientHandler customHttpRolloverHandler = new HttpClientHandler(); - private readonly HttpClient customHttpRolloverClient = new HttpClient(customHttpRolloverHandler); - private bool updatingCustomHttpRollover; - private readonly TokenParser customHttpRolloverTokenParser = new TokenParser(); - internal bool CustomHttpRolloverEnabled; - internal string CustomHttpRolloverString; - - public Thread ftpThread; - public Thread MySqlCatchupThread; - - public string xapHeartbeat; - public string xapsource; - - public MySqlConnection MonthlyMySqlConn = new MySqlConnection(); - public MySqlConnection RealtimeSqlConn = new MySqlConnection(); - public MySqlConnection CustomMysqlSecondsConn = new MySqlConnection(); - public MySqlCommand CustomMysqlSecondsCommand = new MySqlCommand(); - public MySqlConnection CustomMysqlMinutesConn = new MySqlConnection(); - public MySqlCommand CustomMysqlMinutesCommand = new MySqlCommand(); - public MySqlConnection CustomMysqlRolloverConn = new MySqlConnection(); - public MySqlCommand CustomMysqlRolloverCommand = new MySqlCommand(); - - public MySqlConnectionStringBuilder MySqlConnSettings = new MySqlConnectionStringBuilder(); - - public string LatestBuild = "n/a"; - - public bool RealtimeMySqlEnabled; - public bool MonthlyMySqlEnabled; - public bool DayfileMySqlEnabled; - - public string MySqlMonthlyTable; - public string MySqlDayfileTable; - public string MySqlRealtimeTable; - public string MySqlRealtimeRetention; - public string StartOfMonthlyInsertSQL; - public string StartOfDayfileInsertSQL; - public string StartOfRealtimeInsertSQL; - - public string CreateMonthlySQL; - public string CreateDayfileSQL; - public string CreateRealtimeSQL; - - public string CustomMySqlSecondsCommandString; - public string CustomMySqlMinutesCommandString; - public string CustomMySqlRolloverCommandString; - - public bool CustomMySqlSecondsEnabled; - public bool CustomMySqlMinutesEnabled; - public bool CustomMySqlRolloverEnabled; - - public int CustomMySqlSecondsInterval; - public int CustomMySqlMinutesInterval; - public int CustomMySqlMinutesIntervalIndex; - - private bool customMySqlSecondsUpdateInProgress; - private bool customMySqlMinutesUpdateInProgress; - private bool customMySqlRolloverUpdateInProgress; - - public AirLinkData airLinkDataIn; - public AirLinkData airLinkDataOut; - - public string[] StationDesc = - { - "Davis Vantage Pro", "Davis Vantage Pro2", "Oregon Scientific WMR-928", "Oregon Scientific WM-918", "EasyWeather", "Fine Offset", - "LaCrosse WS2300", "Fine Offset with Solar", "Oregon Scientific WMR100", "Oregon Scientific WMR200", "Instromet", "Davis WLL", "GW1000" - }; - - public string[] APRSstationtype = { "DsVP", "DsVP", "WMR928", "WM918", "EW", "FO", "WS2300", "FOs", "WMR100", "WMR200", "Instromet", "DsVP", "Ecowitt" }; - - public string loggingfile; - - public Cumulus(int HTTPport, bool DebugEnabled, string startParms) - { - var fullVer = Assembly.GetExecutingAssembly().GetName().Version; - Version = $"{fullVer.Major}.{fullVer.Minor}.{fullVer.Build}"; - Build = Assembly.GetExecutingAssembly().GetName().Version.Revision.ToString(); - - DirectorySeparator = Path.DirectorySeparatorChar; - - AppDir = Directory.GetCurrentDirectory() + DirectorySeparator; - TwitterTxtFile = AppDir + "twitter.txt"; - WebTagFile = AppDir + "WebTags.txt"; - - //b3045>, use same port for WS... WS port = HTTPS port - //wsPort = WSport; - wsPort = HTTPport; - - DebuggingEnabled = DebugEnabled; - - // Set up the diagnostic tracing - loggingfile = GetLoggingFileName("MXdiags" + DirectorySeparator); - - Program.svcTextListener.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff ") + "Creating main MX log file - " + loggingfile); - Program.svcTextListener.Flush(); - - TextWriterTraceListener myTextListener = new TextWriterTraceListener(loggingfile, "MXlog"); - Trace.Listeners.Add(myTextListener); - Trace.AutoFlush = true; - - // Read the configuration file - - LogMessage(" ========================== Cumulus MX starting =========================="); - - LogMessage("Command line: " + Environment.CommandLine + " " + startParms); - - //Assembly thisAssembly = this.GetType().Assembly; - //Version = thisAssembly.GetName().Version.ToString(); - //VersionLabel.Content = "Cumulus v." + thisAssembly.GetName().Version; - LogMessage("Cumulus MX v." + Version + " build " + Build); - LogConsoleMessage("Cumulus MX v." + Version + " build " + Build); - LogConsoleMessage("Working Dir: " + AppDir); - - IsOSX = IsRunningOnMac(); - - Platform = IsOSX ? "Mac OS X" : Environment.OSVersion.Platform.ToString(); - - // Set the default comport name depending on platform - DefaultComportName = Platform.Substring(0, 3) == "Win" ? "COM1" : "/dev/ttyUSB0"; - - LogMessage("Platform: " + Platform); - - LogMessage("OS version: " + Environment.OSVersion); - - Type type = Type.GetType("Mono.Runtime"); - if (type != null) - { - MethodInfo displayName = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static); - if (displayName != null) - LogMessage("Mono version: "+displayName.Invoke(null, null)); - } - - // determine system uptime based on OS - if (Platform.Substring(0, 3) == "Win") - { - try - { - // Windows enable the performance counter method - UpTime = new PerformanceCounter("System", "System Up Time"); - } - catch (Exception e) - { - LogMessage("Error: Unable to acces the System Up Time performance counter. System up time will not be available"); - LogDebugMessage($"Error: {e}"); - } - } - - LogMessage("Current culture: " + CultureInfo.CurrentCulture.DisplayName); - ListSeparator = CultureInfo.CurrentCulture.TextInfo.ListSeparator; - - DecimalSeparator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; - - LogMessage("Directory separator=[" + DirectorySeparator + "] Decimal separator=[" + DecimalSeparator + "] List separator=[" + ListSeparator + "]"); - LogMessage("Date separator=[" + CultureInfo.CurrentCulture.DateTimeFormat.DateSeparator + "] Time separator=[" + CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator + "]"); - - TimeZone localZone = TimeZone.CurrentTimeZone; - DateTime now = DateTime.Now; - - LogMessage("Standard time zone name: " + localZone.StandardName); - LogMessage("Daylight saving time name: " + localZone.DaylightName); - LogMessage("Daylight saving time? " + localZone.IsDaylightSavingTime(now)); - - LogMessage(DateTime.Now.ToString("G")); - - // find the data folder - //datapath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + DirectorySeparator + "Cumulus" + DirectorySeparator; - - Datapath = "data" + DirectorySeparator; - backupPath = "backup" + DirectorySeparator; - ReportPath = "Reports" + DirectorySeparator; - var WebPath = "web" + DirectorySeparator; - - dbfile = Datapath + "cumulusmx.db"; - diaryfile = Datapath + "diary.db"; - - //AlltimeFile = Datapath + "alltime.rec"; - AlltimeIniFile = Datapath + "alltime.ini"; - Alltimelogfile = Datapath + "alltimelog.txt"; - MonthlyAlltimeIniFile = Datapath + "monthlyalltime.ini"; - MonthlyAlltimeLogFile = Datapath + "monthlyalltimelog.txt"; - logFilePath = Datapath; - DayFileName = Datapath + "dayfile.txt"; - YesterdayFile = Datapath + "yesterday.ini"; - TodayIniFile = Datapath + "today.ini"; - MonthIniFile = Datapath + "month.ini"; - YearIniFile = Datapath + "year.ini"; - //stringsFile = "strings.ini"; - - // Set the default upload intervals for web services - Wund.DefaultInterval = 15; - Windy.DefaultInterval = 15; - WindGuru.DefaultInterval = 1; - PWS.DefaultInterval = 15; - APRS.DefaultInterval = 9; - AWEKAS.DefaultInterval = 15 * 60; - WCloud.DefaultInterval = 10; - OpenWeatherMap.DefaultInterval = 15; - - StdWebFiles = new FileGenerationFtpOptions[2]; - StdWebFiles[0] = new FileGenerationFtpOptions() - { - TemplateFileName = WebPath + "websitedataT.json", - LocalPath = WebPath, - LocalFileName = "websitedata.json", - RemoteFileName = "websitedata.json" - }; - StdWebFiles[1] = new FileGenerationFtpOptions() - { - LocalPath = "", - LocalFileName = "wxnow.txt", - RemoteFileName = "wxnow.txt" - }; - - RealtimeFiles = new FileGenerationFtpOptions[2]; - RealtimeFiles[0] = new FileGenerationFtpOptions() - { - LocalFileName = "realtime.txt", - RemoteFileName = "realtime.txt" - }; - RealtimeFiles[1] = new FileGenerationFtpOptions() - { - TemplateFileName = WebPath + "realtimegaugesT.txt", - LocalPath = WebPath, - LocalFileName = "realtimegauges.txt", - RemoteFileName = "realtimegauges.txt" - }; - - GraphDataFiles = new FileGenerationFtpOptions[13]; - GraphDataFiles[0] = new FileGenerationFtpOptions() - { - LocalPath = WebPath, - LocalFileName = "graphconfig.json", - RemoteFileName = "graphconfig.json" - }; - GraphDataFiles[1] = new FileGenerationFtpOptions() - { - LocalPath = WebPath, - LocalFileName = "availabledata.json", - RemoteFileName = "availabledata.json" - }; - GraphDataFiles[2] = new FileGenerationFtpOptions() - { - LocalPath = WebPath, - LocalFileName = "tempdata.json", - RemoteFileName ="tempdata.json" - }; - GraphDataFiles[3] = new FileGenerationFtpOptions() - { - LocalPath = WebPath, - LocalFileName = "pressdata.json", - RemoteFileName = "pressdata.json" - }; - GraphDataFiles[4] = new FileGenerationFtpOptions() - { - LocalPath = WebPath, - LocalFileName = "winddata.json", - RemoteFileName = "winddata.json" - }; - GraphDataFiles[5] = new FileGenerationFtpOptions() - { - LocalPath = WebPath, - LocalFileName = "wdirdata.json", - RemoteFileName = "wdirdata.json" - }; - GraphDataFiles[6] = new FileGenerationFtpOptions() - { - LocalPath = WebPath, - LocalFileName = "humdata.json", - RemoteFileName = "humdata.json" - }; - GraphDataFiles[7] = new FileGenerationFtpOptions() - { - LocalPath = WebPath, - LocalFileName = "raindata.json", - RemoteFileName = "raindata.json" - }; - GraphDataFiles[8] = new FileGenerationFtpOptions() - { - LocalPath = WebPath, - LocalFileName = "dailyrain.json", - RemoteFileName = "dailyrain.json" - }; - GraphDataFiles[9] = new FileGenerationFtpOptions() - { - LocalPath = WebPath, - LocalFileName = "dailytemp.json", - RemoteFileName = "dailytemp.json" - }; - GraphDataFiles[10] = new FileGenerationFtpOptions() - { - LocalPath = WebPath, - LocalFileName = "solardata.json", - RemoteFileName = "solardata.json" - }; - GraphDataFiles[11] = new FileGenerationFtpOptions() - { - LocalPath = WebPath, - LocalFileName = "sunhours.json", - RemoteFileName = "sunhours.json" - }; - GraphDataFiles[12] = new FileGenerationFtpOptions() - { - LocalPath = WebPath, - LocalFileName = "airquality.json", - RemoteFileName = "airquality.json" - }; - - GraphDataEodFiles = new FileGenerationFtpOptions[8]; - GraphDataEodFiles[0] = new FileGenerationFtpOptions() - { - LocalPath = WebPath, - LocalFileName = "alldailytempdata.json", - RemoteFileName = "alldailytempdata.json" - }; - GraphDataEodFiles[1] = new FileGenerationFtpOptions() - { - LocalPath = WebPath, - LocalFileName = "alldailypressdata.json", - RemoteFileName = "alldailypressdata.json" - }; - GraphDataEodFiles[2] = new FileGenerationFtpOptions() - { - LocalPath = WebPath, - LocalFileName = "alldailywinddata.json", - RemoteFileName = "alldailywinddata.json" - }; - GraphDataEodFiles[3] = new FileGenerationFtpOptions() - { - LocalPath = WebPath, - LocalFileName = "alldailyhumdata.json", - RemoteFileName = "alldailyhumdata.json" - }; - GraphDataEodFiles[4] = new FileGenerationFtpOptions() - { - LocalPath = WebPath, - LocalFileName = "alldailyraindata.json", - RemoteFileName = "alldailyraindata.json" - }; - GraphDataEodFiles[5] = new FileGenerationFtpOptions() - { - LocalPath = WebPath, - LocalFileName = "alldailysolardata.json", - RemoteFileName = "alldailysolardata.json" - }; - GraphDataEodFiles[6] = new FileGenerationFtpOptions() - { - LocalPath = WebPath, - LocalFileName = "alldailydegdaydata.json", - RemoteFileName = "alldailydegdaydata.json" - }; - GraphDataEodFiles[7] = new FileGenerationFtpOptions() - { - LocalPath = WebPath, - LocalFileName = "alltempsumdata.json", - RemoteFileName = "alltempsumdata.json" - }; - - ReadIniFile(); - - // Do we prevent more than one copy of CumulusMX running? - if (ProgramOptions.WarnMultiple && !Program.appMutex.WaitOne(0, false)) - { - LogConsoleMessage("Cumulus is already running - terminating"); - LogConsoleMessage("Program exit"); - LogMessage("Cumulus is already running - terminating"); - LogMessage("Program exit"); - Environment.Exit(1); - } - - // Do we wait for a ping response from a remote host before starting? - if (!string.IsNullOrWhiteSpace(ProgramOptions.StartupPingHost)) - { - var msg1 = $"Waiting for PING reply from {ProgramOptions.StartupPingHost}"; - var msg2 = $"Received PING response from {ProgramOptions.StartupPingHost}, continuing..."; - var msg3 = $"No PING response received in {ProgramOptions.StartupPingEscapeTime} minutes, continuing anyway"; - LogConsoleMessage(msg1); - LogMessage(msg1); - using (var ping = new Ping()) - { - var endTime = DateTime.Now.AddMinutes(ProgramOptions.StartupPingEscapeTime); - PingReply reply = null; - - do - { - try - { - - reply = ping.Send(ProgramOptions.StartupPingHost, 2000); // 2 second timeout - LogMessage($"PING response = {reply.Status}"); - } - catch (Exception e) - { - LogErrorMessage($"PING to {ProgramOptions.StartupPingHost} failed with error: {e.InnerException.Message}"); - } - - if (reply == null || reply.Status != IPStatus.Success) - { - // no response wait 10 seconds before trying again - Thread.Sleep(10000); - // Force a DNS refresh if not an IPv4 address - if (!Utils.ValidateIPv4(ProgramOptions.StartupPingHost)) - { - // catch and ignore IPv6 and invalid hostname for now - try - { - Dns.GetHostEntry(ProgramOptions.StartupPingHost); - } - catch (Exception ex) - { - LogMessage($"PING: Error with DNS refresh - {ex.Message}"); - } - } - } - } while ((reply == null || reply.Status != IPStatus.Success) && DateTime.Now < endTime); - - if (DateTime.Now >= endTime) - { - LogConsoleMessage(msg3); - LogMessage(msg3); - } - else - { - LogConsoleMessage(msg2); - LogMessage(msg2); - } - } - } - else - { - LogMessage("No start-up PING"); - } - - // Do we delay the start of Cumulus MX for a fixed period? - if (ProgramOptions.StartupDelaySecs > 0) - { - // Check uptime - double ts = 0; - if (Platform.Substring(0, 3) == "Win" && UpTime != null) - { - UpTime.NextValue(); - ts = UpTime.NextValue(); - } - else if (File.Exists(@"/proc/uptime")) - { - var text = File.ReadAllText(@"/proc/uptime"); - var strTime = text.Split(' ')[0]; - double.TryParse(strTime, out ts); - } - - // Only delay if the delay uptime is undefined (0), or the current uptime is less than the user specified max uptime to apply the delay - LogMessage($"System uptime = {(int)ts} secs"); - if (ProgramOptions.StartupDelayMaxUptime == 0 || ProgramOptions.StartupDelayMaxUptime > ts) - { - var msg1 = $"Delaying start for {ProgramOptions.StartupDelaySecs} seconds"; - var msg2 = $"Start-up delay complete, continuing..."; - LogConsoleMessage(msg1); - LogMessage(msg1); - Thread.Sleep(ProgramOptions.StartupDelaySecs * 1000); - LogConsoleMessage(msg2); - LogMessage(msg2); - } - else - { - LogMessage("No start-up delay, max uptime exceeded"); - } - } - else - { - LogMessage("No start-up delay - disabled"); - } - - GC.Collect(); - - LogMessage("Data path = " + Datapath); - - AppDomain.CurrentDomain.SetData("DataDirectory", Datapath); - - // Open database (create file if it doesn't exist) - SQLiteOpenFlags flags = SQLiteOpenFlags.Create | SQLiteOpenFlags.ReadWrite; - //LogDB = new SQLiteConnection(dbfile, flags) - //LogDB.CreateTable(); - - // Open diary database (create file if it doesn't exist) - //DiaryDB = new SQLiteConnection(diaryfile, flags, true); // We should be using this - storing datetime as ticks, but historically string storage has been used, so we are stuck with it? - DiaryDB = new SQLiteConnection(diaryfile, flags); - DiaryDB.CreateTable(); - - BackupData(false, DateTime.Now); - - LogMessage("Debug logging is " + (ProgramOptions.DebugLogging ? "enabled" : "disabled")); - LogMessage("Data logging is " + (ProgramOptions.DataLogging ? "enabled" : "disabled")); - LogMessage("FTP logging is " + (FTPlogging ? "enabled" : "disabled")); - LogMessage("Spike logging is " + (ErrorLogSpikeRemoval ? "enabled" : "disabled")); - LogMessage("Logging interval = " + logints[DataLogInterval] + " mins"); - LogMessage("Real time interval = " + RealtimeInterval / 1000 + " secs"); - LogMessage("NoSensorCheck = " + (StationOptions.NoSensorCheck ? "1" : "0")); - - TempFormat = "F" + TempDPlaces; - WindFormat = "F" + WindDPlaces; - WindAvgFormat = "F" + WindAvgDPlaces; - RainFormat = "F" + RainDPlaces; - PressFormat = "F" + PressDPlaces; - HumFormat = "F" + HumDPlaces; - UVFormat = "F" + UVDPlaces; - SunFormat = "F" + SunshineDPlaces; - ETFormat = "F" + (RainDPlaces + 1); - WindRunFormat = "F" + WindRunDPlaces; - TempTrendFormat = "+0.0;-0.0;0"; - AirQualityFormat = "F" + AirQualityDPlaces; - - SetMonthlySqlCreateString(); - - SetDayfileSqlCreateString(); - - SetRealtimeSqlCreateString(); - - if (Sslftp == FtpProtocols.FTP || Sslftp == FtpProtocols.FTPS) - { - if (ActiveFTPMode) - { - RealtimeFTP.DataConnectionType = FtpDataConnectionType.PORT; - } - else if (DisableFtpsEPSV) - { - RealtimeFTP.DataConnectionType = FtpDataConnectionType.PASV; - } - - if (Sslftp == FtpProtocols.FTPS) - { - RealtimeFTP.EncryptionMode = DisableFtpsExplicit ? FtpEncryptionMode.Implicit : FtpEncryptionMode.Explicit; - RealtimeFTP.DataConnectionEncryption = true; - RealtimeFTP.ValidateAnyCertificate = true; - // b3045 - switch from System.Net.Ftp.Client to FluentFTP allows us to specifiy protocols - RealtimeFTP.SslProtocols = SslProtocols.Default | SslProtocols.Tls11 | SslProtocols.Tls12; - } - } - - ReadStringsFile(); - - SetUpHttpProxy(); - - if (MonthlyMySqlEnabled) - { - MonthlyMySqlConn.ConnectionString = MySqlConnSettings.ToString(); - - SetStartOfMonthlyInsertSQL(); - } - - if (DayfileMySqlEnabled) - { - SetStartOfDayfileInsertSQL(); - } - - if (RealtimeMySqlEnabled) - { - RealtimeSqlConn.ConnectionString = MySqlConnSettings.ToString(); - SetStartOfRealtimeInsertSQL(); - } - - - CustomMysqlSecondsConn.ConnectionString = MySqlConnSettings.ToString(); - customMysqlSecondsTokenParser.OnToken += TokenParserOnToken; - CustomMysqlSecondsCommand.Connection = CustomMysqlSecondsConn; - CustomMysqlSecondsTimer = new Timer { Interval = CustomMySqlSecondsInterval * 1000 }; - CustomMysqlSecondsTimer.Elapsed += CustomMysqlSecondsTimerTick; - CustomMysqlSecondsTimer.AutoReset = true; - - CustomMysqlMinutesConn.ConnectionString = MySqlConnSettings.ToString(); - customMysqlMinutesTokenParser.OnToken += TokenParserOnToken; - CustomMysqlMinutesCommand.Connection = CustomMysqlMinutesConn; - - CustomMysqlRolloverConn.ConnectionString = MySqlConnSettings.ToString(); - customMysqlRolloverTokenParser.OnToken += TokenParserOnToken; - CustomMysqlRolloverCommand.Connection = CustomMysqlRolloverConn; - - CustomHttpSecondsTimer = new Timer { Interval = CustomHttpSecondsInterval * 1000 }; - CustomHttpSecondsTimer.Elapsed += CustomHttpSecondsTimerTick; - CustomHttpSecondsTimer.AutoReset = true; - - customHttpSecondsTokenParser.OnToken += TokenParserOnToken; - customHttpMinutesTokenParser.OnToken += TokenParserOnToken; - customHttpRolloverTokenParser.OnToken += TokenParserOnToken; - - if (SmtpOptions.Enabled) - { - emailer = new EmailSender(this); - } - - DoSunriseAndSunset(); - DoMoonPhase(); - MoonAge = MoonriseMoonset.MoonAge(); - DoMoonImage(); - - LogMessage("Station type: " + (StationType == -1 ? "Undefined" : StationDesc[StationType])); - - SetupUnitText(); - - LogMessage($"Units.Wind={Units.WindText} RainUnit={Units.RainText} TempUnit={Units.TempText} PressureUnit={Units.PressText}"); - LogMessage($"YTDRain={YTDrain:F3} Year={YTDrainyear}"); - LogMessage($"RainDayThreshold={RainDayThreshold:F3}"); - LogMessage($"Roll over hour={RolloverHour}"); - - LogOffsetsMultipliers(); - - LogPrimaryAqSensor(); - - // initialise the alarms - DataStoppedAlarm.cumulus = this; - BatteryLowAlarm.cumulus = this; - SensorAlarm.cumulus = this; - SpikeAlarm.cumulus = this; - HighWindAlarm.cumulus = this; - HighWindAlarm.Units = Units.WindText; - HighGustAlarm.cumulus = this; - HighGustAlarm.Units = Units.WindText; - HighRainRateAlarm.cumulus = this; - HighRainRateAlarm.Units = Units.RainTrendText; - HighRainTodayAlarm.cumulus = this; - HighRainTodayAlarm.Units = Units.RainText; - PressChangeAlarm.cumulus = this; - PressChangeAlarm.Units = Units.PressTrendText; - HighPressAlarm.cumulus = this; - HighPressAlarm.Units = Units.PressText; - LowPressAlarm.cumulus = this; - LowPressAlarm.Units = Units.PressText; - TempChangeAlarm.cumulus = this; - TempChangeAlarm.Units = Units.TempTrendText; - HighTempAlarm.cumulus = this; - HighTempAlarm.Units = Units.TempText; - LowTempAlarm.cumulus = this; - LowTempAlarm.Units = Units.TempText; - UpgradeAlarm.cumulus = this; - HttpUploadAlarm.cumulus = this; - MySqlUploadAlarm.cumulus = this; - - GetLatestVersion(); - - LogMessage("Cumulus Starting"); - - // switch off logging from Unosquare.Swan which underlies embedIO - Unosquare.Swan.Terminal.Settings.DisplayLoggingMessageType = Unosquare.Swan.LogMessageType.Fatal; - - httpServer = new WebServer(HTTPport, RoutingStrategy.Wildcard); - - var assemblyPath = Path.GetDirectoryName(typeof(Program).Assembly.Location); - var htmlRootPath = Path.Combine(assemblyPath, "interface"); - - LogMessage("HTML root path = " + htmlRootPath); - - httpServer.RegisterModule(new StaticFilesModule(htmlRootPath, new Dictionary() { { "Cache-Control", "max-age=300" } })); - httpServer.Module().UseRamCache = true; - - // Set up the API web server - // Some APi functions require the station, so set them after station initialisation - Api.Setup(httpServer); - Api.programSettings = new ProgramSettings(this); - Api.stationSettings = new StationSettings(this); - Api.internetSettings = new InternetSettings(this); - Api.thirdpartySettings = new ThirdPartySettings(this); - Api.extraSensorSettings = new ExtraSensorSettings(this); - Api.calibrationSettings = new CalibrationSettings(this); - Api.noaaSettings = new NOAASettings(this); - Api.alarmSettings = new AlarmSettings(this); - Api.mySqlSettings = new MysqlSettings(this); - Api.dataEditor = new DataEditor(this); - Api.tagProcessor = new ApiTagProcessor(this); - - // Set up the Web Socket server - WebSocket.Setup(httpServer, this); - - httpServer.RunAsync(); - - LogConsoleMessage("Cumulus running at: " + httpServer.Listener.Prefixes.First()); - LogConsoleMessage(" (Replace * with any IP address on this machine, or localhost)"); - LogConsoleMessage(" Open the admin interface by entering this URL in a browser."); - - LogDebugMessage("Lock: Cumulus waiting for the lock"); - syncInit.Wait(); - LogDebugMessage("Lock: Cumulus has lock"); - - LogMessage("Opening station"); - - switch (StationType) - { - case StationTypes.FineOffset: - case StationTypes.FineOffsetSolar: - Manufacturer = EW; - station = new FOStation(this); - break; - case StationTypes.VantagePro: - case StationTypes.VantagePro2: - Manufacturer = DAVIS; - station = new DavisStation(this); - break; - case StationTypes.WMR928: - Manufacturer = OREGON; - station = new WMR928Station(this); - break; - case StationTypes.WM918: - Manufacturer = OREGON; - station = new WM918Station(this); - break; - case StationTypes.WS2300: - Manufacturer = LACROSSE; - station = new WS2300Station(this); - break; - case StationTypes.WMR200: - Manufacturer = OREGONUSB; - station = new WMR200Station(this); - break; - case StationTypes.Instromet: - Manufacturer = INSTROMET; - station = new ImetStation(this); - break; - case StationTypes.WMR100: - Manufacturer = OREGONUSB; - station = new WMR100Station(this); - break; - case StationTypes.EasyWeather: - Manufacturer = EW; - station = new EasyWeather(this); - station.LoadLastHoursFromDataLogs(DateTime.Now); - break; - case StationTypes.WLL: - Manufacturer = DAVIS; - station = new DavisWllStation(this); - break; - case StationTypes.GW1000: - Manufacturer = ECOWITT; - station = new GW1000Station(this); - break; - default: - LogConsoleMessage("Station type not set"); - LogMessage("Station type not set"); - break; - } - - if (station != null) - { - Api.Station = station; - Api.stationSettings.SetStation(station); - Api.dataEditor.SetStation(station); - - LogMessage("Creating extra sensors"); - if (AirLinkInEnabled) - { - airLinkDataIn = new AirLinkData(); - airLinkIn = new DavisAirLink(this, true, station); - } - if (AirLinkOutEnabled) - { - airLinkDataOut = new AirLinkData(); - airLinkOut = new DavisAirLink(this, false, station); - } - - webtags = new WebTags(this, station); - webtags.InitialiseWebtags(); - - Api.dataEditor.SetWebTags(webtags); - Api.tagProcessor.SetWebTags(webtags); - - tokenParser = new TokenParser(); - tokenParser.OnToken += TokenParserOnToken; - - realtimeTokenParser = new TokenParser(); - realtimeTokenParser.OnToken += TokenParserOnToken; - - RealtimeTimer.Interval = RealtimeInterval; - RealtimeTimer.Elapsed += RealtimeTimerTick; - RealtimeTimer.AutoReset = true; - - SetFtpLogging(FTPlogging); - - WundTimer.Elapsed += WundTimerTick; - AwekasTimer.Elapsed += AwekasTimerTick; - WebTimer.Elapsed += WebTimerTick; - - xapsource = "sanday.cumulus." + Environment.MachineName; - - xapHeartbeat = "xap-hbeat\n{\nv=12\nhop=1\nuid=FF" + xapUID + "00\nclass=xap-hbeat.alive\nsource=" + xapsource + "\ninterval=60\n}"; - - if (xapEnabled) - { - Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - IPEndPoint iep1 = new IPEndPoint(IPAddress.Broadcast, xapPort); - - byte[] data = Encoding.ASCII.GetBytes(xapHeartbeat); - - sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1); - sock.SendTo(data, iep1); - sock.Close(); - } - - if (MQTT.EnableDataUpdate || MQTT.EnableInterval) - { - MqttPublisher.Setup(this); - - if (MQTT.EnableInterval) - { - MQTTTimer.Elapsed += MQTTTimerTick; - } - } - - InitialiseRG11(); - - if (station.timerStartNeeded) - { - StartTimersAndSensors(); - } - - if ((StationType == StationTypes.WMR100) || (StationType == StationTypes.EasyWeather) || (Manufacturer == OREGON)) - { - station.StartLoop(); - } - - // If enabled generate the daily graph data files, and upload at first opportunity - LogDebugMessage("Generating the daily graph data files"); - station.CreateEodGraphDataFiles(); - } - - LogDebugMessage("Lock: Cumulus releasing the lock"); - syncInit.Release(); - } - - internal void SetUpHttpProxy() - { - if (!string.IsNullOrEmpty(HTTPProxyName)) - { - WUhttpHandler.Proxy = new WebProxy(HTTPProxyName, HTTPProxyPort); - WUhttpHandler.UseProxy = true; - - PWShttpHandler.Proxy = new WebProxy(HTTPProxyName, HTTPProxyPort); - PWShttpHandler.UseProxy = true; - - WOWhttpHandler.Proxy = new WebProxy(HTTPProxyName, HTTPProxyPort); - WOWhttpHandler.UseProxy = true; - - AwekashttpHandler.Proxy = new WebProxy(HTTPProxyName, HTTPProxyPort); - AwekashttpHandler.UseProxy = true; - - WindyhttpHandler.Proxy = new WebProxy(HTTPProxyName, HTTPProxyPort); - WindyhttpHandler.UseProxy = true; - - WCloudhttpHandler.Proxy = new WebProxy(HTTPProxyName, HTTPProxyPort); - WCloudhttpHandler.UseProxy = true; - - customHttpSecondsHandler.Proxy = new WebProxy(HTTPProxyName, HTTPProxyPort); - customHttpSecondsHandler.UseProxy = true; - - customHttpMinutesHandler.Proxy = new WebProxy(HTTPProxyName, HTTPProxyPort); - customHttpMinutesHandler.UseProxy = true; - - customHttpRolloverHandler.Proxy = new WebProxy(HTTPProxyName, HTTPProxyPort); - customHttpRolloverHandler.UseProxy = true; - - if (!string.IsNullOrEmpty(HTTPProxyUser)) - { - WUhttpHandler.Credentials = new NetworkCredential(HTTPProxyUser, HTTPProxyPassword); - PWShttpHandler.Credentials = new NetworkCredential(HTTPProxyUser, HTTPProxyPassword); - WOWhttpHandler.Credentials = new NetworkCredential(HTTPProxyUser, HTTPProxyPassword); - AwekashttpHandler.Credentials = new NetworkCredential(HTTPProxyUser, HTTPProxyPassword); - WindyhttpHandler.Credentials = new NetworkCredential(HTTPProxyUser, HTTPProxyPassword); - WCloudhttpHandler.Credentials = new NetworkCredential(HTTPProxyUser, HTTPProxyPassword); - customHttpSecondsHandler.Credentials = new NetworkCredential(HTTPProxyUser, HTTPProxyPassword); - customHttpMinutesHandler.Credentials = new NetworkCredential(HTTPProxyUser, HTTPProxyPassword); - customHttpRolloverHandler.Credentials = new NetworkCredential(HTTPProxyUser, HTTPProxyPassword); - } - } - } - - private void CustomHttpSecondsTimerTick(object sender, ElapsedEventArgs e) - { - if (!station.DataStopped) - CustomHttpSecondsUpdate(); - } - - internal void SetStartOfRealtimeInsertSQL() - { - StartOfRealtimeInsertSQL = "INSERT IGNORE INTO " + MySqlRealtimeTable + " (" + - "LogDateTime,temp,hum,dew,wspeed,wlatest,bearing,rrate,rfall,press," + - "currentwdir,beaufortnumber,windunit,tempunitnodeg,pressunit,rainunit," + - "windrun,presstrendval,rmonth,ryear,rfallY,intemp,inhum,wchill,temptrend," + - "tempTH,TtempTH,tempTL,TtempTL,windTM,TwindTM,wgustTM,TwgustTM," + - "pressTH,TpressTH,pressTL,TpressTL,version,build,wgust,heatindex,humidex," + - "UV,ET,SolarRad,avgbearing,rhour,forecastnumber,isdaylight,SensorContactLost," + - "wdir,cloudbasevalue,cloudbaseunit,apptemp,SunshineHours,CurrentSolarMax,IsSunny," + - "FeelsLike)"; - } - - internal void SetRealtimeSqlCreateString() - { - CreateRealtimeSQL = "CREATE TABLE " + MySqlRealtimeTable + " (LogDateTime DATETIME NOT NULL," + - "temp decimal(4," + TempDPlaces + ") NOT NULL," + - "hum decimal(4," + HumDPlaces + ") NOT NULL," + - "dew decimal(4," + TempDPlaces + ") NOT NULL," + - "wspeed decimal(4," + WindDPlaces + ") NOT NULL," + - "wlatest decimal(4," + WindDPlaces + ") NOT NULL," + - "bearing VARCHAR(3) NOT NULL," + - "rrate decimal(4," + RainDPlaces + ") NOT NULL," + - "rfall decimal(4," + RainDPlaces + ") NOT NULL," + - "press decimal(6," + PressDPlaces + ") NOT NULL," + - "currentwdir varchar(3) NOT NULL," + - "beaufortnumber varchar(2) NOT NULL," + - "windunit varchar(4) NOT NULL," + - "tempunitnodeg varchar(1) NOT NULL," + - "pressunit varchar(3) NOT NULL," + - "rainunit varchar(2) NOT NULL," + - "windrun decimal(4," + WindRunDPlaces + ") NOT NULL," + - "presstrendval varchar(6) NOT NULL," + - "rmonth decimal(4," + RainDPlaces + ") NOT NULL," + - "ryear decimal(4," + RainDPlaces + ") NOT NULL," + - "rfallY decimal(4," + RainDPlaces + ") NOT NULL," + - "intemp decimal(4," + TempDPlaces + ") NOT NULL," + - "inhum decimal(4," + HumDPlaces + ") NOT NULL," + - "wchill decimal(4," + TempDPlaces + ") NOT NULL," + - "temptrend varchar(5) NOT NULL," + - "tempTH decimal(4," + TempDPlaces + ") NOT NULL," + - "TtempTH varchar(5) NOT NULL," + - "tempTL decimal(4," + TempDPlaces + ") NOT NULL," + - "TtempTL varchar(5) NOT NULL," + - "windTM decimal(4," + WindDPlaces + ") NOT NULL," + - "TwindTM varchar(5) NOT NULL," + - "wgustTM decimal(4," + WindDPlaces + ") NOT NULL," + - "TwgustTM varchar(5) NOT NULL," + - "pressTH decimal(6," + PressDPlaces + ") NOT NULL," + - "TpressTH varchar(5) NOT NULL," + - "pressTL decimal(6," + PressDPlaces + ") NOT NULL," + - "TpressTL varchar(5) NOT NULL," + - "version varchar(8) NOT NULL," + - "build varchar(5) NOT NULL," + - "wgust decimal(4," + WindDPlaces + ") NOT NULL," + - "heatindex decimal(4," + TempDPlaces + ") NOT NULL," + - "humidex decimal(4," + TempDPlaces + ") NOT NULL," + - "UV decimal(3," + UVDPlaces + ") NOT NULL," + - "ET decimal(4," + RainDPlaces + ") NOT NULL," + - "SolarRad decimal(5,1) NOT NULL," + - "avgbearing varchar(3) NOT NULL," + - "rhour decimal(4," + RainDPlaces + ") NOT NULL," + - "forecastnumber varchar(2) NOT NULL," + - "isdaylight varchar(1) NOT NULL," + - "SensorContactLost varchar(1) NOT NULL," + - "wdir varchar(3) NOT NULL," + - "cloudbasevalue varchar(5) NOT NULL," + - "cloudbaseunit varchar(2) NOT NULL," + - "apptemp decimal(4," + TempDPlaces + ") NOT NULL," + - "SunshineHours decimal(3," + SunshineDPlaces + ") NOT NULL," + - "CurrentSolarMax decimal(5,1) NOT NULL," + - "IsSunny varchar(1) NOT NULL," + - "FeelsLike decimal(4," + TempDPlaces + ") NOT NULL," + - "PRIMARY KEY (LogDateTime)) COMMENT = \"Realtime log\""; - } - - internal void SetStartOfDayfileInsertSQL() - { - StartOfDayfileInsertSQL = "INSERT IGNORE INTO " + MySqlDayfileTable + " (" + - "LogDate,HighWindGust,HWindGBear,THWindG,MinTemp,TMinTemp,MaxTemp,TMaxTemp," + - "MinPress,TMinPress,MaxPress,TMaxPress,MaxRainRate,TMaxRR,TotRainFall,AvgTemp," + - "TotWindRun,HighAvgWSpeed,THAvgWSpeed,LowHum,TLowHum,HighHum,THighHum,TotalEvap," + - "HoursSun,HighHeatInd,THighHeatInd,HighAppTemp,THighAppTemp,LowAppTemp,TLowAppTemp," + - "HighHourRain,THighHourRain,LowWindChill,TLowWindChill,HighDewPoint,THighDewPoint," + - "LowDewPoint,TLowDewPoint,DomWindDir,HeatDegDays,CoolDegDays,HighSolarRad," + - "THighSolarRad,HighUV,THighUV,HWindGBearSym,DomWindDirSym," + - "MaxFeelsLike,TMaxFeelsLike,MinFeelsLike,TMinFeelsLike,MaxHumidex,TMaxHumidex)"; - } - - internal void SetStartOfMonthlyInsertSQL() - { - StartOfMonthlyInsertSQL = "INSERT IGNORE INTO " + MySqlMonthlyTable + " (" + - "LogDateTime,Temp,Humidity,Dewpoint,Windspeed,Windgust,Windbearing,RainRate,TodayRainSoFar," + - "Pressure,Raincounter,InsideTemp,InsideHumidity,LatestWindGust,WindChill,HeatIndex,UVindex," + - "SolarRad,Evapotrans,AnnualEvapTran,ApparentTemp,MaxSolarRad,HrsSunShine,CurrWindBearing," + - "RG11rain,RainSinceMidnight,WindbearingSym,CurrWindBearingSym,FeelsLike,Humidex)"; - } - - internal void SetupUnitText() - { - switch (Units.Temp) - { - case 0: - Units.TempText = "°C"; - Units.TempTrendText = "°C/hr"; - break; - case 1: - Units.TempText = "°F"; - Units.TempTrendText = "°F/hr"; - break; - } - - switch (Units.Rain) - { - case 0: - Units.RainText = "mm"; - Units.RainTrendText = "mm/hr"; - break; - case 1: - Units.RainText = "in"; - Units.RainTrendText = "in/hr"; - break; - } - - switch (Units.Press) - { - case 0: - Units.PressText = "mb"; - Units.PressTrendText = "mb/hr"; - break; - case 1: - Units.PressText = "hPa"; - Units.PressTrendText = "hPa/hr"; - break; - case 2: - Units.PressText = "in"; - Units.PressTrendText = "in/hr"; - break; - } - - switch (Units.Wind) - { - case 0: - Units.WindText = "m/s"; - Units.WindRunText = "km"; - break; - case 1: - Units.WindText = "mph"; - Units.WindRunText = "miles"; - break; - case 2: - Units.WindText = "km/h"; - Units.WindRunText = "km"; - break; - case 3: - Units.WindText = "kts"; - Units.WindRunText = "nm"; - break; - } - } - - // If the temperature units are changed, reset NOAA thresholds to defaults - internal void ChangeTempUnits() - { - SetupUnitText(); - - NOAAheatingthreshold = Units.Temp == 0 ? 18.3 : 65; - NOAAcoolingthreshold = Units.Temp == 0 ? 18.3 : 65; - NOAAmaxtempcomp1 = Units.Temp == 0 ? 27 : 80; - NOAAmaxtempcomp2 = Units.Temp == 0 ? 0 : 32; - NOAAmintempcomp1 = Units.Temp == 0 ? 0 : 32; - NOAAmintempcomp2 = Units.Temp == 0 ? -18 : 0; - - ChillHourThreshold = Units.Temp == 0 ? 7 : 45; - - GrowingBase1 = Units.Temp == 0 ? 5.0 : 40.0; - GrowingBase2 = Units.Temp == 0 ? 10.0 : 50.0; - - TempChangeAlarm.Units = Units.TempTrendText; - HighTempAlarm.Units = Units.TempText; - LowTempAlarm.Units = Units.TempText; - } - - internal void ChangeRainUnits() - { - SetupUnitText(); - - NOAAraincomp1 = Units.Rain == 0 ? 0.2 : 0.01; - NOAAraincomp2 = Units.Rain == 0 ? 2 : 0.1; - NOAAraincomp3 = Units.Rain == 0 ? 20 : 1; - - HighRainRateAlarm.Units = Units.RainTrendText; - HighRainTodayAlarm.Units = Units.RainText; - } - - internal void ChangePressureUnits() - { - SetupUnitText(); - - FCPressureThreshold = Units.Press == 2 ? 0.00295333727 : 0.1; - - PressChangeAlarm.Units = Units.PressTrendText; - HighPressAlarm.Units = Units.PressText; - LowPressAlarm.Units = Units.PressText; - } - - internal void ChangeWindUnits() - { - SetupUnitText(); - - HighWindAlarm.Units = Units.WindText; - HighGustAlarm.Units = Units.WindText; - } - - public void SetFtpLogging(bool isSet) - { - try - { - FtpTrace.RemoveListener(FtpTraceListener); - } - catch - { - // ignored - } - - if (isSet) - { - FtpTrace.AddListener(FtpTraceListener); - FtpTrace.FlushOnWrite = true; - } - } - - - /* - private string LocalIPAddress() - { - IPHostEntry host; - string localIP = ""; - host = Dns.GetHostEntry(Dns.GetHostName()); - foreach (IPAddress ip in host.AddressList) - { - if (ip.AddressFamily == AddressFamily.InterNetwork) - { - localIP = ip.ToString(); - break; - } - } - return localIP; - } - */ - - /* - private void OnDisconnect(UserContext context) - { - LogDebugMessage("Disconnect From : " + context.ClientAddress.ToString()); - - foreach (var conn in WSconnections.ToList()) - { - if (context.ClientAddress.ToString().Equals(conn.ClientAddress.ToString())) - { - WSconnections.Remove(conn); - } - } - } - - private void OnConnected(UserContext context) - { - LogDebugMessage("Connected From : " + context.ClientAddress.ToString()); - } - - private void OnConnect(UserContext context) - { - LogDebugMessage("OnConnect From : " + context.ClientAddress.ToString()); - WSconnections.Add(context); - } - - internal List WSconnections = new List(); - - private void OnSend(UserContext context) - { - LogDebugMessage("OnSend From : " + context.ClientAddress.ToString()); - } - - private void OnReceive(UserContext context) - { - LogDebugMessage("WS receive : " + context.DataFrame.ToString()); - } -*/ - private void InitialiseRG11() - { - if (RG11Enabled && RG11Port.Length > 0) - { - cmprtRG11 = new SerialPort(RG11Port, 9600, Parity.None, 8, StopBits.One) { Handshake = Handshake.None, RtsEnable = true, DtrEnable = true }; - - cmprtRG11.PinChanged += RG11StateChange; - } - - if (RG11Enabled2 && RG11Port2.Length > 0 && (!RG11Port2.Equals(RG11Port))) - { - // a second RG11 is in use, using a different com port - cmprt2RG11 = new SerialPort(RG11Port2, 9600, Parity.None, 8, StopBits.One) { Handshake = Handshake.None, RtsEnable = true, DtrEnable = true }; - - cmprt2RG11.PinChanged += RG11StateChange; - } - } - - private void RG11StateChange(object sender, SerialPinChangedEventArgs e) - { - bool isDSR = e.EventType == SerialPinChange.DsrChanged; - bool isCTS = e.EventType == SerialPinChange.CtsChanged; - - // Is this a trigger that the first RG11 is configured for? - bool isDevice1 = (((SerialPort)sender).PortName == RG11Port) && ((isDSR && RG11DTRmode) || (isCTS && !RG11DTRmode)); - // Is this a trigger that the second RG11 is configured for? - bool isDevice2 = (((SerialPort)sender).PortName == RG11Port2) && ((isDSR && RG11DTRmode2) || (isCTS && !RG11DTRmode2)); - - // is the pin on or off? - bool isOn = (isDSR && ((SerialPort)sender).DsrHolding) || (isCTS && ((SerialPort)sender).CtsHolding); - - if (isDevice1) - { - if (RG11TBRmode) - { - if (isOn) - { - // relay closed, record a 'tip' - station.RG11RainToday += RG11tipsize; - } - } - else - { - station.IsRaining = isOn; - } - } - else if (isDevice2) - { - if (RG11TBRmode2) - { - if (isOn) - { - // relay closed, record a 'tip' - station.RG11RainToday += RG11tipsize; - } - } - else - { - station.IsRaining = isOn; - } - } - } - - private void APRSTimerTick(object sender, ElapsedEventArgs e) - { - if (!string.IsNullOrEmpty(APRS.ID)) - { - station.UpdateAPRS(); - } - } - - private void WebTimerTick(object sender, ElapsedEventArgs e) - { - if (station.DataStopped) - { - // No data coming in, do not do anything else - return; - } - - if (WebUpdating == 1) - { - LogMessage("Warning, previous web update is still in progress, first chance, skipping this interval"); - WebUpdating++; - } - else if (WebUpdating >= 2) - { - LogMessage("Warning, previous web update is still in progress, second chance, aborting connection"); - if (ftpThread.ThreadState == System.Threading.ThreadState.Running) - ftpThread.Abort(); - LogMessage("Trying new web update"); - WebUpdating = 1; - ftpThread = new Thread(DoHTMLFiles) { IsBackground = true }; - ftpThread.Start(); - } - else - { - WebUpdating = 1; - ftpThread = new Thread(DoHTMLFiles) { IsBackground = true }; - ftpThread.Start(); - } - } - - internal async void UpdateTwitter() - { - if (station.DataStopped) - { - // No data coming in, do nothing - return; - } - - LogDebugMessage("Starting Twitter update"); - var auth = new XAuthAuthorizer - { - CredentialStore = new XAuthCredentials { ConsumerKey = twitterKey, ConsumerSecret = twitterSecret, UserName = Twitter.ID, Password = Twitter.PW } - }; - - if (Twitter.OauthToken == "unknown") - { - // need to get tokens using xauth - LogDebugMessage("Obtaining Twitter tokens"); - await auth.AuthorizeAsync(); - - Twitter.OauthToken = auth.CredentialStore.OAuthToken; - Twitter.OauthTokenSecret = auth.CredentialStore.OAuthTokenSecret; - //LogDebugMessage("Token=" + TwitterOauthToken); - //LogDebugMessage("TokenSecret=" + TwitterOauthTokenSecret); - LogDebugMessage("Tokens obtained"); - } - else - { - auth.CredentialStore.OAuthToken = Twitter.OauthToken; - auth.CredentialStore.OAuthTokenSecret = Twitter.OauthTokenSecret; - } - - using (var twitterCtx = new TwitterContext(auth)) - { - StringBuilder status = new StringBuilder(1024); - - if (File.Exists(TwitterTxtFile)) - { - // use twitter.txt file - LogDebugMessage("Using twitter.txt file"); - var twitterTokenParser = new TokenParser(); - var utf8WithoutBom = new UTF8Encoding(false); - var encoding = utf8WithoutBom; - twitterTokenParser.Encoding = encoding; - twitterTokenParser.SourceFile = TwitterTxtFile; - twitterTokenParser.OnToken += TokenParserOnToken; - status.Append(twitterTokenParser); - } - else - { - // default message - status.Append($"Wind {station.WindAverage.ToString(WindAvgFormat)} {Units.WindText} {station.AvgBearingText}."); - status.Append($" Barometer {station.Pressure.ToString(PressFormat)} {Units.PressText}, {station.Presstrendstr}."); - status.Append($" Temperature {station.OutdoorTemperature.ToString(TempFormat)} {Units.TempText}."); - status.Append($" Rain today {station.RainToday.ToString(RainFormat)}{Units.RainText}."); - status.Append($" Humidity {station.OutdoorHumidity}%"); - } - - LogDebugMessage($"Updating Twitter: {status}"); - - var statusStr = status.ToString(); - - try - { - Status tweet; - - if (Twitter.SendLocation) - { - tweet = await twitterCtx.TweetAsync(statusStr, (decimal)Latitude, (decimal)Longitude); - } - else - { - tweet = await twitterCtx.TweetAsync(statusStr); - } - - if (tweet == null) - { - LogDebugMessage("Null Twitter response"); - } - else - { - LogDebugMessage($"Status returned: ({tweet.StatusID}) {tweet.User.Name}, {tweet.Text}, {tweet.CreatedAt}"); - } - - HttpUploadAlarm.Triggered = false; - } - catch (Exception ex) - { - LogMessage($"UpdateTwitter: {ex.Message}"); - HttpUploadAlarm.Triggered = true; - } - //if (tweet != null) - // Console.WriteLine("Status returned: " + "(" + tweet.StatusID + ")" + tweet.User.Name + ", " + tweet.Text + "\n"); - } - } - - private void WundTimerTick(object sender, ElapsedEventArgs e) - { - if (!string.IsNullOrWhiteSpace(Wund.ID)) - UpdateWunderground(DateTime.Now); - } - - - private void AwekasTimerTick(object sender, ElapsedEventArgs e) - { - if (!string.IsNullOrWhiteSpace(AWEKAS.ID)) - UpdateAwekas(DateTime.Now); - } - - public void MQTTTimerTick(object sender, ElapsedEventArgs e) - { - if (!station.DataStopped) - MqttPublisher.UpdateMQTTfeed("Interval"); - } - - /* - * 15/1020 - This does nothing! - public void AirLinkTimerTick(object sender, ElapsedEventArgs e) - { - if (AirLinkInEnabled && airLinkIn != null) - { - airLinkIn.GetAirQuality(); - } - if (AirLinkOutEnabled && airLinkOut != null) - { - airLinkOut.GetAirQuality(); - } - } - */ - - internal async void UpdateWunderground(DateTime timestamp) - { - if (Wund.Updating || station.DataStopped) - { - // No data coming in, do not do anything - return; - } - - Wund.Updating = true; - - string pwstring; - string URL = station.GetWundergroundURL(out pwstring, timestamp, false); - - string starredpwstring = "&PASSWORD=" + new string('*', Wund.PW.Length); - - string logUrl = URL.Replace(pwstring, starredpwstring); - if (!Wund.RapidFireEnabled) - { - LogDebugMessage("Wunderground: URL = " + logUrl); - } - - try - { - HttpResponseMessage response = await WUhttpClient.GetAsync(URL); - var responseBodyAsText = await response.Content.ReadAsStringAsync(); - if (!Wund.RapidFireEnabled || response.StatusCode != HttpStatusCode.OK) - { - LogDebugMessage("Wunderground: Response = " + response.StatusCode + ": " + responseBodyAsText); - HttpUploadAlarm.Triggered = true; - } - else - { - HttpUploadAlarm.Triggered = false; - } - } - catch (Exception ex) - { - LogMessage("Wunderground: ERROR - " + ex.Message); - HttpUploadAlarm.Triggered = true; - } - finally - { - Wund.Updating = false; - } - } - - internal async void UpdateWindy(DateTime timestamp) - { - if (Windy.Updating || station.DataStopped) - { - // No data coming in, do not do anything - return; - } - - Windy.Updating = true; - - string apistring; - string url = station.GetWindyURL(out apistring, timestamp); - string logUrl = url.Replace(apistring, "<>"); - - LogDebugMessage("Windy: URL = " + logUrl); - - try - { - HttpResponseMessage response = await WindyhttpClient.GetAsync(url); - var responseBodyAsText = await response.Content.ReadAsStringAsync(); - LogDebugMessage("Windy: Response = " + response.StatusCode + ": " + responseBodyAsText); - if (response.StatusCode != HttpStatusCode.OK) - { - HttpUploadAlarm.Triggered = true; - } - else - { - HttpUploadAlarm.Triggered = false; - } - } - catch (Exception ex) - { - LogMessage("Windy: ERROR - " + ex.Message); - HttpUploadAlarm.Triggered = true; - } - finally - { - Windy.Updating = false; - } - } - - internal async void UpdateWindGuru(DateTime timestamp) - { - if (WindGuru.Updating || station.DataStopped) - { - // No data coming in, do not do anything - return; - } - - WindGuru.Updating = true; - - string apistring; - string url = station.GetWindGuruURL(out apistring, timestamp); - string logUrl = url.Replace(apistring, "<>"); - - LogDebugMessage("WindGuru: URL = " + logUrl); - - try - { - HttpResponseMessage response = await WindGuruhttpClient.GetAsync(url); - var responseBodyAsText = await response.Content.ReadAsStringAsync(); - LogDebugMessage("WindGuru: " + response.StatusCode + ": " + responseBodyAsText); - if (response.StatusCode != HttpStatusCode.OK) - { - HttpUploadAlarm.Triggered = true; - } - else - { - HttpUploadAlarm.Triggered = false; - } - } - catch (Exception ex) - { - LogDebugMessage("WindGuru: ERROR - " + ex.Message); - HttpUploadAlarm.Triggered = true; - } - finally - { - WindGuru.Updating = false; - } - } - - - internal async void UpdateAwekas(DateTime timestamp) - { - if (AWEKAS.Updating || station.DataStopped) - { - // No data coming in, do not do anything - return; - } - - AWEKAS.Updating = true; - - string pwstring; - string url = station.GetAwekasURLv4(out pwstring, timestamp); - - string starredpwstring = ""; - - string logUrl = url.Replace(pwstring, starredpwstring); - - LogDebugMessage("AWEKAS: URL = " + logUrl); - - try - { - using (HttpResponseMessage response = await AwekashttpClient.GetAsync(url)) - { - var responseBodyAsText = await response.Content.ReadAsStringAsync(); - LogDebugMessage("AWEKAS Response code = " + response.StatusCode); - LogDataMessage("AWEKAS: Response text = " + responseBodyAsText); - - if (response.StatusCode != HttpStatusCode.OK) - { - HttpUploadAlarm.Triggered = true; - } - else - { - HttpUploadAlarm.Triggered = false; - } - //var respJson = JsonConvert.DeserializeObject(responseBodyAsText); - var respJson = JsonSerializer.DeserializeFromString(responseBodyAsText); - - // Check the status response - if (respJson.status == 2) - { - LogDebugMessage("AWEKAS: Data stored OK"); - } - else if (respJson.status == 1) - { - LogMessage("AWEKAS: Data PARIALLY stored"); - // TODO: Check errors and disabled - } - else if (respJson.status == 0) // Authenication error or rate limited - { - if (respJson.minuploadtime > 0 && respJson.authentication == 0) - { - LogMessage("AWEKAS: Authentication error"); - if (AWEKAS.Interval < 300) - { - AWEKAS.RateLimited = true; - AWEKAS.OriginalInterval = AWEKAS.Interval; - AWEKAS.Interval = 300; - AwekasTimer.Enabled = false; - AWEKAS.SynchronisedUpdate = true; - LogMessage("AWEKAS: Temporarily increasing AWEKAS upload interval to 300 seconds due to authenication error"); - } - } - else if (respJson.minuploadtime == 0) - { - LogMessage("AWEKAS: Too many requests, rate limited"); - // AWEKAS PLus allows minimum of 60 second updates, try that first - if (!AWEKAS.RateLimited && AWEKAS.Interval < 60) - { - AWEKAS.OriginalInterval = AWEKAS.Interval; - AWEKAS.RateLimited = true; - AWEKAS.Interval = 60; - AwekasTimer.Enabled = false; - AWEKAS.SynchronisedUpdate = true; - LogMessage("AWEKAS: Temporarily increasing AWEKAS upload interval to 60 seconds due to rate limit"); - } - // AWEKAS normal allows minimum of 300 second updates, revert to that - else - { - AWEKAS.RateLimited = true; - AWEKAS.Interval = 300; - AwekasTimer.Interval = AWEKAS.Interval * 1000; - AwekasTimer.Enabled = !AWEKAS.SynchronisedUpdate; - AWEKAS.SynchronisedUpdate = AWEKAS.Interval % 60 == 0; - LogMessage("AWEKAS: Temporarily increasing AWEKAS upload interval to 300 seconds due to rate limit"); - } - } - else - { - LogMessage("AWEKAS: Unknown error"); - HttpUploadAlarm.Triggered = true; - } - } - - // check the min upload time is greater than our upload time - if (respJson.status > 0 && respJson.minuploadtime > AWEKAS.OriginalInterval) - { - LogMessage($"AWEKAS: The minimum upload time to AWEKAS for your station is {respJson.minuploadtime} sec, Cumulus is configured for {AWEKAS.OriginalInterval} sec, increasing Cumulus interval to match AWEKAS"); - AWEKAS.Interval = respJson.minuploadtime; - WriteIniFile(); - AwekasTimer.Interval = AWEKAS.Interval * 1000; - AWEKAS.SynchronisedUpdate = AWEKAS.Interval % 60 == 0; - AwekasTimer.Enabled = !AWEKAS.SynchronisedUpdate; - // we got a successful upload, and reset the interval, so clear the rate limited values - AWEKAS.OriginalInterval = AWEKAS.Interval; - AWEKAS.RateLimited = false; - } - else if (AWEKAS.RateLimited && respJson.status > 0) - { - // We are currently rate limited, it could have been a transient thing because - // we just got a valid response, and our interval is >= the minimum allowed. - // So we just undo the limit, and resume as before - LogMessage($"AWEKAS: Removing temporary increase in upload interval to 60 secs, resuming uploads every {AWEKAS.OriginalInterval} secs"); - AWEKAS.Interval = AWEKAS.OriginalInterval; - AwekasTimer.Interval = AWEKAS.Interval * 1000; - AWEKAS.SynchronisedUpdate = AWEKAS.Interval % 60 == 0; - AwekasTimer.Enabled = !AWEKAS.SynchronisedUpdate; - AWEKAS.RateLimited = false; - } - } - } - catch (Exception ex) - { - LogMessage("AWEKAS: Exception = " + ex.Message); - HttpUploadAlarm.Triggered = true; - } - finally - { - AWEKAS.Updating = false; - } - } - - internal async void UpdateWCloud(DateTime timestamp) - { - if (WCloud.Updating || station.DataStopped) - { - // No data coming in, do not do anything - return; - } - - WCloud.Updating = true; - - string pwstring; - string url = station.GetWCloudURL(out pwstring, timestamp); - - string starredpwstring = ""; - - string logUrl = url.Replace(pwstring, starredpwstring); - - LogDebugMessage("WeatherCloud: URL = " + logUrl); - - try - { - HttpResponseMessage response = await WCloudhttpClient.GetAsync(url); - var responseBodyAsText = await response.Content.ReadAsStringAsync(); - var msg = ""; - switch ((int)response.StatusCode) - { - case 200: - msg = "Success"; - HttpUploadAlarm.Triggered = false; - break; - case 400: - msg = "Bad reuest"; - HttpUploadAlarm.Triggered = true; - break; - case 401: - msg = "Incorrect WID or Key"; - HttpUploadAlarm.Triggered = true; - break; - case 429: - msg = "Too many requests"; - HttpUploadAlarm.Triggered = true; - break; - case 500: - msg = "Server error"; - HttpUploadAlarm.Triggered = true; - break; - default: - msg = "Unknown error"; - HttpUploadAlarm.Triggered = true; - break; - } - LogDebugMessage($"WeatherCloud: Response = {msg} ({response.StatusCode}): {responseBodyAsText}"); - } - catch (Exception ex) - { - LogDebugMessage("WeatherCloud: ERROR - " + ex.Message); - HttpUploadAlarm.Triggered = true; - } - finally - { - WCloud.Updating = false; - } - } - - internal async void UpdateOpenWeatherMap(DateTime timestamp) - { - if (OpenWeatherMap.Updating || station.DataStopped) - { - // No data coming in, do not do anything - return; - } - - OpenWeatherMap.Updating = true; - - string url = "http://api.openweathermap.org/data/3.0/measurements?appid=" + OpenWeatherMap.PW; - string logUrl = url.Replace(OpenWeatherMap.PW, ""); - - string jsonData = station.GetOpenWeatherMapData(timestamp); - - LogDebugMessage("OpenWeatherMap: URL = " + logUrl); - LogDataMessage("OpenWeatherMap: Body = " + jsonData); - - try - { - using (var client = new HttpClient()) - { - var data = new StringContent(jsonData, Encoding.UTF8, "application/json"); - HttpResponseMessage response = await client.PostAsync(url, data); - var responseBodyAsText = await response.Content.ReadAsStringAsync(); - var status = response.StatusCode == HttpStatusCode.NoContent ? "OK" : "Error"; // Returns a 204 reponse for OK! - LogDebugMessage($"OpenWeatherMap: Response code = {status} - {response.StatusCode}"); - if (response.StatusCode != HttpStatusCode.NoContent) - { - LogDataMessage($"OpenWeatherMap: Response data = {responseBodyAsText}"); - HttpUploadAlarm.Triggered = true; - } - else - { - HttpUploadAlarm.Triggered = false; - } - } - } - catch (Exception ex) - { - LogMessage("OpenWeatherMap: ERROR - " + ex.Message); - HttpUploadAlarm.Triggered = true; - } - finally - { - OpenWeatherMap.Updating = false; - } - } - - // Find all stations associated with the users API key - internal OpenWeatherMapStation[] GetOpenWeatherMapStations() - { - OpenWeatherMapStation[] retVal = new OpenWeatherMapStation[0]; - string url = "http://api.openweathermap.org/data/3.0/stations?appid=" + OpenWeatherMap.PW; - try - { - using (var client = new HttpClient()) - { - HttpResponseMessage response = client.GetAsync(url).Result; - var responseBodyAsText = response.Content.ReadAsStringAsync().Result; - LogDataMessage("OpenWeatherMap: Get Stations Response: " + response.StatusCode + ": " + responseBodyAsText); - - if (responseBodyAsText.Length > 10) - { - var respJson = JsonSerializer.DeserializeFromString(responseBodyAsText); - retVal = respJson; - } - } - } - catch (Exception ex) - { - LogMessage("OpenWeatherMap: Get Stations ERROR - " + ex.Message); - } - - return retVal; - } - - // Create a new OpenWeatherMap station - internal void CreateOpenWeatherMapStation() - { - var invC = new CultureInfo(""); - - string url = "http://api.openweathermap.org/data/3.0/stations?appid=" + OpenWeatherMap.PW; - try - { - var datestr = DateTime.Now.ToUniversalTime().ToString("yyMMddHHmm"); - StringBuilder sb = new StringBuilder($"{{\"external_id\":\"CMX-{datestr}\","); - sb.Append($"\"name\":\"{LocationName}\","); - sb.Append($"\"latitude\":{Latitude.ToString(invC)},"); - sb.Append($"\"longitude\":{Longitude.ToString(invC)},"); - sb.Append($"\"altitude\":{(int)station.AltitudeM(Altitude)}}}"); - - LogMessage($"OpenWeatherMap: Creating new station"); - LogMessage($"OpenWeatherMap: - {sb}"); - - - using (var client = new HttpClient()) - { - var data = new StringContent(sb.ToString(), Encoding.UTF8, "application/json"); - - HttpResponseMessage response = client.PostAsync(url, data).Result; - var responseBodyAsText = response.Content.ReadAsStringAsync().Result; - var status = response.StatusCode == HttpStatusCode.Created ? "OK" : "Error"; // Returns a 201 reponse for OK - LogDebugMessage($"OpenWeatherMap: Create station response code = {status} - {response.StatusCode}"); - LogDataMessage($"OpenWeatherMap: Create station response data = {responseBodyAsText}"); - - if (response.StatusCode == HttpStatusCode.Created) - { - // It worked, save the result - var respJson = JsonSerializer.DeserializeFromString(responseBodyAsText); - - LogMessage($"OpenWeatherMap: Created new station, id = {respJson.ID}, name = {respJson.name}"); - OpenWeatherMap.ID = respJson.ID; - WriteIniFile(); - } - else - { - LogMessage($"OpenWeatherMap: Failed to create new station. Error - {response.StatusCode}, text - {responseBodyAsText}"); - } - } - } - catch (Exception ex) - { - LogMessage("OpenWeatherMap: Create station ERROR - " + ex.Message); - } - } - - internal void EnableOpenWeatherMap() - { - if (OpenWeatherMap.Enabled && string.IsNullOrWhiteSpace(OpenWeatherMap.ID)) - { - // oh, oh! OpenWeatherMap is enabled, but we do not have a station id - // first check if one already exists - var stations = GetOpenWeatherMapStations(); - - if (stations.Length == 0) - { - // No stations defined, we will create one - LogMessage($"OpenWeatherMap: No station defined, attempting to create one"); - CreateOpenWeatherMapStation(); - } - else if (stations.Length == 1) - { - // We have one station defined, lets use it! - LogMessage($"OpenWeatherMap: No station defined, but found one associated with this API key, using this station - {stations[0].id} : {stations[0].name}"); - OpenWeatherMap.ID = stations[0].id; - // save the setting - WriteIniFile(); - } - else - { - // multiple stations defined, the user must select which one to use - var msg = $"Multiple OpenWeatherMap stations found, please select the correct station id and enter it into your configuration"; - LogConsoleMessage(msg); - LogMessage("OpenWeatherMap: " + msg); - foreach (var station in stations) - { - msg = $" Station Id = {station.id}, Name = {station.name}"; - LogConsoleMessage(msg); - LogMessage("OpenWeatherMap: " + msg); - } - } - } - } - - internal void RealtimeTimerTick(object sender, ElapsedEventArgs elapsedEventArgs) - { - bool connectionFailed = false; - var cycle = RealtimeCycleCounter++; - - if (station.DataStopped) - { - // No data coming in, do not do anything - return; - } - - LogDebugMessage($"Realtime[{cycle}]: Start cycle"); - try - { - // Process any files - if (RealtimeCopyInProgress) - { - LogMessage($"Realtime[{cycle}]: Warning, a previous cycle is still processing local files. Skipping this interval."); - } - else - { - RealtimeCopyInProgress = true; - CreateRealtimeFile(cycle); - CreateRealtimeHTMLfiles(cycle); - RealtimeCopyInProgress = false; - - if (RealtimeFTPEnabled && !string.IsNullOrWhiteSpace(FtpHostname)) - { - // Is a previous cycle still running? - if (RealtimeFtpInProgress) - { - LogMessage($"Realtime[{cycle}]: Warning, a previous cycle is still trying to connect to FTP server, skip count = {++realtimeFTPRetries}"); - // realtimeinvertval is in ms, if a session has been uploading for 5 minutes - abort it and reconnect - if (realtimeFTPRetries * RealtimeInterval / 1000 > 5 * 60) - { - LogMessage($"Realtime[{cycle}]: Realtime has been in progress for more than 5 minutes, attempting to reconnect."); - RealtimeFTPConnectionTest(cycle); - } - else - { - LogMessage($"Realtime[{cycle}]: No FTP attempted this cycle"); - } - } - else - { - RealtimeFtpInProgress = true; - - // This only happens if the user enables realtime FTP after starting Cumulus - if (Sslftp == FtpProtocols.SFTP) - { - if (RealtimeSSH == null || !RealtimeSSH.ConnectionInfo.IsAuthenticated) - { - RealtimeSSHLogin(cycle); - } - } - else - { - if (!RealtimeFTP.IsConnected) - { - RealtimeFTPLogin(cycle); - } - } - // Force a test of the connection, IsConnected is not always reliable - try - { - string pwd; - if (Sslftp == FtpProtocols.SFTP) - { - pwd = RealtimeSSH.WorkingDirectory; - // Double check - if (!RealtimeSSH.IsConnected) - { - connectionFailed = true; - } - } - else - { - pwd = RealtimeFTP.GetWorkingDirectory(); - // Double check - if (!RealtimeFTP.IsConnected) - { - connectionFailed = true; - } - } - if (pwd.Length == 0) - { - connectionFailed = true; - } - } - catch (Exception ex) - { - LogDebugMessage($"Realtime[{cycle}]: Test of FTP connection failed: {ex.Message}"); - connectionFailed = true; - } - - if (connectionFailed) - { - RealtimeFTPConnectionTest(cycle); - } - else - { - realtimeFTPRetries = 0; - } - - try - { - RealtimeFTPUpload(cycle); - } - catch (Exception ex) - { - LogMessage($"Realtime[{cycle}]: Error during realtime FTP update: {ex.Message}"); - RealtimeFTPConnectionTest(cycle); - } - RealtimeFtpInProgress = false; - } - } - - if (!string.IsNullOrEmpty(RealtimeProgram)) - { - LogDebugMessage($"Realtime[{cycle}]: Execute realtime program - {RealtimeProgram}"); - ExecuteProgram(RealtimeProgram, RealtimeParams); - } - } - } - catch (Exception ex) - { - LogMessage($"Realtime[{cycle}]: Error during update: {ex.Message}"); - if (ex is NullReferenceException) - { - // If we haven't initialised the object (eg. user enables realtime FTP after starting Cumulus) - // then start from the beginning - if (Sslftp == FtpProtocols.SFTP) - { - RealtimeSSHLogin(cycle); - } - else - { - RealtimeFTPLogin(cycle); - } - } - else - { - RealtimeFTPConnectionTest(cycle); - } - RealtimeFtpInProgress = false; - } - LogDebugMessage($"Realtime[{cycle}]: End cycle"); - } - - private void RealtimeFTPConnectionTest(uint cycle) - { - LogDebugMessage($"Realtime[{cycle}]: Realtime ftp attempting disconnect"); - try - { - if (Sslftp == FtpProtocols.SFTP && RealtimeSSH != null) - { - RealtimeSSH.Disconnect(); - } - else - { - RealtimeFTP.Disconnect(); - } - LogDebugMessage($"Realtime[{cycle}]: Realtime ftp disconnected OK"); - } - catch(Exception ex) - { - LogDebugMessage($"Realtime[{cycle}]: Error disconnecting from server - " + ex.Message); - } - - LogDebugMessage($"Realtime[{cycle}]: Realtime ftp attempting to reconnect"); - try - { - if (Sslftp == FtpProtocols.SFTP && RealtimeSSH != null) - { - RealtimeSSH.Connect(); - } - else - { - RealtimeFTP.Connect(); - } - LogMessage($"Realtime[{cycle}]: Reconnected with server OK"); - } - catch (Exception ex) - { - LogMessage($"Realtime[{cycle}]: Error reconnecting ftp server - " + ex.Message); - LogDebugMessage($"Realtime[{cycle}]: Realtime ftp attempting to reinitialise the connection"); - if (Sslftp == FtpProtocols.SFTP) - { - RealtimeSSHLogin(cycle); - } - else - { - RealtimeFTPLogin(cycle); - } - } - if (Sslftp == FtpProtocols.SFTP && RealtimeSSH == null) - { - LogDebugMessage($"Realtime[{cycle}]: Realtime ftp attempting to reinitialise the connection"); - RealtimeSSHLogin(cycle); - } - } - - private void RealtimeFTPUpload(byte cycle) - { - var remotePath = ""; - - if (FtpDirectory.Length > 0) - { - remotePath = (FtpDirectory.EndsWith("/") ? FtpDirectory : FtpDirectory + "/"); - } - - for (var i = 0; i < RealtimeFiles.Length; i++) - { - if (RealtimeFiles[i].Create && RealtimeFiles[i].FTP) - { - var remoteFile = remotePath + RealtimeFiles[i].RemoteFileName; - var localFile = RealtimeFiles[i].LocalPath + RealtimeFiles[i].LocalFileName; - - LogFtpMessage($"Realtime[{cycle}]: Uploading - {RealtimeFiles[i].LocalFileName}"); - if (Sslftp == FtpProtocols.SFTP) - { - UploadFile(RealtimeSSH, localFile, remoteFile, cycle); - } - else - { - UploadFile(RealtimeFTP, localFile, remoteFile, cycle); - } - - } - } - - // Extra files - for (int i = 0; i < numextrafiles; i++) - { - var uploadfile = ExtraFiles[i].local; - var remotefile = ExtraFiles[i].remote; - - if ((uploadfile.Length > 0) && (remotefile.Length > 0) && ExtraFiles[i].realtime && ExtraFiles[i].FTP) - { - if (uploadfile == "") - { - uploadfile = GetLogFileName(DateTime.Now); - } - else if (uploadfile == "") - { - uploadfile = GetExtraLogFileName(DateTime.Now); - } - else if (uploadfile == "")) - { - remotefile = remotefile.Replace("", Path.GetFileName(GetLogFileName(DateTime.Now))); - } - else if (remotefile.Contains("")) - { - remotefile = remotefile.Replace("", Path.GetFileName(GetExtraLogFileName(DateTime.Now))); - } - else if (remotefile.Contains("", Path.GetFileName(GetAirLinkLogFileName(DateTime.Now))); - } - - // all checks OK, file needs to be uploaded - if (ExtraFiles[i].process) - { - // we've already processed the file - uploadfile += "tmp"; - } - LogFtpMessage($"Realtime[{cycle}]: Uploading extra web file[{i}] {uploadfile} to {remotefile}"); - if (Sslftp == FtpProtocols.SFTP) - { - UploadFile(RealtimeSSH, uploadfile, remotefile, cycle); - } - else - { - UploadFile(RealtimeFTP, uploadfile, remotefile, cycle); - } - } - else - { - LogMessage($"Realtime[{cycle}]: Warning, extra web file[{i}] not found! - {uploadfile}"); - } - } - } - } - - private void CreateRealtimeHTMLfiles(int cycle) - { - // Process realtime files - for (var i = 0; i < RealtimeFiles.Length; i++) - { - if (RealtimeFiles[i].Create && !string.IsNullOrWhiteSpace(RealtimeFiles[i].TemplateFileName)) - { - LogDebugMessage($"Realtime[{cycle}]: Processing realtime file - {RealtimeFiles[i].LocalFileName}"); - var destFile = RealtimeFiles[i].LocalPath + RealtimeFiles[i].LocalFileName; - ProcessTemplateFile(RealtimeFiles[i].TemplateFileName, destFile, realtimeTokenParser); - } - } - - for (int i = 0; i < numextrafiles; i++) - { - if (ExtraFiles[i].realtime) - { - var uploadfile = ExtraFiles[i].local; - var remotefile = ExtraFiles[i].remote; - - if ((uploadfile.Length > 0) && (remotefile.Length > 0)) - { - if (uploadfile == "") - { - uploadfile = GetLogFileName(DateTime.Now); - } - else if (uploadfile == "") - { - uploadfile = GetExtraLogFileName(DateTime.Now); - } - else if (uploadfile == "")) - { - remotefile = remotefile.Replace("", Path.GetFileName(GetLogFileName(DateTime.Now))); - } - else if (remotefile.Contains("")) - { - remotefile = remotefile.Replace("", Path.GetFileName(GetExtraLogFileName(DateTime.Now))); - } - else if (remotefile.Contains("", Path.GetFileName(GetAirLinkLogFileName(DateTime.Now))); - } - - if (ExtraFiles[i].process) - { - // process the file - LogDebugMessage($"Realtime[{cycle}]: Processing extra file[{i}] - {uploadfile}"); - var utf8WithoutBom = new UTF8Encoding(false); - var encoding = UTF8encode ? utf8WithoutBom : Encoding.GetEncoding("iso-8859-1"); - realtimeTokenParser.Encoding = encoding; - realtimeTokenParser.SourceFile = uploadfile; - var output = realtimeTokenParser.ToString(); - uploadfile += "tmp"; - try - { - using (StreamWriter file = new StreamWriter(uploadfile, false, encoding)) - { - file.Write(output); - file.Close(); - } - } - catch (Exception ex) - { - LogMessage($"Realtime[{cycle}]: Error writing to extra realtime file[{i}] - {uploadfile}: {ex.Message}"); - } - } - - if (!ExtraFiles[i].FTP) - { - // just copy the file - try - { - LogDebugMessage($"Realtime[{cycle}]: Copying extra file[{i}] {uploadfile} to {remotefile}"); - File.Copy(uploadfile, remotefile, true); - } - catch (Exception ex) - { - LogDebugMessage($"Realtime[{cycle}]: Error copying extra realtime file[{i}] - {uploadfile}: {ex.Message}"); - } - } - } - else - { - LogMessage($"Realtime[{cycle}]: Extra realtime web file[{i}] not found - {uploadfile}"); - } - - } - } - } - } - - public void TokenParserOnToken(string strToken, ref string strReplacement) - { - var tagParams = new Dictionary(); - var paramList = ParseParams(strToken); - var webTag = paramList[0]; - - tagParams.Add("webtag", webTag); - for (int i = 1; i < paramList.Count; i += 2) - { - // odd numbered entries are keys with "=" on the end - remove that - string key = paramList[i].Remove(paramList[i].Length - 1); - // even numbered entries are values - string value = paramList[i + 1]; - tagParams.Add(key, value); - } - - strReplacement = webtags.GetWebTagText(webTag, tagParams); - } - - private List ParseParams(string line) - { - var insideQuotes = false; - var start = -1; - - var parts = new List(); - - for (var i = 0; i < line.Length; i++) - { - if (char.IsWhiteSpace(line[i])) - { - if (!insideQuotes && start != -1) - { - parts.Add(line.Substring(start, i - start)); - start = -1; - } - } - else if (line[i] == '"') - { - if (start != -1) - { - parts.Add(line.Substring(start, i - start)); - start = -1; - } - insideQuotes = !insideQuotes; - } - else if (line[i] == '=') - { - if (!insideQuotes) - { - if (start != -1) - { - parts.Add(line.Substring(start, (i - start) + 1)); - start = -1; - } - } - } - else - { - if (start == -1) - start = i; - } - } - - if (start != -1) - parts.Add(line.Substring(start)); - - return parts; - } - - public string DecimalSeparator { get; set; } - - [DllImport("libc")] - private static extern int uname(IntPtr buf); - - private static bool IsRunningOnMac() - { - IntPtr buf = IntPtr.Zero; - try - { - buf = Marshal.AllocHGlobal(8192); - // This is a hacktastic way of getting sysname from uname () - if (uname(buf) == 0) - { - string os = Marshal.PtrToStringAnsi(buf); - if (os == "Darwin") - return true; - } - } - catch - { - } - finally - { - if (buf != IntPtr.Zero) - Marshal.FreeHGlobal(buf); - } - return false; - } - - internal void DoMoonPhase() - { - DateTime now = DateTime.Now; - double[] moonriseset = MoonriseMoonset.MoonRise(now.Year, now.Month, now.Day, TimeZone.CurrentTimeZone.GetUtcOffset(now).TotalHours, Latitude, Longitude); - MoonRiseTime = TimeSpan.FromHours(moonriseset[0]); - MoonSetTime = TimeSpan.FromHours(moonriseset[1]); - - DateTime utcNow = DateTime.UtcNow; - MoonPhaseAngle = MoonriseMoonset.MoonPhase(utcNow.Year, utcNow.Month, utcNow.Day, utcNow.Hour); - MoonPercent = (100.0 * (1.0 + Math.Cos(MoonPhaseAngle * Math.PI / 180)) / 2.0); - - // If between full moon and new moon, angle is between 180 and 360, make percent negative to indicate waning - if (MoonPhaseAngle > 180) - { - MoonPercent = -MoonPercent; - } - /* - // New = -0.4 -> 0.4 - // 1st Q = 45 -> 55 - // Full = 99.6 -> -99.6 - // 3rd Q = -45 -> -55 - if ((MoonPercent > 0.4) && (MoonPercent < 45)) - MoonPhaseString = WaxingCrescent; - else if ((MoonPercent >= 45) && (MoonPercent <= 55)) - MoonPhaseString = FirstQuarter; - else if ((MoonPercent > 55) && (MoonPercent < 99.6)) - MoonPhaseString = WaxingGibbous; - else if ((MoonPercent >= 99.6) || (MoonPercent <= -99.6)) - MoonPhaseString = Fullmoon; - else if ((MoonPercent < -55) && (MoonPercent > -99.6)) - MoonPhaseString = WaningGibbous; - else if ((MoonPercent <= -45) && (MoonPercent >= -55)) - MoonPhaseString = LastQuarter; - else if ((MoonPercent > -45) && (MoonPercent < -0.4)) - MoonPhaseString = WaningCrescent; - else - MoonPhaseString = Newmoon; - */ - - // Use Phase Angle to determine string - it's linear unlike Illuminated Percentage - // New = 186 - 180 - 174 - // 1st = 96 - 90 - 84 - // Full = 6 - 0 - 354 - // 3rd = 276 - 270 - 264 - if (MoonPhaseAngle < 174 && MoonPhaseAngle > 96) - MoonPhaseString = WaxingCrescent; - else if (MoonPhaseAngle <= 96 && MoonPhaseAngle >= 84) - MoonPhaseString = FirstQuarter; - else if (MoonPhaseAngle < 84 && MoonPhaseAngle > 6) - MoonPhaseString = WaxingGibbous; - else if (MoonPhaseAngle <= 6 || MoonPhaseAngle >= 354) - MoonPhaseString = Fullmoon; - else if (MoonPhaseAngle < 354 && MoonPhaseAngle > 276) - MoonPhaseString = WaningGibbous; - else if (MoonPhaseAngle <= 276 && MoonPhaseAngle >= 264) - MoonPhaseString = LastQuarter; - else if (MoonPhaseAngle < 264 && MoonPhaseAngle > 186) - MoonPhaseString = WaningCrescent; - else - MoonPhaseString = Newmoon; - } - - internal void DoMoonImage() - { - if (MoonImageEnabled) - { - LogDebugMessage("Generating new Moon image"); - var ret = MoonriseMoonset.CreateMoonImage(MoonPhaseAngle, Latitude, MoonImageSize); - - if (ret == "OK") - { - // set a flag to show file is ready for FTP - MoonImageReady = true; - } - else - { - LogMessage(ret); - } - } - } - - - /* - private string GetMoonStage(double fAge) - { - string sStage; - - if (fAge < 1.84566) - { - sStage = Newmoon; - } - else if (fAge < 5.53699) - { - sStage = WaxingCrescent; - } - else if (fAge < 9.22831) - { - sStage = FirstQuarter; - } - else if (fAge < 12.91963) - { - sStage = WaxingGibbous; - } - else if (fAge < 16.61096) - { - sStage = Fullmoon; - } - else if (fAge < 20.30228) - { - sStage = WaningGibbous; - } - else if (fAge < 23.9931) - { - sStage = LastQuarter; - } - else if (fAge < 27.68493) - { - sStage = WaningCrescent; - } - else - { - sStage = Newmoon; - } - - return sStage; - } - */ - - public double MoonAge { get; set; } - - public string MoonPhaseString { get; set; } - - public double MoonPhaseAngle { get; set; } - - public double MoonPercent { get; set; } - - public TimeSpan MoonSetTime { get; set; } - - public TimeSpan MoonRiseTime { get; set; } - - public bool MoonImageEnabled; - - public int MoonImageSize; - - public string MoonImageFtpDest; - - private bool MoonImageReady; - - private void GetSunriseSunset(DateTime time, out DateTime sunrise, out DateTime sunset, out bool alwaysUp, out bool alwaysDown) - { - string rise = SunriseSunset.SunRise(time, TimeZone.CurrentTimeZone.GetUtcOffset(time).TotalHours, Longitude, Latitude); - string set = SunriseSunset.SunSet(time, TimeZone.CurrentTimeZone.GetUtcOffset(time).TotalHours, Longitude, Latitude); - - if (rise.Equals("Always Down") || set.Equals("Always Down")) - { - alwaysDown = true; - alwaysUp = false; - sunrise = DateTime.MinValue; - sunset = DateTime.MinValue; - } - else if (rise.Equals("Always Up") || set.Equals("Always Up")) - { - alwaysDown = false; - alwaysUp = true; - sunrise = DateTime.MinValue; - sunset = DateTime.MinValue; - } - else - { - alwaysDown = false; - alwaysUp = false; - try - { - int h = Convert.ToInt32(rise.Substring(0, 2)); - int m = Convert.ToInt32(rise.Substring(2, 2)); - int s = Convert.ToInt32(rise.Substring(4, 2)); - sunrise = DateTime.Now.Date.Add(new TimeSpan(h, m, s)); - } - catch (Exception) - { - sunrise = DateTime.MinValue; - } - - try - { - int h = Convert.ToInt32(set.Substring(0, 2)); - int m = Convert.ToInt32(set.Substring(2, 2)); - int s = Convert.ToInt32(set.Substring(4, 2)); - sunset = DateTime.Now.Date.Add(new TimeSpan(h, m, s)); - } - catch (Exception) - { - sunset = DateTime.MinValue; - } - } - } - - /* - private DateTime getSunriseTime(DateTime time) - { - string rise = SunriseSunset.sunrise(time, TimeZone.CurrentTimeZone.GetUtcOffset(time).TotalHours, Longitude, Latitude); - //LogMessage("Sunrise: " + rise); - int h = Convert.ToInt32(rise.Substring(0, 2)); - int m = Convert.ToInt32(rise.Substring(2, 2)); - int s = Convert.ToInt32(rise.Substring(4, 2)); - return DateTime.Now.Date.Add(new TimeSpan(h, m, s)); - } - */ - - /* - private DateTime getSunsetTime(DateTime time) - { - string rise = SunriseSunset.sunset(time, TimeZone.CurrentTimeZone.GetUtcOffset(time).TotalHours, Longitude, Latitude); - //LogMessage("Sunrise: " + rise); - int h = Convert.ToInt32(rise.Substring(0, 2)); - int m = Convert.ToInt32(rise.Substring(2, 2)); - int s = Convert.ToInt32(rise.Substring(4, 2)); - return DateTime.Now.Date.Add(new TimeSpan(h, m, s)); - } - */ - - private void GetDawnDusk(DateTime time, out DateTime dawn, out DateTime dusk, out bool alwaysUp, out bool alwaysDown) - { - string dawnStr = SunriseSunset.CivilTwilightEnds(time, TimeZone.CurrentTimeZone.GetUtcOffset(time).TotalHours, Longitude, Latitude); - string duskStr = SunriseSunset.CivilTwilightStarts(time, TimeZone.CurrentTimeZone.GetUtcOffset(time).TotalHours, Longitude, Latitude); - - if (dawnStr.Equals("Always Down") || duskStr.Equals("Always Down")) - { - alwaysDown = true; - alwaysUp = false; - dawn = DateTime.MinValue; - dusk = DateTime.MinValue; - } - else if (dawnStr.Equals("Always Up") || duskStr.Equals("Always Up")) - { - alwaysDown = false; - alwaysUp = true; - dawn = DateTime.MinValue; - dusk = DateTime.MinValue; - } - else - { - alwaysDown = false; - alwaysUp = false; - try - { - int h = Convert.ToInt32(dawnStr.Substring(0, 2)); - int m = Convert.ToInt32(dawnStr.Substring(2, 2)); - int s = Convert.ToInt32(dawnStr.Substring(4, 2)); - dawn = DateTime.Now.Date.Add(new TimeSpan(h, m, s)); - } - catch (Exception) - { - dawn = DateTime.MinValue; - } - - try - { - int h = Convert.ToInt32(duskStr.Substring(0, 2)); - int m = Convert.ToInt32(duskStr.Substring(2, 2)); - int s = Convert.ToInt32(duskStr.Substring(4, 2)); - dusk = DateTime.Now.Date.Add(new TimeSpan(h, m, s)); - } - catch (Exception) - { - dusk = DateTime.MinValue; - } - } - } - - /* - private DateTime getDawnTime(DateTime time) - { - string rise = SunriseSunset.CivilTwilightEnds(time, TimeZone.CurrentTimeZone.GetUtcOffset(time).TotalHours, Longitude, Latitude); - //LogMessage("Sunrise: " + rise); - try - { - int h = Convert.ToInt32(rise.Substring(0, 2)); - int m = Convert.ToInt32(rise.Substring(2, 2)); - int s = Convert.ToInt32(rise.Substring(4, 2)); - return DateTime.Now.Date.Add(new TimeSpan(h, m, s)); - } - catch (Exception) - { - return DateTime.Now.Date; - } - } - */ - - /* - private DateTime getDuskTime(DateTime time) - { - string rise = SunriseSunset.CivilTwilightStarts(time, TimeZone.CurrentTimeZone.GetUtcOffset(time).TotalHours, Longitude, Latitude); - //LogMessage("Sunrise: " + rise); - try - { - int h = Convert.ToInt32(rise.Substring(0, 2)); - int m = Convert.ToInt32(rise.Substring(2, 2)); - int s = Convert.ToInt32(rise.Substring(4, 2)); - return DateTime.Now.Date.Add(new TimeSpan(h, m, s)); - } - catch (Exception) - { - return DateTime.Now.Date; - } - } - */ - - internal void DoSunriseAndSunset() - { - LogMessage("Calculating sunrise and sunset times"); - DateTime now = DateTime.Now; - DateTime tomorrow = now.AddDays(1); - GetSunriseSunset(now, out SunRiseTime, out SunSetTime, out SunAlwaysUp, out SunAlwaysDown); - - if (SunAlwaysUp) - { - LogMessage("Sun always up"); - DayLength = new TimeSpan(24, 0, 0); - } - else if (SunAlwaysDown) - { - LogMessage("Sun always down"); - DayLength = new TimeSpan(0, 0, 0); - } - else - { - LogMessage("Sunrise: " + SunRiseTime.ToString("HH:mm:ss")); - LogMessage("Sunset : " + SunSetTime.ToString("HH:mm:ss")); - if (SunRiseTime == DateTime.MinValue) - { - DayLength = SunSetTime - DateTime.Now.Date; - } - else if (SunSetTime == DateTime.MinValue) - { - DayLength = DateTime.Now.Date.AddDays(1) - SunRiseTime; - } - else if (SunSetTime > SunRiseTime) - { - DayLength = SunSetTime - SunRiseTime; - } - else - { - DayLength = new TimeSpan(24, 0, 0) - (SunRiseTime - SunSetTime); - } - } - - DateTime tomorrowSunRiseTime; - DateTime tomorrowSunSetTime; - TimeSpan tomorrowDayLength; - bool tomorrowSunAlwaysUp; - bool tomorrowSunAlwaysDown; - - GetSunriseSunset(tomorrow, out tomorrowSunRiseTime, out tomorrowSunSetTime, out tomorrowSunAlwaysUp, out tomorrowSunAlwaysDown); - - if (tomorrowSunAlwaysUp) - { - LogMessage("Tomorrow sun always up"); - tomorrowDayLength = new TimeSpan(24, 0, 0); - } - else if (tomorrowSunAlwaysDown) - { - LogMessage("Tomorrow sun always down"); - tomorrowDayLength = new TimeSpan(0, 0, 0); - } - else - { - LogMessage("Tomorrow sunrise: " + tomorrowSunRiseTime.ToString("HH:mm:ss")); - LogMessage("Tomorrow sunset : " + tomorrowSunSetTime.ToString("HH:mm:ss")); - tomorrowDayLength = tomorrowSunSetTime - tomorrowSunRiseTime; - } - - int tomorrowdiff = Convert.ToInt32(tomorrowDayLength.TotalSeconds - DayLength.TotalSeconds); - LogDebugMessage("Tomorrow length diff: " + tomorrowdiff); - - bool tomorrowminus; - - if (tomorrowdiff < 0) - { - tomorrowminus = true; - tomorrowdiff = -tomorrowdiff; - } - else - { - tomorrowminus = false; - } - - int tomorrowmins = tomorrowdiff / 60; - int tomorrowsecs = tomorrowdiff % 60; - - if (tomorrowminus) - { - try - { - TomorrowDayLengthText = string.Format(thereWillBeMinSLessDaylightTomorrow, tomorrowmins, tomorrowsecs); - } - catch (Exception) - { - TomorrowDayLengthText = "Error in LessDaylightTomorrow format string"; - } - } - else - { - try - { - TomorrowDayLengthText = string.Format(thereWillBeMinSMoreDaylightTomorrow, tomorrowmins, tomorrowsecs); - } - catch (Exception) - { - TomorrowDayLengthText = "Error in MoreDaylightTomorrow format string"; - } - } - - GetDawnDusk(now, out Dawn, out Dusk, out TwilightAlways, out TwilightNever); - - if (TwilightAlways) - { - DaylightLength = new TimeSpan(24, 0, 0); - } - else if (TwilightNever) - { - DaylightLength = new TimeSpan(0, 0, 0); - } - else - { - if (Dawn == DateTime.MinValue) - { - DaylightLength = Dusk - DateTime.Now.Date; - } - else if (Dusk == DateTime.MinValue) - { - DaylightLength = DateTime.Now.Date.AddDays(1) - Dawn; - } - else if (Dusk > Dawn) - { - DaylightLength = Dusk - Dawn; - } - else - { - DaylightLength = new TimeSpan(24, 0, 0) - (Dawn - Dusk); - } - } - } - - public DateTime SunSetTime; - - public DateTime SunRiseTime; - - internal bool SunAlwaysUp; - internal bool SunAlwaysDown; - - internal bool TwilightAlways; - internal bool TwilightNever; - - public string TomorrowDayLengthText { get; set; } - - public bool IsDaylight() - { - if (TwilightAlways) - { - return true; - } - if (TwilightNever) - { - return false; - } - if (Dusk > Dawn) - { - // 'Normal' case where sun sets before midnight - return (DateTime.Now >= Dawn) && (DateTime.Now <= Dusk); - } - else - { - return !((DateTime.Now >= Dusk) && (DateTime.Now <= Dawn)); - } - } - - public bool IsSunUp() - { - if (SunAlwaysUp) - { - return true; - } - if (SunAlwaysDown) - { - return false; - } - if (SunSetTime > SunRiseTime) - { - // 'Normal' case where sun sets before midnight - return (DateTime.Now >= SunRiseTime) && (DateTime.Now <= SunSetTime); - } - else - { - return !((DateTime.Now >= SunSetTime) && (DateTime.Now <= SunRiseTime)); - } - } - - private string GetLoggingFileName(string directory) - { - const int maxEntries = 15; - - List fileEntries = new List(Directory.GetFiles(directory)); - - fileEntries.Sort(); - - while (fileEntries.Count >= maxEntries) - { - File.Delete(fileEntries.First()); - fileEntries.RemoveAt(0); - } - - return $"{directory}{DateTime.Now:yyyyMMdd-HHmmss}.txt"; - } - - public void RotateLogFiles() - { - // cycle the MXdiags log file? - var logfileSize = new FileInfo(loggingfile).Length; - // if > 20 MB - if (logfileSize > 20971520) - { - var oldfile = loggingfile; - loggingfile = GetLoggingFileName("MXdiags" + DirectorySeparator); - LogMessage("Rotating log file, new log file will be: " + loggingfile.Split(DirectorySeparator).Last()); - TextWriterTraceListener myTextListener = new TextWriterTraceListener(loggingfile, "MXlog"); - Trace.Listeners.Remove("MXlog"); - Trace.Listeners.Add(myTextListener); - LogMessage("Rotated log file, old log file was: " + oldfile.Split(DirectorySeparator).Last()); - } - } - - private void ReadIniFile() - { - var DavisBaudRates = new List { 1200, 2400, 4800, 9600, 14400, 19200 }; - ImetOptions.BaudRates = new List { 19200, 115200 }; - - LogMessage("Reading Cumulus.ini file"); - //DateTimeToString(LongDate, "ddddd", Now); - - IniFile ini = new IniFile("Cumulus.ini"); - - // check for Cumulus 1 [FTP Site] and correct it - if (ini.GetValue("FTP Site", "Port", -999) != -999) - { - if (File.Exists("Cumulus.ini")) - { - var contents = File.ReadAllText("Cumulus.ini"); - contents = contents.Replace("[FTP Site]", "[FTP site]"); - File.WriteAllText("Cumulus.ini", contents); - ini.Refresh(); - } - } - - ProgramOptions.EnableAccessibility = ini.GetValue("Program", "EnableAccessibility", false); - ProgramOptions.StartupPingHost = ini.GetValue("Program", "StartupPingHost", ""); - ProgramOptions.StartupPingEscapeTime = ini.GetValue("Program", "StartupPingEscapeTime", 999); - ProgramOptions.StartupDelaySecs = ini.GetValue("Program", "StartupDelaySecs", 0); - ProgramOptions.StartupDelayMaxUptime = ini.GetValue("Program", "StartupDelayMaxUptime", 300); - ProgramOptions.WarnMultiple = ini.GetValue("Station", "WarnMultiple", true); - SmtpOptions.Logging = ini.GetValue("SMTP", "Logging", false); - if (DebuggingEnabled) - { - ProgramOptions.DebugLogging = true; - ProgramOptions.DataLogging = true; - } - else - { - ProgramOptions.DebugLogging = ini.GetValue("Station", "Logging", false); - ProgramOptions.DataLogging = ini.GetValue("Station", "DataLogging", false); - } - - ComportName = ini.GetValue("Station", "ComportName", DefaultComportName); - - StationType = ini.GetValue("Station", "Type", -1); - StationModel = ini.GetValue("Station", "Model", ""); - - FineOffsetStation = (StationType == StationTypes.FineOffset || StationType == StationTypes.FineOffsetSolar); - DavisStation = (StationType == StationTypes.VantagePro || StationType == StationTypes.VantagePro2); - - // Davis Options - DavisOptions.UseLoop2 = ini.GetValue("Station", "UseDavisLoop2", true); - DavisOptions.ReadReceptionStats = ini.GetValue("Station", "DavisReadReceptionStats", true); - DavisOptions.SetLoggerInterval = ini.GetValue("Station", "DavisSetLoggerInterval", false); - DavisOptions.InitWaitTime = ini.GetValue("Station", "DavisInitWaitTime", 2000); - DavisOptions.IPResponseTime = ini.GetValue("Station", "DavisIPResponseTime", 500); - //StationOptions.DavisReadTimeout = ini.GetValue("Station", "DavisReadTimeout", 1000); // Not currently used - DavisOptions.IncrementPressureDP = ini.GetValue("Station", "DavisIncrementPressureDP", false); - if (StationType == StationTypes.VantagePro) - { - DavisOptions.UseLoop2 = false; - } - DavisOptions.BaudRate = ini.GetValue("Station", "DavisBaudRate", 19200); - // Check we have a valid value - if (!DavisBaudRates.Contains(DavisOptions.BaudRate)) - { - // nope, that isn't allowed, set the default - LogMessage("Error, the value for DavisBaudRate in the ini file " + DavisOptions.BaudRate + " is not valid, using default 19200."); - DavisOptions.BaudRate = 19200; - } - DavisOptions.ForceVPBarUpdate = ini.GetValue("Station", "ForceVPBarUpdate", false); - //DavisUseDLLBarCalData = ini.GetValue("Station", "DavisUseDLLBarCalData", false); - //DavisCalcAltPress = ini.GetValue("Station", "DavisCalcAltPress", true); - //DavisConsoleHighGust = ini.GetValue("Station", "DavisConsoleHighGust", false); - DavisOptions.RainGaugeType = ini.GetValue("Station", "VPrainGaugeType", -1); - if (DavisOptions.RainGaugeType > 3) - { - DavisOptions.RainGaugeType = -1; - } - DavisOptions.ConnectionType = ini.GetValue("Station", "VP2ConnectionType", VP2SERIALCONNECTION); - DavisOptions.TCPPort = ini.GetValue("Station", "VP2TCPPort", 22222); - DavisOptions.IPAddr = ini.GetValue("Station", "VP2IPAddr", "0.0.0.0"); - //VPClosedownTime = ini.GetValue("Station", "VPClosedownTime", 99999999); - //VP2SleepInterval = ini.GetValue("Station", "VP2SleepInterval", 0); - DavisOptions.PeriodicDisconnectInterval = ini.GetValue("Station", "VP2PeriodicDisconnectInterval", 0); - - Latitude = ini.GetValue("Station", "Latitude", 0.0); - if (Latitude > 90 || Latitude < -90) - { - Latitude = 0; - LogMessage($"Error, invalid latitude value in Cumulus.ini [{Latitude}], defaulting to zero."); - } - Longitude = ini.GetValue("Station", "Longitude", 0.0); - if (Longitude > 180 || Longitude < -180) - { - Longitude = 0; - LogMessage($"Error, invalid longitude value in Cumulus.ini [{Longitude}], defaulting to zero."); - } - - LatTxt = ini.GetValue("Station", "LatTxt", ""); - LatTxt = LatTxt.Replace(" ", " "); - LatTxt = LatTxt.Replace("°", "'"); - LonTxt = ini.GetValue("Station", "LonTxt", ""); - LonTxt = LonTxt.Replace(" ", " "); - LonTxt = LonTxt.Replace("°", "'"); - - Altitude = ini.GetValue("Station", "Altitude", 0.0); - AltitudeInFeet = ini.GetValue("Station", "AltitudeInFeet", true); - - StationOptions.Humidity98Fix = ini.GetValue("Station", "Humidity98Fix", false); - StationOptions.UseWind10MinAve = ini.GetValue("Station", "Wind10MinAverage", false); - StationOptions.UseSpeedForAvgCalc = ini.GetValue("Station", "UseSpeedForAvgCalc", false); - - StationOptions.AvgBearingMinutes = ini.GetValue("Station", "AvgBearingMinutes", 10); - if (StationOptions.AvgBearingMinutes > 120) - { - StationOptions.AvgBearingMinutes = 120; - } - if (StationOptions.AvgBearingMinutes == 0) - { - StationOptions.AvgBearingMinutes = 1; - } - - AvgBearingTime = new TimeSpan(StationOptions.AvgBearingMinutes / 60, StationOptions.AvgBearingMinutes % 60, 0); - - StationOptions.AvgSpeedMinutes = ini.GetValue("Station", "AvgSpeedMinutes", 10); - if (StationOptions.AvgSpeedMinutes > 120) - { - StationOptions.AvgSpeedMinutes = 120; - } - if (StationOptions.AvgSpeedMinutes == 0) - { - StationOptions.AvgSpeedMinutes = 1; - } - - AvgSpeedTime = new TimeSpan(StationOptions.AvgSpeedMinutes / 60, StationOptions.AvgSpeedMinutes % 60, 0); - - LogMessage("AvgSpdMins=" + StationOptions.AvgSpeedMinutes + " AvgSpdTime=" + AvgSpeedTime.ToString()); - - StationOptions.PeakGustMinutes = ini.GetValue("Station", "PeakGustMinutes", 10); - if (StationOptions.PeakGustMinutes > 120) - { - StationOptions.PeakGustMinutes = 120; - } - - if (StationOptions.PeakGustMinutes == 0) - { - StationOptions.PeakGustMinutes = 1; - } - - PeakGustTime = new TimeSpan(StationOptions.PeakGustMinutes / 60, StationOptions.PeakGustMinutes % 60, 0); - - if ((StationType == StationTypes.VantagePro) || (StationType == StationTypes.VantagePro2)) - { - UVdecimaldefault = 1; - } - else - { - UVdecimaldefault = 0; - } - - UVdecimals = ini.GetValue("Station", "UVdecimals", UVdecimaldefault); - - StationOptions.NoSensorCheck = ini.GetValue("Station", "NoSensorCheck", false); - - StationOptions.CalculatedDP = ini.GetValue("Station", "CalculatedDP", false); - StationOptions.CalculatedWC = ini.GetValue("Station", "CalculatedWC", false); - RolloverHour = ini.GetValue("Station", "RolloverHour", 0); - Use10amInSummer = ini.GetValue("Station", "Use10amInSummer", true); - //ConfirmClose = ini.GetValue("Station", "ConfirmClose", false); - //CloseOnSuspend = ini.GetValue("Station", "CloseOnSuspend", false); - //RestartIfUnplugged = ini.GetValue("Station", "RestartIfUnplugged", false); - //RestartIfDataStops = ini.GetValue("Station", "RestartIfDataStops", false); - StationOptions.SyncTime = ini.GetValue("Station", "SyncDavisClock", false); - StationOptions.ClockSettingHour = ini.GetValue("Station", "ClockSettingHour", 4); - StationOptions.WS2300IgnoreStationClock = ini.GetValue("Station", "WS2300IgnoreStationClock", false); - //WS2300Sync = ini.GetValue("Station", "WS2300Sync", false); - StationOptions.LogExtraSensors = ini.GetValue("Station", "LogExtraSensors", false); - ReportDataStoppedErrors = ini.GetValue("Station", "ReportDataStoppedErrors", true); - ReportLostSensorContact = ini.GetValue("Station", "ReportLostSensorContact", true); - //NoFlashWetDryDayRecords = ini.GetValue("Station", "NoFlashWetDryDayRecords", false); - ErrorLogSpikeRemoval = ini.GetValue("Station", "ErrorLogSpikeRemoval", true); - DataLogInterval = ini.GetValue("Station", "DataLogInterval", 2); - // this is now an index - if (DataLogInterval > 5) - { - DataLogInterval = 2; - } - - FineOffsetOptions.SyncReads = ini.GetValue("Station", "SyncFOReads", true); - FineOffsetOptions.ReadAvoidPeriod = ini.GetValue("Station", "FOReadAvoidPeriod", 3); - FineOffsetOptions.ReadTime = ini.GetValue("Station", "FineOffsetReadTime", 150); - FineOffsetOptions.SetLoggerInterval = ini.GetValue("Station", "FineOffsetSetLoggerInterval", false); - FineOffsetOptions.VendorID = ini.GetValue("Station", "VendorID", -1); - FineOffsetOptions.ProductID = ini.GetValue("Station", "ProductID", -1); - - - Units.Wind = ini.GetValue("Station", "WindUnit", 0); - Units.Press = ini.GetValue("Station", "PressureUnit", 0); - - Units.Rain = ini.GetValue("Station", "RainUnit", 0); - Units.Temp = ini.GetValue("Station", "TempUnit", 0); - - StationOptions.RoundWindSpeed = ini.GetValue("Station", "RoundWindSpeed", false); - StationOptions.PrimaryAqSensor = ini.GetValue("Station", "PrimaryAqSensor", -1); - - - // Unit decimals - RainDPlaces = RainDPlaceDefaults[Units.Rain]; - TempDPlaces = TempDPlaceDefaults[Units.Temp]; - PressDPlaces = PressDPlaceDefaults[Units.Press]; - WindDPlaces = StationOptions.RoundWindSpeed ? 0 : WindDPlaceDefaults[Units.Wind]; - WindAvgDPlaces = WindDPlaces; - AirQualityDPlaces = 1; - - // Unit decimal overrides - WindDPlaces = ini.GetValue("Station", "WindSpeedDecimals", WindDPlaces); - WindAvgDPlaces = ini.GetValue("Station", "WindSpeedAvgDecimals", WindAvgDPlaces); - WindRunDPlaces = ini.GetValue("Station", "WindRunDecimals", WindRunDPlaces); - SunshineDPlaces = ini.GetValue("Station", "SunshineHrsDecimals", 1); - - if ((StationType == 0 || StationType == 1) && DavisOptions.IncrementPressureDP) - { - // Use one more DP for Davis stations - ++PressDPlaces; - } - PressDPlaces = ini.GetValue("Station", "PressDecimals", PressDPlaces); - RainDPlaces = ini.GetValue("Station", "RainDecimals", RainDPlaces); - TempDPlaces = ini.GetValue("Station", "TempDecimals", TempDPlaces); - UVDPlaces = ini.GetValue("Station", "UVDecimals", UVDPlaces); - AirQualityDPlaces = ini.GetValue("Station", "AirQualityDecimals", AirQualityDPlaces); - - - LocationName = ini.GetValue("Station", "LocName", ""); - LocationDesc = ini.GetValue("Station", "LocDesc", ""); - - YTDrain = ini.GetValue("Station", "YTDrain", 0.0); - YTDrainyear = ini.GetValue("Station", "YTDrainyear", 0); - - EwOptions.Interval = ini.GetValue("Station", "EWInterval", 1.0); - EwOptions.Filename = ini.GetValue("Station", "EWFile", ""); - //EWallowFF = ini.GetValue("Station", "EWFF", false); - //EWdisablecheckinit = ini.GetValue("Station", "EWdisablecheckinit", false); - //EWduplicatecheck = ini.GetValue("Station", "EWduplicatecheck", true); - EwOptions.MinPressMB = ini.GetValue("Station", "EWminpressureMB", 900); - EwOptions.MaxPressMB = ini.GetValue("Station", "EWmaxpressureMB", 1200); - EwOptions.MaxRainTipDiff = ini.GetValue("Station", "EWMaxRainTipDiff", 30); - EwOptions.PressOffset = ini.GetValue("Station", "EWpressureoffset", 9999.0); - - Spike.TempDiff = ini.GetValue("Station", "EWtempdiff", 999.0); - Spike.PressDiff = ini.GetValue("Station", "EWpressurediff", 999.0); - Spike.HumidityDiff = ini.GetValue("Station", "EWhumiditydiff", 999.0); - Spike.GustDiff = ini.GetValue("Station", "EWgustdiff", 999.0); - Spike.WindDiff = ini.GetValue("Station", "EWwinddiff", 999.0); - Spike.MaxRainRate = ini.GetValue("Station", "EWmaxRainRate", 999.0); - Spike.MaxHourlyRain = ini.GetValue("Station", "EWmaxHourlyRain", 999.0); - - LCMaxWind = ini.GetValue("Station", "LCMaxWind", 9999); - - RecordsBeganDate = ini.GetValue("Station", "StartDate", DateTime.Now.ToLongDateString()); - - LogMessage("Cumulus start date: " + RecordsBeganDate); - - ImetOptions.WaitTime = ini.GetValue("Station", "ImetWaitTime", 500); // delay to wait for a reply to a command - ImetOptions.ReadDelay = ini.GetValue("Station", "ImetReadDelay", 500); // delay between sending read live data commands - ImetOptions.UpdateLogPointer = ini.GetValue("Station", "ImetUpdateLogPointer", true); // keep the logger pointer pointing at last data read - ImetOptions.BaudRate = ini.GetValue("Station", "ImetBaudRate", 19200); - // Check we have a valid value - if (!ImetOptions.BaudRates.Contains(ImetOptions.BaudRate)) - { - // nope, that isn't allowed, set the default - LogMessage("Error, the value for ImetOptions.ImetBaudRate in the ini file " + ImetOptions.BaudRate + " is not valid, using default 19200."); - ImetOptions.BaudRate = 19200; - } - - UseDataLogger = ini.GetValue("Station", "UseDataLogger", true); - UseCumulusForecast = ini.GetValue("Station", "UseCumulusForecast", false); - HourlyForecast = ini.GetValue("Station", "HourlyForecast", false); - StationOptions.UseCumulusPresstrendstr = ini.GetValue("Station", "UseCumulusPresstrendstr", false); - UseWindChillCutoff = ini.GetValue("Station", "UseWindChillCutoff", false); - RecordSetTimeoutHrs = ini.GetValue("Station", "RecordSetTimeoutHrs", 24); - - SnowDepthHour = ini.GetValue("Station", "SnowDepthHour", 0); - - StationOptions.UseZeroBearing = ini.GetValue("Station", "UseZeroBearing", false); - - RainDayThreshold = ini.GetValue("Station", "RainDayThreshold", -1.0); - - FCpressinMB = ini.GetValue("Station", "FCpressinMB", true); - FClowpress = ini.GetValue("Station", "FClowpress", DEFAULTFCLOWPRESS); - FChighpress = ini.GetValue("Station", "FChighpress", DEFAULTFCHIGHPRESS); - FCPressureThreshold = ini.GetValue("Station", "FCPressureThreshold", -1.0); - - RainSeasonStart = ini.GetValue("Station", "RainSeasonStart", 1); - if (RainSeasonStart < 1 || RainSeasonStart > 12) - RainSeasonStart = 1; - ChillHourSeasonStart = ini.GetValue("Station", "ChillHourSeasonStart", 10); - if (ChillHourSeasonStart < 1 || ChillHourSeasonStart > 12) - ChillHourSeasonStart = 1; - ChillHourThreshold = ini.GetValue("Station", "ChillHourThreshold", -999.0); - - RG11Enabled = ini.GetValue("Station", "RG11Enabled", false); - RG11Port = ini.GetValue("Station", "RG11portName", DefaultComportName); - RG11TBRmode = ini.GetValue("Station", "RG11TBRmode", false); - RG11tipsize = ini.GetValue("Station", "RG11tipsize", 0.0); - RG11IgnoreFirst = ini.GetValue("Station", "RG11IgnoreFirst", false); - RG11DTRmode = ini.GetValue("Station", "RG11DTRmode", true); - - RG11Enabled2 = ini.GetValue("Station", "RG11Enabled2", false); - RG11Port2 = ini.GetValue("Station", "RG11port2Name", DefaultComportName); - RG11TBRmode2 = ini.GetValue("Station", "RG11TBRmode2", false); - RG11tipsize2 = ini.GetValue("Station", "RG11tipsize2", 0.0); - RG11IgnoreFirst2 = ini.GetValue("Station", "RG11IgnoreFirst2", false); - RG11DTRmode2 = ini.GetValue("Station", "RG11DTRmode2", true); - - if (ChillHourThreshold < -998) - { - ChillHourThreshold = Units.Temp == 0 ? 7 : 45; - } - - if (FCPressureThreshold < 0) - { - FCPressureThreshold = Units.Press == 2 ? 0.00295333727 : 0.1; - } - - //special_logging = ini.GetValue("Station", "SpecialLog", false); - //solar_logging = ini.GetValue("Station", "SolarLog", false); - - - //RTdisconnectcount = ini.GetValue("Station", "RTdisconnectcount", 0); - - WMR928TempChannel = ini.GetValue("Station", "WMR928TempChannel", 0); - - WMR200TempChannel = ini.GetValue("Station", "WMR200TempChannel", 1); - - ListWebTags = ini.GetValue("Station", "ListWebTags", false); - - // WeatherLink Live device settings - WllApiKey = ini.GetValue("WLL", "WLv2ApiKey", ""); - WllApiSecret = ini.GetValue("WLL", "WLv2ApiSecret", ""); - WllStationId = ini.GetValue("WLL", "WLStationId", -1); - //if (WllStationId == "-1") WllStationId = ""; - WLLAutoUpdateIpAddress = ini.GetValue("WLL", "AutoUpdateIpAddress", true); - WllBroadcastDuration = ini.GetValue("WLL", "BroadcastDuration", 1200); // Readonly setting, default 20 minutes - WllBroadcastPort = ini.GetValue("WLL", "BroadcastPort", 22222); // Readonly setting, default 22222 - WllPrimaryRain = ini.GetValue("WLL", "PrimaryRainTxId", 1); - WllPrimaryTempHum = ini.GetValue("WLL", "PrimaryTempHumTxId", 1); - WllPrimaryWind = ini.GetValue("WLL", "PrimaryWindTxId", 1); - WllPrimaryRain = ini.GetValue("WLL", "PrimaryRainTxId", 1); - WllPrimarySolar = ini.GetValue("WLL", "PrimarySolarTxId", 0); - WllPrimaryUV = ini.GetValue("WLL", "PrimaryUvTxId", 0); - WllExtraSoilTempTx1 = ini.GetValue("WLL", "ExtraSoilTempTxId1", 0); - WllExtraSoilTempIdx1 = ini.GetValue("WLL", "ExtraSoilTempIdx1", 1); - WllExtraSoilTempTx2 = ini.GetValue("WLL", "ExtraSoilTempTxId2", 0); - WllExtraSoilTempIdx2 = ini.GetValue("WLL", "ExtraSoilTempIdx2", 2); - WllExtraSoilTempTx3 = ini.GetValue("WLL", "ExtraSoilTempTxId3", 0); - WllExtraSoilTempIdx3 = ini.GetValue("WLL", "ExtraSoilTempIdx3", 3); - WllExtraSoilTempTx4 = ini.GetValue("WLL", "ExtraSoilTempTxId4", 0); - WllExtraSoilTempIdx4 = ini.GetValue("WLL", "ExtraSoilTempIdx4", 4); - WllExtraSoilMoistureTx1 = ini.GetValue("WLL", "ExtraSoilMoistureTxId1", 0); - WllExtraSoilMoistureIdx1 = ini.GetValue("WLL", "ExtraSoilMoistureIdx1", 1); - WllExtraSoilMoistureTx2 = ini.GetValue("WLL", "ExtraSoilMoistureTxId2", 0); - WllExtraSoilMoistureIdx2 = ini.GetValue("WLL", "ExtraSoilMoistureIdx2", 2); - WllExtraSoilMoistureTx3 = ini.GetValue("WLL", "ExtraSoilMoistureTxId3", 0); - WllExtraSoilMoistureIdx3 = ini.GetValue("WLL", "ExtraSoilMoistureIdx3", 3); - WllExtraSoilMoistureTx4 = ini.GetValue("WLL", "ExtraSoilMoistureTxId4", 0); - WllExtraSoilMoistureIdx4 = ini.GetValue("WLL", "ExtraSoilMoistureIdx4", 4); - WllExtraLeafTx1 = ini.GetValue("WLL", "ExtraLeafTxId1", 0); - WllExtraLeafIdx1 = ini.GetValue("WLL", "ExtraLeafIdx1", 1); - WllExtraLeafTx2 = ini.GetValue("WLL", "ExtraLeafTxId2", 0); - WllExtraLeafIdx2 = ini.GetValue("WLL", "ExtraLeafIdx2", 2); - for (int i = 1; i <=8; i++) - { - WllExtraTempTx[i - 1] = ini.GetValue("WLL", "ExtraTempTxId" + i, 0); - WllExtraHumTx[i - 1] = ini.GetValue("WLL", "ExtraHumOnTxId" + i, false); - } - - // GW1000 settings - Gw1000IpAddress = ini.GetValue("GW1000", "IPAddress", "0.0.0.0"); - Gw1000MacAddress = ini.GetValue("GW1000", "MACAddress", ""); - Gw1000AutoUpdateIpAddress = ini.GetValue("GW1000", "AutoUpdateIpAddress", true); - - // AirLink settings - // We have to convert previous per AL IsNode config to global - // So check if the global value exists - if (ini.ValueExists("AirLink", "IsWllNode")) - { - AirLinkIsNode = ini.GetValue("AirLink", "IsWllNode", false); - } - else - { - AirLinkIsNode = ini.GetValue("AirLink", "In-IsNode", false) || ini.GetValue("AirLink", "Out-IsNode", false); - } - AirLinkApiKey = ini.GetValue("AirLink", "WLv2ApiKey", ""); - AirLinkApiSecret = ini.GetValue("AirLink", "WLv2ApiSecret", ""); - AirLinkAutoUpdateIpAddress = ini.GetValue("AirLink", "AutoUpdateIpAddress", true); - AirLinkInEnabled = ini.GetValue("AirLink", "In-Enabled", false); - AirLinkInIPAddr = ini.GetValue("AirLink", "In-IPAddress", "0.0.0.0"); - AirLinkInStationId = ini.GetValue("AirLink", "In-WLStationId", -1); - if (AirLinkInStationId == -1 && AirLinkIsNode) AirLinkInStationId = WllStationId; - AirLinkInHostName = ini.GetValue("AirLink", "In-Hostname", ""); - - AirLinkOutEnabled = ini.GetValue("AirLink", "Out-Enabled", false); - AirLinkOutIPAddr = ini.GetValue("AirLink", "Out-IPAddress", "0.0.0.0"); - AirLinkOutStationId = ini.GetValue("AirLink", "Out-WLStationId", -1); - if (AirLinkOutStationId == -1 && AirLinkIsNode) AirLinkOutStationId = WllStationId; - AirLinkOutHostName = ini.GetValue("AirLink", "Out-Hostname", ""); - - airQualityIndex = ini.GetValue("AirLink", "AQIformula", 0); - - FtpHostname = ini.GetValue("FTP site", "Host", ""); - FtpHostPort = ini.GetValue("FTP site", "Port", 21); - FtpUsername = ini.GetValue("FTP site", "Username", ""); - FtpPassword = ini.GetValue("FTP site", "Password", ""); - FtpDirectory = ini.GetValue("FTP site", "Directory", ""); - - ActiveFTPMode = ini.GetValue("FTP site", "ActiveFTP", false); - Sslftp = (FtpProtocols)ini.GetValue("FTP site", "Sslftp", 0); - // BUILD 3092 - added alternate SFTP authenication options - SshftpAuthentication = ini.GetValue("FTP site", "SshFtpAuthentication", "password"); // valid options: password, psk, password_psk - if (!sshAuthenticationVals.Any(SshftpAuthentication.Contains)) - { - SshftpAuthentication = "password"; - LogMessage($"Error, invalid SshFtpAuthentication value in Cumulus.ini [{SshftpAuthentication}], defaulting to Password."); - } - SshftpPskFile = ini.GetValue("FTP site", "SshFtpPskFile", ""); - if (SshftpPskFile.Length > 0 && (SshftpAuthentication == "psk" || SshftpAuthentication == "password_psk") && !File.Exists(SshftpPskFile)) - { - SshftpPskFile = ""; - LogMessage($"Error, file name specified by SshFtpPskFile value in Cumulus.ini does not exist [{SshftpPskFile}], defaulting to None."); - } - DisableFtpsEPSV = ini.GetValue("FTP site", "DisableEPSV", false); - DisableFtpsExplicit = ini.GetValue("FTP site", "DisableFtpsExplicit", false); - FTPlogging = ini.GetValue("FTP site", "FTPlogging", false); - RealtimeEnabled = ini.GetValue("FTP site", "EnableRealtime", false); - RealtimeFTPEnabled = ini.GetValue("FTP site", "RealtimeFTPEnabled", false); - - RealtimeFiles[0].Create = ini.GetValue("FTP site", "RealtimeTxtCreate", false); - RealtimeFiles[0].FTP = RealtimeFiles[0].Create && ini.GetValue("FTP site", "RealtimeTxtFTP", false); - RealtimeFiles[1].Create = ini.GetValue("FTP site", "RealtimeGaugesTxtCreate", false); - RealtimeFiles[1].FTP = RealtimeFiles[1].Create && ini.GetValue("FTP site", "RealtimeGaugesTxtFTP", false); - - RealtimeInterval = ini.GetValue("FTP site", "RealtimeInterval", 30000); - if (RealtimeInterval < 1) { RealtimeInterval = 1; } - //RealtimeTimer.Change(0,RealtimeInterval); - - WebAutoUpdate = ini.GetValue("FTP site", "AutoUpdate", false); - // Have to allow for upgrade, set interval enabled to old WebAutoUpdate - if (ini.ValueExists("FTP site", "IntervalEnabled")) - { - WebIntervalEnabled = ini.GetValue("FTP site", "IntervalEnabled", false); - } - else - { - WebIntervalEnabled = WebAutoUpdate; - } - - UpdateInterval = ini.GetValue("FTP site", "UpdateInterval", DefaultWebUpdateInterval); - if (UpdateInterval<1) { UpdateInterval = 1; } - SynchronisedWebUpdate = (60 % UpdateInterval == 0); - - var IncludeStandardFiles = false; - if (ini.ValueExists("FTP site", "IncludeSTD")) - { - IncludeStandardFiles = ini.GetValue("FTP site", "IncludeSTD", false); - } - for (var i = 0; i < StdWebFiles.Length; i++) - { - var keyNameCreate = "Create-" + StdWebFiles[i].LocalFileName.Split('.')[0]; - var keyNameFTP = "Ftp-" + StdWebFiles[i].LocalFileName.Split('.')[0]; - StdWebFiles[i].Create = ini.GetValue("FTP site", keyNameCreate, IncludeStandardFiles); - StdWebFiles[i].FTP = ini.GetValue("FTP site", keyNameFTP, IncludeStandardFiles); - } - - var IncludeGraphDataFiles = false; - if (ini.ValueExists("FTP site", "IncludeGraphDataFiles")) - { - IncludeGraphDataFiles = ini.GetValue("FTP site", "IncludeGraphDataFiles", true); - } - for (var i = 0; i < GraphDataFiles.Length; i++) - { - var keyNameCreate = "Create-" + GraphDataFiles[i].LocalFileName.Split('.')[0]; - var keyNameFTP = "Ftp-" + GraphDataFiles[i].LocalFileName.Split('.')[0]; - GraphDataFiles[i].Create = ini.GetValue("FTP site", keyNameCreate, IncludeGraphDataFiles); - GraphDataFiles[i].FTP = ini.GetValue("FTP site", keyNameFTP, IncludeGraphDataFiles); - } - for (var i = 0; i < GraphDataEodFiles.Length; i++) - { - var keyNameCreate = "Create-" + GraphDataEodFiles[i].LocalFileName.Split('.')[0]; - var keyNameFTP = "Ftp-" + GraphDataEodFiles[i].LocalFileName.Split('.')[0]; - GraphDataEodFiles[i].Create = ini.GetValue("FTP site", keyNameCreate, IncludeGraphDataFiles); - GraphDataEodFiles[i].FTP = ini.GetValue("FTP site", keyNameFTP, IncludeGraphDataFiles); - } - - IncludeMoonImage = ini.GetValue("FTP site", "IncludeMoonImage", false); - - FTPRename = ini.GetValue("FTP site", "FTPRename", false); - UTF8encode = ini.GetValue("FTP site", "UTF8encode", true); - DeleteBeforeUpload = ini.GetValue("FTP site", "DeleteBeforeUpload", false); - - //MaxFTPconnectRetries = ini.GetValue("FTP site", "MaxFTPconnectRetries", 3); - - for (int i = 0; i < numextrafiles; i++) - { - ExtraFiles[i].local = ini.GetValue("FTP site", "ExtraLocal" + i, ""); - ExtraFiles[i].remote = ini.GetValue("FTP site", "ExtraRemote" + i, ""); - ExtraFiles[i].process = ini.GetValue("FTP site", "ExtraProcess" + i, false); - ExtraFiles[i].binary = ini.GetValue("FTP site", "ExtraBinary" + i, false); - ExtraFiles[i].realtime = ini.GetValue("FTP site", "ExtraRealtime" + i, false); - ExtraFiles[i].FTP = ini.GetValue("FTP site", "ExtraFTP" + i, true); - ExtraFiles[i].UTF8 = ini.GetValue("FTP site", "ExtraUTF" + i, false); - ExtraFiles[i].endofday = ini.GetValue("FTP site", "ExtraEOD" + i, false); - } - - ExternalProgram = ini.GetValue("FTP site", "ExternalProgram", ""); - RealtimeProgram = ini.GetValue("FTP site", "RealtimeProgram", ""); - DailyProgram = ini.GetValue("FTP site", "DailyProgram", ""); - ExternalParams = ini.GetValue("FTP site", "ExternalParams", ""); - RealtimeParams = ini.GetValue("FTP site", "RealtimeParams", ""); - DailyParams = ini.GetValue("FTP site", "DailyParams", ""); - - ForumURL = ini.GetValue("Web Site", "ForumURL", ForumDefault); - WebcamURL = ini.GetValue("Web Site", "WebcamURL", WebcamDefault); - - CloudBaseInFeet = ini.GetValue("Station", "CloudBaseInFeet", true); - - GraphDays = ini.GetValue("Graphs", "ChartMaxDays", 31); - GraphHours = ini.GetValue("Graphs", "GraphHours", 72); - MoonImageEnabled = ini.GetValue("Graphs", "MoonImageEnabled", false); - MoonImageSize = ini.GetValue("Graphs", "MoonImageSize", 100); - if (MoonImageSize < 10) - MoonImageSize = 10; - MoonImageFtpDest = ini.GetValue("Graphs", "MoonImageFtpDest", "images/moon.png"); - GraphOptions.TempVisible = ini.GetValue("Graphs", "TempVisible", true); - GraphOptions.InTempVisible = ini.GetValue("Graphs", "InTempVisible", true); - GraphOptions.HIVisible = ini.GetValue("Graphs", "HIVisible", true); - GraphOptions.DPVisible = ini.GetValue("Graphs", "DPVisible", true); - GraphOptions.WCVisible = ini.GetValue("Graphs", "WCVisible", true); - GraphOptions.AppTempVisible = ini.GetValue("Graphs", "AppTempVisible", true); - GraphOptions.FeelsLikeVisible = ini.GetValue("Graphs", "FeelsLikeVisible", true); - GraphOptions.HumidexVisible = ini.GetValue("Graphs", "HumidexVisible", true); - GraphOptions.InHumVisible = ini.GetValue("Graphs", "InHumVisible", true); - GraphOptions.OutHumVisible = ini.GetValue("Graphs", "OutHumVisible", true); - GraphOptions.UVVisible = ini.GetValue("Graphs", "UVVisible", true); - GraphOptions.SolarVisible = ini.GetValue("Graphs", "SolarVisible", true); - GraphOptions.SunshineVisible = ini.GetValue("Graphs", "SunshineVisible", true); - GraphOptions.DailyAvgTempVisible = ini.GetValue("Graphs", "DailyAvgTempVisible", true); - GraphOptions.DailyMaxTempVisible = ini.GetValue("Graphs", "DailyMaxTempVisible", true); - GraphOptions.DailyMinTempVisible = ini.GetValue("Graphs", "DailyMinTempVisible", true); - GraphOptions.GrowingDegreeDaysVisible1 = ini.GetValue("Graphs", "GrowingDegreeDaysVisible1", true); - GraphOptions.GrowingDegreeDaysVisible2 = ini.GetValue("Graphs", "GrowingDegreeDaysVisible2", true); - GraphOptions.TempSumVisible0 = ini.GetValue("Graphs", "TempSumVisible0", true); - GraphOptions.TempSumVisible1 = ini.GetValue("Graphs", "TempSumVisible1", true); - GraphOptions.TempSumVisible2 = ini.GetValue("Graphs", "TempSumVisible2", true); - - - Wund.ID = ini.GetValue("Wunderground", "ID", ""); - Wund.PW = ini.GetValue("Wunderground", "Password", ""); - Wund.Enabled = ini.GetValue("Wunderground", "Enabled", false); - Wund.RapidFireEnabled = ini.GetValue("Wunderground", "RapidFire", false); - Wund.Interval = ini.GetValue("Wunderground", "Interval", Wund.DefaultInterval); - //WundHTTPLogging = ini.GetValue("Wunderground", "Logging", false); - Wund.SendUV = ini.GetValue("Wunderground", "SendUV", false); - Wund.SendSolar = ini.GetValue("Wunderground", "SendSR", false); - Wund.SendIndoor = ini.GetValue("Wunderground", "SendIndoor", false); - Wund.SendSoilTemp1 = ini.GetValue("Wunderground", "SendSoilTemp1", false); - Wund.SendSoilTemp2 = ini.GetValue("Wunderground", "SendSoilTemp2", false); - Wund.SendSoilTemp3 = ini.GetValue("Wunderground", "SendSoilTemp3", false); - Wund.SendSoilTemp4 = ini.GetValue("Wunderground", "SendSoilTemp4", false); - Wund.SendSoilMoisture1 = ini.GetValue("Wunderground", "SendSoilMoisture1", false); - Wund.SendSoilMoisture2 = ini.GetValue("Wunderground", "SendSoilMoisture2", false); - Wund.SendSoilMoisture3 = ini.GetValue("Wunderground", "SendSoilMoisture3", false); - Wund.SendSoilMoisture4 = ini.GetValue("Wunderground", "SendSoilMoisture4", false); - Wund.SendLeafWetness1 = ini.GetValue("Wunderground", "SendLeafWetness1", false); - Wund.SendLeafWetness2 = ini.GetValue("Wunderground", "SendLeafWetness2", false); - Wund.SendAirQuality = ini.GetValue("Wunderground", "SendAirQuality", false); - Wund.SendAverage = ini.GetValue("Wunderground", "SendAverage", false); - Wund.CatchUp = ini.GetValue("Wunderground", "CatchUp", true); - - Wund.SynchronisedUpdate = !Wund.RapidFireEnabled; - - Windy.ApiKey = ini.GetValue("Windy", "APIkey", ""); - Windy.StationIdx = ini.GetValue("Windy", "StationIdx", 0); - Windy.Enabled = ini.GetValue("Windy", "Enabled", false); - Windy.Interval = ini.GetValue("Windy", "Interval", Windy.DefaultInterval); - if (Windy.Interval < 5) { Windy.Interval = 5; } - //WindyHTTPLogging = ini.GetValue("Windy", "Logging", false); - Windy.SendUV = ini.GetValue("Windy", "SendUV", false); - Windy.SendSolar = ini.GetValue("Windy", "SendSolar", false); - Windy.CatchUp = ini.GetValue("Windy", "CatchUp", false); - - AWEKAS.ID = ini.GetValue("Awekas", "User", ""); - AWEKAS.PW = ini.GetValue("Awekas", "Password", ""); - AWEKAS.Enabled = ini.GetValue("Awekas", "Enabled", false); - AWEKAS.Interval = ini.GetValue("Awekas", "Interval", AWEKAS.DefaultInterval); - if (AWEKAS.Interval < 15) { AWEKAS.Interval = 15; } - AWEKAS.Lang = ini.GetValue("Awekas", "Language", "en"); - AWEKAS.OriginalInterval = AWEKAS.Interval; - AWEKAS.SendUV = ini.GetValue("Awekas", "SendUV", false); - AWEKAS.SendSolar = ini.GetValue("Awekas", "SendSR", false); - AWEKAS.SendSoilTemp = ini.GetValue("Awekas", "SendSoilTemp", false); - AWEKAS.SendIndoor = ini.GetValue("Awekas", "SendIndoor", false); - AWEKAS.SendSoilMoisture = ini.GetValue("Awekas", "SendSoilMoisture", false); - AWEKAS.SendLeafWetness = ini.GetValue("Awekas", "SendLeafWetness", false); - AWEKAS.SendAirQuality = ini.GetValue("Awekas", "SendAirQuality", false); - - AWEKAS.SynchronisedUpdate = (AWEKAS.Interval % 60 == 0); - - WindGuru.ID = ini.GetValue("WindGuru", "StationUID", ""); - WindGuru.PW = ini.GetValue("WindGuru", "Password", ""); - WindGuru.Enabled = ini.GetValue("WindGuru", "Enabled", false); - WindGuru.Interval = ini.GetValue("WindGuru", "Interval", WindGuru.DefaultInterval); - if (WindGuru.Interval < 1) { WindGuru.Interval = 1; } - WindGuru.SendRain = ini.GetValue("WindGuru", "SendRain", false); - - WCloud.ID = ini.GetValue("WeatherCloud", "Wid", ""); - WCloud.PW = ini.GetValue("WeatherCloud", "Key", ""); - WCloud.Enabled = ini.GetValue("WeatherCloud", "Enabled", false); - WCloud.Interval = ini.GetValue("WeatherCloud", "Interval", WCloud.DefaultInterval); - WCloud.SendUV = ini.GetValue("WeatherCloud", "SendUV", false); - WCloud.SendSolar = ini.GetValue("WeatherCloud", "SendSR", false); - WCloud.SendAirQuality = ini.GetValue("WeatherCloud", "SendAirQuality", false); - WCloud.SendSoilMoisture = ini.GetValue("WeatherCloud", "SendSoilMoisture", false); - WCloud.SoilMoistureSensor= ini.GetValue("WeatherCloud", "SoilMoistureSensor", 1); - WCloud.SendLeafWetness = ini.GetValue("WeatherCloud", "SendLeafWetness", false); - WCloud.LeafWetnessSensor = ini.GetValue("WeatherCloud", "LeafWetnessSensor", 1); - - Twitter.ID = ini.GetValue("Twitter", "User", ""); - Twitter.PW = ini.GetValue("Twitter", "Password", ""); - Twitter.Enabled = ini.GetValue("Twitter", "Enabled", false); - Twitter.Interval = ini.GetValue("Twitter", "Interval", 60); - if (Twitter.Interval < 1) { Twitter.Interval = 1; } - Twitter.OauthToken = ini.GetValue("Twitter", "OauthToken", "unknown"); - Twitter.OauthTokenSecret = ini.GetValue("Twitter", "OauthTokenSecret", "unknown"); - Twitter.SendLocation = ini.GetValue("Twitter", "SendLocation", true); - - //if HTTPLogging then - // MainForm.WUHTTP.IcsLogger = MainForm.HTTPlogger; - - PWS.ID = ini.GetValue("PWSweather", "ID", ""); - PWS.PW = ini.GetValue("PWSweather", "Password", ""); - PWS.Enabled = ini.GetValue("PWSweather", "Enabled", false); - PWS.Interval = ini.GetValue("PWSweather", "Interval", PWS.DefaultInterval); - if (PWS.Interval < 1) { PWS.Interval = 1; } - PWS.SendUV = ini.GetValue("PWSweather", "SendUV", false); - PWS.SendSolar = ini.GetValue("PWSweather", "SendSR", false); - PWS.CatchUp = ini.GetValue("PWSweather", "CatchUp", true); - - WOW.ID = ini.GetValue("WOW", "ID", ""); - WOW.PW = ini.GetValue("WOW", "Password", ""); - WOW.Enabled = ini.GetValue("WOW", "Enabled", false); - WOW.Interval = ini.GetValue("WOW", "Interval", WOW.DefaultInterval); - if (WOW.Interval < 1) { WOW.Interval = 1; } - WOW.SendUV = ini.GetValue("WOW", "SendUV", false); - WOW.SendSolar = ini.GetValue("WOW", "SendSR", false); - WOW.CatchUp = ini.GetValue("WOW", "CatchUp", true); - - APRS.ID = ini.GetValue("APRS", "ID", ""); - APRS.PW = ini.GetValue("APRS", "pass", "-1"); - APRS.Server = ini.GetValue("APRS", "server", "cwop.aprs.net"); - APRS.Port = ini.GetValue("APRS", "port", 14580); - APRS.Enabled = ini.GetValue("APRS", "Enabled", false); - APRS.Interval = ini.GetValue("APRS", "Interval", APRS.DefaultInterval); - if (APRS.Interval < 1) { APRS.Interval = 1; } - APRS.HumidityCutoff = ini.GetValue("APRS", "APRSHumidityCutoff", false); - APRS.SendSolar = ini.GetValue("APRS", "SendSR", false); - - OpenWeatherMap.Enabled = ini.GetValue("OpenWeatherMap", "Enabled", false); - OpenWeatherMap.CatchUp = ini.GetValue("OpenWeatherMap", "CatchUp", true); - OpenWeatherMap.PW = ini.GetValue("OpenWeatherMap", "APIkey", ""); - OpenWeatherMap.ID = ini.GetValue("OpenWeatherMap", "StationId", ""); - OpenWeatherMap.Interval = ini.GetValue("OpenWeatherMap", "Interval", OpenWeatherMap.DefaultInterval); - - MQTT.Server = ini.GetValue("MQTT", "Server", ""); - MQTT.Port = ini.GetValue("MQTT", "Port", 1883); - MQTT.IpVersion = ini.GetValue("MQTT", "IPversion", 0); // 0 = unspecified, 4 = force IPv4, 6 = force IPv6 - if (MQTT.IpVersion != 0 && MQTT.IpVersion != 4 && MQTT.IpVersion != 6) - MQTT.IpVersion = 0; - MQTT.UseTLS = ini.GetValue("MQTT", "UseTLS", false); - MQTT.Username = ini.GetValue("MQTT", "Username", ""); - MQTT.Password = ini.GetValue("MQTT", "Password", ""); - MQTT.EnableDataUpdate = ini.GetValue("MQTT", "EnableDataUpdate", false); - MQTT.UpdateTopic = ini.GetValue("MQTT", "UpdateTopic", "CumulusMX/DataUpdate"); - MQTT.UpdateTemplate = ini.GetValue("MQTT", "UpdateTemplate", "DataUpdateTemplate.txt"); - MQTT.UpdateRetained = ini.GetValue("MQTT", "UpdateRetained", false); - MQTT.EnableInterval = ini.GetValue("MQTT", "EnableInterval", false); - MQTT.IntervalTime = ini.GetValue("MQTT", "IntervalTime", 600); // default to 10 minutes - MQTT.IntervalTopic = ini.GetValue("MQTT", "IntervalTopic", "CumulusMX/Interval"); - MQTT.IntervalTemplate = ini.GetValue("MQTT", "IntervalTemplate", "IntervalTemplate.txt"); - MQTT.IntervalRetained = ini.GetValue("MQTT", "IntervalRetained", false); - - LowTempAlarm.Value = ini.GetValue("Alarms", "alarmlowtemp", 0.0); - LowTempAlarm.Enabled = ini.GetValue("Alarms", "LowTempAlarmSet", false); - LowTempAlarm.Sound = ini.GetValue("Alarms", "LowTempAlarmSound", false); - LowTempAlarm.SoundFile = ini.GetValue("Alarms", "LowTempAlarmSoundFile", DefaultSoundFile); - if (LowTempAlarm.SoundFile.Contains(DefaultSoundFileOld)) LowTempAlarm.SoundFile = DefaultSoundFile; - LowTempAlarm.Notify = ini.GetValue("Alarms", "LowTempAlarmNotify", false); - LowTempAlarm.Email = ini.GetValue("Alarms", "LowTempAlarmEmail", false); - LowTempAlarm.Latch = ini.GetValue("Alarms", "LowTempAlarmLatch", false); - LowTempAlarm.LatchHours = ini.GetValue("Alarms", "LowTempAlarmLatchHours", 24); - - HighTempAlarm.Value = ini.GetValue("Alarms", "alarmhightemp", 0.0); - HighTempAlarm.Enabled = ini.GetValue("Alarms", "HighTempAlarmSet", false); - HighTempAlarm.Sound = ini.GetValue("Alarms", "HighTempAlarmSound", false); - HighTempAlarm.SoundFile = ini.GetValue("Alarms", "HighTempAlarmSoundFile", DefaultSoundFile); - if (HighTempAlarm.SoundFile.Contains(DefaultSoundFileOld)) HighTempAlarm.SoundFile = DefaultSoundFile; - HighTempAlarm.Notify = ini.GetValue("Alarms", "HighTempAlarmNotify", false); - HighTempAlarm.Email = ini.GetValue("Alarms", "HighTempAlarmEmail", false); - HighTempAlarm.Latch = ini.GetValue("Alarms", "HighTempAlarmLatch", false); - HighTempAlarm.LatchHours = ini.GetValue("Alarms", "HighTempAlarmLatchHours", 24); - - TempChangeAlarm.Value = ini.GetValue("Alarms", "alarmtempchange", 0.0); - TempChangeAlarm.Enabled = ini.GetValue("Alarms", "TempChangeAlarmSet", false); - TempChangeAlarm.Sound = ini.GetValue("Alarms", "TempChangeAlarmSound", false); - TempChangeAlarm.SoundFile = ini.GetValue("Alarms", "TempChangeAlarmSoundFile", DefaultSoundFile); - if (TempChangeAlarm.SoundFile.Contains(DefaultSoundFileOld)) TempChangeAlarm.SoundFile = DefaultSoundFile; - TempChangeAlarm.Notify = ini.GetValue("Alarms", "TempChangeAlarmNotify", false); - TempChangeAlarm.Email = ini.GetValue("Alarms", "TempChangeAlarmEmail", false); - TempChangeAlarm.Latch = ini.GetValue("Alarms", "TempChangeAlarmLatch", false); - TempChangeAlarm.LatchHours = ini.GetValue("Alarms", "TempChangeAlarmLatchHours", 24); - - LowPressAlarm.Value = ini.GetValue("Alarms", "alarmlowpress", 0.0); - LowPressAlarm.Enabled = ini.GetValue("Alarms", "LowPressAlarmSet", false); - LowPressAlarm.Sound = ini.GetValue("Alarms", "LowPressAlarmSound", false); - LowPressAlarm.SoundFile = ini.GetValue("Alarms", "LowPressAlarmSoundFile", DefaultSoundFile); - if (LowPressAlarm.SoundFile.Contains(DefaultSoundFileOld)) LowPressAlarm.SoundFile = DefaultSoundFile; - LowPressAlarm.Notify = ini.GetValue("Alarms", "LowPressAlarmNotify", false); - LowPressAlarm.Email = ini.GetValue("Alarms", "LowPressAlarmEmail", false); - LowPressAlarm.Latch = ini.GetValue("Alarms", "LowPressAlarmLatch", false); - LowPressAlarm.LatchHours = ini.GetValue("Alarms", "LowPressAlarmLatchHours", 24); - - HighPressAlarm.Value = ini.GetValue("Alarms", "alarmhighpress", 0.0); - HighPressAlarm.Enabled = ini.GetValue("Alarms", "HighPressAlarmSet", false); - HighPressAlarm.Sound = ini.GetValue("Alarms", "HighPressAlarmSound", false); - HighPressAlarm.SoundFile = ini.GetValue("Alarms", "HighPressAlarmSoundFile", DefaultSoundFile); - if (HighPressAlarm.SoundFile.Contains(DefaultSoundFileOld)) HighPressAlarm.SoundFile = DefaultSoundFile; - HighPressAlarm.Notify = ini.GetValue("Alarms", "HighPressAlarmNotify", false); - HighPressAlarm.Email = ini.GetValue("Alarms", "HighPressAlarmEmail", false); - HighPressAlarm.Latch = ini.GetValue("Alarms", "HighPressAlarmLatch", false); - HighPressAlarm.LatchHours = ini.GetValue("Alarms", "HighPressAlarmLatchHours", 24); - - PressChangeAlarm.Value = ini.GetValue("Alarms", "alarmpresschange", 0.0); - PressChangeAlarm.Enabled = ini.GetValue("Alarms", "PressChangeAlarmSet", false); - PressChangeAlarm.Sound = ini.GetValue("Alarms", "PressChangeAlarmSound", false); - PressChangeAlarm.SoundFile = ini.GetValue("Alarms", "PressChangeAlarmSoundFile", DefaultSoundFile); - if (PressChangeAlarm.SoundFile.Contains(DefaultSoundFileOld)) PressChangeAlarm.SoundFile = DefaultSoundFile; - PressChangeAlarm.Notify = ini.GetValue("Alarms", "PressChangeAlarmNotify", false); - PressChangeAlarm.Email = ini.GetValue("Alarms", "PressChangeAlarmEmail", false); - PressChangeAlarm.Latch = ini.GetValue("Alarms", "PressChangeAlarmLatch", false); - PressChangeAlarm.LatchHours = ini.GetValue("Alarms", "PressChangeAlarmLatchHours", 24); - - HighRainTodayAlarm.Value = ini.GetValue("Alarms", "alarmhighraintoday", 0.0); - HighRainTodayAlarm.Enabled = ini.GetValue("Alarms", "HighRainTodayAlarmSet", false); - HighRainTodayAlarm.Sound = ini.GetValue("Alarms", "HighRainTodayAlarmSound", false); - HighRainTodayAlarm.SoundFile = ini.GetValue("Alarms", "HighRainTodayAlarmSoundFile", DefaultSoundFile); - if (HighRainTodayAlarm.SoundFile.Contains(DefaultSoundFileOld)) HighRainTodayAlarm.SoundFile = DefaultSoundFile; - HighRainTodayAlarm.Notify = ini.GetValue("Alarms", "HighRainTodayAlarmNotify", false); - HighRainTodayAlarm.Email = ini.GetValue("Alarms", "HighRainTodayAlarmEmail", false); - HighRainTodayAlarm.Latch = ini.GetValue("Alarms", "HighRainTodayAlarmLatch", false); - HighRainTodayAlarm.LatchHours = ini.GetValue("Alarms", "HighRainTodayAlarmLatchHours", 24); - - HighRainRateAlarm.Value = ini.GetValue("Alarms", "alarmhighrainrate", 0.0); - HighRainRateAlarm.Enabled = ini.GetValue("Alarms", "HighRainRateAlarmSet", false); - HighRainRateAlarm.Sound = ini.GetValue("Alarms", "HighRainRateAlarmSound", false); - HighRainRateAlarm.SoundFile = ini.GetValue("Alarms", "HighRainRateAlarmSoundFile", DefaultSoundFile); - if (HighRainRateAlarm.SoundFile.Contains(DefaultSoundFileOld)) HighRainRateAlarm.SoundFile = DefaultSoundFile; - HighRainRateAlarm.Notify = ini.GetValue("Alarms", "HighRainRateAlarmNotify", false); - HighRainRateAlarm.Email = ini.GetValue("Alarms", "HighRainRateAlarmEmail", false); - HighRainRateAlarm.Latch = ini.GetValue("Alarms", "HighRainRateAlarmLatch", false); - HighRainRateAlarm.LatchHours = ini.GetValue("Alarms", "HighRainRateAlarmLatchHours", 24); - - HighGustAlarm.Value = ini.GetValue("Alarms", "alarmhighgust", 0.0); - HighGustAlarm.Enabled = ini.GetValue("Alarms", "HighGustAlarmSet", false); - HighGustAlarm.Sound = ini.GetValue("Alarms", "HighGustAlarmSound", false); - HighGustAlarm.SoundFile = ini.GetValue("Alarms", "HighGustAlarmSoundFile", DefaultSoundFile); - if (HighGustAlarm.SoundFile.Contains(DefaultSoundFileOld)) HighGustAlarm.SoundFile = DefaultSoundFile; - HighGustAlarm.Notify = ini.GetValue("Alarms", "HighGustAlarmNotify", false); - HighGustAlarm.Email = ini.GetValue("Alarms", "HighGustAlarmEmail", false); - HighGustAlarm.Latch = ini.GetValue("Alarms", "HighGustAlarmLatch", false); - HighGustAlarm.LatchHours = ini.GetValue("Alarms", "HighGustAlarmLatchHours", 24); - - HighWindAlarm.Value = ini.GetValue("Alarms", "alarmhighwind", 0.0); - HighWindAlarm.Enabled = ini.GetValue("Alarms", "HighWindAlarmSet", false); - HighWindAlarm.Sound = ini.GetValue("Alarms", "HighWindAlarmSound", false); - HighWindAlarm.SoundFile = ini.GetValue("Alarms", "HighWindAlarmSoundFile", DefaultSoundFile); - if (HighWindAlarm.SoundFile.Contains(DefaultSoundFileOld)) HighWindAlarm.SoundFile = DefaultSoundFile; - HighWindAlarm.Notify = ini.GetValue("Alarms", "HighWindAlarmNotify", false); - HighWindAlarm.Email = ini.GetValue("Alarms", "HighWindAlarmEmail", false); - HighWindAlarm.Latch = ini.GetValue("Alarms", "HighWindAlarmLatch", false); - HighWindAlarm.LatchHours = ini.GetValue("Alarms", "HighWindAlarmLatchHours", 24); - - SensorAlarm.Enabled = ini.GetValue("Alarms", "SensorAlarmSet", false); - SensorAlarm.Sound = ini.GetValue("Alarms", "SensorAlarmSound", false); - SensorAlarm.SoundFile = ini.GetValue("Alarms", "SensorAlarmSoundFile", DefaultSoundFile); - if (SensorAlarm.SoundFile.Contains(DefaultSoundFileOld)) SensorAlarm.SoundFile = DefaultSoundFile; - SensorAlarm.Notify = ini.GetValue("Alarms", "SensorAlarmNotify", false); - SensorAlarm.Email = ini.GetValue("Alarms", "SensorAlarmEmail", false); - SensorAlarm.Latch = ini.GetValue("Alarms", "SensorAlarmLatch", false); - SensorAlarm.LatchHours = ini.GetValue("Alarms", "SensorAlarmLatchHours", 24); - - DataStoppedAlarm.Enabled = ini.GetValue("Alarms", "DataStoppedAlarmSet", false); - DataStoppedAlarm.Sound = ini.GetValue("Alarms", "DataStoppedAlarmSound", false); - DataStoppedAlarm.SoundFile = ini.GetValue("Alarms", "DataStoppedAlarmSoundFile", DefaultSoundFile); - if (DataStoppedAlarm.SoundFile.Contains(DefaultSoundFileOld)) SensorAlarm.SoundFile = DefaultSoundFile; - DataStoppedAlarm.Notify = ini.GetValue("Alarms", "DataStoppedAlarmNotify", false); - DataStoppedAlarm.Email = ini.GetValue("Alarms", "DataStoppedAlarmEmail", false); - DataStoppedAlarm.Latch = ini.GetValue("Alarms", "DataStoppedAlarmLatch", false); - DataStoppedAlarm.LatchHours = ini.GetValue("Alarms", "DataStoppedAlarmLatchHours", 24); - - BatteryLowAlarm.Enabled = ini.GetValue("Alarms", "BatteryLowAlarmSet", false); - BatteryLowAlarm.Sound = ini.GetValue("Alarms", "BatteryLowAlarmSound", false); - BatteryLowAlarm.SoundFile = ini.GetValue("Alarms", "BatteryLowAlarmSoundFile", DefaultSoundFile); - BatteryLowAlarm.Notify = ini.GetValue("Alarms", "BatteryLowAlarmNotify", false); - BatteryLowAlarm.Email = ini.GetValue("Alarms", "BatteryLowAlarmEmail", false); - BatteryLowAlarm.Latch = ini.GetValue("Alarms", "BatteryLowAlarmLatch", false); - BatteryLowAlarm.LatchHours = ini.GetValue("Alarms", "BatteryLowAlarmLatchHours", 24); - - SpikeAlarm.Enabled = ini.GetValue("Alarms", "DataSpikeAlarmSet", false); - SpikeAlarm.Sound = ini.GetValue("Alarms", "DataSpikeAlarmSound", false); - SpikeAlarm.SoundFile = ini.GetValue("Alarms", "DataSpikeAlarmSoundFile", DefaultSoundFile); - SpikeAlarm.Notify = ini.GetValue("Alarms", "SpikeAlarmNotify", true); - SpikeAlarm.Email = ini.GetValue("Alarms", "SpikeAlarmEmail", true); - SpikeAlarm.Latch = ini.GetValue("Alarms", "SpikeAlarmLatch", true); - SpikeAlarm.LatchHours = ini.GetValue("Alarms", "SpikeAlarmLatchHours", 24); - - UpgradeAlarm.Enabled = ini.GetValue("Alarms", "UpgradeAlarmSet", true); - UpgradeAlarm.Sound = ini.GetValue("Alarms", "UpgradeAlarmSound", true); - UpgradeAlarm.SoundFile = ini.GetValue("Alarms", "UpgradeAlarmSoundFile", DefaultSoundFile); - UpgradeAlarm.Notify = ini.GetValue("Alarms", "UpgradeAlarmNotify", true); - UpgradeAlarm.Email = ini.GetValue("Alarms", "UpgradeAlarmEmail", false); - UpgradeAlarm.Latch = ini.GetValue("Alarms", "UpgradeAlarmLatch", false); - UpgradeAlarm.LatchHours = ini.GetValue("Alarms", "UpgradeAlarmLatchHours", 24); - - HttpUploadAlarm.Enabled = ini.GetValue("Alarms", "HttpUploadStoppedAlarmSet", false); - HttpUploadAlarm.Sound = ini.GetValue("Alarms", "HttpUploadStoppedAlarmSound", false); - HttpUploadAlarm.SoundFile = ini.GetValue("Alarms", "HttpUploadStoppedAlarmSoundFile", DefaultSoundFile); - HttpUploadAlarm.Notify = ini.GetValue("Alarms", "HttpUploadStoppedAlarmNotify", false); - HttpUploadAlarm.Email = ini.GetValue("Alarms", "HttpUploadStoppedAlarmEmail", false); - HttpUploadAlarm.Latch = ini.GetValue("Alarms", "HttpUploadStoppedAlarmLatch", false); - HttpUploadAlarm.LatchHours = ini.GetValue("Alarms", "HttpUploadStoppedAlarmLatchHours", 24); - - MySqlUploadAlarm.Enabled = ini.GetValue("Alarms", "HttpUploadStoppedAlarmSet", false); - MySqlUploadAlarm.Sound = ini.GetValue("Alarms", "HttpUploadStoppedAlarmSound", false); - MySqlUploadAlarm.SoundFile = ini.GetValue("Alarms", "HttpUploadStoppedAlarmSoundFile", DefaultSoundFile); - MySqlUploadAlarm.Notify = ini.GetValue("Alarms", "HttpUploadStoppedAlarmNotify", false); - MySqlUploadAlarm.Email = ini.GetValue("Alarms", "HttpUploadStoppedAlarmEmail", false); - MySqlUploadAlarm.Latch = ini.GetValue("Alarms", "HttpUploadStoppedAlarmLatch", false); - MySqlUploadAlarm.LatchHours = ini.GetValue("Alarms", "HttpUploadStoppedAlarmLatchHours", 24); - - - AlarmFromEmail = ini.GetValue("Alarms", "FromEmail", ""); - AlarmDestEmail = ini.GetValue("Alarms", "DestEmail", "").Split(';'); - AlarmEmailHtml = ini.GetValue("Alarms", "UseHTML", false); - - Calib.Press.Offset = ini.GetValue("Offsets", "PressOffset", 0.0); - Calib.Temp.Offset = ini.GetValue("Offsets", "TempOffset", 0.0); - Calib.Hum.Offset = ini.GetValue("Offsets", "HumOffset", 0); - Calib.WindDir.Offset = ini.GetValue("Offsets", "WindDirOffset", 0); - Calib.InTemp.Offset = ini.GetValue("Offsets", "InTempOffset", 0.0); - Calib.Solar.Offset = ini.GetValue("Offsers", "SolarOffset", 0.0); - Calib.UV.Offset = ini.GetValue("Offsets", "UVOffset", 0.0); - Calib.WetBulb.Offset = ini.GetValue("Offsets", "WetBulbOffset", 0.0); - - Calib.Press.Mult = ini.GetValue("Offsets", "PressMult", 1.0); - Calib.WindSpeed.Mult = ini.GetValue("Offsets", "WindSpeedMult", 1.0); - Calib.WindGust.Mult = ini.GetValue("Offsets", "WindGustMult", 1.0); - Calib.Temp.Mult = ini.GetValue("Offsets", "TempMult", 1.0); - Calib.Temp.Mult2 = ini.GetValue("Offsets", "TempMult2", 0.0); - Calib.Hum.Mult = ini.GetValue("Offsets", "HumMult", 1.0); - Calib.Hum.Mult2 = ini.GetValue("Offsets", "HumMult2", 0.0); - Calib.Rain.Mult = ini.GetValue("Offsets", "RainMult", 1.0); - Calib.Solar.Mult = ini.GetValue("Offsets", "SolarMult", 1.0); - Calib.UV.Mult = ini.GetValue("Offsets", "UVMult", 1.0); - Calib.WetBulb.Mult = ini.GetValue("Offsets", "WetBulbMult", 1.0); - - Limit.TempHigh = ini.GetValue("Limits", "TempHighC", 60.0); - Limit.TempLow = ini.GetValue("Limits", "TempLowC", -60.0); - Limit.DewHigh = ini.GetValue("Limits", "DewHighC", 40.0); - Limit.PressHigh = ini.GetValue("Limits", "PressHighMB", 1090.0); - Limit.PressLow = ini.GetValue("Limits", "PressLowMB", 870.0); - Limit.WindHigh = ini.GetValue("Limits", "WindHighMS", 90.0); - - xapEnabled = ini.GetValue("xAP", "Enabled", false); - xapUID = ini.GetValue("xAP", "UID", "4375"); - xapPort = ini.GetValue("xAP", "Port", 3639); - - SunThreshold = ini.GetValue("Solar", "SunThreshold", 75); - RStransfactor = ini.GetValue("Solar", "RStransfactor", 0.8); - SolarMinimum = ini.GetValue("Solar", "SolarMinimum", 0); - LuxToWM2 = ini.GetValue("Solar", "LuxToWM2", 0.0079); - UseBlakeLarsen = ini.GetValue("Solar", "UseBlakeLarsen", false); - SolarCalc = ini.GetValue("Solar", "SolarCalc", 0); - BrasTurbidity = ini.GetValue("Solar", "BrasTurbidity", 2.0); - //SolarFactorSummer = ini.GetValue("Solar", "SolarFactorSummer", -1); - //SolarFactorWinter = ini.GetValue("Solar", "SolarFactorWinter", -1); - - NOAAname = ini.GetValue("NOAA", "Name", " "); - NOAAcity = ini.GetValue("NOAA", "City", " "); - NOAAstate = ini.GetValue("NOAA", "State", " "); - NOAA12hourformat = ini.GetValue("NOAA", "12hourformat", false); - NOAAheatingthreshold = ini.GetValue("NOAA", "HeatingThreshold", -1000.0); - if (NOAAheatingthreshold < -99 || NOAAheatingthreshold > 150) - { - NOAAheatingthreshold = Units.Temp == 0 ? 18.3 : 65; - } - NOAAcoolingthreshold = ini.GetValue("NOAA", "CoolingThreshold", -1000.0); - if (NOAAcoolingthreshold < -99 || NOAAcoolingthreshold > 150) - { - NOAAcoolingthreshold = Units.Temp == 0 ? 18.3 : 65; - } - NOAAmaxtempcomp1 = ini.GetValue("NOAA", "MaxTempComp1", -1000.0); - if (NOAAmaxtempcomp1 < -99 || NOAAmaxtempcomp1 > 150) - { - NOAAmaxtempcomp1 = Units.Temp == 0 ? 27 : 80; - } - NOAAmaxtempcomp2 = ini.GetValue("NOAA", "MaxTempComp2", -1000.0); - if (NOAAmaxtempcomp2 < -99 || NOAAmaxtempcomp2 > 99) - { - NOAAmaxtempcomp2 = Units.Temp == 0 ? 0 : 32; - } - NOAAmintempcomp1 = ini.GetValue("NOAA", "MinTempComp1", -1000.0); - if (NOAAmintempcomp1 < -99 || NOAAmintempcomp1 > 99) - { - NOAAmintempcomp1 = Units.Temp == 0 ? 0 : 32; - } - NOAAmintempcomp2 = ini.GetValue("NOAA", "MinTempComp2", -1000.0); - if (NOAAmintempcomp2 < -99 || NOAAmintempcomp2 > 99) - { - NOAAmintempcomp2 = Units.Temp == 0 ? -18 : 0; - } - NOAAraincomp1 = ini.GetValue("NOAA", "RainComp1", -1000.0); - if (NOAAraincomp1 < 0 || NOAAraincomp1 > 99) - { - NOAAraincomp1 = Units.Rain == 0 ? 0.2 : 0.01; - } - NOAAraincomp2 = ini.GetValue("NOAA", "RainComp2", -1000.0); - if (NOAAraincomp2 < 0 || NOAAraincomp2 > 99) - { - NOAAraincomp2 = Units.Rain == 0 ? 2 : 0.1; - } - NOAAraincomp3 = ini.GetValue("NOAA", "RainComp3", -1000.0); - if (NOAAraincomp3 < 0 || NOAAraincomp3 > 99) - { - NOAAraincomp3 = Units.Rain == 0 ? 20 : 1; - } - - NOAAAutoSave = ini.GetValue("NOAA", "AutoSave", false); - NOAAAutoFTP = ini.GetValue("NOAA", "AutoFTP", false); - NOAAMonthFileFormat = ini.GetValue("NOAA", "MonthFileFormat", "'NOAAMO'MMyy'.txt'"); - // Check for Cumulus 1 default format - and update - if (NOAAMonthFileFormat == "'NOAAMO'mmyy'.txt'" || NOAAMonthFileFormat == "\"NOAAMO\"mmyy\".txt\"") - { - NOAAMonthFileFormat = "'NOAAMO'MMyy'.txt'"; - } - NOAAYearFileFormat = ini.GetValue("NOAA", "YearFileFormat", "'NOAAYR'yyyy'.txt'"); - NOAAFTPDirectory = ini.GetValue("NOAA", "FTPDirectory", ""); - NOAAUseUTF8 = ini.GetValue("NOAA", "NOAAUseUTF8", true); - NOAAUseDotDecimal = ini.GetValue("NOAA", "UseDotDecimal", false); - - NOAATempNorms[1] = ini.GetValue("NOAA", "NOAATempNormJan", -1000.0); - NOAATempNorms[2] = ini.GetValue("NOAA", "NOAATempNormFeb", -1000.0); - NOAATempNorms[3] = ini.GetValue("NOAA", "NOAATempNormMar", -1000.0); - NOAATempNorms[4] = ini.GetValue("NOAA", "NOAATempNormApr", -1000.0); - NOAATempNorms[5] = ini.GetValue("NOAA", "NOAATempNormMay", -1000.0); - NOAATempNorms[6] = ini.GetValue("NOAA", "NOAATempNormJun", -1000.0); - NOAATempNorms[7] = ini.GetValue("NOAA", "NOAATempNormJul", -1000.0); - NOAATempNorms[8] = ini.GetValue("NOAA", "NOAATempNormAug", -1000.0); - NOAATempNorms[9] = ini.GetValue("NOAA", "NOAATempNormSep", -1000.0); - NOAATempNorms[10] = ini.GetValue("NOAA", "NOAATempNormOct", -1000.0); - NOAATempNorms[11] = ini.GetValue("NOAA", "NOAATempNormNov", -1000.0); - NOAATempNorms[12] = ini.GetValue("NOAA", "NOAATempNormDec", -1000.0); - - NOAARainNorms[1] = ini.GetValue("NOAA", "NOAARainNormJan", -1000.0); - NOAARainNorms[2] = ini.GetValue("NOAA", "NOAARainNormFeb", -1000.0); - NOAARainNorms[3] = ini.GetValue("NOAA", "NOAARainNormMar", -1000.0); - NOAARainNorms[4] = ini.GetValue("NOAA", "NOAARainNormApr", -1000.0); - NOAARainNorms[5] = ini.GetValue("NOAA", "NOAARainNormMay", -1000.0); - NOAARainNorms[6] = ini.GetValue("NOAA", "NOAARainNormJun", -1000.0); - NOAARainNorms[7] = ini.GetValue("NOAA", "NOAARainNormJul", -1000.0); - NOAARainNorms[8] = ini.GetValue("NOAA", "NOAARainNormAug", -1000.0); - NOAARainNorms[9] = ini.GetValue("NOAA", "NOAARainNormSep", -1000.0); - NOAARainNorms[10] = ini.GetValue("NOAA", "NOAARainNormOct", -1000.0); - NOAARainNorms[11] = ini.GetValue("NOAA", "NOAARainNormNov", -1000.0); - NOAARainNorms[12] = ini.GetValue("NOAA", "NOAARainNormDec", -1000.0); - - HTTPProxyName = ini.GetValue("Proxies", "HTTPProxyName", ""); - HTTPProxyPort = ini.GetValue("Proxies", "HTTPProxyPort", 0); - HTTPProxyUser = ini.GetValue("Proxies", "HTTPProxyUser", ""); - HTTPProxyPassword = ini.GetValue("Proxies", "HTTPProxyPassword", ""); - - NumWindRosePoints = ini.GetValue("Display", "NumWindRosePoints", 16); - WindRoseAngle = 360.0 / NumWindRosePoints; - DisplayOptions.UseApparent = ini.GetValue("Display", "UseApparent", false); - DisplayOptions.ShowSolar = ini.GetValue("Display", "DisplaySolarData", false); - DisplayOptions.ShowUV = ini.GetValue("Display", "DisplayUvData", false); - - // MySQL - common - MySqlConnSettings.Server = ini.GetValue("MySQL", "Host", "127.0.0.1"); - MySqlConnSettings.Port = (uint)ini.GetValue("MySQL", "Port", 3306); - MySqlConnSettings.UserID = ini.GetValue("MySQL", "User", ""); - MySqlConnSettings.Password = ini.GetValue("MySQL", "Pass", ""); - MySqlConnSettings.Database = ini.GetValue("MySQL", "Database", "database"); - - // MySQL - monthly log file - MonthlyMySqlEnabled = ini.GetValue("MySQL", "MonthlyMySqlEnabled", false); - MySqlMonthlyTable = ini.GetValue("MySQL", "MonthlyTable", "Monthly"); - // MySQL - realtimne - RealtimeMySqlEnabled = ini.GetValue("MySQL", "RealtimeMySqlEnabled", false); - MySqlRealtimeTable = ini.GetValue("MySQL", "RealtimeTable", "Realtime"); - MySqlRealtimeRetention = ini.GetValue("MySQL", "RealtimeRetention", ""); - // MySQL - dayfile - DayfileMySqlEnabled = ini.GetValue("MySQL", "DayfileMySqlEnabled", false); - MySqlDayfileTable = ini.GetValue("MySQL", "DayfileTable", "Dayfile"); - // MySQL - custom seconds - CustomMySqlSecondsCommandString = ini.GetValue("MySQL", "CustomMySqlSecondsCommandString", ""); - CustomMySqlSecondsEnabled = ini.GetValue("MySQL", "CustomMySqlSecondsEnabled", false); - CustomMySqlSecondsInterval = ini.GetValue("MySQL", "CustomMySqlSecondsInterval", 10); - if (CustomMySqlSecondsInterval < 1) { CustomMySqlSecondsInterval = 1; } - // MySQL - custom minutes - CustomMySqlMinutesCommandString = ini.GetValue("MySQL", "CustomMySqlMinutesCommandString", ""); - CustomMySqlMinutesEnabled = ini.GetValue("MySQL", "CustomMySqlMinutesEnabled", false); - CustomMySqlMinutesIntervalIndex = ini.GetValue("MySQL", "CustomMySqlMinutesIntervalIndex", -1); - if (CustomMySqlMinutesIntervalIndex >= 0 && CustomMySqlMinutesIntervalIndex < FactorsOf60.Length) - { - CustomMySqlMinutesInterval = FactorsOf60[CustomMySqlMinutesIntervalIndex]; - } - else - { - CustomMySqlMinutesInterval = 10; - CustomMySqlMinutesIntervalIndex = 6; - } - // MySQL - custom rollover - CustomMySqlRolloverCommandString = ini.GetValue("MySQL", "CustomMySqlRolloverCommandString", ""); - CustomMySqlRolloverEnabled = ini.GetValue("MySQL", "CustomMySqlRolloverEnabled", false); - - // Custom HTTP - seconds - CustomHttpSecondsString = ini.GetValue("HTTP", "CustomHttpSecondsString", ""); - CustomHttpSecondsEnabled = ini.GetValue("HTTP", "CustomHttpSecondsEnabled", false); - CustomHttpSecondsInterval = ini.GetValue("HTTP", "CustomHttpSecondsInterval", 10); - if (CustomHttpSecondsInterval < 1) { CustomHttpSecondsInterval = 1; } - // Custom HTTP - minutes - CustomHttpMinutesString = ini.GetValue("HTTP", "CustomHttpMinutesString", ""); - CustomHttpMinutesEnabled = ini.GetValue("HTTP", "CustomHttpMinutesEnabled", false); - CustomHttpMinutesIntervalIndex = ini.GetValue("HTTP", "CustomHttpMinutesIntervalIndex", -1); - if (CustomHttpMinutesIntervalIndex >= 0 && CustomHttpMinutesIntervalIndex < FactorsOf60.Length) - { - CustomHttpMinutesInterval = FactorsOf60[CustomHttpMinutesIntervalIndex]; - } - else - { - CustomHttpMinutesInterval = 10; - CustomHttpMinutesIntervalIndex = 6; - } - // Http - custom rollover - CustomHttpRolloverString = ini.GetValue("HTTP", "CustomHttpRolloverString", ""); - CustomHttpRolloverEnabled = ini.GetValue("HTTP", "CustomHttpRolloverEnabled", false); - - // Select-a-Chart settings - for (int i = 0; i < SelectaChartOptions.series.Length; i++) - { - SelectaChartOptions.series[i] = ini.GetValue("Select-a-Chart", "Series" + i, "0"); - SelectaChartOptions.colours[i] = ini.GetValue("Select-a-Chart", "Colour" + i, ""); - } - - // Email settings - SmtpOptions.Enabled = ini.GetValue("SMTP", "Enabled", false); - SmtpOptions.Server = ini.GetValue("SMTP", "ServerName", ""); - SmtpOptions.Port = ini.GetValue("SMTP", "Port", 587); - SmtpOptions.SslOption = ini.GetValue("SMTP", "SSLOption", 1); - SmtpOptions.RequiresAuthentication = ini.GetValue("SMTP", "RequiresAuthentication", false); - SmtpOptions.User = ini.GetValue("SMTP", "User", ""); - SmtpOptions.Password = ini.GetValue("SMTP", "Password", ""); - - // Growing Degree Days - GrowingBase1 = ini.GetValue("GrowingDD", "BaseTemperature1", (Units.Temp == 0 ? 5.0 : 40.0)); - GrowingBase2 = ini.GetValue("GrowingDD", "BaseTemperature2", (Units.Temp == 0 ? 10.0 : 50.0)); - GrowingYearStarts = ini.GetValue("GrowingDD", "YearStarts", (Latitude >= 0 ? 1 : 7)); - GrowingCap30C = ini.GetValue("GrowingDD", "Cap30C", true); - - // Temperature Sum - TempSumYearStarts = ini.GetValue("TempSum", "TempSumYearStart", (Latitude >= 0 ? 1 : 7)); - if (TempSumYearStarts < 1 || TempSumYearStarts > 12) - TempSumYearStarts = 1; - TempSumBase1 = ini.GetValue("TempSum", "BaseTemperature1", GrowingBase1); - TempSumBase2 = ini.GetValue("TempSum", "BaseTemperature2", GrowingBase2); - } - - internal void WriteIniFile() - { - LogMessage("Writing Cumulus.ini file"); - - IniFile ini = new IniFile("Cumulus.ini"); - - ini.SetValue("Program", "EnableAccessibility", ProgramOptions.EnableAccessibility); - - ini.SetValue("Program", "StartupPingHost", ProgramOptions.StartupPingHost); - ini.SetValue("Program", "StartupPingEscapeTime", ProgramOptions.StartupPingEscapeTime); - - ini.SetValue("Program", "StartupDelaySecs", ProgramOptions.StartupDelaySecs); - ini.SetValue("Program", "StartupDelayMaxUptime", ProgramOptions.StartupDelayMaxUptime); - - ini.SetValue("Station", "WarnMultiple", ProgramOptions.WarnMultiple); - - ini.SetValue("Station", "Type", StationType); - ini.SetValue("Station", "Model", StationModel); - ini.SetValue("Station", "ComportName", ComportName); - ini.SetValue("Station", "Latitude", Latitude); - ini.SetValue("Station", "Longitude", Longitude); - ini.SetValue("Station", "LatTxt", LatTxt); - ini.SetValue("Station", "LonTxt", LonTxt); - ini.SetValue("Station", "Altitude", Altitude); - ini.SetValue("Station", "AltitudeInFeet", AltitudeInFeet); - ini.SetValue("Station", "Humidity98Fix", StationOptions.Humidity98Fix); - ini.SetValue("Station", "Wind10MinAverage", StationOptions.UseWind10MinAve); - ini.SetValue("Station", "UseSpeedForAvgCalc", StationOptions.UseSpeedForAvgCalc); - ini.SetValue("Station", "AvgBearingMinutes", StationOptions.AvgBearingMinutes); - ini.SetValue("Station", "AvgSpeedMinutes", StationOptions.AvgSpeedMinutes); - ini.SetValue("Station", "PeakGustMinutes", StationOptions.PeakGustMinutes); - - ini.SetValue("Station", "Logging", ProgramOptions.DebugLogging); - ini.SetValue("Station", "DataLogging", ProgramOptions.DataLogging); - - ini.SetValue("Station", "DavisReadReceptionStats", DavisOptions.ReadReceptionStats); - ini.SetValue("Station", "DavisSetLoggerInterval", DavisOptions.SetLoggerInterval); - ini.SetValue("Station", "UseDavisLoop2", DavisOptions.UseLoop2); - ini.SetValue("Station", "DavisInitWaitTime", DavisOptions.InitWaitTime); - ini.SetValue("Station", "DavisIPResponseTime", DavisOptions.IPResponseTime); - ini.SetValue("Station", "DavisBaudRate", DavisOptions.BaudRate); - ini.SetValue("Station", "VPrainGaugeType", DavisOptions.RainGaugeType); - ini.SetValue("Station", "VP2ConnectionType", DavisOptions.ConnectionType); - ini.SetValue("Station", "VP2TCPPort", DavisOptions.TCPPort); - ini.SetValue("Station", "VP2IPAddr", DavisOptions.IPAddr); - ini.SetValue("Station", "VP2PeriodicDisconnectInterval", DavisOptions.PeriodicDisconnectInterval); - ini.SetValue("Station", "ForceVPBarUpdate", DavisOptions.ForceVPBarUpdate); - - ini.SetValue("Station", "NoSensorCheck", StationOptions.NoSensorCheck); - ini.SetValue("Station", "CalculatedDP", StationOptions.CalculatedDP); - ini.SetValue("Station", "CalculatedWC", StationOptions.CalculatedWC); - ini.SetValue("Station", "RolloverHour", RolloverHour); - ini.SetValue("Station", "Use10amInSummer", Use10amInSummer); - //ini.SetValue("Station", "ConfirmClose", ConfirmClose); - //ini.SetValue("Station", "CloseOnSuspend", CloseOnSuspend); - //ini.SetValue("Station", "RestartIfUnplugged", RestartIfUnplugged); - //ini.SetValue("Station", "RestartIfDataStops", RestartIfDataStops); - ini.SetValue("Station", "SyncDavisClock", StationOptions.SyncTime); - ini.SetValue("Station", "ClockSettingHour", StationOptions.ClockSettingHour); - ini.SetValue("Station", "WS2300IgnoreStationClock", StationOptions.WS2300IgnoreStationClock); - ini.SetValue("Station", "LogExtraSensors", StationOptions.LogExtraSensors); - ini.SetValue("Station", "DataLogInterval", DataLogInterval); - - ini.SetValue("Station", "SyncFOReads", FineOffsetOptions.SyncReads); - ini.SetValue("Station", "FOReadAvoidPeriod", FineOffsetOptions.ReadAvoidPeriod); - ini.SetValue("Station", "FineOffsetReadTime", FineOffsetOptions.ReadTime); - ini.SetValue("Station", "FineOffsetSetLoggerInterval", FineOffsetOptions.SetLoggerInterval); - ini.SetValue("Station", "VendorID", FineOffsetOptions.VendorID); - ini.SetValue("Station", "ProductID", FineOffsetOptions.ProductID); - - - ini.SetValue("Station", "WindUnit", Units.Wind); - ini.SetValue("Station", "PressureUnit", Units.Press); - ini.SetValue("Station", "RainUnit", Units.Rain); - ini.SetValue("Station", "TempUnit", Units.Temp); - - ini.SetValue("Station", "WindSpeedDecimals", WindDPlaces); - ini.SetValue("Station", "WindSpeedAvgDecimals", WindAvgDPlaces); - ini.SetValue("Station", "WindRunDecimals", WindRunDPlaces); - ini.SetValue("Station", "SunshineHrsDecimals", SunshineDPlaces); - ini.SetValue("Station", "PressDecimals", PressDPlaces); - ini.SetValue("Station", "RainDecimals", RainDPlaces); - ini.SetValue("Station", "TempDecimals", TempDPlaces); - ini.SetValue("Station", "UVDecimals", UVDPlaces); - ini.SetValue("Station", "AirQualityDecimals", AirQualityDPlaces); - - - ini.SetValue("Station", "LocName", LocationName); - ini.SetValue("Station", "LocDesc", LocationDesc); - ini.SetValue("Station", "StartDate", RecordsBeganDate); - ini.SetValue("Station", "YTDrain", YTDrain); - ini.SetValue("Station", "YTDrainyear", YTDrainyear); - ini.SetValue("Station", "UseDataLogger", UseDataLogger); - ini.SetValue("Station", "UseCumulusForecast", UseCumulusForecast); - ini.SetValue("Station", "HourlyForecast", HourlyForecast); - ini.SetValue("Station", "UseCumulusPresstrendstr", StationOptions.UseCumulusPresstrendstr); - ini.SetValue("Station", "FCpressinMB", FCpressinMB); - ini.SetValue("Station", "FClowpress", FClowpress); - ini.SetValue("Station", "FChighpress", FChighpress); - ini.SetValue("Station", "UseZeroBearing", StationOptions.UseZeroBearing); - ini.SetValue("Station", "RoundWindSpeed", StationOptions.RoundWindSpeed); - ini.SetValue("Station", "PrimaryAqSensor", StationOptions.PrimaryAqSensor); - - ini.SetValue("Station", "EWInterval", EwOptions.Interval); - ini.SetValue("Station", "EWFile", EwOptions.Filename); - ini.SetValue("Station", "EWminpressureMB", EwOptions.MinPressMB); - ini.SetValue("Station", "EWmaxpressureMB", EwOptions.MaxPressMB); - ini.SetValue("Station", "EWMaxRainTipDiff", EwOptions.MaxRainTipDiff); - ini.SetValue("Station", "EWpressureoffset", EwOptions.PressOffset); - - ini.SetValue("Station", "EWtempdiff", Spike.TempDiff); - ini.SetValue("Station", "EWpressurediff", Spike.PressDiff); - ini.SetValue("Station", "EWhumiditydiff", Spike.HumidityDiff); - ini.SetValue("Station", "EWgustdiff", Spike.GustDiff); - ini.SetValue("Station", "EWwinddiff", Spike.WindDiff); - ini.SetValue("Station", "EWmaxHourlyRain", Spike.MaxHourlyRain); - ini.SetValue("Station", "EWmaxRainRate", Spike.MaxRainRate); - - ini.SetValue("Station", "RainSeasonStart", RainSeasonStart); - ini.SetValue("Station", "RainDayThreshold", RainDayThreshold); - - ini.SetValue("Station", "ChillHourSeasonStart", ChillHourSeasonStart); - ini.SetValue("Station", "ChillHourThreshold", ChillHourThreshold); - - ini.SetValue("Station", "ErrorLogSpikeRemoval", ErrorLogSpikeRemoval); - - ini.SetValue("Station", "ImetBaudRate", ImetOptions.BaudRate); - ini.SetValue("Station", "ImetWaitTime", ImetOptions.WaitTime); // delay to wait for a reply to a command - ini.SetValue("Station", "ImetReadDelay", ImetOptions.ReadDelay); // delay between sending read live data commands - ini.SetValue("Station", "ImetUpdateLogPointer", ImetOptions.UpdateLogPointer); // keep the logger pointer pointing at last data read - - ini.SetValue("Station", "RG11Enabled", RG11Enabled); - ini.SetValue("Station", "RG11portName", RG11Port); - ini.SetValue("Station", "RG11TBRmode", RG11TBRmode); - ini.SetValue("Station", "RG11tipsize", RG11tipsize); - ini.SetValue("Station", "RG11IgnoreFirst", RG11IgnoreFirst); - ini.SetValue("Station", "RG11DTRmode", RG11DTRmode); - - ini.SetValue("Station", "RG11Enabled2", RG11Enabled2); - ini.SetValue("Station", "RG11portName2", RG11Port2); - ini.SetValue("Station", "RG11TBRmode2", RG11TBRmode2); - ini.SetValue("Station", "RG11tipsize2", RG11tipsize2); - ini.SetValue("Station", "RG11IgnoreFirst2", RG11IgnoreFirst2); - ini.SetValue("Station", "RG11DTRmode2", RG11DTRmode2); - - // WeatherLink Live device settings - ini.SetValue("WLL", "AutoUpdateIpAddress", WLLAutoUpdateIpAddress); - ini.SetValue("WLL", "WLv2ApiKey", WllApiKey); - ini.SetValue("WLL", "WLv2ApiSecret", WllApiSecret); - ini.SetValue("WLL", "WLStationId", WllStationId); - ini.SetValue("WLL", "PrimaryRainTxId", WllPrimaryRain); - ini.SetValue("WLL", "PrimaryTempHumTxId", WllPrimaryTempHum); - ini.SetValue("WLL", "PrimaryWindTxId", WllPrimaryWind); - ini.SetValue("WLL", "PrimaryRainTxId", WllPrimaryRain); - ini.SetValue("WLL", "PrimarySolarTxId", WllPrimarySolar); - ini.SetValue("WLL", "PrimaryUvTxId", WllPrimaryUV); - ini.SetValue("WLL", "ExtraSoilTempTxId1", WllExtraSoilTempTx1); - ini.SetValue("WLL", "ExtraSoilTempIdx1", WllExtraSoilTempIdx1); - ini.SetValue("WLL", "ExtraSoilTempTxId2", WllExtraSoilTempTx2); - ini.SetValue("WLL", "ExtraSoilTempIdx2", WllExtraSoilTempIdx2); - ini.SetValue("WLL", "ExtraSoilTempTxId3", WllExtraSoilTempTx3); - ini.SetValue("WLL", "ExtraSoilTempIdx3", WllExtraSoilTempIdx3); - ini.SetValue("WLL", "ExtraSoilTempTxId4", WllExtraSoilTempTx4); - ini.SetValue("WLL", "ExtraSoilTempIdx4", WllExtraSoilTempIdx4); - ini.SetValue("WLL", "ExtraSoilMoistureTxId1", WllExtraSoilMoistureTx1); - ini.SetValue("WLL", "ExtraSoilMoistureIdx1", WllExtraSoilMoistureIdx1); - ini.SetValue("WLL", "ExtraSoilMoistureTxId2", WllExtraSoilMoistureTx2); - ini.SetValue("WLL", "ExtraSoilMoistureIdx2", WllExtraSoilMoistureIdx2); - ini.SetValue("WLL", "ExtraSoilMoistureTxId3", WllExtraSoilMoistureTx3); - ini.SetValue("WLL", "ExtraSoilMoistureIdx3", WllExtraSoilMoistureIdx3); - ini.SetValue("WLL", "ExtraSoilMoistureTxId4", WllExtraSoilMoistureTx4); - ini.SetValue("WLL", "ExtraSoilMoistureIdx4", WllExtraSoilMoistureIdx4); - ini.SetValue("WLL", "ExtraLeafTxId1", WllExtraLeafTx1); - ini.SetValue("WLL", "ExtraLeafIdx1", WllExtraLeafIdx1); - ini.SetValue("WLL", "ExtraLeafTxId2", WllExtraLeafTx2); - ini.SetValue("WLL", "ExtraLeafIdx2", WllExtraLeafIdx2); - for (int i = 1; i <= 8; i++) - { - ini.SetValue("WLL", "ExtraTempTxId" + i, WllExtraTempTx[i - 1]); - ini.SetValue("WLL", "ExtraHumOnTxId" + i, WllExtraHumTx[i - 1]); - } - - // GW1000 settings - ini.SetValue("GW1000", "IPAddress", Gw1000IpAddress); - ini.SetValue("GW1000", "MACAddress", Gw1000MacAddress); - ini.SetValue("GW1000", "AutoUpdateIpAddress", Gw1000AutoUpdateIpAddress); - - // AirLink settings - ini.SetValue("AirLink", "IsWllNode", AirLinkIsNode); - ini.SetValue("AirLink", "WLv2ApiKey", AirLinkApiKey); - ini.SetValue("AirLink", "WLv2ApiSecret", AirLinkApiSecret); - ini.SetValue("AirLink", "AutoUpdateIpAddress", AirLinkAutoUpdateIpAddress); - ini.SetValue("AirLink", "In-Enabled", AirLinkInEnabled); - ini.SetValue("AirLink", "In-IPAddress", AirLinkInIPAddr); - ini.SetValue("AirLink", "In-WLStationId", AirLinkInStationId); - ini.SetValue("AirLink", "In-Hostname", AirLinkInHostName); - - ini.SetValue("AirLink", "Out-Enabled", AirLinkOutEnabled); - ini.SetValue("AirLink", "Out-IPAddress", AirLinkOutIPAddr); - ini.SetValue("AirLink", "Out-WLStationId", AirLinkOutStationId); - ini.SetValue("AirLink", "Out-Hostname", AirLinkOutHostName); - ini.SetValue("AirLink", "AQIformula", airQualityIndex); - - ini.SetValue("Web Site", "ForumURL", ForumURL); - ini.SetValue("Web Site", "WebcamURL", WebcamURL); - - ini.SetValue("FTP site", "Host", FtpHostname); - ini.SetValue("FTP site", "Port", FtpHostPort); - ini.SetValue("FTP site", "Username", FtpUsername); - ini.SetValue("FTP site", "Password", FtpPassword); - ini.SetValue("FTP site", "Directory", FtpDirectory); - - ini.SetValue("FTP site", "AutoUpdate", WebAutoUpdate); - ini.SetValue("FTP site", "Sslftp", (int)Sslftp); - // BUILD 3092 - added alternate SFTP authenication options - ini.SetValue("FTP site", "SshFtpAuthentication", SshftpAuthentication); - ini.SetValue("FTP site", "SshFtpPskFile", SshftpPskFile); - - ini.SetValue("FTP site", "FTPlogging", FTPlogging); - ini.SetValue("FTP site", "UTF8encode", UTF8encode); - ini.SetValue("FTP site", "EnableRealtime", RealtimeEnabled); - ini.SetValue("FTP site", "RealtimeInterval", RealtimeInterval); - ini.SetValue("FTP site", "RealtimeFTPEnabled", RealtimeFTPEnabled); - ini.SetValue("FTP site", "RealtimeTxtCreate", RealtimeFiles[0].Create); - ini.SetValue("FTP site", "RealtimeTxtFTP", RealtimeFiles[0].FTP); - ini.SetValue("FTP site", "RealtimeGaugesTxtCreate", RealtimeFiles[1].Create); - ini.SetValue("FTP site", "RealtimeGaugesTxtFTP", RealtimeFiles[1].FTP); - - ini.SetValue("FTP site", "IntervalEnabled", WebIntervalEnabled); - ini.SetValue("FTP site", "UpdateInterval", UpdateInterval); - for (var i = 0; i < StdWebFiles.Length; i++) - { - var keyNameCreate = "Create-" + StdWebFiles[i].LocalFileName.Split('.')[0]; - var keyNameFTP = "Ftp-" + StdWebFiles[i].LocalFileName.Split('.')[0]; - ini.SetValue("FTP site", keyNameCreate, StdWebFiles[i].Create); - ini.SetValue("FTP site", keyNameFTP, StdWebFiles[i].FTP); - } - - for (var i = 0; i < GraphDataFiles.Length; i++) - { - var keyNameCreate = "Create-" + GraphDataFiles[i].LocalFileName.Split('.')[0]; - var keyNameFTP = "Ftp-" + GraphDataFiles[i].LocalFileName.Split('.')[0]; - ini.SetValue("FTP site", keyNameCreate, GraphDataFiles[i].Create); - ini.SetValue("FTP site", keyNameFTP, GraphDataFiles[i].FTP); - } - - for (var i = 0; i < GraphDataEodFiles.Length; i++) - { - var keyNameCreate = "Create-" + GraphDataEodFiles[i].LocalFileName.Split('.')[0]; - var keyNameFTP = "Ftp-" + GraphDataEodFiles[i].LocalFileName.Split('.')[0]; - ini.SetValue("FTP site", keyNameCreate, GraphDataEodFiles[i].Create); - ini.SetValue("FTP site", keyNameFTP, GraphDataEodFiles[i].FTP); - } - - ini.SetValue("FTP site", "IncludeMoonImage", IncludeMoonImage); - ini.SetValue("FTP site", "FTPRename", FTPRename); - ini.SetValue("FTP site", "DeleteBeforeUpload", DeleteBeforeUpload); - ini.SetValue("FTP site", "ActiveFTP", ActiveFTPMode); - ini.SetValue("FTP site", "DisableEPSV", DisableFtpsEPSV); - ini.SetValue("FTP site", "DisableFtpsExplicit", DisableFtpsExplicit); - - - for (int i = 0; i < numextrafiles; i++) - { - ini.SetValue("FTP site", "ExtraLocal" + i, ExtraFiles[i].local); - ini.SetValue("FTP site", "ExtraRemote" + i, ExtraFiles[i].remote); - ini.SetValue("FTP site", "ExtraProcess" + i, ExtraFiles[i].process); - ini.SetValue("FTP site", "ExtraBinary" + i, ExtraFiles[i].binary); - ini.SetValue("FTP site", "ExtraRealtime" + i, ExtraFiles[i].realtime); - ini.SetValue("FTP site", "ExtraFTP" + i, ExtraFiles[i].FTP); - ini.SetValue("FTP site", "ExtraUTF" + i, ExtraFiles[i].UTF8); - ini.SetValue("FTP site", "ExtraEOD" + i, ExtraFiles[i].endofday); - } - - ini.SetValue("FTP site", "ExternalProgram", ExternalProgram); - ini.SetValue("FTP site", "RealtimeProgram", RealtimeProgram); - ini.SetValue("FTP site", "DailyProgram", DailyProgram); - ini.SetValue("FTP site", "ExternalParams", ExternalParams); - ini.SetValue("FTP site", "RealtimeParams", RealtimeParams); - ini.SetValue("FTP site", "DailyParams", DailyParams); - - ini.SetValue("Station", "CloudBaseInFeet", CloudBaseInFeet); - - ini.SetValue("Wunderground", "ID", Wund.ID); - ini.SetValue("Wunderground", "Password", Wund.PW); - ini.SetValue("Wunderground", "Enabled", Wund.Enabled); - ini.SetValue("Wunderground", "RapidFire", Wund.RapidFireEnabled); - ini.SetValue("Wunderground", "Interval", Wund.Interval); - ini.SetValue("Wunderground", "SendUV", Wund.SendUV); - ini.SetValue("Wunderground", "SendSR", Wund.SendSolar); - ini.SetValue("Wunderground", "SendIndoor", Wund.SendIndoor); - ini.SetValue("Wunderground", "SendAverage", Wund.SendAverage); - ini.SetValue("Wunderground", "CatchUp", Wund.CatchUp); - ini.SetValue("Wunderground", "SendSoilTemp1", Wund.SendSoilTemp1); - ini.SetValue("Wunderground", "SendSoilTemp2", Wund.SendSoilTemp2); - ini.SetValue("Wunderground", "SendSoilTemp3", Wund.SendSoilTemp3); - ini.SetValue("Wunderground", "SendSoilTemp4", Wund.SendSoilTemp4); - ini.SetValue("Wunderground", "SendSoilMoisture1", Wund.SendSoilMoisture1); - ini.SetValue("Wunderground", "SendSoilMoisture2", Wund.SendSoilMoisture2); - ini.SetValue("Wunderground", "SendSoilMoisture3", Wund.SendSoilMoisture3); - ini.SetValue("Wunderground", "SendSoilMoisture4", Wund.SendSoilMoisture4); - ini.SetValue("Wunderground", "SendLeafWetness1", Wund.SendLeafWetness1); - ini.SetValue("Wunderground", "SendLeafWetness2", Wund.SendLeafWetness2); - ini.SetValue("Wunderground", "SendAirQuality", Wund.SendAirQuality); - - ini.SetValue("Windy", "APIkey", Windy.ApiKey); - ini.SetValue("Windy", "StationIdx", Windy.StationIdx); - ini.SetValue("Windy", "Enabled", Windy.Enabled); - ini.SetValue("Windy", "Interval", Windy.Interval); - ini.SetValue("Windy", "SendUV", Windy.SendUV); - ini.SetValue("Windy", "CatchUp", Windy.CatchUp); - - ini.SetValue("Awekas", "User", AWEKAS.ID); - ini.SetValue("Awekas", "Password", AWEKAS.PW); - ini.SetValue("Awekas", "Language", AWEKAS.Lang); - ini.SetValue("Awekas", "Enabled", AWEKAS.Enabled); - ini.SetValue("Awekas", "Interval", AWEKAS.Interval); - ini.SetValue("Awekas", "SendUV", AWEKAS.SendUV); - ini.SetValue("Awekas", "SendSR", AWEKAS.SendSolar); - ini.SetValue("Awekas", "SendSoilTemp", AWEKAS.SendSoilTemp); - ini.SetValue("Awekas", "SendIndoor", AWEKAS.SendIndoor); - ini.SetValue("Awekas", "SendSoilMoisture", AWEKAS.SendSoilMoisture); - ini.SetValue("Awekas", "SendLeafWetness", AWEKAS.SendLeafWetness); - ini.SetValue("Awekas", "SendAirQuality", AWEKAS.SendAirQuality); - - ini.SetValue("WeatherCloud", "Wid", WCloud.ID); - ini.SetValue("WeatherCloud", "Key", WCloud.PW); - ini.SetValue("WeatherCloud", "Enabled", WCloud.Enabled); - ini.SetValue("WeatherCloud", "Interval", WCloud.Interval); - ini.SetValue("WeatherCloud", "SendUV", WCloud.SendUV); - ini.SetValue("WeatherCloud", "SendSR", WCloud.SendSolar); - ini.SetValue("WeatherCloud", "SendAQI", WCloud.SendAirQuality); - ini.SetValue("WeatherCloud", "SendSoilMoisture", WCloud.SendSoilMoisture); - ini.SetValue("WeatherCloud", "SoilMoistureSensor", WCloud.SoilMoistureSensor); - ini.SetValue("WeatherCloud", "SendLeafWetness", WCloud.SendLeafWetness); - ini.SetValue("WeatherCloud", "LeafWetnessSensor", WCloud.LeafWetnessSensor); - - ini.SetValue("Twitter", "User", Twitter.ID); - ini.SetValue("Twitter", "Password", Twitter.PW); - ini.SetValue("Twitter", "Enabled", Twitter.Enabled); - ini.SetValue("Twitter", "Interval", Twitter.Interval); - ini.SetValue("Twitter", "OauthToken", Twitter.OauthToken); - ini.SetValue("Twitter", "OauthTokenSecret", Twitter.OauthTokenSecret); - ini.SetValue("Twitter", "TwitterSendLocation", Twitter.SendLocation); - - ini.SetValue("PWSweather", "ID", PWS.ID); - ini.SetValue("PWSweather", "Password", PWS.PW); - ini.SetValue("PWSweather", "Enabled", PWS.Enabled); - ini.SetValue("PWSweather", "Interval", PWS.Interval); - ini.SetValue("PWSweather", "SendUV", PWS.SendUV); - ini.SetValue("PWSweather", "SendSR", PWS.SendSolar); - ini.SetValue("PWSweather", "CatchUp", PWS.CatchUp); - - ini.SetValue("WOW", "ID", WOW.ID); - ini.SetValue("WOW", "Password", WOW.PW); - ini.SetValue("WOW", "Enabled", WOW.Enabled); - ini.SetValue("WOW", "Interval", WOW.Interval); - ini.SetValue("WOW", "SendUV", WOW.SendUV); - ini.SetValue("WOW", "SendSR", WOW.SendSolar); - ini.SetValue("WOW", "CatchUp", WOW.CatchUp); - - ini.SetValue("APRS", "ID", APRS.ID); - ini.SetValue("APRS", "pass", APRS.PW); - ini.SetValue("APRS", "server", APRS.Server); - ini.SetValue("APRS", "port", APRS.Port); - ini.SetValue("APRS", "Enabled", APRS.Enabled); - ini.SetValue("APRS", "Interval", APRS.Interval); - ini.SetValue("APRS", "SendSR", APRS.SendSolar); - ini.SetValue("APRS", "APRSHumidityCutoff", APRS.HumidityCutoff); - - ini.SetValue("OpenWeatherMap", "Enabled", OpenWeatherMap.Enabled); - ini.SetValue("OpenWeatherMap", "CatchUp", OpenWeatherMap.CatchUp); - ini.SetValue("OpenWeatherMap", "APIkey", OpenWeatherMap.PW); - ini.SetValue("OpenWeatherMap", "StationId", OpenWeatherMap.ID); - ini.SetValue("OpenWeatherMap", "Interval", OpenWeatherMap.Interval); - - ini.SetValue("WindGuru", "Enabled", WindGuru.Enabled); - ini.SetValue("WindGuru", "StationUID", WindGuru.ID); - ini.SetValue("WindGuru", "Password", WindGuru.PW); - ini.SetValue("WindGuru", "Interval", WindGuru.Interval); - ini.SetValue("WindGuru", "SendRain", WindGuru.SendRain); - - ini.SetValue("MQTT", "Server", MQTT.Server); - ini.SetValue("MQTT", "Port", MQTT.Port); - ini.SetValue("MQTT", "UseTLS", MQTT.UseTLS); - ini.SetValue("MQTT", "Username", MQTT.Username); - ini.SetValue("MQTT", "Password", MQTT.Password); - ini.SetValue("MQTT", "EnableDataUpdate", MQTT.EnableDataUpdate); - ini.SetValue("MQTT", "UpdateTopic", MQTT.UpdateTopic); - ini.SetValue("MQTT", "UpdateTemplate", MQTT.UpdateTemplate); - ini.SetValue("MQTT", "UpdateRetained", MQTT.UpdateRetained); - ini.SetValue("MQTT", "EnableInterval", MQTT.EnableInterval); - ini.SetValue("MQTT", "IntervalTime", MQTT.IntervalTime); - ini.SetValue("MQTT", "IntervalTopic", MQTT.IntervalTopic); - ini.SetValue("MQTT", "IntervalTemplate", MQTT.IntervalTemplate); - ini.SetValue("MQTT", "IntervalRetained", MQTT.IntervalRetained); - - ini.SetValue("Alarms", "alarmlowtemp", LowTempAlarm.Value); - ini.SetValue("Alarms", "LowTempAlarmSet", LowTempAlarm.Enabled); - ini.SetValue("Alarms", "LowTempAlarmSound", LowTempAlarm.Sound); - ini.SetValue("Alarms", "LowTempAlarm.SoundFile", LowTempAlarm.SoundFile); - ini.SetValue("Alarms", "LowTempAlarmNotify", LowTempAlarm.Notify); - ini.SetValue("Alarms", "LowTempAlarmEmail", LowTempAlarm.Email); - ini.SetValue("Alarms", "LowTempAlarmLatch", LowTempAlarm.Latch); - ini.SetValue("Alarms", "LowTempAlarmLatchHours", LowTempAlarm.LatchHours); - - ini.SetValue("Alarms", "alarmhightemp", HighTempAlarm.Value); - ini.SetValue("Alarms", "HighTempAlarmSet", HighTempAlarm.Enabled); - ini.SetValue("Alarms", "HighTempAlarmSound", HighTempAlarm.Sound); - ini.SetValue("Alarms", "HighTempAlarmSoundFile", HighTempAlarm.SoundFile); - ini.SetValue("Alarms", "HighTempAlarmNotify", HighTempAlarm.Notify); - ini.SetValue("Alarms", "HighTempAlarmEmail", HighTempAlarm.Email); - ini.SetValue("Alarms", "HighTempAlarmLatch", HighTempAlarm.Latch); - ini.SetValue("Alarms", "HighTempAlarmLatchHours", HighTempAlarm.LatchHours); - - ini.SetValue("Alarms", "alarmtempchange", TempChangeAlarm.Value); - ini.SetValue("Alarms", "TempChangeAlarmSet", TempChangeAlarm.Enabled); - ini.SetValue("Alarms", "TempChangeAlarmSound", TempChangeAlarm.Sound); - ini.SetValue("Alarms", "TempChangeAlarmSoundFile", TempChangeAlarm.SoundFile); - ini.SetValue("Alarms", "TempChangeAlarmNotify", TempChangeAlarm.Notify); - ini.SetValue("Alarms", "TempChangeAlarmEmail", TempChangeAlarm.Email); - ini.SetValue("Alarms", "TempChangeAlarmLatch", TempChangeAlarm.Latch); - ini.SetValue("Alarms", "TempChangeAlarmLatchHours", TempChangeAlarm.LatchHours); - - ini.SetValue("Alarms", "alarmlowpress", LowPressAlarm.Value); - ini.SetValue("Alarms", "LowPressAlarmSet", LowPressAlarm.Enabled); - ini.SetValue("Alarms", "LowPressAlarmSound", LowPressAlarm.Sound); - ini.SetValue("Alarms", "LowPressAlarmSoundFile", LowPressAlarm.SoundFile); - ini.SetValue("Alarms", "LowPressAlarmNotify", LowPressAlarm.Notify); - ini.SetValue("Alarms", "LowPressAlarmEmail", LowPressAlarm.Email); - ini.SetValue("Alarms", "LowPressAlarmLatch", LowPressAlarm.Latch); - ini.SetValue("Alarms", "LowPressAlarmLatchHours", LowPressAlarm.LatchHours); - - ini.SetValue("Alarms", "alarmhighpress", HighPressAlarm.Value); - ini.SetValue("Alarms", "HighPressAlarmSet", HighPressAlarm.Enabled); - ini.SetValue("Alarms", "HighPressAlarmSound", HighPressAlarm.Sound); - ini.SetValue("Alarms", "HighPressAlarmSoundFile", HighPressAlarm.SoundFile); - ini.SetValue("Alarms", "HighPressAlarmNotify", HighPressAlarm.Notify); - ini.SetValue("Alarms", "HighPressAlarmEmail", HighPressAlarm.Email); - ini.SetValue("Alarms", "HighPressAlarmLatch", HighPressAlarm.Latch); - ini.SetValue("Alarms", "HighPressAlarmLatchHours", HighPressAlarm.LatchHours); - - ini.SetValue("Alarms", "alarmpresschange", PressChangeAlarm.Value); - ini.SetValue("Alarms", "PressChangeAlarmSet", PressChangeAlarm.Enabled); - ini.SetValue("Alarms", "PressChangeAlarmSound", PressChangeAlarm.Sound); - ini.SetValue("Alarms", "PressChangeAlarmSoundFile", PressChangeAlarm.SoundFile); - ini.SetValue("Alarms", "PressChangeAlarmNotify", PressChangeAlarm.Notify); - ini.SetValue("Alarms", "PressChangeAlarmEmail", PressChangeAlarm.Email); - ini.SetValue("Alarms", "PressChangeAlarmLatch", PressChangeAlarm.Latch); - ini.SetValue("Alarms", "PressChangeAlarmLatchHours", PressChangeAlarm.LatchHours); - - ini.SetValue("Alarms", "alarmhighraintoday", HighRainTodayAlarm.Value); - ini.SetValue("Alarms", "HighRainTodayAlarmSet", HighRainTodayAlarm.Enabled); - ini.SetValue("Alarms", "HighRainTodayAlarmSound", HighRainTodayAlarm.Sound); - ini.SetValue("Alarms", "HighRainTodayAlarmSoundFile", HighRainTodayAlarm.SoundFile); - ini.SetValue("Alarms", "HighRainTodayAlarmNotify", HighRainTodayAlarm.Notify); - ini.SetValue("Alarms", "HighRainTodayAlarmEmail", HighRainTodayAlarm.Email); - ini.SetValue("Alarms", "HighRainTodayAlarmLatch", HighRainTodayAlarm.Latch); - ini.SetValue("Alarms", "HighRainTodayAlarmLatchHours", HighRainTodayAlarm.LatchHours); - - ini.SetValue("Alarms", "alarmhighrainrate", HighRainRateAlarm.Value); - ini.SetValue("Alarms", "HighRainRateAlarmSet", HighRainRateAlarm.Enabled); - ini.SetValue("Alarms", "HighRainRateAlarmSound", HighRainRateAlarm.Sound); - ini.SetValue("Alarms", "HighRainRateAlarmSoundFile", HighRainRateAlarm.SoundFile); - ini.SetValue("Alarms", "HighRainRateAlarmNotify", HighRainRateAlarm.Notify); - ini.SetValue("Alarms", "HighRainRateAlarmEmail", HighRainRateAlarm.Email); - ini.SetValue("Alarms", "HighRainRateAlarmLatch", HighRainRateAlarm.Latch); - ini.SetValue("Alarms", "HighRainRateAlarmLatchHours", HighRainRateAlarm.LatchHours); - - ini.SetValue("Alarms", "alarmhighgust", HighGustAlarm.Value); - ini.SetValue("Alarms", "HighGustAlarmSet", HighGustAlarm.Enabled); - ini.SetValue("Alarms", "HighGustAlarmSound", HighGustAlarm.Sound); - ini.SetValue("Alarms", "HighGustAlarmSoundFile", HighGustAlarm.SoundFile); - ini.SetValue("Alarms", "HighGustAlarmNotify", HighGustAlarm.Notify); - ini.SetValue("Alarms", "HighGustAlarmEmail", HighGustAlarm.Email); - ini.SetValue("Alarms", "HighGustAlarmLatch", HighGustAlarm.Latch); - ini.SetValue("Alarms", "HighGustAlarmLatchHours", HighGustAlarm.LatchHours); - - ini.SetValue("Alarms", "alarmhighwind", HighWindAlarm.Value); - ini.SetValue("Alarms", "HighWindAlarmSet", HighWindAlarm.Enabled); - ini.SetValue("Alarms", "HighWindAlarmSound", HighWindAlarm.Sound); - ini.SetValue("Alarms", "HighWindAlarmSoundFile", HighWindAlarm.SoundFile); - ini.SetValue("Alarms", "HighWindAlarmNotify", HighWindAlarm.Notify); - ini.SetValue("Alarms", "HighWindAlarmEmail", HighWindAlarm.Email); - ini.SetValue("Alarms", "HighWindAlarmLatch", HighWindAlarm.Latch); - ini.SetValue("Alarms", "HighWindAlarmLatchHours", HighWindAlarm.LatchHours); - - ini.SetValue("Alarms", "SensorAlarmSet", SensorAlarm.Enabled); - ini.SetValue("Alarms", "SensorAlarmSound", SensorAlarm.Sound); - ini.SetValue("Alarms", "SensorAlarmSoundFile", SensorAlarm.SoundFile); - ini.SetValue("Alarms", "SensorAlarmNotify", SensorAlarm.Notify); - ini.SetValue("Alarms", "SensorAlarmEmail", SensorAlarm.Email); - ini.SetValue("Alarms", "SensorAlarmLatch", SensorAlarm.Latch); - ini.SetValue("Alarms", "SensorAlarmLatchHours", SensorAlarm.LatchHours); - - ini.SetValue("Alarms", "DataStoppedAlarmSet", DataStoppedAlarm.Enabled); - ini.SetValue("Alarms", "DataStoppedAlarmSound", DataStoppedAlarm.Sound); - ini.SetValue("Alarms", "DataStoppedAlarmSoundFile", DataStoppedAlarm.SoundFile); - ini.SetValue("Alarms", "DataStoppedAlarmNotify", DataStoppedAlarm.Notify); - ini.SetValue("Alarms", "DataStoppedAlarmEmail", DataStoppedAlarm.Email); - ini.SetValue("Alarms", "DataStoppedAlarmLatch", DataStoppedAlarm.Latch); - ini.SetValue("Alarms", "DataStoppedAlarmLatchHours", DataStoppedAlarm.LatchHours); - - ini.SetValue("Alarms", "BatteryLowAlarmSet", BatteryLowAlarm.Enabled); - ini.SetValue("Alarms", "BatteryLowAlarmSound", BatteryLowAlarm.Sound); - ini.SetValue("Alarms", "BatteryLowAlarmSoundFile", BatteryLowAlarm.SoundFile); - ini.SetValue("Alarms", "BatteryLowAlarmNotify", BatteryLowAlarm.Notify); - ini.SetValue("Alarms", "BatteryLowAlarmEmail", BatteryLowAlarm.Email); - ini.SetValue("Alarms", "BatteryLowAlarmLatch", BatteryLowAlarm.Latch); - ini.SetValue("Alarms", "BatteryLowAlarmLatchHours", BatteryLowAlarm.LatchHours); - - ini.SetValue("Alarms", "DataSpikeAlarmSet", SpikeAlarm.Enabled); - ini.SetValue("Alarms", "DataSpikeAlarmSound", SpikeAlarm.Sound); - ini.SetValue("Alarms", "DataSpikeAlarmSoundFile", SpikeAlarm.SoundFile); - ini.SetValue("Alarms", "DataSpikeAlarmNotify", SpikeAlarm.Notify); - ini.SetValue("Alarms", "DataSpikeAlarmEmail", SpikeAlarm.Email); - ini.SetValue("Alarms", "DataSpikeAlarmLatch", SpikeAlarm.Latch); - ini.SetValue("Alarms", "DataSpikeAlarmLatchHours", SpikeAlarm.LatchHours); - - ini.SetValue("Alarms", "UpgradeAlarmSet", UpgradeAlarm.Enabled); - ini.SetValue("Alarms", "UpgradeAlarmSound", UpgradeAlarm.Sound); - ini.SetValue("Alarms", "UpgradeAlarmSoundFile", UpgradeAlarm.SoundFile); - ini.SetValue("Alarms", "UpgradeAlarmNotify", UpgradeAlarm.Notify); - ini.SetValue("Alarms", "UpgradeAlarmEmail", UpgradeAlarm.Email); - ini.SetValue("Alarms", "UpgradeAlarmLatch", UpgradeAlarm.Latch); - ini.SetValue("Alarms", "UpgradeAlarmLatchHours", UpgradeAlarm.LatchHours); - - ini.SetValue("Alarms", "HttpUploadStoppedAlarmSet", HttpUploadAlarm.Enabled); - ini.SetValue("Alarms", "HttpUploadStoppedAlarmSound", HttpUploadAlarm.Sound); - ini.SetValue("Alarms", "HttpUploadStoppedAlarmSoundFile", HttpUploadAlarm.SoundFile); - ini.SetValue("Alarms", "HttpUploadStoppedAlarmNotify", HttpUploadAlarm.Notify); - ini.SetValue("Alarms", "HttpUploadStoppedAlarmEmail", HttpUploadAlarm.Email); - ini.SetValue("Alarms", "HttpUploadStoppedAlarmLatch", HttpUploadAlarm.Latch); - ini.SetValue("Alarms", "HttpUploadStoppedAlarmLatchHours", HttpUploadAlarm.LatchHours); - - ini.SetValue("Alarms", "HttpUploadStoppedAlarmSet", MySqlUploadAlarm.Enabled); - ini.SetValue("Alarms", "HttpUploadStoppedAlarmSound", MySqlUploadAlarm.Sound); - ini.SetValue("Alarms", "HttpUploadStoppedAlarmSoundFile", MySqlUploadAlarm.SoundFile); - ini.SetValue("Alarms", "HttpUploadStoppedAlarmNotify", MySqlUploadAlarm.Notify); - ini.SetValue("Alarms", "HttpUploadStoppedAlarmEmail", MySqlUploadAlarm.Email); - ini.SetValue("Alarms", "HttpUploadStoppedAlarmLatch", MySqlUploadAlarm.Latch); - ini.SetValue("Alarms", "HttpUploadStoppedAlarmLatchHours", MySqlUploadAlarm.LatchHours); - - ini.SetValue("Alarms", "FromEmail", AlarmFromEmail); - ini.SetValue("Alarms", "DestEmail", AlarmDestEmail.Join(";")); - ini.SetValue("Alarms", "UseHTML", AlarmEmailHtml); - - - ini.SetValue("Offsets", "PressOffset", Calib.Press.Offset); - ini.SetValue("Offsets", "TempOffset", Calib.Temp.Offset); - ini.SetValue("Offsets", "HumOffset", Calib.Hum.Offset); - ini.SetValue("Offsets", "WindDirOffset", Calib.WindDir.Offset); - ini.SetValue("Offsets", "InTempOffset", Calib.InTemp.Offset); - ini.SetValue("Offsets", "UVOffset", Calib.UV.Offset); - ini.SetValue("Offsets", "SolarOffset", Calib.Solar.Offset); - ini.SetValue("Offsets", "WetBulbOffset", Calib.WetBulb.Offset); - //ini.SetValue("Offsets", "DavisCalcAltPressOffset", DavisCalcAltPressOffset); - - ini.SetValue("Offsets", "PressMult", Calib.Press.Mult); - ini.SetValue("Offsets", "WindSpeedMult", Calib.WindSpeed.Mult); - ini.SetValue("Offsets", "WindGustMult", Calib.WindGust.Mult); - ini.SetValue("Offsets", "TempMult", Calib.Temp.Mult); - ini.SetValue("Offsets", "HumMult", Calib.Hum.Mult); - ini.SetValue("Offsets", "RainMult", Calib.Rain.Mult); - ini.SetValue("Offsets", "SolarMult", Calib.Solar.Mult); - ini.SetValue("Offsets", "UVMult", Calib.UV.Mult); - ini.SetValue("Offsets", "WetBulbMult", Calib.WetBulb.Mult); - - ini.SetValue("Limits", "TempHighC", Limit.TempHigh); - ini.SetValue("Limits", "TempLowC", Limit.TempLow); - ini.SetValue("Limits", "DewHighC", Limit.DewHigh); - ini.SetValue("Limits", "PressHighMB", Limit.PressHigh); - ini.SetValue("Limits", "PressLowMB", Limit.PressLow); - ini.SetValue("Limits", "WindHighMS", Limit.WindHigh); - - ini.SetValue("xAP", "Enabled", xapEnabled); - ini.SetValue("xAP", "UID", xapUID); - ini.SetValue("xAP", "Port", xapPort); - - ini.SetValue("Solar", "SunThreshold", SunThreshold); - ini.SetValue("Solar", "RStransfactor", RStransfactor); - ini.SetValue("Solar", "SolarMinimum", SolarMinimum); - ini.SetValue("Solar", "UseBlakeLarsen", UseBlakeLarsen); - ini.SetValue("Solar", "SolarCalc", SolarCalc); - ini.SetValue("Solar", "BrasTurbidity", BrasTurbidity); - - ini.SetValue("NOAA", "Name", NOAAname); - ini.SetValue("NOAA", "City", NOAAcity); - ini.SetValue("NOAA", "State", NOAAstate); - ini.SetValue("NOAA", "12hourformat", NOAA12hourformat); - ini.SetValue("NOAA", "HeatingThreshold", NOAAheatingthreshold); - ini.SetValue("NOAA", "CoolingThreshold", NOAAcoolingthreshold); - ini.SetValue("NOAA", "MaxTempComp1", NOAAmaxtempcomp1); - ini.SetValue("NOAA", "MaxTempComp2", NOAAmaxtempcomp2); - ini.SetValue("NOAA", "MinTempComp1", NOAAmintempcomp1); - ini.SetValue("NOAA", "MinTempComp2", NOAAmintempcomp2); - ini.SetValue("NOAA", "RainComp1", NOAAraincomp1); - ini.SetValue("NOAA", "RainComp2", NOAAraincomp2); - ini.SetValue("NOAA", "RainComp3", NOAAraincomp3); - ini.SetValue("NOAA", "AutoSave", NOAAAutoSave); - ini.SetValue("NOAA", "AutoFTP", NOAAAutoFTP); - ini.SetValue("NOAA", "MonthFileFormat", NOAAMonthFileFormat); - ini.SetValue("NOAA", "YearFileFormat", NOAAYearFileFormat); - ini.SetValue("NOAA", "FTPDirectory", NOAAFTPDirectory); - ini.SetValue("NOAA", "NOAAUseUTF8", NOAAUseUTF8); - ini.SetValue("NOAA", "UseDotDecimal", NOAAUseDotDecimal); - - ini.SetValue("NOAA", "NOAATempNormJan", NOAATempNorms[1]); - ini.SetValue("NOAA", "NOAATempNormFeb", NOAATempNorms[2]); - ini.SetValue("NOAA", "NOAATempNormMar", NOAATempNorms[3]); - ini.SetValue("NOAA", "NOAATempNormApr", NOAATempNorms[4]); - ini.SetValue("NOAA", "NOAATempNormMay", NOAATempNorms[5]); - ini.SetValue("NOAA", "NOAATempNormJun", NOAATempNorms[6]); - ini.SetValue("NOAA", "NOAATempNormJul", NOAATempNorms[7]); - ini.SetValue("NOAA", "NOAATempNormAug", NOAATempNorms[8]); - ini.SetValue("NOAA", "NOAATempNormSep", NOAATempNorms[9]); - ini.SetValue("NOAA", "NOAATempNormOct", NOAATempNorms[10]); - ini.SetValue("NOAA", "NOAATempNormNov", NOAATempNorms[11]); - ini.SetValue("NOAA", "NOAATempNormDec", NOAATempNorms[12]); - - ini.SetValue("NOAA", "NOAARainNormJan", NOAARainNorms[1]); - ini.SetValue("NOAA", "NOAARainNormFeb", NOAARainNorms[2]); - ini.SetValue("NOAA", "NOAARainNormMar", NOAARainNorms[3]); - ini.SetValue("NOAA", "NOAARainNormApr", NOAARainNorms[4]); - ini.SetValue("NOAA", "NOAARainNormMay", NOAARainNorms[5]); - ini.SetValue("NOAA", "NOAARainNormJun", NOAARainNorms[6]); - ini.SetValue("NOAA", "NOAARainNormJul", NOAARainNorms[7]); - ini.SetValue("NOAA", "NOAARainNormAug", NOAARainNorms[8]); - ini.SetValue("NOAA", "NOAARainNormSep", NOAARainNorms[9]); - ini.SetValue("NOAA", "NOAARainNormOct", NOAARainNorms[10]); - ini.SetValue("NOAA", "NOAARainNormNov", NOAARainNorms[11]); - ini.SetValue("NOAA", "NOAARainNormDec", NOAARainNorms[12]); - - ini.SetValue("Proxies", "HTTPProxyName", HTTPProxyName); - ini.SetValue("Proxies", "HTTPProxyPort", HTTPProxyPort); - ini.SetValue("Proxies", "HTTPProxyUser", HTTPProxyUser); - ini.SetValue("Proxies", "HTTPProxyPassword", HTTPProxyPassword); - - ini.SetValue("Display", "NumWindRosePoints", NumWindRosePoints); - ini.SetValue("Display", "UseApparent", DisplayOptions.UseApparent); - ini.SetValue("Display", "DisplaySolarData", DisplayOptions.ShowSolar); - ini.SetValue("Display", "DisplayUvData", DisplayOptions.ShowUV); - - ini.SetValue("Graphs", "ChartMaxDays", GraphDays); - ini.SetValue("Graphs", "GraphHours", GraphHours); - ini.SetValue("Graphs", "MoonImageEnabled", MoonImageEnabled); - ini.SetValue("Graphs", "MoonImageSize", MoonImageSize); - ini.SetValue("Graphs", "MoonImageFtpDest", MoonImageFtpDest); - ini.SetValue("Graphs", "TempVisible", GraphOptions.TempVisible); - ini.SetValue("Graphs", "InTempVisible", GraphOptions.InTempVisible); - ini.SetValue("Graphs", "HIVisible", GraphOptions.HIVisible); - ini.SetValue("Graphs", "DPVisible", GraphOptions.DPVisible); - ini.SetValue("Graphs", "WCVisible", GraphOptions.WCVisible); - ini.SetValue("Graphs", "AppTempVisible", GraphOptions.AppTempVisible); - ini.SetValue("Graphs", "FeelsLikeVisible", GraphOptions.FeelsLikeVisible); - ini.SetValue("Graphs", "HumidexVisible", GraphOptions.HumidexVisible); - ini.SetValue("Graphs", "InHumVisible", GraphOptions.InHumVisible); - ini.SetValue("Graphs", "OutHumVisible", GraphOptions.OutHumVisible); - ini.SetValue("Graphs", "UVVisible", GraphOptions.UVVisible); - ini.SetValue("Graphs", "SolarVisible", GraphOptions.SolarVisible); - ini.SetValue("Graphs", "SunshineVisible", GraphOptions.SunshineVisible); - ini.SetValue("Graphs", "DailyAvgTempVisible", GraphOptions.DailyAvgTempVisible); - ini.SetValue("Graphs", "DailyMaxTempVisible", GraphOptions.DailyMaxTempVisible); - ini.SetValue("Graphs", "DailyMinTempVisible", GraphOptions.DailyMinTempVisible); - ini.SetValue("Graphs", "GrowingDegreeDaysVisible1", GraphOptions.GrowingDegreeDaysVisible1); - ini.SetValue("Graphs", "GrowingDegreeDaysVisible2", GraphOptions.GrowingDegreeDaysVisible2); - ini.SetValue("Graphs", "TempSumVisible0", GraphOptions.TempSumVisible0); - ini.SetValue("Graphs", "TempSumVisible1", GraphOptions.TempSumVisible1); - ini.SetValue("Graphs", "TempSumVisible2", GraphOptions.TempSumVisible2); - - - ini.SetValue("MySQL", "Host", MySqlConnSettings.Server); - ini.SetValue("MySQL", "Port", MySqlConnSettings.Port); - ini.SetValue("MySQL", "User", MySqlConnSettings.UserID); - ini.SetValue("MySQL", "Pass", MySqlConnSettings.Password); - ini.SetValue("MySQL", "Database", MySqlConnSettings.Database); - ini.SetValue("MySQL", "MonthlyMySqlEnabled", MonthlyMySqlEnabled); - ini.SetValue("MySQL", "RealtimeMySqlEnabled", RealtimeMySqlEnabled); - ini.SetValue("MySQL", "DayfileMySqlEnabled", DayfileMySqlEnabled); - ini.SetValue("MySQL", "MonthlyTable", MySqlMonthlyTable); - ini.SetValue("MySQL", "DayfileTable", MySqlDayfileTable); - ini.SetValue("MySQL", "RealtimeTable", MySqlRealtimeTable); - ini.SetValue("MySQL", "RealtimeRetention", MySqlRealtimeRetention); - ini.SetValue("MySQL", "CustomMySqlSecondsCommandString", CustomMySqlSecondsCommandString); - ini.SetValue("MySQL", "CustomMySqlMinutesCommandString", CustomMySqlMinutesCommandString); - ini.SetValue("MySQL", "CustomMySqlRolloverCommandString", CustomMySqlRolloverCommandString); - - ini.SetValue("MySQL", "CustomMySqlSecondsEnabled", CustomMySqlSecondsEnabled); - ini.SetValue("MySQL", "CustomMySqlMinutesEnabled", CustomMySqlMinutesEnabled); - ini.SetValue("MySQL", "CustomMySqlRolloverEnabled", CustomMySqlRolloverEnabled); - - ini.SetValue("MySQL", "CustomMySqlSecondsInterval", CustomMySqlSecondsInterval); - ini.SetValue("MySQL", "CustomMySqlMinutesIntervalIndex", CustomMySqlMinutesIntervalIndex); - - ini.SetValue("HTTP", "CustomHttpSecondsString", CustomHttpSecondsString); - ini.SetValue("HTTP", "CustomHttpMinutesString", CustomHttpMinutesString); - ini.SetValue("HTTP", "CustomHttpRolloverString", CustomHttpRolloverString); - - ini.SetValue("HTTP", "CustomHttpSecondsEnabled", CustomHttpSecondsEnabled); - ini.SetValue("HTTP", "CustomHttpMinutesEnabled", CustomHttpMinutesEnabled); - ini.SetValue("HTTP", "CustomHttpRolloverEnabled", CustomHttpRolloverEnabled); - - ini.SetValue("HTTP", "CustomHttpSecondsInterval", CustomHttpSecondsInterval); - ini.SetValue("HTTP", "CustomHttpMinutesIntervalIndex", CustomHttpMinutesIntervalIndex); - - for (int i = 0; i < SelectaChartOptions.series.Length; i++) - { - ini.SetValue("Select-a-Chart", "Series" + i, SelectaChartOptions.series[i]); - ini.SetValue("Select-a-Chart", "Colour" + i, SelectaChartOptions.colours[i]); - } - - // Email settings - ini.SetValue("SMTP", "Enabled", SmtpOptions.Enabled); - ini.SetValue("SMTP", "ServerName", SmtpOptions.Server); - ini.SetValue("SMTP", "Port", SmtpOptions.Port); - ini.SetValue("SMTP", "SSLOption", SmtpOptions.SslOption); - ini.SetValue("SMTP", "RequiresAuthentication", SmtpOptions.RequiresAuthentication); - ini.SetValue("SMTP", "User", SmtpOptions.User); - ini.SetValue("SMTP", "Password", SmtpOptions.Password); - ini.SetValue("SMTP", "Logging", SmtpOptions.Logging); - - // Growing Degree Days - ini.SetValue("GrowingDD", "BaseTemperature1", GrowingBase1); - ini.SetValue("GrowingDD", "BaseTemperature2", GrowingBase2); - ini.SetValue("GrowingDD", "YearStarts", GrowingYearStarts); - ini.SetValue("GrowingDD", "Cap30C", GrowingCap30C); - - // Temperature Sum - ini.SetValue("TempSum", "TempSumYearStart", TempSumYearStarts); - ini.SetValue("TempSum", "BaseTemperature1", TempSumBase1); - ini.SetValue("TempSum", "BaseTemperature2", TempSumBase2); - - ini.Flush(); - - LogMessage("Completed writing Cumulus.ini file"); - } - - private void ReadStringsFile() - { - IniFile ini = new IniFile("strings.ini"); - - // forecast - - ForecastNotAvailable = ini.GetValue("Forecast", "notavailable", ForecastNotAvailable); - - exceptional = ini.GetValue("Forecast", "exceptional", exceptional); - zForecast[0] = ini.GetValue("Forecast", "forecast1", zForecast[0]); - zForecast[1] = ini.GetValue("Forecast", "forecast2", zForecast[1]); - zForecast[2] = ini.GetValue("Forecast", "forecast3", zForecast[2]); - zForecast[3] = ini.GetValue("Forecast", "forecast4", zForecast[3]); - zForecast[4] = ini.GetValue("Forecast", "forecast5", zForecast[4]); - zForecast[5] = ini.GetValue("Forecast", "forecast6", zForecast[5]); - zForecast[6] = ini.GetValue("Forecast", "forecast7", zForecast[6]); - zForecast[7] = ini.GetValue("Forecast", "forecast8", zForecast[7]); - zForecast[8] = ini.GetValue("Forecast", "forecast9", zForecast[8]); - zForecast[9] = ini.GetValue("Forecast", "forecast10", zForecast[9]); - zForecast[10] = ini.GetValue("Forecast", "forecast11", zForecast[10]); - zForecast[11] = ini.GetValue("Forecast", "forecast12", zForecast[11]); - zForecast[12] = ini.GetValue("Forecast", "forecast13", zForecast[12]); - zForecast[13] = ini.GetValue("Forecast", "forecast14", zForecast[13]); - zForecast[14] = ini.GetValue("Forecast", "forecast15", zForecast[14]); - zForecast[15] = ini.GetValue("Forecast", "forecast16", zForecast[15]); - zForecast[16] = ini.GetValue("Forecast", "forecast17", zForecast[16]); - zForecast[17] = ini.GetValue("Forecast", "forecast18", zForecast[17]); - zForecast[18] = ini.GetValue("Forecast", "forecast19", zForecast[18]); - zForecast[19] = ini.GetValue("Forecast", "forecast20", zForecast[19]); - zForecast[20] = ini.GetValue("Forecast", "forecast21", zForecast[20]); - zForecast[21] = ini.GetValue("Forecast", "forecast22", zForecast[21]); - zForecast[22] = ini.GetValue("Forecast", "forecast23", zForecast[22]); - zForecast[23] = ini.GetValue("Forecast", "forecast24", zForecast[23]); - zForecast[24] = ini.GetValue("Forecast", "forecast25", zForecast[24]); - zForecast[25] = ini.GetValue("Forecast", "forecast26", zForecast[25]); - // moon phases - Newmoon = ini.GetValue("MoonPhases", "Newmoon", Newmoon); - WaxingCrescent = ini.GetValue("MoonPhases", "WaxingCrescent", WaxingCrescent); - FirstQuarter = ini.GetValue("MoonPhases", "FirstQuarter", FirstQuarter); - WaxingGibbous = ini.GetValue("MoonPhases", "WaxingGibbous", WaxingGibbous); - Fullmoon = ini.GetValue("MoonPhases", "Fullmoon", Fullmoon); - WaningGibbous = ini.GetValue("MoonPhases", "WaningGibbous", WaningGibbous); - LastQuarter = ini.GetValue("MoonPhases", "LastQuarter", LastQuarter); - WaningCrescent = ini.GetValue("MoonPhases", "WaningCrescent", WaningCrescent); - // beaufort - Calm = ini.GetValue("Beaufort", "Calm", Calm); - Lightair = ini.GetValue("Beaufort", "Lightair", Lightair); - Lightbreeze = ini.GetValue("Beaufort", "Lightbreeze", Lightbreeze); - Gentlebreeze = ini.GetValue("Beaufort", "Gentlebreeze", Gentlebreeze); - Moderatebreeze = ini.GetValue("Beaufort", "Moderatebreeze", Moderatebreeze); - Freshbreeze = ini.GetValue("Beaufort", "Freshbreeze", Freshbreeze); - Strongbreeze = ini.GetValue("Beaufort", "Strongbreeze", Strongbreeze); - Neargale = ini.GetValue("Beaufort", "Neargale", Neargale); - Gale = ini.GetValue("Beaufort", "Gale", Gale); - Stronggale = ini.GetValue("Beaufort", "Stronggale", Stronggale); - Storm = ini.GetValue("Beaufort", "Storm", Storm); - Violentstorm = ini.GetValue("Beaufort", "Violentstorm", Violentstorm); - Hurricane = ini.GetValue("Beaufort", "Hurricane", Hurricane); - // trends - Risingveryrapidly = ini.GetValue("Trends", "Risingveryrapidly", Risingveryrapidly); - Risingquickly = ini.GetValue("Trends", "Risingquickly", Risingquickly); - Rising = ini.GetValue("Trends", "Rising", Rising); - Risingslowly = ini.GetValue("Trends", "Risingslowly", Risingslowly); - Steady = ini.GetValue("Trends", "Steady", Steady); - Fallingslowly = ini.GetValue("Trends", "Fallingslowly", Fallingslowly); - Falling = ini.GetValue("Trends", "Falling", Falling); - Fallingquickly = ini.GetValue("Trends", "Fallingquickly", Fallingquickly); - Fallingveryrapidly = ini.GetValue("Trends", "Fallingveryrapidly", Fallingveryrapidly); - // compass points - compassp[0] = ini.GetValue("Compass", "N", compassp[0]); - compassp[1] = ini.GetValue("Compass", "NNE", compassp[1]); - compassp[2] = ini.GetValue("Compass", "NE", compassp[2]); - compassp[3] = ini.GetValue("Compass", "ENE", compassp[3]); - compassp[4] = ini.GetValue("Compass", "E", compassp[4]); - compassp[5] = ini.GetValue("Compass", "ESE", compassp[5]); - compassp[6] = ini.GetValue("Compass", "SE", compassp[6]); - compassp[7] = ini.GetValue("Compass", "SSE", compassp[7]); - compassp[8] = ini.GetValue("Compass", "S", compassp[8]); - compassp[9] = ini.GetValue("Compass", "SSW", compassp[9]); - compassp[10] = ini.GetValue("Compass", "SW", compassp[10]); - compassp[11] = ini.GetValue("Compass", "WSW", compassp[11]); - compassp[12] = ini.GetValue("Compass", "W", compassp[12]); - compassp[13] = ini.GetValue("Compass", "WNW", compassp[13]); - compassp[14] = ini.GetValue("Compass", "NW", compassp[14]); - compassp[15] = ini.GetValue("Compass", "NNW", compassp[15]); - // graphs - /* - SmallGraphWindSpeedTitle = ini.GetValue("Graphs", "SmallGraphWindSpeedTitle", "Wind Speed"); - SmallGraphOutsideTemperatureTitle = ini.GetValue("Graphs", "SmallGraphOutsideTemperatureTitle", "Outside Temperature"); - SmallGraphInsideTemperatureTitle = ini.GetValue("Graphs", "SmallGraphInsideTemperatureTitle", "Inside Temperature"); - SmallGraphPressureTitle = ini.GetValue("Graphs", "SmallGraphPressureTitle", "Pressure"); - SmallGraphRainfallRateTitle = ini.GetValue("Graphs", "SmallGraphRainfallRateTitle", "Rainfall Rate"); - SmallGraphWindDirectionTitle = ini.GetValue("Graphs", "SmallGraphWindDirectionTitle", "Wind Direction"); - SmallGraphTempMinMaxAvgTitle = ini.GetValue("Graphs", "SmallGraphTempMinMaxAvgTitle", "Temp Min/Max/Avg"); - SmallGraphHumidityTitle = ini.GetValue("Graphs", "SmallGraphHumidityTitle", "Humidity"); - SmallGraphRainTodayTitle = ini.GetValue("Graphs", "SmallGraphRainTodayTitle", "Rain Today"); - SmallGraphDailyRainTitle = ini.GetValue("Graphs", "SmallGraphDailyRainTitle", "Daily Rain"); - SmallGraphSolarTitle = ini.GetValue("Graphs", "SmallGraphSolarTitle", "Solar Radiation"); - SmallGraphUVTitle = ini.GetValue("Graphs", "SmallGraphUVTitle", "UV Index"); - SmallGraphSunshineTitle = ini.GetValue("Graphs", "SmallGraphSunshineTitle", "Daily Sunshine (hrs)"); - - LargeGraphWindSpeedTitle = ini.GetValue("Graphs", "LargeGraphWindSpeedTitle", "Wind Speed"); - LargeGraphWindGustTitle = ini.GetValue("Graphs", "LargeGraphWindGustTitle", "Wind Gust"); - LargeGraphOutsideTempTitle = ini.GetValue("Graphs", "LargeGraphOutsideTempTitle", "Temperature"); - LargeGraphHeatIndexTitle = ini.GetValue("Graphs", "LargeGraphHeatIndexTitle", "Heat Index"); - LargeGraphDewPointTitle = ini.GetValue("Graphs", "LargeGraphDewPointTitle", "Dew Point"); - LargeGraphWindChillTitle = ini.GetValue("Graphs", "LargeGraphWindChillTitle", "Wind Chill"); - LargeGraphApparentTempTitle = ini.GetValue("Graphs", "LargeGraphApparentTempTitle", "Apparent Temperature"); - LargeGraphInsideTempTitle = ini.GetValue("Graphs", "LargeGraphInsideTempTitle", "Inside Temperature"); - LargeGraphPressureTitle = ini.GetValue("Graphs", "LargeGraphPressureTitle", "Pressure"); - LargeGraphRainfallRateTitle = ini.GetValue("Graphs", "LargeGraphRainfallRateTitle", "Rainfall Rate"); - LargeGraphWindDirectionTitle = ini.GetValue("Graphs", "LargeGraphWindDirectionTitle", "Wind Direction"); - LargeGraphWindAvgDirectionTitle = ini.GetValue("Graphs", "LargeGraphWindAvgDirectionTitle", "Average"); - LargeGraphMinTempTitle = ini.GetValue("Graphs", "LargeGraphMinTempTitle", "Min Temp"); - LargeGraphMaxTempTitle = ini.GetValue("Graphs", "LargeGraphMaxTempTitle", "Max Temp"); - LargeGraphAvgTempTitle = ini.GetValue("Graphs", "LargeGraphAvgTempTitle", "Avg Temp"); - LargeGraphInsideHumidityTitle = ini.GetValue("Graphs", "LargeGraphInsideHumidityTitle", "Inside Humidity"); - LargeGraphOutsideHumidityTitle = ini.GetValue("Graphs", "LargeGraphOutsideHumidityTitle", "Outside Humidity"); - LargeGraphRainfallTodayTitle = ini.GetValue("Graphs", "LargeGraphRainfallTodayTitle", "Rainfall Today"); - LargeGraphDailyRainfallTitle = ini.GetValue("Graphs", "LargeGraphDailyRainfallTitle", "Daily Rainfall"); - LargeGraphSolarTitle = ini.GetValue("Graphs", "LargeGraphSolarTitle", "Solar Radiation"); - LargeGraphMaxSolarTitle = ini.GetValue("Graphs", "LargeGraphMaxSolarTitle", "Theoretical Max"); - LargeGraphUVTitle = ini.GetValue("Graphs", "LargeGraphUVTitle", "UV Index"); - LargeGraphSunshineTitle = ini.GetValue("Graphs", "LargeGraphSunshineTitle", "Daily Sunshine (hrs)"); - */ - // Extra sensor captions - WMR200ExtraChannelCaptions[1] = ini.GetValue("ExtraSensorCaptions", "Solar", WMR200ExtraChannelCaptions[1]); - WMR200ExtraChannelCaptions[2] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel2", WMR200ExtraChannelCaptions[2]); - WMR200ExtraChannelCaptions[3] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel3", WMR200ExtraChannelCaptions[3]); - WMR200ExtraChannelCaptions[4] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel4", WMR200ExtraChannelCaptions[4]); - WMR200ExtraChannelCaptions[5] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel5", WMR200ExtraChannelCaptions[5]); - WMR200ExtraChannelCaptions[6] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel6", WMR200ExtraChannelCaptions[6]); - WMR200ExtraChannelCaptions[7] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel7", WMR200ExtraChannelCaptions[7]); - WMR200ExtraChannelCaptions[8] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel8", WMR200ExtraChannelCaptions[8]); - WMR200ExtraChannelCaptions[9] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel9", WMR200ExtraChannelCaptions[9]); - WMR200ExtraChannelCaptions[10] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel10", WMR200ExtraChannelCaptions[10]); - - // Extra temperature captions (for Extra Sensor Data screen) - ExtraTempCaptions[1] = ini.GetValue("ExtraTempCaptions", "Sensor1", ExtraTempCaptions[1]); - ExtraTempCaptions[2] = ini.GetValue("ExtraTempCaptions", "Sensor2", ExtraTempCaptions[2]); - ExtraTempCaptions[3] = ini.GetValue("ExtraTempCaptions", "Sensor3", ExtraTempCaptions[3]); - ExtraTempCaptions[4] = ini.GetValue("ExtraTempCaptions", "Sensor4", ExtraTempCaptions[4]); - ExtraTempCaptions[5] = ini.GetValue("ExtraTempCaptions", "Sensor5", ExtraTempCaptions[5]); - ExtraTempCaptions[6] = ini.GetValue("ExtraTempCaptions", "Sensor6", ExtraTempCaptions[6]); - ExtraTempCaptions[7] = ini.GetValue("ExtraTempCaptions", "Sensor7", ExtraTempCaptions[7]); - ExtraTempCaptions[8] = ini.GetValue("ExtraTempCaptions", "Sensor8", ExtraTempCaptions[8]); - ExtraTempCaptions[9] = ini.GetValue("ExtraTempCaptions", "Sensor9", ExtraTempCaptions[9]); - ExtraTempCaptions[10] = ini.GetValue("ExtraTempCaptions", "Sensor10", ExtraTempCaptions[10]); - - // Extra humidity captions (for Extra Sensor Data screen) - ExtraHumCaptions[1] = ini.GetValue("ExtraHumCaptions", "Sensor1", ExtraHumCaptions[1]); - ExtraHumCaptions[2] = ini.GetValue("ExtraHumCaptions", "Sensor2", ExtraHumCaptions[2]); - ExtraHumCaptions[3] = ini.GetValue("ExtraHumCaptions", "Sensor3", ExtraHumCaptions[3]); - ExtraHumCaptions[4] = ini.GetValue("ExtraHumCaptions", "Sensor4", ExtraHumCaptions[4]); - ExtraHumCaptions[5] = ini.GetValue("ExtraHumCaptions", "Sensor5", ExtraHumCaptions[5]); - ExtraHumCaptions[6] = ini.GetValue("ExtraHumCaptions", "Sensor6", ExtraHumCaptions[6]); - ExtraHumCaptions[7] = ini.GetValue("ExtraHumCaptions", "Sensor7", ExtraHumCaptions[7]); - ExtraHumCaptions[8] = ini.GetValue("ExtraHumCaptions", "Sensor8", ExtraHumCaptions[8]); - ExtraHumCaptions[9] = ini.GetValue("ExtraHumCaptions", "Sensor9", ExtraHumCaptions[9]); - ExtraHumCaptions[10] = ini.GetValue("ExtraHumCaptions", "Sensor10", ExtraHumCaptions[10]); - - // Extra dew point captions (for Extra Sensor Data screen) - ExtraDPCaptions[1] = ini.GetValue("ExtraDPCaptions", "Sensor1", ExtraDPCaptions[1]); - ExtraDPCaptions[2] = ini.GetValue("ExtraDPCaptions", "Sensor2", ExtraDPCaptions[2]); - ExtraDPCaptions[3] = ini.GetValue("ExtraDPCaptions", "Sensor3", ExtraDPCaptions[3]); - ExtraDPCaptions[4] = ini.GetValue("ExtraDPCaptions", "Sensor4", ExtraDPCaptions[4]); - ExtraDPCaptions[5] = ini.GetValue("ExtraDPCaptions", "Sensor5", ExtraDPCaptions[5]); - ExtraDPCaptions[6] = ini.GetValue("ExtraDPCaptions", "Sensor6", ExtraDPCaptions[6]); - ExtraDPCaptions[7] = ini.GetValue("ExtraDPCaptions", "Sensor7", ExtraDPCaptions[7]); - ExtraDPCaptions[8] = ini.GetValue("ExtraDPCaptions", "Sensor8", ExtraDPCaptions[8]); - ExtraDPCaptions[9] = ini.GetValue("ExtraDPCaptions", "Sensor9", ExtraDPCaptions[9]); - ExtraDPCaptions[10] = ini.GetValue("ExtraDPCaptions", "Sensor10", ExtraDPCaptions[10]); - - // soil temp captions (for Extra Sensor Data screen) - SoilTempCaptions[1] = ini.GetValue("SoilTempCaptions", "Sensor1", SoilTempCaptions[1]); - SoilTempCaptions[2] = ini.GetValue("SoilTempCaptions", "Sensor2", SoilTempCaptions[2]); - SoilTempCaptions[3] = ini.GetValue("SoilTempCaptions", "Sensor3", SoilTempCaptions[3]); - SoilTempCaptions[4] = ini.GetValue("SoilTempCaptions", "Sensor4", SoilTempCaptions[4]); - SoilTempCaptions[5] = ini.GetValue("SoilTempCaptions", "Sensor5", SoilTempCaptions[5]); - SoilTempCaptions[6] = ini.GetValue("SoilTempCaptions", "Sensor6", SoilTempCaptions[6]); - SoilTempCaptions[7] = ini.GetValue("SoilTempCaptions", "Sensor7", SoilTempCaptions[7]); - SoilTempCaptions[8] = ini.GetValue("SoilTempCaptions", "Sensor8", SoilTempCaptions[8]); - SoilTempCaptions[9] = ini.GetValue("SoilTempCaptions", "Sensor9", SoilTempCaptions[9]); - SoilTempCaptions[10] = ini.GetValue("SoilTempCaptions", "Sensor10", SoilTempCaptions[10]); - SoilTempCaptions[11] = ini.GetValue("SoilTempCaptions", "Sensor11", SoilTempCaptions[11]); - SoilTempCaptions[12] = ini.GetValue("SoilTempCaptions", "Sensor12", SoilTempCaptions[12]); - SoilTempCaptions[13] = ini.GetValue("SoilTempCaptions", "Sensor13", SoilTempCaptions[13]); - SoilTempCaptions[14] = ini.GetValue("SoilTempCaptions", "Sensor14", SoilTempCaptions[14]); - SoilTempCaptions[15] = ini.GetValue("SoilTempCaptions", "Sensor15", SoilTempCaptions[15]); - SoilTempCaptions[16] = ini.GetValue("SoilTempCaptions", "Sensor16", SoilTempCaptions[16]); - - // soil moisture captions (for Extra Sensor Data screen) - SoilMoistureCaptions[1] = ini.GetValue("SoilMoistureCaptions", "Sensor1", SoilMoistureCaptions[1]); - SoilMoistureCaptions[2] = ini.GetValue("SoilMoistureCaptions", "Sensor2", SoilMoistureCaptions[2]); - SoilMoistureCaptions[3] = ini.GetValue("SoilMoistureCaptions", "Sensor3", SoilMoistureCaptions[3]); - SoilMoistureCaptions[4] = ini.GetValue("SoilMoistureCaptions", "Sensor4", SoilMoistureCaptions[4]); - SoilMoistureCaptions[5] = ini.GetValue("SoilMoistureCaptions", "Sensor5", SoilMoistureCaptions[5]); - SoilMoistureCaptions[6] = ini.GetValue("SoilMoistureCaptions", "Sensor6", SoilMoistureCaptions[6]); - SoilMoistureCaptions[7] = ini.GetValue("SoilMoistureCaptions", "Sensor7", SoilMoistureCaptions[7]); - SoilMoistureCaptions[8] = ini.GetValue("SoilMoistureCaptions", "Sensor8", SoilMoistureCaptions[8]); - SoilMoistureCaptions[9] = ini.GetValue("SoilMoistureCaptions", "Sensor9", SoilMoistureCaptions[9]); - SoilMoistureCaptions[10] = ini.GetValue("SoilMoistureCaptions", "Sensor10", SoilMoistureCaptions[10]); - SoilMoistureCaptions[11] = ini.GetValue("SoilMoistureCaptions", "Sensor11", SoilMoistureCaptions[11]); - SoilMoistureCaptions[12] = ini.GetValue("SoilMoistureCaptions", "Sensor12", SoilMoistureCaptions[12]); - SoilMoistureCaptions[13] = ini.GetValue("SoilMoistureCaptions", "Sensor13", SoilMoistureCaptions[13]); - SoilMoistureCaptions[14] = ini.GetValue("SoilMoistureCaptions", "Sensor14", SoilMoistureCaptions[14]); - SoilMoistureCaptions[15] = ini.GetValue("SoilMoistureCaptions", "Sensor15", SoilMoistureCaptions[15]); - SoilMoistureCaptions[16] = ini.GetValue("SoilMoistureCaptions", "Sensor16", SoilMoistureCaptions[16]); - - // leaf temp captions (for Extra Sensor Data screen) - LeafTempCaptions[1] = ini.GetValue("LeafTempCaptions", "Sensor1", LeafTempCaptions[1]); - LeafTempCaptions[2] = ini.GetValue("LeafTempCaptions", "Sensor2", LeafTempCaptions[2]); - LeafTempCaptions[3] = ini.GetValue("LeafTempCaptions", "Sensor3", LeafTempCaptions[3]); - LeafTempCaptions[4] = ini.GetValue("LeafTempCaptions", "Sensor4", LeafTempCaptions[4]); - - // leaf wetness captions (for Extra Sensor Data screen) - LeafWetnessCaptions[1] = ini.GetValue("LeafWetnessCaptions", "Sensor1", LeafWetnessCaptions[1]); - LeafWetnessCaptions[2] = ini.GetValue("LeafWetnessCaptions", "Sensor2", LeafWetnessCaptions[2]); - LeafWetnessCaptions[3] = ini.GetValue("LeafWetnessCaptions", "Sensor3", LeafWetnessCaptions[3]); - LeafWetnessCaptions[4] = ini.GetValue("LeafWetnessCaptions", "Sensor4", LeafWetnessCaptions[4]); - LeafWetnessCaptions[5] = ini.GetValue("LeafWetnessCaptions", "Sensor5", LeafWetnessCaptions[5]); - LeafWetnessCaptions[6] = ini.GetValue("LeafWetnessCaptions", "Sensor6", LeafWetnessCaptions[6]); - LeafWetnessCaptions[7] = ini.GetValue("LeafWetnessCaptions", "Sensor7", LeafWetnessCaptions[7]); - LeafWetnessCaptions[8] = ini.GetValue("LeafWetnessCaptions", "Sensor8", LeafWetnessCaptions[8]); - - // air quality captions (for Extra Sensor Data screen) - AirQualityCaptions[1] = ini.GetValue("AirQualityCaptions", "Sensor1", AirQualityCaptions[1]); - AirQualityCaptions[2] = ini.GetValue("AirQualityCaptions", "Sensor2", AirQualityCaptions[2]); - AirQualityCaptions[3] = ini.GetValue("AirQualityCaptions", "Sensor3", AirQualityCaptions[3]); - AirQualityCaptions[4] = ini.GetValue("AirQualityCaptions", "Sensor4", AirQualityCaptions[4]); - AirQualityAvgCaptions[1] = ini.GetValue("AirQualityCaptions", "SensorAvg1", AirQualityAvgCaptions[1]); - AirQualityAvgCaptions[2] = ini.GetValue("AirQualityCaptions", "SensorAvg2", AirQualityAvgCaptions[2]); - AirQualityAvgCaptions[3] = ini.GetValue("AirQualityCaptions", "SensorAvg3", AirQualityAvgCaptions[3]); - AirQualityAvgCaptions[4] = ini.GetValue("AirQualityCaptions", "SensorAvg4", AirQualityAvgCaptions[4]); - - // CO2 captions - Ecowitt WH45 sensor - CO2_CurrentCaption = ini.GetValue("CO2Captions", "CO2-Current", CO2_CurrentCaption); - CO2_24HourCaption = ini.GetValue("CO2Captions", "CO2-24hr", CO2_24HourCaption); - CO2_pm2p5Caption = ini.GetValue("CO2Captions", "CO2-Pm2p5", CO2_pm2p5Caption); - CO2_pm2p5_24hrCaption = ini.GetValue("CO2Captions", "CO2-Pm2p5-24hr", CO2_pm2p5_24hrCaption); - CO2_pm10Caption = ini.GetValue("CO2Captions", "CO2-Pm10", CO2_pm10Caption); - CO2_pm10_24hrCaption = ini.GetValue("CO2Captions", "CO2-Pm10-24hr", CO2_pm10_24hrCaption); - - // User temperature captions (for Extra Sensor Data screen) - UserTempCaptions[1] = ini.GetValue("UserTempCaptions", "Sensor1", UserTempCaptions[1]); - UserTempCaptions[2] = ini.GetValue("UserTempCaptions", "Sensor2", UserTempCaptions[2]); - UserTempCaptions[3] = ini.GetValue("UserTempCaptions", "Sensor3", UserTempCaptions[3]); - UserTempCaptions[4] = ini.GetValue("UserTempCaptions", "Sensor4", UserTempCaptions[4]); - UserTempCaptions[5] = ini.GetValue("UserTempCaptions", "Sensor5", UserTempCaptions[5]); - UserTempCaptions[6] = ini.GetValue("UserTempCaptions", "Sensor6", UserTempCaptions[6]); - UserTempCaptions[7] = ini.GetValue("UserTempCaptions", "Sensor7", UserTempCaptions[7]); - UserTempCaptions[8] = ini.GetValue("UserTempCaptions", "Sensor8", UserTempCaptions[8]); - - thereWillBeMinSLessDaylightTomorrow = ini.GetValue("Solar", "LessDaylightTomorrow", thereWillBeMinSLessDaylightTomorrow); - thereWillBeMinSMoreDaylightTomorrow = ini.GetValue("Solar", "MoreDaylightTomorrow", thereWillBeMinSMoreDaylightTomorrow); - - DavisForecast1[0] = ini.GetValue("DavisForecast1", "forecast1", DavisForecast1[0]); - DavisForecast1[1] = ini.GetValue("DavisForecast1", "forecast2", DavisForecast1[1]) + " "; - DavisForecast1[2] = ini.GetValue("DavisForecast1", "forecast3", DavisForecast1[2]) + " "; - DavisForecast1[3] = ini.GetValue("DavisForecast1", "forecast4", DavisForecast1[3]) + " "; - DavisForecast1[4] = ini.GetValue("DavisForecast1", "forecast5", DavisForecast1[4]) + " "; - DavisForecast1[5] = ini.GetValue("DavisForecast1", "forecast6", DavisForecast1[5]) + " "; - DavisForecast1[6] = ini.GetValue("DavisForecast1", "forecast7", DavisForecast1[6]) + " "; - DavisForecast1[7] = ini.GetValue("DavisForecast1", "forecast8", DavisForecast1[7]) + " "; - DavisForecast1[8] = ini.GetValue("DavisForecast1", "forecast9", DavisForecast1[8]) + " "; - DavisForecast1[9] = ini.GetValue("DavisForecast1", "forecast10", DavisForecast1[9]) + " "; - DavisForecast1[10] = ini.GetValue("DavisForecast1", "forecast11", DavisForecast1[10]) + " "; - DavisForecast1[11] = ini.GetValue("DavisForecast1", "forecast12", DavisForecast1[11]) + " "; - DavisForecast1[12] = ini.GetValue("DavisForecast1", "forecast13", DavisForecast1[12]) + " "; - DavisForecast1[13] = ini.GetValue("DavisForecast1", "forecast14", DavisForecast1[13]) + " "; - DavisForecast1[14] = ini.GetValue("DavisForecast1", "forecast15", DavisForecast1[14]) + " "; - DavisForecast1[15] = ini.GetValue("DavisForecast1", "forecast16", DavisForecast1[15]) + " "; - DavisForecast1[16] = ini.GetValue("DavisForecast1", "forecast17", DavisForecast1[16]) + " "; - DavisForecast1[17] = ini.GetValue("DavisForecast1", "forecast18", DavisForecast1[17]) + " "; - DavisForecast1[18] = ini.GetValue("DavisForecast1", "forecast19", DavisForecast1[18]) + " "; - DavisForecast1[19] = ini.GetValue("DavisForecast1", "forecast20", DavisForecast1[19]) + " "; - DavisForecast1[20] = ini.GetValue("DavisForecast1", "forecast21", DavisForecast1[20]) + " "; - DavisForecast1[21] = ini.GetValue("DavisForecast1", "forecast22", DavisForecast1[21]) + " "; - DavisForecast1[22] = ini.GetValue("DavisForecast1", "forecast23", DavisForecast1[22]) + " "; - DavisForecast1[23] = ini.GetValue("DavisForecast1", "forecast24", DavisForecast1[23]) + " "; - DavisForecast1[24] = ini.GetValue("DavisForecast1", "forecast25", DavisForecast1[24]) + " "; - DavisForecast1[25] = ini.GetValue("DavisForecast1", "forecast26", DavisForecast1[25]) + " "; - DavisForecast1[26] = ini.GetValue("DavisForecast1", "forecast27", DavisForecast1[26]); - - DavisForecast2[0] = ini.GetValue("DavisForecast2", "forecast1", DavisForecast2[0]); - DavisForecast2[1] = ini.GetValue("DavisForecast2", "forecast2", DavisForecast2[1]) + " "; - DavisForecast2[2] = ini.GetValue("DavisForecast2", "forecast3", DavisForecast2[2]) + " "; - DavisForecast2[3] = ini.GetValue("DavisForecast2", "forecast4", DavisForecast2[3]) + " "; - DavisForecast2[4] = ini.GetValue("DavisForecast2", "forecast5", DavisForecast2[5]) + " "; - DavisForecast2[5] = ini.GetValue("DavisForecast2", "forecast6", DavisForecast2[5]) + " "; - DavisForecast2[6] = ini.GetValue("DavisForecast2", "forecast7", DavisForecast2[6]) + " "; - DavisForecast2[7] = ini.GetValue("DavisForecast2", "forecast8", DavisForecast2[7]) + " "; - DavisForecast2[8] = ini.GetValue("DavisForecast2", "forecast9", DavisForecast2[8]) + " "; - DavisForecast2[9] = ini.GetValue("DavisForecast2", "forecast10", DavisForecast2[9]) + " "; - DavisForecast2[10] = ini.GetValue("DavisForecast2", "forecast11", DavisForecast2[10]) + " "; - DavisForecast2[11] = ini.GetValue("DavisForecast2", "forecast12", DavisForecast2[11]) + " "; - DavisForecast2[12] = ini.GetValue("DavisForecast2", "forecast13", DavisForecast2[12]) + " "; - DavisForecast2[13] = ini.GetValue("DavisForecast2", "forecast14", DavisForecast2[13]) + " "; - DavisForecast2[14] = ini.GetValue("DavisForecast2", "forecast15", DavisForecast2[14]) + " "; - DavisForecast2[15] = ini.GetValue("DavisForecast2", "forecast16", DavisForecast2[15]) + " "; - DavisForecast2[16] = ini.GetValue("DavisForecast2", "forecast17", DavisForecast2[16]) + " "; - DavisForecast2[17] = ini.GetValue("DavisForecast2", "forecast18", DavisForecast2[17]) + " "; - DavisForecast2[18] = ini.GetValue("DavisForecast2", "forecast19", DavisForecast2[18]) + " "; - - DavisForecast3[0] = ini.GetValue("DavisForecast3", "forecast1", DavisForecast3[0]); - DavisForecast3[1] = ini.GetValue("DavisForecast3", "forecast2", DavisForecast3[1]); - DavisForecast3[2] = ini.GetValue("DavisForecast3", "forecast3", DavisForecast3[2]); - DavisForecast3[3] = ini.GetValue("DavisForecast3", "forecast4", DavisForecast3[3]); - DavisForecast3[4] = ini.GetValue("DavisForecast3", "forecast5", DavisForecast3[4]); - DavisForecast3[5] = ini.GetValue("DavisForecast3", "forecast6", DavisForecast3[5]); - DavisForecast3[6] = ini.GetValue("DavisForecast3", "forecast7", DavisForecast3[6]); - - // alarm emails - AlarmEmailSubject = ini.GetValue("AlarmEmails", "subject", "Cumulus MX Alarm"); - AlarmEmailPreamble = ini.GetValue("AlarmEmails", "preamble", "A Cumulus MX alarm has been triggered."); - HighGustAlarm.EmailMsg = ini.GetValue("AlarmEmails", "windGustAbove", "A wind gust above {0} {1} has occurred."); - HighPressAlarm.EmailMsg = ini.GetValue("AlarmEmails", "pressureAbove", "The pressure has risen above {0} {1}."); - HighTempAlarm.EmailMsg = ini.GetValue("AlarmEmails", "tempAbove", "The temperature has risen above {0} {1}."); - LowPressAlarm.EmailMsg = ini.GetValue("AlarmEmails", "pressBelow", "The pressure has fallen below {0} {1}."); - LowTempAlarm.EmailMsg = ini.GetValue("AlarmEmails", "tempBelow", "The temperature has fallen below {0} {1}."); - PressChangeAlarm.EmailMsgDn = ini.GetValue("AlarmEmails", "pressDown", "The pressure has decreased by more than {0} {1}."); - PressChangeAlarm.EmailMsgUp = ini.GetValue("AlarmEmails", "pressUp", "The pressure has increased by more than {0} {1}."); - HighRainTodayAlarm.EmailMsg = ini.GetValue("AlarmEmails", "rainAbove", "The rainfall today has exceeded {0} {1}."); - HighRainRateAlarm.EmailMsg = ini.GetValue("AlarmEmails", "rainRateAbove", "The rainfall rate has exceeded {0} {1}."); - SensorAlarm.EmailMsg = ini.GetValue("AlarmEmails", "sensorLost", "Contact has been lost with a remote sensor,"); - TempChangeAlarm.EmailMsgDn = ini.GetValue("AlarmEmails", "tempDown", "The temperature decreased by more than {0} {1}."); - TempChangeAlarm.EmailMsgUp = ini.GetValue("AlarmEmails", "tempUp", "The temperature has increased by more than {0} {1}."); - HighWindAlarm.EmailMsg = ini.GetValue("AlarmEmails", "windAbove", "The average wind speed has exceeded {0} {1}."); - DataStoppedAlarm.EmailMsg = ini.GetValue("AlarmEmails", "dataStopped", "Cumulus has stopped receiving data from your weather station."); - BatteryLowAlarm.EmailMsg = ini.GetValue("AlarmEmails", "batteryLow", "A low battery condition has been detected."); - SpikeAlarm.EmailMsg = ini.GetValue("AlarmEmails", "dataSpike", "A data spike from your weather station has been suppressed."); - UpgradeAlarm.EmailMsg = ini.GetValue("AlarmEmails", "upgrade", "An upgrade to Cumulus MX is now available."); - HttpUploadAlarm.EmailMsg = ini.GetValue("AlarmEmails", "httpStopped", "HTTP uploads are failing."); - MySqlUploadAlarm.EmailMsg = ini.GetValue("AlarmEmails", "mySqlStopped", "MySQL uploads are failing."); - } - - - public bool UseBlakeLarsen { get; set; } - - public double LuxToWM2 { get; set; } - - public int SolarMinimum { get; set; } - - public int SunThreshold { get; set; } - - public int SolarCalc { get; set; } - - public double BrasTurbidity { get; set; } - - //public double SolarFactorSummer { get; set; } - //public double SolarFactorWinter { get; set; } - - public int xapPort { get; set; } - - public string xapUID { get; set; } - - public bool xapEnabled { get; set; } - - public bool CloudBaseInFeet { get; set; } - - public string WebcamURL { get; set; } - - public string ForumURL { get; set; } - - public string DailyParams { get; set; } - - public string RealtimeParams { get; set; } - - public string ExternalParams { get; set; } - - public string DailyProgram { get; set; } - - public string RealtimeProgram { get; set; } - - public string ExternalProgram { get; set; } - - public TExtraFiles[] ExtraFiles = new TExtraFiles[numextrafiles]; - - //public int MaxFTPconnectRetries { get; set; } - - public bool DeleteBeforeUpload { get; set; } - - public bool FTPRename { get; set; } - - public int UpdateInterval { get; set; } - - public Timer RealtimeTimer = new Timer(); - - internal Timer CustomMysqlSecondsTimer; - - public bool ActiveFTPMode { get; set; } - - public FtpProtocols Sslftp { get; set; } - - public string SshftpAuthentication { get; set; } - - public string SshftpPskFile { get; set; } - - public bool DisableFtpsEPSV { get; set; } - - public bool DisableFtpsExplicit { get; set; } - - public bool FTPlogging { get; set; } - - public bool WebIntervalEnabled { get; set; } - - public bool WebAutoUpdate { get; set; } - - public string FtpDirectory { get; set; } - - public string FtpPassword { get; set; } - - public string FtpUsername { get; set; } - - public int FtpHostPort { get; set; } - - public string FtpHostname { get; set; } - - public int WMR200TempChannel { get; set; } - - public int WMR928TempChannel { get; set; } - - public int RTdisconnectcount { get; set; } - - //public int VP2SleepInterval { get; set; } - - //public int VPClosedownTime { get; set; } - public string AirLinkInIPAddr { get; set; } - public string AirLinkOutIPAddr { get; set; } - - public bool AirLinkInEnabled { get; set; } - public bool AirLinkOutEnabled { get; set; } - - //public bool solar_logging { get; set; } - - //public bool special_logging { get; set; } - - public bool RG11DTRmode2 { get; set; } - - public bool RG11IgnoreFirst2 { get; set; } - - public double RG11tipsize2 { get; set; } - - public bool RG11TBRmode2 { get; set; } - - public string RG11Port2 { get; set; } - - public bool RG11DTRmode { get; set; } - - public bool RG11IgnoreFirst { get; set; } - - public double RG11tipsize { get; set; } - - public bool RG11TBRmode { get; set; } - - public string RG11Port { get; set; } - - public bool RG11Enabled { get; set; } - public bool RG11Enabled2 { get; set; } - - public double ChillHourThreshold { get; set; } - - public int ChillHourSeasonStart { get; set; } - - public int RainSeasonStart { get; set; } - - public double FCPressureThreshold { get; set; } - - public double FChighpress { get; set; } - - public double FClowpress { get; set; } - - public bool FCpressinMB { get; set; } - - public double RainDayThreshold { get; set; } - - public int SnowDepthHour { get; set; } - - public bool UseWindChillCutoff { get; set; } - - public bool HourlyForecast { get; set; } - - public bool UseCumulusForecast { get; set; } - - public bool UseDataLogger { get; set; } - - public bool DavisConsoleHighGust { get; set; } - - public bool DavisCalcAltPress { get; set; } - - public bool DavisUseDLLBarCalData { get; set; } - - public int LCMaxWind { get; set; } - - //public bool EWduplicatecheck { get; set; } - - public string RecordsBeganDate { get; set; } - - //public bool EWdisablecheckinit { get; set; } - - //public bool EWallowFF { get; set; } - - public int YTDrainyear { get; set; } - - public double YTDrain { get; set; } - - public string LocationDesc { get; set; } - - public string LocationName { get; set; } - - public string HTTPProxyPassword { get; set; } - - public string HTTPProxyUser { get; set; } - - public int HTTPProxyPort { get; set; } - - public string HTTPProxyName { get; set; } - - public int[] WindDPlaceDefaults = { 1, 0, 0, 0 }; // m/s, mph, km/h, knots - public int[] TempDPlaceDefaults = { 1, 1 }; - public int[] PressDPlaceDefaults = { 1, 1, 2 }; - public int[] RainDPlaceDefaults = { 1, 2 }; - public const int numextrafiles = 99; - public const int numOfSelectaChartSeries = 6; - - //public bool WS2300Sync { get; set; } - - public bool ErrorLogSpikeRemoval { get; set; } - - //public bool NoFlashWetDryDayRecords { get; set; } - - public bool ReportLostSensorContact { get; set; } - - public bool ReportDataStoppedErrors { get; set; } - - //public bool RestartIfDataStops { get; set; } - - //public bool RestartIfUnplugged { get; set; } - - //public bool CloseOnSuspend { get; set; } - - //public bool ConfirmClose { get; set; } - - public int DataLogInterval { get; set; } - - public int UVdecimals { get; set; } - - public int UVdecimaldefault { get; set; } - - public string LonTxt { get; set; } - - public string LatTxt { get; set; } - - public bool AltitudeInFeet { get; set; } - - public string StationModel { get; set; } - - public int StationType { get; set; } - - public string LatestImetReading { get; set; } - - public bool FineOffsetStation { get; set; } - - public bool DavisStation { get; set; } - public string TempTrendFormat { get; set; } - public string AppDir { get; set; } - - public int Manufacturer { get; set; } - public int ImetLoggerInterval { get; set; } - public TimeSpan DayLength { get; set; } - public DateTime Dawn; - public DateTime Dusk; - public TimeSpan DaylightLength { get; set; } - public int GraphHours { get; set; } - - // WeatherLink Live transmitter Ids and indexes - public string WllApiKey; - public string WllApiSecret; - public int WllStationId; - public int WllParentId; - - public int WllBroadcastDuration = 300; - public int WllBroadcastPort = 22222; - public bool WLLAutoUpdateIpAddress = true; - public int WllPrimaryWind = 1; - public int WllPrimaryTempHum = 1; - public int WllPrimaryRain = 1; - public int WllPrimarySolar; - public int WllPrimaryUV; - - public int WllExtraSoilTempTx1; - public int WllExtraSoilTempIdx1 = 1; - public int WllExtraSoilTempTx2; - public int WllExtraSoilTempIdx2 = 2; - public int WllExtraSoilTempTx3; - public int WllExtraSoilTempIdx3 = 3; - public int WllExtraSoilTempTx4; - public int WllExtraSoilTempIdx4 = 4; - - public int WllExtraSoilMoistureTx1; - public int WllExtraSoilMoistureIdx1 = 1; - public int WllExtraSoilMoistureTx2; - public int WllExtraSoilMoistureIdx2 = 2; - public int WllExtraSoilMoistureTx3; - public int WllExtraSoilMoistureIdx3 = 3; - public int WllExtraSoilMoistureTx4; - public int WllExtraSoilMoistureIdx4 = 4; - - public int WllExtraLeafTx1; - public int WllExtraLeafIdx1 = 1; - public int WllExtraLeafTx2; - public int WllExtraLeafIdx2 = 2; - - public int[] WllExtraTempTx = { 0, 0, 0, 0, 0, 0, 0, 0 }; - - public bool[] WllExtraHumTx = { false, false, false, false, false, false, false, false }; - - // WeatherLink Live transmitter Ids and indexes - public bool AirLinkIsNode; - public string AirLinkApiKey; - public string AirLinkApiSecret; - public int AirLinkInStationId; - public int AirLinkOutStationId; - public bool AirLinkAutoUpdateIpAddress = true; - - public int airQualityIndex = -1; - - public string Gw1000IpAddress; - public string Gw1000MacAddress; - public bool Gw1000AutoUpdateIpAddress = true; - - public Timer WundTimer = new Timer(); - public Timer WebTimer = new Timer(); - public Timer AwekasTimer = new Timer(); - public Timer MQTTTimer = new Timer(); - //public Timer AirLinkTimer = new Timer(); - - public int DAVIS = 0; - public int OREGON = 1; - public int EW = 2; - public int LACROSSE = 3; - public int OREGONUSB = 4; - public int INSTROMET = 5; - public int ECOWITT = 6; - //public bool startingup = true; - public string ReportPath; - public string LatestError; - public DateTime LatestErrorTS = DateTime.MinValue; - //public DateTime defaultRecordTS = new DateTime(2000, 1, 1, 0, 0, 0); - public DateTime defaultRecordTS = DateTime.MinValue; - public string WxnowFile = "wxnow.txt"; - private readonly string RealtimeFile = "realtime.txt"; - private readonly string TwitterTxtFile; - public bool IncludeMoonImage; - private readonly FtpClient RealtimeFTP = new FtpClient(); - private SftpClient RealtimeSSH; - private volatile bool RealtimeFtpInProgress; - private volatile bool RealtimeCopyInProgress; - private byte RealtimeCycleCounter; - - public FileGenerationFtpOptions[] StdWebFiles; - public FileGenerationFtpOptions[] RealtimeFiles; - public FileGenerationFtpOptions[] GraphDataFiles; - public FileGenerationFtpOptions[] GraphDataEodFiles; - - - public string exceptional = "Exceptional Weather"; -// private WebSocketServer wsServer; - public string[] WMR200ExtraChannelCaptions = new string[11]; - public string[] ExtraTempCaptions = { "", "Sensor 1", "Sensor 2", "Sensor 3", "Sensor 4", "Sensor 5", "Sensor 6", "Sensor 7", "Sensor 8", "Sensor 9", "Sensor 10" }; - public string[] ExtraHumCaptions = { "", "Sensor 1", "Sensor 2", "Sensor 3", "Sensor 4", "Sensor 5", "Sensor 6", "Sensor 7", "Sensor 8", "Sensor 9", "Sensor 10" }; - public string[] ExtraDPCaptions = { "", "Sensor 1", "Sensor 2", "Sensor 3", "Sensor 4", "Sensor 5", "Sensor 6", "Sensor 7", "Sensor 8", "Sensor 9", "Sensor 10" }; - public string[] SoilTempCaptions = { "", "Sensor 1", "Sensor 2", "Sensor 3", "Sensor 4", "Sensor 5", "Sensor 6", "Sensor 7", "Sensor 8", "Sensor 9", "Sensor 10", "Sensor 11", "Sensor 12", "Sensor 13", "Sensor 14", "Sensor 15", "Sensor 16" }; - public string[] SoilMoistureCaptions = { "", "Sensor 1", "Sensor 2", "Sensor 3", "Sensor 4", "Sensor 5", "Sensor 6", "Sensor 7", "Sensor 8", "Sensor 9", "Sensor 10", "Sensor 11", "Sensor 12", "Sensor 13", "Sensor 14", "Sensor 15", "Sensor 16" }; - public string[] AirQualityCaptions = { "", "Sensor 1", "Sensor 2", "Sensor 3", "Sensor 4" }; - public string[] AirQualityAvgCaptions = { "", "Sensor Avg 1", "Sensor Avg 2", "Sensor Avg 3", "Sensor Avg 4" }; - public string[] LeafTempCaptions = { "", "Temp 1", "Temp 2", "Temp 3", "Temp 4" }; - public string[] LeafWetnessCaptions = { "", "Wetness 1", "Wetness 2", "Wetness 3", "Wetness 4", "Wetness 5", "Wetness 6", "Wetness 7", "Wetness 8" }; - public string[] UserTempCaptions = { "", "Sensor 1", "Sensor 2", "Sensor 3", "Sensor 4", "Sensor 5", "Sensor 6", "Sensor 7", "Sensor 8" }; - private string thereWillBeMinSLessDaylightTomorrow = "There will be {0}min {1}s less daylight tomorrow"; - private string thereWillBeMinSMoreDaylightTomorrow = "There will be {0}min {1}s more daylight tomorrow"; - // WH45 CO2 sensor captions - public string CO2_CurrentCaption = "CO₂ Current"; - public string CO2_24HourCaption = "CO₂ 24h avg"; - public string CO2_pm2p5Caption = "PM 2.5"; - public string CO2_pm2p5_24hrCaption = "PM 2.5 24h avg"; - public string CO2_pm10Caption = "PM 10"; - public string CO2_pm10_24hrCaption = "PM 10 24h avg"; - - /* - public string Getversion() - { - return Version; - } - - public void SetComport(string comport) - { - ComportName = comport; - } - - public string GetComport() - { - return ComportName; - } - - public void SetStationType(int type) - { - StationType = type; - } - - public int GetStationType() - { - return StationType; - } - - public void SetVPRainGaugeType(int type) - { - VPrainGaugeType = type; - } - - public int GetVPRainGaugeType() - { - return VPrainGaugeType; - } - - public void SetVPConnectionType(VPConnTypes type) - { - VPconntype = type; - } - - public VPConnTypes GetVPConnectionType() - { - return VPconntype; - } - - public void SetIPaddress(string address) - { - IPaddress = address; - } - - public string GetIPaddress() - { - return IPaddress; - } - - public void SetTCPport(int port) - { - TCPport = port; - } - - public int GetTCPport() - { - return TCPport; - } - */ - - public string GetLogFileName(DateTime thedate) - { - // First determine the date for the logfile. - // If we're using 9am rollover, the date should be 9 hours (10 in summer) - // before 'Now' - DateTime logfiledate; - - if (RolloverHour == 0) - { - logfiledate = thedate; - } - else - { - TimeZone tz = TimeZone.CurrentTimeZone; - - if (Use10amInSummer && tz.IsDaylightSavingTime(thedate)) - { - // Locale is currently on Daylight (summer) time - logfiledate = thedate.AddHours(-10); - } - else - { - // Locale is currently on Standard time or unknown - logfiledate = thedate.AddHours(-9); - } - } - - var datestring = logfiledate.ToString("MMMyy").Replace(".", ""); - - return Datapath + datestring + "log.txt"; - } - - public string GetExtraLogFileName(DateTime thedate) - { - // First determine the date for the logfile. - // If we're using 9am rollover, the date should be 9 hours (10 in summer) - // before 'Now' - DateTime logfiledate; - - if (RolloverHour == 0) - { - logfiledate = thedate; - } - else - { - TimeZone tz = TimeZone.CurrentTimeZone; - - if (Use10amInSummer && tz.IsDaylightSavingTime(thedate)) - { - // Locale is currently on Daylight (summer) time - logfiledate = thedate.AddHours(-10); - } - else - { - // Locale is currently on Standard time or unknown - logfiledate = thedate.AddHours(-9); - } - } - - var datestring = logfiledate.ToString("yyyyMM"); - datestring = datestring.Replace(".", ""); - - return Datapath + "ExtraLog" + datestring + ".txt"; - } - - public string GetAirLinkLogFileName(DateTime thedate) - { - // First determine the date for the logfile. - // If we're using 9am rollover, the date should be 9 hours (10 in summer) - // before 'Now' - DateTime logfiledate; - - if (RolloverHour == 0) - { - logfiledate = thedate; - } - else - { - TimeZone tz = TimeZone.CurrentTimeZone; - - if (Use10amInSummer && tz.IsDaylightSavingTime(thedate)) - { - // Locale is currently on Daylight (summer) time - logfiledate = thedate.AddHours(-10); - } - else - { - // Locale is currently on Standard time or unknown - logfiledate = thedate.AddHours(-9); - } - } - - var datestring = logfiledate.ToString("yyyyMM"); - datestring = datestring.Replace(".", ""); - - return Datapath + "AirLink" + datestring + "log.txt"; - } - - public const int NumLogFileFields = 29; - - public async void DoLogFile(DateTime timestamp, bool live) - { - // Writes an entry to the n-minute logfile. Fields are comma-separated: - // 0 Date in the form dd/mm/yy (the slash may be replaced by a dash in some cases) - // 1 Current time - hh:mm - // 2 Current temperature - // 3 Current humidity - // 4 Current dewpoint - // 5 Current wind speed - // 6 Recent (10-minute) high gust - // 7 Average wind bearing - // 8 Current rainfall rate - // 9 Total rainfall today so far - // 10 Current sea level pressure - // 11 Total rainfall counter as held by the station - // 12 Inside temperature - // 13 Inside humidity - // 14 Current gust (i.e. 'Latest') - // 15 Wind chill - // 16 Heat Index - // 17 UV Index - // 18 Solar Radiation - // 19 Evapotranspiration - // 20 Annual Evapotranspiration - // 21 Apparent temperature - // 22 Current theoretical max solar radiation - // 23 Hours of sunshine so far today - // 24 Current wind bearing - // 25 RG-11 rain total - // 26 Rain since midnight - // 27 Feels like - // 28 Humidex - - // make sure solar max is calculated for those stations without a solar sensor - LogMessage("DoLogFile: Writing log entry for " + timestamp); - LogDebugMessage("DoLogFile: max gust: " + station.RecentMaxGust.ToString(WindFormat)); - station.CurrentSolarMax = AstroLib.SolarMax(timestamp, Longitude, Latitude, station.AltitudeM(Altitude), out station.SolarElevation, RStransfactor, BrasTurbidity, SolarCalc); - var filename = GetLogFileName(timestamp); - - using (FileStream fs = new FileStream(filename, FileMode.Append, FileAccess.Write, FileShare.Read)) - using (StreamWriter file = new StreamWriter(fs)) - { - file.Write(timestamp.ToString("dd/MM/yy") + ListSeparator); - file.Write(timestamp.ToString("HH:mm") + ListSeparator); - file.Write(station.OutdoorTemperature.ToString(TempFormat) + ListSeparator); - file.Write(station.OutdoorHumidity + ListSeparator); - file.Write(station.OutdoorDewpoint.ToString(TempFormat) + ListSeparator); - file.Write(station.WindAverage.ToString(WindAvgFormat) + ListSeparator); - file.Write(station.RecentMaxGust.ToString(WindFormat) + ListSeparator); - file.Write(station.AvgBearing + ListSeparator); - file.Write(station.RainRate.ToString(RainFormat) + ListSeparator); - file.Write(station.RainToday.ToString(RainFormat) + ListSeparator); - file.Write(station.Pressure.ToString(PressFormat) + ListSeparator); - file.Write(station.Raincounter.ToString(RainFormat) + ListSeparator); - file.Write(station.IndoorTemperature.ToString(TempFormat) + ListSeparator); - file.Write(station.IndoorHumidity + ListSeparator); - file.Write(station.WindLatest.ToString(WindFormat) + ListSeparator); - file.Write(station.WindChill.ToString(TempFormat) + ListSeparator); - file.Write(station.HeatIndex.ToString(TempFormat) + ListSeparator); - file.Write(station.UV.ToString(UVFormat) + ListSeparator); - file.Write(station.SolarRad + ListSeparator); - file.Write(station.ET.ToString(ETFormat) + ListSeparator); - file.Write(station.AnnualETTotal.ToString(ETFormat) + ListSeparator); - file.Write(station.ApparentTemperature.ToString(TempFormat) + ListSeparator); - file.Write((Math.Round(station.CurrentSolarMax)) + ListSeparator); - file.Write(station.SunshineHours.ToString(SunFormat) + ListSeparator); - file.Write(station.Bearing + ListSeparator); - file.Write(station.RG11RainToday.ToString(RainFormat) + ListSeparator); - file.Write(station.RainSinceMidnight.ToString(RainFormat) + ListSeparator); - file.Write(station.FeelsLike.ToString(TempFormat) + ListSeparator); - file.WriteLine(station.Humidex.ToString(TempFormat)); - file.Close(); - } - - LastUpdateTime = timestamp; - LogMessage("DoLogFile: Written log entry for " + timestamp); - station.WriteTodayFile(timestamp, true); - - if (MonthlyMySqlEnabled) - { - var InvC = new CultureInfo(""); - - StringBuilder values = new StringBuilder(StartOfMonthlyInsertSQL, 600); - values.Append(" Values('"); - values.Append(timestamp.ToString("yy-MM-dd HH:mm") + "',"); - values.Append(station.OutdoorTemperature.ToString(TempFormat, InvC) + ","); - values.Append(station.OutdoorHumidity + ","); - values.Append(station.OutdoorDewpoint.ToString(TempFormat, InvC) + ","); - values.Append(station.WindAverage.ToString(WindAvgFormat, InvC) + ","); - values.Append(station.RecentMaxGust.ToString(WindFormat, InvC) + ","); - values.Append(station.AvgBearing + ","); - values.Append(station.RainRate.ToString(RainFormat, InvC) + ","); - values.Append(station.RainToday.ToString(RainFormat, InvC) + ","); - values.Append(station.Pressure.ToString(PressFormat, InvC) + ","); - values.Append(station.Raincounter.ToString(RainFormat, InvC) + ","); - values.Append(station.IndoorTemperature.ToString(TempFormat, InvC) + ","); - values.Append(station.IndoorHumidity + ","); - values.Append(station.WindLatest.ToString(WindFormat, InvC) + ","); - values.Append(station.WindChill.ToString(TempFormat, InvC) + ","); - values.Append(station.HeatIndex.ToString(TempFormat, InvC) + ","); - values.Append(station.UV.ToString(UVFormat, InvC) + ","); - values.Append(station.SolarRad + ","); - values.Append(station.ET.ToString(ETFormat, InvC) + ","); - values.Append(station.AnnualETTotal.ToString(ETFormat, InvC) + ","); - values.Append(station.ApparentTemperature.ToString(TempFormat, InvC) + ","); - values.Append((Math.Round(station.CurrentSolarMax)) + ","); - values.Append(station.SunshineHours.ToString(SunFormat, InvC) + ","); - values.Append(station.Bearing + ","); - values.Append(station.RG11RainToday.ToString(RainFormat, InvC) + ","); - values.Append(station.RainSinceMidnight.ToString(RainFormat, InvC) + ",'"); - values.Append(station.CompassPoint(station.AvgBearing) + "','"); - values.Append(station.CompassPoint(station.Bearing) + "',"); - values.Append(station.FeelsLike.ToString(TempFormat, InvC) + ","); - values.Append(station.Humidex.ToString(TempFormat, InvC)); - values.Append(")"); - - string queryString = values.ToString(); - - if (live) - { - // do the update - await MySqlCommandAsync(queryString, MonthlyMySqlConn, "DoLogFile", true, true); - } - else - { - // save the string for later - MySqlList.Add(queryString); - } - } - } - - public const int NumExtraLogFileFields = 92; - - public void DoExtraLogFile(DateTime timestamp) - { - // Writes an entry to the n-minute extralogfile. Fields are comma-separated: - // 0 Date in the form dd/mm/yy (the slash may be replaced by a dash in some cases) - // 1 Current time - hh:mm - // 2-11 Temperature 1-10 - // 12-21 Humidity 1-10 - // 22-31 Dew point 1-10 - // 32-35 Soil temp 1-4 - // 36-39 Soil moisture 1-4 - // 40-41 Leaf temp 1-2 - // 42-43 Leaf wetness 1-2 - // 44-55 Soil temp 5-16 - // 56-67 Soil moisture 5-16 - // 68-71 Air quality 1-4 - // 72-75 Air quality avg 1-4 - // 76-83 User temperature 1-8 - // 84 CO2 - // 85 CO2 avg - // 86 CO2 pm2.5 - // 87 CO2 pm2.5 avg - // 88 CO2 pm10 - // 89 CO2 pm10 avg - // 90 CO2 temp - // 91 CO2 hum - - var filename = GetExtraLogFileName(timestamp); - - using (FileStream fs = new FileStream(filename, FileMode.Append, FileAccess.Write, FileShare.Read)) - using (StreamWriter file = new StreamWriter(fs)) - { - file.Write(timestamp.ToString("dd/MM/yy") + ListSeparator); //0 - file.Write(timestamp.ToString("HH:mm") + ListSeparator); //1 - - for (int i = 1; i < 11; i++) - { - file.Write(station.ExtraTemp[i].ToString(TempFormat) + ListSeparator); //2-11 - } - for (int i = 1; i < 11; i++) - { - file.Write(station.ExtraHum[i].ToString(HumFormat) + ListSeparator); //12-21 - } - for (int i = 1; i < 11; i++) - { - file.Write(station.ExtraDewPoint[i].ToString(TempFormat) + ListSeparator); //22-31 - } - - file.Write(station.SoilTemp1.ToString(TempFormat) + ListSeparator); //32 - file.Write(station.SoilTemp2.ToString(TempFormat) + ListSeparator); //33 - file.Write(station.SoilTemp3.ToString(TempFormat) + ListSeparator); //34 - file.Write(station.SoilTemp4.ToString(TempFormat) + ListSeparator); //35 - - file.Write(station.SoilMoisture1 + ListSeparator); //36 - file.Write(station.SoilMoisture2 + ListSeparator); //37 - file.Write(station.SoilMoisture3 + ListSeparator); //38 - file.Write(station.SoilMoisture4 + ListSeparator); //39 - - file.Write(station.LeafTemp1.ToString(TempFormat) + ListSeparator); //40 - file.Write(station.LeafTemp2.ToString(TempFormat) + ListSeparator); //41 - - file.Write(station.LeafWetness1 + ListSeparator); //42 - file.Write(station.LeafWetness2 + ListSeparator); //43 - - file.Write(station.SoilTemp5.ToString(TempFormat) + ListSeparator); //44 - file.Write(station.SoilTemp6.ToString(TempFormat) + ListSeparator); //45 - file.Write(station.SoilTemp7.ToString(TempFormat) + ListSeparator); //46 - file.Write(station.SoilTemp8.ToString(TempFormat) + ListSeparator); //47 - file.Write(station.SoilTemp9.ToString(TempFormat) + ListSeparator); //48 - file.Write(station.SoilTemp10.ToString(TempFormat) + ListSeparator); //49 - file.Write(station.SoilTemp11.ToString(TempFormat) + ListSeparator); //50 - file.Write(station.SoilTemp12.ToString(TempFormat) + ListSeparator); //51 - file.Write(station.SoilTemp13.ToString(TempFormat) + ListSeparator); //52 - file.Write(station.SoilTemp14.ToString(TempFormat) + ListSeparator); //53 - file.Write(station.SoilTemp15.ToString(TempFormat) + ListSeparator); //54 - file.Write(station.SoilTemp16.ToString(TempFormat) + ListSeparator); //55 - - file.Write(station.SoilMoisture5 + ListSeparator); //56 - file.Write(station.SoilMoisture6 + ListSeparator); //57 - file.Write(station.SoilMoisture7 + ListSeparator); //58 - file.Write(station.SoilMoisture8 + ListSeparator); //59 - file.Write(station.SoilMoisture9 + ListSeparator); //60 - file.Write(station.SoilMoisture10 + ListSeparator); //61 - file.Write(station.SoilMoisture11 + ListSeparator); //62 - file.Write(station.SoilMoisture12 + ListSeparator); //63 - file.Write(station.SoilMoisture13 + ListSeparator); //64 - file.Write(station.SoilMoisture14 + ListSeparator); //65 - file.Write(station.SoilMoisture15 + ListSeparator); //66 - file.Write(station.SoilMoisture16 + ListSeparator); //67 - - file.Write(station.AirQuality1.ToString("F1") + ListSeparator); //68 - file.Write(station.AirQuality2.ToString("F1") + ListSeparator); //69 - file.Write(station.AirQuality3.ToString("F1") + ListSeparator); //70 - file.Write(station.AirQuality4.ToString("F1") + ListSeparator); //71 - file.Write(station.AirQualityAvg1.ToString("F1") + ListSeparator); //72 - file.Write(station.AirQualityAvg2.ToString("F1") + ListSeparator); //73 - file.Write(station.AirQualityAvg3.ToString("F1") + ListSeparator); //74 - file.Write(station.AirQualityAvg4.ToString("F1") + ListSeparator); //75 - - for (int i = 1; i < 8; i++) - { - file.Write(station.UserTemp[i].ToString(TempFormat) + ListSeparator); //76-82 - } - file.Write(station.UserTemp[8].ToString(TempFormat) + ListSeparator); //83 - - file.Write(station.CO2 + ListSeparator); //84 - file.Write(station.CO2_24h + ListSeparator); //85 - file.Write(station.CO2_pm2p5.ToString("F1") + ListSeparator); //86 - file.Write(station.CO2_pm2p5_24h.ToString("F1") + ListSeparator); //87 - file.Write(station.CO2_pm10.ToString("F1") + ListSeparator); //88 - file.Write(station.CO2_pm10_24h.ToString("F1") + ListSeparator); //89 - file.Write(station.CO2_temperature.ToString(TempFormat) + ListSeparator); //90 - file.Write(station.CO2_humidity); //91 - - file.WriteLine(); - file.Close(); - } - } - - public void DoAirLinkLogFile(DateTime timestamp) - { - // Writes an entry to the n-minute airlinklogfile. Fields are comma-separated: - // 0 Date in the form dd/mm/yy (the slash may be replaced by a dash in some cases) - // 1 Current time - hh:mm - // 2 Indoor Temperature - // 3 Indoor Humidity - // 4 Indoor PM 1 - // 5 Indoor PM 2.5 - // 6 Indoor PM 2.5 1-hour - // 7 Indoor PM 2.5 3-hour - // 8 Indoor PM 2.5 24-hour - // 9 Indoor PM 2.5 nowcast - // 10 Indoor PM 10 - // 11 Indoor PM 10 1-hour - // 12 Indoor PM 10 3-hour - // 13 Indoor PM 10 24-hour - // 14 Indoor PM 10 nowcast - // 15 Indoor Percent received 1-hour - // 16 Indoor Percent received 3-hour - // 17 Indoor Percent received nowcast - // 18 Indoor Percent received 24-hour - // 19 Indoor AQI PM2.5 - // 20 Indoor AQI PM2.5 1-hour - // 21 Indoor AQI PM2.5 3-hour - // 22 Indoor AQI PM2.5 24-hour - // 23 Indoor AQI PM2.5 nowcast - // 24 Indoor AQI PM10 - // 25 Indoor AQI PM10 1-hour - // 26 Indoor AQI PM10 3-hour - // 27 Indoor AQI PM10 24-hour - // 28 Indoor AQI PM10 nowcast - // 29 Outdoor Temperature - // 30 Outdoor Humidity - // 31 Outdoor PM 1 - // 32 Outdoor PM 2.5 - // 33 Outdoor PM 2.5 1-hour - // 34 Outdoor PM 2.5 3-hour - // 35 Outdoor PM 2.5 24-hour - // 36 Outdoor PM 2.5 nowcast - // 37 Outdoor PM 10 - // 38 Outdoor PM 10 1-hour - // 39 Outdoor PM 10 3-hour - // 40 Outdoor PM 10 24-hour - // 41 Outdoor PM 10 nowcast - // 42 Outdoor Percent received 1-hour - // 43 Outdoor Percent received 3-hour - // 44 Outdoor Percent received nowcast - // 45 Outdoor Percent received 24-hour - // 46 Outdoor AQI PM2.5 - // 47 Outdoor AQI PM2.5 1-hour - // 48 Outdoor AQI PM2.5 3-hour - // 49 Outdoor AQI PM2.5 24-hour - // 50 Outdoor AQI PM2.5 nowcast - // 51 Outdoor AQI PM10 - // 52 Outdoor AQI PM10 1-hour - // 53 Outdoor AQI PM10 3-hour - // 54 Outdoor AQI PM10 24-hour - // 55 Outdoor AQI PM10 nowcast - - var filename = GetAirLinkLogFileName(timestamp); - - using (FileStream fs = new FileStream(filename, FileMode.Append, FileAccess.Write, FileShare.Read)) - using (StreamWriter file = new StreamWriter(fs)) - { - file.Write(timestamp.ToString("dd/MM/yy") + ListSeparator); - file.Write(timestamp.ToString("HH:mm") + ListSeparator); - - if (AirLinkInEnabled && airLinkDataIn != null) - { - file.Write(airLinkDataIn.temperature.ToString("F1") + ListSeparator); - file.Write(airLinkDataIn.humidity + ListSeparator); - file.Write(airLinkDataIn.pm1.ToString("F1") + ListSeparator); - file.Write(airLinkDataIn.pm2p5.ToString("F1") + ListSeparator); - file.Write(airLinkDataIn.pm2p5_1hr.ToString("F1") + ListSeparator); - file.Write(airLinkDataIn.pm2p5_3hr.ToString("F1") + ListSeparator); - file.Write(airLinkDataIn.pm2p5_24hr.ToString("F1") + ListSeparator); - file.Write(airLinkDataIn.pm2p5_nowcast.ToString("F1") + ListSeparator); - file.Write(airLinkDataIn.pm10.ToString("F1") + ListSeparator); - file.Write(airLinkDataIn.pm10_1hr.ToString("F1") + ListSeparator); - file.Write(airLinkDataIn.pm10_3hr.ToString("F1") + ListSeparator); - file.Write(airLinkDataIn.pm10_24hr.ToString("F1") + ListSeparator); - file.Write(airLinkDataIn.pm10_nowcast.ToString("F1") + ListSeparator); - file.Write(airLinkDataIn.pct_1hr + ListSeparator); - file.Write(airLinkDataIn.pct_3hr + ListSeparator); - file.Write(airLinkDataIn.pct_24hr + ListSeparator); - file.Write(airLinkDataIn.pct_nowcast + ListSeparator); - if (AirQualityDPlaces > 0) - { - file.Write(airLinkDataIn.aqiPm2p5.ToString(AirQualityFormat) + ListSeparator); - file.Write(airLinkDataIn.aqiPm2p5_1hr.ToString(AirQualityFormat) + ListSeparator); - file.Write(airLinkDataIn.aqiPm2p5_3hr.ToString(AirQualityFormat) + ListSeparator); - file.Write(airLinkDataIn.aqiPm2p5_24hr.ToString(AirQualityFormat) + ListSeparator); - file.Write(airLinkDataIn.aqiPm2p5_nowcast.ToString(AirQualityFormat) + ListSeparator); - file.Write(airLinkDataIn.aqiPm10.ToString(AirQualityFormat) + ListSeparator); - file.Write(airLinkDataIn.aqiPm10_1hr.ToString(AirQualityFormat) + ListSeparator); - file.Write(airLinkDataIn.aqiPm10_3hr.ToString(AirQualityFormat) + ListSeparator); - file.Write(airLinkDataIn.aqiPm10_24hr.ToString(AirQualityFormat) + ListSeparator); - file.Write(airLinkDataIn.aqiPm10_nowcast.ToString(AirQualityFormat) + ListSeparator); - } - else // Zero decimals - trucate value rather than round - { - file.Write((int)airLinkDataIn.aqiPm2p5 + ListSeparator); - file.Write((int)airLinkDataIn.aqiPm2p5_1hr + ListSeparator); - file.Write((int)airLinkDataIn.aqiPm2p5_3hr + ListSeparator); - file.Write((int)airLinkDataIn.aqiPm2p5_24hr + ListSeparator); - file.Write((int)airLinkDataIn.aqiPm2p5_nowcast + ListSeparator); - file.Write((int)airLinkDataIn.aqiPm10 + ListSeparator); - file.Write((int)airLinkDataIn.aqiPm10_1hr + ListSeparator); - file.Write((int)airLinkDataIn.aqiPm10_3hr + ListSeparator); - file.Write((int)airLinkDataIn.aqiPm10_24hr + ListSeparator); - file.Write((int)airLinkDataIn.aqiPm10_nowcast + ListSeparator); - } - } - else - { - // write zero values - subtract 2 for firmware version, wifi RSSI - for (var i = 0; i < typeof(AirLinkData).GetProperties().Length - 2; i++) - { - file.Write("0" + ListSeparator); - } - } - - if (AirLinkOutEnabled && airLinkDataOut != null) - { - file.Write(airLinkDataOut.temperature.ToString("F1") + ListSeparator); - file.Write(airLinkDataOut.humidity + ListSeparator); - file.Write(airLinkDataOut.pm1.ToString("F1") + ListSeparator); - file.Write(airLinkDataOut.pm2p5.ToString("F1") + ListSeparator); - file.Write(airLinkDataOut.pm2p5_1hr.ToString("F1") + ListSeparator); - file.Write(airLinkDataOut.pm2p5_3hr.ToString("F1") + ListSeparator); - file.Write(airLinkDataOut.pm2p5_24hr.ToString("F1") + ListSeparator); - file.Write(airLinkDataOut.pm2p5_nowcast.ToString("F1") + ListSeparator); - file.Write(airLinkDataOut.pm10.ToString("F1") + ListSeparator); - file.Write(airLinkDataOut.pm10_1hr.ToString("F1") + ListSeparator); - file.Write(airLinkDataOut.pm10_3hr.ToString("F1") + ListSeparator); - file.Write(airLinkDataOut.pm10_24hr.ToString("F1") + ListSeparator); - file.Write(airLinkDataOut.pm10_nowcast.ToString("F1") + ListSeparator); - file.Write(airLinkDataOut.pct_1hr + ListSeparator); - file.Write(airLinkDataOut.pct_3hr + ListSeparator); - file.Write(airLinkDataOut.pct_24hr + ListSeparator); - file.Write(airLinkDataOut.pct_nowcast + ListSeparator); - file.Write(airLinkDataOut.aqiPm2p5.ToString("F1") + ListSeparator); - file.Write(airLinkDataOut.aqiPm2p5_1hr.ToString("F1") + ListSeparator); - file.Write(airLinkDataOut.aqiPm2p5_3hr.ToString("F1") + ListSeparator); - file.Write(airLinkDataOut.aqiPm2p5_24hr.ToString("F1") + ListSeparator); - file.Write(airLinkDataOut.aqiPm2p5_nowcast.ToString("F1") + ListSeparator); - file.Write(airLinkDataOut.aqiPm10.ToString("F1") + ListSeparator); - file.Write(airLinkDataOut.aqiPm10_1hr.ToString("F1") + ListSeparator); - file.Write(airLinkDataOut.aqiPm10_3hr.ToString("F1") + ListSeparator); - file.Write(airLinkDataOut.aqiPm10_24hr.ToString("F1") + ListSeparator); - file.Write(airLinkDataOut.aqiPm10_nowcast.ToString("F1")); - } - else - { - // write zero values - subtract 2 for firmware version, wifi RSSI - subtract 1 for end field - for (var i = 0; i < typeof(AirLinkData).GetProperties().Length - 3; i++) - { - file.Write("0" + ListSeparator); - } - file.Write("0"); - } - - file.WriteLine(); - file.Close(); - } - } - - public void BackupData(bool daily, DateTime timestamp) - { - string dirpath = daily ? backupPath + "daily" + DirectorySeparator : backupPath; - - if (!Directory.Exists(dirpath)) - { - LogMessage("BackupData: *** Error - backup folder does not exist - " + dirpath); - } - else - { - string[] dirs = Directory.GetDirectories(dirpath); - Array.Sort(dirs); - var dirlist = new List(dirs); - - while (dirlist.Count > 10) - { - if (Path.GetFileName(dirlist[0]) == "daily") - { - LogMessage("BackupData: *** Error - the backup folder has unexpected contents"); - break; - } - else - { - Directory.Delete(dirlist[0], true); - dirlist.RemoveAt(0); - } - } - - string foldername = timestamp.ToString("yyyyMMddHHmmss"); - - foldername = dirpath + foldername + DirectorySeparator; - - LogMessage("BackupData: Creating backup folder " + foldername); - - var alltimebackup = foldername + "alltime.ini"; - var monthlyAlltimebackup = foldername + "monthlyalltime.ini"; - var daybackup = foldername + "dayfile.txt"; - var yesterdaybackup = foldername + "yesterday.ini"; - var todaybackup = foldername + "today.ini"; - var monthbackup = foldername + "month.ini"; - var yearbackup = foldername + "year.ini"; - var diarybackup = foldername + "diary.db"; - var configbackup = foldername + "Cumulus.ini"; - - var LogFile = GetLogFileName(timestamp); - var logbackup = foldername + LogFile.Replace(logFilePath, ""); - - var extraFile = GetExtraLogFileName(timestamp); - var extraBackup = foldername + extraFile.Replace(logFilePath, ""); - - var AirLinkFile = GetAirLinkLogFileName(timestamp); - var AirLinkBackup = foldername + AirLinkFile.Replace(logFilePath, ""); - - if (!Directory.Exists(foldername)) - { - Directory.CreateDirectory(foldername); - CopyBackupFile(AlltimeIniFile, alltimebackup); - CopyBackupFile(MonthlyAlltimeIniFile, monthlyAlltimebackup); - CopyBackupFile(DayFileName, daybackup); - CopyBackupFile(TodayIniFile, todaybackup); - CopyBackupFile(YesterdayFile, yesterdaybackup); - CopyBackupFile(LogFile, logbackup); - CopyBackupFile(MonthIniFile, monthbackup); - CopyBackupFile(YearIniFile, yearbackup); - CopyBackupFile(diaryfile, diarybackup); - CopyBackupFile("Cumulus.ini", configbackup); - CopyBackupFile(extraFile, extraBackup); - CopyBackupFile(AirLinkFile, AirLinkBackup); - // Do not do this extra backup between 00:00 & Rollover hour on the first of the month - // as the month has not yet rolled over - only applies for start-up backups - if (timestamp.Day == 1 && timestamp.Hour >= RolloverHour) - { - // on the first of month, we also need to backup last months files as well - var LogFile2 = GetLogFileName(timestamp.AddDays(-1)); - var logbackup2 = foldername + LogFile2.Replace(logFilePath, ""); - - var extraFile2 = GetExtraLogFileName(timestamp.AddDays(-1)); - var extraBackup2 = foldername + extraFile2.Replace(logFilePath, ""); - - var AirLinkFile2 = GetAirLinkLogFileName(timestamp.AddDays(-1)); - var AirLinkBackup2 = foldername + AirLinkFile2.Replace(logFilePath, ""); - - CopyBackupFile(LogFile2, logbackup2, true); - CopyBackupFile(extraFile2, extraBackup2, true); - CopyBackupFile(AirLinkFile2, AirLinkBackup2, true); - } - - LogMessage("Created backup folder " + foldername); - } - else - { - LogMessage("Backup folder " + foldername + " already exists, skipping backup"); - } - } - } - - private void CopyBackupFile(string src, string dest, bool overwrite=false) - { - try - { - if (File.Exists(src)) - { - File.Copy(src, dest, overwrite); - } - } - catch (Exception e) - { - LogMessage($"BackupData: Error copying {src} - {e}"); - } - } - - /* - /// - /// Get a snapshot of the current data values - /// - /// Structure containing current values - public CurrentData GetCurrentData() - { - CurrentData currentData = new CurrentData(); - - if (station != null) - { - currentData.Avgbearing = station.AvgBearing; - currentData.Bearing = station.Bearing; - currentData.HeatIndex = station.HeatIndex; - currentData.Humidex = station.Humidex; - currentData.AppTemp = station.ApparentTemperature; - currentData.FeelsLike = station.FeelsLike; - currentData.IndoorHumidity = station.IndoorHumidity; - currentData.IndoorTemperature = station.IndoorTemperature; - currentData.OutdoorDewpoint = station.OutdoorDewpoint; - currentData.OutdoorHumidity = station.OutdoorHumidity; - currentData.OutdoorTemperature = station.OutdoorTemperature; - currentData.AvgTempToday = station.TempTotalToday / station.tempsamplestoday; - currentData.Pressure = station.Pressure; - currentData.RainMonth = station.RainMonth; - currentData.RainRate = station.RainRate; - currentData.RainToday = station.RainToday; - currentData.RainYesterday = station.RainYesterday; - currentData.RainYear = station.RainYear; - currentData.RainLastHour = station.RainLastHour; - currentData.Recentmaxgust = station.RecentMaxGust; - currentData.WindAverage = station.WindAverage; - currentData.WindChill = station.WindChill; - currentData.WindLatest = station.WindLatest; - currentData.WindRunToday = station.WindRunToday; - currentData.TempTrend = station.temptrendval; - currentData.PressTrend = station.presstrendval; - } - - return currentData; - } - */ - - /*public HighLowData GetHumidityHighLowData() - { - HighLowData data = new HighLowData(); - - if (station != null) - { - data.TodayHigh = station.HighHumidityToday; - data.TodayHighDT = station.HighHumidityTodayTime; - - data.TodayLow = station.LowHumidityToday; - data.TodayLowDT = station.LowHumidityTodayTime; - - data.YesterdayHigh = station.Yesterdayhighouthumidity; - data.YesterdayHighDT = station.Yesterdayhighouthumiditydt.ToLocalTime(); - - data.YesterdayLow = station.Yesterdaylowouthumidity; - data.YesterdayLowDT = station.Yesterdaylowouthumiditydt.ToLocalTime(); - - data.MonthHigh = station.Monthhighouthumidity; - data.MonthHighDT = station.Monthhighouthumiditydt.ToLocalTime(); - - data.MonthLow = station.Monthlowouthumidity; - data.MonthLowDT = station.Monthlowouthumiditydt.ToLocalTime(); - - data.YearHigh = station.Yearhighouthumidity; - data.YearHighDT = station.Yearhighouthumiditydt.ToLocalTime(); - - data.YearLow = station.Yearlowouthumidity; - data.YearLowDT = station.Yearlowouthumiditydt.ToLocalTime(); - } - - return data; - } - - public HighLowData GetOuttempHighLowData() - { - HighLowData data = new HighLowData(); - - if (station != null) - { - data.TodayHigh = station.Todayhighouttemp; - data.TodayHighDT = station.Todayhighouttempdt.ToLocalTime(); - - data.TodayLow = station.Todaylowouttemp; - data.TodayLowDT = station.Todaylowouttempdt.ToLocalTime(); - - data.YesterdayHigh = station.Yesterdayhighouttemp; - data.YesterdayHighDT = station.Yesterdayhighouttempdt.ToLocalTime(); - - data.YesterdayLow = station.Yesterdaylowouttemp; - data.YesterdayLowDT = station.Yesterdaylowouttempdt.ToLocalTime(); - - data.MonthHigh = station.Monthhighouttemp; - data.MonthHighDT = station.Monthhighouttempdt.ToLocalTime(); - - data.MonthLow = station.Monthlowouttemp; - data.MonthLowDT = station.Monthlowouttempdt.ToLocalTime(); - - data.YearHigh = station.Yearhighouttemp; - data.YearHighDT = station.Yearhighouttempdt.ToLocalTime(); - - data.YearLow = station.Yearlowouttemp; - data.YearLowDT = station.Yearlowouttempdt.ToLocalTime(); - } - - return data; - } - - public HighLowData GetPressureHighLowData() - { - HighLowData data = new HighLowData(); - - if (station != null) - { - data.TodayHigh = station.Todayhighpressure; - data.TodayHighDT = station.Todayhighpressuredt.ToLocalTime(); - - data.TodayLow = station.Todaylowpressure; - data.TodayLowDT = station.Todaylowpressuredt.ToLocalTime(); - - data.YesterdayHigh = station.Yesterdayhighpressure; - data.YesterdayHighDT = station.Yesterdayhighpressuredt.ToLocalTime(); - - data.YesterdayLow = station.Yesterdaylowpressure; - data.YesterdayLowDT = station.Yesterdaylowpressuredt.ToLocalTime(); - - data.MonthHigh = station.Monthhighpressure; - data.MonthHighDT = station.Monthhighpressuredt.ToLocalTime(); - - data.MonthLow = station.Monthlowpressure; - data.MonthLowDT = station.Monthlowpressuredt.ToLocalTime(); - - data.YearHigh = station.Yearhighpressure; - data.YearHighDT = station.Yearhighpressuredt.ToLocalTime(); - - data.YearLow = station.Yearlowpressure; - data.YearLowDT = station.Yearlowpressuredt.ToLocalTime(); - } - - return data; - } - - public HighLowData GetRainRateHighLowData() - { - HighLowData data = new HighLowData(); - - if (station != null) - { - data.TodayHigh = station.Todayhighrainrate; - data.TodayHighDT = station.Todayhighrainratedt.ToLocalTime(); - - data.YesterdayHigh = station.Yesterdayhighrainrate; - data.YesterdayHighDT = station.Yesterdayhighrainratedt.ToLocalTime(); - - data.MonthHigh = station.Monthhighrainrate; - data.MonthHighDT = station.Monthhighrainratedt.ToLocalTime(); - - data.YearHigh = station.Yearhighrainrate; - data.YearHighDT = station.Yearhighrainratedt.ToLocalTime(); - } - - return data; - } - - public HighLowData GetRainHourHighLowData() - { - HighLowData data = new HighLowData(); - - if (station != null) - { - data.TodayHigh = station.Todayhighrainhour; - data.TodayHighDT = station.Todayhighrainhourdt.ToLocalTime(); - - data.MonthHigh = station.Monthhighrainhour; - data.MonthHighDT = station.Monthhighrainhourdt.ToLocalTime(); - - data.YearHigh = station.Yearhighrainhour; - data.YearHighDT = station.Yearhighrainhourdt.ToLocalTime(); - } - - return data; - } - - public HighLowData GetGustHighLowData() - { - HighLowData data = new HighLowData(); - - if (station != null) - { - data.TodayHigh = station.Todayhighgust; - data.TodayHighDT = station.Todayhighgustdt.ToLocalTime(); - - data.YesterdayHigh = station.Yesterdayhighgust; - data.YesterdayHighDT = station.Yesterdayhighgustdt.ToLocalTime(); - - data.MonthHigh = station.ThisMonthRecs.HighGust.Val; - data.MonthHighDT = station.ThisMonthRecs.HighGust.Ts.ToLocalTime(); - - data.YearHigh = station.Yearhighgust; - data.YearHighDT = station.Yearhighgustdt.ToLocalTime(); - } - - return data; - } - - public HighLowData GetSpeedHighLowData() - { - HighLowData data = new HighLowData(); - - if (station != null) - { - data.TodayHigh = station.Todayhighspeed; - data.TodayHighDT = station.Todayhighspeeddt.ToLocalTime(); - - data.YesterdayHigh = station.Yesterdayhighspeed; - data.YesterdayHighDT = station.Yesterdayhighspeeddt.ToLocalTime(); - - data.MonthHigh = station.Monthhighspeed; - data.MonthHighDT = station.Monthhighspeeddt.ToLocalTime(); - - data.YearHigh = station.Yearhighspeed; - data.YearHighDT = station.Yearhighspeeddt.ToLocalTime(); - } - - return data; - }*/ - - /* - public string GetForecast() - { - return station.Forecast; - } - - public string GetCurrentActivity() - { - return CurrentActivity; - } - - public bool GetImportDataSetting() - { - return ImportData; - } - - public void SetImportDataSetting(bool setting) - { - ImportData = setting; - } - - public bool GetLogExtraDataSetting() - { - return LogExtraData; - } - - public void SetLogExtraDataSetting(bool setting) - { - LogExtraData = setting; - } - - public string GetCumulusIniPath() - { - return CumulusIniPath; - } - - public void SetCumulusIniPath(string inipath) - { - CumulusIniPath = inipath; - } - - public int GetLogInterval() - { - return LogInterval; - } - - public void SetLogInterval(int interval) - { - LogInterval = interval; - } - */ - - public int GetHourInc(DateTime timestamp) - { - if (RolloverHour == 0) - { - return 0; - } - else - { - try - { - if (Use10amInSummer && TimeZoneInfo.Local.IsDaylightSavingTime(timestamp)) - { - // Locale is currently on Daylight time - return -10; - } - - else - { - // Locale is currently on Standard time or unknown - return -9; - } - } - catch (Exception) - { - return -9; - } - } - } - - public int GetHourInc() - { - return GetHourInc(DateTime.Now); - } - - /* - private bool IsDaylightSavings() - { - return TimeZoneInfo.Local.IsDaylightSavingTime(DateTime.Now); - } - */ - - public string Beaufort(double Bspeed) // Takes speed in current unit, returns Bft number as text - { - return station.Beaufort(Bspeed).ToString(); - } - - public string BeaufortDesc(double Bspeed) - { - // Takes speed in current units, returns Bft description - - // Convert to Force - var force = station.Beaufort(Bspeed); - switch (force) - { - case 0: - return Calm; - case 1: - return Lightair; - case 2: - return Lightbreeze; - case 3: - return Gentlebreeze; - case 4: - return Moderatebreeze; - case 5: - return Freshbreeze; - case 6: - return Strongbreeze; - case 7: - return Neargale; - case 8: - return Gale; - case 9: - return Stronggale; - case 10: - return Storm; - case 11: - return Violentstorm; - case 12: - return Hurricane; - default: - return "UNKNOWN"; - } - } - - public void LogErrorMessage(string message) - { - LatestError = message; - LatestErrorTS = DateTime.Now; - LogMessage(message); - } - - public void LogSpikeRemoval(string message) - { - if (ErrorLogSpikeRemoval) - { - LogErrorMessage("Spike removal: " + message); - } - } - - public void Stop() - { - LogMessage("Cumulus closing"); - - //WriteIniFile(); - - //httpServer.Stop(); - - //if (httpServer != null) httpServer.Dispose(); - - // Stop the timers - try - { - LogMessage("Stopping timers"); - RealtimeTimer.Stop(); - WundTimer.Stop(); - WebTimer.Stop(); - AwekasTimer.Stop(); - MQTTTimer.Stop(); - //AirLinkTimer.Stop(); - CustomHttpSecondsTimer.Stop(); - CustomMysqlSecondsTimer.Stop(); - MQTTTimer.Stop(); - } - catch { } - - if (station != null) - { - LogMessage("Stopping station..."); - station.Stop(); - LogMessage("Station stopped"); - - if (station.HaveReadData) - { - LogMessage("Writing today.ini file"); - station.WriteTodayFile(DateTime.Now, false); - LogMessage("Completed writing today.ini file"); - } - else - { - LogMessage("No data read this session, today.ini not written"); - } - - LogMessage("Stopping extra sensors..."); - // If we have a Outdoor AirLink sensor, and it is linked to this WLL then stop it now - airLinkOut?.Stop(); - // If we have a Indoor AirLink sensor, and it is linked to this WLL then stop it now - airLinkIn?.Stop(); - LogMessage("Extra sensors stopped"); - - } - LogMessage("Station shutdown complete"); - } - - public void ExecuteProgram(string externalProgram, string externalParams) - { - // Prepare the process to run - ProcessStartInfo start = new ProcessStartInfo() - { - // Enter in the command line arguments - Arguments = externalParams, - // Enter the executable to run, including the complete path - FileName = externalProgram, - // Dont show a console window - CreateNoWindow = true - }; - - // Run the external process - Process.Start(start); - } - - public void DoHTMLFiles() - { - try - { - if (!RealtimeEnabled) - { - CreateRealtimeFile(999); - } - - LogDebugMessage("Creating standard web files"); - for (var i = 0; i < StdWebFiles.Length; i++) - { - if (StdWebFiles[i].Create && !string.IsNullOrWhiteSpace(StdWebFiles[i].TemplateFileName)) - { - var destFile = StdWebFiles[i].LocalPath + StdWebFiles[i].LocalFileName; - ProcessTemplateFile(StdWebFiles[i].TemplateFileName, destFile, tokenParser); - } - } - LogDebugMessage("Done creating standard Data file"); - - LogDebugMessage("Creating graph data files"); - station.CreateGraphDataFiles(); - LogDebugMessage("Done creating graph data files"); - - //LogDebugMessage("Creating extra files"); - // handle any extra files - for (int i = 0; i < numextrafiles; i++) - { - if (!ExtraFiles[i].realtime && !ExtraFiles[i].endofday) - { - var uploadfile = ExtraFiles[i].local; - var remotefile = ExtraFiles[i].remote; - - if ((uploadfile.Length > 0) && (remotefile.Length > 0)) - { - if (uploadfile == "") - { - uploadfile = GetLogFileName(DateTime.Now); - } - else if (uploadfile == "") - { - uploadfile = GetExtraLogFileName(DateTime.Now); - } - else if (uploadfile == "")) - { - remotefile = remotefile.Replace("", Path.GetFileName(GetLogFileName(DateTime.Now))); - } - else if (remotefile.Contains("")) - { - remotefile = remotefile.Replace("", Path.GetFileName(GetExtraLogFileName(DateTime.Now))); - } - else if (remotefile.Contains("", Path.GetFileName(GetAirLinkLogFileName(DateTime.Now))); - } - - if (ExtraFiles[i].process) - { - LogDebugMessage($"Interval: Processing extra file[{i}] - {uploadfile}"); - // process the file - var utf8WithoutBom = new UTF8Encoding(false); - var encoding = UTF8encode ? utf8WithoutBom : Encoding.GetEncoding("iso-8859-1"); - tokenParser.Encoding = encoding; - tokenParser.SourceFile = uploadfile; - var output = tokenParser.ToString(); - uploadfile += "tmp"; - try - { - using (StreamWriter file = new StreamWriter(uploadfile, false, encoding)) - { - file.Write(output); - - file.Close(); - } - } - catch (Exception ex) - { - LogDebugMessage($"Interval: Error writing file[{i}] - {uploadfile}"); - LogDebugMessage(ex.Message); - } - //LogDebugMessage("Finished processing extra file " + uploadfile); - } - - if (!ExtraFiles[i].FTP) - { - // just copy the file - LogDebugMessage($"Interval: Copying extra file[{i}] {uploadfile} to {remotefile}"); - try - { - File.Copy(uploadfile, remotefile, true); - } - catch (Exception ex) - { - LogDebugMessage($"Interval: Error copying extra file[{i}]: " + ex.Message); - } - //LogDebugMessage("Finished copying extra file " + uploadfile); - } - } - else - { - LogMessage($"Interval: Warning, extra web file[{i}] not found - {uploadfile}"); - } - } - } - } - - if (!string.IsNullOrEmpty(ExternalProgram)) - { - LogDebugMessage("Interval: Executing program " + ExternalProgram + " " + ExternalParams); - try - { - ExecuteProgram(ExternalProgram, ExternalParams); - LogDebugMessage("Interval: External program started"); - } - catch (Exception ex) - { - LogMessage("Interval: Error starting external program: " + ex.Message); - } - } - - //LogDebugMessage("Done creating extra files"); - - if (!string.IsNullOrEmpty(FtpHostname)) - { - DoFTPLogin(); - } - } - finally - { - WebUpdating = 0; - } - } - - public void DoFTPLogin() - { - var remotePath = ""; - - if (FtpDirectory.Length > 0) - { - remotePath = (FtpDirectory.EndsWith("/") ? FtpDirectory : FtpDirectory + "/"); - } - - if (Sslftp == FtpProtocols.SFTP) - { - // BUILD 3092 - added alternate SFTP authenication options - ConnectionInfo connectionInfo; - if (SshftpAuthentication == "password") - { - connectionInfo = new ConnectionInfo(FtpHostname, FtpHostPort, FtpUsername, new PasswordAuthenticationMethod(FtpUsername, FtpPassword)); - LogFtpDebugMessage("SFTP[Int]: Connecting using password authentication"); - } - else if (SshftpAuthentication == "psk") - { - PrivateKeyFile pskFile = new PrivateKeyFile(SshftpPskFile); - connectionInfo = new ConnectionInfo(FtpHostname, FtpHostPort, FtpUsername, new PrivateKeyAuthenticationMethod(FtpUsername, pskFile)); - LogFtpDebugMessage("SFTP[Int]: Connecting using PSK authentication"); - } - else if (SshftpAuthentication == "password_psk") - { - PrivateKeyFile pskFile = new PrivateKeyFile(SshftpPskFile); - connectionInfo = new ConnectionInfo(FtpHostname, FtpHostPort, FtpUsername, new PasswordAuthenticationMethod(FtpUsername, FtpPassword), new PrivateKeyAuthenticationMethod(FtpUsername, pskFile)); - LogFtpDebugMessage("SFTP[Int]: Connecting using password or PSK authentication"); - } - else - { - LogFtpMessage($"SFTP[Int]: Invalid SshftpAuthentication specified [{SshftpAuthentication}]"); - return; - } - - using (SftpClient conn = new SftpClient(connectionInfo)) - { - try - { - LogFtpDebugMessage($"SFTP[Int]: CumulusMX Connecting to {FtpHostname} on port {FtpHostPort}"); - conn.Connect(); - } - catch (Exception ex) - { - LogFtpMessage($"SFTP[Int]: Error connecting sftp - {ex.Message}"); - return; - } - - if (conn.IsConnected) - { - if (NOAANeedFTP) - { - try - { - // upload NOAA reports - LogFtpDebugMessage("SFTP[Int]: Uploading NOAA reports"); - - var uploadfile = ReportPath + NOAALatestMonthlyReport; - var remotefile = NOAAFTPDirectory + '/' + NOAALatestMonthlyReport; - - UploadFile(conn, uploadfile, remotefile, -1); - - uploadfile = ReportPath + NOAALatestYearlyReport; - remotefile = NOAAFTPDirectory + '/' + NOAALatestYearlyReport; - - UploadFile(conn, uploadfile, remotefile, -1); - - LogFtpDebugMessage("SFTP[Int]: Done uploading NOAA reports"); - } - catch (Exception e) - { - LogFtpMessage($"SFTP[Int]: Error uploading file - {e.Message}"); - } - NOAANeedFTP = false; - } - - LogFtpDebugMessage("SFTP[Int]: Uploading extra files"); - // Extra files - for (int i = 0; i < numextrafiles; i++) - { - var uploadfile = ExtraFiles[i].local; - var remotefile = ExtraFiles[i].remote; - - if ((uploadfile.Length > 0) && - (remotefile.Length > 0) && - !ExtraFiles[i].realtime && - (!ExtraFiles[i].endofday || EODfilesNeedFTP == ExtraFiles[i].endofday) && // Either, it's not flagged as an EOD file, OR: It is flagged as EOD and EOD FTP is required - ExtraFiles[i].FTP) - { - // For EOD files, we want the previous days log files since it is now just past the day rollover time. Makes a difference on month rollover - var logDay = ExtraFiles[i].endofday ? DateTime.Now.AddDays(-1) : DateTime.Now; - - if (uploadfile == "") - { - uploadfile = GetLogFileName(logDay); - } - else if (uploadfile == "") - { - uploadfile = GetExtraLogFileName(logDay); - } - else if (uploadfile == "")) - { - remotefile = remotefile.Replace("", Path.GetFileName(GetLogFileName(logDay))); - } - else if (remotefile.Contains("")) - { - remotefile = remotefile.Replace("", Path.GetFileName(GetExtraLogFileName(logDay))); - } - else if (remotefile.Contains("", Path.GetFileName(GetAirLinkLogFileName(logDay))); - } - - // all checks OK, file needs to be uploaded - if (ExtraFiles[i].process) - { - // we've already processed the file - uploadfile += "tmp"; - } - - try - { - UploadFile(conn, uploadfile, remotefile, -1); - } - catch (Exception e) - { - LogFtpMessage($"SFTP[Int]: Error uploading Extra web file #{i} [{uploadfile}]"); - LogFtpMessage($"SFTP[Int]: Error = {e.Message}"); - } - } - else - { - LogFtpMessage($"SFTP[Int]: Extra web file #{i} [{uploadfile}] not found!"); - } - } - } - if (EODfilesNeedFTP) - { - EODfilesNeedFTP = false; - } - LogFtpDebugMessage("SFTP[Int]: Done uploading extra files"); - - // standard files - LogFtpDebugMessage("SFTP[Int]: Uploading standard web files"); - for (var i = 0; i < StdWebFiles.Length; i++) - { - if (StdWebFiles[i].FTP && StdWebFiles[i].FtpRequired) - { - try - { - var localFile = StdWebFiles[i].LocalPath + StdWebFiles[i].LocalFileName; - var remotefile = remotePath + StdWebFiles[i].RemoteFileName; - UploadFile(conn, localFile, remotefile, -1); - } - catch (Exception e) - { - LogFtpMessage($"SFTP[Int]: Error uploading standard data file [{StdWebFiles[i].LocalFileName}]"); - LogFtpMessage($"SFTP[Int]: Error = {e}"); - } - } - } - LogFtpDebugMessage("SFTP[Int]: Done uploading standard web files"); - - LogFtpDebugMessage("SFTP[Int]: Uploading graph data files"); - - for (int i = 0; i < GraphDataFiles.Length; i++) - { - if (GraphDataFiles[i].FTP && GraphDataFiles[i].FtpRequired) - { - var uploadfile = GraphDataFiles[i].LocalPath + GraphDataFiles[i].LocalFileName; - var remotefile = remotePath + GraphDataFiles[i].RemoteFileName; - - try - { - UploadFile(conn, uploadfile, remotefile, -1); - // The config files only need uploading once per change - if (GraphDataFiles[i].LocalFileName == "availabledata.json" || - GraphDataFiles[i].LocalFileName == "graphconfig.json") - { - GraphDataFiles[i].FtpRequired = false; - } - } - catch (Exception e) - { - LogFtpMessage($"SFTP[Int]: Error uploading graph data file [{uploadfile}]"); - LogFtpMessage($"SFTP[Int]: Error = {e}"); - } - } - } - LogFtpDebugMessage("SFTP[Int]: Done uploading graph data files"); - - LogFtpMessage("SFTP[Int]: Uploading daily graph data files"); - for (int i = 0; i < GraphDataEodFiles.Length; i++) - { - if (GraphDataEodFiles[i].FTP && GraphDataEodFiles[i].FtpRequired) - { - var uploadfile = GraphDataEodFiles[i].LocalPath + GraphDataEodFiles[i].LocalFileName; - var remotefile = remotePath + GraphDataEodFiles[i].RemoteFileName; - try - { - UploadFile(conn, uploadfile, remotefile, -1); - // Uploaded OK, reset the upload required flag - GraphDataEodFiles[i].FtpRequired = false; - } - catch (Exception e) - { - LogFtpMessage($"SFTP[Int]: Error uploading daily graph data file [{uploadfile}]"); - LogFtpMessage($"SFTP[Int]: Error = {e}"); - } - } - } - LogFtpMessage("SFTP[Int]: Done uploading daily graph data files"); - - if (IncludeMoonImage && MoonImageReady) - { - try - { - LogFtpMessage("SFTP[Int]: Uploading Moon image file"); - UploadFile(conn, "web" + DirectorySeparator + "moon.png", remotePath + MoonImageFtpDest, -1); - LogFtpMessage("SFTP[Int]: Done uploading Moon image file"); - // clear the image ready for FTP flag, only upload once an hour - MoonImageReady = false; - } - catch (Exception e) - { - LogMessage($"SFTP[Int]: Error uploading moon image - {e.Message}"); - } - } - } - try - { - // do not error on disconnect - conn.Disconnect(); - } - catch { } - } - LogFtpDebugMessage("SFTP[Int]: Connection process complete"); - } - else - { - using (FtpClient conn = new FtpClient()) - { - if (FTPlogging) FtpTrace.WriteLine(""); // insert a blank line - LogFtpDebugMessage($"FTP[Int]: CumulusMX Connecting to " + FtpHostname); - conn.Host = FtpHostname; - conn.Port = FtpHostPort; - conn.Credentials = new NetworkCredential(FtpUsername, FtpPassword); - - if (Sslftp == FtpProtocols.FTPS) - { - // Explicit = Current protocol - connects using FTP and switches to TLS - // Implicit = Old depreciated protcol - connects using TLS - conn.EncryptionMode = DisableFtpsExplicit ? FtpEncryptionMode.Implicit : FtpEncryptionMode.Explicit; - conn.DataConnectionEncryption = true; - conn.ValidateAnyCertificate = true; - // b3045 - switch from System.Net.Ftp.Client to FluentFTP allows us to specify protocols - conn.SslProtocols = SslProtocols.Default | SslProtocols.Tls11 | SslProtocols.Tls12; - } - - if (ActiveFTPMode) - { - conn.DataConnectionType = FtpDataConnectionType.PORT; - } - else if (DisableFtpsEPSV) - { - conn.DataConnectionType = FtpDataConnectionType.PASV; - } - - try - { - conn.Connect(); - } - catch (Exception ex) - { - LogFtpMessage("FTP[Int]: Error connecting ftp - " + ex.Message); - return; - } - - conn.EnableThreadSafeDataConnections = false; // use same connection for all transfers - - if (conn.IsConnected) - { - if (NOAANeedFTP) - { - try - { - // upload NOAA reports - LogFtpDebugMessage("FTP[Int]: Uploading NOAA reports"); - - var uploadfile = ReportPath + NOAALatestMonthlyReport; - var remotefile = NOAAFTPDirectory + '/' + NOAALatestMonthlyReport; - - UploadFile(conn, uploadfile, remotefile); - - uploadfile = ReportPath + NOAALatestYearlyReport; - remotefile = NOAAFTPDirectory + '/' + NOAALatestYearlyReport; - - UploadFile(conn, uploadfile, remotefile); - LogFtpDebugMessage("FTP[Int]: Upload of NOAA reports complete"); - } - catch (Exception e) - { - LogFtpMessage($"FTP[Int]: Error uploading NOAA files: {e.Message}"); - } - NOAANeedFTP = false; - } - - // Extra files - LogFtpDebugMessage("FTP[Int]: Uploading Extra files"); - for (int i = 0; i < numextrafiles; i++) - { - var uploadfile = ExtraFiles[i].local; - var remotefile = ExtraFiles[i].remote; - - if ((uploadfile.Length > 0) && - (remotefile.Length > 0) && - !ExtraFiles[i].realtime && - (EODfilesNeedFTP || (EODfilesNeedFTP == ExtraFiles[i].endofday)) && - ExtraFiles[i].FTP) - { - // For EOD files, we want the previous days log files since it is now just past the day rollover time. Makes a difference on month rollover - var logDay = ExtraFiles[i].endofday ? DateTime.Now.AddDays(-1) : DateTime.Now; - - if (uploadfile == "") - { - uploadfile = GetLogFileName(logDay); - } - else if (uploadfile == "") - { - uploadfile = GetExtraLogFileName(logDay); - } - else if (uploadfile == "")) - { - remotefile = remotefile.Replace("", Path.GetFileName(GetLogFileName(logDay))); - } - else if (remotefile.Contains("")) - { - remotefile = remotefile.Replace("", Path.GetFileName(GetExtraLogFileName(logDay))); - } - else if (remotefile.Contains("", Path.GetFileName(GetAirLinkLogFileName(logDay))); - } - - // all checks OK, file needs to be uploaded - if (ExtraFiles[i].process) - { - // we've already processed the file - uploadfile += "tmp"; - } - - try - { - UploadFile(conn, uploadfile, remotefile); - } - catch (Exception e) - { - LogFtpMessage($"FTP[Int]: Error uploading file {uploadfile}: {e.Message}"); - } - } - else - { - LogFtpMessage("FTP[Int]: Extra web file #" + i + " [" + uploadfile + "] not found!"); - } - } - } - if (EODfilesNeedFTP) - { - EODfilesNeedFTP = false; - } - // standard files - LogFtpDebugMessage("FTP[Int]: Uploading standard Data file"); - for (int i = 0; i < StdWebFiles.Length; i++) - { - if (StdWebFiles[i].FTP && StdWebFiles[i].FtpRequired) - { - try - { - var localfile = StdWebFiles[i].LocalPath + StdWebFiles[i].LocalFileName; - UploadFile(conn, localfile, remotePath + StdWebFiles[i].RemoteFileName); - } - catch (Exception e) - { - LogFtpMessage($"FTP[Int]: Error uploading file {StdWebFiles[i].LocalFileName}: {e}"); - } - } - } - LogFtpMessage("Done uploading standard Data file"); - - LogFtpDebugMessage("FTP[Int]: Uploading graph data files"); - for (int i = 0; i < GraphDataFiles.Length; i++) - { - if (GraphDataFiles[i].FTP && GraphDataFiles[i].FtpRequired) - { - try - { - var localfile = GraphDataFiles[i].LocalPath + GraphDataFiles[i].LocalFileName; - var remotefile = remotePath + GraphDataFiles[i].RemoteFileName; - UploadFile(conn, localfile, remotefile); - } - catch (Exception e) - { - LogFtpMessage($"FTP[Int]: Error uploading graph data file [{GraphDataFiles[i].LocalFileName}]"); - LogFtpMessage($"FTP[Int]: Error = {e}"); - } - } - } - LogFtpMessage("Done uploading graph data files"); - - LogFtpMessage("FTP[Int]: Uploading daily graph data files"); - for (int i = 0; i < GraphDataEodFiles.Length; i++) - { - if (GraphDataEodFiles[i].FTP && GraphDataEodFiles[i].FtpRequired) - { - var localfile = GraphDataEodFiles[i].LocalPath + GraphDataEodFiles[i].LocalFileName; - var remotefile = remotePath + GraphDataEodFiles[i].RemoteFileName; - try - { - UploadFile(conn, localfile, remotefile, -1); - // Uploaded OK, reset the upload required flag - GraphDataEodFiles[i].FtpRequired = false; - } - catch (Exception e) - { - LogFtpMessage($"SFTP[Int]: Error uploading daily graph data file [{GraphDataEodFiles[i].LocalFileName}]"); - LogFtpMessage($"SFTP[Int]: Error = {e}"); - } - } - } - LogFtpMessage("FTP[Int]: Done uploading daily graph data files"); - - if (IncludeMoonImage && MoonImageReady) - { - try - { - LogFtpDebugMessage("FTP[Int]: Uploading Moon image file"); - UploadFile(conn, "web" + DirectorySeparator + "moon.png", remotePath + MoonImageFtpDest); - // clear the image ready for FTP flag, only upload once an hour - MoonImageReady = false; - } - catch (Exception e) - { - LogMessage($"FTP[Int]: Error uploading moon image - {e.Message}"); - } - } - } - - // b3045 - dispose of connection - conn.Disconnect(); - LogFtpDebugMessage("FTP[Int]: Disconnected from " + FtpHostname); - } - LogFtpMessage("FTP[Int]: Process complete"); - } - } - - private void UploadFile(FtpClient conn, string localfile, string remotefile, int cycle = -1) - { - string remotefilename = FTPRename ? remotefile + "tmp" : remotefile; - string cycleStr = cycle >= 0 ? cycle.ToString() : "Int"; - - if (FTPlogging) FtpTrace.WriteLine(""); - try - { - if (!File.Exists(localfile)) - { - LogMessage($"FTP[{cycleStr}]: Error! Local file not found, aborting upload: {localfile}"); - return; - } - - if (DeleteBeforeUpload) - { - // delete the existing file - try - { - LogFtpDebugMessage($"FTP[{cycleStr}]: Deleting {remotefile}"); - conn.DeleteFile(remotefile); - } - catch (Exception ex) - { - LogFtpMessage($"FTP[{cycleStr}]: Error deleting {remotefile} : {ex.Message}"); - } - } - - LogFtpDebugMessage($"FTP[{cycleStr}]: Uploading {localfile} to {remotefilename}"); - - using (Stream ostream = conn.OpenWrite(remotefilename)) - using (Stream istream = new FileStream(localfile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - try - { - var buffer = new byte[4096]; - int read; - while ((read = istream.Read(buffer, 0, buffer.Length)) > 0) - { - ostream.Write(buffer, 0, read); - } - - LogFtpDebugMessage($"FTP[{cycleStr}]: Uploaded {localfile}"); - } - catch (Exception ex) - { - LogFtpMessage($"FTP[{cycleStr}]: Error uploading {localfile} to {remotefilename} : {ex.Message}"); - } - finally - { - ostream.Close(); - istream.Close(); - conn.GetReply(); // required FluentFTP 19.2 - } - } - - if (FTPRename) - { - // rename the file - LogFtpDebugMessage($"FTP[{cycleStr}]: Renaming {remotefilename} to {remotefile}"); - - try - { - conn.Rename(remotefilename, remotefile); - LogFtpDebugMessage($"FTP[{cycleStr}]: Renamed {remotefilename}"); - } - catch (Exception ex) - { - LogFtpMessage($"FTP[{cycleStr}]: Error renaming {remotefilename} to {remotefile} : {ex.Message}"); - } - } - } - catch (Exception ex) - { - LogFtpMessage($"FTP[{cycleStr}]: Error uploading {localfile} to {remotefile} : {ex.Message}"); - } - } - - private void UploadFile(SftpClient conn, string localfile, string remotefile, int cycle) - { - string remotefilename = FTPRename ? remotefile + "tmp" : remotefile; - string cycleStr = cycle >= 0 ? cycle.ToString() : "Int"; - - if (!File.Exists(localfile)) - { - LogMessage($"SFTP[{cycleStr}]: Error! Local file not found, aborting upload: {localfile}"); - return; - } - - try - { - if (conn == null || !conn.IsConnected) - { - LogFtpMessage($"SFTP[{cycleStr}]: The SFTP object is null or not connected - skipping upload of {localfile}"); - return; - } - } - catch (ObjectDisposedException) - { - LogFtpMessage($"SFTP[{cycleStr}]: The SFTP object is disposed - skipping upload of {localfile}"); - return; - } - - try - { - // No delete before upload required for SFTP as we use the overwrite flag - - using (Stream istream = new FileStream(localfile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - try - { - LogFtpDebugMessage($"SFTP[{cycleStr}]: Uploading {localfile} to {remotefilename}"); - - conn.OperationTimeout = TimeSpan.FromSeconds(15); - conn.UploadFile(istream, remotefilename, true); - - LogFtpDebugMessage($"SFTP[{cycleStr}]: Uploaded {localfile}"); - } - catch (Exception ex) - { - LogFtpMessage($"SFTP[{cycleStr}]: Error uploading {localfile} to {remotefilename} : {ex.Message}"); - - if (ex.Message.Contains("Permission denied")) // Non-fatal - return; - - // Lets start again anyway! Too hard to tell if the error is recoverable - conn.Dispose(); - return; - } - } - - if (FTPRename) - { - // rename the file - try - { - LogFtpDebugMessage($"SFTP[{cycleStr}]: Renaming {remotefilename} to {remotefile}"); - conn.RenameFile(remotefilename, remotefile, true); - LogFtpDebugMessage($"SFTP[{cycleStr}]: Renamed {remotefilename}"); - } - catch (Exception ex) - { - LogFtpMessage($"SFTP[{cycleStr}]: Error renaming {remotefilename} to {remotefile} : {ex.Message}"); - return; - } - } - LogFtpDebugMessage($"SFTP[{cycleStr}]: Completed uploading {localfile} to {remotefile}"); - } - catch (Exception ex) - { - LogFtpMessage($"SFTP[{cycleStr}]: Error uploading {localfile} to {remotefile} - {ex.Message}"); - } - } - - public void LogMessage(string message) - { - Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff ") + message); - } - - public void LogDebugMessage(string message) - { - if (ProgramOptions.DebugLogging || ProgramOptions.DataLogging) - { - Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff ") + message); - } - } - - public void LogDataMessage(string message) - { - if (ProgramOptions.DataLogging) - { - Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff ") + message); - } - } - - public void LogFtpMessage(string message) - { - LogMessage(message); - if (FTPlogging) - { - FtpTraceListener.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff ") + message); - } - } - - public void LogFtpDebugMessage(string message) - { - if (FTPlogging) - { - LogDebugMessage(message); - FtpTraceListener.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff ") + message); - } - } - - public void LogConsoleMessage(string message) - { - if (!Program.service) - { - Console.WriteLine(message); - } - - Program.svcTextListener.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff ") + message); - Program.svcTextListener.Flush(); - } - - /* - public string ReplaceCommas(string AStr) - { - return AStr.Replace(',', '.'); - } - */ - - private void CreateRealtimeFile(int cycle) - { - /* - Example: 18/10/08 16:03:45 8.4 84 5.8 24.2 33.0 261 0.0 1.0 999.7 W 6 mph C mb mm 146.6 +0.1 85.2 588.4 11.6 20.3 57 3.6 -0.7 10.9 12:00 7.8 14:41 37.4 14:38 44.0 14:28 999.8 16:01 998.4 12:06 1.8.2 448 36.0 10.3 10.5 0 9.3 - - Field Example Description - 1 18/10/08 date (always dd/mm/yy) - 2 16:03:45 time (always hh:mm:ss) - 3 8.4 outside temperature - 4 84 relative humidity - 5 5.8 dewpoint - 6 24.2 wind speed (average) - 7 33.0 latest wind speed - 8 261 wind bearing - 9 0.0 current rain rate - 10 1.0 rain today - 11 999.7 barometer - 12 W wind direction - 13 6 wind speed (beaufort) - 14 mph wind units - 15 C temperature units - 16 mb pressure units - 17 mm rain units - 18 146.6 wind run (today) - 19 +0.1 pressure trend value - 20 85.2 monthly rain - 21 588.4 yearly rain - 22 11.6 yesterday's rainfall - 23 20.3 inside temperature - 24 57 inside humidity - 25 3.6 wind chill - 26 -0.7 temperature trend value - 27 10.9 today's high temp - 28 12:00 time of today's high temp (hh:mm) - 29 7.8 today's low temp - 30 14:41 time of today's low temp (hh:mm) - 31 37.4 today's high wind speed (average) - 32 14:38 time of today's high wind speed (average) (hh:mm) - 33 44.0 today's high wind gust - 34 14:28 time of today's high wind gust (hh:mm) - 35 999.8 today's high pressure - 36 16:01 time of today's high pressure (hh:mm) - 37 998.4 today's low pressure - 38 12:06 time of today's low pressure (hh:mm) - 39 1.8.2 Cumulus version - 40 448 Cumulus build - 41 36.0 10-minute high gust - 42 10.3 heat index - 43 10.5 humidex - 44 UV - 45 ET - 46 Solar radiation - 47 234 Average Bearing (degrees) - 48 2.5 Rain last hour - 49 5 Forecast number - 50 1 Is daylight? (1 = yes) - 51 0 Sensor contact lost (1 = yes) - 52 NNW wind direction (average) - 53 2040 Cloudbase - 54 ft Cloudbase units - 55 12.3 Apparent Temp - 56 11.4 Sunshine hours today - 57 420 Current theoretical max solar radiation - 58 1 Is sunny? - 59 8.4 Feels Like temperature - */ - - // Does the user want to create the realtime.txt file? - if (!RealtimeFiles[0].Create) - { - return; - } - - var filename = AppDir + RealtimeFile; - DateTime timestamp = DateTime.Now; - - try - { - LogDebugMessage($"Realtime[{cycle}]: Creating realtime.txt"); - using (StreamWriter file = new StreamWriter(filename, false)) - { - var InvC = new CultureInfo(""); - - file.Write(timestamp.ToString("dd/MM/yy HH:mm:ss ")); // 1, 2 - file.Write(station.OutdoorTemperature.ToString(TempFormat, InvC) + ' '); // 3 - file.Write(station.OutdoorHumidity.ToString() + ' '); // 4 - file.Write(station.OutdoorDewpoint.ToString(TempFormat, InvC) + ' '); // 5 - file.Write(station.WindAverage.ToString(WindAvgFormat, InvC) + ' '); // 6 - file.Write(station.WindLatest.ToString(WindFormat, InvC) + ' '); // 7 - file.Write(station.Bearing.ToString() + ' '); // 8 - file.Write(station.RainRate.ToString(RainFormat, InvC) + ' '); // 9 - file.Write(station.RainToday.ToString(RainFormat, InvC) + ' '); // 10 - file.Write(station.Pressure.ToString(PressFormat, InvC) + ' '); // 11 - file.Write(station.CompassPoint(station.Bearing) + ' '); // 12 - file.Write(Beaufort(station.WindAverage) + ' '); // 13 - file.Write(Units.WindText + ' '); // 14 - file.Write(Units.TempText[1].ToString() + ' '); // 15 - file.Write(Units.PressText + ' '); // 16 - file.Write(Units.RainText + ' '); // 17 - file.Write(station.WindRunToday.ToString(WindRunFormat, InvC) + ' '); // 18 - if (station.presstrendval > 0) - file.Write('+' + station.presstrendval.ToString(PressFormat, InvC) + ' '); // 19 - else - file.Write(station.presstrendval.ToString(PressFormat, InvC) + ' '); - file.Write(station.RainMonth.ToString(RainFormat, InvC) + ' '); // 20 - file.Write(station.RainYear.ToString(RainFormat, InvC) + ' '); // 21 - file.Write(station.RainYesterday.ToString(RainFormat, InvC) + ' '); // 22 - file.Write(station.IndoorTemperature.ToString(TempFormat, InvC) + ' '); // 23 - file.Write(station.IndoorHumidity.ToString() + ' '); // 24 - file.Write(station.WindChill.ToString(TempFormat, InvC) + ' '); // 25 - file.Write(station.temptrendval.ToString(TempTrendFormat, InvC) + ' '); // 26 - file.Write(station.HiLoToday.HighTemp.ToString(TempFormat, InvC) + ' '); // 27 - file.Write(station.HiLoToday.HighTempTime.ToString("HH:mm ") ); // 28 - file.Write(station.HiLoToday.LowTemp.ToString(TempFormat, InvC) + ' '); // 29 - file.Write(station.HiLoToday.LowTempTime.ToString("HH:mm ")); // 30 - file.Write(station.HiLoToday.HighWind.ToString(WindAvgFormat, InvC) + ' '); // 31 - file.Write(station.HiLoToday.HighWindTime.ToString("HH:mm ")); // 32 - file.Write(station.HiLoToday.HighGust.ToString(WindFormat, InvC) + ' '); // 33 - file.Write(station.HiLoToday.HighGustTime.ToString("HH:mm ")); // 34 - file.Write(station.HiLoToday.HighPress.ToString(PressFormat, InvC) + ' '); // 35 - file.Write(station.HiLoToday.HighPressTime.ToString("HH:mm ")); // 36 - file.Write(station.HiLoToday.LowPress.ToString(PressFormat, InvC) + ' '); // 37 - file.Write(station.HiLoToday.LowPressTime.ToString("HH:mm ")); // 38 - file.Write(Version + ' '); // 39 - file.Write(Build + ' '); // 40 - file.Write(station.RecentMaxGust.ToString(WindFormat, InvC) + ' '); // 41 - file.Write(station.HeatIndex.ToString(TempFormat, InvC) + ' '); // 42 - file.Write(station.Humidex.ToString(TempFormat, InvC) + ' '); // 43 - file.Write(station.UV.ToString(UVFormat, InvC) + ' '); // 44 - file.Write(station.ET.ToString(ETFormat, InvC) + ' '); // 45 - file.Write((Convert.ToInt32(station.SolarRad)).ToString() + ' '); // 46 - file.Write(station.AvgBearing.ToString() + ' '); // 47 - file.Write(station.RainLastHour.ToString(RainFormat, InvC) + ' '); // 48 - file.Write(station.Forecastnumber.ToString() + ' '); // 49 - file.Write(IsDaylight() ? "1 " : "0 "); // 50 - file.Write(station.SensorContactLost ? "1 " : "0 "); // 51 - file.Write(station.CompassPoint(station.AvgBearing) + ' '); // 52 - file.Write((Convert.ToInt32(station.CloudBase)).ToString() + ' '); // 53 - file.Write(CloudBaseInFeet ? "ft " : "m "); // 54 - file.Write(station.ApparentTemperature.ToString(TempFormat, InvC) + ' '); // 55 - file.Write(station.SunshineHours.ToString(SunFormat, InvC) + ' '); // 56 - file.Write(Convert.ToInt32(station.CurrentSolarMax).ToString() + ' '); // 57 - file.Write(station.IsSunny ? "1 " : "0 "); // 58 - file.WriteLine(station.FeelsLike.ToString(TempFormat, InvC)); // 59 - - file.Close(); - } - } - catch (Exception ex) - { - LogMessage("Error encountered during Realtime file update."); - LogMessage(ex.Message); - } - - - if (RealtimeMySqlEnabled) - { - var InvC = new CultureInfo(""); - - StringBuilder values = new StringBuilder(StartOfRealtimeInsertSQL, 1024); - values.Append(" Values('"); - values.Append(timestamp.ToString("yy-MM-dd HH:mm:ss") + "',"); - values.Append(station.OutdoorTemperature.ToString(TempFormat, InvC) + ','); - values.Append(station.OutdoorHumidity.ToString() + ','); - values.Append(station.OutdoorDewpoint.ToString(TempFormat, InvC) + ','); - values.Append(station.WindAverage.ToString(WindAvgFormat, InvC) + ','); - values.Append(station.WindLatest.ToString(WindFormat, InvC) + ','); - values.Append(station.Bearing.ToString() + ','); - values.Append(station.RainRate.ToString(RainFormat, InvC) + ','); - values.Append(station.RainToday.ToString(RainFormat, InvC) + ','); - values.Append(station.Pressure.ToString(PressFormat, InvC) + ",'"); - values.Append(station.CompassPoint(station.Bearing) + "','"); - values.Append(Beaufort(station.WindAverage) + "','"); - values.Append(Units.WindText + "','"); - values.Append(Units.TempText[1].ToString() + "','"); - values.Append(Units.PressText + "','"); - values.Append(Units.RainText + "',"); - values.Append(station.WindRunToday.ToString(WindRunFormat, InvC) + ",'"); - values.Append((station.presstrendval > 0 ? '+' + station.presstrendval.ToString(PressFormat, InvC) : station.presstrendval.ToString(PressFormat, InvC)) + "',"); - values.Append(station.RainMonth.ToString(RainFormat, InvC) + ','); - values.Append(station.RainYear.ToString(RainFormat, InvC) + ','); - values.Append(station.RainYesterday.ToString(RainFormat, InvC) + ','); - values.Append(station.IndoorTemperature.ToString(TempFormat, InvC) + ','); - values.Append(station.IndoorHumidity.ToString() + ','); - values.Append(station.WindChill.ToString(TempFormat, InvC) + ','); - values.Append(station.temptrendval.ToString(TempTrendFormat, InvC) + ','); - values.Append(station.HiLoToday.HighTemp.ToString(TempFormat, InvC) + ",'"); - values.Append(station.HiLoToday.HighTempTime.ToString("HH:mm") + "',"); - values.Append(station.HiLoToday.LowTemp.ToString(TempFormat, InvC) + ",'"); - values.Append(station.HiLoToday.LowTempTime.ToString("HH:mm") + "',"); - values.Append(station.HiLoToday.HighWind.ToString(WindAvgFormat, InvC) + ",'"); - values.Append(station.HiLoToday.HighWindTime.ToString("HH:mm") + "',"); - values.Append(station.HiLoToday.HighGust.ToString(WindFormat, InvC) + ",'"); - values.Append(station.HiLoToday.HighGustTime.ToString("HH:mm") + "',"); - values.Append(station.HiLoToday.HighPress.ToString(PressFormat, InvC) + ",'"); - values.Append(station.HiLoToday.HighPressTime.ToString("HH:mm") + "',"); - values.Append(station.HiLoToday.LowPress.ToString(PressFormat, InvC) + ",'"); - values.Append(station.HiLoToday.LowPressTime.ToString("HH:mm") + "','"); - values.Append(Version + "','"); - values.Append(Build + "',"); - values.Append(station.RecentMaxGust.ToString(WindFormat, InvC) + ','); - values.Append(station.HeatIndex.ToString(TempFormat, InvC) + ','); - values.Append(station.Humidex.ToString(TempFormat, InvC) + ','); - values.Append(station.UV.ToString(UVFormat, InvC) + ','); - values.Append(station.ET.ToString(ETFormat, InvC) + ','); - values.Append(((int)station.SolarRad).ToString() + ','); - values.Append(station.AvgBearing.ToString() + ','); - values.Append(station.RainLastHour.ToString(RainFormat, InvC) + ','); - values.Append(station.Forecastnumber.ToString() + ",'"); - values.Append((IsDaylight() ? "1" : "0") + "','"); - values.Append((station.SensorContactLost ? "1" : "0") + "','"); - values.Append(station.CompassPoint(station.AvgBearing) + "',"); - values.Append((station.CloudBase).ToString() + ",'"); - values.Append((CloudBaseInFeet ? "ft" : "m") + "',"); - values.Append(station.ApparentTemperature.ToString(TempFormat, InvC) + ','); - values.Append(station.SunshineHours.ToString(SunFormat, InvC) + ','); - values.Append(((int)Math.Round(station.CurrentSolarMax)).ToString() + ",'"); - values.Append((station.IsSunny ? "1" : "0") + "',"); - values.Append(station.FeelsLike.ToString(TempFormat, InvC)); - values.Append(")"); - - string valuesString = values.ToString(); - List cmds = new List() { valuesString }; - - if (!string.IsNullOrEmpty(MySqlRealtimeRetention)) - { - cmds.Add($"DELETE IGNORE FROM {MySqlRealtimeTable} WHERE LogDateTime < DATE_SUB('{DateTime.Now:yyyy-MM-dd HH:mm:ss}', INTERVAL {MySqlRealtimeRetention});"); - } - - // do the update - MySqlCommandSync(cmds, RealtimeSqlConn, $"Realtime[{cycle}]", true, true); - } - } - - private void ProcessTemplateFile(string template, string outputfile, TokenParser parser) - { - string templatefile = AppDir + template; - if (File.Exists(templatefile)) - { - var utf8WithoutBom = new UTF8Encoding(false); - var encoding = UTF8encode ? utf8WithoutBom : Encoding.GetEncoding("iso-8859-1"); - parser.Encoding = encoding; - parser.SourceFile = template; - var output = parser.ToString(); - - try - { - using (StreamWriter file = new StreamWriter(outputfile, false, encoding)) - { - file.Write(output); - file.Close(); - } - } - catch (Exception e) - { - LogMessage($"ProcessTemplateFile: Error writing to file '{outputfile}', error was - {e}"); - } - } - } - - public void StartTimersAndSensors() - { - LogMessage("Start Extra Sensors"); - airLinkOut?.Start(); - airLinkIn?.Start(); - - LogMessage("Start Timers"); - // start the general one-minute timer - LogMessage("Starting 1-minute timer"); - station.StartMinuteTimer(); - LogMessage($"Data logging interval = {DataLogInterval} ({logints[DataLogInterval]} mins)"); - - - if (RealtimeEnabled) - { - if (RealtimeFTPEnabled) - { - LogConsoleMessage("Connecting real time FTP"); - if (Sslftp == FtpProtocols.SFTP) - { - RealtimeSSHLogin(RealtimeCycleCounter++); - } - else - { - RealtimeFTPLogin(RealtimeCycleCounter++); - } - } - - LogMessage("Starting Realtime timer, interval = " + RealtimeInterval / 1000 + " seconds"); - } - else - { - LogMessage("Realtime not enabled"); - } - - RealtimeTimer.Enabled = RealtimeEnabled; - - CustomMysqlSecondsTimer.Enabled = CustomMySqlSecondsEnabled; - - CustomHttpSecondsTimer.Enabled = CustomHttpSecondsEnabled; - - if (Wund.RapidFireEnabled) - { - WundTimer.Interval = 5000; // 5 seconds in rapid-fire mode - } - else - { - WundTimer.Interval = Wund.Interval * 60 * 1000; // mins to millisecs - } - - - AwekasTimer.Interval = AWEKAS.Interval * 1000; - - MQTTTimer.Interval = MQTT.IntervalTime * 1000; // secs to millisecs - - - // 15/10/20 What is doing? Nothing - /* - if (AirLinkInEnabled || AirLinkOutEnabled) - { - AirLinkTimer.Interval = 60 * 1000; // 1 minute - AirLinkTimer.Enabled = true; - AirLinkTimer.Elapsed += AirLinkTimerTick; - } - */ - - if (MQTT.EnableInterval) - { - MQTTTimer.Enabled = true; - } - - if (WundList == null) - { - // we've already been through here - // do nothing - LogDebugMessage("Wundlist is null"); - } - else if (WundList.Count == 0) - { - // No archived entries to upload - WundList = null; - LogDebugMessage("Wundlist count is zero"); - WundTimer.Enabled = Wund.Enabled && !Wund.SynchronisedUpdate; - } - else - { - // start the archive upload thread - Wund.CatchingUp = true; - WundCatchup(); - } - - if (WindyList == null) - { - // we've already been through here - // do nothing - LogDebugMessage("Windylist is null"); - } - else if (WindyList.Count == 0) - { - // No archived entries to upload - WindyList = null; - LogDebugMessage("Windylist count is zero"); - } - else - { - // start the archive upload thread - Windy.CatchingUp = true; - WindyCatchUp(); - } - - if (PWSList == null) - { - // we've already been through here - // do nothing - } - else if (PWSList.Count == 0) - { - // No archived entries to upload - PWSList = null; - } - else - { - // start the archive upload thread - PWS.CatchingUp = true; - PWSCatchUp(); - } - - if (WOWList == null) - { - // we've already been through here - // do nothing - } - else if (WOWList.Count == 0) - { - // No archived entries to upload - WOWList = null; - } - else - { - // start the archive upload thread - WOW.CatchingUp = true; - WOWCatchUp(); - } - - if (OWMList == null) - { - // we've already been through here - // do nothing - } - else if (OWMList.Count == 0) - { - // No archived entries to upload - OWMList = null; - } - else - { - // start the archive upload thread - OpenWeatherMap.CatchingUp = true; - OpenWeatherMapCatchUp(); - } - - if (MySqlList == null) - { - // we've already been through here - // do nothing - LogDebugMessage("MySqlList is null"); - } - else if (MySqlList.Count == 0) - { - // No archived entries to upload - MySqlList = null; - LogDebugMessage("MySqlList count is zero"); - } - else - { - // start the archive upload thread - LogMessage("Starting MySQL catchup thread"); - MySqlCatchupThread = new Thread(MySqlCatchup) {IsBackground = true}; - MySqlCatchupThread.Start(); - } - - WebTimer.Interval = UpdateInterval * 60 * 1000; // mins to millisecs - WebTimer.Enabled = WebIntervalEnabled && !SynchronisedWebUpdate; - - AwekasTimer.Enabled = AWEKAS.Enabled && !AWEKAS.SynchronisedUpdate; - - EnableOpenWeatherMap(); - - LogMessage("Normal running"); - LogConsoleMessage("Normal running"); - } - - private void CustomMysqlSecondsTimerTick(object sender, ElapsedEventArgs e) - { - if (station.DataStopped) - { - // No data coming in, do not do anything - return; - } - - _ = CustomMysqlSecondsWork(); - } - - private async Task CustomMysqlSecondsWork() - { - if (station.DataStopped) - { - // No data coming in, do not do anything - return; - } - - if (!customMySqlSecondsUpdateInProgress) - { - customMySqlSecondsUpdateInProgress = true; - - customMysqlSecondsTokenParser.InputText = CustomMySqlSecondsCommandString; - CustomMysqlSecondsCommand.CommandText = customMysqlSecondsTokenParser.ToStringFromString(); - - await MySqlCommandAsync(CustomMysqlSecondsCommand.CommandText, CustomMysqlSecondsConn, "CustomSqlSecs", true, true); - - customMySqlSecondsUpdateInProgress = false; - } - } - - - internal async Task CustomMysqlMinutesTimerTick() - { - if (station.DataStopped) - { - // No data coming in, do not do anything - return; - } - - if (!customMySqlMinutesUpdateInProgress) - { - customMySqlMinutesUpdateInProgress = true; - - customMysqlMinutesTokenParser.InputText = CustomMySqlMinutesCommandString; - CustomMysqlMinutesCommand.CommandText = customMysqlMinutesTokenParser.ToStringFromString(); - - await MySqlCommandAsync(CustomMysqlMinutesCommand.CommandText, CustomMysqlMinutesConn, "CustomSqlMins", true, true); - - customMySqlMinutesUpdateInProgress = false; - } - } - - internal async Task CustomMysqlRolloverTimerTick() - { - if (station.DataStopped) - { - // No data coming in, do not do anything - return; - } - - if (!customMySqlRolloverUpdateInProgress) - { - customMySqlRolloverUpdateInProgress = true; - - customMysqlRolloverTokenParser.InputText = CustomMySqlRolloverCommandString; - CustomMysqlRolloverCommand.CommandText = customMysqlRolloverTokenParser.ToStringFromString(); - - await MySqlCommandAsync(CustomMysqlRolloverCommand.CommandText, CustomMysqlRolloverConn, "CustomSqlRollover", true, true); - - customMySqlRolloverUpdateInProgress = false; - } - } - - public void DoExtraEndOfDayFiles() - { - int i; - - // handle any extra files that only require EOD processing - for (i = 0; i < numextrafiles; i++) - { - if (ExtraFiles[i].endofday) - { - var uploadfile = ExtraFiles[i].local; - var remotefile = ExtraFiles[i].remote; - - if ((uploadfile.Length > 0) && (remotefile.Length > 0)) - { - // For EOD files, we want the previous days log files since it is now just past the day rollover time. Makes a difference on month rollover - var logDay = DateTime.Now.AddDays(-1); - - if (uploadfile == "") - { - uploadfile = GetLogFileName(logDay); - } - else if (uploadfile == "") - { - uploadfile = GetExtraLogFileName(logDay); - } - else if (uploadfile == "")) - { - remotefile = remotefile.Replace("", Path.GetFileName(GetLogFileName(logDay))); - } - else if (remotefile.Contains("")) - { - remotefile = remotefile.Replace("", Path.GetFileName(GetExtraLogFileName(logDay))); - } - else if (remotefile.Contains("", Path.GetFileName(GetAirLinkLogFileName(logDay))); - } - - if (ExtraFiles[i].process) - { - LogDebugMessage("EOD: Processing extra file " + uploadfile); - // process the file - var utf8WithoutBom = new UTF8Encoding(false); - var encoding = UTF8encode ? utf8WithoutBom : Encoding.GetEncoding("iso-8859-1"); - tokenParser.Encoding = encoding; - tokenParser.SourceFile = uploadfile; - var output = tokenParser.ToString(); - uploadfile += "tmp"; - try - { - using (StreamWriter file = new StreamWriter(uploadfile, false, encoding)) - { - file.Write(output); - - file.Close(); - } - } - catch (Exception ex) - { - LogDebugMessage("EOD: Error writing file " + uploadfile); - LogDebugMessage(ex.Message); - } - //LogDebugMessage("Finished processing extra file " + uploadfile); - } - - if (ExtraFiles[i].FTP) - { - // FTP the file at the next interval - EODfilesNeedFTP = true; - } - else - { - // just copy the file - LogDebugMessage($"EOD: Copying extra file {uploadfile} to {remotefile}"); - try - { - File.Copy(uploadfile, remotefile, true); - } - catch (Exception ex) - { - LogDebugMessage("EOD: Error copying extra file: " + ex.Message); - } - //LogDebugMessage("Finished copying extra file " + uploadfile); - } - } - } - } - } - } - - private void MySqlCatchup() - { - try - { - using (var mySqlConn = new MySqlConnection(MySqlConnSettings.ToString())) - { - MySqlCommandSync(MySqlList, mySqlConn, "MySQL Archive", true, true); - } - } - catch (Exception ex) - { - LogMessage("MySQL Archive: Error encountered during catchup MySQL operation."); - LogMessage(ex.Message); - } - - LogMessage("MySQL Archive: End of MySQL archive upload"); - MySqlList.Clear(); - } - - public void RealtimeFTPDisconnect() - { - try - { - if (Sslftp == FtpProtocols.SFTP && RealtimeSSH != null) - { - RealtimeSSH.Disconnect(); - } - else if (RealtimeFTP != null) - { - RealtimeFTP.Disconnect(); - } - LogDebugMessage("Disconnected Realtime FTP session"); - } - catch { } - } - - private void RealtimeFTPLogin(uint cycle) - { - //RealtimeTimer.Enabled = false; - RealtimeFTP.Host = FtpHostname; - RealtimeFTP.Port = FtpHostPort; - RealtimeFTP.Credentials = new NetworkCredential(FtpUsername, FtpPassword); - - if (Sslftp == FtpProtocols.FTPS) - { - RealtimeFTP.EncryptionMode = DisableFtpsExplicit ? FtpEncryptionMode.Implicit : FtpEncryptionMode.Explicit; - RealtimeFTP.DataConnectionEncryption = true; - RealtimeFTP.ValidateAnyCertificate = true; - // b3045 - switch from System.Net.Ftp.Client to FluentFTP allows us to specifiy protocols - RealtimeFTP.SslProtocols = SslProtocols.Default | SslProtocols.Tls11 | SslProtocols.Tls12; - LogDebugMessage($"FTP[{cycle}]: Using FTPS protocol"); - } - - if (ActiveFTPMode) - { - RealtimeFTP.DataConnectionType = FtpDataConnectionType.PORT; - LogDebugMessage($"FTP[{cycle}]: Using Active FTP mode"); - } - else if (DisableFtpsEPSV) - { - RealtimeFTP.DataConnectionType = FtpDataConnectionType.PASV; - LogDebugMessage($"FTP[{cycle}]: Disabling EPSV mode"); - } - - if (FtpHostname.Length > 0 && FtpHostname.Length > 0) - { - LogMessage($"FTP[{ cycle}]: Attempting realtime FTP connect to host {FtpHostname} on port {FtpHostPort}"); - try - { - RealtimeFTP.Connect(); - LogMessage($"FTP[{cycle}]: Realtime FTP connected"); - RealtimeFTP.SocketKeepAlive = true; - } - catch (Exception ex) - { - LogMessage($"FTP[{cycle}]: Error connecting ftp - " + ex.Message); - RealtimeFTP.Disconnect(); - } - - RealtimeFTP.EnableThreadSafeDataConnections = false; // use same connection for all transfers - } - //RealtimeTimer.Enabled = true; - } - - private void RealtimeSSHLogin(uint cycle) - { - if (FtpHostname != "" && FtpHostname != " ") - { - LogMessage($"SFTP[{cycle}]: Attempting realtime SFTP connect to host {FtpHostname} on port {FtpHostPort}"); - try - { - // BUILD 3092 - added alternate SFTP authentication options - ConnectionInfo connectionInfo; - PrivateKeyFile pskFile; - if (SshftpAuthentication == "password") - { - connectionInfo = new ConnectionInfo(FtpHostname, FtpHostPort, FtpUsername, new PasswordAuthenticationMethod(FtpUsername, FtpPassword)); - LogDebugMessage($"SFTP[{cycle}]: Connecting using password authentication"); - } - else if (SshftpAuthentication == "psk") - { - pskFile = new PrivateKeyFile(SshftpPskFile); - connectionInfo = new ConnectionInfo(FtpHostname, FtpHostPort, FtpUsername, new PrivateKeyAuthenticationMethod(FtpUsername, pskFile)); - LogDebugMessage($"SFTP[{cycle}]: Connecting using PSK authentication"); - } - else if (SshftpAuthentication == "password_psk") - { - pskFile = new PrivateKeyFile(SshftpPskFile); - connectionInfo = new ConnectionInfo(FtpHostname, FtpHostPort, FtpUsername, new PasswordAuthenticationMethod(FtpUsername, FtpPassword), new PrivateKeyAuthenticationMethod(FtpUsername, pskFile)); - LogDebugMessage($"SFTP[{cycle}]: Connecting using password or PSK authentication"); - } - else - { - LogMessage($"SFTP[{cycle}]: Invalid SshftpAuthentication specified [{SshftpAuthentication}]"); - return; - } - - RealtimeSSH = new SftpClient(connectionInfo); - - //if (RealtimeSSH != null) RealtimeSSH.Dispose(); - //RealtimeSSH = new SftpClient(ftp_host, ftp_port, ftp_user, ftp_password); - - RealtimeSSH.Connect(); - RealtimeSSH.ConnectionInfo.Timeout = TimeSpan.FromSeconds(15); // 15 seconds to match FTP default timeout - LogMessage($"SFTP[{cycle}]: Realtime SFTP connected"); - } - catch (Exception ex) - { - LogMessage($"SFTP[{cycle}]: Error connecting sftp - {ex.Message}"); - } - } - } - - /// - /// Process the list of WU updates created at startup from logger entries - /// - private async void WundCatchup() - { - Wund.Updating = true; - for (int i = 0; i < WundList.Count; i++) - { - LogMessage("Uploading WU archive #" + (i + 1)); - try - { - HttpResponseMessage response = await WUhttpClient.GetAsync(WundList[i]); - LogMessage("WU Response: " + response.StatusCode + ": " + response.ReasonPhrase); - } - catch (Exception ex) - { - LogMessage("WU update: " + ex.Message); - } - } - - LogMessage("End of WU archive upload"); - WundList.Clear(); - Wund.CatchingUp = false; - WundTimer.Enabled = Wund.Enabled && !Wund.SynchronisedUpdate; - Wund.Updating = false; - } - - private async void WindyCatchUp() - { - Windy.Updating = true; - for (int i = 0; i < WindyList.Count; i++) - { - LogMessage("Uploading Windy archive #" + (i + 1)); - try - { - HttpResponseMessage response = await WindyhttpClient.GetAsync(WindyList[i]); - LogMessage("Windy Response: " + response.StatusCode + ": " + response.ReasonPhrase); - } - catch (Exception ex) - { - LogMessage("Windy update: " + ex.Message); - } - } - - LogMessage("End of Windy archive upload"); - WindyList.Clear(); - Windy.CatchingUp = false; - Windy.Updating = false; - } - - /// - /// Process the list of PWS Weather updates created at startup from logger entries - /// - private async void PWSCatchUp() - { - PWS.Updating = true; - - for (int i = 0; i < PWSList.Count; i++) - { - LogMessage("Uploading PWS archive #" + (i + 1)); - try - { - HttpResponseMessage response = await PWShttpClient.GetAsync(PWSList[i]); - var responseBodyAsText = await response.Content.ReadAsStringAsync(); - LogMessage("PWS Response: " + response.StatusCode + ": " + responseBodyAsText); - } - catch (Exception ex) - { - LogMessage("PWS update: " + ex.Message); - } - } - - LogMessage("End of PWS archive upload"); - PWSList.Clear(); - PWS.CatchingUp = false; - PWS.Updating = false; - } - - /// - /// Process the list of WOW updates created at startup from logger entries - /// - private async void WOWCatchUp() - { - WOW.Updating = true; - - for (int i = 0; i < WOWList.Count; i++) - { - LogMessage("Uploading WOW archive #" + (i + 1)); - try - { - HttpResponseMessage response = await PWShttpClient.GetAsync(WOWList[i]); - var responseBodyAsText = await response.Content.ReadAsStringAsync(); - LogMessage("WOW Response: " + response.StatusCode + ": " + responseBodyAsText); - } - catch (Exception ex) - { - LogMessage("WOW update: " + ex.Message); - } - } - - LogMessage("End of WOW archive upload"); - WOWList.Clear(); - WOW.CatchingUp = false; - WOW.Updating = false; - } - - /// - /// Process the list of OpenWeatherMap updates created at startup from logger entries - /// - private async void OpenWeatherMapCatchUp() - { - OpenWeatherMap.Updating = true; - - string url = "http://api.openweathermap.org/data/3.0/measurements?appid=" + OpenWeatherMap.PW; - string logUrl = url.Replace(OpenWeatherMap.PW, ""); - - using (var client = new HttpClient()) - { - for (int i = 0; i < OWMList.Count; i++) - { - LogMessage("Uploading OpenWeatherMap archive #" + (i + 1)); - LogDebugMessage("OpenWeatherMap: URL = " + logUrl); - LogDataMessage("OpenWeatherMap: Body = " + OWMList[i]); - - try - { - var data = new StringContent(OWMList[i], Encoding.UTF8, "application/json"); - HttpResponseMessage response = await client.PostAsync(url, data); - var responseBodyAsText = await response.Content.ReadAsStringAsync(); - var status = response.StatusCode == HttpStatusCode.NoContent ? "OK" : "Error"; // Returns a 204 reponse for OK! - LogDebugMessage($"OpenWeatherMap: Response code = {status} - {response.StatusCode}"); - if (response.StatusCode != HttpStatusCode.NoContent) - LogDataMessage($"OpenWeatherMap: Response data = {responseBodyAsText}"); - } - catch (Exception ex) - { - LogMessage("OpenWeatherMap: Update error = " + ex.Message); - } - } - } - - LogMessage("End of OpenWeatherMap archive upload"); - OWMList.Clear(); - OpenWeatherMap.CatchingUp = false; - OpenWeatherMap.Updating = false; - } - - - public async void UpdatePWSweather(DateTime timestamp) - { - if (!PWS.Updating) - { - PWS.Updating = true; - - string pwstring; - string URL = station.GetPWSURL(out pwstring, timestamp); - - string starredpwstring = "&PASSWORD=" + new string('*', PWS.PW.Length); - - string LogURL = URL.Replace(pwstring, starredpwstring); - LogDebugMessage(LogURL); - - try - { - HttpResponseMessage response = await PWShttpClient.GetAsync(URL); - var responseBodyAsText = await response.Content.ReadAsStringAsync(); - LogDebugMessage("PWS Response: " + response.StatusCode + ": " + responseBodyAsText); - if (response.StatusCode != HttpStatusCode.OK) - { - HttpUploadAlarm.Triggered = true; - } - else - { - HttpUploadAlarm.Triggered = false; - } - } - catch (Exception ex) - { - LogDebugMessage("PWS update: " + ex.Message); - HttpUploadAlarm.Triggered = true; - } - finally - { - PWS.Updating = false; - } - } - } - - public async void UpdateWOW(DateTime timestamp) - { - if (!WOW.Updating) - { - WOW.Updating = true; - - string pwstring; - string URL = station.GetWOWURL(out pwstring, timestamp); - - string starredpwstring = "&siteAuthenticationKey=" + new string('*', WOW.PW.Length); - - string LogURL = URL.Replace(pwstring, starredpwstring); - LogDebugMessage(LogURL); - - try - { - HttpResponseMessage response = await WOWhttpClient.GetAsync(URL); - var responseBodyAsText = await response.Content.ReadAsStringAsync(); - LogDebugMessage("WOW Response: " + response.StatusCode + ": " + responseBodyAsText); - if (response.StatusCode != HttpStatusCode.OK) - { - HttpUploadAlarm.Triggered = true; - } - else - { - HttpUploadAlarm.Triggered = false; - } - } - catch (Exception ex) - { - LogDebugMessage("WOW update: " + ex.Message); - HttpUploadAlarm.Triggered = true; - } - finally - { - WOW.Updating = false; - } - } - } - - public async Task MySqlCommandAsync(string Cmd, MySqlConnection Connection, string CallingFunction, bool OpenConnection, bool CloseConnection) - { - try - { - if (OpenConnection) - { - LogDebugMessage($"{CallingFunction}: Opening MySQL Connection"); - await Connection.OpenAsync(); - } - - using (MySqlCommand cmd = new MySqlCommand(Cmd, Connection)) - { - LogDebugMessage($"{CallingFunction}: MySQL executing - {Cmd}"); - - int aff = await cmd.ExecuteNonQueryAsync(); - LogDebugMessage($"{CallingFunction}: MySQL {aff} rows were affected."); - } - - MySqlUploadAlarm.Triggered = false; - } - catch (Exception ex) - { - LogMessage($"{CallingFunction}: Error encountered during MySQL operation."); - LogMessage($"{CallingFunction}: SQL was - \"{Cmd}\""); - LogMessage(ex.Message); - MySqlUploadAlarm.Triggered = true; - } - finally - { - if (CloseConnection) - { - try - { - Connection.Close(); - } - catch { } - } - } - - } - - public Task MySqlCommandSync(List Cmds, MySqlConnection Connection, string CallingFunction, bool OpenConnection, bool CloseConnection, bool ClearCommands=false) - { - return Task.Run(() => - { - try - { - if (OpenConnection) - { - LogDebugMessage($"{CallingFunction}: Opening MySQL Connection"); - Connection.Open(); - } - - for (var i = 0; i < Cmds.Count; i++) - { - try - { - using (MySqlCommand cmd = new MySqlCommand(Cmds[i], Connection)) - { - LogDebugMessage($"{CallingFunction}: MySQL executing[{i + 1}] - {Cmds[i]}"); - - int aff = cmd.ExecuteNonQuery(); - LogDebugMessage($"{CallingFunction}: MySQL {aff} rows were affected."); - } - - MySqlUploadAlarm.Triggered = false; - } - catch (Exception ex) - { - LogMessage($"{CallingFunction}: Error encountered during MySQL operation."); - LogMessage($"{CallingFunction}: SQL was - \"{Cmds[i]}\""); - LogMessage(ex.Message); - MySqlUploadAlarm.Triggered = true; - } - } - - if (CloseConnection) - { - try - { - Connection.Close(); - } - catch - { } - } - } - catch (Exception e) - { - LogMessage($"{CallingFunction}: Error opening MySQL Connection"); - LogMessage(e.Message); - MySqlUploadAlarm.Triggered = true; - } - - if (ClearCommands) - { - Cmds.Clear(); - } - }); - } - - public async void GetLatestVersion() - { - var http = new HttpClient(); - ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; - try - { - var retVal = await http.GetAsync("https://github.com/cumulusmx/CumulusMX/releases/latest"); - var latestUri = retVal.RequestMessage.RequestUri.AbsolutePath; - LatestBuild = new string(latestUri.Split('/').Last().Where(char.IsDigit).ToArray()); - if (int.Parse(Build) < int.Parse(LatestBuild)) - { - var msg = $"You are not running the latest version of Cumulus MX, build {LatestBuild} is available."; - LogConsoleMessage(msg); - LogMessage(msg); - UpgradeAlarm.Triggered = true; - } - else if (int.Parse(Build) == int.Parse(LatestBuild)) - { - LogMessage("This Cumulus MX instance is running the latest version"); - UpgradeAlarm.Triggered = false; - } - else - { - LogMessage($"Could not determine if you are running the latest Cumulus MX build or not. This build = {Build}, latest build = {LatestBuild}"); - } - } - catch (Exception ex) - { - LogMessage("Failed to get the latest build version from Github: " + ex.Message); - } - } - - public async void CustomHttpSecondsUpdate() - { - if (!updatingCustomHttpSeconds) - { - updatingCustomHttpSeconds = true; - - try - { - customHttpSecondsTokenParser.InputText = CustomHttpSecondsString; - var processedString = customHttpSecondsTokenParser.ToStringFromString(); - LogDebugMessage("CustomHttpSeconds: Querying - " + processedString); - var response = await customHttpSecondsClient.GetAsync(processedString); - response.EnsureSuccessStatusCode(); - var responseBodyAsText = await response.Content.ReadAsStringAsync(); - LogDebugMessage("CustomHttpSeconds: Response - " + response.StatusCode); - LogDataMessage("CustomHttpSeconds: Response Text - " + responseBodyAsText); - } - catch (Exception ex) - { - LogDebugMessage("CustomHttpSeconds: " + ex.Message); - } - finally - { - updatingCustomHttpSeconds = false; - } - } - } - - public async void CustomHttpMinutesUpdate() - { - if (!updatingCustomHttpMinutes) - { - updatingCustomHttpMinutes = true; - - try - { - customHttpMinutesTokenParser.InputText = CustomHttpMinutesString; - var processedString = customHttpMinutesTokenParser.ToStringFromString(); - LogDebugMessage("CustomHttpMinutes: Querying - " + processedString); - var response = await customHttpMinutesClient.GetAsync(processedString); - var responseBodyAsText = await response.Content.ReadAsStringAsync(); - LogDebugMessage("CustomHttpMinutes: Response code - " + response.StatusCode); - LogDataMessage("CustomHttpMinutes: Response text - " + responseBodyAsText); - } - catch (Exception ex) - { - LogDebugMessage("CustomHttpMinutes: " + ex.Message); - } - finally - { - updatingCustomHttpMinutes = false; - } - } - } - - public async void CustomHttpRolloverUpdate() - { - if (!updatingCustomHttpRollover) - { - updatingCustomHttpRollover = true; - - try - { - customHttpRolloverTokenParser.InputText = CustomHttpRolloverString; - var processedString = customHttpRolloverTokenParser.ToStringFromString(); - LogDebugMessage("CustomHttpRollover: Querying - " + processedString); - var response = await customHttpRolloverClient.GetAsync(processedString); - var responseBodyAsText = await response.Content.ReadAsStringAsync(); - LogDebugMessage("CustomHttpRollover: Response code - " + response.StatusCode); - LogDataMessage("CustomHttpRollover: Response text - " + responseBodyAsText); - } - catch (Exception ex) - { - LogDebugMessage("CustomHttpRollover: " + ex.Message); - } - finally - { - updatingCustomHttpRollover = false; - } - } - } - - public void DegToDMS(double degrees, out int d, out int m, out int s) - { - int secs = (int)(degrees * 60 * 60); - - s = secs % 60; - - secs = (secs - s) / 60; - - m = secs % 60; - d = secs / 60; - } - - public void AddToWebServiceLists(DateTime timestamp) - { - AddToWundList(timestamp); - AddToWindyList(timestamp); - AddToPWSList(timestamp); - AddToWOWList(timestamp); - AddToOpenWeatherMapList(timestamp); - } - - /// - /// Add an archive entry to the WU 'catchup' list for sending to WU - /// - /// - private void AddToWundList(DateTime timestamp) - { - if (Wund.Enabled && Wund.CatchUp) - { - string pwstring; - string URL = station.GetWundergroundURL(out pwstring, timestamp, true); - - WundList.Add(URL); - - string starredpwstring = "&PASSWORD=" + new string('*', Wund.PW.Length); - - string LogURL = URL.Replace(pwstring, starredpwstring); - - LogMessage("Creating WU URL #" + WundList.Count); - - LogMessage(LogURL); - } - } - - private void AddToWindyList(DateTime timestamp) - { - if (Windy.Enabled && Windy.CatchUp) - { - string apistring; - string URL = station.GetWindyURL(out apistring, timestamp); - - WindyList.Add(URL); - - string LogURL = URL.Replace(apistring, "<>"); - - LogMessage("Creating Windy URL #" + WindyList.Count); - - LogMessage(LogURL); - } - } - - private void AddToPWSList(DateTime timestamp) - { - if (PWS.Enabled && PWS.CatchUp) - { - string pwstring; - string URL = station.GetPWSURL(out pwstring, timestamp); - - PWSList.Add(URL); - - string starredpwstring = "&PASSWORD=" + new string('*', PWS.PW.Length); - - string LogURL = URL.Replace(pwstring, starredpwstring); - - LogMessage("Creating PWS URL #" + PWSList.Count); - - LogMessage(LogURL); - } - } - - private void AddToWOWList(DateTime timestamp) - { - if (WOW.Enabled && WOW.CatchUp) - { - string pwstring; - string URL = station.GetWOWURL(out pwstring, timestamp); - - WOWList.Add(URL); - - string starredpwstring = "&siteAuthenticationKey=" + new string('*', WOW.PW.Length); - - string LogURL = URL.Replace(pwstring, starredpwstring); - - LogMessage("Creating WOW URL #" + WOWList.Count); - - LogMessage(LogURL); - } - } - - private void AddToOpenWeatherMapList(DateTime timestamp) - { - if (OpenWeatherMap.Enabled && OpenWeatherMap.CatchUp) - { - OWMList.Add(station.GetOpenWeatherMapData(timestamp)); - - LogMessage("Creating OpenWeatherMap data #" + OWMList.Count); - } - } - - public void SetMonthlySqlCreateString() - { - StringBuilder strb = new StringBuilder("CREATE TABLE " + MySqlMonthlyTable + " (", 1500); - strb.Append("LogDateTime DATETIME NOT NULL,"); - strb.Append("Temp decimal(4," + TempDPlaces + ") NOT NULL,"); - strb.Append("Humidity decimal(4," + HumDPlaces + ") NOT NULL,"); - strb.Append("Dewpoint decimal(4," + TempDPlaces + ") NOT NULL,"); - strb.Append("Windspeed decimal(4," + WindAvgDPlaces + ") NOT NULL,"); - strb.Append("Windgust decimal(4," + WindDPlaces + ") NOT NULL,"); - strb.Append("Windbearing VARCHAR(3) NOT NULL,"); - strb.Append("RainRate decimal(4," + RainDPlaces + ") NOT NULL,"); - strb.Append("TodayRainSoFar decimal(4," + RainDPlaces + ") NOT NULL,"); - strb.Append("Pressure decimal(6," + PressDPlaces + ") NOT NULL,"); - strb.Append("Raincounter decimal(6," + RainDPlaces + ") NOT NULL,"); - strb.Append("InsideTemp decimal(4," + TempDPlaces + ") NOT NULL,"); - strb.Append("InsideHumidity decimal(4," + HumDPlaces + ") NOT NULL,"); - strb.Append("LatestWindGust decimal(5," + WindDPlaces + ") NOT NULL,"); - strb.Append("WindChill decimal(4," + TempDPlaces + ") NOT NULL,"); - strb.Append("HeatIndex decimal(4," + TempDPlaces + ") NOT NULL,"); - strb.Append("UVindex decimal(4," + UVDPlaces + "),"); - strb.Append("SolarRad decimal(5,1),"); - strb.Append("Evapotrans decimal(4," + RainDPlaces + "),"); - strb.Append("AnnualEvapTran decimal(5," + RainDPlaces + "),"); - strb.Append("ApparentTemp decimal(4," + TempDPlaces + "),"); - strb.Append("MaxSolarRad decimal(5,1),"); - strb.Append("HrsSunShine decimal(3," + SunshineDPlaces + "),"); - strb.Append("CurrWindBearing varchar(3),"); - strb.Append("RG11rain decimal(4," + RainDPlaces + "),"); - strb.Append("RainSinceMidnight decimal(4," + RainDPlaces + "),"); - strb.Append("WindbearingSym varchar(3),"); - strb.Append("CurrWindBearingSym varchar(3),"); - strb.Append("FeelsLike decimal(4," + TempDPlaces + "),"); - strb.Append("Humidex decimal(4," + TempDPlaces + "),"); - strb.Append("PRIMARY KEY (LogDateTime)) COMMENT = \"Monthly logs from Cumulus\""); - CreateMonthlySQL = strb.ToString(); - } - - internal void SetDayfileSqlCreateString() - { - StringBuilder strb = new StringBuilder("CREATE TABLE " + MySqlDayfileTable + " (", 2048); - strb.Append("LogDate date NOT NULL ,"); - strb.Append("HighWindGust decimal(4," + WindDPlaces + ") NOT NULL,"); - strb.Append("HWindGBear varchar(3) NOT NULL,"); - strb.Append("THWindG varchar(5) NOT NULL,"); - strb.Append("MinTemp decimal(5," + TempDPlaces + ") NOT NULL,"); - strb.Append("TMinTemp varchar(5) NOT NULL,"); - strb.Append("MaxTemp decimal(5," + TempDPlaces + ") NOT NULL,"); - strb.Append("TMaxTemp varchar(5) NOT NULL,"); - strb.Append("MinPress decimal(6," + PressDPlaces + ") NOT NULL,"); - strb.Append("TMinPress varchar(5) NOT NULL,"); - strb.Append("MaxPress decimal(6," + PressDPlaces + ") NOT NULL,"); - strb.Append("TMaxPress varchar(5) NOT NULL,"); - strb.Append("MaxRainRate decimal(4," + RainDPlaces + ") NOT NULL,"); - strb.Append("TMaxRR varchar(5) NOT NULL,TotRainFall decimal(6," + RainDPlaces + ") NOT NULL,"); - strb.Append("AvgTemp decimal(4," + TempDPlaces + ") NOT NULL,"); - strb.Append("TotWindRun decimal(5," + WindRunDPlaces +") NOT NULL,"); - strb.Append("HighAvgWSpeed decimal(3," + WindAvgDPlaces + "),"); - strb.Append("THAvgWSpeed varchar(5),LowHum decimal(4," + HumDPlaces + "),"); - strb.Append("TLowHum varchar(5),"); - strb.Append("HighHum decimal(4," + HumDPlaces + "),"); - strb.Append("THighHum varchar(5),TotalEvap decimal(5," + RainDPlaces + "),"); - strb.Append("HoursSun decimal(3," + SunshineDPlaces + "),"); - strb.Append("HighHeatInd decimal(4," + TempDPlaces + "),"); - strb.Append("THighHeatInd varchar(5),"); - strb.Append("HighAppTemp decimal(4," + TempDPlaces + "),"); - strb.Append("THighAppTemp varchar(5),"); - strb.Append("LowAppTemp decimal(4," + TempDPlaces + "),"); - strb.Append("TLowAppTemp varchar(5),"); - strb.Append("HighHourRain decimal(4," + RainDPlaces + "),"); - strb.Append("THighHourRain varchar(5),"); - strb.Append("LowWindChill decimal(4," + TempDPlaces + "),"); - strb.Append("TLowWindChill varchar(5),"); - strb.Append("HighDewPoint decimal(4," + TempDPlaces + "),"); - strb.Append("THighDewPoint varchar(5),"); - strb.Append("LowDewPoint decimal(4," + TempDPlaces + "),"); - strb.Append("TLowDewPoint varchar(5),"); - strb.Append("DomWindDir varchar(3),"); - strb.Append("HeatDegDays decimal(4,1),"); - strb.Append("CoolDegDays decimal(4,1),"); - strb.Append("HighSolarRad decimal(5,1),"); - strb.Append("THighSolarRad varchar(5),"); - strb.Append("HighUV decimal(3," + UVDPlaces + "),"); - strb.Append("THighUV varchar(5),"); - strb.Append("HWindGBearSym varchar(3),"); - strb.Append("DomWindDirSym varchar(3),"); - strb.Append("MaxFeelsLike decimal(5," + TempDPlaces + "),"); - strb.Append("TMaxFeelsLike varchar(5),"); - strb.Append("MinFeelsLike decimal(5," + TempDPlaces + "),"); - strb.Append("TMinFeelsLike varchar(5),"); - strb.Append("MaxHumidex decimal(5," + TempDPlaces + "),"); - strb.Append("TMaxHumidex varchar(5),"); - //strb.Append("MinHumidex decimal(5," + TempDPlaces + "),"); - //strb.Append("TMinHumidex varchar(5),"); - strb.Append("PRIMARY KEY(LogDate)) COMMENT = \"Dayfile from Cumulus\""); - CreateDayfileSQL = strb.ToString(); - } - - public void LogOffsetsMultipliers() - { - LogMessage("Offsets and Multipliers:"); - LogMessage($"PO={Calib.Press.Offset:F3} TO={Calib.Temp.Offset:F3} HO={Calib.Hum.Offset} WDO={Calib.WindDir.Offset} ITO={Calib.InTemp.Offset:F3} SO={Calib.Solar.Offset:F3} UVO={Calib.UV.Offset:F3}"); - LogMessage($"PM={Calib.Press.Mult:F3} WSM={Calib.WindSpeed.Mult:F3} WGM={Calib.WindGust.Mult:F3} TM={Calib.Temp.Mult:F3} TM2={Calib.Temp.Mult2:F3} " + - $"HM={Calib.Hum.Mult:F3} HM2={Calib.Hum.Mult2:F3} RM={Calib.Rain.Mult:F3} SM={Calib.Solar.Mult:F3} UVM={Calib.UV.Mult:F3}"); - LogMessage("Spike removal:"); - LogMessage($"TD={Spike.TempDiff:F3} GD={Spike.GustDiff:F3} WD={Spike.WindDiff:F3} HD={Spike.HumidityDiff:F3} PD={Spike.PressDiff:F3} MR={Spike.MaxRainRate:F3} MH={Spike.MaxHourlyRain:F3}"); - LogMessage("Limits:"); - LogMessage($"TH={Limit.TempHigh.ToString(TempFormat)} TL={Limit.TempLow.ToString(TempFormat)} DH={Limit.DewHigh.ToString(TempFormat)} PH={Limit.PressHigh.ToString(PressFormat)} PL={Limit.PressLow.ToString(PressFormat)} GH={Limit.WindHigh:F3}"); - - } - - private void LogPrimaryAqSensor() - { - switch (StationOptions.PrimaryAqSensor) - { - case (int)PrimaryAqSensor.Undefined: - LogMessage("Primary AQ Sensor = Undefined"); - break; - case (int)PrimaryAqSensor.Ecowitt1: - case (int)PrimaryAqSensor.Ecowitt2: - case (int)PrimaryAqSensor.Ecowitt3: - case (int)PrimaryAqSensor.Ecowitt4: - LogMessage("Primary AQ Sensor = Ecowitt" + StationOptions.PrimaryAqSensor); - break; - case (int)PrimaryAqSensor.EcowittCO2: - LogMessage("Primary AQ Sensor = Ecowitt CO2"); - break; - case (int)PrimaryAqSensor.AirLinkIndoor: - LogMessage("Primary AQ Sensor = Airlink Indoor"); - break; - case (int)PrimaryAqSensor.AirLinkOutdoor: - LogMessage("Primary AQ Sensor = Airlink Outdoor"); - break; - } - } - } - - /* - internal class Raintotaldata - { - public DateTime timestamp; - public double raintotal; - - public Raintotaldata(DateTime ts, double rain) - { - timestamp = ts; - raintotal = rain; - } - } - */ - - public static class StationTypes - { - public const int Undefined = -1; - public const int VantagePro = 0; - public const int VantagePro2 = 1; - public const int WMR928 = 2; - public const int WM918 = 3; - public const int EasyWeather = 4; - public const int FineOffset = 5; - public const int WS2300 = 6; - public const int FineOffsetSolar = 7; - public const int WMR100 = 8; - public const int WMR200 = 9; - public const int Instromet = 10; - public const int WLL = 11; - public const int GW1000 = 12; - } - - /* - public static class AirQualityIndex - { - public const int US_EPA = 0; - public const int UK_COMEAP = 1; - public const int EU_AQI = 2; - public const int CANADA_AQHI = 3; - public const int EU_CAQI = 4; - } - */ - - /* - public static class DoubleExtensions - { - public static string ToUKString(this double value) - { - return value.ToString(CultureInfo.GetCultureInfo("en-GB")); - } - } - */ - - public class DiaryData - { - [PrimaryKey] - public DateTime Timestamp { get; set; } - public string entry { get; set; } - public int snowFalling { get; set; } - public int snowLying { get; set; } - public double snowDepth { get; set; } - } - - public class ProgramOptionsClass - { - public bool EnableAccessibility { get; set; } - public string StartupPingHost { get; set; } - public int StartupPingEscapeTime { get; set; } - public int StartupDelaySecs { get; set; } - public int StartupDelayMaxUptime { get; set; } - public bool DebugLogging { get; set; } - public bool DataLogging { get; set; } - public bool WarnMultiple { get; set; } - } - - public class StationUnits - { - public int Wind { get; set; } - public int Press { get; set; } - public int Rain { get; set; } - public int Temp { get; set; } - - public string WindText { get; set; } - public string PressText { get; set; } - public string RainText { get; set; } - public string TempText { get; set; } - - public string TempTrendText { get; set; } - public string RainTrendText { get; set; } - public string PressTrendText { get; set; } - public string WindRunText { get; set; } - public string AirQualityUnitText { get; set; } - public string SoilMoistureUnitText { get; set; } - public string CO2UnitText { get; set; } - - public StationUnits() - { - AirQualityUnitText = "µg/m³"; - SoilMoistureUnitText = "cb"; - CO2UnitText = "ppm"; - } - } - - public class StationOptions - { - public bool UseZeroBearing { get; set; } - public bool UseWind10MinAve { get; set; } - public bool UseSpeedForAvgCalc { get; set; } - public bool Humidity98Fix { get; set; } - public bool CalculatedDP { get; set; } - public bool CalculatedWC { get; set; } - public bool SyncTime { get; set; } - public int ClockSettingHour { get; set; } - public bool UseCumulusPresstrendstr { get; set; } - public bool LogExtraSensors { get; set; } - public bool WS2300IgnoreStationClock { get; set; } - public bool RoundWindSpeed { get; set; } - public int PrimaryAqSensor { get; set; } - public bool NoSensorCheck { get; set; } - public int AvgBearingMinutes { get; set; } - public int AvgSpeedMinutes { get; set; } - public int PeakGustMinutes { get; set; } - } - - public class FileGenerationFtpOptions - { - public string TemplateFileName { get; set; } - public string LocalFileName { get; set; } - public string LocalPath { get; set; } - public string RemoteFileName { get; set; } - public bool Create { get; set; } - public bool FTP { get; set; } - public bool FtpRequired { get; set; } - public bool CreateRequired { get; set; } - public FileGenerationFtpOptions() - { - CreateRequired = true; - FtpRequired = true; - } - } - - public class DavisOptions - { - public bool ForceVPBarUpdate { get; set; } - public bool ReadReceptionStats { get; set; } - public bool SetLoggerInterval { get; set; } - public bool UseLoop2 { get; set; } - public int InitWaitTime { get; set; } - public int IPResponseTime { get; set; } - public int ReadTimeout { get; set; } - public bool IncrementPressureDP { get; set; } - public int BaudRate { get; set; } - public int RainGaugeType { get; set; } - public int ConnectionType { get; set; } - public int TCPPort { get; set; } - public string IPAddr { get; set; } - public int PeriodicDisconnectInterval { get; set; } - } - - public class FineOffsetOptions - { - public bool SyncReads { get; set; } - public int ReadAvoidPeriod { get; set; } - public int ReadTime { get; set; } - public bool SetLoggerInterval { get; set; } - public int VendorID { get; set; } - public int ProductID { get; set; } -} - - public class ImetOptions - { - public List BaudRates { get; set; } - public int BaudRate { get; set; } - public int WaitTime { get; set; } - public int ReadDelay { get; set; } - public bool UpdateLogPointer { get; set; } - } - - public class EasyWeatherOptions - { - public double Interval { get; set; } - public string Filename { get; set; } - public int MinPressMB { get; set; } - public int MaxPressMB { get; set; } - public int MaxRainTipDiff { get; set; } - public double PressOffset { get; set; } - } - - public class GraphOptions - { - public bool TempVisible { get; set; } - public bool InTempVisible { get; set; } - public bool HIVisible { get; set; } - public bool DPVisible { get; set; } - public bool WCVisible { get; set; } - public bool AppTempVisible { get; set; } - public bool FeelsLikeVisible { get; set; } - public bool HumidexVisible { get; set; } - public bool InHumVisible { get; set; } - public bool OutHumVisible { get; set; } - public bool UVVisible { get; set; } - public bool SolarVisible { get; set; } - public bool SunshineVisible { get; set; } - public bool DailyMaxTempVisible { get; set; } - public bool DailyAvgTempVisible { get; set; } - public bool DailyMinTempVisible { get; set; } - public bool GrowingDegreeDaysVisible1 { get; set; } - public bool GrowingDegreeDaysVisible2 { get; set; } - public bool TempSumVisible0 { get; set; } - public bool TempSumVisible1 { get; set; } - public bool TempSumVisible2 { get; set; } - } - - public class SelectaChartOptions - { - public string[] series { get; set; } - public string[] colours { get; set; } - - public SelectaChartOptions() - { - series = new string[6]; - colours = new string[6]; - } - } - - public class AwekasResponse - { - public int status { get; set; } - public int authentication { get; set; } - public int minuploadtime { get; set; } - public AwekasErrors error { get; set; } - public AwekasDisabled disabled { get; set; } - } - - public class AwekasErrors - { - public int count { get; set; } - public int time { get; set; } - public int date { get; set; } - public int temp { get; set; } - public int hum { get; set; } - public int airp { get; set; } - public int rain { get; set; } - public int rainrate { get; set; } - public int wind { get; set; } - public int gust { get; set; } - public int snow { get; set; } - public int solar { get; set; } - public int uv { get; set; } - public int brightness { get; set; } - public int suntime { get; set; } - public int indoortemp { get; set; } - public int indoorhumidity { get; set; } - public int soilmoisture1 { get; set; } - public int soilmoisture2 { get; set; } - public int soilmoisture3 { get; set; } - public int soilmoisture4 { get; set; } - public int soiltemp1 { get; set; } - public int soiltemp2 { get; set; } - public int soiltemp3 { get; set; } - public int soiltemp4 { get; set; } - public int leafwetness1 { get; set; } - public int leafwetness2 { get; set; } - public int warning { get; set; } - } - - public class AwekasDisabled - { - public int temp { get; set; } - public int hum { get; set; } - public int airp { get; set; } - public int rain { get; set; } - public int rainrate { get; set; } - public int wind { get; set; } - public int snow { get; set; } - public int solar { get; set; } - public int uv { get; set; } - public int indoortemp { get; set; } - public int indoorhumidity { get; set; } - public int soilmoisture1 { get; set; } - public int soilmoisture2 { get; set; } - public int soilmoisture3 { get; set; } - public int soilmoisture4 { get; set; } - public int soiltemp1 { get; set; } - public int soiltemp2 { get; set; } - public int soiltemp3 { get; set; } - public int soiltemp4 { get; set; } - public int leafwetness1 { get; set; } - public int leafwetness2 { get; set; } - public int report { get; set; } - } - - public class OpenWeatherMapStation - { - public string id { get; set; } - public string created_at { get; set; } - public string updated_at { get; set; } - public string external_id { get; set; } - public string name { get; set; } - public double longitude { get; set; } - public double latitude { get; set; } - public int altitude { get; set; } - public int rank { get; set; } - } - - public class OpenWeatherMapNewStation - { - public string ID { get; set; } - public string created_at { get; set; } - public string updated_at { get; set; } - public string user_id { get; set; } - public string external_id { get; set; } - public string name { get; set; } - public double longitude { get; set; } - public double latitude { get; set; } - public int altitude { get; set; } - public int source_type { get; set; } - } - - public class Alarm - { - public Cumulus cumulus { get; set; } - - public bool Enabled { get; set; } - public double Value { get; set; } - public bool Sound { get; set; } - public string SoundFile { get; set; } - - bool triggered; - public bool Triggered - { - get => triggered; - set - { - if (value) - { - // If we were not set before, so we need to send an email? - if (!triggered && Enabled && Email && cumulus.SmtpOptions.Enabled) - { - // Construct the message - preamble, plus values - var msg = cumulus.AlarmEmailPreamble + "\r\n" + string.Format(EmailMsg, Value, Units); - cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); - } - - // If we get a new trigger, record the time - triggered = true; - TriggeredTime = DateTime.Now; - } - else - { - // If the trigger is cleared, check if we should be latching the value - if (Latch) - { - if (DateTime.Now > TriggeredTime.AddHours(LatchHours)) - { - // We are latching, but the latch period has expired, clear the trigger - triggered = false; - } - } - else - { - // No latch, just clear the trigger - triggered = false; - } - } - } - } - public DateTime TriggeredTime { get; set; } - public bool Notify { get; set; } - public bool Email { get; set; } - public bool Latch { get; set; } - public int LatchHours { get; set; } - public string EmailMsg { get; set; } - public string Units { get; set; } - } - - public class AlarmChange : Alarm - { - //public bool changeUp { get; set; } - //public bool changeDown { get; set; } - - bool upTriggered; - public bool UpTriggered - { - get => upTriggered; - set - { - if (value) - { - // If we were not set before, so we need to send an email? - if (!upTriggered && Enabled && Email && cumulus.SmtpOptions.Enabled) - { - // Construct the message - preamble, plus values - var msg = Program.cumulus.AlarmEmailPreamble + "\r\n" + string.Format(EmailMsgUp, Value, Units); - cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); - } - - // If we get a new trigger, record the time - upTriggered = true; - UpTriggeredTime = DateTime.Now; - } - else - { - // If the trigger is cleared, check if we should be latching the value - if (Latch) - { - if (DateTime.Now > UpTriggeredTime.AddHours(LatchHours)) - { - // We are latching, but the latch period has expired, clear the trigger - upTriggered = false; - } - } - else - { - // No latch, just clear the trigger - upTriggered = false; - } - } - } - } - public DateTime UpTriggeredTime { get; set; } - - - bool downTriggered; - public bool DownTriggered - { - get => downTriggered; - set - { - if (value) - { - // If we were not set before, so we need to send an email? - if (!downTriggered && Enabled && Email && cumulus.SmtpOptions.Enabled) - { - // Construct the message - preamble, plus values - var msg = Program.cumulus.AlarmEmailPreamble + "\n" + string.Format(EmailMsgDn, Value, Units); - cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); - } - - // If we get a new trigger, record the time - downTriggered = true; - DownTriggeredTime = DateTime.Now; - } - else - { - // If the trigger is cleared, check if we should be latching the value - if (Latch) - { - if (DateTime.Now > DownTriggeredTime.AddHours(LatchHours)) - { - // We are latching, but the latch period has expired, clear the trigger - downTriggered = false; - } - } - else - { - // No latch, just clear the trigger - downTriggered = false; - } - } - } - } - - public DateTime DownTriggeredTime { get; set; } - - public string EmailMsgUp { get; set; } - public string EmailMsgDn { get; set; } - - } - - public class WebUploadService - { - public string Server; - public int Port; - public string ID; - public string PW; - public bool Enabled; - public int Interval; - public int DefaultInterval; - public bool SynchronisedUpdate; - public bool SendUV; - public bool SendSolar; - public bool SendIndoor; - public bool SendAirQuality; - public bool CatchUp; - public bool CatchingUp; - public bool Updating; - } - - public class WebUploadTwitter : WebUploadService - { - public string OauthToken; - public string OauthTokenSecret; - public bool SendLocation; - } - - public class WebUploadWund : WebUploadService - { - public bool RapidFireEnabled; - public bool SendAverage; - public bool SendSoilTemp1; - public bool SendSoilTemp2; - public bool SendSoilTemp3; - public bool SendSoilTemp4; - public bool SendSoilMoisture1; - public bool SendSoilMoisture2; - public bool SendSoilMoisture3; - public bool SendSoilMoisture4; - public bool SendLeafWetness1; - public bool SendLeafWetness2; - } - - public class WebUploadWindy : WebUploadService - { - public string ApiKey; - public int StationIdx; - } - - public class WebUploadWindGuru : WebUploadService - { - public bool SendRain; - } - - public class WebUploadAwekas : WebUploadService - { - public bool RateLimited; - public int OriginalInterval; - public string Lang; - public bool SendSoilTemp; - public bool SendSoilMoisture; - public bool SendLeafWetness; - } - - public class WebUploadWCloud : WebUploadService - { - public bool SendSoilMoisture; - public int SoilMoistureSensor; - public bool SendLeafWetness; - public int LeafWetnessSensor; - } - - public class WebUploadAprs : WebUploadService - { - public bool HumidityCutoff; - } - - public class DisplayOptions - { - public bool UseApparent { get; set; } - public bool ShowSolar { get; set; } - public bool ShowUV { get; set; } - } - - public class AlarmEmails - { - public string Preamble { get; set; } - public string HighGust { get; set; } - public string HighWind { get; set; } - public string HighTemp { get; set; } - public string LowTemp { get; set; } - public string TempDown { get; set; } - public string TempUp { get; set; } - public string HighPress { get; set; } - public string LowPress { get; set; } - public string PressDown { get; set; } - public string PressUp { get; set; } - public string Rain { get; set; } - public string RainRate { get; set; } - public string SensorLost { get; set; } - public string DataStopped { get; set; } - public string BatteryLow { get; set; } - public string DataSpike { get; set; } - public string Upgrade { get; set; } - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.IO.Ports; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Sockets; +using System.Net.NetworkInformation; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Security.Authentication; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Timers; +using MySqlConnector; +using FluentFTP; +using LinqToTwitter; +using ServiceStack.Text; +using Unosquare.Labs.EmbedIO; +using Unosquare.Labs.EmbedIO.Modules; +using Unosquare.Labs.EmbedIO.Constants; +using Timer = System.Timers.Timer; +using SQLite; +using Renci.SshNet; +using FluentFTP.Helpers; + +namespace CumulusMX +{ + public class Cumulus + { + ///////////////////////////////// + /// Now derived from app properties + public string Version; + public string Build; + ///////////////////////////////// + + + + public static SemaphoreSlim syncInit = new SemaphoreSlim(1); + + /* + public enum VPRainGaugeTypes + { + MM = 0, + IN = 1 + } + */ + + /* + public enum VPConnTypes + { + Serial = 0, + TCPIP = 1 + } + */ + + public enum PressUnits + { + MB, + HPA, + IN + } + + public enum WindUnits + { + MS, + MPH, + KPH, + KNOTS + } + + public enum TempUnits + { + C, + F + } + + public enum RainUnits + { + MM, + IN + } + + /* + public enum SolarCalcTypes + { + RyanStolzenbach = 0, + Bras = 1 + } + */ + + public enum FtpProtocols + { + FTP = 0, + FTPS = 1, + SFTP = 2 + } + + public enum PrimaryAqSensor + { + Undefined = -1, + AirLinkOutdoor = 0, + Ecowitt1 = 1, + Ecowitt2 = 2, + Ecowitt3 = 3, + Ecowitt4 = 4, + AirLinkIndoor = 5, + EcowittCO2 = 6 + } + + private readonly string[] sshAuthenticationVals = { "password", "psk", "password_psk" }; + + /* + public struct Dataunits + { + public Units.Presss Units.Press; + public Units.Winds Units.Wind; + public tempunits tempunit; + public rainunits rainunit; + } + */ + + /* + public struct CurrentData + { + public double OutdoorTemperature; + public double AvgTempToday; + public double IndoorTemperature; + public double OutdoorDewpoint; + public double WindChill; + public int IndoorHumidity; + public int OutdoorHumidity; + public double Pressure; + public double WindLatest; + public double WindAverage; + public double Recentmaxgust; + public double WindRunToday; + public int Bearing; + public int Avgbearing; + public double RainToday; + public double RainYesterday; + public double RainMonth; + public double RainYear; + public double RainRate; + public double RainLastHour; + public double HeatIndex; + public double Humidex; + public double AppTemp; + public double FeelsLike; + public double TempTrend; + public double PressTrend; + } + */ + + /* + public struct HighLowData + { + public double TodayLow; + public DateTime TodayLowDT; + public double TodayHigh; + public DateTime TodayHighDT; + public double YesterdayLow; + public DateTime YesterdayLowDT; + public double YesterdayHigh; + public DateTime YesterdayHighDT; + public double MonthLow; + public DateTime MonthLowDT; + public double MonthHigh; + public DateTime MonthHighDT; + public double YearLow; + public DateTime YearLowDT; + public double YearHigh; + public DateTime YearHighDT; + } + */ + + public struct TExtraFiles + { + public string local; + public string remote; + public bool process; + public bool binary; + public bool realtime; + public bool endofday; + public bool FTP; + public bool UTF8; + } + + //public Dataunits Units; + + public const int DayfileFields = 52; + + private readonly WeatherStation station; + + internal DavisAirLink airLinkIn; + public int airLinkInLsid; + public string AirLinkInHostName; + internal DavisAirLink airLinkOut; + public int airLinkOutLsid; + public string AirLinkOutHostName; + + public DateTime LastUpdateTime; + + public PerformanceCounter UpTime; + + private readonly WebTags webtags; + private readonly TokenParser tokenParser; + private readonly TokenParser realtimeTokenParser; + private readonly TokenParser customMysqlSecondsTokenParser = new TokenParser(); + private readonly TokenParser customMysqlMinutesTokenParser = new TokenParser(); + private readonly TokenParser customMysqlRolloverTokenParser = new TokenParser(); + + public string CurrentActivity = "Stopped"; + + private static readonly TraceListener FtpTraceListener = new TextWriterTraceListener("ftplog.txt", "ftplog"); + + + public string AirQualityUnitText = "µg/m³"; + public string SoilMoistureUnitText = "cb"; + public string CO2UnitText = "ppm"; + + public volatile int WebUpdating; + + public double WindRoseAngle { get; set; } + + public int NumWindRosePoints { get; set; } + + //public int[] WUnitFact = new[] { 1000, 2237, 3600, 1944 }; + //public int[] TUnitFact = new[] { 1000, 1800 }; + //public int[] TUnitAdd = new[] { 0, 32 }; + //public int[] PUnitFact = new[] { 1000, 1000, 2953 }; + //public int[] PressFact = new[] { 1, 1, 100 }; + //public int[] RUnitFact = new[] { 1000, 39 }; + + public int[] logints = new[] { 1, 5, 10, 15, 20, 30 }; + + //public int UnitMult = 1000; + + public int GraphDays = 31; + + public string Newmoon = "New Moon", + WaxingCrescent = "Waxing Crescent", + FirstQuarter = "First Quarter", + WaxingGibbous = "Waxing Gibbous", + Fullmoon = "Full Moon", + WaningGibbous = "Waning Gibbous", + LastQuarter = "Last Quarter", + WaningCrescent = "Waning Crescent"; + + public string Calm = "Calm", + Lightair = "Light air", + Lightbreeze = "Light breeze", + Gentlebreeze = "Gentle breeze", + Moderatebreeze = "Moderate breeze", + Freshbreeze = "Fresh breeze", + Strongbreeze = "Strong breeze", + Neargale = "Near gale", + Gale = "Gale", + Stronggale = "Strong gale", + Storm = "Storm", + Violentstorm = "Violent storm", + Hurricane = "Hurricane"; + + public string Risingveryrapidly = "Rising very rapidly", + Risingquickly = "Rising quickly", + Rising = "Rising", + Risingslowly = "Rising slowly", + Steady = "Steady", + Fallingslowly = "Falling slowly", + Falling = "Falling", + Fallingquickly = "Falling quickly", + Fallingveryrapidly = "Falling very rapidly"; + + public string[] compassp = { "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW" }; + + public string[] zForecast = + { + "Settled fine", "Fine weather", "Becoming fine", "Fine, becoming less settled", "Fine, possible showers", "Fairly fine, improving", + "Fairly fine, possible showers early", "Fairly fine, showery later", "Showery early, improving", "Changeable, mending", + "Fairly fine, showers likely", "Rather unsettled clearing later", "Unsettled, probably improving", "Showery, bright intervals", + "Showery, becoming less settled", "Changeable, some rain", "Unsettled, short fine intervals", "Unsettled, rain later", "Unsettled, some rain", + "Mostly very unsettled", "Occasional rain, worsening", "Rain at times, very unsettled", "Rain at frequent intervals", "Rain, very unsettled", + "Stormy, may improve", "Stormy, much rain" + }; + + public string[] DavisForecast1 = + { + "FORECAST REQUIRES 3 HRS. OF RECENT DATA", "Mostly cloudy with little temperature change. ", "Mostly cloudy and cooler. ", + "Clearing, cooler and windy. ", "Clearing and cooler. ", "Increasing clouds and cooler. ", + "Increasing clouds with little temperature change. ", "Increasing clouds and warmer. ", + "Mostly clear for 12 to 24 hours with little temperature change. ", "Mostly clear for 6 to 12 hours with little temperature change. ", + "Mostly clear and warmer. ", "Mostly clear for 12 to 24 hours and cooler. ", "Mostly clear for 12 hours with little temperature change. ", + "Mostly clear with little temperature change. ", "Mostly clear and cooler. ", "Partially cloudy, Rain and/or snow possible or continuing. ", + "Partially cloudy, Snow possible or continuing. ", "Partially cloudy, Rain possible or continuing. ", + "Mostly cloudy, Rain and/or snow possible or continuing. ", "Mostly cloudy, Snow possible or continuing. ", + "Mostly cloudy, Rain possible or continuing. ", "Mostly cloudy. ", "Partially cloudy. ", "Mostly clear. ", + "Partly cloudy with little temperature change. ", "Partly cloudy and cooler. ", "Unknown forecast rule." + }; + + public string[] DavisForecast2 = + { + "", "Precipitation possible within 48 hours. ", "Precipitation possible within 24 to 48 hours. ", + "Precipitation possible within 24 hours. ", "Precipitation possible within 12 to 24 hours. ", + "Precipitation possible within 12 hours, possibly heavy at times. ", "Precipitation possible within 12 hours. ", + "Precipitation possible within 6 to 12 hours. ", "Precipitation possible within 6 to 12 hours, possibly heavy at times. ", + "Precipitation possible and windy within 6 hours. ", "Precipitation possible within 6 hours. ", "Precipitation ending in 12 to 24 hours. ", + "Precipitation possibly heavy at times and ending within 12 hours. ", "Precipitation ending within 12 hours. ", + "Precipitation ending within 6 hours. ", "Precipitation likely, possibly heavy at times. ", "Precipitation likely. ", + "Precipitation continuing, possibly heavy at times. ", "Precipitation continuing. " + }; + + public string[] DavisForecast3 = + { + "", "Windy with possible wind shift to the W, SW, or S.", "Possible wind shift to the W, SW, or S.", + "Windy with possible wind shift to the W, NW, or N.", "Possible wind shift to the W, NW, or N.", "Windy.", "Increasing winds." + }; + + public int[,] DavisForecastLookup = + { + {14, 0, 0}, {13, 0, 0}, {12, 0, 0}, {11, 0, 0}, {13, 0, 0}, {25, 0, 0}, {24, 0, 0}, {24, 0, 0}, {10, 0, 0}, {24, 0, 0}, {24, 0, 0}, + {13, 0, 0}, {7, 2, 0}, {24, 0, 0}, {13, 0, 0}, {6, 3, 0}, {13, 0, 0}, {24, 0, 0}, {13, 0, 0}, {6, 6, 0}, {13, 0, 0}, {24, 0, 0}, + {13, 0, 0}, {7, 3, 0}, {10, 0, 6}, {24, 0, 0}, {13, 0, 0}, {7, 6, 6}, {10, 0, 6}, {7, 0, 0}, {24, 0, 0}, {13, 0, 0}, {7, 6, 6}, + {10, 0, 6}, {7, 0, 0}, {24, 0, 0}, {13, 0, 0}, {7, 6, 6}, {24, 0, 0}, {13, 0, 0}, {10, 1, 0}, {10, 0, 0}, {24, 0, 0}, {13, 0, 0}, + {6, 2, 0}, {6, 0, 0}, {24, 0, 0}, {13, 0, 0}, {7, 4, 0}, {24, 0, 0}, {13, 0, 0}, {7, 4, 5}, {24, 0, 0}, {13, 0, 0}, {7, 4, 5}, + {24, 0, 0}, {13, 0, 0}, {7, 7, 0}, {24, 0, 0}, {13, 0, 0}, {7, 7, 5}, {24, 0, 0}, {13, 0, 0}, {7, 4, 5}, {24, 0, 0}, {13, 0, 0}, + {7, 6, 0}, {24, 0, 0}, {13, 0, 0}, {7, 16, 0}, {4, 14, 0}, {24, 0, 0}, {4, 14, 0}, {13, 0, 0}, {4, 14, 0}, {25, 0, 0}, {24, 0, 0}, + {14, 0, 0}, {4, 14, 0}, {13, 0, 0}, {4, 14, 0}, {14, 0, 0}, {24, 0, 0}, {13, 0, 0}, {6, 3, 0}, {2, 18, 0}, {24, 0, 0}, {13, 0, 0}, + {2, 16, 0}, {1, 18, 0}, {1, 16, 0}, {24, 0, 0}, {13, 0, 0}, {5, 9, 0}, {6, 9, 0}, {2, 18, 6}, {24, 0, 0}, {13, 0, 0}, {2, 16, 6}, + {1, 18, 6}, {1, 16, 6}, {24, 0, 0}, {13, 0, 0}, {5, 4, 4}, {6, 4, 4}, {24, 0, 0}, {13, 0, 0}, {5, 10, 4}, {6, 10, 4}, {2, 13, 4}, + {2, 0, 4}, {1, 13, 4}, {1, 0, 4}, {2, 13, 4}, {24, 0, 0}, {13, 0, 0}, {2, 3, 4}, {1, 13, 4}, {1, 3, 4}, {3, 14, 0}, {3, 0, 0}, + {2, 14, 3}, {2, 0, 3}, {3, 0, 0}, {24, 0, 0}, {13, 0, 0}, {1, 6, 5}, {24, 0, 0}, {13, 0, 0}, {5, 5, 5}, {2, 14, 5}, {24, 0, 0}, + {13, 0, 0}, {2, 6, 5}, {2, 11, 0}, {2, 0, 0}, {2, 17, 5}, {24, 0, 0}, {13, 0, 0}, {2, 7, 5}, {1, 17, 5}, {24, 0, 0}, {13, 0, 0}, + {1, 7, 5}, {24, 0, 0}, {13, 0, 0}, {6, 5, 5}, {2, 0, 5}, {2, 17, 5}, {24, 0, 0}, {13, 0, 0}, {2, 15, 5}, {1, 17, 5}, {1, 15, 5}, + {24, 0, 0}, {13, 0, 0}, {5, 10, 5}, {6, 10, 5}, {5, 18, 3}, {24, 0, 0}, {13, 0, 0}, {2, 16, 3}, {1, 18, 3}, {1, 16, 3}, {5, 10, 3}, + {24, 0, 0}, {13, 0, 0}, {5, 10, 4}, {6, 10, 3}, {6, 10, 4}, {24, 0, 0}, {13, 0, 0}, {5, 10, 3}, {6, 10, 3}, {24, 0, 0}, {13, 0, 0}, + {5, 4, 3}, {6, 4, 3}, {2, 12, 3}, {24, 0, 0}, {13, 0, 0}, {2, 8, 3}, {1, 13, 3}, {1, 8, 3}, {2, 18, 0}, {24, 0, 0}, {13, 0, 0}, + {2, 16, 3}, {1, 18, 0}, {1, 16, 0}, {24, 0, 0}, {13, 0, 0}, {2, 5, 5}, {0, 0, 0}, {26, 0, 0}, {26, 0, 0}, {26, 0, 0}, {26, 0, 0}, {26, 0, 0}, {26, 0, 0}, {26, 0, 0} + }; + + // equivalents of Zambretti "dial window" letters A - Z + public int[] riseOptions = { 25, 25, 25, 24, 24, 19, 16, 12, 11, 9, 8, 6, 5, 2, 1, 1, 0, 0, 0, 0, 0, 0 }; + public int[] steadyOptions = { 25, 25, 25, 25, 25, 25, 23, 23, 22, 18, 15, 13, 10, 4, 1, 1, 0, 0, 0, 0, 0, 0 }; + public int[] fallOptions = { 25, 25, 25, 25, 25, 25, 25, 25, 23, 23, 21, 20, 17, 14, 7, 3, 1, 1, 1, 0, 0, 0 }; + + internal int[] FactorsOf60 = { 1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60 }; + + public TimeSpan AvgSpeedTime { get; set; } + + public TimeSpan PeakGustTime { get; set; } + public TimeSpan AvgBearingTime { get; set; } + + public bool UTF8encode { get; set; } + + internal int TempDPlaces = 1; + public string TempFormat; + + internal int WindDPlaces = 1; + internal int WindAvgDPlaces = 1; + public string WindFormat; + public string WindAvgFormat; + + internal int HumDPlaces = 0; + public string HumFormat; + + internal int AirQualityDPlaces = 1; + public string AirQualityFormat; + + public int WindRunDPlaces = 1; + public string WindRunFormat; + + public int RainDPlaces = 1; + public string RainFormat; + + internal int PressDPlaces = 1; + public string PressFormat; + + internal int SunshineDPlaces = 1; + public string SunFormat; + + internal int UVDPlaces = 1; + public string UVFormat; + + public string ETFormat; + + public string ComportName; + public string DefaultComportName; + + //public string IPaddress; + + //public int TCPport; + + //public VPConnTypes VPconntype; + + public string Platform; + + public string dbfile; + public SQLiteConnection LogDB; + + public string diaryfile; + public SQLiteConnection DiaryDB; + + public string Datapath; + + public string ListSeparator; + public char DirectorySeparator; + + public int RolloverHour; + public bool Use10amInSummer; + + public double Latitude; + public double Longitude; + public double Altitude; + + public double RStransfactor = 0.8; + + internal int wsPort; + private readonly bool DebuggingEnabled; + + public SerialPort cmprtRG11; + public SerialPort cmprt2RG11; + + private const int DefaultWebUpdateInterval = 15; + + public int RecordSetTimeoutHrs = 24; + + private const int VP2SERIALCONNECTION = 0; + //private const int VP2USBCONNECTION = 1; + //private const int VP2TCPIPCONNECTION = 2; + + private readonly string twitterKey = "lQiGNdtlYUJ4wS3d7souPw"; + private readonly string twitterSecret = "AoB7OqimfoaSfGQAd47Hgatqdv3YeTTiqpinkje6Xg"; + + public string AlltimeIniFile; + public string Alltimelogfile; + public string MonthlyAlltimeIniFile; + public string MonthlyAlltimeLogFile; + private readonly string logFilePath; + public string DayFileName; + public string YesterdayFile; + public string TodayIniFile; + public string MonthIniFile; + public string YearIniFile; + //private readonly string stringsFile; + private readonly string backupPath; + //private readonly string ExternaldataFile; + public string WebTagFile; + + public bool SynchronisedWebUpdate; + + private List WundList = new List(); + private List WindyList = new List(); + private List PWSList = new List(); + private List WOWList = new List(); + private List OWMList = new List(); + + private List MySqlList = new List(); + + // Calibration settings + /// + /// User value calibration settings + /// + public Calibrations Calib = new Calibrations(); + + /// + /// User extreme limit settings + /// + public Limits Limit = new Limits(); + + /// + /// User spike limit settings + /// + public Spikes Spike = new Spikes(); + + public ProgramOptionsClass ProgramOptions = new ProgramOptionsClass(); + + public StationOptions StationOptions = new StationOptions(); + + public StationUnits Units = new StationUnits(); + + public DavisOptions DavisOptions = new DavisOptions(); + public FineOffsetOptions FineOffsetOptions = new FineOffsetOptions(); + public ImetOptions ImetOptions = new ImetOptions(); + public EasyWeatherOptions EwOptions = new EasyWeatherOptions(); + + public GraphOptions GraphOptions = new GraphOptions(); + + public SelectaChartOptions SelectaChartOptions = new SelectaChartOptions(); + + public DisplayOptions DisplayOptions = new DisplayOptions(); + + public EmailSender emailer; + public EmailSender.SmtpOptions SmtpOptions = new EmailSender.SmtpOptions(); + + public string AlarmEmailPreamble; + public string AlarmEmailSubject; + public string AlarmFromEmail; + public string[] AlarmDestEmail; + public bool AlarmEmailHtml; + + public bool ListWebTags; + + public bool RealtimeEnabled; // The timer is to be started + public bool RealtimeFTPEnabled; // The FTP connection is to be established + private int realtimeFTPRetries; // Count of failed realtime FTP attempts + + // Twitter settings + public WebUploadTwitter Twitter = new WebUploadTwitter(); + + // Wunderground settings + public WebUploadWund Wund = new WebUploadWund(); + + // Windy.com settings + public WebUploadWindy Windy = new WebUploadWindy(); + + // Wind Guru settings + public WebUploadWindGuru WindGuru = new WebUploadWindGuru(); + + // PWS Weather settings + public WebUploadService PWS = new WebUploadService(); + + // WOW settings + public WebUploadService WOW = new WebUploadService(); + + // APRS settings + public WebUploadAprs APRS = new WebUploadAprs(); + + // Awekas settings + public WebUploadAwekas AWEKAS = new WebUploadAwekas(); + + // WeatherCloud settings + public WebUploadWCloud WCloud = new WebUploadWCloud(); + + // OpenWeatherMap settings + public WebUploadService OpenWeatherMap = new WebUploadService(); + + + // MQTT settings + public struct MqttSettings + { + public string Server; + public int Port; + public int IpVersion; + public bool UseTLS; + public string Username; + public string Password; + public bool EnableDataUpdate; + public string UpdateTopic; + public string UpdateTemplate; + public bool UpdateRetained; + public bool EnableInterval; + public int IntervalTime; + public string IntervalTopic; + public string IntervalTemplate; + public bool IntervalRetained; + } + public MqttSettings MQTT; + + // NOAA report settings + public string NOAAname; + public string NOAAcity; + public string NOAAstate; + public double NOAAheatingthreshold; + public double NOAAcoolingthreshold; + public double NOAAmaxtempcomp1; + public double NOAAmaxtempcomp2; + public double NOAAmintempcomp1; + public double NOAAmintempcomp2; + public double NOAAraincomp1; + public double NOAAraincomp2; + public double NOAAraincomp3; + public bool NOAA12hourformat; + public bool NOAAAutoSave; + public bool NOAAAutoFTP; + public bool NOAANeedFTP; + public string NOAAMonthFileFormat; + public string NOAAYearFileFormat; + public string NOAAFTPDirectory; + public string NOAALatestMonthlyReport; + public string NOAALatestYearlyReport; + public bool NOAAUseUTF8; + public bool NOAAUseDotDecimal; + + public double[] NOAATempNorms = new double[13]; + + public double[] NOAARainNorms = new double[13]; + + // Growing Degree Days + public double GrowingBase1; + public double GrowingBase2; + public int GrowingYearStarts; + public bool GrowingCap30C; + + public int TempSumYearStarts; + public double TempSumBase1; + public double TempSumBase2; + + public bool EODfilesNeedFTP; + + public bool IsOSX; + public double CPUtemp = -999; + + // Alarms + public Alarm DataStoppedAlarm = new Alarm(); + public Alarm BatteryLowAlarm = new Alarm(); + public Alarm SensorAlarm = new Alarm(); + public Alarm SpikeAlarm = new Alarm(); + public Alarm HighWindAlarm = new Alarm(); + public Alarm HighGustAlarm = new Alarm(); + public Alarm HighRainRateAlarm = new Alarm(); + public Alarm HighRainTodayAlarm = new Alarm(); + public AlarmChange PressChangeAlarm = new AlarmChange(); + public Alarm HighPressAlarm = new Alarm(); + public Alarm LowPressAlarm = new Alarm(); + public AlarmChange TempChangeAlarm = new AlarmChange(); + public Alarm HighTempAlarm = new Alarm(); + public Alarm LowTempAlarm = new Alarm(); + public Alarm UpgradeAlarm = new Alarm(); + public Alarm HttpUploadAlarm = new Alarm(); + public Alarm MySqlUploadAlarm = new Alarm(); + + + private const double DEFAULTFCLOWPRESS = 950.0; + private const double DEFAULTFCHIGHPRESS = 1050.0; + + private const string ForumDefault = "https://cumulus.hosiene.co.uk/"; + + private const string WebcamDefault = ""; + + private const string DefaultSoundFile = "alarm.mp3"; + private const string DefaultSoundFileOld = "alert.wav"; + + public int RealtimeInterval; + + public string ForecastNotAvailable = "Not available"; + + public WebServer httpServer; + //public WebSocket websock; + + //private Thread httpThread; + + private static readonly HttpClientHandler WUhttpHandler = new HttpClientHandler(); + private readonly HttpClient WUhttpClient = new HttpClient(WUhttpHandler); + + private static readonly HttpClientHandler WindyhttpHandler = new HttpClientHandler(); + private readonly HttpClient WindyhttpClient = new HttpClient(WindyhttpHandler); + + private static readonly HttpClientHandler WindGuruhttpHandler = new HttpClientHandler(); + private readonly HttpClient WindGuruhttpClient = new HttpClient(WindGuruhttpHandler); + + private static readonly HttpClientHandler AwekashttpHandler = new HttpClientHandler(); + private readonly HttpClient AwekashttpClient = new HttpClient(AwekashttpHandler); + + private static readonly HttpClientHandler WCloudhttpHandler = new HttpClientHandler(); + private readonly HttpClient WCloudhttpClient = new HttpClient(WCloudhttpHandler); + + private static readonly HttpClientHandler PWShttpHandler = new HttpClientHandler(); + private readonly HttpClient PWShttpClient = new HttpClient(PWShttpHandler); + + private static readonly HttpClientHandler WOWhttpHandler = new HttpClientHandler(); + private readonly HttpClient WOWhttpClient = new HttpClient(WOWhttpHandler); + + // Custom HTTP - seconds + private static readonly HttpClientHandler customHttpSecondsHandler = new HttpClientHandler(); + private readonly HttpClient customHttpSecondsClient = new HttpClient(customHttpSecondsHandler); + private bool updatingCustomHttpSeconds; + private readonly TokenParser customHttpSecondsTokenParser = new TokenParser(); + internal Timer CustomHttpSecondsTimer; + internal bool CustomHttpSecondsEnabled; + internal string CustomHttpSecondsString; + internal int CustomHttpSecondsInterval; + + // Custom HTTP - minutes + private static readonly HttpClientHandler customHttpMinutesHandler = new HttpClientHandler(); + private readonly HttpClient customHttpMinutesClient = new HttpClient(customHttpMinutesHandler); + private bool updatingCustomHttpMinutes; + private readonly TokenParser customHttpMinutesTokenParser = new TokenParser(); + internal bool CustomHttpMinutesEnabled; + internal string CustomHttpMinutesString; + internal int CustomHttpMinutesInterval; + internal int CustomHttpMinutesIntervalIndex; + + // Custom HTTP - rollover + private static readonly HttpClientHandler customHttpRolloverHandler = new HttpClientHandler(); + private readonly HttpClient customHttpRolloverClient = new HttpClient(customHttpRolloverHandler); + private bool updatingCustomHttpRollover; + private readonly TokenParser customHttpRolloverTokenParser = new TokenParser(); + internal bool CustomHttpRolloverEnabled; + internal string CustomHttpRolloverString; + + public Thread ftpThread; + public Thread MySqlCatchupThread; + + public string xapHeartbeat; + public string xapsource; + + public MySqlConnection MonthlyMySqlConn = new MySqlConnection(); + public MySqlConnection RealtimeSqlConn = new MySqlConnection(); + public MySqlConnection CustomMysqlSecondsConn = new MySqlConnection(); + public MySqlCommand CustomMysqlSecondsCommand = new MySqlCommand(); + public MySqlConnection CustomMysqlMinutesConn = new MySqlConnection(); + public MySqlCommand CustomMysqlMinutesCommand = new MySqlCommand(); + public MySqlConnection CustomMysqlRolloverConn = new MySqlConnection(); + public MySqlCommand CustomMysqlRolloverCommand = new MySqlCommand(); + + public MySqlConnectionStringBuilder MySqlConnSettings = new MySqlConnectionStringBuilder(); + + public string LatestBuild = "n/a"; + + public bool RealtimeMySqlEnabled; + public bool MonthlyMySqlEnabled; + public bool DayfileMySqlEnabled; + + public bool RealtimeMySql1MinLimit; + private int RealtimeMySqlLastMinute = -1; + + public string MySqlMonthlyTable; + public string MySqlDayfileTable; + public string MySqlRealtimeTable; + public string MySqlRealtimeRetention; + public string StartOfMonthlyInsertSQL; + public string StartOfDayfileInsertSQL; + public string StartOfRealtimeInsertSQL; + + public string CreateMonthlySQL; + public string CreateDayfileSQL; + public string CreateRealtimeSQL; + + public string CustomMySqlSecondsCommandString; + public string CustomMySqlMinutesCommandString; + public string CustomMySqlRolloverCommandString; + + public bool CustomMySqlSecondsEnabled; + public bool CustomMySqlMinutesEnabled; + public bool CustomMySqlRolloverEnabled; + + public int CustomMySqlSecondsInterval; + public int CustomMySqlMinutesInterval; + public int CustomMySqlMinutesIntervalIndex; + + private bool customMySqlSecondsUpdateInProgress; + private bool customMySqlMinutesUpdateInProgress; + private bool customMySqlRolloverUpdateInProgress; + + public AirLinkData airLinkDataIn; + public AirLinkData airLinkDataOut; + + public string[] StationDesc = + { + "Davis Vantage Pro", "Davis Vantage Pro2", "Oregon Scientific WMR-928", "Oregon Scientific WM-918", "EasyWeather", "Fine Offset", + "LaCrosse WS2300", "Fine Offset with Solar", "Oregon Scientific WMR100", "Oregon Scientific WMR200", "Instromet", "Davis WLL", "GW1000" + }; + + public string[] APRSstationtype = { "DsVP", "DsVP", "WMR928", "WM918", "EW", "FO", "WS2300", "FOs", "WMR100", "WMR200", "Instromet", "DsVP", "Ecowitt" }; + + public string loggingfile; + + public Cumulus(int HTTPport, bool DebugEnabled, string startParms) + { + var fullVer = Assembly.GetExecutingAssembly().GetName().Version; + Version = $"{fullVer.Major}.{fullVer.Minor}.{fullVer.Build}"; + Build = Assembly.GetExecutingAssembly().GetName().Version.Revision.ToString(); + + DirectorySeparator = Path.DirectorySeparatorChar; + + AppDir = Directory.GetCurrentDirectory() + DirectorySeparator; + TwitterTxtFile = AppDir + "twitter.txt"; + WebTagFile = AppDir + "WebTags.txt"; + + //b3045>, use same port for WS... WS port = HTTPS port + //wsPort = WSport; + wsPort = HTTPport; + + DebuggingEnabled = DebugEnabled; + + // Set up the diagnostic tracing + loggingfile = GetLoggingFileName("MXdiags" + DirectorySeparator); + + Program.svcTextListener.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff ") + "Creating main MX log file - " + loggingfile); + Program.svcTextListener.Flush(); + + TextWriterTraceListener myTextListener = new TextWriterTraceListener(loggingfile, "MXlog"); + Trace.Listeners.Add(myTextListener); + Trace.AutoFlush = true; + + // Read the configuration file + + LogMessage(" ========================== Cumulus MX starting =========================="); + + LogMessage("Command line: " + Environment.CommandLine + " " + startParms); + + //Assembly thisAssembly = this.GetType().Assembly; + //Version = thisAssembly.GetName().Version.ToString(); + //VersionLabel.Content = "Cumulus v." + thisAssembly.GetName().Version; + LogMessage("Cumulus MX v." + Version + " build " + Build); + LogConsoleMessage("Cumulus MX v." + Version + " build " + Build); + LogConsoleMessage("Working Dir: " + AppDir); + + IsOSX = IsRunningOnMac(); + + Platform = IsOSX ? "Mac OS X" : Environment.OSVersion.Platform.ToString(); + + // Set the default comport name depending on platform + DefaultComportName = Platform.Substring(0, 3) == "Win" ? "COM1" : "/dev/ttyUSB0"; + + LogMessage("Platform: " + Platform); + + LogMessage("OS version: " + Environment.OSVersion); + + Type type = Type.GetType("Mono.Runtime"); + if (type != null) + { + MethodInfo displayName = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static); + if (displayName != null) + LogMessage("Mono version: "+displayName.Invoke(null, null)); + } + + // determine system uptime based on OS + if (Platform.Substring(0, 3) == "Win") + { + try + { + // Windows enable the performance counter method + UpTime = new PerformanceCounter("System", "System Up Time"); + } + catch (Exception e) + { + LogMessage("Error: Unable to acces the System Up Time performance counter. System up time will not be available"); + LogDebugMessage($"Error: {e}"); + } + } + + LogMessage("Current culture: " + CultureInfo.CurrentCulture.DisplayName); + ListSeparator = CultureInfo.CurrentCulture.TextInfo.ListSeparator; + + DecimalSeparator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; + + LogMessage("Directory separator=[" + DirectorySeparator + "] Decimal separator=[" + DecimalSeparator + "] List separator=[" + ListSeparator + "]"); + LogMessage("Date separator=[" + CultureInfo.CurrentCulture.DateTimeFormat.DateSeparator + "] Time separator=[" + CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator + "]"); + + TimeZone localZone = TimeZone.CurrentTimeZone; + DateTime now = DateTime.Now; + + LogMessage("Standard time zone name: " + localZone.StandardName); + LogMessage("Daylight saving time name: " + localZone.DaylightName); + LogMessage("Daylight saving time? " + localZone.IsDaylightSavingTime(now)); + + LogMessage(DateTime.Now.ToString("G")); + + // find the data folder + //datapath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + DirectorySeparator + "Cumulus" + DirectorySeparator; + + Datapath = "data" + DirectorySeparator; + backupPath = "backup" + DirectorySeparator; + ReportPath = "Reports" + DirectorySeparator; + var WebPath = "web" + DirectorySeparator; + + dbfile = Datapath + "cumulusmx.db"; + diaryfile = Datapath + "diary.db"; + + //AlltimeFile = Datapath + "alltime.rec"; + AlltimeIniFile = Datapath + "alltime.ini"; + Alltimelogfile = Datapath + "alltimelog.txt"; + MonthlyAlltimeIniFile = Datapath + "monthlyalltime.ini"; + MonthlyAlltimeLogFile = Datapath + "monthlyalltimelog.txt"; + logFilePath = Datapath; + DayFileName = Datapath + "dayfile.txt"; + YesterdayFile = Datapath + "yesterday.ini"; + TodayIniFile = Datapath + "today.ini"; + MonthIniFile = Datapath + "month.ini"; + YearIniFile = Datapath + "year.ini"; + //stringsFile = "strings.ini"; + + // Set the default upload intervals for web services + Wund.DefaultInterval = 15; + Windy.DefaultInterval = 15; + WindGuru.DefaultInterval = 1; + PWS.DefaultInterval = 15; + APRS.DefaultInterval = 9; + AWEKAS.DefaultInterval = 15 * 60; + WCloud.DefaultInterval = 10; + OpenWeatherMap.DefaultInterval = 15; + + StdWebFiles = new FileGenerationFtpOptions[2]; + StdWebFiles[0] = new FileGenerationFtpOptions() + { + TemplateFileName = WebPath + "websitedataT.json", + LocalPath = WebPath, + LocalFileName = "websitedata.json", + RemoteFileName = "websitedata.json" + }; + StdWebFiles[1] = new FileGenerationFtpOptions() + { + LocalPath = "", + LocalFileName = "wxnow.txt", + RemoteFileName = "wxnow.txt" + }; + + RealtimeFiles = new FileGenerationFtpOptions[2]; + RealtimeFiles[0] = new FileGenerationFtpOptions() + { + LocalFileName = "realtime.txt", + RemoteFileName = "realtime.txt" + }; + RealtimeFiles[1] = new FileGenerationFtpOptions() + { + TemplateFileName = WebPath + "realtimegaugesT.txt", + LocalPath = WebPath, + LocalFileName = "realtimegauges.txt", + RemoteFileName = "realtimegauges.txt" + }; + + GraphDataFiles = new FileGenerationFtpOptions[13]; + GraphDataFiles[0] = new FileGenerationFtpOptions() + { + LocalPath = WebPath, + LocalFileName = "graphconfig.json", + RemoteFileName = "graphconfig.json" + }; + GraphDataFiles[1] = new FileGenerationFtpOptions() + { + LocalPath = WebPath, + LocalFileName = "availabledata.json", + RemoteFileName = "availabledata.json" + }; + GraphDataFiles[2] = new FileGenerationFtpOptions() + { + LocalPath = WebPath, + LocalFileName = "tempdata.json", + RemoteFileName ="tempdata.json" + }; + GraphDataFiles[3] = new FileGenerationFtpOptions() + { + LocalPath = WebPath, + LocalFileName = "pressdata.json", + RemoteFileName = "pressdata.json" + }; + GraphDataFiles[4] = new FileGenerationFtpOptions() + { + LocalPath = WebPath, + LocalFileName = "winddata.json", + RemoteFileName = "winddata.json" + }; + GraphDataFiles[5] = new FileGenerationFtpOptions() + { + LocalPath = WebPath, + LocalFileName = "wdirdata.json", + RemoteFileName = "wdirdata.json" + }; + GraphDataFiles[6] = new FileGenerationFtpOptions() + { + LocalPath = WebPath, + LocalFileName = "humdata.json", + RemoteFileName = "humdata.json" + }; + GraphDataFiles[7] = new FileGenerationFtpOptions() + { + LocalPath = WebPath, + LocalFileName = "raindata.json", + RemoteFileName = "raindata.json" + }; + GraphDataFiles[8] = new FileGenerationFtpOptions() + { + LocalPath = WebPath, + LocalFileName = "dailyrain.json", + RemoteFileName = "dailyrain.json" + }; + GraphDataFiles[9] = new FileGenerationFtpOptions() + { + LocalPath = WebPath, + LocalFileName = "dailytemp.json", + RemoteFileName = "dailytemp.json" + }; + GraphDataFiles[10] = new FileGenerationFtpOptions() + { + LocalPath = WebPath, + LocalFileName = "solardata.json", + RemoteFileName = "solardata.json" + }; + GraphDataFiles[11] = new FileGenerationFtpOptions() + { + LocalPath = WebPath, + LocalFileName = "sunhours.json", + RemoteFileName = "sunhours.json" + }; + GraphDataFiles[12] = new FileGenerationFtpOptions() + { + LocalPath = WebPath, + LocalFileName = "airquality.json", + RemoteFileName = "airquality.json" + }; + + GraphDataEodFiles = new FileGenerationFtpOptions[8]; + GraphDataEodFiles[0] = new FileGenerationFtpOptions() + { + LocalPath = WebPath, + LocalFileName = "alldailytempdata.json", + RemoteFileName = "alldailytempdata.json" + }; + GraphDataEodFiles[1] = new FileGenerationFtpOptions() + { + LocalPath = WebPath, + LocalFileName = "alldailypressdata.json", + RemoteFileName = "alldailypressdata.json" + }; + GraphDataEodFiles[2] = new FileGenerationFtpOptions() + { + LocalPath = WebPath, + LocalFileName = "alldailywinddata.json", + RemoteFileName = "alldailywinddata.json" + }; + GraphDataEodFiles[3] = new FileGenerationFtpOptions() + { + LocalPath = WebPath, + LocalFileName = "alldailyhumdata.json", + RemoteFileName = "alldailyhumdata.json" + }; + GraphDataEodFiles[4] = new FileGenerationFtpOptions() + { + LocalPath = WebPath, + LocalFileName = "alldailyraindata.json", + RemoteFileName = "alldailyraindata.json" + }; + GraphDataEodFiles[5] = new FileGenerationFtpOptions() + { + LocalPath = WebPath, + LocalFileName = "alldailysolardata.json", + RemoteFileName = "alldailysolardata.json" + }; + GraphDataEodFiles[6] = new FileGenerationFtpOptions() + { + LocalPath = WebPath, + LocalFileName = "alldailydegdaydata.json", + RemoteFileName = "alldailydegdaydata.json" + }; + GraphDataEodFiles[7] = new FileGenerationFtpOptions() + { + LocalPath = WebPath, + LocalFileName = "alltempsumdata.json", + RemoteFileName = "alltempsumdata.json" + }; + + ReadIniFile(); + + // Do we prevent more than one copy of CumulusMX running? + if (ProgramOptions.WarnMultiple && !Program.appMutex.WaitOne(0, false)) + { + LogConsoleMessage("Cumulus is already running - terminating"); + LogConsoleMessage("Program exit"); + LogMessage("Cumulus is already running - terminating"); + LogMessage("Program exit"); + Environment.Exit(1); + } + + // Do we wait for a ping response from a remote host before starting? + if (!string.IsNullOrWhiteSpace(ProgramOptions.StartupPingHost)) + { + var msg1 = $"Waiting for PING reply from {ProgramOptions.StartupPingHost}"; + var msg2 = $"Received PING response from {ProgramOptions.StartupPingHost}, continuing..."; + var msg3 = $"No PING response received in {ProgramOptions.StartupPingEscapeTime} minutes, continuing anyway"; + LogConsoleMessage(msg1); + LogMessage(msg1); + using (var ping = new Ping()) + { + var endTime = DateTime.Now.AddMinutes(ProgramOptions.StartupPingEscapeTime); + PingReply reply = null; + + do + { + try + { + + reply = ping.Send(ProgramOptions.StartupPingHost, 2000); // 2 second timeout + LogMessage($"PING response = {reply.Status}"); + } + catch (Exception e) + { + LogErrorMessage($"PING to {ProgramOptions.StartupPingHost} failed with error: {e.InnerException.Message}"); + } + + if (reply == null || reply.Status != IPStatus.Success) + { + // no response wait 10 seconds before trying again + Thread.Sleep(10000); + // Force a DNS refresh if not an IPv4 address + if (!Utils.ValidateIPv4(ProgramOptions.StartupPingHost)) + { + // catch and ignore IPv6 and invalid hostname for now + try + { + Dns.GetHostEntry(ProgramOptions.StartupPingHost); + } + catch (Exception ex) + { + LogMessage($"PING: Error with DNS refresh - {ex.Message}"); + } + } + } + } while ((reply == null || reply.Status != IPStatus.Success) && DateTime.Now < endTime); + + if (DateTime.Now >= endTime) + { + LogConsoleMessage(msg3); + LogMessage(msg3); + } + else + { + LogConsoleMessage(msg2); + LogMessage(msg2); + } + } + } + else + { + LogMessage("No start-up PING"); + } + + // Do we delay the start of Cumulus MX for a fixed period? + if (ProgramOptions.StartupDelaySecs > 0) + { + // Check uptime + double ts = 0; + if (Platform.Substring(0, 3) == "Win" && UpTime != null) + { + UpTime.NextValue(); + ts = UpTime.NextValue(); + } + else if (File.Exists(@"/proc/uptime")) + { + var text = File.ReadAllText(@"/proc/uptime"); + var strTime = text.Split(' ')[0]; + double.TryParse(strTime, out ts); + } + + // Only delay if the delay uptime is undefined (0), or the current uptime is less than the user specified max uptime to apply the delay + LogMessage($"System uptime = {(int)ts} secs"); + if (ProgramOptions.StartupDelayMaxUptime == 0 || ProgramOptions.StartupDelayMaxUptime > ts) + { + var msg1 = $"Delaying start for {ProgramOptions.StartupDelaySecs} seconds"; + var msg2 = $"Start-up delay complete, continuing..."; + LogConsoleMessage(msg1); + LogMessage(msg1); + Thread.Sleep(ProgramOptions.StartupDelaySecs * 1000); + LogConsoleMessage(msg2); + LogMessage(msg2); + } + else + { + LogMessage("No start-up delay, max uptime exceeded"); + } + } + else + { + LogMessage("No start-up delay - disabled"); + } + + GC.Collect(); + + LogMessage("Data path = " + Datapath); + + AppDomain.CurrentDomain.SetData("DataDirectory", Datapath); + + // Open database (create file if it doesn't exist) + SQLiteOpenFlags flags = SQLiteOpenFlags.Create | SQLiteOpenFlags.ReadWrite; + //LogDB = new SQLiteConnection(dbfile, flags) + //LogDB.CreateTable(); + + // Open diary database (create file if it doesn't exist) + //DiaryDB = new SQLiteConnection(diaryfile, flags, true); // We should be using this - storing datetime as ticks, but historically string storage has been used, so we are stuck with it? + DiaryDB = new SQLiteConnection(diaryfile, flags); + DiaryDB.CreateTable(); + + BackupData(false, DateTime.Now); + + LogMessage("Debug logging is " + (ProgramOptions.DebugLogging ? "enabled" : "disabled")); + LogMessage("Data logging is " + (ProgramOptions.DataLogging ? "enabled" : "disabled")); + LogMessage("FTP logging is " + (FTPlogging ? "enabled" : "disabled")); + LogMessage("Email logging is " + (SmtpOptions.Logging ? "enabled" : "disabled")); + LogMessage("Spike logging is " + (ErrorLogSpikeRemoval ? "enabled" : "disabled")); + LogMessage("Logging interval = " + logints[DataLogInterval] + " mins"); + LogMessage("Real time interval = " + RealtimeInterval / 1000 + " secs"); + LogMessage("NoSensorCheck = " + (StationOptions.NoSensorCheck ? "1" : "0")); + + TempFormat = "F" + TempDPlaces; + WindFormat = "F" + WindDPlaces; + WindAvgFormat = "F" + WindAvgDPlaces; + RainFormat = "F" + RainDPlaces; + PressFormat = "F" + PressDPlaces; + HumFormat = "F" + HumDPlaces; + UVFormat = "F" + UVDPlaces; + SunFormat = "F" + SunshineDPlaces; + ETFormat = "F" + (RainDPlaces + 1); + WindRunFormat = "F" + WindRunDPlaces; + TempTrendFormat = "+0.0;-0.0;0"; + AirQualityFormat = "F" + AirQualityDPlaces; + + SetMonthlySqlCreateString(); + + SetDayfileSqlCreateString(); + + SetRealtimeSqlCreateString(); + + if (Sslftp == FtpProtocols.FTP || Sslftp == FtpProtocols.FTPS) + { + if (ActiveFTPMode) + { + RealtimeFTP.DataConnectionType = FtpDataConnectionType.PORT; + } + else if (DisableFtpsEPSV) + { + RealtimeFTP.DataConnectionType = FtpDataConnectionType.PASV; + } + + if (Sslftp == FtpProtocols.FTPS) + { + RealtimeFTP.EncryptionMode = DisableFtpsExplicit ? FtpEncryptionMode.Implicit : FtpEncryptionMode.Explicit; + RealtimeFTP.DataConnectionEncryption = true; + RealtimeFTP.ValidateAnyCertificate = true; + // b3045 - switch from System.Net.Ftp.Client to FluentFTP allows us to specifiy protocols + RealtimeFTP.SslProtocols = SslProtocols.Default | SslProtocols.Tls11 | SslProtocols.Tls12; + } + } + + ReadStringsFile(); + + SetUpHttpProxy(); + + if (MonthlyMySqlEnabled) + { + MonthlyMySqlConn.ConnectionString = MySqlConnSettings.ToString(); + + SetStartOfMonthlyInsertSQL(); + } + + if (DayfileMySqlEnabled) + { + SetStartOfDayfileInsertSQL(); + } + + if (RealtimeMySqlEnabled) + { + RealtimeSqlConn.ConnectionString = MySqlConnSettings.ToString(); + SetStartOfRealtimeInsertSQL(); + } + + + CustomMysqlSecondsConn.ConnectionString = MySqlConnSettings.ToString(); + customMysqlSecondsTokenParser.OnToken += TokenParserOnToken; + CustomMysqlSecondsCommand.Connection = CustomMysqlSecondsConn; + CustomMysqlSecondsTimer = new Timer { Interval = CustomMySqlSecondsInterval * 1000 }; + CustomMysqlSecondsTimer.Elapsed += CustomMysqlSecondsTimerTick; + CustomMysqlSecondsTimer.AutoReset = true; + + CustomMysqlMinutesConn.ConnectionString = MySqlConnSettings.ToString(); + customMysqlMinutesTokenParser.OnToken += TokenParserOnToken; + CustomMysqlMinutesCommand.Connection = CustomMysqlMinutesConn; + + CustomMysqlRolloverConn.ConnectionString = MySqlConnSettings.ToString(); + customMysqlRolloverTokenParser.OnToken += TokenParserOnToken; + CustomMysqlRolloverCommand.Connection = CustomMysqlRolloverConn; + + CustomHttpSecondsTimer = new Timer { Interval = CustomHttpSecondsInterval * 1000 }; + CustomHttpSecondsTimer.Elapsed += CustomHttpSecondsTimerTick; + CustomHttpSecondsTimer.AutoReset = true; + + customHttpSecondsTokenParser.OnToken += TokenParserOnToken; + customHttpMinutesTokenParser.OnToken += TokenParserOnToken; + customHttpRolloverTokenParser.OnToken += TokenParserOnToken; + + if (SmtpOptions.Enabled) + { + emailer = new EmailSender(this); + } + + DoSunriseAndSunset(); + DoMoonPhase(); + MoonAge = MoonriseMoonset.MoonAge(); + DoMoonImage(); + + LogMessage("Station type: " + (StationType == -1 ? "Undefined" : StationDesc[StationType])); + + SetupUnitText(); + + LogMessage($"Units.Wind={Units.WindText} RainUnit={Units.RainText} TempUnit={Units.TempText} PressureUnit={Units.PressText}"); + LogMessage($"YTDRain={YTDrain:F3} Year={YTDrainyear}"); + LogMessage($"RainDayThreshold={RainDayThreshold:F3}"); + LogMessage($"Roll over hour={RolloverHour}"); + + LogOffsetsMultipliers(); + + LogPrimaryAqSensor(); + + // initialise the alarms + DataStoppedAlarm.cumulus = this; + BatteryLowAlarm.cumulus = this; + SensorAlarm.cumulus = this; + SpikeAlarm.cumulus = this; + HighWindAlarm.cumulus = this; + HighWindAlarm.Units = Units.WindText; + HighGustAlarm.cumulus = this; + HighGustAlarm.Units = Units.WindText; + HighRainRateAlarm.cumulus = this; + HighRainRateAlarm.Units = Units.RainTrendText; + HighRainTodayAlarm.cumulus = this; + HighRainTodayAlarm.Units = Units.RainText; + PressChangeAlarm.cumulus = this; + PressChangeAlarm.Units = Units.PressTrendText; + HighPressAlarm.cumulus = this; + HighPressAlarm.Units = Units.PressText; + LowPressAlarm.cumulus = this; + LowPressAlarm.Units = Units.PressText; + TempChangeAlarm.cumulus = this; + TempChangeAlarm.Units = Units.TempTrendText; + HighTempAlarm.cumulus = this; + HighTempAlarm.Units = Units.TempText; + LowTempAlarm.cumulus = this; + LowTempAlarm.Units = Units.TempText; + UpgradeAlarm.cumulus = this; + HttpUploadAlarm.cumulus = this; + MySqlUploadAlarm.cumulus = this; + + GetLatestVersion(); + + LogMessage("Cumulus Starting"); + + // switch off logging from Unosquare.Swan which underlies embedIO + Unosquare.Swan.Terminal.Settings.DisplayLoggingMessageType = Unosquare.Swan.LogMessageType.Fatal; + + httpServer = new WebServer(HTTPport, RoutingStrategy.Wildcard); + + var assemblyPath = Path.GetDirectoryName(typeof(Program).Assembly.Location); + var htmlRootPath = Path.Combine(assemblyPath, "interface"); + + LogMessage("HTML root path = " + htmlRootPath); + + httpServer.RegisterModule(new StaticFilesModule(htmlRootPath, new Dictionary() { { "Cache-Control", "max-age=300" } })); + httpServer.Module().UseRamCache = true; + + // Set up the API web server + // Some APi functions require the station, so set them after station initialisation + Api.Setup(httpServer); + Api.programSettings = new ProgramSettings(this); + Api.stationSettings = new StationSettings(this); + Api.internetSettings = new InternetSettings(this); + Api.thirdpartySettings = new ThirdPartySettings(this); + Api.extraSensorSettings = new ExtraSensorSettings(this); + Api.calibrationSettings = new CalibrationSettings(this); + Api.noaaSettings = new NOAASettings(this); + Api.alarmSettings = new AlarmSettings(this); + Api.mySqlSettings = new MysqlSettings(this); + Api.dataEditor = new DataEditor(this); + Api.tagProcessor = new ApiTagProcessor(this); + + // Set up the Web Socket server + WebSocket.Setup(httpServer, this); + + httpServer.RunAsync(); + + LogConsoleMessage("Cumulus running at: " + httpServer.Listener.Prefixes.First()); + LogConsoleMessage(" (Replace * with any IP address on this machine, or localhost)"); + LogConsoleMessage(" Open the admin interface by entering this URL in a browser."); + + LogDebugMessage("Lock: Cumulus waiting for the lock"); + syncInit.Wait(); + LogDebugMessage("Lock: Cumulus has lock"); + + LogMessage("Opening station"); + + switch (StationType) + { + case StationTypes.FineOffset: + case StationTypes.FineOffsetSolar: + Manufacturer = EW; + station = new FOStation(this); + break; + case StationTypes.VantagePro: + case StationTypes.VantagePro2: + Manufacturer = DAVIS; + station = new DavisStation(this); + break; + case StationTypes.WMR928: + Manufacturer = OREGON; + station = new WMR928Station(this); + break; + case StationTypes.WM918: + Manufacturer = OREGON; + station = new WM918Station(this); + break; + case StationTypes.WS2300: + Manufacturer = LACROSSE; + station = new WS2300Station(this); + break; + case StationTypes.WMR200: + Manufacturer = OREGONUSB; + station = new WMR200Station(this); + break; + case StationTypes.Instromet: + Manufacturer = INSTROMET; + station = new ImetStation(this); + break; + case StationTypes.WMR100: + Manufacturer = OREGONUSB; + station = new WMR100Station(this); + break; + case StationTypes.EasyWeather: + Manufacturer = EW; + station = new EasyWeather(this); + station.LoadLastHoursFromDataLogs(DateTime.Now); + break; + case StationTypes.WLL: + Manufacturer = DAVIS; + station = new DavisWllStation(this); + break; + case StationTypes.GW1000: + Manufacturer = ECOWITT; + station = new GW1000Station(this); + break; + default: + LogConsoleMessage("Station type not set"); + LogMessage("Station type not set"); + break; + } + + if (station != null) + { + Api.Station = station; + Api.stationSettings.SetStation(station); + Api.dataEditor.SetStation(station); + + LogMessage("Creating extra sensors"); + if (AirLinkInEnabled) + { + airLinkDataIn = new AirLinkData(); + airLinkIn = new DavisAirLink(this, true, station); + } + if (AirLinkOutEnabled) + { + airLinkDataOut = new AirLinkData(); + airLinkOut = new DavisAirLink(this, false, station); + } + + webtags = new WebTags(this, station); + webtags.InitialiseWebtags(); + + Api.dataEditor.SetWebTags(webtags); + Api.tagProcessor.SetWebTags(webtags); + + tokenParser = new TokenParser(); + tokenParser.OnToken += TokenParserOnToken; + + realtimeTokenParser = new TokenParser(); + realtimeTokenParser.OnToken += TokenParserOnToken; + + RealtimeTimer.Interval = RealtimeInterval; + RealtimeTimer.Elapsed += RealtimeTimerTick; + RealtimeTimer.AutoReset = true; + + SetFtpLogging(FTPlogging); + + WundTimer.Elapsed += WundTimerTick; + AwekasTimer.Elapsed += AwekasTimerTick; + WebTimer.Elapsed += WebTimerTick; + + xapsource = "sanday.cumulus." + Environment.MachineName; + + xapHeartbeat = "xap-hbeat\n{\nv=12\nhop=1\nuid=FF" + xapUID + "00\nclass=xap-hbeat.alive\nsource=" + xapsource + "\ninterval=60\n}"; + + if (xapEnabled) + { + Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + IPEndPoint iep1 = new IPEndPoint(IPAddress.Broadcast, xapPort); + + byte[] data = Encoding.ASCII.GetBytes(xapHeartbeat); + + sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1); + sock.SendTo(data, iep1); + sock.Close(); + } + + if (MQTT.EnableDataUpdate || MQTT.EnableInterval) + { + MqttPublisher.Setup(this); + + if (MQTT.EnableInterval) + { + MQTTTimer.Elapsed += MQTTTimerTick; + } + } + + InitialiseRG11(); + + if (station.timerStartNeeded) + { + StartTimersAndSensors(); + } + + if ((StationType == StationTypes.WMR100) || (StationType == StationTypes.EasyWeather) || (Manufacturer == OREGON)) + { + station.StartLoop(); + } + + // If enabled generate the daily graph data files, and upload at first opportunity + LogDebugMessage("Generating the daily graph data files"); + station.CreateEodGraphDataFiles(); + } + + LogDebugMessage("Lock: Cumulus releasing the lock"); + syncInit.Release(); + } + + internal void SetUpHttpProxy() + { + if (!string.IsNullOrEmpty(HTTPProxyName)) + { + WUhttpHandler.Proxy = new WebProxy(HTTPProxyName, HTTPProxyPort); + WUhttpHandler.UseProxy = true; + + PWShttpHandler.Proxy = new WebProxy(HTTPProxyName, HTTPProxyPort); + PWShttpHandler.UseProxy = true; + + WOWhttpHandler.Proxy = new WebProxy(HTTPProxyName, HTTPProxyPort); + WOWhttpHandler.UseProxy = true; + + AwekashttpHandler.Proxy = new WebProxy(HTTPProxyName, HTTPProxyPort); + AwekashttpHandler.UseProxy = true; + + WindyhttpHandler.Proxy = new WebProxy(HTTPProxyName, HTTPProxyPort); + WindyhttpHandler.UseProxy = true; + + WCloudhttpHandler.Proxy = new WebProxy(HTTPProxyName, HTTPProxyPort); + WCloudhttpHandler.UseProxy = true; + + customHttpSecondsHandler.Proxy = new WebProxy(HTTPProxyName, HTTPProxyPort); + customHttpSecondsHandler.UseProxy = true; + + customHttpMinutesHandler.Proxy = new WebProxy(HTTPProxyName, HTTPProxyPort); + customHttpMinutesHandler.UseProxy = true; + + customHttpRolloverHandler.Proxy = new WebProxy(HTTPProxyName, HTTPProxyPort); + customHttpRolloverHandler.UseProxy = true; + + if (!string.IsNullOrEmpty(HTTPProxyUser)) + { + WUhttpHandler.Credentials = new NetworkCredential(HTTPProxyUser, HTTPProxyPassword); + PWShttpHandler.Credentials = new NetworkCredential(HTTPProxyUser, HTTPProxyPassword); + WOWhttpHandler.Credentials = new NetworkCredential(HTTPProxyUser, HTTPProxyPassword); + AwekashttpHandler.Credentials = new NetworkCredential(HTTPProxyUser, HTTPProxyPassword); + WindyhttpHandler.Credentials = new NetworkCredential(HTTPProxyUser, HTTPProxyPassword); + WCloudhttpHandler.Credentials = new NetworkCredential(HTTPProxyUser, HTTPProxyPassword); + customHttpSecondsHandler.Credentials = new NetworkCredential(HTTPProxyUser, HTTPProxyPassword); + customHttpMinutesHandler.Credentials = new NetworkCredential(HTTPProxyUser, HTTPProxyPassword); + customHttpRolloverHandler.Credentials = new NetworkCredential(HTTPProxyUser, HTTPProxyPassword); + } + } + } + + private void CustomHttpSecondsTimerTick(object sender, ElapsedEventArgs e) + { + if (!station.DataStopped) + CustomHttpSecondsUpdate(); + } + + internal void SetStartOfRealtimeInsertSQL() + { + StartOfRealtimeInsertSQL = "INSERT IGNORE INTO " + MySqlRealtimeTable + " (" + + "LogDateTime,temp,hum,dew,wspeed,wlatest,bearing,rrate,rfall,press," + + "currentwdir,beaufortnumber,windunit,tempunitnodeg,pressunit,rainunit," + + "windrun,presstrendval,rmonth,ryear,rfallY,intemp,inhum,wchill,temptrend," + + "tempTH,TtempTH,tempTL,TtempTL,windTM,TwindTM,wgustTM,TwgustTM," + + "pressTH,TpressTH,pressTL,TpressTL,version,build,wgust,heatindex,humidex," + + "UV,ET,SolarRad,avgbearing,rhour,forecastnumber,isdaylight,SensorContactLost," + + "wdir,cloudbasevalue,cloudbaseunit,apptemp,SunshineHours,CurrentSolarMax,IsSunny," + + "FeelsLike)"; + } + + internal void SetRealtimeSqlCreateString() + { + CreateRealtimeSQL = "CREATE TABLE " + MySqlRealtimeTable + " (LogDateTime DATETIME NOT NULL," + + "temp decimal(4," + TempDPlaces + ") NOT NULL," + + "hum decimal(4," + HumDPlaces + ") NOT NULL," + + "dew decimal(4," + TempDPlaces + ") NOT NULL," + + "wspeed decimal(4," + WindDPlaces + ") NOT NULL," + + "wlatest decimal(4," + WindDPlaces + ") NOT NULL," + + "bearing VARCHAR(3) NOT NULL," + + "rrate decimal(4," + RainDPlaces + ") NOT NULL," + + "rfall decimal(4," + RainDPlaces + ") NOT NULL," + + "press decimal(6," + PressDPlaces + ") NOT NULL," + + "currentwdir varchar(3) NOT NULL," + + "beaufortnumber varchar(2) NOT NULL," + + "windunit varchar(4) NOT NULL," + + "tempunitnodeg varchar(1) NOT NULL," + + "pressunit varchar(3) NOT NULL," + + "rainunit varchar(2) NOT NULL," + + "windrun decimal(4," + WindRunDPlaces + ") NOT NULL," + + "presstrendval varchar(6) NOT NULL," + + "rmonth decimal(4," + RainDPlaces + ") NOT NULL," + + "ryear decimal(4," + RainDPlaces + ") NOT NULL," + + "rfallY decimal(4," + RainDPlaces + ") NOT NULL," + + "intemp decimal(4," + TempDPlaces + ") NOT NULL," + + "inhum decimal(4," + HumDPlaces + ") NOT NULL," + + "wchill decimal(4," + TempDPlaces + ") NOT NULL," + + "temptrend varchar(5) NOT NULL," + + "tempTH decimal(4," + TempDPlaces + ") NOT NULL," + + "TtempTH varchar(5) NOT NULL," + + "tempTL decimal(4," + TempDPlaces + ") NOT NULL," + + "TtempTL varchar(5) NOT NULL," + + "windTM decimal(4," + WindDPlaces + ") NOT NULL," + + "TwindTM varchar(5) NOT NULL," + + "wgustTM decimal(4," + WindDPlaces + ") NOT NULL," + + "TwgustTM varchar(5) NOT NULL," + + "pressTH decimal(6," + PressDPlaces + ") NOT NULL," + + "TpressTH varchar(5) NOT NULL," + + "pressTL decimal(6," + PressDPlaces + ") NOT NULL," + + "TpressTL varchar(5) NOT NULL," + + "version varchar(8) NOT NULL," + + "build varchar(5) NOT NULL," + + "wgust decimal(4," + WindDPlaces + ") NOT NULL," + + "heatindex decimal(4," + TempDPlaces + ") NOT NULL," + + "humidex decimal(4," + TempDPlaces + ") NOT NULL," + + "UV decimal(3," + UVDPlaces + ") NOT NULL," + + "ET decimal(4," + RainDPlaces + ") NOT NULL," + + "SolarRad decimal(5,1) NOT NULL," + + "avgbearing varchar(3) NOT NULL," + + "rhour decimal(4," + RainDPlaces + ") NOT NULL," + + "forecastnumber varchar(2) NOT NULL," + + "isdaylight varchar(1) NOT NULL," + + "SensorContactLost varchar(1) NOT NULL," + + "wdir varchar(3) NOT NULL," + + "cloudbasevalue varchar(5) NOT NULL," + + "cloudbaseunit varchar(2) NOT NULL," + + "apptemp decimal(4," + TempDPlaces + ") NOT NULL," + + "SunshineHours decimal(3," + SunshineDPlaces + ") NOT NULL," + + "CurrentSolarMax decimal(5,1) NOT NULL," + + "IsSunny varchar(1) NOT NULL," + + "FeelsLike decimal(4," + TempDPlaces + ") NOT NULL," + + "PRIMARY KEY (LogDateTime)) COMMENT = \"Realtime log\""; + } + + internal void SetStartOfDayfileInsertSQL() + { + StartOfDayfileInsertSQL = "INSERT IGNORE INTO " + MySqlDayfileTable + " (" + + "LogDate,HighWindGust,HWindGBear,THWindG,MinTemp,TMinTemp,MaxTemp,TMaxTemp," + + "MinPress,TMinPress,MaxPress,TMaxPress,MaxRainRate,TMaxRR,TotRainFall,AvgTemp," + + "TotWindRun,HighAvgWSpeed,THAvgWSpeed,LowHum,TLowHum,HighHum,THighHum,TotalEvap," + + "HoursSun,HighHeatInd,THighHeatInd,HighAppTemp,THighAppTemp,LowAppTemp,TLowAppTemp," + + "HighHourRain,THighHourRain,LowWindChill,TLowWindChill,HighDewPoint,THighDewPoint," + + "LowDewPoint,TLowDewPoint,DomWindDir,HeatDegDays,CoolDegDays,HighSolarRad," + + "THighSolarRad,HighUV,THighUV,HWindGBearSym,DomWindDirSym," + + "MaxFeelsLike,TMaxFeelsLike,MinFeelsLike,TMinFeelsLike,MaxHumidex,TMaxHumidex)"; + } + + internal void SetStartOfMonthlyInsertSQL() + { + StartOfMonthlyInsertSQL = "INSERT IGNORE INTO " + MySqlMonthlyTable + " (" + + "LogDateTime,Temp,Humidity,Dewpoint,Windspeed,Windgust,Windbearing,RainRate,TodayRainSoFar," + + "Pressure,Raincounter,InsideTemp,InsideHumidity,LatestWindGust,WindChill,HeatIndex,UVindex," + + "SolarRad,Evapotrans,AnnualEvapTran,ApparentTemp,MaxSolarRad,HrsSunShine,CurrWindBearing," + + "RG11rain,RainSinceMidnight,WindbearingSym,CurrWindBearingSym,FeelsLike,Humidex)"; + } + + internal void SetupUnitText() + { + switch (Units.Temp) + { + case 0: + Units.TempText = "°C"; + Units.TempTrendText = "°C/hr"; + break; + case 1: + Units.TempText = "°F"; + Units.TempTrendText = "°F/hr"; + break; + } + + switch (Units.Rain) + { + case 0: + Units.RainText = "mm"; + Units.RainTrendText = "mm/hr"; + break; + case 1: + Units.RainText = "in"; + Units.RainTrendText = "in/hr"; + break; + } + + switch (Units.Press) + { + case 0: + Units.PressText = "mb"; + Units.PressTrendText = "mb/hr"; + break; + case 1: + Units.PressText = "hPa"; + Units.PressTrendText = "hPa/hr"; + break; + case 2: + Units.PressText = "in"; + Units.PressTrendText = "in/hr"; + break; + } + + switch (Units.Wind) + { + case 0: + Units.WindText = "m/s"; + Units.WindRunText = "km"; + break; + case 1: + Units.WindText = "mph"; + Units.WindRunText = "miles"; + break; + case 2: + Units.WindText = "km/h"; + Units.WindRunText = "km"; + break; + case 3: + Units.WindText = "kts"; + Units.WindRunText = "nm"; + break; + } + } + + // If the temperature units are changed, reset NOAA thresholds to defaults + internal void ChangeTempUnits() + { + SetupUnitText(); + + NOAAheatingthreshold = Units.Temp == 0 ? 18.3 : 65; + NOAAcoolingthreshold = Units.Temp == 0 ? 18.3 : 65; + NOAAmaxtempcomp1 = Units.Temp == 0 ? 27 : 80; + NOAAmaxtempcomp2 = Units.Temp == 0 ? 0 : 32; + NOAAmintempcomp1 = Units.Temp == 0 ? 0 : 32; + NOAAmintempcomp2 = Units.Temp == 0 ? -18 : 0; + + ChillHourThreshold = Units.Temp == 0 ? 7 : 45; + + GrowingBase1 = Units.Temp == 0 ? 5.0 : 40.0; + GrowingBase2 = Units.Temp == 0 ? 10.0 : 50.0; + + TempChangeAlarm.Units = Units.TempTrendText; + HighTempAlarm.Units = Units.TempText; + LowTempAlarm.Units = Units.TempText; + } + + internal void ChangeRainUnits() + { + SetupUnitText(); + + NOAAraincomp1 = Units.Rain == 0 ? 0.2 : 0.01; + NOAAraincomp2 = Units.Rain == 0 ? 2 : 0.1; + NOAAraincomp3 = Units.Rain == 0 ? 20 : 1; + + HighRainRateAlarm.Units = Units.RainTrendText; + HighRainTodayAlarm.Units = Units.RainText; + } + + internal void ChangePressureUnits() + { + SetupUnitText(); + + FCPressureThreshold = Units.Press == 2 ? 0.00295333727 : 0.1; + + PressChangeAlarm.Units = Units.PressTrendText; + HighPressAlarm.Units = Units.PressText; + LowPressAlarm.Units = Units.PressText; + } + + internal void ChangeWindUnits() + { + SetupUnitText(); + + HighWindAlarm.Units = Units.WindText; + HighGustAlarm.Units = Units.WindText; + } + + public void SetFtpLogging(bool isSet) + { + try + { + FtpTrace.RemoveListener(FtpTraceListener); + } + catch + { + // ignored + } + + if (isSet) + { + FtpTrace.AddListener(FtpTraceListener); + FtpTrace.FlushOnWrite = true; + } + } + + + /* + private string LocalIPAddress() + { + IPHostEntry host; + string localIP = ""; + host = Dns.GetHostEntry(Dns.GetHostName()); + foreach (IPAddress ip in host.AddressList) + { + if (ip.AddressFamily == AddressFamily.InterNetwork) + { + localIP = ip.ToString(); + break; + } + } + return localIP; + } + */ + + /* + private void OnDisconnect(UserContext context) + { + LogDebugMessage("Disconnect From : " + context.ClientAddress.ToString()); + + foreach (var conn in WSconnections.ToList()) + { + if (context.ClientAddress.ToString().Equals(conn.ClientAddress.ToString())) + { + WSconnections.Remove(conn); + } + } + } + + private void OnConnected(UserContext context) + { + LogDebugMessage("Connected From : " + context.ClientAddress.ToString()); + } + + private void OnConnect(UserContext context) + { + LogDebugMessage("OnConnect From : " + context.ClientAddress.ToString()); + WSconnections.Add(context); + } + + internal List WSconnections = new List(); + + private void OnSend(UserContext context) + { + LogDebugMessage("OnSend From : " + context.ClientAddress.ToString()); + } + + private void OnReceive(UserContext context) + { + LogDebugMessage("WS receive : " + context.DataFrame.ToString()); + } +*/ + private void InitialiseRG11() + { + if (RG11Enabled && RG11Port.Length > 0) + { + cmprtRG11 = new SerialPort(RG11Port, 9600, Parity.None, 8, StopBits.One) { Handshake = Handshake.None, RtsEnable = true, DtrEnable = true }; + + cmprtRG11.PinChanged += RG11StateChange; + } + + if (RG11Enabled2 && RG11Port2.Length > 0 && (!RG11Port2.Equals(RG11Port))) + { + // a second RG11 is in use, using a different com port + cmprt2RG11 = new SerialPort(RG11Port2, 9600, Parity.None, 8, StopBits.One) { Handshake = Handshake.None, RtsEnable = true, DtrEnable = true }; + + cmprt2RG11.PinChanged += RG11StateChange; + } + } + + private void RG11StateChange(object sender, SerialPinChangedEventArgs e) + { + bool isDSR = e.EventType == SerialPinChange.DsrChanged; + bool isCTS = e.EventType == SerialPinChange.CtsChanged; + + // Is this a trigger that the first RG11 is configured for? + bool isDevice1 = (((SerialPort)sender).PortName == RG11Port) && ((isDSR && RG11DTRmode) || (isCTS && !RG11DTRmode)); + // Is this a trigger that the second RG11 is configured for? + bool isDevice2 = (((SerialPort)sender).PortName == RG11Port2) && ((isDSR && RG11DTRmode2) || (isCTS && !RG11DTRmode2)); + + // is the pin on or off? + bool isOn = (isDSR && ((SerialPort)sender).DsrHolding) || (isCTS && ((SerialPort)sender).CtsHolding); + + if (isDevice1) + { + if (RG11TBRmode) + { + if (isOn) + { + // relay closed, record a 'tip' + station.RG11RainToday += RG11tipsize; + } + } + else + { + station.IsRaining = isOn; + } + } + else if (isDevice2) + { + if (RG11TBRmode2) + { + if (isOn) + { + // relay closed, record a 'tip' + station.RG11RainToday += RG11tipsize; + } + } + else + { + station.IsRaining = isOn; + } + } + } + + private void APRSTimerTick(object sender, ElapsedEventArgs e) + { + if (!string.IsNullOrEmpty(APRS.ID)) + { + station.UpdateAPRS(); + } + } + + private void WebTimerTick(object sender, ElapsedEventArgs e) + { + if (station.DataStopped) + { + // No data coming in, do not do anything else + return; + } + + if (WebUpdating == 1) + { + LogMessage("Warning, previous web update is still in progress, first chance, skipping this interval"); + WebUpdating++; + } + else if (WebUpdating >= 2) + { + LogMessage("Warning, previous web update is still in progress, second chance, aborting connection"); + if (ftpThread.ThreadState == System.Threading.ThreadState.Running) + ftpThread.Abort(); + LogMessage("Trying new web update"); + WebUpdating = 1; + ftpThread = new Thread(DoHTMLFiles) { IsBackground = true }; + ftpThread.Start(); + } + else + { + WebUpdating = 1; + ftpThread = new Thread(DoHTMLFiles) { IsBackground = true }; + ftpThread.Start(); + } + } + + internal async void UpdateTwitter() + { + if (station.DataStopped) + { + // No data coming in, do nothing + return; + } + + LogDebugMessage("Starting Twitter update"); + var auth = new XAuthAuthorizer + { + CredentialStore = new XAuthCredentials { ConsumerKey = twitterKey, ConsumerSecret = twitterSecret, UserName = Twitter.ID, Password = Twitter.PW } + }; + + if (Twitter.OauthToken == "unknown") + { + // need to get tokens using xauth + LogDebugMessage("Obtaining Twitter tokens"); + await auth.AuthorizeAsync(); + + Twitter.OauthToken = auth.CredentialStore.OAuthToken; + Twitter.OauthTokenSecret = auth.CredentialStore.OAuthTokenSecret; + //LogDebugMessage("Token=" + TwitterOauthToken); + //LogDebugMessage("TokenSecret=" + TwitterOauthTokenSecret); + LogDebugMessage("Tokens obtained"); + } + else + { + auth.CredentialStore.OAuthToken = Twitter.OauthToken; + auth.CredentialStore.OAuthTokenSecret = Twitter.OauthTokenSecret; + } + + using (var twitterCtx = new TwitterContext(auth)) + { + StringBuilder status = new StringBuilder(1024); + + if (File.Exists(TwitterTxtFile)) + { + // use twitter.txt file + LogDebugMessage("Using twitter.txt file"); + var twitterTokenParser = new TokenParser(); + var utf8WithoutBom = new UTF8Encoding(false); + var encoding = utf8WithoutBom; + twitterTokenParser.Encoding = encoding; + twitterTokenParser.SourceFile = TwitterTxtFile; + twitterTokenParser.OnToken += TokenParserOnToken; + status.Append(twitterTokenParser); + } + else + { + // default message + status.Append($"Wind {station.WindAverage.ToString(WindAvgFormat)} {Units.WindText} {station.AvgBearingText}."); + status.Append($" Barometer {station.Pressure.ToString(PressFormat)} {Units.PressText}, {station.Presstrendstr}."); + status.Append($" Temperature {station.OutdoorTemperature.ToString(TempFormat)} {Units.TempText}."); + status.Append($" Rain today {station.RainToday.ToString(RainFormat)}{Units.RainText}."); + status.Append($" Humidity {station.OutdoorHumidity}%"); + } + + LogDebugMessage($"Updating Twitter: {status}"); + + var statusStr = status.ToString(); + + try + { + Status tweet; + + if (Twitter.SendLocation) + { + tweet = await twitterCtx.TweetAsync(statusStr, (decimal)Latitude, (decimal)Longitude); + } + else + { + tweet = await twitterCtx.TweetAsync(statusStr); + } + + if (tweet == null) + { + LogDebugMessage("Null Twitter response"); + } + else + { + LogDebugMessage($"Status returned: ({tweet.StatusID}) {tweet.User.Name}, {tweet.Text}, {tweet.CreatedAt}"); + } + + HttpUploadAlarm.Triggered = false; + } + catch (Exception ex) + { + LogMessage($"UpdateTwitter: {ex.Message}"); + HttpUploadAlarm.LastError = "Twitter: " + ex.Message; + HttpUploadAlarm.Triggered = true; + } + //if (tweet != null) + // Console.WriteLine("Status returned: " + "(" + tweet.StatusID + ")" + tweet.User.Name + ", " + tweet.Text + "\n"); + } + } + + private void WundTimerTick(object sender, ElapsedEventArgs e) + { + if (!string.IsNullOrWhiteSpace(Wund.ID)) + UpdateWunderground(DateTime.Now); + } + + + private void AwekasTimerTick(object sender, ElapsedEventArgs e) + { + if (!string.IsNullOrWhiteSpace(AWEKAS.ID)) + UpdateAwekas(DateTime.Now); + } + + public void MQTTTimerTick(object sender, ElapsedEventArgs e) + { + if (!station.DataStopped) + MqttPublisher.UpdateMQTTfeed("Interval"); + } + + /* + * 15/1020 - This does nothing! + public void AirLinkTimerTick(object sender, ElapsedEventArgs e) + { + if (AirLinkInEnabled && airLinkIn != null) + { + airLinkIn.GetAirQuality(); + } + if (AirLinkOutEnabled && airLinkOut != null) + { + airLinkOut.GetAirQuality(); + } + } + */ + + internal async void UpdateWunderground(DateTime timestamp) + { + if (Wund.Updating || station.DataStopped) + { + // No data coming in, do not do anything + return; + } + + Wund.Updating = true; + + string pwstring; + string URL = station.GetWundergroundURL(out pwstring, timestamp, false); + + string starredpwstring = "&PASSWORD=" + new string('*', Wund.PW.Length); + + string logUrl = URL.Replace(pwstring, starredpwstring); + if (!Wund.RapidFireEnabled) + { + LogDebugMessage("Wunderground: URL = " + logUrl); + } + + try + { + HttpResponseMessage response = await WUhttpClient.GetAsync(URL); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + if (!Wund.RapidFireEnabled || response.StatusCode != HttpStatusCode.OK) + { + LogMessage("Wunderground: Response = " + response.StatusCode + ": " + responseBodyAsText); + HttpUploadAlarm.LastError = "Wunderground: HTTP response - " + response.StatusCode; + HttpUploadAlarm.Triggered = true; + } + else + { + HttpUploadAlarm.Triggered = false; + } + } + catch (Exception ex) + { + LogMessage("Wunderground: ERROR - " + ex.Message); + HttpUploadAlarm.LastError = "Wunderground: " + ex.Message; + HttpUploadAlarm.Triggered = true; + } + finally + { + Wund.Updating = false; + } + } + + internal async void UpdateWindy(DateTime timestamp) + { + if (Windy.Updating || station.DataStopped) + { + // No data coming in, do not do anything + return; + } + + Windy.Updating = true; + + string apistring; + string url = station.GetWindyURL(out apistring, timestamp); + string logUrl = url.Replace(apistring, "<>"); + + LogDebugMessage("Windy: URL = " + logUrl); + + try + { + HttpResponseMessage response = await WindyhttpClient.GetAsync(url); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + LogDebugMessage("Windy: Response = " + response.StatusCode + ": " + responseBodyAsText); + if (response.StatusCode != HttpStatusCode.OK) + { + LogMessage("Windy: ERROR - Response = " + response.StatusCode + ": " + responseBodyAsText); + HttpUploadAlarm.LastError = "Windy: HTTP response - " + response.StatusCode; + HttpUploadAlarm.Triggered = true; + } + else + { + HttpUploadAlarm.Triggered = false; + } + } + catch (Exception ex) + { + LogMessage("Windy: ERROR - " + ex.Message); + HttpUploadAlarm.LastError = "Windy: " + ex.Message; + HttpUploadAlarm.Triggered = true; + } + finally + { + Windy.Updating = false; + } + } + + internal async void UpdateWindGuru(DateTime timestamp) + { + if (WindGuru.Updating || station.DataStopped) + { + // No data coming in, do not do anything + return; + } + + WindGuru.Updating = true; + + string apistring; + string url = station.GetWindGuruURL(out apistring, timestamp); + string logUrl = url.Replace(apistring, "<>"); + + LogDebugMessage("WindGuru: URL = " + logUrl); + + try + { + HttpResponseMessage response = await WindGuruhttpClient.GetAsync(url); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + LogDebugMessage("WindGuru: " + response.StatusCode + ": " + responseBodyAsText); + if (response.StatusCode != HttpStatusCode.OK) + { + LogMessage("WindGuru: ERROR - " + response.StatusCode + ": " + responseBodyAsText); + HttpUploadAlarm.LastError = "WindGuru: HTTP response - " + response.StatusCode; + HttpUploadAlarm.Triggered = true; + } + else + { + HttpUploadAlarm.Triggered = false; + } + } + catch (Exception ex) + { + LogMessage("WindGuru: ERROR - " + ex.Message); + HttpUploadAlarm.LastError = "WindGuru: " + ex.Message; + HttpUploadAlarm.Triggered = true; + } + finally + { + WindGuru.Updating = false; + } + } + + + internal async void UpdateAwekas(DateTime timestamp) + { + if (AWEKAS.Updating || station.DataStopped) + { + // No data coming in, do not do anything + return; + } + + AWEKAS.Updating = true; + + string pwstring; + string url = station.GetAwekasURLv4(out pwstring, timestamp); + + string starredpwstring = ""; + + string logUrl = url.Replace(pwstring, starredpwstring); + + LogDebugMessage("AWEKAS: URL = " + logUrl); + + try + { + using (HttpResponseMessage response = await AwekashttpClient.GetAsync(url)) + { + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + LogDebugMessage("AWEKAS Response code = " + response.StatusCode); + LogDataMessage("AWEKAS: Response text = " + responseBodyAsText); + + if (response.StatusCode != HttpStatusCode.OK) + { + LogMessage($"AWEKAS: ERROR - Response code = {response.StatusCode}, body = {responseBodyAsText}"); + HttpUploadAlarm.LastError = $"AWEKAS: HTTP Response code = {response.StatusCode}, body = {responseBodyAsText}"; + HttpUploadAlarm.Triggered = true; + } + else + { + HttpUploadAlarm.Triggered = false; + } + //var respJson = JsonConvert.DeserializeObject(responseBodyAsText); + var respJson = JsonSerializer.DeserializeFromString(responseBodyAsText); + + // Check the status response + if (respJson.status == 2) + { + LogDebugMessage("AWEKAS: Data stored OK"); + } + else if (respJson.status == 1) + { + LogMessage("AWEKAS: Data PARIALLY stored"); + // TODO: Check errors and disabled + } + else if (respJson.status == 0) // Authenication error or rate limited + { + if (respJson.minuploadtime > 0 && respJson.authentication == 0) + { + LogMessage("AWEKAS: Authentication error"); + if (AWEKAS.Interval < 300) + { + AWEKAS.RateLimited = true; + AWEKAS.OriginalInterval = AWEKAS.Interval; + AWEKAS.Interval = 300; + AwekasTimer.Enabled = false; + AWEKAS.SynchronisedUpdate = true; + LogMessage("AWEKAS: Temporarily increasing AWEKAS upload interval to 300 seconds due to authenication error"); + } + } + else if (respJson.minuploadtime == 0) + { + LogMessage("AWEKAS: Too many requests, rate limited"); + // AWEKAS PLus allows minimum of 60 second updates, try that first + if (!AWEKAS.RateLimited && AWEKAS.Interval < 60) + { + AWEKAS.OriginalInterval = AWEKAS.Interval; + AWEKAS.RateLimited = true; + AWEKAS.Interval = 60; + AwekasTimer.Enabled = false; + AWEKAS.SynchronisedUpdate = true; + LogMessage("AWEKAS: Temporarily increasing AWEKAS upload interval to 60 seconds due to rate limit"); + } + // AWEKAS normal allows minimum of 300 second updates, revert to that + else + { + AWEKAS.RateLimited = true; + AWEKAS.Interval = 300; + AwekasTimer.Interval = AWEKAS.Interval * 1000; + AwekasTimer.Enabled = !AWEKAS.SynchronisedUpdate; + AWEKAS.SynchronisedUpdate = AWEKAS.Interval % 60 == 0; + LogMessage("AWEKAS: Temporarily increasing AWEKAS upload interval to 300 seconds due to rate limit"); + } + } + else + { + LogMessage("AWEKAS: Unknown error"); + HttpUploadAlarm.LastError = "AWEKAS: Unknown error"; + HttpUploadAlarm.Triggered = true; + } + } + + // check the min upload time is greater than our upload time + if (respJson.status > 0 && respJson.minuploadtime > AWEKAS.OriginalInterval) + { + LogMessage($"AWEKAS: The minimum upload time to AWEKAS for your station is {respJson.minuploadtime} sec, Cumulus is configured for {AWEKAS.OriginalInterval} sec, increasing Cumulus interval to match AWEKAS"); + AWEKAS.Interval = respJson.minuploadtime; + WriteIniFile(); + AwekasTimer.Interval = AWEKAS.Interval * 1000; + AWEKAS.SynchronisedUpdate = AWEKAS.Interval % 60 == 0; + AwekasTimer.Enabled = !AWEKAS.SynchronisedUpdate; + // we got a successful upload, and reset the interval, so clear the rate limited values + AWEKAS.OriginalInterval = AWEKAS.Interval; + AWEKAS.RateLimited = false; + } + else if (AWEKAS.RateLimited && respJson.status > 0) + { + // We are currently rate limited, it could have been a transient thing because + // we just got a valid response, and our interval is >= the minimum allowed. + // So we just undo the limit, and resume as before + LogMessage($"AWEKAS: Removing temporary increase in upload interval to 60 secs, resuming uploads every {AWEKAS.OriginalInterval} secs"); + AWEKAS.Interval = AWEKAS.OriginalInterval; + AwekasTimer.Interval = AWEKAS.Interval * 1000; + AWEKAS.SynchronisedUpdate = AWEKAS.Interval % 60 == 0; + AwekasTimer.Enabled = !AWEKAS.SynchronisedUpdate; + AWEKAS.RateLimited = false; + } + } + } + catch (Exception ex) + { + LogMessage("AWEKAS: Exception = " + ex.Message); + HttpUploadAlarm.LastError = "AWEKAS: " + ex.Message; + HttpUploadAlarm.Triggered = true; + } + finally + { + AWEKAS.Updating = false; + } + } + + internal async void UpdateWCloud(DateTime timestamp) + { + if (WCloud.Updating || station.DataStopped) + { + // No data coming in, do not do anything + return; + } + + WCloud.Updating = true; + + string pwstring; + string url = station.GetWCloudURL(out pwstring, timestamp); + + string starredpwstring = ""; + + string logUrl = url.Replace(pwstring, starredpwstring); + + LogDebugMessage("WeatherCloud: URL = " + logUrl); + + try + { + HttpResponseMessage response = await WCloudhttpClient.GetAsync(url); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + var msg = ""; + switch ((int)response.StatusCode) + { + case 200: + msg = "Success"; + HttpUploadAlarm.Triggered = false; + break; + case 400: + msg = "Bad request"; + HttpUploadAlarm.LastError = "WeatherCloud: " + msg; + HttpUploadAlarm.Triggered = true; + break; + case 401: + msg = "Incorrect WID or Key"; + HttpUploadAlarm.LastError = "WeatherCloud: " + msg; + HttpUploadAlarm.Triggered = true; + break; + case 429: + msg = "Too many requests"; + HttpUploadAlarm.LastError = "WeatherCloud: " + msg; + HttpUploadAlarm.Triggered = true; + break; + case 500: + msg = "Server error"; + HttpUploadAlarm.LastError = "WeatherCloud: " + msg; + HttpUploadAlarm.Triggered = true; + break; + default: + msg = "Unknown error"; + HttpUploadAlarm.LastError = "WeatherCloud: " + msg; + HttpUploadAlarm.Triggered = true; + break; + } + if ((int)response.StatusCode == 200) + LogDebugMessage($"WeatherCloud: Response = {msg} ({response.StatusCode}): {responseBodyAsText}"); + else + LogMessage($"WeatherCloud: ERROR - Response = {msg} ({response.StatusCode}): {responseBodyAsText}"); + } + catch (Exception ex) + { + LogMessage("WeatherCloud: ERROR - " + ex.Message); + HttpUploadAlarm.LastError = "WeatherCloud: " + ex.Message; + HttpUploadAlarm.Triggered = true; + } + finally + { + WCloud.Updating = false; + } + } + + internal async void UpdateOpenWeatherMap(DateTime timestamp) + { + if (OpenWeatherMap.Updating || station.DataStopped) + { + // No data coming in, do not do anything + return; + } + + OpenWeatherMap.Updating = true; + + string url = "http://api.openweathermap.org/data/3.0/measurements?appid=" + OpenWeatherMap.PW; + string logUrl = url.Replace(OpenWeatherMap.PW, ""); + + string jsonData = station.GetOpenWeatherMapData(timestamp); + + LogDebugMessage("OpenWeatherMap: URL = " + logUrl); + LogDataMessage("OpenWeatherMap: Body = " + jsonData); + + try + { + using (var client = new HttpClient()) + { + var data = new StringContent(jsonData, Encoding.UTF8, "application/json"); + HttpResponseMessage response = await client.PostAsync(url, data); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + var status = response.StatusCode == HttpStatusCode.NoContent ? "OK" : "Error"; // Returns a 204 reponse for OK! + LogDebugMessage($"OpenWeatherMap: Response code = {status} - {response.StatusCode}"); + if (response.StatusCode != HttpStatusCode.NoContent) + { + LogMessage($"OpenWeatherMap: ERROR - Response code = {response.StatusCode}, Response data = {responseBodyAsText}"); + HttpUploadAlarm.LastError = $"OpenWeatherMap: HTTP response - {response.StatusCode}, Response data = {responseBodyAsText}"; + HttpUploadAlarm.Triggered = true; + } + else + { + HttpUploadAlarm.Triggered = false; + } + } + } + catch (Exception ex) + { + LogMessage("OpenWeatherMap: ERROR - " + ex.Message); + HttpUploadAlarm.LastError = "OpenWeatherMap: " + ex.Message; + HttpUploadAlarm.Triggered = true; + } + finally + { + OpenWeatherMap.Updating = false; + } + } + + // Find all stations associated with the users API key + internal OpenWeatherMapStation[] GetOpenWeatherMapStations() + { + OpenWeatherMapStation[] retVal = new OpenWeatherMapStation[0]; + string url = "http://api.openweathermap.org/data/3.0/stations?appid=" + OpenWeatherMap.PW; + try + { + using (var client = new HttpClient()) + { + HttpResponseMessage response = client.GetAsync(url).Result; + var responseBodyAsText = response.Content.ReadAsStringAsync().Result; + LogDataMessage("OpenWeatherMap: Get Stations Response: " + response.StatusCode + ": " + responseBodyAsText); + + if (responseBodyAsText.Length > 10) + { + var respJson = JsonSerializer.DeserializeFromString(responseBodyAsText); + retVal = respJson; + } + } + } + catch (Exception ex) + { + LogMessage("OpenWeatherMap: Get Stations ERROR - " + ex.Message); + } + + return retVal; + } + + // Create a new OpenWeatherMap station + internal void CreateOpenWeatherMapStation() + { + var invC = new CultureInfo(""); + + string url = "http://api.openweathermap.org/data/3.0/stations?appid=" + OpenWeatherMap.PW; + try + { + var datestr = DateTime.Now.ToUniversalTime().ToString("yyMMddHHmm"); + StringBuilder sb = new StringBuilder($"{{\"external_id\":\"CMX-{datestr}\","); + sb.Append($"\"name\":\"{LocationName}\","); + sb.Append($"\"latitude\":{Latitude.ToString(invC)},"); + sb.Append($"\"longitude\":{Longitude.ToString(invC)},"); + sb.Append($"\"altitude\":{(int)station.AltitudeM(Altitude)}}}"); + + LogMessage($"OpenWeatherMap: Creating new station"); + LogMessage($"OpenWeatherMap: - {sb}"); + + + using (var client = new HttpClient()) + { + var data = new StringContent(sb.ToString(), Encoding.UTF8, "application/json"); + + HttpResponseMessage response = client.PostAsync(url, data).Result; + var responseBodyAsText = response.Content.ReadAsStringAsync().Result; + var status = response.StatusCode == HttpStatusCode.Created ? "OK" : "Error"; // Returns a 201 reponse for OK + LogDebugMessage($"OpenWeatherMap: Create station response code = {status} - {response.StatusCode}"); + LogDataMessage($"OpenWeatherMap: Create station response data = {responseBodyAsText}"); + + if (response.StatusCode == HttpStatusCode.Created) + { + // It worked, save the result + var respJson = JsonSerializer.DeserializeFromString(responseBodyAsText); + + LogMessage($"OpenWeatherMap: Created new station, id = {respJson.ID}, name = {respJson.name}"); + OpenWeatherMap.ID = respJson.ID; + WriteIniFile(); + } + else + { + LogMessage($"OpenWeatherMap: Failed to create new station. Error - {response.StatusCode}, text - {responseBodyAsText}"); + } + } + } + catch (Exception ex) + { + LogMessage("OpenWeatherMap: Create station ERROR - " + ex.Message); + } + } + + internal void EnableOpenWeatherMap() + { + if (OpenWeatherMap.Enabled && string.IsNullOrWhiteSpace(OpenWeatherMap.ID)) + { + // oh, oh! OpenWeatherMap is enabled, but we do not have a station id + // first check if one already exists + var stations = GetOpenWeatherMapStations(); + + if (stations.Length == 0) + { + // No stations defined, we will create one + LogMessage($"OpenWeatherMap: No station defined, attempting to create one"); + CreateOpenWeatherMapStation(); + } + else if (stations.Length == 1) + { + // We have one station defined, lets use it! + LogMessage($"OpenWeatherMap: No station defined, but found one associated with this API key, using this station - {stations[0].id} : {stations[0].name}"); + OpenWeatherMap.ID = stations[0].id; + // save the setting + WriteIniFile(); + } + else + { + // multiple stations defined, the user must select which one to use + var msg = $"Multiple OpenWeatherMap stations found, please select the correct station id and enter it into your configuration"; + LogConsoleMessage(msg); + LogMessage("OpenWeatherMap: " + msg); + foreach (var station in stations) + { + msg = $" Station Id = {station.id}, Name = {station.name}"; + LogConsoleMessage(msg); + LogMessage("OpenWeatherMap: " + msg); + } + } + } + } + + internal void RealtimeTimerTick(object sender, ElapsedEventArgs elapsedEventArgs) + { + bool connectionFailed = false; + var cycle = RealtimeCycleCounter++; + + if (station.DataStopped) + { + // No data coming in, do not do anything + return; + } + + LogDebugMessage($"Realtime[{cycle}]: Start cycle"); + try + { + // Process any files + if (RealtimeCopyInProgress) + { + LogMessage($"Realtime[{cycle}]: Warning, a previous cycle is still processing local files. Skipping this interval."); + } + else + { + RealtimeCopyInProgress = true; + CreateRealtimeFile(cycle); + CreateRealtimeHTMLfiles(cycle); + RealtimeCopyInProgress = false; + + MySqlRealtimeFile(cycle); + + if (RealtimeFTPEnabled && !string.IsNullOrWhiteSpace(FtpHostname)) + { + // Is a previous cycle still running? + if (RealtimeFtpInProgress) + { + LogMessage($"Realtime[{cycle}]: Warning, a previous cycle is still trying to connect to FTP server, skip count = {++realtimeFTPRetries}"); + // realtimeinvertval is in ms, if a session has been uploading for 5 minutes - abort it and reconnect + if (realtimeFTPRetries * RealtimeInterval / 1000 > 5 * 60) + { + LogMessage($"Realtime[{cycle}]: Realtime has been in progress for more than 5 minutes, attempting to reconnect."); + RealtimeFTPConnectionTest(cycle); + } + else + { + LogMessage($"Realtime[{cycle}]: No FTP attempted this cycle"); + } + } + else + { + RealtimeFtpInProgress = true; + + // This only happens if the user enables realtime FTP after starting Cumulus + if (Sslftp == FtpProtocols.SFTP) + { + if (RealtimeSSH == null || !RealtimeSSH.ConnectionInfo.IsAuthenticated) + { + RealtimeSSHLogin(cycle); + } + } + else + { + if (!RealtimeFTP.IsConnected) + { + RealtimeFTPLogin(cycle); + } + } + // Force a test of the connection, IsConnected is not always reliable + try + { + string pwd; + if (Sslftp == FtpProtocols.SFTP) + { + pwd = RealtimeSSH.WorkingDirectory; + // Double check + if (!RealtimeSSH.IsConnected) + { + connectionFailed = true; + } + } + else + { + pwd = RealtimeFTP.GetWorkingDirectory(); + // Double check + if (!RealtimeFTP.IsConnected) + { + connectionFailed = true; + } + } + if (pwd.Length == 0) + { + connectionFailed = true; + } + } + catch (Exception ex) + { + LogDebugMessage($"Realtime[{cycle}]: Test of FTP connection failed: {ex.Message}"); + connectionFailed = true; + } + + if (connectionFailed) + { + RealtimeFTPConnectionTest(cycle); + } + else + { + realtimeFTPRetries = 0; + } + + try + { + RealtimeFTPUpload(cycle); + } + catch (Exception ex) + { + LogMessage($"Realtime[{cycle}]: Error during realtime FTP update: {ex.Message}"); + RealtimeFTPConnectionTest(cycle); + } + RealtimeFtpInProgress = false; + } + } + + if (!string.IsNullOrEmpty(RealtimeProgram)) + { + LogDebugMessage($"Realtime[{cycle}]: Execute realtime program - {RealtimeProgram}"); + ExecuteProgram(RealtimeProgram, RealtimeParams); + } + } + } + catch (Exception ex) + { + LogMessage($"Realtime[{cycle}]: Error during update: {ex.Message}"); + if (ex is NullReferenceException) + { + // If we haven't initialised the object (eg. user enables realtime FTP after starting Cumulus) + // then start from the beginning + if (Sslftp == FtpProtocols.SFTP) + { + RealtimeSSHLogin(cycle); + } + else + { + RealtimeFTPLogin(cycle); + } + } + else + { + RealtimeFTPConnectionTest(cycle); + } + RealtimeFtpInProgress = false; + } + LogDebugMessage($"Realtime[{cycle}]: End cycle"); + } + + private void RealtimeFTPConnectionTest(uint cycle) + { + LogDebugMessage($"Realtime[{cycle}]: Realtime ftp attempting disconnect"); + try + { + if (Sslftp == FtpProtocols.SFTP && RealtimeSSH != null) + { + RealtimeSSH.Disconnect(); + } + else + { + RealtimeFTP.Disconnect(); + } + LogDebugMessage($"Realtime[{cycle}]: Realtime ftp disconnected OK"); + } + catch(Exception ex) + { + LogDebugMessage($"Realtime[{cycle}]: Error disconnecting from server - " + ex.Message); + } + + LogDebugMessage($"Realtime[{cycle}]: Realtime ftp attempting to reconnect"); + try + { + if (Sslftp == FtpProtocols.SFTP && RealtimeSSH != null) + { + RealtimeSSH.Connect(); + } + else + { + RealtimeFTP.Connect(); + } + LogMessage($"Realtime[{cycle}]: Reconnected with server OK"); + } + catch (Exception ex) + { + LogMessage($"Realtime[{cycle}]: Error reconnecting ftp server - " + ex.Message); + LogDebugMessage($"Realtime[{cycle}]: Realtime ftp attempting to reinitialise the connection"); + if (Sslftp == FtpProtocols.SFTP) + { + RealtimeSSHLogin(cycle); + } + else + { + RealtimeFTPLogin(cycle); + } + } + if (Sslftp == FtpProtocols.SFTP && RealtimeSSH == null) + { + LogDebugMessage($"Realtime[{cycle}]: Realtime ftp attempting to reinitialise the connection"); + RealtimeSSHLogin(cycle); + } + } + + private void RealtimeFTPUpload(byte cycle) + { + var remotePath = ""; + + if (FtpDirectory.Length > 0) + { + remotePath = (FtpDirectory.EndsWith("/") ? FtpDirectory : FtpDirectory + "/"); + } + + for (var i = 0; i < RealtimeFiles.Length; i++) + { + if (RealtimeFiles[i].Create && RealtimeFiles[i].FTP) + { + var remoteFile = remotePath + RealtimeFiles[i].RemoteFileName; + var localFile = RealtimeFiles[i].LocalPath + RealtimeFiles[i].LocalFileName; + + LogFtpMessage($"Realtime[{cycle}]: Uploading - {RealtimeFiles[i].LocalFileName}"); + if (Sslftp == FtpProtocols.SFTP) + { + UploadFile(RealtimeSSH, localFile, remoteFile, cycle); + } + else + { + UploadFile(RealtimeFTP, localFile, remoteFile, cycle); + } + + } + } + + // Extra files + for (int i = 0; i < numextrafiles; i++) + { + var uploadfile = ExtraFiles[i].local; + var remotefile = ExtraFiles[i].remote; + + if ((uploadfile.Length > 0) && (remotefile.Length > 0) && ExtraFiles[i].realtime && ExtraFiles[i].FTP) + { + uploadfile = GetUploadFilename(uploadfile, DateTime.Now); + + if (File.Exists(uploadfile)) + { + remotefile = GetRemoteFileName(remotefile, DateTime.Now); + + // all checks OK, file needs to be uploaded + if (ExtraFiles[i].process) + { + // we've already processed the file + uploadfile += "tmp"; + } + LogFtpMessage($"Realtime[{cycle}]: Uploading extra web file[{i}] {uploadfile} to {remotefile}"); + if (Sslftp == FtpProtocols.SFTP) + { + UploadFile(RealtimeSSH, uploadfile, remotefile, cycle); + } + else + { + UploadFile(RealtimeFTP, uploadfile, remotefile, cycle); + } + } + else + { + LogMessage($"Realtime[{cycle}]: Warning, extra web file[{i}] not found! - {uploadfile}"); + } + } + } + } + + private void CreateRealtimeHTMLfiles(int cycle) + { + // Process realtime files + for (var i = 0; i < RealtimeFiles.Length; i++) + { + if (RealtimeFiles[i].Create && !string.IsNullOrWhiteSpace(RealtimeFiles[i].TemplateFileName)) + { + LogDebugMessage($"Realtime[{cycle}]: Processing realtime file - {RealtimeFiles[i].LocalFileName}"); + var destFile = RealtimeFiles[i].LocalPath + RealtimeFiles[i].LocalFileName; + ProcessTemplateFile(RealtimeFiles[i].TemplateFileName, destFile, realtimeTokenParser); + } + } + + for (int i = 0; i < numextrafiles; i++) + { + if (ExtraFiles[i].realtime) + { + var uploadfile = ExtraFiles[i].local; + var remotefile = ExtraFiles[i].remote; + + if ((uploadfile.Length > 0) && (remotefile.Length > 0)) + { + uploadfile = GetUploadFilename(uploadfile, DateTime.Now); + + if (File.Exists(uploadfile)) + { + remotefile = GetRemoteFileName(remotefile, DateTime.Now); + + if (ExtraFiles[i].process) + { + // process the file + LogDebugMessage($"Realtime[{cycle}]: Processing extra file[{i}] - {uploadfile}"); + var utf8WithoutBom = new UTF8Encoding(false); + var encoding = UTF8encode ? utf8WithoutBom : Encoding.GetEncoding("iso-8859-1"); + realtimeTokenParser.Encoding = encoding; + realtimeTokenParser.SourceFile = uploadfile; + var output = realtimeTokenParser.ToString(); + uploadfile += "tmp"; + try + { + using (StreamWriter file = new StreamWriter(uploadfile, false, encoding)) + { + file.Write(output); + file.Close(); + } + } + catch (Exception ex) + { + LogMessage($"Realtime[{cycle}]: Error writing to extra realtime file[{i}] - {uploadfile}: {ex.Message}"); + } + } + + if (!ExtraFiles[i].FTP) + { + // just copy the file + try + { + LogDebugMessage($"Realtime[{cycle}]: Copying extra file[{i}] {uploadfile} to {remotefile}"); + File.Copy(uploadfile, remotefile, true); + } + catch (Exception ex) + { + LogDebugMessage($"Realtime[{cycle}]: Error copying extra realtime file[{i}] - {uploadfile}: {ex.Message}"); + } + } + } + else + { + LogMessage($"Realtime[{cycle}]: Extra realtime web file[{i}] not found - {uploadfile}"); + } + + } + } + } + } + + public void TokenParserOnToken(string strToken, ref string strReplacement) + { + var tagParams = new Dictionary(); + var paramList = ParseParams(strToken); + var webTag = paramList[0]; + + tagParams.Add("webtag", webTag); + for (int i = 1; i < paramList.Count; i += 2) + { + // odd numbered entries are keys with "=" on the end - remove that + string key = paramList[i].Remove(paramList[i].Length - 1); + // even numbered entries are values + string value = paramList[i + 1]; + tagParams.Add(key, value); + } + + strReplacement = webtags.GetWebTagText(webTag, tagParams); + } + + private List ParseParams(string line) + { + var insideQuotes = false; + var start = -1; + + var parts = new List(); + + for (var i = 0; i < line.Length; i++) + { + if (char.IsWhiteSpace(line[i])) + { + if (!insideQuotes && start != -1) + { + parts.Add(line.Substring(start, i - start)); + start = -1; + } + } + else if (line[i] == '"') + { + if (start != -1) + { + parts.Add(line.Substring(start, i - start)); + start = -1; + } + insideQuotes = !insideQuotes; + } + else if (line[i] == '=') + { + if (!insideQuotes) + { + if (start != -1) + { + parts.Add(line.Substring(start, (i - start) + 1)); + start = -1; + } + } + } + else + { + if (start == -1) + start = i; + } + } + + if (start != -1) + parts.Add(line.Substring(start)); + + return parts; + } + + public string DecimalSeparator { get; set; } + + [DllImport("libc")] + private static extern int uname(IntPtr buf); + + private static bool IsRunningOnMac() + { + IntPtr buf = IntPtr.Zero; + try + { + buf = Marshal.AllocHGlobal(8192); + // This is a hacktastic way of getting sysname from uname () + if (uname(buf) == 0) + { + string os = Marshal.PtrToStringAnsi(buf); + if (os == "Darwin") + return true; + } + } + catch + { + } + finally + { + if (buf != IntPtr.Zero) + Marshal.FreeHGlobal(buf); + } + return false; + } + + internal void DoMoonPhase() + { + DateTime now = DateTime.Now; + double[] moonriseset = MoonriseMoonset.MoonRise(now.Year, now.Month, now.Day, TimeZone.CurrentTimeZone.GetUtcOffset(now).TotalHours, Latitude, Longitude); + MoonRiseTime = TimeSpan.FromHours(moonriseset[0]); + MoonSetTime = TimeSpan.FromHours(moonriseset[1]); + + DateTime utcNow = DateTime.UtcNow; + MoonPhaseAngle = MoonriseMoonset.MoonPhase(utcNow.Year, utcNow.Month, utcNow.Day, utcNow.Hour); + MoonPercent = (100.0 * (1.0 + Math.Cos(MoonPhaseAngle * Math.PI / 180)) / 2.0); + + // If between full moon and new moon, angle is between 180 and 360, make percent negative to indicate waning + if (MoonPhaseAngle > 180) + { + MoonPercent = -MoonPercent; + } + /* + // New = -0.4 -> 0.4 + // 1st Q = 45 -> 55 + // Full = 99.6 -> -99.6 + // 3rd Q = -45 -> -55 + if ((MoonPercent > 0.4) && (MoonPercent < 45)) + MoonPhaseString = WaxingCrescent; + else if ((MoonPercent >= 45) && (MoonPercent <= 55)) + MoonPhaseString = FirstQuarter; + else if ((MoonPercent > 55) && (MoonPercent < 99.6)) + MoonPhaseString = WaxingGibbous; + else if ((MoonPercent >= 99.6) || (MoonPercent <= -99.6)) + MoonPhaseString = Fullmoon; + else if ((MoonPercent < -55) && (MoonPercent > -99.6)) + MoonPhaseString = WaningGibbous; + else if ((MoonPercent <= -45) && (MoonPercent >= -55)) + MoonPhaseString = LastQuarter; + else if ((MoonPercent > -45) && (MoonPercent < -0.4)) + MoonPhaseString = WaningCrescent; + else + MoonPhaseString = Newmoon; + */ + + // Use Phase Angle to determine string - it's linear unlike Illuminated Percentage + // New = 186 - 180 - 174 + // 1st = 96 - 90 - 84 + // Full = 6 - 0 - 354 + // 3rd = 276 - 270 - 264 + if (MoonPhaseAngle < 174 && MoonPhaseAngle > 96) + MoonPhaseString = WaxingCrescent; + else if (MoonPhaseAngle <= 96 && MoonPhaseAngle >= 84) + MoonPhaseString = FirstQuarter; + else if (MoonPhaseAngle < 84 && MoonPhaseAngle > 6) + MoonPhaseString = WaxingGibbous; + else if (MoonPhaseAngle <= 6 || MoonPhaseAngle >= 354) + MoonPhaseString = Fullmoon; + else if (MoonPhaseAngle < 354 && MoonPhaseAngle > 276) + MoonPhaseString = WaningGibbous; + else if (MoonPhaseAngle <= 276 && MoonPhaseAngle >= 264) + MoonPhaseString = LastQuarter; + else if (MoonPhaseAngle < 264 && MoonPhaseAngle > 186) + MoonPhaseString = WaningCrescent; + else + MoonPhaseString = Newmoon; + } + + internal void DoMoonImage() + { + if (MoonImageEnabled) + { + LogDebugMessage("Generating new Moon image"); + var ret = MoonriseMoonset.CreateMoonImage(MoonPhaseAngle, Latitude, MoonImageSize); + + if (ret == "OK") + { + // set a flag to show file is ready for FTP + MoonImageReady = true; + } + else + { + LogMessage(ret); + } + } + } + + + /* + private string GetMoonStage(double fAge) + { + string sStage; + + if (fAge < 1.84566) + { + sStage = Newmoon; + } + else if (fAge < 5.53699) + { + sStage = WaxingCrescent; + } + else if (fAge < 9.22831) + { + sStage = FirstQuarter; + } + else if (fAge < 12.91963) + { + sStage = WaxingGibbous; + } + else if (fAge < 16.61096) + { + sStage = Fullmoon; + } + else if (fAge < 20.30228) + { + sStage = WaningGibbous; + } + else if (fAge < 23.9931) + { + sStage = LastQuarter; + } + else if (fAge < 27.68493) + { + sStage = WaningCrescent; + } + else + { + sStage = Newmoon; + } + + return sStage; + } + */ + + public double MoonAge { get; set; } + + public string MoonPhaseString { get; set; } + + public double MoonPhaseAngle { get; set; } + + public double MoonPercent { get; set; } + + public TimeSpan MoonSetTime { get; set; } + + public TimeSpan MoonRiseTime { get; set; } + + public bool MoonImageEnabled; + + public int MoonImageSize; + + public string MoonImageFtpDest; + + private bool MoonImageReady; + + private void GetSunriseSunset(DateTime time, out DateTime sunrise, out DateTime sunset, out bool alwaysUp, out bool alwaysDown) + { + string rise = SunriseSunset.SunRise(time, TimeZone.CurrentTimeZone.GetUtcOffset(time).TotalHours, Longitude, Latitude); + string set = SunriseSunset.SunSet(time, TimeZone.CurrentTimeZone.GetUtcOffset(time).TotalHours, Longitude, Latitude); + + if (rise.Equals("Always Down") || set.Equals("Always Down")) + { + alwaysDown = true; + alwaysUp = false; + sunrise = DateTime.MinValue; + sunset = DateTime.MinValue; + } + else if (rise.Equals("Always Up") || set.Equals("Always Up")) + { + alwaysDown = false; + alwaysUp = true; + sunrise = DateTime.MinValue; + sunset = DateTime.MinValue; + } + else + { + alwaysDown = false; + alwaysUp = false; + try + { + int h = Convert.ToInt32(rise.Substring(0, 2)); + int m = Convert.ToInt32(rise.Substring(2, 2)); + int s = Convert.ToInt32(rise.Substring(4, 2)); + sunrise = DateTime.Now.Date.Add(new TimeSpan(h, m, s)); + } + catch (Exception) + { + sunrise = DateTime.MinValue; + } + + try + { + int h = Convert.ToInt32(set.Substring(0, 2)); + int m = Convert.ToInt32(set.Substring(2, 2)); + int s = Convert.ToInt32(set.Substring(4, 2)); + sunset = DateTime.Now.Date.Add(new TimeSpan(h, m, s)); + } + catch (Exception) + { + sunset = DateTime.MinValue; + } + } + } + + /* + private DateTime getSunriseTime(DateTime time) + { + string rise = SunriseSunset.sunrise(time, TimeZone.CurrentTimeZone.GetUtcOffset(time).TotalHours, Longitude, Latitude); + //LogMessage("Sunrise: " + rise); + int h = Convert.ToInt32(rise.Substring(0, 2)); + int m = Convert.ToInt32(rise.Substring(2, 2)); + int s = Convert.ToInt32(rise.Substring(4, 2)); + return DateTime.Now.Date.Add(new TimeSpan(h, m, s)); + } + */ + + /* + private DateTime getSunsetTime(DateTime time) + { + string rise = SunriseSunset.sunset(time, TimeZone.CurrentTimeZone.GetUtcOffset(time).TotalHours, Longitude, Latitude); + //LogMessage("Sunrise: " + rise); + int h = Convert.ToInt32(rise.Substring(0, 2)); + int m = Convert.ToInt32(rise.Substring(2, 2)); + int s = Convert.ToInt32(rise.Substring(4, 2)); + return DateTime.Now.Date.Add(new TimeSpan(h, m, s)); + } + */ + + private void GetDawnDusk(DateTime time, out DateTime dawn, out DateTime dusk, out bool alwaysUp, out bool alwaysDown) + { + string dawnStr = SunriseSunset.CivilTwilightEnds(time, TimeZone.CurrentTimeZone.GetUtcOffset(time).TotalHours, Longitude, Latitude); + string duskStr = SunriseSunset.CivilTwilightStarts(time, TimeZone.CurrentTimeZone.GetUtcOffset(time).TotalHours, Longitude, Latitude); + + if (dawnStr.Equals("Always Down") || duskStr.Equals("Always Down")) + { + alwaysDown = true; + alwaysUp = false; + dawn = DateTime.MinValue; + dusk = DateTime.MinValue; + } + else if (dawnStr.Equals("Always Up") || duskStr.Equals("Always Up")) + { + alwaysDown = false; + alwaysUp = true; + dawn = DateTime.MinValue; + dusk = DateTime.MinValue; + } + else + { + alwaysDown = false; + alwaysUp = false; + try + { + int h = Convert.ToInt32(dawnStr.Substring(0, 2)); + int m = Convert.ToInt32(dawnStr.Substring(2, 2)); + int s = Convert.ToInt32(dawnStr.Substring(4, 2)); + dawn = DateTime.Now.Date.Add(new TimeSpan(h, m, s)); + } + catch (Exception) + { + dawn = DateTime.MinValue; + } + + try + { + int h = Convert.ToInt32(duskStr.Substring(0, 2)); + int m = Convert.ToInt32(duskStr.Substring(2, 2)); + int s = Convert.ToInt32(duskStr.Substring(4, 2)); + dusk = DateTime.Now.Date.Add(new TimeSpan(h, m, s)); + } + catch (Exception) + { + dusk = DateTime.MinValue; + } + } + } + + /* + private DateTime getDawnTime(DateTime time) + { + string rise = SunriseSunset.CivilTwilightEnds(time, TimeZone.CurrentTimeZone.GetUtcOffset(time).TotalHours, Longitude, Latitude); + //LogMessage("Sunrise: " + rise); + try + { + int h = Convert.ToInt32(rise.Substring(0, 2)); + int m = Convert.ToInt32(rise.Substring(2, 2)); + int s = Convert.ToInt32(rise.Substring(4, 2)); + return DateTime.Now.Date.Add(new TimeSpan(h, m, s)); + } + catch (Exception) + { + return DateTime.Now.Date; + } + } + */ + + /* + private DateTime getDuskTime(DateTime time) + { + string rise = SunriseSunset.CivilTwilightStarts(time, TimeZone.CurrentTimeZone.GetUtcOffset(time).TotalHours, Longitude, Latitude); + //LogMessage("Sunrise: " + rise); + try + { + int h = Convert.ToInt32(rise.Substring(0, 2)); + int m = Convert.ToInt32(rise.Substring(2, 2)); + int s = Convert.ToInt32(rise.Substring(4, 2)); + return DateTime.Now.Date.Add(new TimeSpan(h, m, s)); + } + catch (Exception) + { + return DateTime.Now.Date; + } + } + */ + + internal void DoSunriseAndSunset() + { + LogMessage("Calculating sunrise and sunset times"); + DateTime now = DateTime.Now; + DateTime tomorrow = now.AddDays(1); + GetSunriseSunset(now, out SunRiseTime, out SunSetTime, out SunAlwaysUp, out SunAlwaysDown); + + if (SunAlwaysUp) + { + LogMessage("Sun always up"); + DayLength = new TimeSpan(24, 0, 0); + } + else if (SunAlwaysDown) + { + LogMessage("Sun always down"); + DayLength = new TimeSpan(0, 0, 0); + } + else + { + LogMessage("Sunrise: " + SunRiseTime.ToString("HH:mm:ss")); + LogMessage("Sunset : " + SunSetTime.ToString("HH:mm:ss")); + if (SunRiseTime == DateTime.MinValue) + { + DayLength = SunSetTime - DateTime.Now.Date; + } + else if (SunSetTime == DateTime.MinValue) + { + DayLength = DateTime.Now.Date.AddDays(1) - SunRiseTime; + } + else if (SunSetTime > SunRiseTime) + { + DayLength = SunSetTime - SunRiseTime; + } + else + { + DayLength = new TimeSpan(24, 0, 0) - (SunRiseTime - SunSetTime); + } + } + + DateTime tomorrowSunRiseTime; + DateTime tomorrowSunSetTime; + TimeSpan tomorrowDayLength; + bool tomorrowSunAlwaysUp; + bool tomorrowSunAlwaysDown; + + GetSunriseSunset(tomorrow, out tomorrowSunRiseTime, out tomorrowSunSetTime, out tomorrowSunAlwaysUp, out tomorrowSunAlwaysDown); + + if (tomorrowSunAlwaysUp) + { + LogMessage("Tomorrow sun always up"); + tomorrowDayLength = new TimeSpan(24, 0, 0); + } + else if (tomorrowSunAlwaysDown) + { + LogMessage("Tomorrow sun always down"); + tomorrowDayLength = new TimeSpan(0, 0, 0); + } + else + { + LogMessage("Tomorrow sunrise: " + tomorrowSunRiseTime.ToString("HH:mm:ss")); + LogMessage("Tomorrow sunset : " + tomorrowSunSetTime.ToString("HH:mm:ss")); + tomorrowDayLength = tomorrowSunSetTime - tomorrowSunRiseTime; + } + + int tomorrowdiff = Convert.ToInt32(tomorrowDayLength.TotalSeconds - DayLength.TotalSeconds); + LogDebugMessage("Tomorrow length diff: " + tomorrowdiff); + + bool tomorrowminus; + + if (tomorrowdiff < 0) + { + tomorrowminus = true; + tomorrowdiff = -tomorrowdiff; + } + else + { + tomorrowminus = false; + } + + int tomorrowmins = tomorrowdiff / 60; + int tomorrowsecs = tomorrowdiff % 60; + + if (tomorrowminus) + { + try + { + TomorrowDayLengthText = string.Format(thereWillBeMinSLessDaylightTomorrow, tomorrowmins, tomorrowsecs); + } + catch (Exception) + { + TomorrowDayLengthText = "Error in LessDaylightTomorrow format string"; + } + } + else + { + try + { + TomorrowDayLengthText = string.Format(thereWillBeMinSMoreDaylightTomorrow, tomorrowmins, tomorrowsecs); + } + catch (Exception) + { + TomorrowDayLengthText = "Error in MoreDaylightTomorrow format string"; + } + } + + GetDawnDusk(now, out Dawn, out Dusk, out TwilightAlways, out TwilightNever); + + if (TwilightAlways) + { + DaylightLength = new TimeSpan(24, 0, 0); + } + else if (TwilightNever) + { + DaylightLength = new TimeSpan(0, 0, 0); + } + else + { + if (Dawn == DateTime.MinValue) + { + DaylightLength = Dusk - DateTime.Now.Date; + } + else if (Dusk == DateTime.MinValue) + { + DaylightLength = DateTime.Now.Date.AddDays(1) - Dawn; + } + else if (Dusk > Dawn) + { + DaylightLength = Dusk - Dawn; + } + else + { + DaylightLength = new TimeSpan(24, 0, 0) - (Dawn - Dusk); + } + } + } + + public DateTime SunSetTime; + + public DateTime SunRiseTime; + + internal bool SunAlwaysUp; + internal bool SunAlwaysDown; + + internal bool TwilightAlways; + internal bool TwilightNever; + + public string TomorrowDayLengthText { get; set; } + + public bool IsDaylight() + { + if (TwilightAlways) + { + return true; + } + if (TwilightNever) + { + return false; + } + if (Dusk > Dawn) + { + // 'Normal' case where sun sets before midnight + return (DateTime.Now >= Dawn) && (DateTime.Now <= Dusk); + } + else + { + return !((DateTime.Now >= Dusk) && (DateTime.Now <= Dawn)); + } + } + + public bool IsSunUp() + { + if (SunAlwaysUp) + { + return true; + } + if (SunAlwaysDown) + { + return false; + } + if (SunSetTime > SunRiseTime) + { + // 'Normal' case where sun sets before midnight + return (DateTime.Now >= SunRiseTime) && (DateTime.Now <= SunSetTime); + } + else + { + return !((DateTime.Now >= SunSetTime) && (DateTime.Now <= SunRiseTime)); + } + } + + private string GetLoggingFileName(string directory) + { + const int maxEntries = 15; + + List fileEntries = new List(Directory.GetFiles(directory)); + + fileEntries.Sort(); + + while (fileEntries.Count >= maxEntries) + { + File.Delete(fileEntries.First()); + fileEntries.RemoveAt(0); + } + + return $"{directory}{DateTime.Now:yyyyMMdd-HHmmss}.txt"; + } + + public void RotateLogFiles() + { + // cycle the MXdiags log file? + var logfileSize = new FileInfo(loggingfile).Length; + // if > 20 MB + if (logfileSize > 20971520) + { + var oldfile = loggingfile; + loggingfile = GetLoggingFileName("MXdiags" + DirectorySeparator); + LogMessage("Rotating log file, new log file will be: " + loggingfile.Split(DirectorySeparator).Last()); + TextWriterTraceListener myTextListener = new TextWriterTraceListener(loggingfile, "MXlog"); + Trace.Listeners.Remove("MXlog"); + Trace.Listeners.Add(myTextListener); + LogMessage("Rotated log file, old log file was: " + oldfile.Split(DirectorySeparator).Last()); + } + } + + private void ReadIniFile() + { + var DavisBaudRates = new List { 1200, 2400, 4800, 9600, 14400, 19200 }; + ImetOptions.BaudRates = new List { 19200, 115200 }; + + LogMessage("Reading Cumulus.ini file"); + //DateTimeToString(LongDate, "ddddd", Now); + + IniFile ini = new IniFile("Cumulus.ini"); + + // check for Cumulus 1 [FTP Site] and correct it + if (ini.GetValue("FTP Site", "Port", -999) != -999) + { + if (File.Exists("Cumulus.ini")) + { + var contents = File.ReadAllText("Cumulus.ini"); + contents = contents.Replace("[FTP Site]", "[FTP site]"); + File.WriteAllText("Cumulus.ini", contents); + ini.Refresh(); + } + } + + ProgramOptions.EnableAccessibility = ini.GetValue("Program", "EnableAccessibility", false); + ProgramOptions.StartupPingHost = ini.GetValue("Program", "StartupPingHost", ""); + ProgramOptions.StartupPingEscapeTime = ini.GetValue("Program", "StartupPingEscapeTime", 999); + ProgramOptions.StartupDelaySecs = ini.GetValue("Program", "StartupDelaySecs", 0); + ProgramOptions.StartupDelayMaxUptime = ini.GetValue("Program", "StartupDelayMaxUptime", 300); + ProgramOptions.WarnMultiple = ini.GetValue("Station", "WarnMultiple", true); + SmtpOptions.Logging = ini.GetValue("SMTP", "Logging", false); + if (DebuggingEnabled) + { + ProgramOptions.DebugLogging = true; + ProgramOptions.DataLogging = true; + } + else + { + ProgramOptions.DebugLogging = ini.GetValue("Station", "Logging", false); + ProgramOptions.DataLogging = ini.GetValue("Station", "DataLogging", false); + } + + ComportName = ini.GetValue("Station", "ComportName", DefaultComportName); + + StationType = ini.GetValue("Station", "Type", -1); + StationModel = ini.GetValue("Station", "Model", ""); + + FineOffsetStation = (StationType == StationTypes.FineOffset || StationType == StationTypes.FineOffsetSolar); + DavisStation = (StationType == StationTypes.VantagePro || StationType == StationTypes.VantagePro2); + + // Davis Options + DavisOptions.UseLoop2 = ini.GetValue("Station", "UseDavisLoop2", true); + DavisOptions.ReadReceptionStats = ini.GetValue("Station", "DavisReadReceptionStats", true); + DavisOptions.SetLoggerInterval = ini.GetValue("Station", "DavisSetLoggerInterval", false); + DavisOptions.InitWaitTime = ini.GetValue("Station", "DavisInitWaitTime", 2000); + DavisOptions.IPResponseTime = ini.GetValue("Station", "DavisIPResponseTime", 500); + //StationOptions.DavisReadTimeout = ini.GetValue("Station", "DavisReadTimeout", 1000); // Not currently used + DavisOptions.IncrementPressureDP = ini.GetValue("Station", "DavisIncrementPressureDP", false); + if (StationType == StationTypes.VantagePro) + { + DavisOptions.UseLoop2 = false; + } + DavisOptions.BaudRate = ini.GetValue("Station", "DavisBaudRate", 19200); + // Check we have a valid value + if (!DavisBaudRates.Contains(DavisOptions.BaudRate)) + { + // nope, that isn't allowed, set the default + LogMessage("Error, the value for DavisBaudRate in the ini file " + DavisOptions.BaudRate + " is not valid, using default 19200."); + DavisOptions.BaudRate = 19200; + } + DavisOptions.ForceVPBarUpdate = ini.GetValue("Station", "ForceVPBarUpdate", false); + //DavisUseDLLBarCalData = ini.GetValue("Station", "DavisUseDLLBarCalData", false); + //DavisCalcAltPress = ini.GetValue("Station", "DavisCalcAltPress", true); + //DavisConsoleHighGust = ini.GetValue("Station", "DavisConsoleHighGust", false); + DavisOptions.RainGaugeType = ini.GetValue("Station", "VPrainGaugeType", -1); + if (DavisOptions.RainGaugeType > 3) + { + DavisOptions.RainGaugeType = -1; + } + DavisOptions.ConnectionType = ini.GetValue("Station", "VP2ConnectionType", VP2SERIALCONNECTION); + DavisOptions.TCPPort = ini.GetValue("Station", "VP2TCPPort", 22222); + DavisOptions.IPAddr = ini.GetValue("Station", "VP2IPAddr", "0.0.0.0"); + //VPClosedownTime = ini.GetValue("Station", "VPClosedownTime", 99999999); + //VP2SleepInterval = ini.GetValue("Station", "VP2SleepInterval", 0); + DavisOptions.PeriodicDisconnectInterval = ini.GetValue("Station", "VP2PeriodicDisconnectInterval", 0); + + Latitude = ini.GetValue("Station", "Latitude", 0.0); + if (Latitude > 90 || Latitude < -90) + { + Latitude = 0; + LogMessage($"Error, invalid latitude value in Cumulus.ini [{Latitude}], defaulting to zero."); + } + Longitude = ini.GetValue("Station", "Longitude", 0.0); + if (Longitude > 180 || Longitude < -180) + { + Longitude = 0; + LogMessage($"Error, invalid longitude value in Cumulus.ini [{Longitude}], defaulting to zero."); + } + + LatTxt = ini.GetValue("Station", "LatTxt", ""); + LatTxt = LatTxt.Replace(" ", " "); + LatTxt = LatTxt.Replace("°", "'"); + LonTxt = ini.GetValue("Station", "LonTxt", ""); + LonTxt = LonTxt.Replace(" ", " "); + LonTxt = LonTxt.Replace("°", "'"); + + Altitude = ini.GetValue("Station", "Altitude", 0.0); + AltitudeInFeet = ini.GetValue("Station", "AltitudeInFeet", true); + + StationOptions.Humidity98Fix = ini.GetValue("Station", "Humidity98Fix", false); + StationOptions.UseWind10MinAve = ini.GetValue("Station", "Wind10MinAverage", false); + StationOptions.UseSpeedForAvgCalc = ini.GetValue("Station", "UseSpeedForAvgCalc", false); + + StationOptions.AvgBearingMinutes = ini.GetValue("Station", "AvgBearingMinutes", 10); + if (StationOptions.AvgBearingMinutes > 120) + { + StationOptions.AvgBearingMinutes = 120; + } + if (StationOptions.AvgBearingMinutes == 0) + { + StationOptions.AvgBearingMinutes = 1; + } + + AvgBearingTime = new TimeSpan(StationOptions.AvgBearingMinutes / 60, StationOptions.AvgBearingMinutes % 60, 0); + + StationOptions.AvgSpeedMinutes = ini.GetValue("Station", "AvgSpeedMinutes", 10); + if (StationOptions.AvgSpeedMinutes > 120) + { + StationOptions.AvgSpeedMinutes = 120; + } + if (StationOptions.AvgSpeedMinutes == 0) + { + StationOptions.AvgSpeedMinutes = 1; + } + + AvgSpeedTime = new TimeSpan(StationOptions.AvgSpeedMinutes / 60, StationOptions.AvgSpeedMinutes % 60, 0); + + LogMessage("AvgSpdMins=" + StationOptions.AvgSpeedMinutes + " AvgSpdTime=" + AvgSpeedTime.ToString()); + + StationOptions.PeakGustMinutes = ini.GetValue("Station", "PeakGustMinutes", 10); + if (StationOptions.PeakGustMinutes > 120) + { + StationOptions.PeakGustMinutes = 120; + } + + if (StationOptions.PeakGustMinutes == 0) + { + StationOptions.PeakGustMinutes = 1; + } + + PeakGustTime = new TimeSpan(StationOptions.PeakGustMinutes / 60, StationOptions.PeakGustMinutes % 60, 0); + + if ((StationType == StationTypes.VantagePro) || (StationType == StationTypes.VantagePro2)) + { + UVdecimaldefault = 1; + } + else + { + UVdecimaldefault = 0; + } + + UVdecimals = ini.GetValue("Station", "UVdecimals", UVdecimaldefault); + + StationOptions.NoSensorCheck = ini.GetValue("Station", "NoSensorCheck", false); + + StationOptions.CalculatedDP = ini.GetValue("Station", "CalculatedDP", false); + StationOptions.CalculatedWC = ini.GetValue("Station", "CalculatedWC", false); + RolloverHour = ini.GetValue("Station", "RolloverHour", 0); + Use10amInSummer = ini.GetValue("Station", "Use10amInSummer", true); + //ConfirmClose = ini.GetValue("Station", "ConfirmClose", false); + //CloseOnSuspend = ini.GetValue("Station", "CloseOnSuspend", false); + //RestartIfUnplugged = ini.GetValue("Station", "RestartIfUnplugged", false); + //RestartIfDataStops = ini.GetValue("Station", "RestartIfDataStops", false); + StationOptions.SyncTime = ini.GetValue("Station", "SyncDavisClock", false); + StationOptions.ClockSettingHour = ini.GetValue("Station", "ClockSettingHour", 4); + StationOptions.WS2300IgnoreStationClock = ini.GetValue("Station", "WS2300IgnoreStationClock", false); + //WS2300Sync = ini.GetValue("Station", "WS2300Sync", false); + StationOptions.LogExtraSensors = ini.GetValue("Station", "LogExtraSensors", false); + ReportDataStoppedErrors = ini.GetValue("Station", "ReportDataStoppedErrors", true); + ReportLostSensorContact = ini.GetValue("Station", "ReportLostSensorContact", true); + //NoFlashWetDryDayRecords = ini.GetValue("Station", "NoFlashWetDryDayRecords", false); + ErrorLogSpikeRemoval = ini.GetValue("Station", "ErrorLogSpikeRemoval", true); + DataLogInterval = ini.GetValue("Station", "DataLogInterval", 2); + // this is now an index + if (DataLogInterval > 5) + { + DataLogInterval = 2; + } + + FineOffsetOptions.SyncReads = ini.GetValue("Station", "SyncFOReads", true); + FineOffsetOptions.ReadAvoidPeriod = ini.GetValue("Station", "FOReadAvoidPeriod", 3); + FineOffsetOptions.ReadTime = ini.GetValue("Station", "FineOffsetReadTime", 150); + FineOffsetOptions.SetLoggerInterval = ini.GetValue("Station", "FineOffsetSetLoggerInterval", false); + FineOffsetOptions.VendorID = ini.GetValue("Station", "VendorID", -1); + FineOffsetOptions.ProductID = ini.GetValue("Station", "ProductID", -1); + + + Units.Wind = ini.GetValue("Station", "WindUnit", 0); + Units.Press = ini.GetValue("Station", "PressureUnit", 0); + + Units.Rain = ini.GetValue("Station", "RainUnit", 0); + Units.Temp = ini.GetValue("Station", "TempUnit", 0); + + StationOptions.RoundWindSpeed = ini.GetValue("Station", "RoundWindSpeed", false); + StationOptions.PrimaryAqSensor = ini.GetValue("Station", "PrimaryAqSensor", -1); + + + // Unit decimals + RainDPlaces = RainDPlaceDefaults[Units.Rain]; + TempDPlaces = TempDPlaceDefaults[Units.Temp]; + PressDPlaces = PressDPlaceDefaults[Units.Press]; + WindDPlaces = StationOptions.RoundWindSpeed ? 0 : WindDPlaceDefaults[Units.Wind]; + WindAvgDPlaces = WindDPlaces; + AirQualityDPlaces = 1; + + // Unit decimal overrides + WindDPlaces = ini.GetValue("Station", "WindSpeedDecimals", WindDPlaces); + WindAvgDPlaces = ini.GetValue("Station", "WindSpeedAvgDecimals", WindAvgDPlaces); + WindRunDPlaces = ini.GetValue("Station", "WindRunDecimals", WindRunDPlaces); + SunshineDPlaces = ini.GetValue("Station", "SunshineHrsDecimals", 1); + + if ((StationType == 0 || StationType == 1) && DavisOptions.IncrementPressureDP) + { + // Use one more DP for Davis stations + ++PressDPlaces; + } + PressDPlaces = ini.GetValue("Station", "PressDecimals", PressDPlaces); + RainDPlaces = ini.GetValue("Station", "RainDecimals", RainDPlaces); + TempDPlaces = ini.GetValue("Station", "TempDecimals", TempDPlaces); + UVDPlaces = ini.GetValue("Station", "UVDecimals", UVDPlaces); + AirQualityDPlaces = ini.GetValue("Station", "AirQualityDecimals", AirQualityDPlaces); + + + LocationName = ini.GetValue("Station", "LocName", ""); + LocationDesc = ini.GetValue("Station", "LocDesc", ""); + + YTDrain = ini.GetValue("Station", "YTDrain", 0.0); + YTDrainyear = ini.GetValue("Station", "YTDrainyear", 0); + + EwOptions.Interval = ini.GetValue("Station", "EWInterval", 1.0); + EwOptions.Filename = ini.GetValue("Station", "EWFile", ""); + //EWallowFF = ini.GetValue("Station", "EWFF", false); + //EWdisablecheckinit = ini.GetValue("Station", "EWdisablecheckinit", false); + //EWduplicatecheck = ini.GetValue("Station", "EWduplicatecheck", true); + EwOptions.MinPressMB = ini.GetValue("Station", "EWminpressureMB", 900); + EwOptions.MaxPressMB = ini.GetValue("Station", "EWmaxpressureMB", 1200); + EwOptions.MaxRainTipDiff = ini.GetValue("Station", "EWMaxRainTipDiff", 30); + EwOptions.PressOffset = ini.GetValue("Station", "EWpressureoffset", 9999.0); + + Spike.TempDiff = ini.GetValue("Station", "EWtempdiff", 999.0); + Spike.PressDiff = ini.GetValue("Station", "EWpressurediff", 999.0); + Spike.HumidityDiff = ini.GetValue("Station", "EWhumiditydiff", 999.0); + Spike.GustDiff = ini.GetValue("Station", "EWgustdiff", 999.0); + Spike.WindDiff = ini.GetValue("Station", "EWwinddiff", 999.0); + Spike.MaxRainRate = ini.GetValue("Station", "EWmaxRainRate", 999.0); + Spike.MaxHourlyRain = ini.GetValue("Station", "EWmaxHourlyRain", 999.0); + + LCMaxWind = ini.GetValue("Station", "LCMaxWind", 9999); + + RecordsBeganDate = ini.GetValue("Station", "StartDate", DateTime.Now.ToLongDateString()); + + LogMessage("Cumulus start date: " + RecordsBeganDate); + + ImetOptions.WaitTime = ini.GetValue("Station", "ImetWaitTime", 500); // delay to wait for a reply to a command + ImetOptions.ReadDelay = ini.GetValue("Station", "ImetReadDelay", 500); // delay between sending read live data commands + ImetOptions.UpdateLogPointer = ini.GetValue("Station", "ImetUpdateLogPointer", true); // keep the logger pointer pointing at last data read + ImetOptions.BaudRate = ini.GetValue("Station", "ImetBaudRate", 19200); + // Check we have a valid value + if (!ImetOptions.BaudRates.Contains(ImetOptions.BaudRate)) + { + // nope, that isn't allowed, set the default + LogMessage("Error, the value for ImetOptions.ImetBaudRate in the ini file " + ImetOptions.BaudRate + " is not valid, using default 19200."); + ImetOptions.BaudRate = 19200; + } + + UseDataLogger = ini.GetValue("Station", "UseDataLogger", true); + UseCumulusForecast = ini.GetValue("Station", "UseCumulusForecast", false); + HourlyForecast = ini.GetValue("Station", "HourlyForecast", false); + StationOptions.UseCumulusPresstrendstr = ini.GetValue("Station", "UseCumulusPresstrendstr", false); + UseWindChillCutoff = ini.GetValue("Station", "UseWindChillCutoff", false); + RecordSetTimeoutHrs = ini.GetValue("Station", "RecordSetTimeoutHrs", 24); + + SnowDepthHour = ini.GetValue("Station", "SnowDepthHour", 0); + + StationOptions.UseZeroBearing = ini.GetValue("Station", "UseZeroBearing", false); + + RainDayThreshold = ini.GetValue("Station", "RainDayThreshold", -1.0); + + FCpressinMB = ini.GetValue("Station", "FCpressinMB", true); + FClowpress = ini.GetValue("Station", "FClowpress", DEFAULTFCLOWPRESS); + FChighpress = ini.GetValue("Station", "FChighpress", DEFAULTFCHIGHPRESS); + FCPressureThreshold = ini.GetValue("Station", "FCPressureThreshold", -1.0); + + RainSeasonStart = ini.GetValue("Station", "RainSeasonStart", 1); + if (RainSeasonStart < 1 || RainSeasonStart > 12) + RainSeasonStart = 1; + ChillHourSeasonStart = ini.GetValue("Station", "ChillHourSeasonStart", 10); + if (ChillHourSeasonStart < 1 || ChillHourSeasonStart > 12) + ChillHourSeasonStart = 1; + ChillHourThreshold = ini.GetValue("Station", "ChillHourThreshold", -999.0); + + RG11Enabled = ini.GetValue("Station", "RG11Enabled", false); + RG11Port = ini.GetValue("Station", "RG11portName", DefaultComportName); + RG11TBRmode = ini.GetValue("Station", "RG11TBRmode", false); + RG11tipsize = ini.GetValue("Station", "RG11tipsize", 0.0); + RG11IgnoreFirst = ini.GetValue("Station", "RG11IgnoreFirst", false); + RG11DTRmode = ini.GetValue("Station", "RG11DTRmode", true); + + RG11Enabled2 = ini.GetValue("Station", "RG11Enabled2", false); + RG11Port2 = ini.GetValue("Station", "RG11port2Name", DefaultComportName); + RG11TBRmode2 = ini.GetValue("Station", "RG11TBRmode2", false); + RG11tipsize2 = ini.GetValue("Station", "RG11tipsize2", 0.0); + RG11IgnoreFirst2 = ini.GetValue("Station", "RG11IgnoreFirst2", false); + RG11DTRmode2 = ini.GetValue("Station", "RG11DTRmode2", true); + + if (ChillHourThreshold < -998) + { + ChillHourThreshold = Units.Temp == 0 ? 7 : 45; + } + + if (FCPressureThreshold < 0) + { + FCPressureThreshold = Units.Press == 2 ? 0.00295333727 : 0.1; + } + + //special_logging = ini.GetValue("Station", "SpecialLog", false); + //solar_logging = ini.GetValue("Station", "SolarLog", false); + + + //RTdisconnectcount = ini.GetValue("Station", "RTdisconnectcount", 0); + + WMR928TempChannel = ini.GetValue("Station", "WMR928TempChannel", 0); + + WMR200TempChannel = ini.GetValue("Station", "WMR200TempChannel", 1); + + ListWebTags = ini.GetValue("Station", "ListWebTags", false); + + // WeatherLink Live device settings + WllApiKey = ini.GetValue("WLL", "WLv2ApiKey", ""); + WllApiSecret = ini.GetValue("WLL", "WLv2ApiSecret", ""); + WllStationId = ini.GetValue("WLL", "WLStationId", -1); + //if (WllStationId == "-1") WllStationId = ""; + WLLAutoUpdateIpAddress = ini.GetValue("WLL", "AutoUpdateIpAddress", true); + WllBroadcastDuration = ini.GetValue("WLL", "BroadcastDuration", 1200); // Readonly setting, default 20 minutes + WllBroadcastPort = ini.GetValue("WLL", "BroadcastPort", 22222); // Readonly setting, default 22222 + WllPrimaryRain = ini.GetValue("WLL", "PrimaryRainTxId", 1); + WllPrimaryTempHum = ini.GetValue("WLL", "PrimaryTempHumTxId", 1); + WllPrimaryWind = ini.GetValue("WLL", "PrimaryWindTxId", 1); + WllPrimaryRain = ini.GetValue("WLL", "PrimaryRainTxId", 1); + WllPrimarySolar = ini.GetValue("WLL", "PrimarySolarTxId", 0); + WllPrimaryUV = ini.GetValue("WLL", "PrimaryUvTxId", 0); + WllExtraSoilTempTx1 = ini.GetValue("WLL", "ExtraSoilTempTxId1", 0); + WllExtraSoilTempIdx1 = ini.GetValue("WLL", "ExtraSoilTempIdx1", 1); + WllExtraSoilTempTx2 = ini.GetValue("WLL", "ExtraSoilTempTxId2", 0); + WllExtraSoilTempIdx2 = ini.GetValue("WLL", "ExtraSoilTempIdx2", 2); + WllExtraSoilTempTx3 = ini.GetValue("WLL", "ExtraSoilTempTxId3", 0); + WllExtraSoilTempIdx3 = ini.GetValue("WLL", "ExtraSoilTempIdx3", 3); + WllExtraSoilTempTx4 = ini.GetValue("WLL", "ExtraSoilTempTxId4", 0); + WllExtraSoilTempIdx4 = ini.GetValue("WLL", "ExtraSoilTempIdx4", 4); + WllExtraSoilMoistureTx1 = ini.GetValue("WLL", "ExtraSoilMoistureTxId1", 0); + WllExtraSoilMoistureIdx1 = ini.GetValue("WLL", "ExtraSoilMoistureIdx1", 1); + WllExtraSoilMoistureTx2 = ini.GetValue("WLL", "ExtraSoilMoistureTxId2", 0); + WllExtraSoilMoistureIdx2 = ini.GetValue("WLL", "ExtraSoilMoistureIdx2", 2); + WllExtraSoilMoistureTx3 = ini.GetValue("WLL", "ExtraSoilMoistureTxId3", 0); + WllExtraSoilMoistureIdx3 = ini.GetValue("WLL", "ExtraSoilMoistureIdx3", 3); + WllExtraSoilMoistureTx4 = ini.GetValue("WLL", "ExtraSoilMoistureTxId4", 0); + WllExtraSoilMoistureIdx4 = ini.GetValue("WLL", "ExtraSoilMoistureIdx4", 4); + WllExtraLeafTx1 = ini.GetValue("WLL", "ExtraLeafTxId1", 0); + WllExtraLeafIdx1 = ini.GetValue("WLL", "ExtraLeafIdx1", 1); + WllExtraLeafTx2 = ini.GetValue("WLL", "ExtraLeafTxId2", 0); + WllExtraLeafIdx2 = ini.GetValue("WLL", "ExtraLeafIdx2", 2); + for (int i = 1; i <=8; i++) + { + WllExtraTempTx[i - 1] = ini.GetValue("WLL", "ExtraTempTxId" + i, 0); + WllExtraHumTx[i - 1] = ini.GetValue("WLL", "ExtraHumOnTxId" + i, false); + } + + // GW1000 settings + Gw1000IpAddress = ini.GetValue("GW1000", "IPAddress", "0.0.0.0"); + Gw1000MacAddress = ini.GetValue("GW1000", "MACAddress", ""); + Gw1000AutoUpdateIpAddress = ini.GetValue("GW1000", "AutoUpdateIpAddress", true); + + // AirLink settings + // We have to convert previous per AL IsNode config to global + // So check if the global value exists + if (ini.ValueExists("AirLink", "IsWllNode")) + { + AirLinkIsNode = ini.GetValue("AirLink", "IsWllNode", false); + } + else + { + AirLinkIsNode = ini.GetValue("AirLink", "In-IsNode", false) || ini.GetValue("AirLink", "Out-IsNode", false); + } + AirLinkApiKey = ini.GetValue("AirLink", "WLv2ApiKey", ""); + AirLinkApiSecret = ini.GetValue("AirLink", "WLv2ApiSecret", ""); + AirLinkAutoUpdateIpAddress = ini.GetValue("AirLink", "AutoUpdateIpAddress", true); + AirLinkInEnabled = ini.GetValue("AirLink", "In-Enabled", false); + AirLinkInIPAddr = ini.GetValue("AirLink", "In-IPAddress", "0.0.0.0"); + AirLinkInStationId = ini.GetValue("AirLink", "In-WLStationId", -1); + if (AirLinkInStationId == -1 && AirLinkIsNode) AirLinkInStationId = WllStationId; + AirLinkInHostName = ini.GetValue("AirLink", "In-Hostname", ""); + + AirLinkOutEnabled = ini.GetValue("AirLink", "Out-Enabled", false); + AirLinkOutIPAddr = ini.GetValue("AirLink", "Out-IPAddress", "0.0.0.0"); + AirLinkOutStationId = ini.GetValue("AirLink", "Out-WLStationId", -1); + if (AirLinkOutStationId == -1 && AirLinkIsNode) AirLinkOutStationId = WllStationId; + AirLinkOutHostName = ini.GetValue("AirLink", "Out-Hostname", ""); + + airQualityIndex = ini.GetValue("AirLink", "AQIformula", 0); + + FtpHostname = ini.GetValue("FTP site", "Host", ""); + FtpHostPort = ini.GetValue("FTP site", "Port", 21); + FtpUsername = ini.GetValue("FTP site", "Username", ""); + FtpPassword = ini.GetValue("FTP site", "Password", ""); + FtpDirectory = ini.GetValue("FTP site", "Directory", ""); + + ActiveFTPMode = ini.GetValue("FTP site", "ActiveFTP", false); + Sslftp = (FtpProtocols)ini.GetValue("FTP site", "Sslftp", 0); + // BUILD 3092 - added alternate SFTP authenication options + SshftpAuthentication = ini.GetValue("FTP site", "SshFtpAuthentication", "password"); // valid options: password, psk, password_psk + if (!sshAuthenticationVals.Any(SshftpAuthentication.Contains)) + { + SshftpAuthentication = "password"; + LogMessage($"Error, invalid SshFtpAuthentication value in Cumulus.ini [{SshftpAuthentication}], defaulting to Password."); + } + SshftpPskFile = ini.GetValue("FTP site", "SshFtpPskFile", ""); + if (SshftpPskFile.Length > 0 && (SshftpAuthentication == "psk" || SshftpAuthentication == "password_psk") && !File.Exists(SshftpPskFile)) + { + SshftpPskFile = ""; + LogMessage($"Error, file name specified by SshFtpPskFile value in Cumulus.ini does not exist [{SshftpPskFile}], defaulting to None."); + } + DisableFtpsEPSV = ini.GetValue("FTP site", "DisableEPSV", false); + DisableFtpsExplicit = ini.GetValue("FTP site", "DisableFtpsExplicit", false); + FTPlogging = ini.GetValue("FTP site", "FTPlogging", false); + RealtimeEnabled = ini.GetValue("FTP site", "EnableRealtime", false); + RealtimeFTPEnabled = ini.GetValue("FTP site", "RealtimeFTPEnabled", false); + + RealtimeFiles[0].Create = ini.GetValue("FTP site", "RealtimeTxtCreate", false); + RealtimeFiles[0].FTP = RealtimeFiles[0].Create && ini.GetValue("FTP site", "RealtimeTxtFTP", false); + RealtimeFiles[1].Create = ini.GetValue("FTP site", "RealtimeGaugesTxtCreate", false); + RealtimeFiles[1].FTP = RealtimeFiles[1].Create && ini.GetValue("FTP site", "RealtimeGaugesTxtFTP", false); + + RealtimeInterval = ini.GetValue("FTP site", "RealtimeInterval", 30000); + if (RealtimeInterval < 1) { RealtimeInterval = 1; } + //RealtimeTimer.Change(0,RealtimeInterval); + + WebAutoUpdate = ini.GetValue("FTP site", "AutoUpdate", false); + // Have to allow for upgrade, set interval enabled to old WebAutoUpdate + if (ini.ValueExists("FTP site", "IntervalEnabled")) + { + WebIntervalEnabled = ini.GetValue("FTP site", "IntervalEnabled", false); + } + else + { + WebIntervalEnabled = WebAutoUpdate; + } + + UpdateInterval = ini.GetValue("FTP site", "UpdateInterval", DefaultWebUpdateInterval); + if (UpdateInterval<1) { UpdateInterval = 1; } + SynchronisedWebUpdate = (60 % UpdateInterval == 0); + + var IncludeStandardFiles = false; + if (ini.ValueExists("FTP site", "IncludeSTD")) + { + IncludeStandardFiles = ini.GetValue("FTP site", "IncludeSTD", false); + } + for (var i = 0; i < StdWebFiles.Length; i++) + { + var keyNameCreate = "Create-" + StdWebFiles[i].LocalFileName.Split('.')[0]; + var keyNameFTP = "Ftp-" + StdWebFiles[i].LocalFileName.Split('.')[0]; + StdWebFiles[i].Create = ini.GetValue("FTP site", keyNameCreate, IncludeStandardFiles); + StdWebFiles[i].FTP = ini.GetValue("FTP site", keyNameFTP, IncludeStandardFiles); + } + + var IncludeGraphDataFiles = false; + if (ini.ValueExists("FTP site", "IncludeGraphDataFiles")) + { + IncludeGraphDataFiles = ini.GetValue("FTP site", "IncludeGraphDataFiles", true); + } + for (var i = 0; i < GraphDataFiles.Length; i++) + { + var keyNameCreate = "Create-" + GraphDataFiles[i].LocalFileName.Split('.')[0]; + var keyNameFTP = "Ftp-" + GraphDataFiles[i].LocalFileName.Split('.')[0]; + GraphDataFiles[i].Create = ini.GetValue("FTP site", keyNameCreate, IncludeGraphDataFiles); + GraphDataFiles[i].FTP = ini.GetValue("FTP site", keyNameFTP, IncludeGraphDataFiles); + } + for (var i = 0; i < GraphDataEodFiles.Length; i++) + { + var keyNameCreate = "Create-" + GraphDataEodFiles[i].LocalFileName.Split('.')[0]; + var keyNameFTP = "Ftp-" + GraphDataEodFiles[i].LocalFileName.Split('.')[0]; + GraphDataEodFiles[i].Create = ini.GetValue("FTP site", keyNameCreate, IncludeGraphDataFiles); + GraphDataEodFiles[i].FTP = ini.GetValue("FTP site", keyNameFTP, IncludeGraphDataFiles); + } + + IncludeMoonImage = ini.GetValue("FTP site", "IncludeMoonImage", false); + + FTPRename = ini.GetValue("FTP site", "FTPRename", false); + UTF8encode = ini.GetValue("FTP site", "UTF8encode", true); + DeleteBeforeUpload = ini.GetValue("FTP site", "DeleteBeforeUpload", false); + + //MaxFTPconnectRetries = ini.GetValue("FTP site", "MaxFTPconnectRetries", 3); + + for (int i = 0; i < numextrafiles; i++) + { + ExtraFiles[i].local = ini.GetValue("FTP site", "ExtraLocal" + i, ""); + ExtraFiles[i].remote = ini.GetValue("FTP site", "ExtraRemote" + i, ""); + ExtraFiles[i].process = ini.GetValue("FTP site", "ExtraProcess" + i, false); + ExtraFiles[i].binary = ini.GetValue("FTP site", "ExtraBinary" + i, false); + ExtraFiles[i].realtime = ini.GetValue("FTP site", "ExtraRealtime" + i, false); + ExtraFiles[i].FTP = ini.GetValue("FTP site", "ExtraFTP" + i, true); + ExtraFiles[i].UTF8 = ini.GetValue("FTP site", "ExtraUTF" + i, false); + ExtraFiles[i].endofday = ini.GetValue("FTP site", "ExtraEOD" + i, false); + } + + ExternalProgram = ini.GetValue("FTP site", "ExternalProgram", ""); + RealtimeProgram = ini.GetValue("FTP site", "RealtimeProgram", ""); + DailyProgram = ini.GetValue("FTP site", "DailyProgram", ""); + ExternalParams = ini.GetValue("FTP site", "ExternalParams", ""); + RealtimeParams = ini.GetValue("FTP site", "RealtimeParams", ""); + DailyParams = ini.GetValue("FTP site", "DailyParams", ""); + + ForumURL = ini.GetValue("Web Site", "ForumURL", ForumDefault); + WebcamURL = ini.GetValue("Web Site", "WebcamURL", WebcamDefault); + + CloudBaseInFeet = ini.GetValue("Station", "CloudBaseInFeet", true); + + GraphDays = ini.GetValue("Graphs", "ChartMaxDays", 31); + GraphHours = ini.GetValue("Graphs", "GraphHours", 72); + MoonImageEnabled = ini.GetValue("Graphs", "MoonImageEnabled", false); + MoonImageSize = ini.GetValue("Graphs", "MoonImageSize", 100); + if (MoonImageSize < 10) + MoonImageSize = 10; + MoonImageFtpDest = ini.GetValue("Graphs", "MoonImageFtpDest", "images/moon.png"); + GraphOptions.TempVisible = ini.GetValue("Graphs", "TempVisible", true); + GraphOptions.InTempVisible = ini.GetValue("Graphs", "InTempVisible", true); + GraphOptions.HIVisible = ini.GetValue("Graphs", "HIVisible", true); + GraphOptions.DPVisible = ini.GetValue("Graphs", "DPVisible", true); + GraphOptions.WCVisible = ini.GetValue("Graphs", "WCVisible", true); + GraphOptions.AppTempVisible = ini.GetValue("Graphs", "AppTempVisible", true); + GraphOptions.FeelsLikeVisible = ini.GetValue("Graphs", "FeelsLikeVisible", true); + GraphOptions.HumidexVisible = ini.GetValue("Graphs", "HumidexVisible", true); + GraphOptions.InHumVisible = ini.GetValue("Graphs", "InHumVisible", true); + GraphOptions.OutHumVisible = ini.GetValue("Graphs", "OutHumVisible", true); + GraphOptions.UVVisible = ini.GetValue("Graphs", "UVVisible", true); + GraphOptions.SolarVisible = ini.GetValue("Graphs", "SolarVisible", true); + GraphOptions.SunshineVisible = ini.GetValue("Graphs", "SunshineVisible", true); + GraphOptions.DailyAvgTempVisible = ini.GetValue("Graphs", "DailyAvgTempVisible", true); + GraphOptions.DailyMaxTempVisible = ini.GetValue("Graphs", "DailyMaxTempVisible", true); + GraphOptions.DailyMinTempVisible = ini.GetValue("Graphs", "DailyMinTempVisible", true); + GraphOptions.GrowingDegreeDaysVisible1 = ini.GetValue("Graphs", "GrowingDegreeDaysVisible1", true); + GraphOptions.GrowingDegreeDaysVisible2 = ini.GetValue("Graphs", "GrowingDegreeDaysVisible2", true); + GraphOptions.TempSumVisible0 = ini.GetValue("Graphs", "TempSumVisible0", true); + GraphOptions.TempSumVisible1 = ini.GetValue("Graphs", "TempSumVisible1", true); + GraphOptions.TempSumVisible2 = ini.GetValue("Graphs", "TempSumVisible2", true); + + + Wund.ID = ini.GetValue("Wunderground", "ID", ""); + Wund.PW = ini.GetValue("Wunderground", "Password", ""); + Wund.Enabled = ini.GetValue("Wunderground", "Enabled", false); + Wund.RapidFireEnabled = ini.GetValue("Wunderground", "RapidFire", false); + Wund.Interval = ini.GetValue("Wunderground", "Interval", Wund.DefaultInterval); + //WundHTTPLogging = ini.GetValue("Wunderground", "Logging", false); + Wund.SendUV = ini.GetValue("Wunderground", "SendUV", false); + Wund.SendSolar = ini.GetValue("Wunderground", "SendSR", false); + Wund.SendIndoor = ini.GetValue("Wunderground", "SendIndoor", false); + Wund.SendSoilTemp1 = ini.GetValue("Wunderground", "SendSoilTemp1", false); + Wund.SendSoilTemp2 = ini.GetValue("Wunderground", "SendSoilTemp2", false); + Wund.SendSoilTemp3 = ini.GetValue("Wunderground", "SendSoilTemp3", false); + Wund.SendSoilTemp4 = ini.GetValue("Wunderground", "SendSoilTemp4", false); + Wund.SendSoilMoisture1 = ini.GetValue("Wunderground", "SendSoilMoisture1", false); + Wund.SendSoilMoisture2 = ini.GetValue("Wunderground", "SendSoilMoisture2", false); + Wund.SendSoilMoisture3 = ini.GetValue("Wunderground", "SendSoilMoisture3", false); + Wund.SendSoilMoisture4 = ini.GetValue("Wunderground", "SendSoilMoisture4", false); + Wund.SendLeafWetness1 = ini.GetValue("Wunderground", "SendLeafWetness1", false); + Wund.SendLeafWetness2 = ini.GetValue("Wunderground", "SendLeafWetness2", false); + Wund.SendAirQuality = ini.GetValue("Wunderground", "SendAirQuality", false); + Wund.SendAverage = ini.GetValue("Wunderground", "SendAverage", false); + Wund.CatchUp = ini.GetValue("Wunderground", "CatchUp", true); + + Wund.SynchronisedUpdate = !Wund.RapidFireEnabled; + + Windy.ApiKey = ini.GetValue("Windy", "APIkey", ""); + Windy.StationIdx = ini.GetValue("Windy", "StationIdx", 0); + Windy.Enabled = ini.GetValue("Windy", "Enabled", false); + Windy.Interval = ini.GetValue("Windy", "Interval", Windy.DefaultInterval); + if (Windy.Interval < 5) { Windy.Interval = 5; } + //WindyHTTPLogging = ini.GetValue("Windy", "Logging", false); + Windy.SendUV = ini.GetValue("Windy", "SendUV", false); + Windy.SendSolar = ini.GetValue("Windy", "SendSolar", false); + Windy.CatchUp = ini.GetValue("Windy", "CatchUp", false); + + AWEKAS.ID = ini.GetValue("Awekas", "User", ""); + AWEKAS.PW = ini.GetValue("Awekas", "Password", ""); + AWEKAS.Enabled = ini.GetValue("Awekas", "Enabled", false); + AWEKAS.Interval = ini.GetValue("Awekas", "Interval", AWEKAS.DefaultInterval); + if (AWEKAS.Interval < 15) { AWEKAS.Interval = 15; } + AWEKAS.Lang = ini.GetValue("Awekas", "Language", "en"); + AWEKAS.OriginalInterval = AWEKAS.Interval; + AWEKAS.SendUV = ini.GetValue("Awekas", "SendUV", false); + AWEKAS.SendSolar = ini.GetValue("Awekas", "SendSR", false); + AWEKAS.SendSoilTemp = ini.GetValue("Awekas", "SendSoilTemp", false); + AWEKAS.SendIndoor = ini.GetValue("Awekas", "SendIndoor", false); + AWEKAS.SendSoilMoisture = ini.GetValue("Awekas", "SendSoilMoisture", false); + AWEKAS.SendLeafWetness = ini.GetValue("Awekas", "SendLeafWetness", false); + AWEKAS.SendAirQuality = ini.GetValue("Awekas", "SendAirQuality", false); + + AWEKAS.SynchronisedUpdate = (AWEKAS.Interval % 60 == 0); + + WindGuru.ID = ini.GetValue("WindGuru", "StationUID", ""); + WindGuru.PW = ini.GetValue("WindGuru", "Password", ""); + WindGuru.Enabled = ini.GetValue("WindGuru", "Enabled", false); + WindGuru.Interval = ini.GetValue("WindGuru", "Interval", WindGuru.DefaultInterval); + if (WindGuru.Interval < 1) { WindGuru.Interval = 1; } + WindGuru.SendRain = ini.GetValue("WindGuru", "SendRain", false); + + WCloud.ID = ini.GetValue("WeatherCloud", "Wid", ""); + WCloud.PW = ini.GetValue("WeatherCloud", "Key", ""); + WCloud.Enabled = ini.GetValue("WeatherCloud", "Enabled", false); + WCloud.Interval = ini.GetValue("WeatherCloud", "Interval", WCloud.DefaultInterval); + WCloud.SendUV = ini.GetValue("WeatherCloud", "SendUV", false); + WCloud.SendSolar = ini.GetValue("WeatherCloud", "SendSR", false); + WCloud.SendAirQuality = ini.GetValue("WeatherCloud", "SendAirQuality", false); + WCloud.SendSoilMoisture = ini.GetValue("WeatherCloud", "SendSoilMoisture", false); + WCloud.SoilMoistureSensor= ini.GetValue("WeatherCloud", "SoilMoistureSensor", 1); + WCloud.SendLeafWetness = ini.GetValue("WeatherCloud", "SendLeafWetness", false); + WCloud.LeafWetnessSensor = ini.GetValue("WeatherCloud", "LeafWetnessSensor", 1); + + Twitter.ID = ini.GetValue("Twitter", "User", ""); + Twitter.PW = ini.GetValue("Twitter", "Password", ""); + Twitter.Enabled = ini.GetValue("Twitter", "Enabled", false); + Twitter.Interval = ini.GetValue("Twitter", "Interval", 60); + if (Twitter.Interval < 1) { Twitter.Interval = 1; } + Twitter.OauthToken = ini.GetValue("Twitter", "OauthToken", "unknown"); + Twitter.OauthTokenSecret = ini.GetValue("Twitter", "OauthTokenSecret", "unknown"); + Twitter.SendLocation = ini.GetValue("Twitter", "SendLocation", true); + + //if HTTPLogging then + // MainForm.WUHTTP.IcsLogger = MainForm.HTTPlogger; + + PWS.ID = ini.GetValue("PWSweather", "ID", ""); + PWS.PW = ini.GetValue("PWSweather", "Password", ""); + PWS.Enabled = ini.GetValue("PWSweather", "Enabled", false); + PWS.Interval = ini.GetValue("PWSweather", "Interval", PWS.DefaultInterval); + if (PWS.Interval < 1) { PWS.Interval = 1; } + PWS.SendUV = ini.GetValue("PWSweather", "SendUV", false); + PWS.SendSolar = ini.GetValue("PWSweather", "SendSR", false); + PWS.CatchUp = ini.GetValue("PWSweather", "CatchUp", true); + + WOW.ID = ini.GetValue("WOW", "ID", ""); + WOW.PW = ini.GetValue("WOW", "Password", ""); + WOW.Enabled = ini.GetValue("WOW", "Enabled", false); + WOW.Interval = ini.GetValue("WOW", "Interval", WOW.DefaultInterval); + if (WOW.Interval < 1) { WOW.Interval = 1; } + WOW.SendUV = ini.GetValue("WOW", "SendUV", false); + WOW.SendSolar = ini.GetValue("WOW", "SendSR", false); + WOW.CatchUp = ini.GetValue("WOW", "CatchUp", true); + + APRS.ID = ini.GetValue("APRS", "ID", ""); + APRS.PW = ini.GetValue("APRS", "pass", "-1"); + APRS.Server = ini.GetValue("APRS", "server", "cwop.aprs.net"); + APRS.Port = ini.GetValue("APRS", "port", 14580); + APRS.Enabled = ini.GetValue("APRS", "Enabled", false); + APRS.Interval = ini.GetValue("APRS", "Interval", APRS.DefaultInterval); + if (APRS.Interval < 1) { APRS.Interval = 1; } + APRS.HumidityCutoff = ini.GetValue("APRS", "APRSHumidityCutoff", false); + APRS.SendSolar = ini.GetValue("APRS", "SendSR", false); + + OpenWeatherMap.Enabled = ini.GetValue("OpenWeatherMap", "Enabled", false); + OpenWeatherMap.CatchUp = ini.GetValue("OpenWeatherMap", "CatchUp", true); + OpenWeatherMap.PW = ini.GetValue("OpenWeatherMap", "APIkey", ""); + OpenWeatherMap.ID = ini.GetValue("OpenWeatherMap", "StationId", ""); + OpenWeatherMap.Interval = ini.GetValue("OpenWeatherMap", "Interval", OpenWeatherMap.DefaultInterval); + + MQTT.Server = ini.GetValue("MQTT", "Server", ""); + MQTT.Port = ini.GetValue("MQTT", "Port", 1883); + MQTT.IpVersion = ini.GetValue("MQTT", "IPversion", 0); // 0 = unspecified, 4 = force IPv4, 6 = force IPv6 + if (MQTT.IpVersion != 0 && MQTT.IpVersion != 4 && MQTT.IpVersion != 6) + MQTT.IpVersion = 0; + MQTT.UseTLS = ini.GetValue("MQTT", "UseTLS", false); + MQTT.Username = ini.GetValue("MQTT", "Username", ""); + MQTT.Password = ini.GetValue("MQTT", "Password", ""); + MQTT.EnableDataUpdate = ini.GetValue("MQTT", "EnableDataUpdate", false); + MQTT.UpdateTopic = ini.GetValue("MQTT", "UpdateTopic", "CumulusMX/DataUpdate"); + MQTT.UpdateTemplate = ini.GetValue("MQTT", "UpdateTemplate", "DataUpdateTemplate.txt"); + MQTT.UpdateRetained = ini.GetValue("MQTT", "UpdateRetained", false); + MQTT.EnableInterval = ini.GetValue("MQTT", "EnableInterval", false); + MQTT.IntervalTime = ini.GetValue("MQTT", "IntervalTime", 600); // default to 10 minutes + MQTT.IntervalTopic = ini.GetValue("MQTT", "IntervalTopic", "CumulusMX/Interval"); + MQTT.IntervalTemplate = ini.GetValue("MQTT", "IntervalTemplate", "IntervalTemplate.txt"); + MQTT.IntervalRetained = ini.GetValue("MQTT", "IntervalRetained", false); + + LowTempAlarm.Value = ini.GetValue("Alarms", "alarmlowtemp", 0.0); + LowTempAlarm.Enabled = ini.GetValue("Alarms", "LowTempAlarmSet", false); + LowTempAlarm.Sound = ini.GetValue("Alarms", "LowTempAlarmSound", false); + LowTempAlarm.SoundFile = ini.GetValue("Alarms", "LowTempAlarmSoundFile", DefaultSoundFile); + if (LowTempAlarm.SoundFile.Contains(DefaultSoundFileOld)) LowTempAlarm.SoundFile = DefaultSoundFile; + LowTempAlarm.Notify = ini.GetValue("Alarms", "LowTempAlarmNotify", false); + LowTempAlarm.Email = ini.GetValue("Alarms", "LowTempAlarmEmail", false); + LowTempAlarm.Latch = ini.GetValue("Alarms", "LowTempAlarmLatch", false); + LowTempAlarm.LatchHours = ini.GetValue("Alarms", "LowTempAlarmLatchHours", 24); + + HighTempAlarm.Value = ini.GetValue("Alarms", "alarmhightemp", 0.0); + HighTempAlarm.Enabled = ini.GetValue("Alarms", "HighTempAlarmSet", false); + HighTempAlarm.Sound = ini.GetValue("Alarms", "HighTempAlarmSound", false); + HighTempAlarm.SoundFile = ini.GetValue("Alarms", "HighTempAlarmSoundFile", DefaultSoundFile); + if (HighTempAlarm.SoundFile.Contains(DefaultSoundFileOld)) HighTempAlarm.SoundFile = DefaultSoundFile; + HighTempAlarm.Notify = ini.GetValue("Alarms", "HighTempAlarmNotify", false); + HighTempAlarm.Email = ini.GetValue("Alarms", "HighTempAlarmEmail", false); + HighTempAlarm.Latch = ini.GetValue("Alarms", "HighTempAlarmLatch", false); + HighTempAlarm.LatchHours = ini.GetValue("Alarms", "HighTempAlarmLatchHours", 24); + + TempChangeAlarm.Value = ini.GetValue("Alarms", "alarmtempchange", 0.0); + TempChangeAlarm.Enabled = ini.GetValue("Alarms", "TempChangeAlarmSet", false); + TempChangeAlarm.Sound = ini.GetValue("Alarms", "TempChangeAlarmSound", false); + TempChangeAlarm.SoundFile = ini.GetValue("Alarms", "TempChangeAlarmSoundFile", DefaultSoundFile); + if (TempChangeAlarm.SoundFile.Contains(DefaultSoundFileOld)) TempChangeAlarm.SoundFile = DefaultSoundFile; + TempChangeAlarm.Notify = ini.GetValue("Alarms", "TempChangeAlarmNotify", false); + TempChangeAlarm.Email = ini.GetValue("Alarms", "TempChangeAlarmEmail", false); + TempChangeAlarm.Latch = ini.GetValue("Alarms", "TempChangeAlarmLatch", false); + TempChangeAlarm.LatchHours = ini.GetValue("Alarms", "TempChangeAlarmLatchHours", 24); + + LowPressAlarm.Value = ini.GetValue("Alarms", "alarmlowpress", 0.0); + LowPressAlarm.Enabled = ini.GetValue("Alarms", "LowPressAlarmSet", false); + LowPressAlarm.Sound = ini.GetValue("Alarms", "LowPressAlarmSound", false); + LowPressAlarm.SoundFile = ini.GetValue("Alarms", "LowPressAlarmSoundFile", DefaultSoundFile); + if (LowPressAlarm.SoundFile.Contains(DefaultSoundFileOld)) LowPressAlarm.SoundFile = DefaultSoundFile; + LowPressAlarm.Notify = ini.GetValue("Alarms", "LowPressAlarmNotify", false); + LowPressAlarm.Email = ini.GetValue("Alarms", "LowPressAlarmEmail", false); + LowPressAlarm.Latch = ini.GetValue("Alarms", "LowPressAlarmLatch", false); + LowPressAlarm.LatchHours = ini.GetValue("Alarms", "LowPressAlarmLatchHours", 24); + + HighPressAlarm.Value = ini.GetValue("Alarms", "alarmhighpress", 0.0); + HighPressAlarm.Enabled = ini.GetValue("Alarms", "HighPressAlarmSet", false); + HighPressAlarm.Sound = ini.GetValue("Alarms", "HighPressAlarmSound", false); + HighPressAlarm.SoundFile = ini.GetValue("Alarms", "HighPressAlarmSoundFile", DefaultSoundFile); + if (HighPressAlarm.SoundFile.Contains(DefaultSoundFileOld)) HighPressAlarm.SoundFile = DefaultSoundFile; + HighPressAlarm.Notify = ini.GetValue("Alarms", "HighPressAlarmNotify", false); + HighPressAlarm.Email = ini.GetValue("Alarms", "HighPressAlarmEmail", false); + HighPressAlarm.Latch = ini.GetValue("Alarms", "HighPressAlarmLatch", false); + HighPressAlarm.LatchHours = ini.GetValue("Alarms", "HighPressAlarmLatchHours", 24); + + PressChangeAlarm.Value = ini.GetValue("Alarms", "alarmpresschange", 0.0); + PressChangeAlarm.Enabled = ini.GetValue("Alarms", "PressChangeAlarmSet", false); + PressChangeAlarm.Sound = ini.GetValue("Alarms", "PressChangeAlarmSound", false); + PressChangeAlarm.SoundFile = ini.GetValue("Alarms", "PressChangeAlarmSoundFile", DefaultSoundFile); + if (PressChangeAlarm.SoundFile.Contains(DefaultSoundFileOld)) PressChangeAlarm.SoundFile = DefaultSoundFile; + PressChangeAlarm.Notify = ini.GetValue("Alarms", "PressChangeAlarmNotify", false); + PressChangeAlarm.Email = ini.GetValue("Alarms", "PressChangeAlarmEmail", false); + PressChangeAlarm.Latch = ini.GetValue("Alarms", "PressChangeAlarmLatch", false); + PressChangeAlarm.LatchHours = ini.GetValue("Alarms", "PressChangeAlarmLatchHours", 24); + + HighRainTodayAlarm.Value = ini.GetValue("Alarms", "alarmhighraintoday", 0.0); + HighRainTodayAlarm.Enabled = ini.GetValue("Alarms", "HighRainTodayAlarmSet", false); + HighRainTodayAlarm.Sound = ini.GetValue("Alarms", "HighRainTodayAlarmSound", false); + HighRainTodayAlarm.SoundFile = ini.GetValue("Alarms", "HighRainTodayAlarmSoundFile", DefaultSoundFile); + if (HighRainTodayAlarm.SoundFile.Contains(DefaultSoundFileOld)) HighRainTodayAlarm.SoundFile = DefaultSoundFile; + HighRainTodayAlarm.Notify = ini.GetValue("Alarms", "HighRainTodayAlarmNotify", false); + HighRainTodayAlarm.Email = ini.GetValue("Alarms", "HighRainTodayAlarmEmail", false); + HighRainTodayAlarm.Latch = ini.GetValue("Alarms", "HighRainTodayAlarmLatch", false); + HighRainTodayAlarm.LatchHours = ini.GetValue("Alarms", "HighRainTodayAlarmLatchHours", 24); + + HighRainRateAlarm.Value = ini.GetValue("Alarms", "alarmhighrainrate", 0.0); + HighRainRateAlarm.Enabled = ini.GetValue("Alarms", "HighRainRateAlarmSet", false); + HighRainRateAlarm.Sound = ini.GetValue("Alarms", "HighRainRateAlarmSound", false); + HighRainRateAlarm.SoundFile = ini.GetValue("Alarms", "HighRainRateAlarmSoundFile", DefaultSoundFile); + if (HighRainRateAlarm.SoundFile.Contains(DefaultSoundFileOld)) HighRainRateAlarm.SoundFile = DefaultSoundFile; + HighRainRateAlarm.Notify = ini.GetValue("Alarms", "HighRainRateAlarmNotify", false); + HighRainRateAlarm.Email = ini.GetValue("Alarms", "HighRainRateAlarmEmail", false); + HighRainRateAlarm.Latch = ini.GetValue("Alarms", "HighRainRateAlarmLatch", false); + HighRainRateAlarm.LatchHours = ini.GetValue("Alarms", "HighRainRateAlarmLatchHours", 24); + + HighGustAlarm.Value = ini.GetValue("Alarms", "alarmhighgust", 0.0); + HighGustAlarm.Enabled = ini.GetValue("Alarms", "HighGustAlarmSet", false); + HighGustAlarm.Sound = ini.GetValue("Alarms", "HighGustAlarmSound", false); + HighGustAlarm.SoundFile = ini.GetValue("Alarms", "HighGustAlarmSoundFile", DefaultSoundFile); + if (HighGustAlarm.SoundFile.Contains(DefaultSoundFileOld)) HighGustAlarm.SoundFile = DefaultSoundFile; + HighGustAlarm.Notify = ini.GetValue("Alarms", "HighGustAlarmNotify", false); + HighGustAlarm.Email = ini.GetValue("Alarms", "HighGustAlarmEmail", false); + HighGustAlarm.Latch = ini.GetValue("Alarms", "HighGustAlarmLatch", false); + HighGustAlarm.LatchHours = ini.GetValue("Alarms", "HighGustAlarmLatchHours", 24); + + HighWindAlarm.Value = ini.GetValue("Alarms", "alarmhighwind", 0.0); + HighWindAlarm.Enabled = ini.GetValue("Alarms", "HighWindAlarmSet", false); + HighWindAlarm.Sound = ini.GetValue("Alarms", "HighWindAlarmSound", false); + HighWindAlarm.SoundFile = ini.GetValue("Alarms", "HighWindAlarmSoundFile", DefaultSoundFile); + if (HighWindAlarm.SoundFile.Contains(DefaultSoundFileOld)) HighWindAlarm.SoundFile = DefaultSoundFile; + HighWindAlarm.Notify = ini.GetValue("Alarms", "HighWindAlarmNotify", false); + HighWindAlarm.Email = ini.GetValue("Alarms", "HighWindAlarmEmail", false); + HighWindAlarm.Latch = ini.GetValue("Alarms", "HighWindAlarmLatch", false); + HighWindAlarm.LatchHours = ini.GetValue("Alarms", "HighWindAlarmLatchHours", 24); + + SensorAlarm.Enabled = ini.GetValue("Alarms", "SensorAlarmSet", false); + SensorAlarm.Sound = ini.GetValue("Alarms", "SensorAlarmSound", false); + SensorAlarm.SoundFile = ini.GetValue("Alarms", "SensorAlarmSoundFile", DefaultSoundFile); + if (SensorAlarm.SoundFile.Contains(DefaultSoundFileOld)) SensorAlarm.SoundFile = DefaultSoundFile; + SensorAlarm.Notify = ini.GetValue("Alarms", "SensorAlarmNotify", false); + SensorAlarm.Email = ini.GetValue("Alarms", "SensorAlarmEmail", false); + SensorAlarm.Latch = ini.GetValue("Alarms", "SensorAlarmLatch", false); + SensorAlarm.LatchHours = ini.GetValue("Alarms", "SensorAlarmLatchHours", 24); + + DataStoppedAlarm.Enabled = ini.GetValue("Alarms", "DataStoppedAlarmSet", false); + DataStoppedAlarm.Sound = ini.GetValue("Alarms", "DataStoppedAlarmSound", false); + DataStoppedAlarm.SoundFile = ini.GetValue("Alarms", "DataStoppedAlarmSoundFile", DefaultSoundFile); + if (DataStoppedAlarm.SoundFile.Contains(DefaultSoundFileOld)) SensorAlarm.SoundFile = DefaultSoundFile; + DataStoppedAlarm.Notify = ini.GetValue("Alarms", "DataStoppedAlarmNotify", false); + DataStoppedAlarm.Email = ini.GetValue("Alarms", "DataStoppedAlarmEmail", false); + DataStoppedAlarm.Latch = ini.GetValue("Alarms", "DataStoppedAlarmLatch", false); + DataStoppedAlarm.LatchHours = ini.GetValue("Alarms", "DataStoppedAlarmLatchHours", 24); + + BatteryLowAlarm.Enabled = ini.GetValue("Alarms", "BatteryLowAlarmSet", false); + BatteryLowAlarm.Sound = ini.GetValue("Alarms", "BatteryLowAlarmSound", false); + BatteryLowAlarm.SoundFile = ini.GetValue("Alarms", "BatteryLowAlarmSoundFile", DefaultSoundFile); + BatteryLowAlarm.Notify = ini.GetValue("Alarms", "BatteryLowAlarmNotify", false); + BatteryLowAlarm.Email = ini.GetValue("Alarms", "BatteryLowAlarmEmail", false); + BatteryLowAlarm.Latch = ini.GetValue("Alarms", "BatteryLowAlarmLatch", false); + BatteryLowAlarm.LatchHours = ini.GetValue("Alarms", "BatteryLowAlarmLatchHours", 24); + + SpikeAlarm.Enabled = ini.GetValue("Alarms", "DataSpikeAlarmSet", false); + SpikeAlarm.Sound = ini.GetValue("Alarms", "DataSpikeAlarmSound", false); + SpikeAlarm.SoundFile = ini.GetValue("Alarms", "DataSpikeAlarmSoundFile", DefaultSoundFile); + SpikeAlarm.Notify = ini.GetValue("Alarms", "DataSpikeAlarmNotify", true); + SpikeAlarm.Email = ini.GetValue("Alarms", "DataSpikeAlarmEmail", true); + SpikeAlarm.Latch = ini.GetValue("Alarms", "DataSpikeAlarmLatch", true); + SpikeAlarm.LatchHours = ini.GetValue("Alarms", "DataSpikeAlarmLatchHours", 24); + SpikeAlarm.TriggerThreshold = ini.GetValue("Alarms", "DataSpikeAlarmTriggerCount", 1); + + + UpgradeAlarm.Enabled = ini.GetValue("Alarms", "UpgradeAlarmSet", true); + UpgradeAlarm.Sound = ini.GetValue("Alarms", "UpgradeAlarmSound", true); + UpgradeAlarm.SoundFile = ini.GetValue("Alarms", "UpgradeAlarmSoundFile", DefaultSoundFile); + UpgradeAlarm.Notify = ini.GetValue("Alarms", "UpgradeAlarmNotify", true); + UpgradeAlarm.Email = ini.GetValue("Alarms", "UpgradeAlarmEmail", false); + UpgradeAlarm.Latch = ini.GetValue("Alarms", "UpgradeAlarmLatch", false); + UpgradeAlarm.LatchHours = ini.GetValue("Alarms", "UpgradeAlarmLatchHours", 24); + + HttpUploadAlarm.Enabled = ini.GetValue("Alarms", "HttpUploadAlarmSet", false); + HttpUploadAlarm.Sound = ini.GetValue("Alarms", "HttpUploadAlarmSound", false); + HttpUploadAlarm.SoundFile = ini.GetValue("Alarms", "HttpUploadAlarmSoundFile", DefaultSoundFile); + HttpUploadAlarm.Notify = ini.GetValue("Alarms", "HttpUploadAlarmNotify", false); + HttpUploadAlarm.Email = ini.GetValue("Alarms", "HttpUploadAlarmEmail", false); + HttpUploadAlarm.Latch = ini.GetValue("Alarms", "HttpUploadAlarmLatch", false); + HttpUploadAlarm.LatchHours = ini.GetValue("Alarms", "HttpUploadAlarmLatchHours", 24); + HttpUploadAlarm.TriggerThreshold = ini.GetValue("Alarms", "HttpUploadAlarmTriggerCount", 1); + + MySqlUploadAlarm.Enabled = ini.GetValue("Alarms", "MySqlUploadAlarmSet", false); + MySqlUploadAlarm.Sound = ini.GetValue("Alarms", "MySqlUploadAlarmSound", false); + MySqlUploadAlarm.SoundFile = ini.GetValue("Alarms", "MySqlUploadAlarmSoundFile", DefaultSoundFile); + MySqlUploadAlarm.Notify = ini.GetValue("Alarms", "MySqlUploadAlarmNotify", false); + MySqlUploadAlarm.Email = ini.GetValue("Alarms", "MySqlUploadAlarmEmail", false); + MySqlUploadAlarm.Latch = ini.GetValue("Alarms", "MySqlUploadAlarmLatch", false); + MySqlUploadAlarm.LatchHours = ini.GetValue("Alarms", "MySqlUploadAlarmLatchHours", 24); + MySqlUploadAlarm.TriggerThreshold = ini.GetValue("Alarms", "MySqlUploadAlarmTriggerCount", 1); + + + AlarmFromEmail = ini.GetValue("Alarms", "FromEmail", ""); + AlarmDestEmail = ini.GetValue("Alarms", "DestEmail", "").Split(';'); + AlarmEmailHtml = ini.GetValue("Alarms", "UseHTML", false); + + Calib.Press.Offset = ini.GetValue("Offsets", "PressOffset", 0.0); + Calib.Temp.Offset = ini.GetValue("Offsets", "TempOffset", 0.0); + Calib.Hum.Offset = ini.GetValue("Offsets", "HumOffset", 0); + Calib.WindDir.Offset = ini.GetValue("Offsets", "WindDirOffset", 0); + Calib.InTemp.Offset = ini.GetValue("Offsets", "InTempOffset", 0.0); + Calib.Solar.Offset = ini.GetValue("Offsers", "SolarOffset", 0.0); + Calib.UV.Offset = ini.GetValue("Offsets", "UVOffset", 0.0); + Calib.WetBulb.Offset = ini.GetValue("Offsets", "WetBulbOffset", 0.0); + + Calib.Press.Mult = ini.GetValue("Offsets", "PressMult", 1.0); + Calib.WindSpeed.Mult = ini.GetValue("Offsets", "WindSpeedMult", 1.0); + Calib.WindGust.Mult = ini.GetValue("Offsets", "WindGustMult", 1.0); + Calib.Temp.Mult = ini.GetValue("Offsets", "TempMult", 1.0); + Calib.Temp.Mult2 = ini.GetValue("Offsets", "TempMult2", 0.0); + Calib.Hum.Mult = ini.GetValue("Offsets", "HumMult", 1.0); + Calib.Hum.Mult2 = ini.GetValue("Offsets", "HumMult2", 0.0); + Calib.Rain.Mult = ini.GetValue("Offsets", "RainMult", 1.0); + Calib.Solar.Mult = ini.GetValue("Offsets", "SolarMult", 1.0); + Calib.UV.Mult = ini.GetValue("Offsets", "UVMult", 1.0); + Calib.WetBulb.Mult = ini.GetValue("Offsets", "WetBulbMult", 1.0); + + Limit.TempHigh = ini.GetValue("Limits", "TempHighC", 60.0); + Limit.TempLow = ini.GetValue("Limits", "TempLowC", -60.0); + Limit.DewHigh = ini.GetValue("Limits", "DewHighC", 40.0); + Limit.PressHigh = ini.GetValue("Limits", "PressHighMB", 1090.0); + Limit.PressLow = ini.GetValue("Limits", "PressLowMB", 870.0); + Limit.WindHigh = ini.GetValue("Limits", "WindHighMS", 90.0); + + xapEnabled = ini.GetValue("xAP", "Enabled", false); + xapUID = ini.GetValue("xAP", "UID", "4375"); + xapPort = ini.GetValue("xAP", "Port", 3639); + + SunThreshold = ini.GetValue("Solar", "SunThreshold", 75); + RStransfactor = ini.GetValue("Solar", "RStransfactor", 0.8); + SolarMinimum = ini.GetValue("Solar", "SolarMinimum", 0); + LuxToWM2 = ini.GetValue("Solar", "LuxToWM2", 0.0079); + UseBlakeLarsen = ini.GetValue("Solar", "UseBlakeLarsen", false); + SolarCalc = ini.GetValue("Solar", "SolarCalc", 0); + BrasTurbidity = ini.GetValue("Solar", "BrasTurbidity", 2.0); + //SolarFactorSummer = ini.GetValue("Solar", "SolarFactorSummer", -1); + //SolarFactorWinter = ini.GetValue("Solar", "SolarFactorWinter", -1); + + NOAAname = ini.GetValue("NOAA", "Name", " "); + NOAAcity = ini.GetValue("NOAA", "City", " "); + NOAAstate = ini.GetValue("NOAA", "State", " "); + NOAA12hourformat = ini.GetValue("NOAA", "12hourformat", false); + NOAAheatingthreshold = ini.GetValue("NOAA", "HeatingThreshold", -1000.0); + if (NOAAheatingthreshold < -99 || NOAAheatingthreshold > 150) + { + NOAAheatingthreshold = Units.Temp == 0 ? 18.3 : 65; + } + NOAAcoolingthreshold = ini.GetValue("NOAA", "CoolingThreshold", -1000.0); + if (NOAAcoolingthreshold < -99 || NOAAcoolingthreshold > 150) + { + NOAAcoolingthreshold = Units.Temp == 0 ? 18.3 : 65; + } + NOAAmaxtempcomp1 = ini.GetValue("NOAA", "MaxTempComp1", -1000.0); + if (NOAAmaxtempcomp1 < -99 || NOAAmaxtempcomp1 > 150) + { + NOAAmaxtempcomp1 = Units.Temp == 0 ? 27 : 80; + } + NOAAmaxtempcomp2 = ini.GetValue("NOAA", "MaxTempComp2", -1000.0); + if (NOAAmaxtempcomp2 < -99 || NOAAmaxtempcomp2 > 99) + { + NOAAmaxtempcomp2 = Units.Temp == 0 ? 0 : 32; + } + NOAAmintempcomp1 = ini.GetValue("NOAA", "MinTempComp1", -1000.0); + if (NOAAmintempcomp1 < -99 || NOAAmintempcomp1 > 99) + { + NOAAmintempcomp1 = Units.Temp == 0 ? 0 : 32; + } + NOAAmintempcomp2 = ini.GetValue("NOAA", "MinTempComp2", -1000.0); + if (NOAAmintempcomp2 < -99 || NOAAmintempcomp2 > 99) + { + NOAAmintempcomp2 = Units.Temp == 0 ? -18 : 0; + } + NOAAraincomp1 = ini.GetValue("NOAA", "RainComp1", -1000.0); + if (NOAAraincomp1 < 0 || NOAAraincomp1 > 99) + { + NOAAraincomp1 = Units.Rain == 0 ? 0.2 : 0.01; + } + NOAAraincomp2 = ini.GetValue("NOAA", "RainComp2", -1000.0); + if (NOAAraincomp2 < 0 || NOAAraincomp2 > 99) + { + NOAAraincomp2 = Units.Rain == 0 ? 2 : 0.1; + } + NOAAraincomp3 = ini.GetValue("NOAA", "RainComp3", -1000.0); + if (NOAAraincomp3 < 0 || NOAAraincomp3 > 99) + { + NOAAraincomp3 = Units.Rain == 0 ? 20 : 1; + } + + NOAAAutoSave = ini.GetValue("NOAA", "AutoSave", false); + NOAAAutoFTP = ini.GetValue("NOAA", "AutoFTP", false); + NOAAMonthFileFormat = ini.GetValue("NOAA", "MonthFileFormat", "'NOAAMO'MMyy'.txt'"); + // Check for Cumulus 1 default format - and update + if (NOAAMonthFileFormat == "'NOAAMO'mmyy'.txt'" || NOAAMonthFileFormat == "\"NOAAMO\"mmyy\".txt\"") + { + NOAAMonthFileFormat = "'NOAAMO'MMyy'.txt'"; + } + NOAAYearFileFormat = ini.GetValue("NOAA", "YearFileFormat", "'NOAAYR'yyyy'.txt'"); + NOAAFTPDirectory = ini.GetValue("NOAA", "FTPDirectory", ""); + NOAAUseUTF8 = ini.GetValue("NOAA", "NOAAUseUTF8", true); + NOAAUseDotDecimal = ini.GetValue("NOAA", "UseDotDecimal", false); + + NOAATempNorms[1] = ini.GetValue("NOAA", "NOAATempNormJan", -1000.0); + NOAATempNorms[2] = ini.GetValue("NOAA", "NOAATempNormFeb", -1000.0); + NOAATempNorms[3] = ini.GetValue("NOAA", "NOAATempNormMar", -1000.0); + NOAATempNorms[4] = ini.GetValue("NOAA", "NOAATempNormApr", -1000.0); + NOAATempNorms[5] = ini.GetValue("NOAA", "NOAATempNormMay", -1000.0); + NOAATempNorms[6] = ini.GetValue("NOAA", "NOAATempNormJun", -1000.0); + NOAATempNorms[7] = ini.GetValue("NOAA", "NOAATempNormJul", -1000.0); + NOAATempNorms[8] = ini.GetValue("NOAA", "NOAATempNormAug", -1000.0); + NOAATempNorms[9] = ini.GetValue("NOAA", "NOAATempNormSep", -1000.0); + NOAATempNorms[10] = ini.GetValue("NOAA", "NOAATempNormOct", -1000.0); + NOAATempNorms[11] = ini.GetValue("NOAA", "NOAATempNormNov", -1000.0); + NOAATempNorms[12] = ini.GetValue("NOAA", "NOAATempNormDec", -1000.0); + + NOAARainNorms[1] = ini.GetValue("NOAA", "NOAARainNormJan", -1000.0); + NOAARainNorms[2] = ini.GetValue("NOAA", "NOAARainNormFeb", -1000.0); + NOAARainNorms[3] = ini.GetValue("NOAA", "NOAARainNormMar", -1000.0); + NOAARainNorms[4] = ini.GetValue("NOAA", "NOAARainNormApr", -1000.0); + NOAARainNorms[5] = ini.GetValue("NOAA", "NOAARainNormMay", -1000.0); + NOAARainNorms[6] = ini.GetValue("NOAA", "NOAARainNormJun", -1000.0); + NOAARainNorms[7] = ini.GetValue("NOAA", "NOAARainNormJul", -1000.0); + NOAARainNorms[8] = ini.GetValue("NOAA", "NOAARainNormAug", -1000.0); + NOAARainNorms[9] = ini.GetValue("NOAA", "NOAARainNormSep", -1000.0); + NOAARainNorms[10] = ini.GetValue("NOAA", "NOAARainNormOct", -1000.0); + NOAARainNorms[11] = ini.GetValue("NOAA", "NOAARainNormNov", -1000.0); + NOAARainNorms[12] = ini.GetValue("NOAA", "NOAARainNormDec", -1000.0); + + HTTPProxyName = ini.GetValue("Proxies", "HTTPProxyName", ""); + HTTPProxyPort = ini.GetValue("Proxies", "HTTPProxyPort", 0); + HTTPProxyUser = ini.GetValue("Proxies", "HTTPProxyUser", ""); + HTTPProxyPassword = ini.GetValue("Proxies", "HTTPProxyPassword", ""); + + NumWindRosePoints = ini.GetValue("Display", "NumWindRosePoints", 16); + WindRoseAngle = 360.0 / NumWindRosePoints; + DisplayOptions.UseApparent = ini.GetValue("Display", "UseApparent", false); + DisplayOptions.ShowSolar = ini.GetValue("Display", "DisplaySolarData", false); + DisplayOptions.ShowUV = ini.GetValue("Display", "DisplayUvData", false); + + // MySQL - common + MySqlConnSettings.Server = ini.GetValue("MySQL", "Host", "127.0.0.1"); + MySqlConnSettings.Port = (uint)ini.GetValue("MySQL", "Port", 3306); + MySqlConnSettings.UserID = ini.GetValue("MySQL", "User", ""); + MySqlConnSettings.Password = ini.GetValue("MySQL", "Pass", ""); + MySqlConnSettings.Database = ini.GetValue("MySQL", "Database", "database"); + + // MySQL - monthly log file + MonthlyMySqlEnabled = ini.GetValue("MySQL", "MonthlyMySqlEnabled", false); + MySqlMonthlyTable = ini.GetValue("MySQL", "MonthlyTable", "Monthly"); + // MySQL - realtimne + RealtimeMySqlEnabled = ini.GetValue("MySQL", "RealtimeMySqlEnabled", false); + MySqlRealtimeTable = ini.GetValue("MySQL", "RealtimeTable", "Realtime"); + MySqlRealtimeRetention = ini.GetValue("MySQL", "RealtimeRetention", ""); + RealtimeMySql1MinLimit = ini.GetValue("MySQL", "RealtimeMySql1MinLimit", false); + // MySQL - dayfile + DayfileMySqlEnabled = ini.GetValue("MySQL", "DayfileMySqlEnabled", false); + MySqlDayfileTable = ini.GetValue("MySQL", "DayfileTable", "Dayfile"); + // MySQL - custom seconds + CustomMySqlSecondsCommandString = ini.GetValue("MySQL", "CustomMySqlSecondsCommandString", ""); + CustomMySqlSecondsEnabled = ini.GetValue("MySQL", "CustomMySqlSecondsEnabled", false); + CustomMySqlSecondsInterval = ini.GetValue("MySQL", "CustomMySqlSecondsInterval", 10); + if (CustomMySqlSecondsInterval < 1) { CustomMySqlSecondsInterval = 1; } + // MySQL - custom minutes + CustomMySqlMinutesCommandString = ini.GetValue("MySQL", "CustomMySqlMinutesCommandString", ""); + CustomMySqlMinutesEnabled = ini.GetValue("MySQL", "CustomMySqlMinutesEnabled", false); + CustomMySqlMinutesIntervalIndex = ini.GetValue("MySQL", "CustomMySqlMinutesIntervalIndex", -1); + if (CustomMySqlMinutesIntervalIndex >= 0 && CustomMySqlMinutesIntervalIndex < FactorsOf60.Length) + { + CustomMySqlMinutesInterval = FactorsOf60[CustomMySqlMinutesIntervalIndex]; + } + else + { + CustomMySqlMinutesInterval = 10; + CustomMySqlMinutesIntervalIndex = 6; + } + // MySQL - custom rollover + CustomMySqlRolloverCommandString = ini.GetValue("MySQL", "CustomMySqlRolloverCommandString", ""); + CustomMySqlRolloverEnabled = ini.GetValue("MySQL", "CustomMySqlRolloverEnabled", false); + + // Custom HTTP - seconds + CustomHttpSecondsString = ini.GetValue("HTTP", "CustomHttpSecondsString", ""); + CustomHttpSecondsEnabled = ini.GetValue("HTTP", "CustomHttpSecondsEnabled", false); + CustomHttpSecondsInterval = ini.GetValue("HTTP", "CustomHttpSecondsInterval", 10); + if (CustomHttpSecondsInterval < 1) { CustomHttpSecondsInterval = 1; } + // Custom HTTP - minutes + CustomHttpMinutesString = ini.GetValue("HTTP", "CustomHttpMinutesString", ""); + CustomHttpMinutesEnabled = ini.GetValue("HTTP", "CustomHttpMinutesEnabled", false); + CustomHttpMinutesIntervalIndex = ini.GetValue("HTTP", "CustomHttpMinutesIntervalIndex", -1); + if (CustomHttpMinutesIntervalIndex >= 0 && CustomHttpMinutesIntervalIndex < FactorsOf60.Length) + { + CustomHttpMinutesInterval = FactorsOf60[CustomHttpMinutesIntervalIndex]; + } + else + { + CustomHttpMinutesInterval = 10; + CustomHttpMinutesIntervalIndex = 6; + } + // Http - custom rollover + CustomHttpRolloverString = ini.GetValue("HTTP", "CustomHttpRolloverString", ""); + CustomHttpRolloverEnabled = ini.GetValue("HTTP", "CustomHttpRolloverEnabled", false); + + // Select-a-Chart settings + for (int i = 0; i < SelectaChartOptions.series.Length; i++) + { + SelectaChartOptions.series[i] = ini.GetValue("Select-a-Chart", "Series" + i, "0"); + SelectaChartOptions.colours[i] = ini.GetValue("Select-a-Chart", "Colour" + i, ""); + } + + // Email settings + SmtpOptions.Enabled = ini.GetValue("SMTP", "Enabled", false); + SmtpOptions.Server = ini.GetValue("SMTP", "ServerName", ""); + SmtpOptions.Port = ini.GetValue("SMTP", "Port", 587); + SmtpOptions.SslOption = ini.GetValue("SMTP", "SSLOption", 1); + SmtpOptions.RequiresAuthentication = ini.GetValue("SMTP", "RequiresAuthentication", false); + SmtpOptions.User = ini.GetValue("SMTP", "User", ""); + SmtpOptions.Password = ini.GetValue("SMTP", "Password", ""); + + // Growing Degree Days + GrowingBase1 = ini.GetValue("GrowingDD", "BaseTemperature1", (Units.Temp == 0 ? 5.0 : 40.0)); + GrowingBase2 = ini.GetValue("GrowingDD", "BaseTemperature2", (Units.Temp == 0 ? 10.0 : 50.0)); + GrowingYearStarts = ini.GetValue("GrowingDD", "YearStarts", (Latitude >= 0 ? 1 : 7)); + GrowingCap30C = ini.GetValue("GrowingDD", "Cap30C", true); + + // Temperature Sum + TempSumYearStarts = ini.GetValue("TempSum", "TempSumYearStart", (Latitude >= 0 ? 1 : 7)); + if (TempSumYearStarts < 1 || TempSumYearStarts > 12) + TempSumYearStarts = 1; + TempSumBase1 = ini.GetValue("TempSum", "BaseTemperature1", GrowingBase1); + TempSumBase2 = ini.GetValue("TempSum", "BaseTemperature2", GrowingBase2); + } + + internal void WriteIniFile() + { + LogMessage("Writing Cumulus.ini file"); + + IniFile ini = new IniFile("Cumulus.ini"); + + ini.SetValue("Program", "EnableAccessibility", ProgramOptions.EnableAccessibility); + + ini.SetValue("Program", "StartupPingHost", ProgramOptions.StartupPingHost); + ini.SetValue("Program", "StartupPingEscapeTime", ProgramOptions.StartupPingEscapeTime); + + ini.SetValue("Program", "StartupDelaySecs", ProgramOptions.StartupDelaySecs); + ini.SetValue("Program", "StartupDelayMaxUptime", ProgramOptions.StartupDelayMaxUptime); + + ini.SetValue("Station", "WarnMultiple", ProgramOptions.WarnMultiple); + + ini.SetValue("Station", "Type", StationType); + ini.SetValue("Station", "Model", StationModel); + ini.SetValue("Station", "ComportName", ComportName); + ini.SetValue("Station", "Latitude", Latitude); + ini.SetValue("Station", "Longitude", Longitude); + ini.SetValue("Station", "LatTxt", LatTxt); + ini.SetValue("Station", "LonTxt", LonTxt); + ini.SetValue("Station", "Altitude", Altitude); + ini.SetValue("Station", "AltitudeInFeet", AltitudeInFeet); + ini.SetValue("Station", "Humidity98Fix", StationOptions.Humidity98Fix); + ini.SetValue("Station", "Wind10MinAverage", StationOptions.UseWind10MinAve); + ini.SetValue("Station", "UseSpeedForAvgCalc", StationOptions.UseSpeedForAvgCalc); + ini.SetValue("Station", "AvgBearingMinutes", StationOptions.AvgBearingMinutes); + ini.SetValue("Station", "AvgSpeedMinutes", StationOptions.AvgSpeedMinutes); + ini.SetValue("Station", "PeakGustMinutes", StationOptions.PeakGustMinutes); + + ini.SetValue("Station", "Logging", ProgramOptions.DebugLogging); + ini.SetValue("Station", "DataLogging", ProgramOptions.DataLogging); + + ini.SetValue("Station", "DavisReadReceptionStats", DavisOptions.ReadReceptionStats); + ini.SetValue("Station", "DavisSetLoggerInterval", DavisOptions.SetLoggerInterval); + ini.SetValue("Station", "UseDavisLoop2", DavisOptions.UseLoop2); + ini.SetValue("Station", "DavisInitWaitTime", DavisOptions.InitWaitTime); + ini.SetValue("Station", "DavisIPResponseTime", DavisOptions.IPResponseTime); + ini.SetValue("Station", "DavisBaudRate", DavisOptions.BaudRate); + ini.SetValue("Station", "VPrainGaugeType", DavisOptions.RainGaugeType); + ini.SetValue("Station", "VP2ConnectionType", DavisOptions.ConnectionType); + ini.SetValue("Station", "VP2TCPPort", DavisOptions.TCPPort); + ini.SetValue("Station", "VP2IPAddr", DavisOptions.IPAddr); + ini.SetValue("Station", "VP2PeriodicDisconnectInterval", DavisOptions.PeriodicDisconnectInterval); + ini.SetValue("Station", "ForceVPBarUpdate", DavisOptions.ForceVPBarUpdate); + + ini.SetValue("Station", "NoSensorCheck", StationOptions.NoSensorCheck); + ini.SetValue("Station", "CalculatedDP", StationOptions.CalculatedDP); + ini.SetValue("Station", "CalculatedWC", StationOptions.CalculatedWC); + ini.SetValue("Station", "RolloverHour", RolloverHour); + ini.SetValue("Station", "Use10amInSummer", Use10amInSummer); + //ini.SetValue("Station", "ConfirmClose", ConfirmClose); + //ini.SetValue("Station", "CloseOnSuspend", CloseOnSuspend); + //ini.SetValue("Station", "RestartIfUnplugged", RestartIfUnplugged); + //ini.SetValue("Station", "RestartIfDataStops", RestartIfDataStops); + ini.SetValue("Station", "SyncDavisClock", StationOptions.SyncTime); + ini.SetValue("Station", "ClockSettingHour", StationOptions.ClockSettingHour); + ini.SetValue("Station", "WS2300IgnoreStationClock", StationOptions.WS2300IgnoreStationClock); + ini.SetValue("Station", "LogExtraSensors", StationOptions.LogExtraSensors); + ini.SetValue("Station", "DataLogInterval", DataLogInterval); + + ini.SetValue("Station", "SyncFOReads", FineOffsetOptions.SyncReads); + ini.SetValue("Station", "FOReadAvoidPeriod", FineOffsetOptions.ReadAvoidPeriod); + ini.SetValue("Station", "FineOffsetReadTime", FineOffsetOptions.ReadTime); + ini.SetValue("Station", "FineOffsetSetLoggerInterval", FineOffsetOptions.SetLoggerInterval); + ini.SetValue("Station", "VendorID", FineOffsetOptions.VendorID); + ini.SetValue("Station", "ProductID", FineOffsetOptions.ProductID); + + + ini.SetValue("Station", "WindUnit", Units.Wind); + ini.SetValue("Station", "PressureUnit", Units.Press); + ini.SetValue("Station", "RainUnit", Units.Rain); + ini.SetValue("Station", "TempUnit", Units.Temp); + + ini.SetValue("Station", "WindSpeedDecimals", WindDPlaces); + ini.SetValue("Station", "WindSpeedAvgDecimals", WindAvgDPlaces); + ini.SetValue("Station", "WindRunDecimals", WindRunDPlaces); + ini.SetValue("Station", "SunshineHrsDecimals", SunshineDPlaces); + ini.SetValue("Station", "PressDecimals", PressDPlaces); + ini.SetValue("Station", "RainDecimals", RainDPlaces); + ini.SetValue("Station", "TempDecimals", TempDPlaces); + ini.SetValue("Station", "UVDecimals", UVDPlaces); + ini.SetValue("Station", "AirQualityDecimals", AirQualityDPlaces); + + + ini.SetValue("Station", "LocName", LocationName); + ini.SetValue("Station", "LocDesc", LocationDesc); + ini.SetValue("Station", "StartDate", RecordsBeganDate); + ini.SetValue("Station", "YTDrain", YTDrain); + ini.SetValue("Station", "YTDrainyear", YTDrainyear); + ini.SetValue("Station", "UseDataLogger", UseDataLogger); + ini.SetValue("Station", "UseCumulusForecast", UseCumulusForecast); + ini.SetValue("Station", "HourlyForecast", HourlyForecast); + ini.SetValue("Station", "UseCumulusPresstrendstr", StationOptions.UseCumulusPresstrendstr); + ini.SetValue("Station", "FCpressinMB", FCpressinMB); + ini.SetValue("Station", "FClowpress", FClowpress); + ini.SetValue("Station", "FChighpress", FChighpress); + ini.SetValue("Station", "UseZeroBearing", StationOptions.UseZeroBearing); + ini.SetValue("Station", "RoundWindSpeed", StationOptions.RoundWindSpeed); + ini.SetValue("Station", "PrimaryAqSensor", StationOptions.PrimaryAqSensor); + + ini.SetValue("Station", "EWInterval", EwOptions.Interval); + ini.SetValue("Station", "EWFile", EwOptions.Filename); + ini.SetValue("Station", "EWminpressureMB", EwOptions.MinPressMB); + ini.SetValue("Station", "EWmaxpressureMB", EwOptions.MaxPressMB); + ini.SetValue("Station", "EWMaxRainTipDiff", EwOptions.MaxRainTipDiff); + ini.SetValue("Station", "EWpressureoffset", EwOptions.PressOffset); + + ini.SetValue("Station", "EWtempdiff", Spike.TempDiff); + ini.SetValue("Station", "EWpressurediff", Spike.PressDiff); + ini.SetValue("Station", "EWhumiditydiff", Spike.HumidityDiff); + ini.SetValue("Station", "EWgustdiff", Spike.GustDiff); + ini.SetValue("Station", "EWwinddiff", Spike.WindDiff); + ini.SetValue("Station", "EWmaxHourlyRain", Spike.MaxHourlyRain); + ini.SetValue("Station", "EWmaxRainRate", Spike.MaxRainRate); + + ini.SetValue("Station", "RainSeasonStart", RainSeasonStart); + ini.SetValue("Station", "RainDayThreshold", RainDayThreshold); + + ini.SetValue("Station", "ChillHourSeasonStart", ChillHourSeasonStart); + ini.SetValue("Station", "ChillHourThreshold", ChillHourThreshold); + + ini.SetValue("Station", "ErrorLogSpikeRemoval", ErrorLogSpikeRemoval); + + ini.SetValue("Station", "ImetBaudRate", ImetOptions.BaudRate); + ini.SetValue("Station", "ImetWaitTime", ImetOptions.WaitTime); // delay to wait for a reply to a command + ini.SetValue("Station", "ImetReadDelay", ImetOptions.ReadDelay); // delay between sending read live data commands + ini.SetValue("Station", "ImetUpdateLogPointer", ImetOptions.UpdateLogPointer); // keep the logger pointer pointing at last data read + + ini.SetValue("Station", "RG11Enabled", RG11Enabled); + ini.SetValue("Station", "RG11portName", RG11Port); + ini.SetValue("Station", "RG11TBRmode", RG11TBRmode); + ini.SetValue("Station", "RG11tipsize", RG11tipsize); + ini.SetValue("Station", "RG11IgnoreFirst", RG11IgnoreFirst); + ini.SetValue("Station", "RG11DTRmode", RG11DTRmode); + + ini.SetValue("Station", "RG11Enabled2", RG11Enabled2); + ini.SetValue("Station", "RG11portName2", RG11Port2); + ini.SetValue("Station", "RG11TBRmode2", RG11TBRmode2); + ini.SetValue("Station", "RG11tipsize2", RG11tipsize2); + ini.SetValue("Station", "RG11IgnoreFirst2", RG11IgnoreFirst2); + ini.SetValue("Station", "RG11DTRmode2", RG11DTRmode2); + + // WeatherLink Live device settings + ini.SetValue("WLL", "AutoUpdateIpAddress", WLLAutoUpdateIpAddress); + ini.SetValue("WLL", "WLv2ApiKey", WllApiKey); + ini.SetValue("WLL", "WLv2ApiSecret", WllApiSecret); + ini.SetValue("WLL", "WLStationId", WllStationId); + ini.SetValue("WLL", "PrimaryRainTxId", WllPrimaryRain); + ini.SetValue("WLL", "PrimaryTempHumTxId", WllPrimaryTempHum); + ini.SetValue("WLL", "PrimaryWindTxId", WllPrimaryWind); + ini.SetValue("WLL", "PrimaryRainTxId", WllPrimaryRain); + ini.SetValue("WLL", "PrimarySolarTxId", WllPrimarySolar); + ini.SetValue("WLL", "PrimaryUvTxId", WllPrimaryUV); + ini.SetValue("WLL", "ExtraSoilTempTxId1", WllExtraSoilTempTx1); + ini.SetValue("WLL", "ExtraSoilTempIdx1", WllExtraSoilTempIdx1); + ini.SetValue("WLL", "ExtraSoilTempTxId2", WllExtraSoilTempTx2); + ini.SetValue("WLL", "ExtraSoilTempIdx2", WllExtraSoilTempIdx2); + ini.SetValue("WLL", "ExtraSoilTempTxId3", WllExtraSoilTempTx3); + ini.SetValue("WLL", "ExtraSoilTempIdx3", WllExtraSoilTempIdx3); + ini.SetValue("WLL", "ExtraSoilTempTxId4", WllExtraSoilTempTx4); + ini.SetValue("WLL", "ExtraSoilTempIdx4", WllExtraSoilTempIdx4); + ini.SetValue("WLL", "ExtraSoilMoistureTxId1", WllExtraSoilMoistureTx1); + ini.SetValue("WLL", "ExtraSoilMoistureIdx1", WllExtraSoilMoistureIdx1); + ini.SetValue("WLL", "ExtraSoilMoistureTxId2", WllExtraSoilMoistureTx2); + ini.SetValue("WLL", "ExtraSoilMoistureIdx2", WllExtraSoilMoistureIdx2); + ini.SetValue("WLL", "ExtraSoilMoistureTxId3", WllExtraSoilMoistureTx3); + ini.SetValue("WLL", "ExtraSoilMoistureIdx3", WllExtraSoilMoistureIdx3); + ini.SetValue("WLL", "ExtraSoilMoistureTxId4", WllExtraSoilMoistureTx4); + ini.SetValue("WLL", "ExtraSoilMoistureIdx4", WllExtraSoilMoistureIdx4); + ini.SetValue("WLL", "ExtraLeafTxId1", WllExtraLeafTx1); + ini.SetValue("WLL", "ExtraLeafIdx1", WllExtraLeafIdx1); + ini.SetValue("WLL", "ExtraLeafTxId2", WllExtraLeafTx2); + ini.SetValue("WLL", "ExtraLeafIdx2", WllExtraLeafIdx2); + for (int i = 1; i <= 8; i++) + { + ini.SetValue("WLL", "ExtraTempTxId" + i, WllExtraTempTx[i - 1]); + ini.SetValue("WLL", "ExtraHumOnTxId" + i, WllExtraHumTx[i - 1]); + } + + // GW1000 settings + ini.SetValue("GW1000", "IPAddress", Gw1000IpAddress); + ini.SetValue("GW1000", "MACAddress", Gw1000MacAddress); + ini.SetValue("GW1000", "AutoUpdateIpAddress", Gw1000AutoUpdateIpAddress); + + // AirLink settings + ini.SetValue("AirLink", "IsWllNode", AirLinkIsNode); + ini.SetValue("AirLink", "WLv2ApiKey", AirLinkApiKey); + ini.SetValue("AirLink", "WLv2ApiSecret", AirLinkApiSecret); + ini.SetValue("AirLink", "AutoUpdateIpAddress", AirLinkAutoUpdateIpAddress); + ini.SetValue("AirLink", "In-Enabled", AirLinkInEnabled); + ini.SetValue("AirLink", "In-IPAddress", AirLinkInIPAddr); + ini.SetValue("AirLink", "In-WLStationId", AirLinkInStationId); + ini.SetValue("AirLink", "In-Hostname", AirLinkInHostName); + + ini.SetValue("AirLink", "Out-Enabled", AirLinkOutEnabled); + ini.SetValue("AirLink", "Out-IPAddress", AirLinkOutIPAddr); + ini.SetValue("AirLink", "Out-WLStationId", AirLinkOutStationId); + ini.SetValue("AirLink", "Out-Hostname", AirLinkOutHostName); + ini.SetValue("AirLink", "AQIformula", airQualityIndex); + + ini.SetValue("Web Site", "ForumURL", ForumURL); + ini.SetValue("Web Site", "WebcamURL", WebcamURL); + + ini.SetValue("FTP site", "Host", FtpHostname); + ini.SetValue("FTP site", "Port", FtpHostPort); + ini.SetValue("FTP site", "Username", FtpUsername); + ini.SetValue("FTP site", "Password", FtpPassword); + ini.SetValue("FTP site", "Directory", FtpDirectory); + + ini.SetValue("FTP site", "AutoUpdate", WebAutoUpdate); + ini.SetValue("FTP site", "Sslftp", (int)Sslftp); + // BUILD 3092 - added alternate SFTP authenication options + ini.SetValue("FTP site", "SshFtpAuthentication", SshftpAuthentication); + ini.SetValue("FTP site", "SshFtpPskFile", SshftpPskFile); + + ini.SetValue("FTP site", "FTPlogging", FTPlogging); + ini.SetValue("FTP site", "UTF8encode", UTF8encode); + ini.SetValue("FTP site", "EnableRealtime", RealtimeEnabled); + ini.SetValue("FTP site", "RealtimeInterval", RealtimeInterval); + ini.SetValue("FTP site", "RealtimeFTPEnabled", RealtimeFTPEnabled); + ini.SetValue("FTP site", "RealtimeTxtCreate", RealtimeFiles[0].Create); + ini.SetValue("FTP site", "RealtimeTxtFTP", RealtimeFiles[0].FTP); + ini.SetValue("FTP site", "RealtimeGaugesTxtCreate", RealtimeFiles[1].Create); + ini.SetValue("FTP site", "RealtimeGaugesTxtFTP", RealtimeFiles[1].FTP); + + ini.SetValue("FTP site", "IntervalEnabled", WebIntervalEnabled); + ini.SetValue("FTP site", "UpdateInterval", UpdateInterval); + for (var i = 0; i < StdWebFiles.Length; i++) + { + var keyNameCreate = "Create-" + StdWebFiles[i].LocalFileName.Split('.')[0]; + var keyNameFTP = "Ftp-" + StdWebFiles[i].LocalFileName.Split('.')[0]; + ini.SetValue("FTP site", keyNameCreate, StdWebFiles[i].Create); + ini.SetValue("FTP site", keyNameFTP, StdWebFiles[i].FTP); + } + + for (var i = 0; i < GraphDataFiles.Length; i++) + { + var keyNameCreate = "Create-" + GraphDataFiles[i].LocalFileName.Split('.')[0]; + var keyNameFTP = "Ftp-" + GraphDataFiles[i].LocalFileName.Split('.')[0]; + ini.SetValue("FTP site", keyNameCreate, GraphDataFiles[i].Create); + ini.SetValue("FTP site", keyNameFTP, GraphDataFiles[i].FTP); + } + + for (var i = 0; i < GraphDataEodFiles.Length; i++) + { + var keyNameCreate = "Create-" + GraphDataEodFiles[i].LocalFileName.Split('.')[0]; + var keyNameFTP = "Ftp-" + GraphDataEodFiles[i].LocalFileName.Split('.')[0]; + ini.SetValue("FTP site", keyNameCreate, GraphDataEodFiles[i].Create); + ini.SetValue("FTP site", keyNameFTP, GraphDataEodFiles[i].FTP); + } + + ini.SetValue("FTP site", "IncludeMoonImage", IncludeMoonImage); + ini.SetValue("FTP site", "FTPRename", FTPRename); + ini.SetValue("FTP site", "DeleteBeforeUpload", DeleteBeforeUpload); + ini.SetValue("FTP site", "ActiveFTP", ActiveFTPMode); + ini.SetValue("FTP site", "DisableEPSV", DisableFtpsEPSV); + ini.SetValue("FTP site", "DisableFtpsExplicit", DisableFtpsExplicit); + + + for (int i = 0; i < numextrafiles; i++) + { + ini.SetValue("FTP site", "ExtraLocal" + i, ExtraFiles[i].local); + ini.SetValue("FTP site", "ExtraRemote" + i, ExtraFiles[i].remote); + ini.SetValue("FTP site", "ExtraProcess" + i, ExtraFiles[i].process); + ini.SetValue("FTP site", "ExtraBinary" + i, ExtraFiles[i].binary); + ini.SetValue("FTP site", "ExtraRealtime" + i, ExtraFiles[i].realtime); + ini.SetValue("FTP site", "ExtraFTP" + i, ExtraFiles[i].FTP); + ini.SetValue("FTP site", "ExtraUTF" + i, ExtraFiles[i].UTF8); + ini.SetValue("FTP site", "ExtraEOD" + i, ExtraFiles[i].endofday); + } + + ini.SetValue("FTP site", "ExternalProgram", ExternalProgram); + ini.SetValue("FTP site", "RealtimeProgram", RealtimeProgram); + ini.SetValue("FTP site", "DailyProgram", DailyProgram); + ini.SetValue("FTP site", "ExternalParams", ExternalParams); + ini.SetValue("FTP site", "RealtimeParams", RealtimeParams); + ini.SetValue("FTP site", "DailyParams", DailyParams); + + ini.SetValue("Station", "CloudBaseInFeet", CloudBaseInFeet); + + ini.SetValue("Wunderground", "ID", Wund.ID); + ini.SetValue("Wunderground", "Password", Wund.PW); + ini.SetValue("Wunderground", "Enabled", Wund.Enabled); + ini.SetValue("Wunderground", "RapidFire", Wund.RapidFireEnabled); + ini.SetValue("Wunderground", "Interval", Wund.Interval); + ini.SetValue("Wunderground", "SendUV", Wund.SendUV); + ini.SetValue("Wunderground", "SendSR", Wund.SendSolar); + ini.SetValue("Wunderground", "SendIndoor", Wund.SendIndoor); + ini.SetValue("Wunderground", "SendAverage", Wund.SendAverage); + ini.SetValue("Wunderground", "CatchUp", Wund.CatchUp); + ini.SetValue("Wunderground", "SendSoilTemp1", Wund.SendSoilTemp1); + ini.SetValue("Wunderground", "SendSoilTemp2", Wund.SendSoilTemp2); + ini.SetValue("Wunderground", "SendSoilTemp3", Wund.SendSoilTemp3); + ini.SetValue("Wunderground", "SendSoilTemp4", Wund.SendSoilTemp4); + ini.SetValue("Wunderground", "SendSoilMoisture1", Wund.SendSoilMoisture1); + ini.SetValue("Wunderground", "SendSoilMoisture2", Wund.SendSoilMoisture2); + ini.SetValue("Wunderground", "SendSoilMoisture3", Wund.SendSoilMoisture3); + ini.SetValue("Wunderground", "SendSoilMoisture4", Wund.SendSoilMoisture4); + ini.SetValue("Wunderground", "SendLeafWetness1", Wund.SendLeafWetness1); + ini.SetValue("Wunderground", "SendLeafWetness2", Wund.SendLeafWetness2); + ini.SetValue("Wunderground", "SendAirQuality", Wund.SendAirQuality); + + ini.SetValue("Windy", "APIkey", Windy.ApiKey); + ini.SetValue("Windy", "StationIdx", Windy.StationIdx); + ini.SetValue("Windy", "Enabled", Windy.Enabled); + ini.SetValue("Windy", "Interval", Windy.Interval); + ini.SetValue("Windy", "SendUV", Windy.SendUV); + ini.SetValue("Windy", "CatchUp", Windy.CatchUp); + + ini.SetValue("Awekas", "User", AWEKAS.ID); + ini.SetValue("Awekas", "Password", AWEKAS.PW); + ini.SetValue("Awekas", "Language", AWEKAS.Lang); + ini.SetValue("Awekas", "Enabled", AWEKAS.Enabled); + ini.SetValue("Awekas", "Interval", AWEKAS.Interval); + ini.SetValue("Awekas", "SendUV", AWEKAS.SendUV); + ini.SetValue("Awekas", "SendSR", AWEKAS.SendSolar); + ini.SetValue("Awekas", "SendSoilTemp", AWEKAS.SendSoilTemp); + ini.SetValue("Awekas", "SendIndoor", AWEKAS.SendIndoor); + ini.SetValue("Awekas", "SendSoilMoisture", AWEKAS.SendSoilMoisture); + ini.SetValue("Awekas", "SendLeafWetness", AWEKAS.SendLeafWetness); + ini.SetValue("Awekas", "SendAirQuality", AWEKAS.SendAirQuality); + + ini.SetValue("WeatherCloud", "Wid", WCloud.ID); + ini.SetValue("WeatherCloud", "Key", WCloud.PW); + ini.SetValue("WeatherCloud", "Enabled", WCloud.Enabled); + ini.SetValue("WeatherCloud", "Interval", WCloud.Interval); + ini.SetValue("WeatherCloud", "SendUV", WCloud.SendUV); + ini.SetValue("WeatherCloud", "SendSR", WCloud.SendSolar); + ini.SetValue("WeatherCloud", "SendAQI", WCloud.SendAirQuality); + ini.SetValue("WeatherCloud", "SendSoilMoisture", WCloud.SendSoilMoisture); + ini.SetValue("WeatherCloud", "SoilMoistureSensor", WCloud.SoilMoistureSensor); + ini.SetValue("WeatherCloud", "SendLeafWetness", WCloud.SendLeafWetness); + ini.SetValue("WeatherCloud", "LeafWetnessSensor", WCloud.LeafWetnessSensor); + + ini.SetValue("Twitter", "User", Twitter.ID); + ini.SetValue("Twitter", "Password", Twitter.PW); + ini.SetValue("Twitter", "Enabled", Twitter.Enabled); + ini.SetValue("Twitter", "Interval", Twitter.Interval); + ini.SetValue("Twitter", "OauthToken", Twitter.OauthToken); + ini.SetValue("Twitter", "OauthTokenSecret", Twitter.OauthTokenSecret); + ini.SetValue("Twitter", "TwitterSendLocation", Twitter.SendLocation); + + ini.SetValue("PWSweather", "ID", PWS.ID); + ini.SetValue("PWSweather", "Password", PWS.PW); + ini.SetValue("PWSweather", "Enabled", PWS.Enabled); + ini.SetValue("PWSweather", "Interval", PWS.Interval); + ini.SetValue("PWSweather", "SendUV", PWS.SendUV); + ini.SetValue("PWSweather", "SendSR", PWS.SendSolar); + ini.SetValue("PWSweather", "CatchUp", PWS.CatchUp); + + ini.SetValue("WOW", "ID", WOW.ID); + ini.SetValue("WOW", "Password", WOW.PW); + ini.SetValue("WOW", "Enabled", WOW.Enabled); + ini.SetValue("WOW", "Interval", WOW.Interval); + ini.SetValue("WOW", "SendUV", WOW.SendUV); + ini.SetValue("WOW", "SendSR", WOW.SendSolar); + ini.SetValue("WOW", "CatchUp", WOW.CatchUp); + + ini.SetValue("APRS", "ID", APRS.ID); + ini.SetValue("APRS", "pass", APRS.PW); + ini.SetValue("APRS", "server", APRS.Server); + ini.SetValue("APRS", "port", APRS.Port); + ini.SetValue("APRS", "Enabled", APRS.Enabled); + ini.SetValue("APRS", "Interval", APRS.Interval); + ini.SetValue("APRS", "SendSR", APRS.SendSolar); + ini.SetValue("APRS", "APRSHumidityCutoff", APRS.HumidityCutoff); + + ini.SetValue("OpenWeatherMap", "Enabled", OpenWeatherMap.Enabled); + ini.SetValue("OpenWeatherMap", "CatchUp", OpenWeatherMap.CatchUp); + ini.SetValue("OpenWeatherMap", "APIkey", OpenWeatherMap.PW); + ini.SetValue("OpenWeatherMap", "StationId", OpenWeatherMap.ID); + ini.SetValue("OpenWeatherMap", "Interval", OpenWeatherMap.Interval); + + ini.SetValue("WindGuru", "Enabled", WindGuru.Enabled); + ini.SetValue("WindGuru", "StationUID", WindGuru.ID); + ini.SetValue("WindGuru", "Password", WindGuru.PW); + ini.SetValue("WindGuru", "Interval", WindGuru.Interval); + ini.SetValue("WindGuru", "SendRain", WindGuru.SendRain); + + ini.SetValue("MQTT", "Server", MQTT.Server); + ini.SetValue("MQTT", "Port", MQTT.Port); + ini.SetValue("MQTT", "UseTLS", MQTT.UseTLS); + ini.SetValue("MQTT", "Username", MQTT.Username); + ini.SetValue("MQTT", "Password", MQTT.Password); + ini.SetValue("MQTT", "EnableDataUpdate", MQTT.EnableDataUpdate); + ini.SetValue("MQTT", "UpdateTopic", MQTT.UpdateTopic); + ini.SetValue("MQTT", "UpdateTemplate", MQTT.UpdateTemplate); + ini.SetValue("MQTT", "UpdateRetained", MQTT.UpdateRetained); + ini.SetValue("MQTT", "EnableInterval", MQTT.EnableInterval); + ini.SetValue("MQTT", "IntervalTime", MQTT.IntervalTime); + ini.SetValue("MQTT", "IntervalTopic", MQTT.IntervalTopic); + ini.SetValue("MQTT", "IntervalTemplate", MQTT.IntervalTemplate); + ini.SetValue("MQTT", "IntervalRetained", MQTT.IntervalRetained); + + ini.SetValue("Alarms", "alarmlowtemp", LowTempAlarm.Value); + ini.SetValue("Alarms", "LowTempAlarmSet", LowTempAlarm.Enabled); + ini.SetValue("Alarms", "LowTempAlarmSound", LowTempAlarm.Sound); + ini.SetValue("Alarms", "LowTempAlarm.SoundFile", LowTempAlarm.SoundFile); + ini.SetValue("Alarms", "LowTempAlarmNotify", LowTempAlarm.Notify); + ini.SetValue("Alarms", "LowTempAlarmEmail", LowTempAlarm.Email); + ini.SetValue("Alarms", "LowTempAlarmLatch", LowTempAlarm.Latch); + ini.SetValue("Alarms", "LowTempAlarmLatchHours", LowTempAlarm.LatchHours); + + ini.SetValue("Alarms", "alarmhightemp", HighTempAlarm.Value); + ini.SetValue("Alarms", "HighTempAlarmSet", HighTempAlarm.Enabled); + ini.SetValue("Alarms", "HighTempAlarmSound", HighTempAlarm.Sound); + ini.SetValue("Alarms", "HighTempAlarmSoundFile", HighTempAlarm.SoundFile); + ini.SetValue("Alarms", "HighTempAlarmNotify", HighTempAlarm.Notify); + ini.SetValue("Alarms", "HighTempAlarmEmail", HighTempAlarm.Email); + ini.SetValue("Alarms", "HighTempAlarmLatch", HighTempAlarm.Latch); + ini.SetValue("Alarms", "HighTempAlarmLatchHours", HighTempAlarm.LatchHours); + + ini.SetValue("Alarms", "alarmtempchange", TempChangeAlarm.Value); + ini.SetValue("Alarms", "TempChangeAlarmSet", TempChangeAlarm.Enabled); + ini.SetValue("Alarms", "TempChangeAlarmSound", TempChangeAlarm.Sound); + ini.SetValue("Alarms", "TempChangeAlarmSoundFile", TempChangeAlarm.SoundFile); + ini.SetValue("Alarms", "TempChangeAlarmNotify", TempChangeAlarm.Notify); + ini.SetValue("Alarms", "TempChangeAlarmEmail", TempChangeAlarm.Email); + ini.SetValue("Alarms", "TempChangeAlarmLatch", TempChangeAlarm.Latch); + ini.SetValue("Alarms", "TempChangeAlarmLatchHours", TempChangeAlarm.LatchHours); + + ini.SetValue("Alarms", "alarmlowpress", LowPressAlarm.Value); + ini.SetValue("Alarms", "LowPressAlarmSet", LowPressAlarm.Enabled); + ini.SetValue("Alarms", "LowPressAlarmSound", LowPressAlarm.Sound); + ini.SetValue("Alarms", "LowPressAlarmSoundFile", LowPressAlarm.SoundFile); + ini.SetValue("Alarms", "LowPressAlarmNotify", LowPressAlarm.Notify); + ini.SetValue("Alarms", "LowPressAlarmEmail", LowPressAlarm.Email); + ini.SetValue("Alarms", "LowPressAlarmLatch", LowPressAlarm.Latch); + ini.SetValue("Alarms", "LowPressAlarmLatchHours", LowPressAlarm.LatchHours); + + ini.SetValue("Alarms", "alarmhighpress", HighPressAlarm.Value); + ini.SetValue("Alarms", "HighPressAlarmSet", HighPressAlarm.Enabled); + ini.SetValue("Alarms", "HighPressAlarmSound", HighPressAlarm.Sound); + ini.SetValue("Alarms", "HighPressAlarmSoundFile", HighPressAlarm.SoundFile); + ini.SetValue("Alarms", "HighPressAlarmNotify", HighPressAlarm.Notify); + ini.SetValue("Alarms", "HighPressAlarmEmail", HighPressAlarm.Email); + ini.SetValue("Alarms", "HighPressAlarmLatch", HighPressAlarm.Latch); + ini.SetValue("Alarms", "HighPressAlarmLatchHours", HighPressAlarm.LatchHours); + + ini.SetValue("Alarms", "alarmpresschange", PressChangeAlarm.Value); + ini.SetValue("Alarms", "PressChangeAlarmSet", PressChangeAlarm.Enabled); + ini.SetValue("Alarms", "PressChangeAlarmSound", PressChangeAlarm.Sound); + ini.SetValue("Alarms", "PressChangeAlarmSoundFile", PressChangeAlarm.SoundFile); + ini.SetValue("Alarms", "PressChangeAlarmNotify", PressChangeAlarm.Notify); + ini.SetValue("Alarms", "PressChangeAlarmEmail", PressChangeAlarm.Email); + ini.SetValue("Alarms", "PressChangeAlarmLatch", PressChangeAlarm.Latch); + ini.SetValue("Alarms", "PressChangeAlarmLatchHours", PressChangeAlarm.LatchHours); + + ini.SetValue("Alarms", "alarmhighraintoday", HighRainTodayAlarm.Value); + ini.SetValue("Alarms", "HighRainTodayAlarmSet", HighRainTodayAlarm.Enabled); + ini.SetValue("Alarms", "HighRainTodayAlarmSound", HighRainTodayAlarm.Sound); + ini.SetValue("Alarms", "HighRainTodayAlarmSoundFile", HighRainTodayAlarm.SoundFile); + ini.SetValue("Alarms", "HighRainTodayAlarmNotify", HighRainTodayAlarm.Notify); + ini.SetValue("Alarms", "HighRainTodayAlarmEmail", HighRainTodayAlarm.Email); + ini.SetValue("Alarms", "HighRainTodayAlarmLatch", HighRainTodayAlarm.Latch); + ini.SetValue("Alarms", "HighRainTodayAlarmLatchHours", HighRainTodayAlarm.LatchHours); + + ini.SetValue("Alarms", "alarmhighrainrate", HighRainRateAlarm.Value); + ini.SetValue("Alarms", "HighRainRateAlarmSet", HighRainRateAlarm.Enabled); + ini.SetValue("Alarms", "HighRainRateAlarmSound", HighRainRateAlarm.Sound); + ini.SetValue("Alarms", "HighRainRateAlarmSoundFile", HighRainRateAlarm.SoundFile); + ini.SetValue("Alarms", "HighRainRateAlarmNotify", HighRainRateAlarm.Notify); + ini.SetValue("Alarms", "HighRainRateAlarmEmail", HighRainRateAlarm.Email); + ini.SetValue("Alarms", "HighRainRateAlarmLatch", HighRainRateAlarm.Latch); + ini.SetValue("Alarms", "HighRainRateAlarmLatchHours", HighRainRateAlarm.LatchHours); + + ini.SetValue("Alarms", "alarmhighgust", HighGustAlarm.Value); + ini.SetValue("Alarms", "HighGustAlarmSet", HighGustAlarm.Enabled); + ini.SetValue("Alarms", "HighGustAlarmSound", HighGustAlarm.Sound); + ini.SetValue("Alarms", "HighGustAlarmSoundFile", HighGustAlarm.SoundFile); + ini.SetValue("Alarms", "HighGustAlarmNotify", HighGustAlarm.Notify); + ini.SetValue("Alarms", "HighGustAlarmEmail", HighGustAlarm.Email); + ini.SetValue("Alarms", "HighGustAlarmLatch", HighGustAlarm.Latch); + ini.SetValue("Alarms", "HighGustAlarmLatchHours", HighGustAlarm.LatchHours); + + ini.SetValue("Alarms", "alarmhighwind", HighWindAlarm.Value); + ini.SetValue("Alarms", "HighWindAlarmSet", HighWindAlarm.Enabled); + ini.SetValue("Alarms", "HighWindAlarmSound", HighWindAlarm.Sound); + ini.SetValue("Alarms", "HighWindAlarmSoundFile", HighWindAlarm.SoundFile); + ini.SetValue("Alarms", "HighWindAlarmNotify", HighWindAlarm.Notify); + ini.SetValue("Alarms", "HighWindAlarmEmail", HighWindAlarm.Email); + ini.SetValue("Alarms", "HighWindAlarmLatch", HighWindAlarm.Latch); + ini.SetValue("Alarms", "HighWindAlarmLatchHours", HighWindAlarm.LatchHours); + + ini.SetValue("Alarms", "SensorAlarmSet", SensorAlarm.Enabled); + ini.SetValue("Alarms", "SensorAlarmSound", SensorAlarm.Sound); + ini.SetValue("Alarms", "SensorAlarmSoundFile", SensorAlarm.SoundFile); + ini.SetValue("Alarms", "SensorAlarmNotify", SensorAlarm.Notify); + ini.SetValue("Alarms", "SensorAlarmEmail", SensorAlarm.Email); + ini.SetValue("Alarms", "SensorAlarmLatch", SensorAlarm.Latch); + ini.SetValue("Alarms", "SensorAlarmLatchHours", SensorAlarm.LatchHours); + + ini.SetValue("Alarms", "DataStoppedAlarmSet", DataStoppedAlarm.Enabled); + ini.SetValue("Alarms", "DataStoppedAlarmSound", DataStoppedAlarm.Sound); + ini.SetValue("Alarms", "DataStoppedAlarmSoundFile", DataStoppedAlarm.SoundFile); + ini.SetValue("Alarms", "DataStoppedAlarmNotify", DataStoppedAlarm.Notify); + ini.SetValue("Alarms", "DataStoppedAlarmEmail", DataStoppedAlarm.Email); + ini.SetValue("Alarms", "DataStoppedAlarmLatch", DataStoppedAlarm.Latch); + ini.SetValue("Alarms", "DataStoppedAlarmLatchHours", DataStoppedAlarm.LatchHours); + + ini.SetValue("Alarms", "BatteryLowAlarmSet", BatteryLowAlarm.Enabled); + ini.SetValue("Alarms", "BatteryLowAlarmSound", BatteryLowAlarm.Sound); + ini.SetValue("Alarms", "BatteryLowAlarmSoundFile", BatteryLowAlarm.SoundFile); + ini.SetValue("Alarms", "BatteryLowAlarmNotify", BatteryLowAlarm.Notify); + ini.SetValue("Alarms", "BatteryLowAlarmEmail", BatteryLowAlarm.Email); + ini.SetValue("Alarms", "BatteryLowAlarmLatch", BatteryLowAlarm.Latch); + ini.SetValue("Alarms", "BatteryLowAlarmLatchHours", BatteryLowAlarm.LatchHours); + + ini.SetValue("Alarms", "DataSpikeAlarmSet", SpikeAlarm.Enabled); + ini.SetValue("Alarms", "DataSpikeAlarmSound", SpikeAlarm.Sound); + ini.SetValue("Alarms", "DataSpikeAlarmSoundFile", SpikeAlarm.SoundFile); + ini.SetValue("Alarms", "DataSpikeAlarmNotify", SpikeAlarm.Notify); + ini.SetValue("Alarms", "DataSpikeAlarmEmail", SpikeAlarm.Email); + ini.SetValue("Alarms", "DataSpikeAlarmLatch", SpikeAlarm.Latch); + ini.SetValue("Alarms", "DataSpikeAlarmLatchHours", SpikeAlarm.LatchHours); + + ini.SetValue("Alarms", "UpgradeAlarmSet", UpgradeAlarm.Enabled); + ini.SetValue("Alarms", "UpgradeAlarmSound", UpgradeAlarm.Sound); + ini.SetValue("Alarms", "UpgradeAlarmSoundFile", UpgradeAlarm.SoundFile); + ini.SetValue("Alarms", "UpgradeAlarmNotify", UpgradeAlarm.Notify); + ini.SetValue("Alarms", "UpgradeAlarmEmail", UpgradeAlarm.Email); + ini.SetValue("Alarms", "UpgradeAlarmLatch", UpgradeAlarm.Latch); + ini.SetValue("Alarms", "UpgradeAlarmLatchHours", UpgradeAlarm.LatchHours); + + ini.SetValue("Alarms", "HttpUploadAlarmSet", HttpUploadAlarm.Enabled); + ini.SetValue("Alarms", "HttpUploadAlarmSound", HttpUploadAlarm.Sound); + ini.SetValue("Alarms", "HttpUploadAlarmSoundFile", HttpUploadAlarm.SoundFile); + ini.SetValue("Alarms", "HttpUploadAlarmNotify", HttpUploadAlarm.Notify); + ini.SetValue("Alarms", "HttpUploadAlarmEmail", HttpUploadAlarm.Email); + ini.SetValue("Alarms", "HttpUploadAlarmLatch", HttpUploadAlarm.Latch); + ini.SetValue("Alarms", "HttpUploadAlarmLatchHours", HttpUploadAlarm.LatchHours); + + ini.SetValue("Alarms", "MySqlUploadAlarmSet", MySqlUploadAlarm.Enabled); + ini.SetValue("Alarms", "MySqlUploadAlarmSound", MySqlUploadAlarm.Sound); + ini.SetValue("Alarms", "MySqlUploadAlarmSoundFile", MySqlUploadAlarm.SoundFile); + ini.SetValue("Alarms", "MySqlUploadAlarmNotify", MySqlUploadAlarm.Notify); + ini.SetValue("Alarms", "MySqlUploadAlarmEmail", MySqlUploadAlarm.Email); + ini.SetValue("Alarms", "MySqlUploadAlarmLatch", MySqlUploadAlarm.Latch); + ini.SetValue("Alarms", "MySqlUploadAlarmLatchHours", MySqlUploadAlarm.LatchHours); + + ini.SetValue("Alarms", "FromEmail", AlarmFromEmail); + ini.SetValue("Alarms", "DestEmail", AlarmDestEmail.Join(";")); + ini.SetValue("Alarms", "UseHTML", AlarmEmailHtml); + + + ini.SetValue("Offsets", "PressOffset", Calib.Press.Offset); + ini.SetValue("Offsets", "TempOffset", Calib.Temp.Offset); + ini.SetValue("Offsets", "HumOffset", Calib.Hum.Offset); + ini.SetValue("Offsets", "WindDirOffset", Calib.WindDir.Offset); + ini.SetValue("Offsets", "InTempOffset", Calib.InTemp.Offset); + ini.SetValue("Offsets", "UVOffset", Calib.UV.Offset); + ini.SetValue("Offsets", "SolarOffset", Calib.Solar.Offset); + ini.SetValue("Offsets", "WetBulbOffset", Calib.WetBulb.Offset); + //ini.SetValue("Offsets", "DavisCalcAltPressOffset", DavisCalcAltPressOffset); + + ini.SetValue("Offsets", "PressMult", Calib.Press.Mult); + ini.SetValue("Offsets", "WindSpeedMult", Calib.WindSpeed.Mult); + ini.SetValue("Offsets", "WindGustMult", Calib.WindGust.Mult); + ini.SetValue("Offsets", "TempMult", Calib.Temp.Mult); + ini.SetValue("Offsets", "HumMult", Calib.Hum.Mult); + ini.SetValue("Offsets", "RainMult", Calib.Rain.Mult); + ini.SetValue("Offsets", "SolarMult", Calib.Solar.Mult); + ini.SetValue("Offsets", "UVMult", Calib.UV.Mult); + ini.SetValue("Offsets", "WetBulbMult", Calib.WetBulb.Mult); + + ini.SetValue("Limits", "TempHighC", Limit.TempHigh); + ini.SetValue("Limits", "TempLowC", Limit.TempLow); + ini.SetValue("Limits", "DewHighC", Limit.DewHigh); + ini.SetValue("Limits", "PressHighMB", Limit.PressHigh); + ini.SetValue("Limits", "PressLowMB", Limit.PressLow); + ini.SetValue("Limits", "WindHighMS", Limit.WindHigh); + + ini.SetValue("xAP", "Enabled", xapEnabled); + ini.SetValue("xAP", "UID", xapUID); + ini.SetValue("xAP", "Port", xapPort); + + ini.SetValue("Solar", "SunThreshold", SunThreshold); + ini.SetValue("Solar", "RStransfactor", RStransfactor); + ini.SetValue("Solar", "SolarMinimum", SolarMinimum); + ini.SetValue("Solar", "UseBlakeLarsen", UseBlakeLarsen); + ini.SetValue("Solar", "SolarCalc", SolarCalc); + ini.SetValue("Solar", "BrasTurbidity", BrasTurbidity); + + ini.SetValue("NOAA", "Name", NOAAname); + ini.SetValue("NOAA", "City", NOAAcity); + ini.SetValue("NOAA", "State", NOAAstate); + ini.SetValue("NOAA", "12hourformat", NOAA12hourformat); + ini.SetValue("NOAA", "HeatingThreshold", NOAAheatingthreshold); + ini.SetValue("NOAA", "CoolingThreshold", NOAAcoolingthreshold); + ini.SetValue("NOAA", "MaxTempComp1", NOAAmaxtempcomp1); + ini.SetValue("NOAA", "MaxTempComp2", NOAAmaxtempcomp2); + ini.SetValue("NOAA", "MinTempComp1", NOAAmintempcomp1); + ini.SetValue("NOAA", "MinTempComp2", NOAAmintempcomp2); + ini.SetValue("NOAA", "RainComp1", NOAAraincomp1); + ini.SetValue("NOAA", "RainComp2", NOAAraincomp2); + ini.SetValue("NOAA", "RainComp3", NOAAraincomp3); + ini.SetValue("NOAA", "AutoSave", NOAAAutoSave); + ini.SetValue("NOAA", "AutoFTP", NOAAAutoFTP); + ini.SetValue("NOAA", "MonthFileFormat", NOAAMonthFileFormat); + ini.SetValue("NOAA", "YearFileFormat", NOAAYearFileFormat); + ini.SetValue("NOAA", "FTPDirectory", NOAAFTPDirectory); + ini.SetValue("NOAA", "NOAAUseUTF8", NOAAUseUTF8); + ini.SetValue("NOAA", "UseDotDecimal", NOAAUseDotDecimal); + + ini.SetValue("NOAA", "NOAATempNormJan", NOAATempNorms[1]); + ini.SetValue("NOAA", "NOAATempNormFeb", NOAATempNorms[2]); + ini.SetValue("NOAA", "NOAATempNormMar", NOAATempNorms[3]); + ini.SetValue("NOAA", "NOAATempNormApr", NOAATempNorms[4]); + ini.SetValue("NOAA", "NOAATempNormMay", NOAATempNorms[5]); + ini.SetValue("NOAA", "NOAATempNormJun", NOAATempNorms[6]); + ini.SetValue("NOAA", "NOAATempNormJul", NOAATempNorms[7]); + ini.SetValue("NOAA", "NOAATempNormAug", NOAATempNorms[8]); + ini.SetValue("NOAA", "NOAATempNormSep", NOAATempNorms[9]); + ini.SetValue("NOAA", "NOAATempNormOct", NOAATempNorms[10]); + ini.SetValue("NOAA", "NOAATempNormNov", NOAATempNorms[11]); + ini.SetValue("NOAA", "NOAATempNormDec", NOAATempNorms[12]); + + ini.SetValue("NOAA", "NOAARainNormJan", NOAARainNorms[1]); + ini.SetValue("NOAA", "NOAARainNormFeb", NOAARainNorms[2]); + ini.SetValue("NOAA", "NOAARainNormMar", NOAARainNorms[3]); + ini.SetValue("NOAA", "NOAARainNormApr", NOAARainNorms[4]); + ini.SetValue("NOAA", "NOAARainNormMay", NOAARainNorms[5]); + ini.SetValue("NOAA", "NOAARainNormJun", NOAARainNorms[6]); + ini.SetValue("NOAA", "NOAARainNormJul", NOAARainNorms[7]); + ini.SetValue("NOAA", "NOAARainNormAug", NOAARainNorms[8]); + ini.SetValue("NOAA", "NOAARainNormSep", NOAARainNorms[9]); + ini.SetValue("NOAA", "NOAARainNormOct", NOAARainNorms[10]); + ini.SetValue("NOAA", "NOAARainNormNov", NOAARainNorms[11]); + ini.SetValue("NOAA", "NOAARainNormDec", NOAARainNorms[12]); + + ini.SetValue("Proxies", "HTTPProxyName", HTTPProxyName); + ini.SetValue("Proxies", "HTTPProxyPort", HTTPProxyPort); + ini.SetValue("Proxies", "HTTPProxyUser", HTTPProxyUser); + ini.SetValue("Proxies", "HTTPProxyPassword", HTTPProxyPassword); + + ini.SetValue("Display", "NumWindRosePoints", NumWindRosePoints); + ini.SetValue("Display", "UseApparent", DisplayOptions.UseApparent); + ini.SetValue("Display", "DisplaySolarData", DisplayOptions.ShowSolar); + ini.SetValue("Display", "DisplayUvData", DisplayOptions.ShowUV); + + ini.SetValue("Graphs", "ChartMaxDays", GraphDays); + ini.SetValue("Graphs", "GraphHours", GraphHours); + ini.SetValue("Graphs", "MoonImageEnabled", MoonImageEnabled); + ini.SetValue("Graphs", "MoonImageSize", MoonImageSize); + ini.SetValue("Graphs", "MoonImageFtpDest", MoonImageFtpDest); + ini.SetValue("Graphs", "TempVisible", GraphOptions.TempVisible); + ini.SetValue("Graphs", "InTempVisible", GraphOptions.InTempVisible); + ini.SetValue("Graphs", "HIVisible", GraphOptions.HIVisible); + ini.SetValue("Graphs", "DPVisible", GraphOptions.DPVisible); + ini.SetValue("Graphs", "WCVisible", GraphOptions.WCVisible); + ini.SetValue("Graphs", "AppTempVisible", GraphOptions.AppTempVisible); + ini.SetValue("Graphs", "FeelsLikeVisible", GraphOptions.FeelsLikeVisible); + ini.SetValue("Graphs", "HumidexVisible", GraphOptions.HumidexVisible); + ini.SetValue("Graphs", "InHumVisible", GraphOptions.InHumVisible); + ini.SetValue("Graphs", "OutHumVisible", GraphOptions.OutHumVisible); + ini.SetValue("Graphs", "UVVisible", GraphOptions.UVVisible); + ini.SetValue("Graphs", "SolarVisible", GraphOptions.SolarVisible); + ini.SetValue("Graphs", "SunshineVisible", GraphOptions.SunshineVisible); + ini.SetValue("Graphs", "DailyAvgTempVisible", GraphOptions.DailyAvgTempVisible); + ini.SetValue("Graphs", "DailyMaxTempVisible", GraphOptions.DailyMaxTempVisible); + ini.SetValue("Graphs", "DailyMinTempVisible", GraphOptions.DailyMinTempVisible); + ini.SetValue("Graphs", "GrowingDegreeDaysVisible1", GraphOptions.GrowingDegreeDaysVisible1); + ini.SetValue("Graphs", "GrowingDegreeDaysVisible2", GraphOptions.GrowingDegreeDaysVisible2); + ini.SetValue("Graphs", "TempSumVisible0", GraphOptions.TempSumVisible0); + ini.SetValue("Graphs", "TempSumVisible1", GraphOptions.TempSumVisible1); + ini.SetValue("Graphs", "TempSumVisible2", GraphOptions.TempSumVisible2); + + + ini.SetValue("MySQL", "Host", MySqlConnSettings.Server); + ini.SetValue("MySQL", "Port", MySqlConnSettings.Port); + ini.SetValue("MySQL", "User", MySqlConnSettings.UserID); + ini.SetValue("MySQL", "Pass", MySqlConnSettings.Password); + ini.SetValue("MySQL", "Database", MySqlConnSettings.Database); + ini.SetValue("MySQL", "MonthlyMySqlEnabled", MonthlyMySqlEnabled); + ini.SetValue("MySQL", "RealtimeMySqlEnabled", RealtimeMySqlEnabled); + ini.SetValue("MySQL", "RealtimeMySql1MinLimit", RealtimeMySql1MinLimit); + ini.SetValue("MySQL", "DayfileMySqlEnabled", DayfileMySqlEnabled); + ini.SetValue("MySQL", "MonthlyTable", MySqlMonthlyTable); + ini.SetValue("MySQL", "DayfileTable", MySqlDayfileTable); + ini.SetValue("MySQL", "RealtimeTable", MySqlRealtimeTable); + ini.SetValue("MySQL", "RealtimeRetention", MySqlRealtimeRetention); + ini.SetValue("MySQL", "CustomMySqlSecondsCommandString", CustomMySqlSecondsCommandString); + ini.SetValue("MySQL", "CustomMySqlMinutesCommandString", CustomMySqlMinutesCommandString); + ini.SetValue("MySQL", "CustomMySqlRolloverCommandString", CustomMySqlRolloverCommandString); + + ini.SetValue("MySQL", "CustomMySqlSecondsEnabled", CustomMySqlSecondsEnabled); + ini.SetValue("MySQL", "CustomMySqlMinutesEnabled", CustomMySqlMinutesEnabled); + ini.SetValue("MySQL", "CustomMySqlRolloverEnabled", CustomMySqlRolloverEnabled); + + ini.SetValue("MySQL", "CustomMySqlSecondsInterval", CustomMySqlSecondsInterval); + ini.SetValue("MySQL", "CustomMySqlMinutesIntervalIndex", CustomMySqlMinutesIntervalIndex); + + ini.SetValue("HTTP", "CustomHttpSecondsString", CustomHttpSecondsString); + ini.SetValue("HTTP", "CustomHttpMinutesString", CustomHttpMinutesString); + ini.SetValue("HTTP", "CustomHttpRolloverString", CustomHttpRolloverString); + + ini.SetValue("HTTP", "CustomHttpSecondsEnabled", CustomHttpSecondsEnabled); + ini.SetValue("HTTP", "CustomHttpMinutesEnabled", CustomHttpMinutesEnabled); + ini.SetValue("HTTP", "CustomHttpRolloverEnabled", CustomHttpRolloverEnabled); + + ini.SetValue("HTTP", "CustomHttpSecondsInterval", CustomHttpSecondsInterval); + ini.SetValue("HTTP", "CustomHttpMinutesIntervalIndex", CustomHttpMinutesIntervalIndex); + + for (int i = 0; i < SelectaChartOptions.series.Length; i++) + { + ini.SetValue("Select-a-Chart", "Series" + i, SelectaChartOptions.series[i]); + ini.SetValue("Select-a-Chart", "Colour" + i, SelectaChartOptions.colours[i]); + } + + // Email settings + ini.SetValue("SMTP", "Enabled", SmtpOptions.Enabled); + ini.SetValue("SMTP", "ServerName", SmtpOptions.Server); + ini.SetValue("SMTP", "Port", SmtpOptions.Port); + ini.SetValue("SMTP", "SSLOption", SmtpOptions.SslOption); + ini.SetValue("SMTP", "RequiresAuthentication", SmtpOptions.RequiresAuthentication); + ini.SetValue("SMTP", "User", SmtpOptions.User); + ini.SetValue("SMTP", "Password", SmtpOptions.Password); + ini.SetValue("SMTP", "Logging", SmtpOptions.Logging); + + // Growing Degree Days + ini.SetValue("GrowingDD", "BaseTemperature1", GrowingBase1); + ini.SetValue("GrowingDD", "BaseTemperature2", GrowingBase2); + ini.SetValue("GrowingDD", "YearStarts", GrowingYearStarts); + ini.SetValue("GrowingDD", "Cap30C", GrowingCap30C); + + // Temperature Sum + ini.SetValue("TempSum", "TempSumYearStart", TempSumYearStarts); + ini.SetValue("TempSum", "BaseTemperature1", TempSumBase1); + ini.SetValue("TempSum", "BaseTemperature2", TempSumBase2); + + ini.Flush(); + + LogMessage("Completed writing Cumulus.ini file"); + } + + private void ReadStringsFile() + { + IniFile ini = new IniFile("strings.ini"); + + // forecast + + ForecastNotAvailable = ini.GetValue("Forecast", "notavailable", ForecastNotAvailable); + + exceptional = ini.GetValue("Forecast", "exceptional", exceptional); + zForecast[0] = ini.GetValue("Forecast", "forecast1", zForecast[0]); + zForecast[1] = ini.GetValue("Forecast", "forecast2", zForecast[1]); + zForecast[2] = ini.GetValue("Forecast", "forecast3", zForecast[2]); + zForecast[3] = ini.GetValue("Forecast", "forecast4", zForecast[3]); + zForecast[4] = ini.GetValue("Forecast", "forecast5", zForecast[4]); + zForecast[5] = ini.GetValue("Forecast", "forecast6", zForecast[5]); + zForecast[6] = ini.GetValue("Forecast", "forecast7", zForecast[6]); + zForecast[7] = ini.GetValue("Forecast", "forecast8", zForecast[7]); + zForecast[8] = ini.GetValue("Forecast", "forecast9", zForecast[8]); + zForecast[9] = ini.GetValue("Forecast", "forecast10", zForecast[9]); + zForecast[10] = ini.GetValue("Forecast", "forecast11", zForecast[10]); + zForecast[11] = ini.GetValue("Forecast", "forecast12", zForecast[11]); + zForecast[12] = ini.GetValue("Forecast", "forecast13", zForecast[12]); + zForecast[13] = ini.GetValue("Forecast", "forecast14", zForecast[13]); + zForecast[14] = ini.GetValue("Forecast", "forecast15", zForecast[14]); + zForecast[15] = ini.GetValue("Forecast", "forecast16", zForecast[15]); + zForecast[16] = ini.GetValue("Forecast", "forecast17", zForecast[16]); + zForecast[17] = ini.GetValue("Forecast", "forecast18", zForecast[17]); + zForecast[18] = ini.GetValue("Forecast", "forecast19", zForecast[18]); + zForecast[19] = ini.GetValue("Forecast", "forecast20", zForecast[19]); + zForecast[20] = ini.GetValue("Forecast", "forecast21", zForecast[20]); + zForecast[21] = ini.GetValue("Forecast", "forecast22", zForecast[21]); + zForecast[22] = ini.GetValue("Forecast", "forecast23", zForecast[22]); + zForecast[23] = ini.GetValue("Forecast", "forecast24", zForecast[23]); + zForecast[24] = ini.GetValue("Forecast", "forecast25", zForecast[24]); + zForecast[25] = ini.GetValue("Forecast", "forecast26", zForecast[25]); + // moon phases + Newmoon = ini.GetValue("MoonPhases", "Newmoon", Newmoon); + WaxingCrescent = ini.GetValue("MoonPhases", "WaxingCrescent", WaxingCrescent); + FirstQuarter = ini.GetValue("MoonPhases", "FirstQuarter", FirstQuarter); + WaxingGibbous = ini.GetValue("MoonPhases", "WaxingGibbous", WaxingGibbous); + Fullmoon = ini.GetValue("MoonPhases", "Fullmoon", Fullmoon); + WaningGibbous = ini.GetValue("MoonPhases", "WaningGibbous", WaningGibbous); + LastQuarter = ini.GetValue("MoonPhases", "LastQuarter", LastQuarter); + WaningCrescent = ini.GetValue("MoonPhases", "WaningCrescent", WaningCrescent); + // beaufort + Calm = ini.GetValue("Beaufort", "Calm", Calm); + Lightair = ini.GetValue("Beaufort", "Lightair", Lightair); + Lightbreeze = ini.GetValue("Beaufort", "Lightbreeze", Lightbreeze); + Gentlebreeze = ini.GetValue("Beaufort", "Gentlebreeze", Gentlebreeze); + Moderatebreeze = ini.GetValue("Beaufort", "Moderatebreeze", Moderatebreeze); + Freshbreeze = ini.GetValue("Beaufort", "Freshbreeze", Freshbreeze); + Strongbreeze = ini.GetValue("Beaufort", "Strongbreeze", Strongbreeze); + Neargale = ini.GetValue("Beaufort", "Neargale", Neargale); + Gale = ini.GetValue("Beaufort", "Gale", Gale); + Stronggale = ini.GetValue("Beaufort", "Stronggale", Stronggale); + Storm = ini.GetValue("Beaufort", "Storm", Storm); + Violentstorm = ini.GetValue("Beaufort", "Violentstorm", Violentstorm); + Hurricane = ini.GetValue("Beaufort", "Hurricane", Hurricane); + // trends + Risingveryrapidly = ini.GetValue("Trends", "Risingveryrapidly", Risingveryrapidly); + Risingquickly = ini.GetValue("Trends", "Risingquickly", Risingquickly); + Rising = ini.GetValue("Trends", "Rising", Rising); + Risingslowly = ini.GetValue("Trends", "Risingslowly", Risingslowly); + Steady = ini.GetValue("Trends", "Steady", Steady); + Fallingslowly = ini.GetValue("Trends", "Fallingslowly", Fallingslowly); + Falling = ini.GetValue("Trends", "Falling", Falling); + Fallingquickly = ini.GetValue("Trends", "Fallingquickly", Fallingquickly); + Fallingveryrapidly = ini.GetValue("Trends", "Fallingveryrapidly", Fallingveryrapidly); + // compass points + compassp[0] = ini.GetValue("Compass", "N", compassp[0]); + compassp[1] = ini.GetValue("Compass", "NNE", compassp[1]); + compassp[2] = ini.GetValue("Compass", "NE", compassp[2]); + compassp[3] = ini.GetValue("Compass", "ENE", compassp[3]); + compassp[4] = ini.GetValue("Compass", "E", compassp[4]); + compassp[5] = ini.GetValue("Compass", "ESE", compassp[5]); + compassp[6] = ini.GetValue("Compass", "SE", compassp[6]); + compassp[7] = ini.GetValue("Compass", "SSE", compassp[7]); + compassp[8] = ini.GetValue("Compass", "S", compassp[8]); + compassp[9] = ini.GetValue("Compass", "SSW", compassp[9]); + compassp[10] = ini.GetValue("Compass", "SW", compassp[10]); + compassp[11] = ini.GetValue("Compass", "WSW", compassp[11]); + compassp[12] = ini.GetValue("Compass", "W", compassp[12]); + compassp[13] = ini.GetValue("Compass", "WNW", compassp[13]); + compassp[14] = ini.GetValue("Compass", "NW", compassp[14]); + compassp[15] = ini.GetValue("Compass", "NNW", compassp[15]); + // graphs + /* + SmallGraphWindSpeedTitle = ini.GetValue("Graphs", "SmallGraphWindSpeedTitle", "Wind Speed"); + SmallGraphOutsideTemperatureTitle = ini.GetValue("Graphs", "SmallGraphOutsideTemperatureTitle", "Outside Temperature"); + SmallGraphInsideTemperatureTitle = ini.GetValue("Graphs", "SmallGraphInsideTemperatureTitle", "Inside Temperature"); + SmallGraphPressureTitle = ini.GetValue("Graphs", "SmallGraphPressureTitle", "Pressure"); + SmallGraphRainfallRateTitle = ini.GetValue("Graphs", "SmallGraphRainfallRateTitle", "Rainfall Rate"); + SmallGraphWindDirectionTitle = ini.GetValue("Graphs", "SmallGraphWindDirectionTitle", "Wind Direction"); + SmallGraphTempMinMaxAvgTitle = ini.GetValue("Graphs", "SmallGraphTempMinMaxAvgTitle", "Temp Min/Max/Avg"); + SmallGraphHumidityTitle = ini.GetValue("Graphs", "SmallGraphHumidityTitle", "Humidity"); + SmallGraphRainTodayTitle = ini.GetValue("Graphs", "SmallGraphRainTodayTitle", "Rain Today"); + SmallGraphDailyRainTitle = ini.GetValue("Graphs", "SmallGraphDailyRainTitle", "Daily Rain"); + SmallGraphSolarTitle = ini.GetValue("Graphs", "SmallGraphSolarTitle", "Solar Radiation"); + SmallGraphUVTitle = ini.GetValue("Graphs", "SmallGraphUVTitle", "UV Index"); + SmallGraphSunshineTitle = ini.GetValue("Graphs", "SmallGraphSunshineTitle", "Daily Sunshine (hrs)"); + + LargeGraphWindSpeedTitle = ini.GetValue("Graphs", "LargeGraphWindSpeedTitle", "Wind Speed"); + LargeGraphWindGustTitle = ini.GetValue("Graphs", "LargeGraphWindGustTitle", "Wind Gust"); + LargeGraphOutsideTempTitle = ini.GetValue("Graphs", "LargeGraphOutsideTempTitle", "Temperature"); + LargeGraphHeatIndexTitle = ini.GetValue("Graphs", "LargeGraphHeatIndexTitle", "Heat Index"); + LargeGraphDewPointTitle = ini.GetValue("Graphs", "LargeGraphDewPointTitle", "Dew Point"); + LargeGraphWindChillTitle = ini.GetValue("Graphs", "LargeGraphWindChillTitle", "Wind Chill"); + LargeGraphApparentTempTitle = ini.GetValue("Graphs", "LargeGraphApparentTempTitle", "Apparent Temperature"); + LargeGraphInsideTempTitle = ini.GetValue("Graphs", "LargeGraphInsideTempTitle", "Inside Temperature"); + LargeGraphPressureTitle = ini.GetValue("Graphs", "LargeGraphPressureTitle", "Pressure"); + LargeGraphRainfallRateTitle = ini.GetValue("Graphs", "LargeGraphRainfallRateTitle", "Rainfall Rate"); + LargeGraphWindDirectionTitle = ini.GetValue("Graphs", "LargeGraphWindDirectionTitle", "Wind Direction"); + LargeGraphWindAvgDirectionTitle = ini.GetValue("Graphs", "LargeGraphWindAvgDirectionTitle", "Average"); + LargeGraphMinTempTitle = ini.GetValue("Graphs", "LargeGraphMinTempTitle", "Min Temp"); + LargeGraphMaxTempTitle = ini.GetValue("Graphs", "LargeGraphMaxTempTitle", "Max Temp"); + LargeGraphAvgTempTitle = ini.GetValue("Graphs", "LargeGraphAvgTempTitle", "Avg Temp"); + LargeGraphInsideHumidityTitle = ini.GetValue("Graphs", "LargeGraphInsideHumidityTitle", "Inside Humidity"); + LargeGraphOutsideHumidityTitle = ini.GetValue("Graphs", "LargeGraphOutsideHumidityTitle", "Outside Humidity"); + LargeGraphRainfallTodayTitle = ini.GetValue("Graphs", "LargeGraphRainfallTodayTitle", "Rainfall Today"); + LargeGraphDailyRainfallTitle = ini.GetValue("Graphs", "LargeGraphDailyRainfallTitle", "Daily Rainfall"); + LargeGraphSolarTitle = ini.GetValue("Graphs", "LargeGraphSolarTitle", "Solar Radiation"); + LargeGraphMaxSolarTitle = ini.GetValue("Graphs", "LargeGraphMaxSolarTitle", "Theoretical Max"); + LargeGraphUVTitle = ini.GetValue("Graphs", "LargeGraphUVTitle", "UV Index"); + LargeGraphSunshineTitle = ini.GetValue("Graphs", "LargeGraphSunshineTitle", "Daily Sunshine (hrs)"); + */ + // Extra sensor captions + WMR200ExtraChannelCaptions[1] = ini.GetValue("ExtraSensorCaptions", "Solar", WMR200ExtraChannelCaptions[1]); + WMR200ExtraChannelCaptions[2] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel2", WMR200ExtraChannelCaptions[2]); + WMR200ExtraChannelCaptions[3] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel3", WMR200ExtraChannelCaptions[3]); + WMR200ExtraChannelCaptions[4] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel4", WMR200ExtraChannelCaptions[4]); + WMR200ExtraChannelCaptions[5] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel5", WMR200ExtraChannelCaptions[5]); + WMR200ExtraChannelCaptions[6] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel6", WMR200ExtraChannelCaptions[6]); + WMR200ExtraChannelCaptions[7] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel7", WMR200ExtraChannelCaptions[7]); + WMR200ExtraChannelCaptions[8] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel8", WMR200ExtraChannelCaptions[8]); + WMR200ExtraChannelCaptions[9] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel9", WMR200ExtraChannelCaptions[9]); + WMR200ExtraChannelCaptions[10] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel10", WMR200ExtraChannelCaptions[10]); + + // Extra temperature captions (for Extra Sensor Data screen) + ExtraTempCaptions[1] = ini.GetValue("ExtraTempCaptions", "Sensor1", ExtraTempCaptions[1]); + ExtraTempCaptions[2] = ini.GetValue("ExtraTempCaptions", "Sensor2", ExtraTempCaptions[2]); + ExtraTempCaptions[3] = ini.GetValue("ExtraTempCaptions", "Sensor3", ExtraTempCaptions[3]); + ExtraTempCaptions[4] = ini.GetValue("ExtraTempCaptions", "Sensor4", ExtraTempCaptions[4]); + ExtraTempCaptions[5] = ini.GetValue("ExtraTempCaptions", "Sensor5", ExtraTempCaptions[5]); + ExtraTempCaptions[6] = ini.GetValue("ExtraTempCaptions", "Sensor6", ExtraTempCaptions[6]); + ExtraTempCaptions[7] = ini.GetValue("ExtraTempCaptions", "Sensor7", ExtraTempCaptions[7]); + ExtraTempCaptions[8] = ini.GetValue("ExtraTempCaptions", "Sensor8", ExtraTempCaptions[8]); + ExtraTempCaptions[9] = ini.GetValue("ExtraTempCaptions", "Sensor9", ExtraTempCaptions[9]); + ExtraTempCaptions[10] = ini.GetValue("ExtraTempCaptions", "Sensor10", ExtraTempCaptions[10]); + + // Extra humidity captions (for Extra Sensor Data screen) + ExtraHumCaptions[1] = ini.GetValue("ExtraHumCaptions", "Sensor1", ExtraHumCaptions[1]); + ExtraHumCaptions[2] = ini.GetValue("ExtraHumCaptions", "Sensor2", ExtraHumCaptions[2]); + ExtraHumCaptions[3] = ini.GetValue("ExtraHumCaptions", "Sensor3", ExtraHumCaptions[3]); + ExtraHumCaptions[4] = ini.GetValue("ExtraHumCaptions", "Sensor4", ExtraHumCaptions[4]); + ExtraHumCaptions[5] = ini.GetValue("ExtraHumCaptions", "Sensor5", ExtraHumCaptions[5]); + ExtraHumCaptions[6] = ini.GetValue("ExtraHumCaptions", "Sensor6", ExtraHumCaptions[6]); + ExtraHumCaptions[7] = ini.GetValue("ExtraHumCaptions", "Sensor7", ExtraHumCaptions[7]); + ExtraHumCaptions[8] = ini.GetValue("ExtraHumCaptions", "Sensor8", ExtraHumCaptions[8]); + ExtraHumCaptions[9] = ini.GetValue("ExtraHumCaptions", "Sensor9", ExtraHumCaptions[9]); + ExtraHumCaptions[10] = ini.GetValue("ExtraHumCaptions", "Sensor10", ExtraHumCaptions[10]); + + // Extra dew point captions (for Extra Sensor Data screen) + ExtraDPCaptions[1] = ini.GetValue("ExtraDPCaptions", "Sensor1", ExtraDPCaptions[1]); + ExtraDPCaptions[2] = ini.GetValue("ExtraDPCaptions", "Sensor2", ExtraDPCaptions[2]); + ExtraDPCaptions[3] = ini.GetValue("ExtraDPCaptions", "Sensor3", ExtraDPCaptions[3]); + ExtraDPCaptions[4] = ini.GetValue("ExtraDPCaptions", "Sensor4", ExtraDPCaptions[4]); + ExtraDPCaptions[5] = ini.GetValue("ExtraDPCaptions", "Sensor5", ExtraDPCaptions[5]); + ExtraDPCaptions[6] = ini.GetValue("ExtraDPCaptions", "Sensor6", ExtraDPCaptions[6]); + ExtraDPCaptions[7] = ini.GetValue("ExtraDPCaptions", "Sensor7", ExtraDPCaptions[7]); + ExtraDPCaptions[8] = ini.GetValue("ExtraDPCaptions", "Sensor8", ExtraDPCaptions[8]); + ExtraDPCaptions[9] = ini.GetValue("ExtraDPCaptions", "Sensor9", ExtraDPCaptions[9]); + ExtraDPCaptions[10] = ini.GetValue("ExtraDPCaptions", "Sensor10", ExtraDPCaptions[10]); + + // soil temp captions (for Extra Sensor Data screen) + SoilTempCaptions[1] = ini.GetValue("SoilTempCaptions", "Sensor1", SoilTempCaptions[1]); + SoilTempCaptions[2] = ini.GetValue("SoilTempCaptions", "Sensor2", SoilTempCaptions[2]); + SoilTempCaptions[3] = ini.GetValue("SoilTempCaptions", "Sensor3", SoilTempCaptions[3]); + SoilTempCaptions[4] = ini.GetValue("SoilTempCaptions", "Sensor4", SoilTempCaptions[4]); + SoilTempCaptions[5] = ini.GetValue("SoilTempCaptions", "Sensor5", SoilTempCaptions[5]); + SoilTempCaptions[6] = ini.GetValue("SoilTempCaptions", "Sensor6", SoilTempCaptions[6]); + SoilTempCaptions[7] = ini.GetValue("SoilTempCaptions", "Sensor7", SoilTempCaptions[7]); + SoilTempCaptions[8] = ini.GetValue("SoilTempCaptions", "Sensor8", SoilTempCaptions[8]); + SoilTempCaptions[9] = ini.GetValue("SoilTempCaptions", "Sensor9", SoilTempCaptions[9]); + SoilTempCaptions[10] = ini.GetValue("SoilTempCaptions", "Sensor10", SoilTempCaptions[10]); + SoilTempCaptions[11] = ini.GetValue("SoilTempCaptions", "Sensor11", SoilTempCaptions[11]); + SoilTempCaptions[12] = ini.GetValue("SoilTempCaptions", "Sensor12", SoilTempCaptions[12]); + SoilTempCaptions[13] = ini.GetValue("SoilTempCaptions", "Sensor13", SoilTempCaptions[13]); + SoilTempCaptions[14] = ini.GetValue("SoilTempCaptions", "Sensor14", SoilTempCaptions[14]); + SoilTempCaptions[15] = ini.GetValue("SoilTempCaptions", "Sensor15", SoilTempCaptions[15]); + SoilTempCaptions[16] = ini.GetValue("SoilTempCaptions", "Sensor16", SoilTempCaptions[16]); + + // soil moisture captions (for Extra Sensor Data screen) + SoilMoistureCaptions[1] = ini.GetValue("SoilMoistureCaptions", "Sensor1", SoilMoistureCaptions[1]); + SoilMoistureCaptions[2] = ini.GetValue("SoilMoistureCaptions", "Sensor2", SoilMoistureCaptions[2]); + SoilMoistureCaptions[3] = ini.GetValue("SoilMoistureCaptions", "Sensor3", SoilMoistureCaptions[3]); + SoilMoistureCaptions[4] = ini.GetValue("SoilMoistureCaptions", "Sensor4", SoilMoistureCaptions[4]); + SoilMoistureCaptions[5] = ini.GetValue("SoilMoistureCaptions", "Sensor5", SoilMoistureCaptions[5]); + SoilMoistureCaptions[6] = ini.GetValue("SoilMoistureCaptions", "Sensor6", SoilMoistureCaptions[6]); + SoilMoistureCaptions[7] = ini.GetValue("SoilMoistureCaptions", "Sensor7", SoilMoistureCaptions[7]); + SoilMoistureCaptions[8] = ini.GetValue("SoilMoistureCaptions", "Sensor8", SoilMoistureCaptions[8]); + SoilMoistureCaptions[9] = ini.GetValue("SoilMoistureCaptions", "Sensor9", SoilMoistureCaptions[9]); + SoilMoistureCaptions[10] = ini.GetValue("SoilMoistureCaptions", "Sensor10", SoilMoistureCaptions[10]); + SoilMoistureCaptions[11] = ini.GetValue("SoilMoistureCaptions", "Sensor11", SoilMoistureCaptions[11]); + SoilMoistureCaptions[12] = ini.GetValue("SoilMoistureCaptions", "Sensor12", SoilMoistureCaptions[12]); + SoilMoistureCaptions[13] = ini.GetValue("SoilMoistureCaptions", "Sensor13", SoilMoistureCaptions[13]); + SoilMoistureCaptions[14] = ini.GetValue("SoilMoistureCaptions", "Sensor14", SoilMoistureCaptions[14]); + SoilMoistureCaptions[15] = ini.GetValue("SoilMoistureCaptions", "Sensor15", SoilMoistureCaptions[15]); + SoilMoistureCaptions[16] = ini.GetValue("SoilMoistureCaptions", "Sensor16", SoilMoistureCaptions[16]); + + // leaf temp captions (for Extra Sensor Data screen) + LeafTempCaptions[1] = ini.GetValue("LeafTempCaptions", "Sensor1", LeafTempCaptions[1]); + LeafTempCaptions[2] = ini.GetValue("LeafTempCaptions", "Sensor2", LeafTempCaptions[2]); + LeafTempCaptions[3] = ini.GetValue("LeafTempCaptions", "Sensor3", LeafTempCaptions[3]); + LeafTempCaptions[4] = ini.GetValue("LeafTempCaptions", "Sensor4", LeafTempCaptions[4]); + + // leaf wetness captions (for Extra Sensor Data screen) + LeafWetnessCaptions[1] = ini.GetValue("LeafWetnessCaptions", "Sensor1", LeafWetnessCaptions[1]); + LeafWetnessCaptions[2] = ini.GetValue("LeafWetnessCaptions", "Sensor2", LeafWetnessCaptions[2]); + LeafWetnessCaptions[3] = ini.GetValue("LeafWetnessCaptions", "Sensor3", LeafWetnessCaptions[3]); + LeafWetnessCaptions[4] = ini.GetValue("LeafWetnessCaptions", "Sensor4", LeafWetnessCaptions[4]); + LeafWetnessCaptions[5] = ini.GetValue("LeafWetnessCaptions", "Sensor5", LeafWetnessCaptions[5]); + LeafWetnessCaptions[6] = ini.GetValue("LeafWetnessCaptions", "Sensor6", LeafWetnessCaptions[6]); + LeafWetnessCaptions[7] = ini.GetValue("LeafWetnessCaptions", "Sensor7", LeafWetnessCaptions[7]); + LeafWetnessCaptions[8] = ini.GetValue("LeafWetnessCaptions", "Sensor8", LeafWetnessCaptions[8]); + + // air quality captions (for Extra Sensor Data screen) + AirQualityCaptions[1] = ini.GetValue("AirQualityCaptions", "Sensor1", AirQualityCaptions[1]); + AirQualityCaptions[2] = ini.GetValue("AirQualityCaptions", "Sensor2", AirQualityCaptions[2]); + AirQualityCaptions[3] = ini.GetValue("AirQualityCaptions", "Sensor3", AirQualityCaptions[3]); + AirQualityCaptions[4] = ini.GetValue("AirQualityCaptions", "Sensor4", AirQualityCaptions[4]); + AirQualityAvgCaptions[1] = ini.GetValue("AirQualityCaptions", "SensorAvg1", AirQualityAvgCaptions[1]); + AirQualityAvgCaptions[2] = ini.GetValue("AirQualityCaptions", "SensorAvg2", AirQualityAvgCaptions[2]); + AirQualityAvgCaptions[3] = ini.GetValue("AirQualityCaptions", "SensorAvg3", AirQualityAvgCaptions[3]); + AirQualityAvgCaptions[4] = ini.GetValue("AirQualityCaptions", "SensorAvg4", AirQualityAvgCaptions[4]); + + // CO2 captions - Ecowitt WH45 sensor + CO2_CurrentCaption = ini.GetValue("CO2Captions", "CO2-Current", CO2_CurrentCaption); + CO2_24HourCaption = ini.GetValue("CO2Captions", "CO2-24hr", CO2_24HourCaption); + CO2_pm2p5Caption = ini.GetValue("CO2Captions", "CO2-Pm2p5", CO2_pm2p5Caption); + CO2_pm2p5_24hrCaption = ini.GetValue("CO2Captions", "CO2-Pm2p5-24hr", CO2_pm2p5_24hrCaption); + CO2_pm10Caption = ini.GetValue("CO2Captions", "CO2-Pm10", CO2_pm10Caption); + CO2_pm10_24hrCaption = ini.GetValue("CO2Captions", "CO2-Pm10-24hr", CO2_pm10_24hrCaption); + + // User temperature captions (for Extra Sensor Data screen) + UserTempCaptions[1] = ini.GetValue("UserTempCaptions", "Sensor1", UserTempCaptions[1]); + UserTempCaptions[2] = ini.GetValue("UserTempCaptions", "Sensor2", UserTempCaptions[2]); + UserTempCaptions[3] = ini.GetValue("UserTempCaptions", "Sensor3", UserTempCaptions[3]); + UserTempCaptions[4] = ini.GetValue("UserTempCaptions", "Sensor4", UserTempCaptions[4]); + UserTempCaptions[5] = ini.GetValue("UserTempCaptions", "Sensor5", UserTempCaptions[5]); + UserTempCaptions[6] = ini.GetValue("UserTempCaptions", "Sensor6", UserTempCaptions[6]); + UserTempCaptions[7] = ini.GetValue("UserTempCaptions", "Sensor7", UserTempCaptions[7]); + UserTempCaptions[8] = ini.GetValue("UserTempCaptions", "Sensor8", UserTempCaptions[8]); + + thereWillBeMinSLessDaylightTomorrow = ini.GetValue("Solar", "LessDaylightTomorrow", thereWillBeMinSLessDaylightTomorrow); + thereWillBeMinSMoreDaylightTomorrow = ini.GetValue("Solar", "MoreDaylightTomorrow", thereWillBeMinSMoreDaylightTomorrow); + + DavisForecast1[0] = ini.GetValue("DavisForecast1", "forecast1", DavisForecast1[0]); + DavisForecast1[1] = ini.GetValue("DavisForecast1", "forecast2", DavisForecast1[1]) + " "; + DavisForecast1[2] = ini.GetValue("DavisForecast1", "forecast3", DavisForecast1[2]) + " "; + DavisForecast1[3] = ini.GetValue("DavisForecast1", "forecast4", DavisForecast1[3]) + " "; + DavisForecast1[4] = ini.GetValue("DavisForecast1", "forecast5", DavisForecast1[4]) + " "; + DavisForecast1[5] = ini.GetValue("DavisForecast1", "forecast6", DavisForecast1[5]) + " "; + DavisForecast1[6] = ini.GetValue("DavisForecast1", "forecast7", DavisForecast1[6]) + " "; + DavisForecast1[7] = ini.GetValue("DavisForecast1", "forecast8", DavisForecast1[7]) + " "; + DavisForecast1[8] = ini.GetValue("DavisForecast1", "forecast9", DavisForecast1[8]) + " "; + DavisForecast1[9] = ini.GetValue("DavisForecast1", "forecast10", DavisForecast1[9]) + " "; + DavisForecast1[10] = ini.GetValue("DavisForecast1", "forecast11", DavisForecast1[10]) + " "; + DavisForecast1[11] = ini.GetValue("DavisForecast1", "forecast12", DavisForecast1[11]) + " "; + DavisForecast1[12] = ini.GetValue("DavisForecast1", "forecast13", DavisForecast1[12]) + " "; + DavisForecast1[13] = ini.GetValue("DavisForecast1", "forecast14", DavisForecast1[13]) + " "; + DavisForecast1[14] = ini.GetValue("DavisForecast1", "forecast15", DavisForecast1[14]) + " "; + DavisForecast1[15] = ini.GetValue("DavisForecast1", "forecast16", DavisForecast1[15]) + " "; + DavisForecast1[16] = ini.GetValue("DavisForecast1", "forecast17", DavisForecast1[16]) + " "; + DavisForecast1[17] = ini.GetValue("DavisForecast1", "forecast18", DavisForecast1[17]) + " "; + DavisForecast1[18] = ini.GetValue("DavisForecast1", "forecast19", DavisForecast1[18]) + " "; + DavisForecast1[19] = ini.GetValue("DavisForecast1", "forecast20", DavisForecast1[19]) + " "; + DavisForecast1[20] = ini.GetValue("DavisForecast1", "forecast21", DavisForecast1[20]) + " "; + DavisForecast1[21] = ini.GetValue("DavisForecast1", "forecast22", DavisForecast1[21]) + " "; + DavisForecast1[22] = ini.GetValue("DavisForecast1", "forecast23", DavisForecast1[22]) + " "; + DavisForecast1[23] = ini.GetValue("DavisForecast1", "forecast24", DavisForecast1[23]) + " "; + DavisForecast1[24] = ini.GetValue("DavisForecast1", "forecast25", DavisForecast1[24]) + " "; + DavisForecast1[25] = ini.GetValue("DavisForecast1", "forecast26", DavisForecast1[25]) + " "; + DavisForecast1[26] = ini.GetValue("DavisForecast1", "forecast27", DavisForecast1[26]); + + DavisForecast2[0] = ini.GetValue("DavisForecast2", "forecast1", DavisForecast2[0]); + DavisForecast2[1] = ini.GetValue("DavisForecast2", "forecast2", DavisForecast2[1]) + " "; + DavisForecast2[2] = ini.GetValue("DavisForecast2", "forecast3", DavisForecast2[2]) + " "; + DavisForecast2[3] = ini.GetValue("DavisForecast2", "forecast4", DavisForecast2[3]) + " "; + DavisForecast2[4] = ini.GetValue("DavisForecast2", "forecast5", DavisForecast2[5]) + " "; + DavisForecast2[5] = ini.GetValue("DavisForecast2", "forecast6", DavisForecast2[5]) + " "; + DavisForecast2[6] = ini.GetValue("DavisForecast2", "forecast7", DavisForecast2[6]) + " "; + DavisForecast2[7] = ini.GetValue("DavisForecast2", "forecast8", DavisForecast2[7]) + " "; + DavisForecast2[8] = ini.GetValue("DavisForecast2", "forecast9", DavisForecast2[8]) + " "; + DavisForecast2[9] = ini.GetValue("DavisForecast2", "forecast10", DavisForecast2[9]) + " "; + DavisForecast2[10] = ini.GetValue("DavisForecast2", "forecast11", DavisForecast2[10]) + " "; + DavisForecast2[11] = ini.GetValue("DavisForecast2", "forecast12", DavisForecast2[11]) + " "; + DavisForecast2[12] = ini.GetValue("DavisForecast2", "forecast13", DavisForecast2[12]) + " "; + DavisForecast2[13] = ini.GetValue("DavisForecast2", "forecast14", DavisForecast2[13]) + " "; + DavisForecast2[14] = ini.GetValue("DavisForecast2", "forecast15", DavisForecast2[14]) + " "; + DavisForecast2[15] = ini.GetValue("DavisForecast2", "forecast16", DavisForecast2[15]) + " "; + DavisForecast2[16] = ini.GetValue("DavisForecast2", "forecast17", DavisForecast2[16]) + " "; + DavisForecast2[17] = ini.GetValue("DavisForecast2", "forecast18", DavisForecast2[17]) + " "; + DavisForecast2[18] = ini.GetValue("DavisForecast2", "forecast19", DavisForecast2[18]) + " "; + + DavisForecast3[0] = ini.GetValue("DavisForecast3", "forecast1", DavisForecast3[0]); + DavisForecast3[1] = ini.GetValue("DavisForecast3", "forecast2", DavisForecast3[1]); + DavisForecast3[2] = ini.GetValue("DavisForecast3", "forecast3", DavisForecast3[2]); + DavisForecast3[3] = ini.GetValue("DavisForecast3", "forecast4", DavisForecast3[3]); + DavisForecast3[4] = ini.GetValue("DavisForecast3", "forecast5", DavisForecast3[4]); + DavisForecast3[5] = ini.GetValue("DavisForecast3", "forecast6", DavisForecast3[5]); + DavisForecast3[6] = ini.GetValue("DavisForecast3", "forecast7", DavisForecast3[6]); + + // alarm emails + AlarmEmailSubject = ini.GetValue("AlarmEmails", "subject", "Cumulus MX Alarm"); + AlarmEmailPreamble = ini.GetValue("AlarmEmails", "preamble", "A Cumulus MX alarm has been triggered."); + HighGustAlarm.EmailMsg = ini.GetValue("AlarmEmails", "windGustAbove", "A wind gust above {0} {1} has occurred."); + HighPressAlarm.EmailMsg = ini.GetValue("AlarmEmails", "pressureAbove", "The pressure has risen above {0} {1}."); + HighTempAlarm.EmailMsg = ini.GetValue("AlarmEmails", "tempAbove", "The temperature has risen above {0} {1}."); + LowPressAlarm.EmailMsg = ini.GetValue("AlarmEmails", "pressBelow", "The pressure has fallen below {0} {1}."); + LowTempAlarm.EmailMsg = ini.GetValue("AlarmEmails", "tempBelow", "The temperature has fallen below {0} {1}."); + PressChangeAlarm.EmailMsgDn = ini.GetValue("AlarmEmails", "pressDown", "The pressure has decreased by more than {0} {1}."); + PressChangeAlarm.EmailMsgUp = ini.GetValue("AlarmEmails", "pressUp", "The pressure has increased by more than {0} {1}."); + HighRainTodayAlarm.EmailMsg = ini.GetValue("AlarmEmails", "rainAbove", "The rainfall today has exceeded {0} {1}."); + HighRainRateAlarm.EmailMsg = ini.GetValue("AlarmEmails", "rainRateAbove", "The rainfall rate has exceeded {0} {1}."); + SensorAlarm.EmailMsg = ini.GetValue("AlarmEmails", "sensorLost", "Contact has been lost with a remote sensor,"); + TempChangeAlarm.EmailMsgDn = ini.GetValue("AlarmEmails", "tempDown", "The temperature decreased by more than {0} {1}."); + TempChangeAlarm.EmailMsgUp = ini.GetValue("AlarmEmails", "tempUp", "The temperature has increased by more than {0} {1}."); + HighWindAlarm.EmailMsg = ini.GetValue("AlarmEmails", "windAbove", "The average wind speed has exceeded {0} {1}."); + DataStoppedAlarm.EmailMsg = ini.GetValue("AlarmEmails", "dataStopped", "Cumulus has stopped receiving data from your weather station."); + BatteryLowAlarm.EmailMsg = ini.GetValue("AlarmEmails", "batteryLow", "A low battery condition has been detected."); + SpikeAlarm.EmailMsg = ini.GetValue("AlarmEmails", "dataSpike", "A data spike from your weather station has been suppressed."); + UpgradeAlarm.EmailMsg = ini.GetValue("AlarmEmails", "upgrade", "An upgrade to Cumulus MX is now available."); + HttpUploadAlarm.EmailMsg = ini.GetValue("AlarmEmails", "httpStopped", "HTTP uploads are failing."); + MySqlUploadAlarm.EmailMsg = ini.GetValue("AlarmEmails", "mySqlStopped", "MySQL uploads are failing."); + } + + + public bool UseBlakeLarsen { get; set; } + + public double LuxToWM2 { get; set; } + + public int SolarMinimum { get; set; } + + public int SunThreshold { get; set; } + + public int SolarCalc { get; set; } + + public double BrasTurbidity { get; set; } + + //public double SolarFactorSummer { get; set; } + //public double SolarFactorWinter { get; set; } + + public int xapPort { get; set; } + + public string xapUID { get; set; } + + public bool xapEnabled { get; set; } + + public bool CloudBaseInFeet { get; set; } + + public string WebcamURL { get; set; } + + public string ForumURL { get; set; } + + public string DailyParams { get; set; } + + public string RealtimeParams { get; set; } + + public string ExternalParams { get; set; } + + public string DailyProgram { get; set; } + + public string RealtimeProgram { get; set; } + + public string ExternalProgram { get; set; } + + public TExtraFiles[] ExtraFiles = new TExtraFiles[numextrafiles]; + + //public int MaxFTPconnectRetries { get; set; } + + public bool DeleteBeforeUpload { get; set; } + + public bool FTPRename { get; set; } + + public int UpdateInterval { get; set; } + + public Timer RealtimeTimer = new Timer(); + + internal Timer CustomMysqlSecondsTimer; + + public bool ActiveFTPMode { get; set; } + + public FtpProtocols Sslftp { get; set; } + + public string SshftpAuthentication { get; set; } + + public string SshftpPskFile { get; set; } + + public bool DisableFtpsEPSV { get; set; } + + public bool DisableFtpsExplicit { get; set; } + + public bool FTPlogging { get; set; } + + public bool WebIntervalEnabled { get; set; } + + public bool WebAutoUpdate { get; set; } + + public string FtpDirectory { get; set; } + + public string FtpPassword { get; set; } + + public string FtpUsername { get; set; } + + public int FtpHostPort { get; set; } + + public string FtpHostname { get; set; } + + public int WMR200TempChannel { get; set; } + + public int WMR928TempChannel { get; set; } + + public int RTdisconnectcount { get; set; } + + //public int VP2SleepInterval { get; set; } + + //public int VPClosedownTime { get; set; } + public string AirLinkInIPAddr { get; set; } + public string AirLinkOutIPAddr { get; set; } + + public bool AirLinkInEnabled { get; set; } + public bool AirLinkOutEnabled { get; set; } + + //public bool solar_logging { get; set; } + + //public bool special_logging { get; set; } + + public bool RG11DTRmode2 { get; set; } + + public bool RG11IgnoreFirst2 { get; set; } + + public double RG11tipsize2 { get; set; } + + public bool RG11TBRmode2 { get; set; } + + public string RG11Port2 { get; set; } + + public bool RG11DTRmode { get; set; } + + public bool RG11IgnoreFirst { get; set; } + + public double RG11tipsize { get; set; } + + public bool RG11TBRmode { get; set; } + + public string RG11Port { get; set; } + + public bool RG11Enabled { get; set; } + public bool RG11Enabled2 { get; set; } + + public double ChillHourThreshold { get; set; } + + public int ChillHourSeasonStart { get; set; } + + public int RainSeasonStart { get; set; } + + public double FCPressureThreshold { get; set; } + + public double FChighpress { get; set; } + + public double FClowpress { get; set; } + + public bool FCpressinMB { get; set; } + + public double RainDayThreshold { get; set; } + + public int SnowDepthHour { get; set; } + + public bool UseWindChillCutoff { get; set; } + + public bool HourlyForecast { get; set; } + + public bool UseCumulusForecast { get; set; } + + public bool UseDataLogger { get; set; } + + public bool DavisConsoleHighGust { get; set; } + + public bool DavisCalcAltPress { get; set; } + + public bool DavisUseDLLBarCalData { get; set; } + + public int LCMaxWind { get; set; } + + //public bool EWduplicatecheck { get; set; } + + public string RecordsBeganDate { get; set; } + + //public bool EWdisablecheckinit { get; set; } + + //public bool EWallowFF { get; set; } + + public int YTDrainyear { get; set; } + + public double YTDrain { get; set; } + + public string LocationDesc { get; set; } + + public string LocationName { get; set; } + + public string HTTPProxyPassword { get; set; } + + public string HTTPProxyUser { get; set; } + + public int HTTPProxyPort { get; set; } + + public string HTTPProxyName { get; set; } + + public int[] WindDPlaceDefaults = { 1, 0, 0, 0 }; // m/s, mph, km/h, knots + public int[] TempDPlaceDefaults = { 1, 1 }; + public int[] PressDPlaceDefaults = { 1, 1, 2 }; + public int[] RainDPlaceDefaults = { 1, 2 }; + public const int numextrafiles = 99; + public const int numOfSelectaChartSeries = 6; + + //public bool WS2300Sync { get; set; } + + public bool ErrorLogSpikeRemoval { get; set; } + + //public bool NoFlashWetDryDayRecords { get; set; } + + public bool ReportLostSensorContact { get; set; } + + public bool ReportDataStoppedErrors { get; set; } + + //public bool RestartIfDataStops { get; set; } + + //public bool RestartIfUnplugged { get; set; } + + //public bool CloseOnSuspend { get; set; } + + //public bool ConfirmClose { get; set; } + + public int DataLogInterval { get; set; } + + public int UVdecimals { get; set; } + + public int UVdecimaldefault { get; set; } + + public string LonTxt { get; set; } + + public string LatTxt { get; set; } + + public bool AltitudeInFeet { get; set; } + + public string StationModel { get; set; } + + public int StationType { get; set; } + + public string LatestImetReading { get; set; } + + public bool FineOffsetStation { get; set; } + + public bool DavisStation { get; set; } + public string TempTrendFormat { get; set; } + public string AppDir { get; set; } + + public int Manufacturer { get; set; } + public int ImetLoggerInterval { get; set; } + public TimeSpan DayLength { get; set; } + public DateTime Dawn; + public DateTime Dusk; + public TimeSpan DaylightLength { get; set; } + public int GraphHours { get; set; } + + // WeatherLink Live transmitter Ids and indexes + public string WllApiKey; + public string WllApiSecret; + public int WllStationId; + public int WllParentId; + + public int WllBroadcastDuration = 300; + public int WllBroadcastPort = 22222; + public bool WLLAutoUpdateIpAddress = true; + public int WllPrimaryWind = 1; + public int WllPrimaryTempHum = 1; + public int WllPrimaryRain = 1; + public int WllPrimarySolar; + public int WllPrimaryUV; + + public int WllExtraSoilTempTx1; + public int WllExtraSoilTempIdx1 = 1; + public int WllExtraSoilTempTx2; + public int WllExtraSoilTempIdx2 = 2; + public int WllExtraSoilTempTx3; + public int WllExtraSoilTempIdx3 = 3; + public int WllExtraSoilTempTx4; + public int WllExtraSoilTempIdx4 = 4; + + public int WllExtraSoilMoistureTx1; + public int WllExtraSoilMoistureIdx1 = 1; + public int WllExtraSoilMoistureTx2; + public int WllExtraSoilMoistureIdx2 = 2; + public int WllExtraSoilMoistureTx3; + public int WllExtraSoilMoistureIdx3 = 3; + public int WllExtraSoilMoistureTx4; + public int WllExtraSoilMoistureIdx4 = 4; + + public int WllExtraLeafTx1; + public int WllExtraLeafIdx1 = 1; + public int WllExtraLeafTx2; + public int WllExtraLeafIdx2 = 2; + + public int[] WllExtraTempTx = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + public bool[] WllExtraHumTx = { false, false, false, false, false, false, false, false }; + + // WeatherLink Live transmitter Ids and indexes + public bool AirLinkIsNode; + public string AirLinkApiKey; + public string AirLinkApiSecret; + public int AirLinkInStationId; + public int AirLinkOutStationId; + public bool AirLinkAutoUpdateIpAddress = true; + + public int airQualityIndex = -1; + + public string Gw1000IpAddress; + public string Gw1000MacAddress; + public bool Gw1000AutoUpdateIpAddress = true; + + public Timer WundTimer = new Timer(); + public Timer WebTimer = new Timer(); + public Timer AwekasTimer = new Timer(); + public Timer MQTTTimer = new Timer(); + //public Timer AirLinkTimer = new Timer(); + + public int DAVIS = 0; + public int OREGON = 1; + public int EW = 2; + public int LACROSSE = 3; + public int OREGONUSB = 4; + public int INSTROMET = 5; + public int ECOWITT = 6; + //public bool startingup = true; + public string ReportPath; + public string LatestError; + public DateTime LatestErrorTS = DateTime.MinValue; + //public DateTime defaultRecordTS = new DateTime(2000, 1, 1, 0, 0, 0); + public DateTime defaultRecordTS = DateTime.MinValue; + public string WxnowFile = "wxnow.txt"; + private readonly string RealtimeFile = "realtime.txt"; + private readonly string TwitterTxtFile; + public bool IncludeMoonImage; + private readonly FtpClient RealtimeFTP = new FtpClient(); + private SftpClient RealtimeSSH; + private volatile bool RealtimeFtpInProgress; + private volatile bool RealtimeCopyInProgress; + private byte RealtimeCycleCounter; + + public FileGenerationFtpOptions[] StdWebFiles; + public FileGenerationFtpOptions[] RealtimeFiles; + public FileGenerationFtpOptions[] GraphDataFiles; + public FileGenerationFtpOptions[] GraphDataEodFiles; + + + public string exceptional = "Exceptional Weather"; +// private WebSocketServer wsServer; + public string[] WMR200ExtraChannelCaptions = new string[11]; + public string[] ExtraTempCaptions = { "", "Sensor 1", "Sensor 2", "Sensor 3", "Sensor 4", "Sensor 5", "Sensor 6", "Sensor 7", "Sensor 8", "Sensor 9", "Sensor 10" }; + public string[] ExtraHumCaptions = { "", "Sensor 1", "Sensor 2", "Sensor 3", "Sensor 4", "Sensor 5", "Sensor 6", "Sensor 7", "Sensor 8", "Sensor 9", "Sensor 10" }; + public string[] ExtraDPCaptions = { "", "Sensor 1", "Sensor 2", "Sensor 3", "Sensor 4", "Sensor 5", "Sensor 6", "Sensor 7", "Sensor 8", "Sensor 9", "Sensor 10" }; + public string[] SoilTempCaptions = { "", "Sensor 1", "Sensor 2", "Sensor 3", "Sensor 4", "Sensor 5", "Sensor 6", "Sensor 7", "Sensor 8", "Sensor 9", "Sensor 10", "Sensor 11", "Sensor 12", "Sensor 13", "Sensor 14", "Sensor 15", "Sensor 16" }; + public string[] SoilMoistureCaptions = { "", "Sensor 1", "Sensor 2", "Sensor 3", "Sensor 4", "Sensor 5", "Sensor 6", "Sensor 7", "Sensor 8", "Sensor 9", "Sensor 10", "Sensor 11", "Sensor 12", "Sensor 13", "Sensor 14", "Sensor 15", "Sensor 16" }; + public string[] AirQualityCaptions = { "", "Sensor 1", "Sensor 2", "Sensor 3", "Sensor 4" }; + public string[] AirQualityAvgCaptions = { "", "Sensor Avg 1", "Sensor Avg 2", "Sensor Avg 3", "Sensor Avg 4" }; + public string[] LeafTempCaptions = { "", "Temp 1", "Temp 2", "Temp 3", "Temp 4" }; + public string[] LeafWetnessCaptions = { "", "Wetness 1", "Wetness 2", "Wetness 3", "Wetness 4", "Wetness 5", "Wetness 6", "Wetness 7", "Wetness 8" }; + public string[] UserTempCaptions = { "", "Sensor 1", "Sensor 2", "Sensor 3", "Sensor 4", "Sensor 5", "Sensor 6", "Sensor 7", "Sensor 8" }; + private string thereWillBeMinSLessDaylightTomorrow = "There will be {0}min {1}s less daylight tomorrow"; + private string thereWillBeMinSMoreDaylightTomorrow = "There will be {0}min {1}s more daylight tomorrow"; + // WH45 CO2 sensor captions + public string CO2_CurrentCaption = "CO₂ Current"; + public string CO2_24HourCaption = "CO₂ 24h avg"; + public string CO2_pm2p5Caption = "PM 2.5"; + public string CO2_pm2p5_24hrCaption = "PM 2.5 24h avg"; + public string CO2_pm10Caption = "PM 10"; + public string CO2_pm10_24hrCaption = "PM 10 24h avg"; + + /* + public string Getversion() + { + return Version; + } + + public void SetComport(string comport) + { + ComportName = comport; + } + + public string GetComport() + { + return ComportName; + } + + public void SetStationType(int type) + { + StationType = type; + } + + public int GetStationType() + { + return StationType; + } + + public void SetVPRainGaugeType(int type) + { + VPrainGaugeType = type; + } + + public int GetVPRainGaugeType() + { + return VPrainGaugeType; + } + + public void SetVPConnectionType(VPConnTypes type) + { + VPconntype = type; + } + + public VPConnTypes GetVPConnectionType() + { + return VPconntype; + } + + public void SetIPaddress(string address) + { + IPaddress = address; + } + + public string GetIPaddress() + { + return IPaddress; + } + + public void SetTCPport(int port) + { + TCPport = port; + } + + public int GetTCPport() + { + return TCPport; + } + */ + + public string GetLogFileName(DateTime thedate) + { + // First determine the date for the logfile. + // If we're using 9am rollover, the date should be 9 hours (10 in summer) + // before 'Now' + DateTime logfiledate; + + if (RolloverHour == 0) + { + logfiledate = thedate; + } + else + { + TimeZone tz = TimeZone.CurrentTimeZone; + + if (Use10amInSummer && tz.IsDaylightSavingTime(thedate)) + { + // Locale is currently on Daylight (summer) time + logfiledate = thedate.AddHours(-10); + } + else + { + // Locale is currently on Standard time or unknown + logfiledate = thedate.AddHours(-9); + } + } + + var datestring = logfiledate.ToString("MMMyy").Replace(".", ""); + + return Datapath + datestring + "log.txt"; + } + + public string GetExtraLogFileName(DateTime thedate) + { + // First determine the date for the logfile. + // If we're using 9am rollover, the date should be 9 hours (10 in summer) + // before 'Now' + DateTime logfiledate; + + if (RolloverHour == 0) + { + logfiledate = thedate; + } + else + { + TimeZone tz = TimeZone.CurrentTimeZone; + + if (Use10amInSummer && tz.IsDaylightSavingTime(thedate)) + { + // Locale is currently on Daylight (summer) time + logfiledate = thedate.AddHours(-10); + } + else + { + // Locale is currently on Standard time or unknown + logfiledate = thedate.AddHours(-9); + } + } + + var datestring = logfiledate.ToString("yyyyMM"); + datestring = datestring.Replace(".", ""); + + return Datapath + "ExtraLog" + datestring + ".txt"; + } + + public string GetAirLinkLogFileName(DateTime thedate) + { + // First determine the date for the logfile. + // If we're using 9am rollover, the date should be 9 hours (10 in summer) + // before 'Now' + DateTime logfiledate; + + if (RolloverHour == 0) + { + logfiledate = thedate; + } + else + { + TimeZone tz = TimeZone.CurrentTimeZone; + + if (Use10amInSummer && tz.IsDaylightSavingTime(thedate)) + { + // Locale is currently on Daylight (summer) time + logfiledate = thedate.AddHours(-10); + } + else + { + // Locale is currently on Standard time or unknown + logfiledate = thedate.AddHours(-9); + } + } + + var datestring = logfiledate.ToString("yyyyMM"); + datestring = datestring.Replace(".", ""); + + return Datapath + "AirLink" + datestring + "log.txt"; + } + + public const int NumLogFileFields = 29; + + public async void DoLogFile(DateTime timestamp, bool live) + { + // Writes an entry to the n-minute logfile. Fields are comma-separated: + // 0 Date in the form dd/mm/yy (the slash may be replaced by a dash in some cases) + // 1 Current time - hh:mm + // 2 Current temperature + // 3 Current humidity + // 4 Current dewpoint + // 5 Current wind speed + // 6 Recent (10-minute) high gust + // 7 Average wind bearing + // 8 Current rainfall rate + // 9 Total rainfall today so far + // 10 Current sea level pressure + // 11 Total rainfall counter as held by the station + // 12 Inside temperature + // 13 Inside humidity + // 14 Current gust (i.e. 'Latest') + // 15 Wind chill + // 16 Heat Index + // 17 UV Index + // 18 Solar Radiation + // 19 Evapotranspiration + // 20 Annual Evapotranspiration + // 21 Apparent temperature + // 22 Current theoretical max solar radiation + // 23 Hours of sunshine so far today + // 24 Current wind bearing + // 25 RG-11 rain total + // 26 Rain since midnight + // 27 Feels like + // 28 Humidex + + // make sure solar max is calculated for those stations without a solar sensor + LogMessage("DoLogFile: Writing log entry for " + timestamp); + LogDebugMessage("DoLogFile: max gust: " + station.RecentMaxGust.ToString(WindFormat)); + station.CurrentSolarMax = AstroLib.SolarMax(timestamp, Longitude, Latitude, station.AltitudeM(Altitude), out station.SolarElevation, RStransfactor, BrasTurbidity, SolarCalc); + var filename = GetLogFileName(timestamp); + + using (FileStream fs = new FileStream(filename, FileMode.Append, FileAccess.Write, FileShare.Read)) + using (StreamWriter file = new StreamWriter(fs)) + { + file.Write(timestamp.ToString("dd/MM/yy") + ListSeparator); + file.Write(timestamp.ToString("HH:mm") + ListSeparator); + file.Write(station.OutdoorTemperature.ToString(TempFormat) + ListSeparator); + file.Write(station.OutdoorHumidity + ListSeparator); + file.Write(station.OutdoorDewpoint.ToString(TempFormat) + ListSeparator); + file.Write(station.WindAverage.ToString(WindAvgFormat) + ListSeparator); + file.Write(station.RecentMaxGust.ToString(WindFormat) + ListSeparator); + file.Write(station.AvgBearing + ListSeparator); + file.Write(station.RainRate.ToString(RainFormat) + ListSeparator); + file.Write(station.RainToday.ToString(RainFormat) + ListSeparator); + file.Write(station.Pressure.ToString(PressFormat) + ListSeparator); + file.Write(station.Raincounter.ToString(RainFormat) + ListSeparator); + file.Write(station.IndoorTemperature.ToString(TempFormat) + ListSeparator); + file.Write(station.IndoorHumidity + ListSeparator); + file.Write(station.WindLatest.ToString(WindFormat) + ListSeparator); + file.Write(station.WindChill.ToString(TempFormat) + ListSeparator); + file.Write(station.HeatIndex.ToString(TempFormat) + ListSeparator); + file.Write(station.UV.ToString(UVFormat) + ListSeparator); + file.Write(station.SolarRad + ListSeparator); + file.Write(station.ET.ToString(ETFormat) + ListSeparator); + file.Write(station.AnnualETTotal.ToString(ETFormat) + ListSeparator); + file.Write(station.ApparentTemperature.ToString(TempFormat) + ListSeparator); + file.Write((Math.Round(station.CurrentSolarMax)) + ListSeparator); + file.Write(station.SunshineHours.ToString(SunFormat) + ListSeparator); + file.Write(station.Bearing + ListSeparator); + file.Write(station.RG11RainToday.ToString(RainFormat) + ListSeparator); + file.Write(station.RainSinceMidnight.ToString(RainFormat) + ListSeparator); + file.Write(station.FeelsLike.ToString(TempFormat) + ListSeparator); + file.WriteLine(station.Humidex.ToString(TempFormat)); + file.Close(); + } + + LastUpdateTime = timestamp; + LogMessage("DoLogFile: Written log entry for " + timestamp); + station.WriteTodayFile(timestamp, true); + + if (MonthlyMySqlEnabled) + { + var InvC = new CultureInfo(""); + + StringBuilder values = new StringBuilder(StartOfMonthlyInsertSQL, 600); + values.Append(" Values('"); + values.Append(timestamp.ToString("yy-MM-dd HH:mm") + "',"); + values.Append(station.OutdoorTemperature.ToString(TempFormat, InvC) + ","); + values.Append(station.OutdoorHumidity + ","); + values.Append(station.OutdoorDewpoint.ToString(TempFormat, InvC) + ","); + values.Append(station.WindAverage.ToString(WindAvgFormat, InvC) + ","); + values.Append(station.RecentMaxGust.ToString(WindFormat, InvC) + ","); + values.Append(station.AvgBearing + ","); + values.Append(station.RainRate.ToString(RainFormat, InvC) + ","); + values.Append(station.RainToday.ToString(RainFormat, InvC) + ","); + values.Append(station.Pressure.ToString(PressFormat, InvC) + ","); + values.Append(station.Raincounter.ToString(RainFormat, InvC) + ","); + values.Append(station.IndoorTemperature.ToString(TempFormat, InvC) + ","); + values.Append(station.IndoorHumidity + ","); + values.Append(station.WindLatest.ToString(WindFormat, InvC) + ","); + values.Append(station.WindChill.ToString(TempFormat, InvC) + ","); + values.Append(station.HeatIndex.ToString(TempFormat, InvC) + ","); + values.Append(station.UV.ToString(UVFormat, InvC) + ","); + values.Append(station.SolarRad + ","); + values.Append(station.ET.ToString(ETFormat, InvC) + ","); + values.Append(station.AnnualETTotal.ToString(ETFormat, InvC) + ","); + values.Append(station.ApparentTemperature.ToString(TempFormat, InvC) + ","); + values.Append((Math.Round(station.CurrentSolarMax)) + ","); + values.Append(station.SunshineHours.ToString(SunFormat, InvC) + ","); + values.Append(station.Bearing + ","); + values.Append(station.RG11RainToday.ToString(RainFormat, InvC) + ","); + values.Append(station.RainSinceMidnight.ToString(RainFormat, InvC) + ",'"); + values.Append(station.CompassPoint(station.AvgBearing) + "','"); + values.Append(station.CompassPoint(station.Bearing) + "',"); + values.Append(station.FeelsLike.ToString(TempFormat, InvC) + ","); + values.Append(station.Humidex.ToString(TempFormat, InvC)); + values.Append(")"); + + string queryString = values.ToString(); + + if (live) + { + // do the update + await MySqlCommandAsync(queryString, MonthlyMySqlConn, "DoLogFile", true, true); + } + else + { + // save the string for later + MySqlList.Add(queryString); + } + } + } + + public const int NumExtraLogFileFields = 92; + + public void DoExtraLogFile(DateTime timestamp) + { + // Writes an entry to the n-minute extralogfile. Fields are comma-separated: + // 0 Date in the form dd/mm/yy (the slash may be replaced by a dash in some cases) + // 1 Current time - hh:mm + // 2-11 Temperature 1-10 + // 12-21 Humidity 1-10 + // 22-31 Dew point 1-10 + // 32-35 Soil temp 1-4 + // 36-39 Soil moisture 1-4 + // 40-41 Leaf temp 1-2 + // 42-43 Leaf wetness 1-2 + // 44-55 Soil temp 5-16 + // 56-67 Soil moisture 5-16 + // 68-71 Air quality 1-4 + // 72-75 Air quality avg 1-4 + // 76-83 User temperature 1-8 + // 84 CO2 + // 85 CO2 avg + // 86 CO2 pm2.5 + // 87 CO2 pm2.5 avg + // 88 CO2 pm10 + // 89 CO2 pm10 avg + // 90 CO2 temp + // 91 CO2 hum + + var filename = GetExtraLogFileName(timestamp); + + using (FileStream fs = new FileStream(filename, FileMode.Append, FileAccess.Write, FileShare.Read)) + using (StreamWriter file = new StreamWriter(fs)) + { + file.Write(timestamp.ToString("dd/MM/yy") + ListSeparator); //0 + file.Write(timestamp.ToString("HH:mm") + ListSeparator); //1 + + for (int i = 1; i < 11; i++) + { + file.Write(station.ExtraTemp[i].ToString(TempFormat) + ListSeparator); //2-11 + } + for (int i = 1; i < 11; i++) + { + file.Write(station.ExtraHum[i].ToString(HumFormat) + ListSeparator); //12-21 + } + for (int i = 1; i < 11; i++) + { + file.Write(station.ExtraDewPoint[i].ToString(TempFormat) + ListSeparator); //22-31 + } + + file.Write(station.SoilTemp1.ToString(TempFormat) + ListSeparator); //32 + file.Write(station.SoilTemp2.ToString(TempFormat) + ListSeparator); //33 + file.Write(station.SoilTemp3.ToString(TempFormat) + ListSeparator); //34 + file.Write(station.SoilTemp4.ToString(TempFormat) + ListSeparator); //35 + + file.Write(station.SoilMoisture1 + ListSeparator); //36 + file.Write(station.SoilMoisture2 + ListSeparator); //37 + file.Write(station.SoilMoisture3 + ListSeparator); //38 + file.Write(station.SoilMoisture4 + ListSeparator); //39 + + file.Write(station.LeafTemp1.ToString(TempFormat) + ListSeparator); //40 + file.Write(station.LeafTemp2.ToString(TempFormat) + ListSeparator); //41 + + file.Write(station.LeafWetness1 + ListSeparator); //42 + file.Write(station.LeafWetness2 + ListSeparator); //43 + + file.Write(station.SoilTemp5.ToString(TempFormat) + ListSeparator); //44 + file.Write(station.SoilTemp6.ToString(TempFormat) + ListSeparator); //45 + file.Write(station.SoilTemp7.ToString(TempFormat) + ListSeparator); //46 + file.Write(station.SoilTemp8.ToString(TempFormat) + ListSeparator); //47 + file.Write(station.SoilTemp9.ToString(TempFormat) + ListSeparator); //48 + file.Write(station.SoilTemp10.ToString(TempFormat) + ListSeparator); //49 + file.Write(station.SoilTemp11.ToString(TempFormat) + ListSeparator); //50 + file.Write(station.SoilTemp12.ToString(TempFormat) + ListSeparator); //51 + file.Write(station.SoilTemp13.ToString(TempFormat) + ListSeparator); //52 + file.Write(station.SoilTemp14.ToString(TempFormat) + ListSeparator); //53 + file.Write(station.SoilTemp15.ToString(TempFormat) + ListSeparator); //54 + file.Write(station.SoilTemp16.ToString(TempFormat) + ListSeparator); //55 + + file.Write(station.SoilMoisture5 + ListSeparator); //56 + file.Write(station.SoilMoisture6 + ListSeparator); //57 + file.Write(station.SoilMoisture7 + ListSeparator); //58 + file.Write(station.SoilMoisture8 + ListSeparator); //59 + file.Write(station.SoilMoisture9 + ListSeparator); //60 + file.Write(station.SoilMoisture10 + ListSeparator); //61 + file.Write(station.SoilMoisture11 + ListSeparator); //62 + file.Write(station.SoilMoisture12 + ListSeparator); //63 + file.Write(station.SoilMoisture13 + ListSeparator); //64 + file.Write(station.SoilMoisture14 + ListSeparator); //65 + file.Write(station.SoilMoisture15 + ListSeparator); //66 + file.Write(station.SoilMoisture16 + ListSeparator); //67 + + file.Write(station.AirQuality1.ToString("F1") + ListSeparator); //68 + file.Write(station.AirQuality2.ToString("F1") + ListSeparator); //69 + file.Write(station.AirQuality3.ToString("F1") + ListSeparator); //70 + file.Write(station.AirQuality4.ToString("F1") + ListSeparator); //71 + file.Write(station.AirQualityAvg1.ToString("F1") + ListSeparator); //72 + file.Write(station.AirQualityAvg2.ToString("F1") + ListSeparator); //73 + file.Write(station.AirQualityAvg3.ToString("F1") + ListSeparator); //74 + file.Write(station.AirQualityAvg4.ToString("F1") + ListSeparator); //75 + + for (int i = 1; i < 8; i++) + { + file.Write(station.UserTemp[i].ToString(TempFormat) + ListSeparator); //76-82 + } + file.Write(station.UserTemp[8].ToString(TempFormat) + ListSeparator); //83 + + file.Write(station.CO2 + ListSeparator); //84 + file.Write(station.CO2_24h + ListSeparator); //85 + file.Write(station.CO2_pm2p5.ToString("F1") + ListSeparator); //86 + file.Write(station.CO2_pm2p5_24h.ToString("F1") + ListSeparator); //87 + file.Write(station.CO2_pm10.ToString("F1") + ListSeparator); //88 + file.Write(station.CO2_pm10_24h.ToString("F1") + ListSeparator); //89 + file.Write(station.CO2_temperature.ToString(TempFormat) + ListSeparator); //90 + file.Write(station.CO2_humidity); //91 + + file.WriteLine(); + file.Close(); + } + } + + public void DoAirLinkLogFile(DateTime timestamp) + { + // Writes an entry to the n-minute airlinklogfile. Fields are comma-separated: + // 0 Date in the form dd/mm/yy (the slash may be replaced by a dash in some cases) + // 1 Current time - hh:mm + // 2 Indoor Temperature + // 3 Indoor Humidity + // 4 Indoor PM 1 + // 5 Indoor PM 2.5 + // 6 Indoor PM 2.5 1-hour + // 7 Indoor PM 2.5 3-hour + // 8 Indoor PM 2.5 24-hour + // 9 Indoor PM 2.5 nowcast + // 10 Indoor PM 10 + // 11 Indoor PM 10 1-hour + // 12 Indoor PM 10 3-hour + // 13 Indoor PM 10 24-hour + // 14 Indoor PM 10 nowcast + // 15 Indoor Percent received 1-hour + // 16 Indoor Percent received 3-hour + // 17 Indoor Percent received nowcast + // 18 Indoor Percent received 24-hour + // 19 Indoor AQI PM2.5 + // 20 Indoor AQI PM2.5 1-hour + // 21 Indoor AQI PM2.5 3-hour + // 22 Indoor AQI PM2.5 24-hour + // 23 Indoor AQI PM2.5 nowcast + // 24 Indoor AQI PM10 + // 25 Indoor AQI PM10 1-hour + // 26 Indoor AQI PM10 3-hour + // 27 Indoor AQI PM10 24-hour + // 28 Indoor AQI PM10 nowcast + // 29 Outdoor Temperature + // 30 Outdoor Humidity + // 31 Outdoor PM 1 + // 32 Outdoor PM 2.5 + // 33 Outdoor PM 2.5 1-hour + // 34 Outdoor PM 2.5 3-hour + // 35 Outdoor PM 2.5 24-hour + // 36 Outdoor PM 2.5 nowcast + // 37 Outdoor PM 10 + // 38 Outdoor PM 10 1-hour + // 39 Outdoor PM 10 3-hour + // 40 Outdoor PM 10 24-hour + // 41 Outdoor PM 10 nowcast + // 42 Outdoor Percent received 1-hour + // 43 Outdoor Percent received 3-hour + // 44 Outdoor Percent received nowcast + // 45 Outdoor Percent received 24-hour + // 46 Outdoor AQI PM2.5 + // 47 Outdoor AQI PM2.5 1-hour + // 48 Outdoor AQI PM2.5 3-hour + // 49 Outdoor AQI PM2.5 24-hour + // 50 Outdoor AQI PM2.5 nowcast + // 51 Outdoor AQI PM10 + // 52 Outdoor AQI PM10 1-hour + // 53 Outdoor AQI PM10 3-hour + // 54 Outdoor AQI PM10 24-hour + // 55 Outdoor AQI PM10 nowcast + + var filename = GetAirLinkLogFileName(timestamp); + + using (FileStream fs = new FileStream(filename, FileMode.Append, FileAccess.Write, FileShare.Read)) + using (StreamWriter file = new StreamWriter(fs)) + { + file.Write(timestamp.ToString("dd/MM/yy") + ListSeparator); + file.Write(timestamp.ToString("HH:mm") + ListSeparator); + + if (AirLinkInEnabled && airLinkDataIn != null) + { + file.Write(airLinkDataIn.temperature.ToString("F1") + ListSeparator); + file.Write(airLinkDataIn.humidity + ListSeparator); + file.Write(airLinkDataIn.pm1.ToString("F1") + ListSeparator); + file.Write(airLinkDataIn.pm2p5.ToString("F1") + ListSeparator); + file.Write(airLinkDataIn.pm2p5_1hr.ToString("F1") + ListSeparator); + file.Write(airLinkDataIn.pm2p5_3hr.ToString("F1") + ListSeparator); + file.Write(airLinkDataIn.pm2p5_24hr.ToString("F1") + ListSeparator); + file.Write(airLinkDataIn.pm2p5_nowcast.ToString("F1") + ListSeparator); + file.Write(airLinkDataIn.pm10.ToString("F1") + ListSeparator); + file.Write(airLinkDataIn.pm10_1hr.ToString("F1") + ListSeparator); + file.Write(airLinkDataIn.pm10_3hr.ToString("F1") + ListSeparator); + file.Write(airLinkDataIn.pm10_24hr.ToString("F1") + ListSeparator); + file.Write(airLinkDataIn.pm10_nowcast.ToString("F1") + ListSeparator); + file.Write(airLinkDataIn.pct_1hr + ListSeparator); + file.Write(airLinkDataIn.pct_3hr + ListSeparator); + file.Write(airLinkDataIn.pct_24hr + ListSeparator); + file.Write(airLinkDataIn.pct_nowcast + ListSeparator); + if (AirQualityDPlaces > 0) + { + file.Write(airLinkDataIn.aqiPm2p5.ToString(AirQualityFormat) + ListSeparator); + file.Write(airLinkDataIn.aqiPm2p5_1hr.ToString(AirQualityFormat) + ListSeparator); + file.Write(airLinkDataIn.aqiPm2p5_3hr.ToString(AirQualityFormat) + ListSeparator); + file.Write(airLinkDataIn.aqiPm2p5_24hr.ToString(AirQualityFormat) + ListSeparator); + file.Write(airLinkDataIn.aqiPm2p5_nowcast.ToString(AirQualityFormat) + ListSeparator); + file.Write(airLinkDataIn.aqiPm10.ToString(AirQualityFormat) + ListSeparator); + file.Write(airLinkDataIn.aqiPm10_1hr.ToString(AirQualityFormat) + ListSeparator); + file.Write(airLinkDataIn.aqiPm10_3hr.ToString(AirQualityFormat) + ListSeparator); + file.Write(airLinkDataIn.aqiPm10_24hr.ToString(AirQualityFormat) + ListSeparator); + file.Write(airLinkDataIn.aqiPm10_nowcast.ToString(AirQualityFormat) + ListSeparator); + } + else // Zero decimals - trucate value rather than round + { + file.Write((int)airLinkDataIn.aqiPm2p5 + ListSeparator); + file.Write((int)airLinkDataIn.aqiPm2p5_1hr + ListSeparator); + file.Write((int)airLinkDataIn.aqiPm2p5_3hr + ListSeparator); + file.Write((int)airLinkDataIn.aqiPm2p5_24hr + ListSeparator); + file.Write((int)airLinkDataIn.aqiPm2p5_nowcast + ListSeparator); + file.Write((int)airLinkDataIn.aqiPm10 + ListSeparator); + file.Write((int)airLinkDataIn.aqiPm10_1hr + ListSeparator); + file.Write((int)airLinkDataIn.aqiPm10_3hr + ListSeparator); + file.Write((int)airLinkDataIn.aqiPm10_24hr + ListSeparator); + file.Write((int)airLinkDataIn.aqiPm10_nowcast + ListSeparator); + } + } + else + { + // write zero values - subtract 2 for firmware version, wifi RSSI + for (var i = 0; i < typeof(AirLinkData).GetProperties().Length - 2; i++) + { + file.Write("0" + ListSeparator); + } + } + + if (AirLinkOutEnabled && airLinkDataOut != null) + { + file.Write(airLinkDataOut.temperature.ToString("F1") + ListSeparator); + file.Write(airLinkDataOut.humidity + ListSeparator); + file.Write(airLinkDataOut.pm1.ToString("F1") + ListSeparator); + file.Write(airLinkDataOut.pm2p5.ToString("F1") + ListSeparator); + file.Write(airLinkDataOut.pm2p5_1hr.ToString("F1") + ListSeparator); + file.Write(airLinkDataOut.pm2p5_3hr.ToString("F1") + ListSeparator); + file.Write(airLinkDataOut.pm2p5_24hr.ToString("F1") + ListSeparator); + file.Write(airLinkDataOut.pm2p5_nowcast.ToString("F1") + ListSeparator); + file.Write(airLinkDataOut.pm10.ToString("F1") + ListSeparator); + file.Write(airLinkDataOut.pm10_1hr.ToString("F1") + ListSeparator); + file.Write(airLinkDataOut.pm10_3hr.ToString("F1") + ListSeparator); + file.Write(airLinkDataOut.pm10_24hr.ToString("F1") + ListSeparator); + file.Write(airLinkDataOut.pm10_nowcast.ToString("F1") + ListSeparator); + file.Write(airLinkDataOut.pct_1hr + ListSeparator); + file.Write(airLinkDataOut.pct_3hr + ListSeparator); + file.Write(airLinkDataOut.pct_24hr + ListSeparator); + file.Write(airLinkDataOut.pct_nowcast + ListSeparator); + file.Write(airLinkDataOut.aqiPm2p5.ToString("F1") + ListSeparator); + file.Write(airLinkDataOut.aqiPm2p5_1hr.ToString("F1") + ListSeparator); + file.Write(airLinkDataOut.aqiPm2p5_3hr.ToString("F1") + ListSeparator); + file.Write(airLinkDataOut.aqiPm2p5_24hr.ToString("F1") + ListSeparator); + file.Write(airLinkDataOut.aqiPm2p5_nowcast.ToString("F1") + ListSeparator); + file.Write(airLinkDataOut.aqiPm10.ToString("F1") + ListSeparator); + file.Write(airLinkDataOut.aqiPm10_1hr.ToString("F1") + ListSeparator); + file.Write(airLinkDataOut.aqiPm10_3hr.ToString("F1") + ListSeparator); + file.Write(airLinkDataOut.aqiPm10_24hr.ToString("F1") + ListSeparator); + file.Write(airLinkDataOut.aqiPm10_nowcast.ToString("F1")); + } + else + { + // write zero values - subtract 2 for firmware version, wifi RSSI - subtract 1 for end field + for (var i = 0; i < typeof(AirLinkData).GetProperties().Length - 3; i++) + { + file.Write("0" + ListSeparator); + } + file.Write("0"); + } + + file.WriteLine(); + file.Close(); + } + } + + public void BackupData(bool daily, DateTime timestamp) + { + string dirpath = daily ? backupPath + "daily" + DirectorySeparator : backupPath; + + if (!Directory.Exists(dirpath)) + { + LogMessage("BackupData: *** Error - backup folder does not exist - " + dirpath); + } + else + { + string[] dirs = Directory.GetDirectories(dirpath); + Array.Sort(dirs); + var dirlist = new List(dirs); + + while (dirlist.Count > 10) + { + if (Path.GetFileName(dirlist[0]) == "daily") + { + LogMessage("BackupData: *** Error - the backup folder has unexpected contents"); + break; + } + else + { + Directory.Delete(dirlist[0], true); + dirlist.RemoveAt(0); + } + } + + string foldername = timestamp.ToString("yyyyMMddHHmmss"); + + foldername = dirpath + foldername + DirectorySeparator; + + LogMessage("BackupData: Creating backup folder " + foldername); + + var alltimebackup = foldername + "alltime.ini"; + var monthlyAlltimebackup = foldername + "monthlyalltime.ini"; + var daybackup = foldername + "dayfile.txt"; + var yesterdaybackup = foldername + "yesterday.ini"; + var todaybackup = foldername + "today.ini"; + var monthbackup = foldername + "month.ini"; + var yearbackup = foldername + "year.ini"; + var diarybackup = foldername + "diary.db"; + var configbackup = foldername + "Cumulus.ini"; + + var LogFile = GetLogFileName(timestamp); + var logbackup = foldername + LogFile.Replace(logFilePath, ""); + + var extraFile = GetExtraLogFileName(timestamp); + var extraBackup = foldername + extraFile.Replace(logFilePath, ""); + + var AirLinkFile = GetAirLinkLogFileName(timestamp); + var AirLinkBackup = foldername + AirLinkFile.Replace(logFilePath, ""); + + if (!Directory.Exists(foldername)) + { + Directory.CreateDirectory(foldername); + CopyBackupFile(AlltimeIniFile, alltimebackup); + CopyBackupFile(MonthlyAlltimeIniFile, monthlyAlltimebackup); + CopyBackupFile(DayFileName, daybackup); + CopyBackupFile(TodayIniFile, todaybackup); + CopyBackupFile(YesterdayFile, yesterdaybackup); + CopyBackupFile(LogFile, logbackup); + CopyBackupFile(MonthIniFile, monthbackup); + CopyBackupFile(YearIniFile, yearbackup); + CopyBackupFile(diaryfile, diarybackup); + CopyBackupFile("Cumulus.ini", configbackup); + CopyBackupFile(extraFile, extraBackup); + CopyBackupFile(AirLinkFile, AirLinkBackup); + // Do not do this extra backup between 00:00 & Rollover hour on the first of the month + // as the month has not yet rolled over - only applies for start-up backups + if (timestamp.Day == 1 && timestamp.Hour >= RolloverHour) + { + // on the first of month, we also need to backup last months files as well + var LogFile2 = GetLogFileName(timestamp.AddDays(-1)); + var logbackup2 = foldername + LogFile2.Replace(logFilePath, ""); + + var extraFile2 = GetExtraLogFileName(timestamp.AddDays(-1)); + var extraBackup2 = foldername + extraFile2.Replace(logFilePath, ""); + + var AirLinkFile2 = GetAirLinkLogFileName(timestamp.AddDays(-1)); + var AirLinkBackup2 = foldername + AirLinkFile2.Replace(logFilePath, ""); + + CopyBackupFile(LogFile2, logbackup2, true); + CopyBackupFile(extraFile2, extraBackup2, true); + CopyBackupFile(AirLinkFile2, AirLinkBackup2, true); + } + + LogMessage("Created backup folder " + foldername); + } + else + { + LogMessage("Backup folder " + foldername + " already exists, skipping backup"); + } + } + } + + private void CopyBackupFile(string src, string dest, bool overwrite=false) + { + try + { + if (File.Exists(src)) + { + File.Copy(src, dest, overwrite); + } + } + catch (Exception e) + { + LogMessage($"BackupData: Error copying {src} - {e}"); + } + } + + /* + /// + /// Get a snapshot of the current data values + /// + /// Structure containing current values + public CurrentData GetCurrentData() + { + CurrentData currentData = new CurrentData(); + + if (station != null) + { + currentData.Avgbearing = station.AvgBearing; + currentData.Bearing = station.Bearing; + currentData.HeatIndex = station.HeatIndex; + currentData.Humidex = station.Humidex; + currentData.AppTemp = station.ApparentTemperature; + currentData.FeelsLike = station.FeelsLike; + currentData.IndoorHumidity = station.IndoorHumidity; + currentData.IndoorTemperature = station.IndoorTemperature; + currentData.OutdoorDewpoint = station.OutdoorDewpoint; + currentData.OutdoorHumidity = station.OutdoorHumidity; + currentData.OutdoorTemperature = station.OutdoorTemperature; + currentData.AvgTempToday = station.TempTotalToday / station.tempsamplestoday; + currentData.Pressure = station.Pressure; + currentData.RainMonth = station.RainMonth; + currentData.RainRate = station.RainRate; + currentData.RainToday = station.RainToday; + currentData.RainYesterday = station.RainYesterday; + currentData.RainYear = station.RainYear; + currentData.RainLastHour = station.RainLastHour; + currentData.Recentmaxgust = station.RecentMaxGust; + currentData.WindAverage = station.WindAverage; + currentData.WindChill = station.WindChill; + currentData.WindLatest = station.WindLatest; + currentData.WindRunToday = station.WindRunToday; + currentData.TempTrend = station.temptrendval; + currentData.PressTrend = station.presstrendval; + } + + return currentData; + } + */ + + /*public HighLowData GetHumidityHighLowData() + { + HighLowData data = new HighLowData(); + + if (station != null) + { + data.TodayHigh = station.HighHumidityToday; + data.TodayHighDT = station.HighHumidityTodayTime; + + data.TodayLow = station.LowHumidityToday; + data.TodayLowDT = station.LowHumidityTodayTime; + + data.YesterdayHigh = station.Yesterdayhighouthumidity; + data.YesterdayHighDT = station.Yesterdayhighouthumiditydt.ToLocalTime(); + + data.YesterdayLow = station.Yesterdaylowouthumidity; + data.YesterdayLowDT = station.Yesterdaylowouthumiditydt.ToLocalTime(); + + data.MonthHigh = station.Monthhighouthumidity; + data.MonthHighDT = station.Monthhighouthumiditydt.ToLocalTime(); + + data.MonthLow = station.Monthlowouthumidity; + data.MonthLowDT = station.Monthlowouthumiditydt.ToLocalTime(); + + data.YearHigh = station.Yearhighouthumidity; + data.YearHighDT = station.Yearhighouthumiditydt.ToLocalTime(); + + data.YearLow = station.Yearlowouthumidity; + data.YearLowDT = station.Yearlowouthumiditydt.ToLocalTime(); + } + + return data; + } + + public HighLowData GetOuttempHighLowData() + { + HighLowData data = new HighLowData(); + + if (station != null) + { + data.TodayHigh = station.Todayhighouttemp; + data.TodayHighDT = station.Todayhighouttempdt.ToLocalTime(); + + data.TodayLow = station.Todaylowouttemp; + data.TodayLowDT = station.Todaylowouttempdt.ToLocalTime(); + + data.YesterdayHigh = station.Yesterdayhighouttemp; + data.YesterdayHighDT = station.Yesterdayhighouttempdt.ToLocalTime(); + + data.YesterdayLow = station.Yesterdaylowouttemp; + data.YesterdayLowDT = station.Yesterdaylowouttempdt.ToLocalTime(); + + data.MonthHigh = station.Monthhighouttemp; + data.MonthHighDT = station.Monthhighouttempdt.ToLocalTime(); + + data.MonthLow = station.Monthlowouttemp; + data.MonthLowDT = station.Monthlowouttempdt.ToLocalTime(); + + data.YearHigh = station.Yearhighouttemp; + data.YearHighDT = station.Yearhighouttempdt.ToLocalTime(); + + data.YearLow = station.Yearlowouttemp; + data.YearLowDT = station.Yearlowouttempdt.ToLocalTime(); + } + + return data; + } + + public HighLowData GetPressureHighLowData() + { + HighLowData data = new HighLowData(); + + if (station != null) + { + data.TodayHigh = station.Todayhighpressure; + data.TodayHighDT = station.Todayhighpressuredt.ToLocalTime(); + + data.TodayLow = station.Todaylowpressure; + data.TodayLowDT = station.Todaylowpressuredt.ToLocalTime(); + + data.YesterdayHigh = station.Yesterdayhighpressure; + data.YesterdayHighDT = station.Yesterdayhighpressuredt.ToLocalTime(); + + data.YesterdayLow = station.Yesterdaylowpressure; + data.YesterdayLowDT = station.Yesterdaylowpressuredt.ToLocalTime(); + + data.MonthHigh = station.Monthhighpressure; + data.MonthHighDT = station.Monthhighpressuredt.ToLocalTime(); + + data.MonthLow = station.Monthlowpressure; + data.MonthLowDT = station.Monthlowpressuredt.ToLocalTime(); + + data.YearHigh = station.Yearhighpressure; + data.YearHighDT = station.Yearhighpressuredt.ToLocalTime(); + + data.YearLow = station.Yearlowpressure; + data.YearLowDT = station.Yearlowpressuredt.ToLocalTime(); + } + + return data; + } + + public HighLowData GetRainRateHighLowData() + { + HighLowData data = new HighLowData(); + + if (station != null) + { + data.TodayHigh = station.Todayhighrainrate; + data.TodayHighDT = station.Todayhighrainratedt.ToLocalTime(); + + data.YesterdayHigh = station.Yesterdayhighrainrate; + data.YesterdayHighDT = station.Yesterdayhighrainratedt.ToLocalTime(); + + data.MonthHigh = station.Monthhighrainrate; + data.MonthHighDT = station.Monthhighrainratedt.ToLocalTime(); + + data.YearHigh = station.Yearhighrainrate; + data.YearHighDT = station.Yearhighrainratedt.ToLocalTime(); + } + + return data; + } + + public HighLowData GetRainHourHighLowData() + { + HighLowData data = new HighLowData(); + + if (station != null) + { + data.TodayHigh = station.Todayhighrainhour; + data.TodayHighDT = station.Todayhighrainhourdt.ToLocalTime(); + + data.MonthHigh = station.Monthhighrainhour; + data.MonthHighDT = station.Monthhighrainhourdt.ToLocalTime(); + + data.YearHigh = station.Yearhighrainhour; + data.YearHighDT = station.Yearhighrainhourdt.ToLocalTime(); + } + + return data; + } + + public HighLowData GetGustHighLowData() + { + HighLowData data = new HighLowData(); + + if (station != null) + { + data.TodayHigh = station.Todayhighgust; + data.TodayHighDT = station.Todayhighgustdt.ToLocalTime(); + + data.YesterdayHigh = station.Yesterdayhighgust; + data.YesterdayHighDT = station.Yesterdayhighgustdt.ToLocalTime(); + + data.MonthHigh = station.ThisMonthRecs.HighGust.Val; + data.MonthHighDT = station.ThisMonthRecs.HighGust.Ts.ToLocalTime(); + + data.YearHigh = station.Yearhighgust; + data.YearHighDT = station.Yearhighgustdt.ToLocalTime(); + } + + return data; + } + + public HighLowData GetSpeedHighLowData() + { + HighLowData data = new HighLowData(); + + if (station != null) + { + data.TodayHigh = station.Todayhighspeed; + data.TodayHighDT = station.Todayhighspeeddt.ToLocalTime(); + + data.YesterdayHigh = station.Yesterdayhighspeed; + data.YesterdayHighDT = station.Yesterdayhighspeeddt.ToLocalTime(); + + data.MonthHigh = station.Monthhighspeed; + data.MonthHighDT = station.Monthhighspeeddt.ToLocalTime(); + + data.YearHigh = station.Yearhighspeed; + data.YearHighDT = station.Yearhighspeeddt.ToLocalTime(); + } + + return data; + }*/ + + /* + public string GetForecast() + { + return station.Forecast; + } + + public string GetCurrentActivity() + { + return CurrentActivity; + } + + public bool GetImportDataSetting() + { + return ImportData; + } + + public void SetImportDataSetting(bool setting) + { + ImportData = setting; + } + + public bool GetLogExtraDataSetting() + { + return LogExtraData; + } + + public void SetLogExtraDataSetting(bool setting) + { + LogExtraData = setting; + } + + public string GetCumulusIniPath() + { + return CumulusIniPath; + } + + public void SetCumulusIniPath(string inipath) + { + CumulusIniPath = inipath; + } + + public int GetLogInterval() + { + return LogInterval; + } + + public void SetLogInterval(int interval) + { + LogInterval = interval; + } + */ + + public int GetHourInc(DateTime timestamp) + { + if (RolloverHour == 0) + { + return 0; + } + else + { + try + { + if (Use10amInSummer && TimeZoneInfo.Local.IsDaylightSavingTime(timestamp)) + { + // Locale is currently on Daylight time + return -10; + } + + else + { + // Locale is currently on Standard time or unknown + return -9; + } + } + catch (Exception) + { + return -9; + } + } + } + + public int GetHourInc() + { + return GetHourInc(DateTime.Now); + } + + /* + private bool IsDaylightSavings() + { + return TimeZoneInfo.Local.IsDaylightSavingTime(DateTime.Now); + } + */ + + public string Beaufort(double Bspeed) // Takes speed in current unit, returns Bft number as text + { + return station.Beaufort(Bspeed).ToString(); + } + + public string BeaufortDesc(double Bspeed) + { + // Takes speed in current units, returns Bft description + + // Convert to Force + var force = station.Beaufort(Bspeed); + switch (force) + { + case 0: + return Calm; + case 1: + return Lightair; + case 2: + return Lightbreeze; + case 3: + return Gentlebreeze; + case 4: + return Moderatebreeze; + case 5: + return Freshbreeze; + case 6: + return Strongbreeze; + case 7: + return Neargale; + case 8: + return Gale; + case 9: + return Stronggale; + case 10: + return Storm; + case 11: + return Violentstorm; + case 12: + return Hurricane; + default: + return "UNKNOWN"; + } + } + + public void LogErrorMessage(string message) + { + LatestError = message; + LatestErrorTS = DateTime.Now; + LogMessage(message); + } + + public void LogSpikeRemoval(string message) + { + if (ErrorLogSpikeRemoval) + { + LogErrorMessage("Spike removal: " + message); + } + } + + public void Stop() + { + LogMessage("Cumulus closing"); + + //WriteIniFile(); + + //httpServer.Stop(); + + //if (httpServer != null) httpServer.Dispose(); + + // Stop the timers + try + { + LogMessage("Stopping timers"); + RealtimeTimer.Stop(); + WundTimer.Stop(); + WebTimer.Stop(); + AwekasTimer.Stop(); + MQTTTimer.Stop(); + //AirLinkTimer.Stop(); + CustomHttpSecondsTimer.Stop(); + CustomMysqlSecondsTimer.Stop(); + MQTTTimer.Stop(); + } + catch { } + + if (station != null) + { + LogMessage("Stopping station..."); + station.Stop(); + LogMessage("Station stopped"); + + if (station.HaveReadData) + { + LogMessage("Writing today.ini file"); + station.WriteTodayFile(DateTime.Now, false); + LogMessage("Completed writing today.ini file"); + } + else + { + LogMessage("No data read this session, today.ini not written"); + } + + LogMessage("Stopping extra sensors..."); + // If we have a Outdoor AirLink sensor, and it is linked to this WLL then stop it now + airLinkOut?.Stop(); + // If we have a Indoor AirLink sensor, and it is linked to this WLL then stop it now + airLinkIn?.Stop(); + LogMessage("Extra sensors stopped"); + + } + LogMessage("Station shutdown complete"); + } + + public void ExecuteProgram(string externalProgram, string externalParams) + { + // Prepare the process to run + ProcessStartInfo start = new ProcessStartInfo() + { + // Enter in the command line arguments + Arguments = externalParams, + // Enter the executable to run, including the complete path + FileName = externalProgram, + // Dont show a console window + CreateNoWindow = true + }; + + // Run the external process + Process.Start(start); + } + + public void DoHTMLFiles() + { + try + { + if (!RealtimeEnabled) + { + CreateRealtimeFile(999); + MySqlRealtimeFile(999); + } + + LogDebugMessage("Creating standard web files"); + for (var i = 0; i < StdWebFiles.Length; i++) + { + if (StdWebFiles[i].Create && !string.IsNullOrWhiteSpace(StdWebFiles[i].TemplateFileName)) + { + var destFile = StdWebFiles[i].LocalPath + StdWebFiles[i].LocalFileName; + ProcessTemplateFile(StdWebFiles[i].TemplateFileName, destFile, tokenParser); + } + } + LogDebugMessage("Done creating standard Data file"); + + LogDebugMessage("Creating graph data files"); + station.CreateGraphDataFiles(); + LogDebugMessage("Done creating graph data files"); + + //LogDebugMessage("Creating extra files"); + // handle any extra files + for (int i = 0; i < numextrafiles; i++) + { + if (!ExtraFiles[i].realtime && !ExtraFiles[i].endofday) + { + var uploadfile = ExtraFiles[i].local; + var remotefile = ExtraFiles[i].remote; + + if ((uploadfile.Length > 0) && (remotefile.Length > 0)) + { + uploadfile = GetUploadFilename(uploadfile, DateTime.Now); + + if (File.Exists(uploadfile)) + { + remotefile = GetRemoteFileName(remotefile, DateTime.Now); + + if (ExtraFiles[i].process) + { + LogDebugMessage($"Interval: Processing extra file[{i}] - {uploadfile}"); + // process the file + var utf8WithoutBom = new UTF8Encoding(false); + var encoding = UTF8encode ? utf8WithoutBom : Encoding.GetEncoding("iso-8859-1"); + tokenParser.Encoding = encoding; + tokenParser.SourceFile = uploadfile; + var output = tokenParser.ToString(); + uploadfile += "tmp"; + try + { + using (StreamWriter file = new StreamWriter(uploadfile, false, encoding)) + { + file.Write(output); + + file.Close(); + } + } + catch (Exception ex) + { + LogDebugMessage($"Interval: Error writing file[{i}] - {uploadfile}"); + LogDebugMessage(ex.Message); + } + //LogDebugMessage("Finished processing extra file " + uploadfile); + } + + if (!ExtraFiles[i].FTP) + { + // just copy the file + LogDebugMessage($"Interval: Copying extra file[{i}] {uploadfile} to {remotefile}"); + try + { + File.Copy(uploadfile, remotefile, true); + } + catch (Exception ex) + { + LogDebugMessage($"Interval: Error copying extra file[{i}]: " + ex.Message); + } + //LogDebugMessage("Finished copying extra file " + uploadfile); + } + } + else + { + LogMessage($"Interval: Warning, extra web file[{i}] not found - {uploadfile}"); + } + } + } + } + + if (!string.IsNullOrEmpty(ExternalProgram)) + { + LogDebugMessage("Interval: Executing program " + ExternalProgram + " " + ExternalParams); + try + { + ExecuteProgram(ExternalProgram, ExternalParams); + LogDebugMessage("Interval: External program started"); + } + catch (Exception ex) + { + LogMessage("Interval: Error starting external program: " + ex.Message); + } + } + + //LogDebugMessage("Done creating extra files"); + + if (!string.IsNullOrEmpty(FtpHostname)) + { + DoFTPLogin(); + } + } + finally + { + WebUpdating = 0; + } + } + + public void DoFTPLogin() + { + var remotePath = ""; + + if (FtpDirectory.Length > 0) + { + remotePath = (FtpDirectory.EndsWith("/") ? FtpDirectory : FtpDirectory + "/"); + } + + if (Sslftp == FtpProtocols.SFTP) + { + // BUILD 3092 - added alternate SFTP authenication options + ConnectionInfo connectionInfo; + if (SshftpAuthentication == "password") + { + connectionInfo = new ConnectionInfo(FtpHostname, FtpHostPort, FtpUsername, new PasswordAuthenticationMethod(FtpUsername, FtpPassword)); + LogFtpDebugMessage("SFTP[Int]: Connecting using password authentication"); + } + else if (SshftpAuthentication == "psk") + { + PrivateKeyFile pskFile = new PrivateKeyFile(SshftpPskFile); + connectionInfo = new ConnectionInfo(FtpHostname, FtpHostPort, FtpUsername, new PrivateKeyAuthenticationMethod(FtpUsername, pskFile)); + LogFtpDebugMessage("SFTP[Int]: Connecting using PSK authentication"); + } + else if (SshftpAuthentication == "password_psk") + { + PrivateKeyFile pskFile = new PrivateKeyFile(SshftpPskFile); + connectionInfo = new ConnectionInfo(FtpHostname, FtpHostPort, FtpUsername, new PasswordAuthenticationMethod(FtpUsername, FtpPassword), new PrivateKeyAuthenticationMethod(FtpUsername, pskFile)); + LogFtpDebugMessage("SFTP[Int]: Connecting using password or PSK authentication"); + } + else + { + LogFtpMessage($"SFTP[Int]: Invalid SshftpAuthentication specified [{SshftpAuthentication}]"); + return; + } + + using (SftpClient conn = new SftpClient(connectionInfo)) + { + try + { + LogFtpDebugMessage($"SFTP[Int]: CumulusMX Connecting to {FtpHostname} on port {FtpHostPort}"); + conn.Connect(); + } + catch (Exception ex) + { + LogFtpMessage($"SFTP[Int]: Error connecting sftp - {ex.Message}"); + return; + } + + if (conn.IsConnected) + { + if (NOAANeedFTP) + { + try + { + // upload NOAA reports + LogFtpDebugMessage("SFTP[Int]: Uploading NOAA reports"); + + var uploadfile = ReportPath + NOAALatestMonthlyReport; + var remotefile = NOAAFTPDirectory + '/' + NOAALatestMonthlyReport; + + UploadFile(conn, uploadfile, remotefile, -1); + + uploadfile = ReportPath + NOAALatestYearlyReport; + remotefile = NOAAFTPDirectory + '/' + NOAALatestYearlyReport; + + UploadFile(conn, uploadfile, remotefile, -1); + + LogFtpDebugMessage("SFTP[Int]: Done uploading NOAA reports"); + } + catch (Exception e) + { + LogFtpMessage($"SFTP[Int]: Error uploading file - {e.Message}"); + } + NOAANeedFTP = false; + } + + LogFtpDebugMessage("SFTP[Int]: Uploading extra files"); + // Extra files + for (int i = 0; i < numextrafiles; i++) + { + var uploadfile = ExtraFiles[i].local; + var remotefile = ExtraFiles[i].remote; + + if ((uploadfile.Length > 0) && + (remotefile.Length > 0) && + !ExtraFiles[i].realtime && + (!ExtraFiles[i].endofday || EODfilesNeedFTP == ExtraFiles[i].endofday) && // Either, it's not flagged as an EOD file, OR: It is flagged as EOD and EOD FTP is required + ExtraFiles[i].FTP) + { + // For EOD files, we want the previous days log files since it is now just past the day rollover time. Makes a difference on month rollover + var logDay = ExtraFiles[i].endofday ? DateTime.Now.AddDays(-1) : DateTime.Now; + + uploadfile = GetUploadFilename(uploadfile, logDay); + + if (File.Exists(uploadfile)) + { + remotefile = GetRemoteFileName(remotefile, logDay); + + // all checks OK, file needs to be uploaded + if (ExtraFiles[i].process) + { + // we've already processed the file + uploadfile += "tmp"; + } + + try + { + UploadFile(conn, uploadfile, remotefile, -1); + } + catch (Exception e) + { + LogFtpMessage($"SFTP[Int]: Error uploading Extra web file #{i} [{uploadfile}]"); + LogFtpMessage($"SFTP[Int]: Error = {e.Message}"); + } + } + else + { + LogFtpMessage($"SFTP[Int]: Extra web file #{i} [{uploadfile}] not found!"); + } + } + } + if (EODfilesNeedFTP) + { + EODfilesNeedFTP = false; + } + LogFtpDebugMessage("SFTP[Int]: Done uploading extra files"); + + // standard files + LogFtpDebugMessage("SFTP[Int]: Uploading standard web files"); + for (var i = 0; i < StdWebFiles.Length; i++) + { + if (StdWebFiles[i].FTP && StdWebFiles[i].FtpRequired) + { + try + { + var localFile = StdWebFiles[i].LocalPath + StdWebFiles[i].LocalFileName; + var remotefile = remotePath + StdWebFiles[i].RemoteFileName; + UploadFile(conn, localFile, remotefile, -1); + } + catch (Exception e) + { + LogFtpMessage($"SFTP[Int]: Error uploading standard data file [{StdWebFiles[i].LocalFileName}]"); + LogFtpMessage($"SFTP[Int]: Error = {e}"); + } + } + } + LogFtpDebugMessage("SFTP[Int]: Done uploading standard web files"); + + LogFtpDebugMessage("SFTP[Int]: Uploading graph data files"); + + for (int i = 0; i < GraphDataFiles.Length; i++) + { + if (GraphDataFiles[i].FTP && GraphDataFiles[i].FtpRequired) + { + var uploadfile = GraphDataFiles[i].LocalPath + GraphDataFiles[i].LocalFileName; + var remotefile = remotePath + GraphDataFiles[i].RemoteFileName; + + try + { + UploadFile(conn, uploadfile, remotefile, -1); + // The config files only need uploading once per change + if (GraphDataFiles[i].LocalFileName == "availabledata.json" || + GraphDataFiles[i].LocalFileName == "graphconfig.json") + { + GraphDataFiles[i].FtpRequired = false; + } + } + catch (Exception e) + { + LogFtpMessage($"SFTP[Int]: Error uploading graph data file [{uploadfile}]"); + LogFtpMessage($"SFTP[Int]: Error = {e}"); + } + } + } + LogFtpDebugMessage("SFTP[Int]: Done uploading graph data files"); + + LogFtpMessage("SFTP[Int]: Uploading daily graph data files"); + for (int i = 0; i < GraphDataEodFiles.Length; i++) + { + if (GraphDataEodFiles[i].FTP && GraphDataEodFiles[i].FtpRequired) + { + var uploadfile = GraphDataEodFiles[i].LocalPath + GraphDataEodFiles[i].LocalFileName; + var remotefile = remotePath + GraphDataEodFiles[i].RemoteFileName; + try + { + UploadFile(conn, uploadfile, remotefile, -1); + // Uploaded OK, reset the upload required flag + GraphDataEodFiles[i].FtpRequired = false; + } + catch (Exception e) + { + LogFtpMessage($"SFTP[Int]: Error uploading daily graph data file [{uploadfile}]"); + LogFtpMessage($"SFTP[Int]: Error = {e}"); + } + } + } + LogFtpMessage("SFTP[Int]: Done uploading daily graph data files"); + + if (IncludeMoonImage && MoonImageReady) + { + try + { + LogFtpMessage("SFTP[Int]: Uploading Moon image file"); + UploadFile(conn, "web" + DirectorySeparator + "moon.png", remotePath + MoonImageFtpDest, -1); + LogFtpMessage("SFTP[Int]: Done uploading Moon image file"); + // clear the image ready for FTP flag, only upload once an hour + MoonImageReady = false; + } + catch (Exception e) + { + LogMessage($"SFTP[Int]: Error uploading moon image - {e.Message}"); + } + } + } + try + { + // do not error on disconnect + conn.Disconnect(); + } + catch { } + } + LogFtpDebugMessage("SFTP[Int]: Connection process complete"); + } + else + { + using (FtpClient conn = new FtpClient()) + { + if (FTPlogging) FtpTrace.WriteLine(""); // insert a blank line + LogFtpDebugMessage($"FTP[Int]: CumulusMX Connecting to " + FtpHostname); + conn.Host = FtpHostname; + conn.Port = FtpHostPort; + conn.Credentials = new NetworkCredential(FtpUsername, FtpPassword); + + if (Sslftp == FtpProtocols.FTPS) + { + // Explicit = Current protocol - connects using FTP and switches to TLS + // Implicit = Old depreciated protcol - connects using TLS + conn.EncryptionMode = DisableFtpsExplicit ? FtpEncryptionMode.Implicit : FtpEncryptionMode.Explicit; + conn.DataConnectionEncryption = true; + conn.ValidateAnyCertificate = true; + // b3045 - switch from System.Net.Ftp.Client to FluentFTP allows us to specify protocols + conn.SslProtocols = SslProtocols.Default | SslProtocols.Tls11 | SslProtocols.Tls12; + } + + if (ActiveFTPMode) + { + conn.DataConnectionType = FtpDataConnectionType.PORT; + } + else if (DisableFtpsEPSV) + { + conn.DataConnectionType = FtpDataConnectionType.PASV; + } + + try + { + conn.Connect(); + } + catch (Exception ex) + { + LogFtpMessage("FTP[Int]: Error connecting ftp - " + ex.Message); + return; + } + + conn.EnableThreadSafeDataConnections = false; // use same connection for all transfers + + if (conn.IsConnected) + { + if (NOAANeedFTP) + { + try + { + // upload NOAA reports + LogFtpDebugMessage("FTP[Int]: Uploading NOAA reports"); + + var uploadfile = ReportPath + NOAALatestMonthlyReport; + var remotefile = NOAAFTPDirectory + '/' + NOAALatestMonthlyReport; + + UploadFile(conn, uploadfile, remotefile); + + uploadfile = ReportPath + NOAALatestYearlyReport; + remotefile = NOAAFTPDirectory + '/' + NOAALatestYearlyReport; + + UploadFile(conn, uploadfile, remotefile); + LogFtpDebugMessage("FTP[Int]: Upload of NOAA reports complete"); + } + catch (Exception e) + { + LogFtpMessage($"FTP[Int]: Error uploading NOAA files: {e.Message}"); + } + NOAANeedFTP = false; + } + + // Extra files + LogFtpDebugMessage("FTP[Int]: Uploading Extra files"); + for (int i = 0; i < numextrafiles; i++) + { + var uploadfile = ExtraFiles[i].local; + var remotefile = ExtraFiles[i].remote; + + if ((uploadfile.Length > 0) && + (remotefile.Length > 0) && + !ExtraFiles[i].realtime && + (EODfilesNeedFTP || (EODfilesNeedFTP == ExtraFiles[i].endofday)) && + ExtraFiles[i].FTP) + { + // For EOD files, we want the previous days log files since it is now just past the day rollover time. Makes a difference on month rollover + var logDay = ExtraFiles[i].endofday ? DateTime.Now.AddDays(-1) : DateTime.Now; + + uploadfile = GetUploadFilename(uploadfile, logDay); + + if (File.Exists(uploadfile)) + { + remotefile = GetRemoteFileName(remotefile, logDay); + + // all checks OK, file needs to be uploaded + if (ExtraFiles[i].process) + { + // we've already processed the file + uploadfile += "tmp"; + } + + try + { + UploadFile(conn, uploadfile, remotefile); + } + catch (Exception e) + { + LogFtpMessage($"FTP[Int]: Error uploading file {uploadfile}: {e.Message}"); + } + } + else + { + LogFtpMessage("FTP[Int]: Extra web file #" + i + " [" + uploadfile + "] not found!"); + } + } + } + if (EODfilesNeedFTP) + { + EODfilesNeedFTP = false; + } + // standard files + LogFtpDebugMessage("FTP[Int]: Uploading standard Data file"); + for (int i = 0; i < StdWebFiles.Length; i++) + { + if (StdWebFiles[i].FTP && StdWebFiles[i].FtpRequired) + { + try + { + var localfile = StdWebFiles[i].LocalPath + StdWebFiles[i].LocalFileName; + UploadFile(conn, localfile, remotePath + StdWebFiles[i].RemoteFileName); + } + catch (Exception e) + { + LogFtpMessage($"FTP[Int]: Error uploading file {StdWebFiles[i].LocalFileName}: {e}"); + } + } + } + LogFtpMessage("Done uploading standard Data file"); + + LogFtpDebugMessage("FTP[Int]: Uploading graph data files"); + for (int i = 0; i < GraphDataFiles.Length; i++) + { + if (GraphDataFiles[i].FTP && GraphDataFiles[i].FtpRequired) + { + try + { + var localfile = GraphDataFiles[i].LocalPath + GraphDataFiles[i].LocalFileName; + var remotefile = remotePath + GraphDataFiles[i].RemoteFileName; + UploadFile(conn, localfile, remotefile); + } + catch (Exception e) + { + LogFtpMessage($"FTP[Int]: Error uploading graph data file [{GraphDataFiles[i].LocalFileName}]"); + LogFtpMessage($"FTP[Int]: Error = {e}"); + } + } + } + LogFtpMessage("Done uploading graph data files"); + + LogFtpMessage("FTP[Int]: Uploading daily graph data files"); + for (int i = 0; i < GraphDataEodFiles.Length; i++) + { + if (GraphDataEodFiles[i].FTP && GraphDataEodFiles[i].FtpRequired) + { + var localfile = GraphDataEodFiles[i].LocalPath + GraphDataEodFiles[i].LocalFileName; + var remotefile = remotePath + GraphDataEodFiles[i].RemoteFileName; + try + { + UploadFile(conn, localfile, remotefile, -1); + // Uploaded OK, reset the upload required flag + GraphDataEodFiles[i].FtpRequired = false; + } + catch (Exception e) + { + LogFtpMessage($"SFTP[Int]: Error uploading daily graph data file [{GraphDataEodFiles[i].LocalFileName}]"); + LogFtpMessage($"SFTP[Int]: Error = {e}"); + } + } + } + LogFtpMessage("FTP[Int]: Done uploading daily graph data files"); + + if (IncludeMoonImage && MoonImageReady) + { + try + { + LogFtpDebugMessage("FTP[Int]: Uploading Moon image file"); + UploadFile(conn, "web" + DirectorySeparator + "moon.png", remotePath + MoonImageFtpDest); + // clear the image ready for FTP flag, only upload once an hour + MoonImageReady = false; + } + catch (Exception e) + { + LogMessage($"FTP[Int]: Error uploading moon image - {e.Message}"); + } + } + } + + // b3045 - dispose of connection + conn.Disconnect(); + LogFtpDebugMessage("FTP[Int]: Disconnected from " + FtpHostname); + } + LogFtpMessage("FTP[Int]: Process complete"); + } + } + + private void UploadFile(FtpClient conn, string localfile, string remotefile, int cycle = -1) + { + string remotefilename = FTPRename ? remotefile + "tmp" : remotefile; + string cycleStr = cycle >= 0 ? cycle.ToString() : "Int"; + + if (FTPlogging) FtpTrace.WriteLine(""); + try + { + if (!File.Exists(localfile)) + { + LogMessage($"FTP[{cycleStr}]: Error! Local file not found, aborting upload: {localfile}"); + return; + } + + if (DeleteBeforeUpload) + { + // delete the existing file + try + { + LogFtpDebugMessage($"FTP[{cycleStr}]: Deleting {remotefile}"); + conn.DeleteFile(remotefile); + } + catch (Exception ex) + { + LogFtpMessage($"FTP[{cycleStr}]: Error deleting {remotefile} : {ex.Message}"); + } + } + + LogFtpDebugMessage($"FTP[{cycleStr}]: Uploading {localfile} to {remotefilename}"); + + using (Stream ostream = conn.OpenWrite(remotefilename)) + using (Stream istream = new FileStream(localfile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + try + { + var buffer = new byte[4096]; + int read; + while ((read = istream.Read(buffer, 0, buffer.Length)) > 0) + { + ostream.Write(buffer, 0, read); + } + + LogFtpDebugMessage($"FTP[{cycleStr}]: Uploaded {localfile}"); + } + catch (Exception ex) + { + LogFtpMessage($"FTP[{cycleStr}]: Error uploading {localfile} to {remotefilename} : {ex.Message}"); + } + finally + { + ostream.Close(); + istream.Close(); + conn.GetReply(); // required FluentFTP 19.2 + } + } + + if (FTPRename) + { + // rename the file + LogFtpDebugMessage($"FTP[{cycleStr}]: Renaming {remotefilename} to {remotefile}"); + + try + { + conn.Rename(remotefilename, remotefile); + LogFtpDebugMessage($"FTP[{cycleStr}]: Renamed {remotefilename}"); + } + catch (Exception ex) + { + LogFtpMessage($"FTP[{cycleStr}]: Error renaming {remotefilename} to {remotefile} : {ex.Message}"); + } + } + } + catch (Exception ex) + { + LogFtpMessage($"FTP[{cycleStr}]: Error uploading {localfile} to {remotefile} : {ex.Message}"); + } + } + + private void UploadFile(SftpClient conn, string localfile, string remotefile, int cycle) + { + string remotefilename = FTPRename ? remotefile + "tmp" : remotefile; + string cycleStr = cycle >= 0 ? cycle.ToString() : "Int"; + + if (!File.Exists(localfile)) + { + LogMessage($"SFTP[{cycleStr}]: Error! Local file not found, aborting upload: {localfile}"); + return; + } + + try + { + if (conn == null || !conn.IsConnected) + { + LogFtpMessage($"SFTP[{cycleStr}]: The SFTP object is null or not connected - skipping upload of {localfile}"); + return; + } + } + catch (ObjectDisposedException) + { + LogFtpMessage($"SFTP[{cycleStr}]: The SFTP object is disposed - skipping upload of {localfile}"); + return; + } + + try + { + // No delete before upload required for SFTP as we use the overwrite flag + + using (Stream istream = new FileStream(localfile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + try + { + LogFtpDebugMessage($"SFTP[{cycleStr}]: Uploading {localfile} to {remotefilename}"); + + conn.OperationTimeout = TimeSpan.FromSeconds(15); + conn.UploadFile(istream, remotefilename, true); + + LogFtpDebugMessage($"SFTP[{cycleStr}]: Uploaded {localfile}"); + } + catch (Exception ex) + { + LogFtpMessage($"SFTP[{cycleStr}]: Error uploading {localfile} to {remotefilename} : {ex.Message}"); + + if (ex.Message.Contains("Permission denied")) // Non-fatal + return; + + // Lets start again anyway! Too hard to tell if the error is recoverable + conn.Dispose(); + return; + } + } + + if (FTPRename) + { + // rename the file + try + { + LogFtpDebugMessage($"SFTP[{cycleStr}]: Renaming {remotefilename} to {remotefile}"); + conn.RenameFile(remotefilename, remotefile, true); + LogFtpDebugMessage($"SFTP[{cycleStr}]: Renamed {remotefilename}"); + } + catch (Exception ex) + { + LogFtpMessage($"SFTP[{cycleStr}]: Error renaming {remotefilename} to {remotefile} : {ex.Message}"); + return; + } + } + LogFtpDebugMessage($"SFTP[{cycleStr}]: Completed uploading {localfile} to {remotefile}"); + } + catch (Exception ex) + { + LogFtpMessage($"SFTP[{cycleStr}]: Error uploading {localfile} to {remotefile} - {ex.Message}"); + } + } + + public void LogMessage(string message) + { + Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff ") + message); + } + + public void LogDebugMessage(string message) + { + if (ProgramOptions.DebugLogging || ProgramOptions.DataLogging) + { + Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff ") + message); + } + } + + public void LogDataMessage(string message) + { + if (ProgramOptions.DataLogging) + { + Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff ") + message); + } + } + + public void LogFtpMessage(string message) + { + LogMessage(message); + if (FTPlogging) + { + FtpTraceListener.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff ") + message); + } + } + + public void LogFtpDebugMessage(string message) + { + if (FTPlogging) + { + LogDebugMessage(message); + FtpTraceListener.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff ") + message); + } + } + + public void LogConsoleMessage(string message) + { + if (!Program.service) + { + Console.WriteLine(message); + } + + Program.svcTextListener.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff ") + message); + Program.svcTextListener.Flush(); + } + + /* + public string ReplaceCommas(string AStr) + { + return AStr.Replace(',', '.'); + } + */ + + private void CreateRealtimeFile(int cycle) + { + /* + Example: 18/10/08 16:03:45 8.4 84 5.8 24.2 33.0 261 0.0 1.0 999.7 W 6 mph C mb mm 146.6 +0.1 85.2 588.4 11.6 20.3 57 3.6 -0.7 10.9 12:00 7.8 14:41 37.4 14:38 44.0 14:28 999.8 16:01 998.4 12:06 1.8.2 448 36.0 10.3 10.5 0 9.3 + + Field Example Description + 1 18/10/08 date (always dd/mm/yy) + 2 16:03:45 time (always hh:mm:ss) + 3 8.4 outside temperature + 4 84 relative humidity + 5 5.8 dewpoint + 6 24.2 wind speed (average) + 7 33.0 latest wind speed + 8 261 wind bearing + 9 0.0 current rain rate + 10 1.0 rain today + 11 999.7 barometer + 12 W wind direction + 13 6 wind speed (beaufort) + 14 mph wind units + 15 C temperature units + 16 mb pressure units + 17 mm rain units + 18 146.6 wind run (today) + 19 +0.1 pressure trend value + 20 85.2 monthly rain + 21 588.4 yearly rain + 22 11.6 yesterday's rainfall + 23 20.3 inside temperature + 24 57 inside humidity + 25 3.6 wind chill + 26 -0.7 temperature trend value + 27 10.9 today's high temp + 28 12:00 time of today's high temp (hh:mm) + 29 7.8 today's low temp + 30 14:41 time of today's low temp (hh:mm) + 31 37.4 today's high wind speed (average) + 32 14:38 time of today's high wind speed (average) (hh:mm) + 33 44.0 today's high wind gust + 34 14:28 time of today's high wind gust (hh:mm) + 35 999.8 today's high pressure + 36 16:01 time of today's high pressure (hh:mm) + 37 998.4 today's low pressure + 38 12:06 time of today's low pressure (hh:mm) + 39 1.8.2 Cumulus version + 40 448 Cumulus build + 41 36.0 10-minute high gust + 42 10.3 heat index + 43 10.5 humidex + 44 UV + 45 ET + 46 Solar radiation + 47 234 Average Bearing (degrees) + 48 2.5 Rain last hour + 49 5 Forecast number + 50 1 Is daylight? (1 = yes) + 51 0 Sensor contact lost (1 = yes) + 52 NNW wind direction (average) + 53 2040 Cloudbase + 54 ft Cloudbase units + 55 12.3 Apparent Temp + 56 11.4 Sunshine hours today + 57 420 Current theoretical max solar radiation + 58 1 Is sunny? + 59 8.4 Feels Like temperature + */ + + // Does the user want to create the realtime.txt file? + if (!RealtimeFiles[0].Create) + { + return; + } + + var filename = AppDir + RealtimeFile; + DateTime timestamp = DateTime.Now; + + try + { + LogDebugMessage($"Realtime[{cycle}]: Creating realtime.txt"); + using (StreamWriter file = new StreamWriter(filename, false)) + { + var InvC = new CultureInfo(""); + + file.Write(timestamp.ToString("dd/MM/yy HH:mm:ss ")); // 1, 2 + file.Write(station.OutdoorTemperature.ToString(TempFormat, InvC) + ' '); // 3 + file.Write(station.OutdoorHumidity.ToString() + ' '); // 4 + file.Write(station.OutdoorDewpoint.ToString(TempFormat, InvC) + ' '); // 5 + file.Write(station.WindAverage.ToString(WindAvgFormat, InvC) + ' '); // 6 + file.Write(station.WindLatest.ToString(WindFormat, InvC) + ' '); // 7 + file.Write(station.Bearing.ToString() + ' '); // 8 + file.Write(station.RainRate.ToString(RainFormat, InvC) + ' '); // 9 + file.Write(station.RainToday.ToString(RainFormat, InvC) + ' '); // 10 + file.Write(station.Pressure.ToString(PressFormat, InvC) + ' '); // 11 + file.Write(station.CompassPoint(station.Bearing) + ' '); // 12 + file.Write(Beaufort(station.WindAverage) + ' '); // 13 + file.Write(Units.WindText + ' '); // 14 + file.Write(Units.TempText[1].ToString() + ' '); // 15 + file.Write(Units.PressText + ' '); // 16 + file.Write(Units.RainText + ' '); // 17 + file.Write(station.WindRunToday.ToString(WindRunFormat, InvC) + ' '); // 18 + if (station.presstrendval > 0) + file.Write('+' + station.presstrendval.ToString(PressFormat, InvC) + ' '); // 19 + else + file.Write(station.presstrendval.ToString(PressFormat, InvC) + ' '); + file.Write(station.RainMonth.ToString(RainFormat, InvC) + ' '); // 20 + file.Write(station.RainYear.ToString(RainFormat, InvC) + ' '); // 21 + file.Write(station.RainYesterday.ToString(RainFormat, InvC) + ' '); // 22 + file.Write(station.IndoorTemperature.ToString(TempFormat, InvC) + ' '); // 23 + file.Write(station.IndoorHumidity.ToString() + ' '); // 24 + file.Write(station.WindChill.ToString(TempFormat, InvC) + ' '); // 25 + file.Write(station.temptrendval.ToString(TempTrendFormat, InvC) + ' '); // 26 + file.Write(station.HiLoToday.HighTemp.ToString(TempFormat, InvC) + ' '); // 27 + file.Write(station.HiLoToday.HighTempTime.ToString("HH:mm ")); // 28 + file.Write(station.HiLoToday.LowTemp.ToString(TempFormat, InvC) + ' '); // 29 + file.Write(station.HiLoToday.LowTempTime.ToString("HH:mm ")); // 30 + file.Write(station.HiLoToday.HighWind.ToString(WindAvgFormat, InvC) + ' '); // 31 + file.Write(station.HiLoToday.HighWindTime.ToString("HH:mm ")); // 32 + file.Write(station.HiLoToday.HighGust.ToString(WindFormat, InvC) + ' '); // 33 + file.Write(station.HiLoToday.HighGustTime.ToString("HH:mm ")); // 34 + file.Write(station.HiLoToday.HighPress.ToString(PressFormat, InvC) + ' '); // 35 + file.Write(station.HiLoToday.HighPressTime.ToString("HH:mm ")); // 36 + file.Write(station.HiLoToday.LowPress.ToString(PressFormat, InvC) + ' '); // 37 + file.Write(station.HiLoToday.LowPressTime.ToString("HH:mm ")); // 38 + file.Write(Version + ' '); // 39 + file.Write(Build + ' '); // 40 + file.Write(station.RecentMaxGust.ToString(WindFormat, InvC) + ' '); // 41 + file.Write(station.HeatIndex.ToString(TempFormat, InvC) + ' '); // 42 + file.Write(station.Humidex.ToString(TempFormat, InvC) + ' '); // 43 + file.Write(station.UV.ToString(UVFormat, InvC) + ' '); // 44 + file.Write(station.ET.ToString(ETFormat, InvC) + ' '); // 45 + file.Write((Convert.ToInt32(station.SolarRad)).ToString() + ' '); // 46 + file.Write(station.AvgBearing.ToString() + ' '); // 47 + file.Write(station.RainLastHour.ToString(RainFormat, InvC) + ' '); // 48 + file.Write(station.Forecastnumber.ToString() + ' '); // 49 + file.Write(IsDaylight() ? "1 " : "0 "); // 50 + file.Write(station.SensorContactLost ? "1 " : "0 "); // 51 + file.Write(station.CompassPoint(station.AvgBearing) + ' '); // 52 + file.Write((Convert.ToInt32(station.CloudBase)).ToString() + ' '); // 53 + file.Write(CloudBaseInFeet ? "ft " : "m "); // 54 + file.Write(station.ApparentTemperature.ToString(TempFormat, InvC) + ' '); // 55 + file.Write(station.SunshineHours.ToString(SunFormat, InvC) + ' '); // 56 + file.Write(Convert.ToInt32(station.CurrentSolarMax).ToString() + ' '); // 57 + file.Write(station.IsSunny ? "1 " : "0 "); // 58 + file.WriteLine(station.FeelsLike.ToString(TempFormat, InvC)); // 59 + + file.Close(); + } + } + catch (Exception ex) + { + LogMessage("Error encountered during Realtime file update."); + LogMessage(ex.Message); + } + } + + private void MySqlRealtimeFile(int cycle) + { + DateTime timestamp = DateTime.Now; + + if (!RealtimeMySqlEnabled) + return; + + if (RealtimeMySql1MinLimit && RealtimeMySqlLastMinute == timestamp.Minute) + return; + + RealtimeMySqlLastMinute = timestamp.Minute; + + var InvC = new CultureInfo(""); + + StringBuilder values = new StringBuilder(StartOfRealtimeInsertSQL, 1024); + values.Append(" Values('"); + values.Append(timestamp.ToString("yy-MM-dd HH:mm:ss") + "',"); + values.Append(station.OutdoorTemperature.ToString(TempFormat, InvC) + ','); + values.Append(station.OutdoorHumidity.ToString() + ','); + values.Append(station.OutdoorDewpoint.ToString(TempFormat, InvC) + ','); + values.Append(station.WindAverage.ToString(WindAvgFormat, InvC) + ','); + values.Append(station.WindLatest.ToString(WindFormat, InvC) + ','); + values.Append(station.Bearing.ToString() + ','); + values.Append(station.RainRate.ToString(RainFormat, InvC) + ','); + values.Append(station.RainToday.ToString(RainFormat, InvC) + ','); + values.Append(station.Pressure.ToString(PressFormat, InvC) + ",'"); + values.Append(station.CompassPoint(station.Bearing) + "','"); + values.Append(Beaufort(station.WindAverage) + "','"); + values.Append(Units.WindText + "','"); + values.Append(Units.TempText[1].ToString() + "','"); + values.Append(Units.PressText + "','"); + values.Append(Units.RainText + "',"); + values.Append(station.WindRunToday.ToString(WindRunFormat, InvC) + ",'"); + values.Append((station.presstrendval > 0 ? '+' + station.presstrendval.ToString(PressFormat, InvC) : station.presstrendval.ToString(PressFormat, InvC)) + "',"); + values.Append(station.RainMonth.ToString(RainFormat, InvC) + ','); + values.Append(station.RainYear.ToString(RainFormat, InvC) + ','); + values.Append(station.RainYesterday.ToString(RainFormat, InvC) + ','); + values.Append(station.IndoorTemperature.ToString(TempFormat, InvC) + ','); + values.Append(station.IndoorHumidity.ToString() + ','); + values.Append(station.WindChill.ToString(TempFormat, InvC) + ','); + values.Append(station.temptrendval.ToString(TempTrendFormat, InvC) + ','); + values.Append(station.HiLoToday.HighTemp.ToString(TempFormat, InvC) + ",'"); + values.Append(station.HiLoToday.HighTempTime.ToString("HH:mm") + "',"); + values.Append(station.HiLoToday.LowTemp.ToString(TempFormat, InvC) + ",'"); + values.Append(station.HiLoToday.LowTempTime.ToString("HH:mm") + "',"); + values.Append(station.HiLoToday.HighWind.ToString(WindAvgFormat, InvC) + ",'"); + values.Append(station.HiLoToday.HighWindTime.ToString("HH:mm") + "',"); + values.Append(station.HiLoToday.HighGust.ToString(WindFormat, InvC) + ",'"); + values.Append(station.HiLoToday.HighGustTime.ToString("HH:mm") + "',"); + values.Append(station.HiLoToday.HighPress.ToString(PressFormat, InvC) + ",'"); + values.Append(station.HiLoToday.HighPressTime.ToString("HH:mm") + "',"); + values.Append(station.HiLoToday.LowPress.ToString(PressFormat, InvC) + ",'"); + values.Append(station.HiLoToday.LowPressTime.ToString("HH:mm") + "','"); + values.Append(Version + "','"); + values.Append(Build + "',"); + values.Append(station.RecentMaxGust.ToString(WindFormat, InvC) + ','); + values.Append(station.HeatIndex.ToString(TempFormat, InvC) + ','); + values.Append(station.Humidex.ToString(TempFormat, InvC) + ','); + values.Append(station.UV.ToString(UVFormat, InvC) + ','); + values.Append(station.ET.ToString(ETFormat, InvC) + ','); + values.Append(((int)station.SolarRad).ToString() + ','); + values.Append(station.AvgBearing.ToString() + ','); + values.Append(station.RainLastHour.ToString(RainFormat, InvC) + ','); + values.Append(station.Forecastnumber.ToString() + ",'"); + values.Append((IsDaylight() ? "1" : "0") + "','"); + values.Append((station.SensorContactLost ? "1" : "0") + "','"); + values.Append(station.CompassPoint(station.AvgBearing) + "',"); + values.Append((station.CloudBase).ToString() + ",'"); + values.Append((CloudBaseInFeet ? "ft" : "m") + "',"); + values.Append(station.ApparentTemperature.ToString(TempFormat, InvC) + ','); + values.Append(station.SunshineHours.ToString(SunFormat, InvC) + ','); + values.Append(((int)Math.Round(station.CurrentSolarMax)).ToString() + ",'"); + values.Append((station.IsSunny ? "1" : "0") + "',"); + values.Append(station.FeelsLike.ToString(TempFormat, InvC)); + values.Append(")"); + + string valuesString = values.ToString(); + List cmds = new List() { valuesString }; + + if (!string.IsNullOrEmpty(MySqlRealtimeRetention)) + { + cmds.Add($"DELETE IGNORE FROM {MySqlRealtimeTable} WHERE LogDateTime < DATE_SUB('{DateTime.Now:yyyy-MM-dd HH:mm:ss}', INTERVAL {MySqlRealtimeRetention});"); + } + + // do the update + _ = MySqlCommandAsync(cmds, RealtimeSqlConn, $"Realtime[{cycle}]", true, true); + } + + private void ProcessTemplateFile(string template, string outputfile, TokenParser parser) + { + string templatefile = AppDir + template; + if (File.Exists(templatefile)) + { + var utf8WithoutBom = new UTF8Encoding(false); + var encoding = UTF8encode ? utf8WithoutBom : Encoding.GetEncoding("iso-8859-1"); + parser.Encoding = encoding; + parser.SourceFile = template; + var output = parser.ToString(); + + try + { + using (StreamWriter file = new StreamWriter(outputfile, false, encoding)) + { + file.Write(output); + file.Close(); + } + } + catch (Exception e) + { + LogMessage($"ProcessTemplateFile: Error writing to file '{outputfile}', error was - {e}"); + } + } + } + + public void StartTimersAndSensors() + { + LogMessage("Start Extra Sensors"); + airLinkOut?.Start(); + airLinkIn?.Start(); + + LogMessage("Start Timers"); + // start the general one-minute timer + LogMessage("Starting 1-minute timer"); + station.StartMinuteTimer(); + LogMessage($"Data logging interval = {DataLogInterval} ({logints[DataLogInterval]} mins)"); + + + if (RealtimeEnabled) + { + if (RealtimeFTPEnabled) + { + LogConsoleMessage("Connecting real time FTP"); + if (Sslftp == FtpProtocols.SFTP) + { + RealtimeSSHLogin(RealtimeCycleCounter++); + } + else + { + RealtimeFTPLogin(RealtimeCycleCounter++); + } + } + + LogMessage("Starting Realtime timer, interval = " + RealtimeInterval / 1000 + " seconds"); + } + else + { + LogMessage("Realtime not enabled"); + } + + RealtimeTimer.Enabled = RealtimeEnabled; + + CustomMysqlSecondsTimer.Enabled = CustomMySqlSecondsEnabled; + + CustomHttpSecondsTimer.Enabled = CustomHttpSecondsEnabled; + + if (Wund.RapidFireEnabled) + { + WundTimer.Interval = 5000; // 5 seconds in rapid-fire mode + } + else + { + WundTimer.Interval = Wund.Interval * 60 * 1000; // mins to millisecs + } + + + AwekasTimer.Interval = AWEKAS.Interval * 1000; + + MQTTTimer.Interval = MQTT.IntervalTime * 1000; // secs to millisecs + + + // 15/10/20 What is doing? Nothing + /* + if (AirLinkInEnabled || AirLinkOutEnabled) + { + AirLinkTimer.Interval = 60 * 1000; // 1 minute + AirLinkTimer.Enabled = true; + AirLinkTimer.Elapsed += AirLinkTimerTick; + } + */ + + if (MQTT.EnableInterval) + { + MQTTTimer.Enabled = true; + } + + if (WundList == null) + { + // we've already been through here + // do nothing + LogDebugMessage("Wundlist is null"); + } + else if (WundList.Count == 0) + { + // No archived entries to upload + WundList = null; + LogDebugMessage("Wundlist count is zero"); + WundTimer.Enabled = Wund.Enabled && !Wund.SynchronisedUpdate; + } + else + { + // start the archive upload thread + Wund.CatchingUp = true; + WundCatchup(); + } + + if (WindyList == null) + { + // we've already been through here + // do nothing + LogDebugMessage("Windylist is null"); + } + else if (WindyList.Count == 0) + { + // No archived entries to upload + WindyList = null; + LogDebugMessage("Windylist count is zero"); + } + else + { + // start the archive upload thread + Windy.CatchingUp = true; + WindyCatchUp(); + } + + if (PWSList == null) + { + // we've already been through here + // do nothing + } + else if (PWSList.Count == 0) + { + // No archived entries to upload + PWSList = null; + } + else + { + // start the archive upload thread + PWS.CatchingUp = true; + PWSCatchUp(); + } + + if (WOWList == null) + { + // we've already been through here + // do nothing + } + else if (WOWList.Count == 0) + { + // No archived entries to upload + WOWList = null; + } + else + { + // start the archive upload thread + WOW.CatchingUp = true; + WOWCatchUp(); + } + + if (OWMList == null) + { + // we've already been through here + // do nothing + } + else if (OWMList.Count == 0) + { + // No archived entries to upload + OWMList = null; + } + else + { + // start the archive upload thread + OpenWeatherMap.CatchingUp = true; + OpenWeatherMapCatchUp(); + } + + if (MySqlList == null) + { + // we've already been through here + // do nothing + LogDebugMessage("MySqlList is null"); + } + else if (MySqlList.Count == 0) + { + // No archived entries to upload + MySqlList = null; + LogDebugMessage("MySqlList count is zero"); + } + else + { + // start the archive upload thread + LogMessage("Starting MySQL catchup thread"); + MySqlCatchupThread = new Thread(MySqlCatchup) {IsBackground = true}; + MySqlCatchupThread.Start(); + } + + WebTimer.Interval = UpdateInterval * 60 * 1000; // mins to millisecs + WebTimer.Enabled = WebIntervalEnabled && !SynchronisedWebUpdate; + + AwekasTimer.Enabled = AWEKAS.Enabled && !AWEKAS.SynchronisedUpdate; + + EnableOpenWeatherMap(); + + LogMessage("Normal running"); + LogConsoleMessage("Normal running"); + } + + private void CustomMysqlSecondsTimerTick(object sender, ElapsedEventArgs e) + { + if (station.DataStopped) + { + // No data coming in, do not do anything + return; + } + + _ = CustomMysqlSecondsWork(); + } + + private async Task CustomMysqlSecondsWork() + { + if (station.DataStopped) + { + // No data coming in, do not do anything + return; + } + + if (!customMySqlSecondsUpdateInProgress) + { + customMySqlSecondsUpdateInProgress = true; + + customMysqlSecondsTokenParser.InputText = CustomMySqlSecondsCommandString; + CustomMysqlSecondsCommand.CommandText = customMysqlSecondsTokenParser.ToStringFromString(); + + await MySqlCommandAsync(CustomMysqlSecondsCommand.CommandText, CustomMysqlSecondsConn, "CustomSqlSecs", true, true); + + customMySqlSecondsUpdateInProgress = false; + } + } + + + internal async Task CustomMysqlMinutesTimerTick() + { + if (station.DataStopped) + { + // No data coming in, do not do anything + return; + } + + if (!customMySqlMinutesUpdateInProgress) + { + customMySqlMinutesUpdateInProgress = true; + + customMysqlMinutesTokenParser.InputText = CustomMySqlMinutesCommandString; + CustomMysqlMinutesCommand.CommandText = customMysqlMinutesTokenParser.ToStringFromString(); + + await MySqlCommandAsync(CustomMysqlMinutesCommand.CommandText, CustomMysqlMinutesConn, "CustomSqlMins", true, true); + + customMySqlMinutesUpdateInProgress = false; + } + } + + internal async Task CustomMysqlRolloverTimerTick() + { + if (station.DataStopped) + { + // No data coming in, do not do anything + return; + } + + if (!customMySqlRolloverUpdateInProgress) + { + customMySqlRolloverUpdateInProgress = true; + + customMysqlRolloverTokenParser.InputText = CustomMySqlRolloverCommandString; + CustomMysqlRolloverCommand.CommandText = customMysqlRolloverTokenParser.ToStringFromString(); + + await MySqlCommandAsync(CustomMysqlRolloverCommand.CommandText, CustomMysqlRolloverConn, "CustomSqlRollover", true, true); + + customMySqlRolloverUpdateInProgress = false; + } + } + + public void DoExtraEndOfDayFiles() + { + int i; + + // handle any extra files that only require EOD processing + for (i = 0; i < numextrafiles; i++) + { + if (ExtraFiles[i].endofday) + { + var uploadfile = ExtraFiles[i].local; + var remotefile = ExtraFiles[i].remote; + + if ((uploadfile.Length > 0) && (remotefile.Length > 0)) + { + // For EOD files, we want the previous days log files since it is now just past the day rollover time. Makes a difference on month rollover + var logDay = DateTime.Now.AddDays(-1); + + uploadfile = GetUploadFilename(uploadfile, logDay); + + if (File.Exists(uploadfile)) + { + remotefile = GetRemoteFileName(remotefile, logDay); + + if (ExtraFiles[i].process) + { + LogDebugMessage("EOD: Processing extra file " + uploadfile); + // process the file + var utf8WithoutBom = new UTF8Encoding(false); + var encoding = UTF8encode ? utf8WithoutBom : Encoding.GetEncoding("iso-8859-1"); + tokenParser.Encoding = encoding; + tokenParser.SourceFile = uploadfile; + var output = tokenParser.ToString(); + uploadfile += "tmp"; + try + { + using (StreamWriter file = new StreamWriter(uploadfile, false, encoding)) + { + file.Write(output); + + file.Close(); + } + } + catch (Exception ex) + { + LogDebugMessage("EOD: Error writing file " + uploadfile); + LogDebugMessage(ex.Message); + } + //LogDebugMessage("Finished processing extra file " + uploadfile); + } + + if (ExtraFiles[i].FTP) + { + // FTP the file at the next interval + EODfilesNeedFTP = true; + } + else + { + // just copy the file + LogDebugMessage($"EOD: Copying extra file {uploadfile} to {remotefile}"); + try + { + File.Copy(uploadfile, remotefile, true); + } + catch (Exception ex) + { + LogDebugMessage("EOD: Error copying extra file: " + ex.Message); + } + //LogDebugMessage("Finished copying extra file " + uploadfile); + } + } + } + } + } + } + + private void MySqlCatchup() + { + try + { + var mySqlConn = new MySqlConnection(MySqlConnSettings.ToString()); + _= MySqlCommandAsync(MySqlList, mySqlConn, "MySQL Archive", true, true, true); + } + catch (Exception ex) + { + LogMessage("MySQL Archive: Error encountered during catchup MySQL operation."); + LogMessage(ex.Message); + } + } + + public void RealtimeFTPDisconnect() + { + try + { + if (Sslftp == FtpProtocols.SFTP && RealtimeSSH != null) + { + RealtimeSSH.Disconnect(); + } + else if (RealtimeFTP != null) + { + RealtimeFTP.Disconnect(); + } + LogDebugMessage("Disconnected Realtime FTP session"); + } + catch { } + } + + private void RealtimeFTPLogin(uint cycle) + { + //RealtimeTimer.Enabled = false; + RealtimeFTP.Host = FtpHostname; + RealtimeFTP.Port = FtpHostPort; + RealtimeFTP.Credentials = new NetworkCredential(FtpUsername, FtpPassword); + + if (Sslftp == FtpProtocols.FTPS) + { + RealtimeFTP.EncryptionMode = DisableFtpsExplicit ? FtpEncryptionMode.Implicit : FtpEncryptionMode.Explicit; + RealtimeFTP.DataConnectionEncryption = true; + RealtimeFTP.ValidateAnyCertificate = true; + // b3045 - switch from System.Net.Ftp.Client to FluentFTP allows us to specifiy protocols + RealtimeFTP.SslProtocols = SslProtocols.Default | SslProtocols.Tls11 | SslProtocols.Tls12; + LogDebugMessage($"FTP[{cycle}]: Using FTPS protocol"); + } + + if (ActiveFTPMode) + { + RealtimeFTP.DataConnectionType = FtpDataConnectionType.PORT; + LogDebugMessage($"FTP[{cycle}]: Using Active FTP mode"); + } + else if (DisableFtpsEPSV) + { + RealtimeFTP.DataConnectionType = FtpDataConnectionType.PASV; + LogDebugMessage($"FTP[{cycle}]: Disabling EPSV mode"); + } + + if (FtpHostname.Length > 0 && FtpHostname.Length > 0) + { + LogMessage($"FTP[{ cycle}]: Attempting realtime FTP connect to host {FtpHostname} on port {FtpHostPort}"); + try + { + RealtimeFTP.Connect(); + LogMessage($"FTP[{cycle}]: Realtime FTP connected"); + RealtimeFTP.SocketKeepAlive = true; + } + catch (Exception ex) + { + LogMessage($"FTP[{cycle}]: Error connecting ftp - " + ex.Message); + RealtimeFTP.Disconnect(); + } + + RealtimeFTP.EnableThreadSafeDataConnections = false; // use same connection for all transfers + } + //RealtimeTimer.Enabled = true; + } + + private void RealtimeSSHLogin(uint cycle) + { + if (FtpHostname != "" && FtpHostname != " ") + { + LogMessage($"SFTP[{cycle}]: Attempting realtime SFTP connect to host {FtpHostname} on port {FtpHostPort}"); + try + { + // BUILD 3092 - added alternate SFTP authentication options + ConnectionInfo connectionInfo; + PrivateKeyFile pskFile; + if (SshftpAuthentication == "password") + { + connectionInfo = new ConnectionInfo(FtpHostname, FtpHostPort, FtpUsername, new PasswordAuthenticationMethod(FtpUsername, FtpPassword)); + LogDebugMessage($"SFTP[{cycle}]: Connecting using password authentication"); + } + else if (SshftpAuthentication == "psk") + { + pskFile = new PrivateKeyFile(SshftpPskFile); + connectionInfo = new ConnectionInfo(FtpHostname, FtpHostPort, FtpUsername, new PrivateKeyAuthenticationMethod(FtpUsername, pskFile)); + LogDebugMessage($"SFTP[{cycle}]: Connecting using PSK authentication"); + } + else if (SshftpAuthentication == "password_psk") + { + pskFile = new PrivateKeyFile(SshftpPskFile); + connectionInfo = new ConnectionInfo(FtpHostname, FtpHostPort, FtpUsername, new PasswordAuthenticationMethod(FtpUsername, FtpPassword), new PrivateKeyAuthenticationMethod(FtpUsername, pskFile)); + LogDebugMessage($"SFTP[{cycle}]: Connecting using password or PSK authentication"); + } + else + { + LogMessage($"SFTP[{cycle}]: Invalid SshftpAuthentication specified [{SshftpAuthentication}]"); + return; + } + + RealtimeSSH = new SftpClient(connectionInfo); + + //if (RealtimeSSH != null) RealtimeSSH.Dispose(); + //RealtimeSSH = new SftpClient(ftp_host, ftp_port, ftp_user, ftp_password); + + RealtimeSSH.Connect(); + RealtimeSSH.ConnectionInfo.Timeout = TimeSpan.FromSeconds(15); // 15 seconds to match FTP default timeout + LogMessage($"SFTP[{cycle}]: Realtime SFTP connected"); + } + catch (Exception ex) + { + LogMessage($"SFTP[{cycle}]: Error connecting sftp - {ex.Message}"); + } + } + } + + /// + /// Process the list of WU updates created at startup from logger entries + /// + private async void WundCatchup() + { + Wund.Updating = true; + for (int i = 0; i < WundList.Count; i++) + { + LogMessage("Uploading WU archive #" + (i + 1)); + try + { + HttpResponseMessage response = await WUhttpClient.GetAsync(WundList[i]); + LogMessage("WU Response: " + response.StatusCode + ": " + response.ReasonPhrase); + } + catch (Exception ex) + { + LogMessage("WU update: " + ex.Message); + } + } + + LogMessage("End of WU archive upload"); + WundList.Clear(); + Wund.CatchingUp = false; + WundTimer.Enabled = Wund.Enabled && !Wund.SynchronisedUpdate; + Wund.Updating = false; + } + + private async void WindyCatchUp() + { + Windy.Updating = true; + for (int i = 0; i < WindyList.Count; i++) + { + LogMessage("Uploading Windy archive #" + (i + 1)); + try + { + HttpResponseMessage response = await WindyhttpClient.GetAsync(WindyList[i]); + LogMessage("Windy Response: " + response.StatusCode + ": " + response.ReasonPhrase); + } + catch (Exception ex) + { + LogMessage("Windy update: " + ex.Message); + } + } + + LogMessage("End of Windy archive upload"); + WindyList.Clear(); + Windy.CatchingUp = false; + Windy.Updating = false; + } + + /// + /// Process the list of PWS Weather updates created at startup from logger entries + /// + private async void PWSCatchUp() + { + PWS.Updating = true; + + for (int i = 0; i < PWSList.Count; i++) + { + LogMessage("Uploading PWS archive #" + (i + 1)); + try + { + HttpResponseMessage response = await PWShttpClient.GetAsync(PWSList[i]); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + LogMessage("PWS Response: " + response.StatusCode + ": " + responseBodyAsText); + } + catch (Exception ex) + { + LogMessage("PWS update: " + ex.Message); + } + } + + LogMessage("End of PWS archive upload"); + PWSList.Clear(); + PWS.CatchingUp = false; + PWS.Updating = false; + } + + /// + /// Process the list of WOW updates created at startup from logger entries + /// + private async void WOWCatchUp() + { + WOW.Updating = true; + + for (int i = 0; i < WOWList.Count; i++) + { + LogMessage("Uploading WOW archive #" + (i + 1)); + try + { + HttpResponseMessage response = await PWShttpClient.GetAsync(WOWList[i]); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + LogMessage("WOW Response: " + response.StatusCode + ": " + responseBodyAsText); + } + catch (Exception ex) + { + LogMessage("WOW update: " + ex.Message); + } + } + + LogMessage("End of WOW archive upload"); + WOWList.Clear(); + WOW.CatchingUp = false; + WOW.Updating = false; + } + + /// + /// Process the list of OpenWeatherMap updates created at startup from logger entries + /// + private async void OpenWeatherMapCatchUp() + { + OpenWeatherMap.Updating = true; + + string url = "http://api.openweathermap.org/data/3.0/measurements?appid=" + OpenWeatherMap.PW; + string logUrl = url.Replace(OpenWeatherMap.PW, ""); + + using (var client = new HttpClient()) + { + for (int i = 0; i < OWMList.Count; i++) + { + LogMessage("Uploading OpenWeatherMap archive #" + (i + 1)); + LogDebugMessage("OpenWeatherMap: URL = " + logUrl); + LogDataMessage("OpenWeatherMap: Body = " + OWMList[i]); + + try + { + var data = new StringContent(OWMList[i], Encoding.UTF8, "application/json"); + HttpResponseMessage response = await client.PostAsync(url, data); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + var status = response.StatusCode == HttpStatusCode.NoContent ? "OK" : "Error"; // Returns a 204 reponse for OK! + LogDebugMessage($"OpenWeatherMap: Response code = {status} - {response.StatusCode}"); + if (response.StatusCode != HttpStatusCode.NoContent) + LogDataMessage($"OpenWeatherMap: Response data = {responseBodyAsText}"); + } + catch (Exception ex) + { + LogMessage("OpenWeatherMap: Update error = " + ex.Message); + } + } + } + + LogMessage("End of OpenWeatherMap archive upload"); + OWMList.Clear(); + OpenWeatherMap.CatchingUp = false; + OpenWeatherMap.Updating = false; + } + + + public async void UpdatePWSweather(DateTime timestamp) + { + if (!PWS.Updating) + { + PWS.Updating = true; + + string pwstring; + string URL = station.GetPWSURL(out pwstring, timestamp); + + string starredpwstring = "&PASSWORD=" + new string('*', PWS.PW.Length); + + string LogURL = URL.Replace(pwstring, starredpwstring); + LogDebugMessage(LogURL); + + try + { + HttpResponseMessage response = await PWShttpClient.GetAsync(URL); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + if (response.StatusCode != HttpStatusCode.OK) + { + LogMessage($"PWS Response: ERROR - Response code = {response.StatusCode}, Body = {responseBodyAsText}"); + HttpUploadAlarm.LastError = $"PWS: HTTP Response code = {response.StatusCode}, Body = {responseBodyAsText}"; + HttpUploadAlarm.Triggered = true; + } + else + { + LogDebugMessage("PWS Response: " + response.StatusCode + ": " + responseBodyAsText); + HttpUploadAlarm.Triggered = false; + } + } + catch (Exception ex) + { + LogMessage("PWS update: " + ex.Message); + HttpUploadAlarm.LastError = "PWS: " + ex.Message; + HttpUploadAlarm.Triggered = true; + } + finally + { + PWS.Updating = false; + } + } + } + + public async void UpdateWOW(DateTime timestamp) + { + if (!WOW.Updating) + { + WOW.Updating = true; + + string pwstring; + string URL = station.GetWOWURL(out pwstring, timestamp); + + string starredpwstring = "&siteAuthenticationKey=" + new string('*', WOW.PW.Length); + + string LogURL = URL.Replace(pwstring, starredpwstring); + LogDebugMessage("WOW URL = " + LogURL); + + try + { + HttpResponseMessage response = await WOWhttpClient.GetAsync(URL); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + if (response.StatusCode != HttpStatusCode.OK) + { + LogMessage($"WOW Response: ERROR - Response code = {response.StatusCode}, body = {responseBodyAsText}"); + HttpUploadAlarm.LastError = $"WOW: HTTP response - Response code = {response.StatusCode}, body = {responseBodyAsText}"; + HttpUploadAlarm.Triggered = true; + } + else + { + LogDebugMessage("WOW Response: " + response.StatusCode + ": " + responseBodyAsText); + HttpUploadAlarm.Triggered = false; + } + } + catch (Exception ex) + { + LogMessage("WOW update: " + ex.Message); + HttpUploadAlarm.LastError = "WOW: " + ex.Message; + HttpUploadAlarm.Triggered = true; + } + finally + { + WOW.Updating = false; + } + } + } + + public async Task MySqlCommandAsync(string Cmd, MySqlConnection Connection, string CallingFunction, bool OpenConnection, bool CloseConnection) + { + var Cmds = new List() { Cmd }; + await MySqlCommandAsync(Cmds, Connection, CallingFunction, OpenConnection, CloseConnection, true); + } + + public async Task MySqlCommandAsync(List Cmds, MySqlConnection Connection, string CallingFunction, bool OpenConnection, bool CloseConnection, bool ClearCommands = false) + { + int errorCount = 10; + try + { + if (OpenConnection) + { + LogDebugMessage($"{CallingFunction}: Opening MySQL Connection"); + await Connection.OpenAsync(); + } + + for (var i = 0; i < Cmds.Count; i++) + { + try + { + using (MySqlCommand cmd = new MySqlCommand(Cmds[i], Connection)) + { + LogDebugMessage($"{CallingFunction}: MySQL executing - {Cmds[i]}"); + + int aff = await cmd.ExecuteNonQueryAsync(); + LogDebugMessage($"{CallingFunction}: MySQL {aff} rows were affected."); + } + + MySqlUploadAlarm.Triggered = false; + } + catch (Exception ex) + { + LogMessage($"{CallingFunction}: Error encountered during MySQL operation."); + LogMessage($"{CallingFunction}: SQL was - \"{Cmds[i]}\""); + LogMessage(ex.Message); + MySqlUploadAlarm.LastError = ex.Message; + MySqlUploadAlarm.Triggered = true; + if (--errorCount <=0) + { + LogMessage($"{CallingFunction}: Too many errors, aborting!"); + return; + } + } + } + } + catch (Exception ex) + { + LogMessage($"{CallingFunction}: Error encountered during MySQL operation."); + LogMessage(ex.Message); + MySqlUploadAlarm.LastError = ex.Message; + MySqlUploadAlarm.Triggered = true; + } + finally + { + if (CloseConnection) + { + try + { + Connection.Close(); + } + catch { } + } + } + } + + public void MySqlCommandSync(List Cmds, MySqlConnection Connection, string CallingFunction, bool OpenConnection, bool CloseConnection, bool ClearCommands=false) + { + try + { + if (OpenConnection) + { + LogDebugMessage($"{CallingFunction}: Opening MySQL Connection"); + Connection.Open(); + } + + for (var i = 0; i < Cmds.Count; i++) + { + try + { + using (MySqlCommand cmd = new MySqlCommand(Cmds[i], Connection)) + { + LogDebugMessage($"{CallingFunction}: MySQL executing[{i + 1}] - {Cmds[i]}"); + + int aff = cmd.ExecuteNonQuery(); + LogDebugMessage($"{CallingFunction}: MySQL {aff} rows were affected."); + } + + MySqlUploadAlarm.Triggered = false; + } + catch (Exception ex) + { + LogMessage($"{CallingFunction}: Error encountered during MySQL operation."); + LogMessage($"{CallingFunction}: SQL was - \"{Cmds[i]}\""); + LogMessage(ex.Message); + MySqlUploadAlarm.LastError = ex.Message; + MySqlUploadAlarm.Triggered = true; + } + } + + if (CloseConnection) + { + try + { + Connection.Close(); + } + catch + { } + } + } + catch (Exception e) + { + LogMessage($"{CallingFunction}: Error opening MySQL Connection"); + LogMessage(e.Message); + MySqlUploadAlarm.LastError = e.Message; + MySqlUploadAlarm.Triggered = true; + } + + if (ClearCommands) + { + Cmds.Clear(); + } + } + + public async void GetLatestVersion() + { + var http = new HttpClient(); + ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; + try + { + var retVal = await http.GetAsync("https://github.com/cumulusmx/CumulusMX/releases/latest"); + var latestUri = retVal.RequestMessage.RequestUri.AbsolutePath; + LatestBuild = new string(latestUri.Split('/').Last().Where(char.IsDigit).ToArray()); + if (int.Parse(Build) < int.Parse(LatestBuild)) + { + var msg = $"You are not running the latest version of Cumulus MX, build {LatestBuild} is available."; + LogConsoleMessage(msg); + LogMessage(msg); + UpgradeAlarm.LastError = $"Build {LatestBuild} is available"; + UpgradeAlarm.Triggered = true; + } + else if (int.Parse(Build) == int.Parse(LatestBuild)) + { + LogMessage("This Cumulus MX instance is running the latest version"); + UpgradeAlarm.Triggered = false; + } + else + { + LogMessage($"Could not determine if you are running the latest Cumulus MX build or not. This build = {Build}, latest build = {LatestBuild}"); + } + } + catch (Exception ex) + { + LogMessage("Failed to get the latest build version from Github: " + ex.Message); + } + } + + public async void CustomHttpSecondsUpdate() + { + if (!updatingCustomHttpSeconds) + { + updatingCustomHttpSeconds = true; + + try + { + customHttpSecondsTokenParser.InputText = CustomHttpSecondsString; + var processedString = customHttpSecondsTokenParser.ToStringFromString(); + LogDebugMessage("CustomHttpSeconds: Querying - " + processedString); + var response = await customHttpSecondsClient.GetAsync(processedString); + response.EnsureSuccessStatusCode(); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + LogDebugMessage("CustomHttpSeconds: Response - " + response.StatusCode); + LogDataMessage("CustomHttpSeconds: Response Text - " + responseBodyAsText); + } + catch (Exception ex) + { + LogDebugMessage("CustomHttpSeconds: " + ex.Message); + } + finally + { + updatingCustomHttpSeconds = false; + } + } + } + + public async void CustomHttpMinutesUpdate() + { + if (!updatingCustomHttpMinutes) + { + updatingCustomHttpMinutes = true; + + try + { + customHttpMinutesTokenParser.InputText = CustomHttpMinutesString; + var processedString = customHttpMinutesTokenParser.ToStringFromString(); + LogDebugMessage("CustomHttpMinutes: Querying - " + processedString); + var response = await customHttpMinutesClient.GetAsync(processedString); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + LogDebugMessage("CustomHttpMinutes: Response code - " + response.StatusCode); + LogDataMessage("CustomHttpMinutes: Response text - " + responseBodyAsText); + } + catch (Exception ex) + { + LogDebugMessage("CustomHttpMinutes: " + ex.Message); + } + finally + { + updatingCustomHttpMinutes = false; + } + } + } + + public async void CustomHttpRolloverUpdate() + { + if (!updatingCustomHttpRollover) + { + updatingCustomHttpRollover = true; + + try + { + customHttpRolloverTokenParser.InputText = CustomHttpRolloverString; + var processedString = customHttpRolloverTokenParser.ToStringFromString(); + LogDebugMessage("CustomHttpRollover: Querying - " + processedString); + var response = await customHttpRolloverClient.GetAsync(processedString); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + LogDebugMessage("CustomHttpRollover: Response code - " + response.StatusCode); + LogDataMessage("CustomHttpRollover: Response text - " + responseBodyAsText); + } + catch (Exception ex) + { + LogDebugMessage("CustomHttpRollover: " + ex.Message); + } + finally + { + updatingCustomHttpRollover = false; + } + } + } + + public void DegToDMS(double degrees, out int d, out int m, out int s) + { + int secs = (int)(degrees * 60 * 60); + + s = secs % 60; + + secs = (secs - s) / 60; + + m = secs % 60; + d = secs / 60; + } + + public void AddToWebServiceLists(DateTime timestamp) + { + AddToWundList(timestamp); + AddToWindyList(timestamp); + AddToPWSList(timestamp); + AddToWOWList(timestamp); + AddToOpenWeatherMapList(timestamp); + } + + /// + /// Add an archive entry to the WU 'catchup' list for sending to WU + /// + /// + private void AddToWundList(DateTime timestamp) + { + if (Wund.Enabled && Wund.CatchUp) + { + string pwstring; + string URL = station.GetWundergroundURL(out pwstring, timestamp, true); + + WundList.Add(URL); + + string starredpwstring = "&PASSWORD=" + new string('*', Wund.PW.Length); + + string LogURL = URL.Replace(pwstring, starredpwstring); + + LogMessage("Creating WU URL #" + WundList.Count); + + LogMessage(LogURL); + } + } + + private void AddToWindyList(DateTime timestamp) + { + if (Windy.Enabled && Windy.CatchUp) + { + string apistring; + string URL = station.GetWindyURL(out apistring, timestamp); + + WindyList.Add(URL); + + string LogURL = URL.Replace(apistring, "<>"); + + LogMessage("Creating Windy URL #" + WindyList.Count); + + LogMessage(LogURL); + } + } + + private void AddToPWSList(DateTime timestamp) + { + if (PWS.Enabled && PWS.CatchUp) + { + string pwstring; + string URL = station.GetPWSURL(out pwstring, timestamp); + + PWSList.Add(URL); + + string starredpwstring = "&PASSWORD=" + new string('*', PWS.PW.Length); + + string LogURL = URL.Replace(pwstring, starredpwstring); + + LogMessage("Creating PWS URL #" + PWSList.Count); + + LogMessage(LogURL); + } + } + + private void AddToWOWList(DateTime timestamp) + { + if (WOW.Enabled && WOW.CatchUp) + { + string pwstring; + string URL = station.GetWOWURL(out pwstring, timestamp); + + WOWList.Add(URL); + + string starredpwstring = "&siteAuthenticationKey=" + new string('*', WOW.PW.Length); + + string LogURL = URL.Replace(pwstring, starredpwstring); + + LogMessage("Creating WOW URL #" + WOWList.Count); + + LogMessage(LogURL); + } + } + + private void AddToOpenWeatherMapList(DateTime timestamp) + { + if (OpenWeatherMap.Enabled && OpenWeatherMap.CatchUp) + { + OWMList.Add(station.GetOpenWeatherMapData(timestamp)); + + LogMessage("Creating OpenWeatherMap data #" + OWMList.Count); + } + } + + private string GetUploadFilename(string input, DateTime dat) + { + if (input == "") + { + return GetLogFileName(dat); + } + else if (input == "") + { + return GetExtraLogFileName(dat); + } + else if (input == "") + { + NOAAReports noaa = new NOAAReports(this); + return noaa.GetLastNoaaYearReportFilename(dat, true); + } + else if (input == "") + { + NOAAReports noaa = new NOAAReports(this); + return noaa.GetLastNoaaMonthReportFilename(dat, true); + } + + return input; + } + + private string GetRemoteFileName(string input, DateTime dat) + { + if (input.Contains("")) + { + return input.Replace("", Path.GetFileName(GetLogFileName(dat))); + } + else if (input.Contains("")) + { + return input.Replace("", Path.GetFileName(GetExtraLogFileName(dat))); + } + else if (input.Contains("")) + { + return input.Replace("", Path.GetFileName(GetAirLinkLogFileName(dat))); + } + else if (input.Contains("")) + { + NOAAReports noaa = new NOAAReports(this); + return input.Replace("", Path.GetFileName(noaa.GetLastNoaaYearReportFilename(dat, false))); + } + else if (input.Contains("")) + { + NOAAReports noaa = new NOAAReports(this); + return input.Replace("", Path.GetFileName(noaa.GetLastNoaaMonthReportFilename(dat, false))); + } + + return input; + } + + public void SetMonthlySqlCreateString() + { + StringBuilder strb = new StringBuilder("CREATE TABLE " + MySqlMonthlyTable + " (", 1500); + strb.Append("LogDateTime DATETIME NOT NULL,"); + strb.Append("Temp decimal(4," + TempDPlaces + ") NOT NULL,"); + strb.Append("Humidity decimal(4," + HumDPlaces + ") NOT NULL,"); + strb.Append("Dewpoint decimal(4," + TempDPlaces + ") NOT NULL,"); + strb.Append("Windspeed decimal(4," + WindAvgDPlaces + ") NOT NULL,"); + strb.Append("Windgust decimal(4," + WindDPlaces + ") NOT NULL,"); + strb.Append("Windbearing VARCHAR(3) NOT NULL,"); + strb.Append("RainRate decimal(4," + RainDPlaces + ") NOT NULL,"); + strb.Append("TodayRainSoFar decimal(4," + RainDPlaces + ") NOT NULL,"); + strb.Append("Pressure decimal(6," + PressDPlaces + ") NOT NULL,"); + strb.Append("Raincounter decimal(6," + RainDPlaces + ") NOT NULL,"); + strb.Append("InsideTemp decimal(4," + TempDPlaces + ") NOT NULL,"); + strb.Append("InsideHumidity decimal(4," + HumDPlaces + ") NOT NULL,"); + strb.Append("LatestWindGust decimal(5," + WindDPlaces + ") NOT NULL,"); + strb.Append("WindChill decimal(4," + TempDPlaces + ") NOT NULL,"); + strb.Append("HeatIndex decimal(4," + TempDPlaces + ") NOT NULL,"); + strb.Append("UVindex decimal(4," + UVDPlaces + "),"); + strb.Append("SolarRad decimal(5,1),"); + strb.Append("Evapotrans decimal(4," + RainDPlaces + "),"); + strb.Append("AnnualEvapTran decimal(5," + RainDPlaces + "),"); + strb.Append("ApparentTemp decimal(4," + TempDPlaces + "),"); + strb.Append("MaxSolarRad decimal(5,1),"); + strb.Append("HrsSunShine decimal(3," + SunshineDPlaces + "),"); + strb.Append("CurrWindBearing varchar(3),"); + strb.Append("RG11rain decimal(4," + RainDPlaces + "),"); + strb.Append("RainSinceMidnight decimal(4," + RainDPlaces + "),"); + strb.Append("WindbearingSym varchar(3),"); + strb.Append("CurrWindBearingSym varchar(3),"); + strb.Append("FeelsLike decimal(4," + TempDPlaces + "),"); + strb.Append("Humidex decimal(4," + TempDPlaces + "),"); + strb.Append("PRIMARY KEY (LogDateTime)) COMMENT = \"Monthly logs from Cumulus\""); + CreateMonthlySQL = strb.ToString(); + } + + internal void SetDayfileSqlCreateString() + { + StringBuilder strb = new StringBuilder("CREATE TABLE " + MySqlDayfileTable + " (", 2048); + strb.Append("LogDate date NOT NULL ,"); + strb.Append("HighWindGust decimal(4," + WindDPlaces + ") NOT NULL,"); + strb.Append("HWindGBear varchar(3) NOT NULL,"); + strb.Append("THWindG varchar(5) NOT NULL,"); + strb.Append("MinTemp decimal(5," + TempDPlaces + ") NOT NULL,"); + strb.Append("TMinTemp varchar(5) NOT NULL,"); + strb.Append("MaxTemp decimal(5," + TempDPlaces + ") NOT NULL,"); + strb.Append("TMaxTemp varchar(5) NOT NULL,"); + strb.Append("MinPress decimal(6," + PressDPlaces + ") NOT NULL,"); + strb.Append("TMinPress varchar(5) NOT NULL,"); + strb.Append("MaxPress decimal(6," + PressDPlaces + ") NOT NULL,"); + strb.Append("TMaxPress varchar(5) NOT NULL,"); + strb.Append("MaxRainRate decimal(4," + RainDPlaces + ") NOT NULL,"); + strb.Append("TMaxRR varchar(5) NOT NULL,TotRainFall decimal(6," + RainDPlaces + ") NOT NULL,"); + strb.Append("AvgTemp decimal(4," + TempDPlaces + ") NOT NULL,"); + strb.Append("TotWindRun decimal(5," + WindRunDPlaces +") NOT NULL,"); + strb.Append("HighAvgWSpeed decimal(3," + WindAvgDPlaces + "),"); + strb.Append("THAvgWSpeed varchar(5),LowHum decimal(4," + HumDPlaces + "),"); + strb.Append("TLowHum varchar(5),"); + strb.Append("HighHum decimal(4," + HumDPlaces + "),"); + strb.Append("THighHum varchar(5),TotalEvap decimal(5," + RainDPlaces + "),"); + strb.Append("HoursSun decimal(3," + SunshineDPlaces + "),"); + strb.Append("HighHeatInd decimal(4," + TempDPlaces + "),"); + strb.Append("THighHeatInd varchar(5),"); + strb.Append("HighAppTemp decimal(4," + TempDPlaces + "),"); + strb.Append("THighAppTemp varchar(5),"); + strb.Append("LowAppTemp decimal(4," + TempDPlaces + "),"); + strb.Append("TLowAppTemp varchar(5),"); + strb.Append("HighHourRain decimal(4," + RainDPlaces + "),"); + strb.Append("THighHourRain varchar(5),"); + strb.Append("LowWindChill decimal(4," + TempDPlaces + "),"); + strb.Append("TLowWindChill varchar(5),"); + strb.Append("HighDewPoint decimal(4," + TempDPlaces + "),"); + strb.Append("THighDewPoint varchar(5),"); + strb.Append("LowDewPoint decimal(4," + TempDPlaces + "),"); + strb.Append("TLowDewPoint varchar(5),"); + strb.Append("DomWindDir varchar(3),"); + strb.Append("HeatDegDays decimal(4,1),"); + strb.Append("CoolDegDays decimal(4,1),"); + strb.Append("HighSolarRad decimal(5,1),"); + strb.Append("THighSolarRad varchar(5),"); + strb.Append("HighUV decimal(3," + UVDPlaces + "),"); + strb.Append("THighUV varchar(5),"); + strb.Append("HWindGBearSym varchar(3),"); + strb.Append("DomWindDirSym varchar(3),"); + strb.Append("MaxFeelsLike decimal(5," + TempDPlaces + "),"); + strb.Append("TMaxFeelsLike varchar(5),"); + strb.Append("MinFeelsLike decimal(5," + TempDPlaces + "),"); + strb.Append("TMinFeelsLike varchar(5),"); + strb.Append("MaxHumidex decimal(5," + TempDPlaces + "),"); + strb.Append("TMaxHumidex varchar(5),"); + //strb.Append("MinHumidex decimal(5," + TempDPlaces + "),"); + //strb.Append("TMinHumidex varchar(5),"); + strb.Append("PRIMARY KEY(LogDate)) COMMENT = \"Dayfile from Cumulus\""); + CreateDayfileSQL = strb.ToString(); + } + + public void LogOffsetsMultipliers() + { + LogMessage("Offsets and Multipliers:"); + LogMessage($"PO={Calib.Press.Offset:F3} TO={Calib.Temp.Offset:F3} HO={Calib.Hum.Offset} WDO={Calib.WindDir.Offset} ITO={Calib.InTemp.Offset:F3} SO={Calib.Solar.Offset:F3} UVO={Calib.UV.Offset:F3}"); + LogMessage($"PM={Calib.Press.Mult:F3} WSM={Calib.WindSpeed.Mult:F3} WGM={Calib.WindGust.Mult:F3} TM={Calib.Temp.Mult:F3} TM2={Calib.Temp.Mult2:F3} " + + $"HM={Calib.Hum.Mult:F3} HM2={Calib.Hum.Mult2:F3} RM={Calib.Rain.Mult:F3} SM={Calib.Solar.Mult:F3} UVM={Calib.UV.Mult:F3}"); + LogMessage("Spike removal:"); + LogMessage($"TD={Spike.TempDiff:F3} GD={Spike.GustDiff:F3} WD={Spike.WindDiff:F3} HD={Spike.HumidityDiff:F3} PD={Spike.PressDiff:F3} MR={Spike.MaxRainRate:F3} MH={Spike.MaxHourlyRain:F3}"); + LogMessage("Limits:"); + LogMessage($"TH={Limit.TempHigh.ToString(TempFormat)} TL={Limit.TempLow.ToString(TempFormat)} DH={Limit.DewHigh.ToString(TempFormat)} PH={Limit.PressHigh.ToString(PressFormat)} PL={Limit.PressLow.ToString(PressFormat)} GH={Limit.WindHigh:F3}"); + + } + + private void LogPrimaryAqSensor() + { + switch (StationOptions.PrimaryAqSensor) + { + case (int)PrimaryAqSensor.Undefined: + LogMessage("Primary AQ Sensor = Undefined"); + break; + case (int)PrimaryAqSensor.Ecowitt1: + case (int)PrimaryAqSensor.Ecowitt2: + case (int)PrimaryAqSensor.Ecowitt3: + case (int)PrimaryAqSensor.Ecowitt4: + LogMessage("Primary AQ Sensor = Ecowitt" + StationOptions.PrimaryAqSensor); + break; + case (int)PrimaryAqSensor.EcowittCO2: + LogMessage("Primary AQ Sensor = Ecowitt CO2"); + break; + case (int)PrimaryAqSensor.AirLinkIndoor: + LogMessage("Primary AQ Sensor = Airlink Indoor"); + break; + case (int)PrimaryAqSensor.AirLinkOutdoor: + LogMessage("Primary AQ Sensor = Airlink Outdoor"); + break; + } + } + } + + /* + internal class Raintotaldata + { + public DateTime timestamp; + public double raintotal; + + public Raintotaldata(DateTime ts, double rain) + { + timestamp = ts; + raintotal = rain; + } + } + */ + + public static class StationTypes + { + public const int Undefined = -1; + public const int VantagePro = 0; + public const int VantagePro2 = 1; + public const int WMR928 = 2; + public const int WM918 = 3; + public const int EasyWeather = 4; + public const int FineOffset = 5; + public const int WS2300 = 6; + public const int FineOffsetSolar = 7; + public const int WMR100 = 8; + public const int WMR200 = 9; + public const int Instromet = 10; + public const int WLL = 11; + public const int GW1000 = 12; + } + + /* + public static class AirQualityIndex + { + public const int US_EPA = 0; + public const int UK_COMEAP = 1; + public const int EU_AQI = 2; + public const int CANADA_AQHI = 3; + public const int EU_CAQI = 4; + } + */ + + /* + public static class DoubleExtensions + { + public static string ToUKString(this double value) + { + return value.ToString(CultureInfo.GetCultureInfo("en-GB")); + } + } + */ + + public class DiaryData + { + [PrimaryKey] + public DateTime Timestamp { get; set; } + public string entry { get; set; } + public int snowFalling { get; set; } + public int snowLying { get; set; } + public double snowDepth { get; set; } + } + + public class ProgramOptionsClass + { + public bool EnableAccessibility { get; set; } + public string StartupPingHost { get; set; } + public int StartupPingEscapeTime { get; set; } + public int StartupDelaySecs { get; set; } + public int StartupDelayMaxUptime { get; set; } + public bool DebugLogging { get; set; } + public bool DataLogging { get; set; } + public bool WarnMultiple { get; set; } + } + + public class StationUnits + { + public int Wind { get; set; } + public int Press { get; set; } + public int Rain { get; set; } + public int Temp { get; set; } + + public string WindText { get; set; } + public string PressText { get; set; } + public string RainText { get; set; } + public string TempText { get; set; } + + public string TempTrendText { get; set; } + public string RainTrendText { get; set; } + public string PressTrendText { get; set; } + public string WindRunText { get; set; } + public string AirQualityUnitText { get; set; } + public string SoilMoistureUnitText { get; set; } + public string CO2UnitText { get; set; } + + public StationUnits() + { + AirQualityUnitText = "µg/m³"; + SoilMoistureUnitText = "cb"; + CO2UnitText = "ppm"; + } + } + + public class StationOptions + { + public bool UseZeroBearing { get; set; } + public bool UseWind10MinAve { get; set; } + public bool UseSpeedForAvgCalc { get; set; } + public bool Humidity98Fix { get; set; } + public bool CalculatedDP { get; set; } + public bool CalculatedWC { get; set; } + public bool SyncTime { get; set; } + public int ClockSettingHour { get; set; } + public bool UseCumulusPresstrendstr { get; set; } + public bool LogExtraSensors { get; set; } + public bool WS2300IgnoreStationClock { get; set; } + public bool RoundWindSpeed { get; set; } + public int PrimaryAqSensor { get; set; } + public bool NoSensorCheck { get; set; } + public int AvgBearingMinutes { get; set; } + public int AvgSpeedMinutes { get; set; } + public int PeakGustMinutes { get; set; } + } + + public class FileGenerationFtpOptions + { + public string TemplateFileName { get; set; } + public string LocalFileName { get; set; } + public string LocalPath { get; set; } + public string RemoteFileName { get; set; } + public bool Create { get; set; } + public bool FTP { get; set; } + public bool FtpRequired { get; set; } + public bool CreateRequired { get; set; } + public FileGenerationFtpOptions() + { + CreateRequired = true; + FtpRequired = true; + } + } + + public class DavisOptions + { + public bool ForceVPBarUpdate { get; set; } + public bool ReadReceptionStats { get; set; } + public bool SetLoggerInterval { get; set; } + public bool UseLoop2 { get; set; } + public int InitWaitTime { get; set; } + public int IPResponseTime { get; set; } + public int ReadTimeout { get; set; } + public bool IncrementPressureDP { get; set; } + public int BaudRate { get; set; } + public int RainGaugeType { get; set; } + public int ConnectionType { get; set; } + public int TCPPort { get; set; } + public string IPAddr { get; set; } + public int PeriodicDisconnectInterval { get; set; } + } + + public class FineOffsetOptions + { + public bool SyncReads { get; set; } + public int ReadAvoidPeriod { get; set; } + public int ReadTime { get; set; } + public bool SetLoggerInterval { get; set; } + public int VendorID { get; set; } + public int ProductID { get; set; } +} + + public class ImetOptions + { + public List BaudRates { get; set; } + public int BaudRate { get; set; } + public int WaitTime { get; set; } + public int ReadDelay { get; set; } + public bool UpdateLogPointer { get; set; } + } + + public class EasyWeatherOptions + { + public double Interval { get; set; } + public string Filename { get; set; } + public int MinPressMB { get; set; } + public int MaxPressMB { get; set; } + public int MaxRainTipDiff { get; set; } + public double PressOffset { get; set; } + } + + public class GraphOptions + { + public bool TempVisible { get; set; } + public bool InTempVisible { get; set; } + public bool HIVisible { get; set; } + public bool DPVisible { get; set; } + public bool WCVisible { get; set; } + public bool AppTempVisible { get; set; } + public bool FeelsLikeVisible { get; set; } + public bool HumidexVisible { get; set; } + public bool InHumVisible { get; set; } + public bool OutHumVisible { get; set; } + public bool UVVisible { get; set; } + public bool SolarVisible { get; set; } + public bool SunshineVisible { get; set; } + public bool DailyMaxTempVisible { get; set; } + public bool DailyAvgTempVisible { get; set; } + public bool DailyMinTempVisible { get; set; } + public bool GrowingDegreeDaysVisible1 { get; set; } + public bool GrowingDegreeDaysVisible2 { get; set; } + public bool TempSumVisible0 { get; set; } + public bool TempSumVisible1 { get; set; } + public bool TempSumVisible2 { get; set; } + } + + public class SelectaChartOptions + { + public string[] series { get; set; } + public string[] colours { get; set; } + + public SelectaChartOptions() + { + series = new string[6]; + colours = new string[6]; + } + } + + public class AwekasResponse + { + public int status { get; set; } + public int authentication { get; set; } + public int minuploadtime { get; set; } + public AwekasErrors error { get; set; } + public AwekasDisabled disabled { get; set; } + } + + public class AwekasErrors + { + public int count { get; set; } + public int time { get; set; } + public int date { get; set; } + public int temp { get; set; } + public int hum { get; set; } + public int airp { get; set; } + public int rain { get; set; } + public int rainrate { get; set; } + public int wind { get; set; } + public int gust { get; set; } + public int snow { get; set; } + public int solar { get; set; } + public int uv { get; set; } + public int brightness { get; set; } + public int suntime { get; set; } + public int indoortemp { get; set; } + public int indoorhumidity { get; set; } + public int soilmoisture1 { get; set; } + public int soilmoisture2 { get; set; } + public int soilmoisture3 { get; set; } + public int soilmoisture4 { get; set; } + public int soiltemp1 { get; set; } + public int soiltemp2 { get; set; } + public int soiltemp3 { get; set; } + public int soiltemp4 { get; set; } + public int leafwetness1 { get; set; } + public int leafwetness2 { get; set; } + public int warning { get; set; } + } + + public class AwekasDisabled + { + public int temp { get; set; } + public int hum { get; set; } + public int airp { get; set; } + public int rain { get; set; } + public int rainrate { get; set; } + public int wind { get; set; } + public int snow { get; set; } + public int solar { get; set; } + public int uv { get; set; } + public int indoortemp { get; set; } + public int indoorhumidity { get; set; } + public int soilmoisture1 { get; set; } + public int soilmoisture2 { get; set; } + public int soilmoisture3 { get; set; } + public int soilmoisture4 { get; set; } + public int soiltemp1 { get; set; } + public int soiltemp2 { get; set; } + public int soiltemp3 { get; set; } + public int soiltemp4 { get; set; } + public int leafwetness1 { get; set; } + public int leafwetness2 { get; set; } + public int report { get; set; } + } + + public class OpenWeatherMapStation + { + public string id { get; set; } + public string created_at { get; set; } + public string updated_at { get; set; } + public string external_id { get; set; } + public string name { get; set; } + public double longitude { get; set; } + public double latitude { get; set; } + public int altitude { get; set; } + public int rank { get; set; } + } + + public class OpenWeatherMapNewStation + { + public string ID { get; set; } + public string created_at { get; set; } + public string updated_at { get; set; } + public string user_id { get; set; } + public string external_id { get; set; } + public string name { get; set; } + public double longitude { get; set; } + public double latitude { get; set; } + public int altitude { get; set; } + public int source_type { get; set; } + } + + public class WebUploadService + { + public string Server; + public int Port; + public string ID; + public string PW; + public bool Enabled; + public int Interval; + public int DefaultInterval; + public bool SynchronisedUpdate; + public bool SendUV; + public bool SendSolar; + public bool SendIndoor; + public bool SendAirQuality; + public bool CatchUp; + public bool CatchingUp; + public bool Updating; + } + + public class WebUploadTwitter : WebUploadService + { + public string OauthToken; + public string OauthTokenSecret; + public bool SendLocation; + } + + public class WebUploadWund : WebUploadService + { + public bool RapidFireEnabled; + public bool SendAverage; + public bool SendSoilTemp1; + public bool SendSoilTemp2; + public bool SendSoilTemp3; + public bool SendSoilTemp4; + public bool SendSoilMoisture1; + public bool SendSoilMoisture2; + public bool SendSoilMoisture3; + public bool SendSoilMoisture4; + public bool SendLeafWetness1; + public bool SendLeafWetness2; + } + + public class WebUploadWindy : WebUploadService + { + public string ApiKey; + public int StationIdx; + } + + public class WebUploadWindGuru : WebUploadService + { + public bool SendRain; + } + + public class WebUploadAwekas : WebUploadService + { + public bool RateLimited; + public int OriginalInterval; + public string Lang; + public bool SendSoilTemp; + public bool SendSoilMoisture; + public bool SendLeafWetness; + } + + public class WebUploadWCloud : WebUploadService + { + public bool SendSoilMoisture; + public int SoilMoistureSensor; + public bool SendLeafWetness; + public int LeafWetnessSensor; + } + + public class WebUploadAprs : WebUploadService + { + public bool HumidityCutoff; + } + + public class DisplayOptions + { + public bool UseApparent { get; set; } + public bool ShowSolar { get; set; } + public bool ShowUV { get; set; } + } + + public class AlarmEmails + { + public string Preamble { get; set; } + public string HighGust { get; set; } + public string HighWind { get; set; } + public string HighTemp { get; set; } + public string LowTemp { get; set; } + public string TempDown { get; set; } + public string TempUp { get; set; } + public string HighPress { get; set; } + public string LowPress { get; set; } + public string PressDown { get; set; } + public string PressUp { get; set; } + public string Rain { get; set; } + public string RainRate { get; set; } + public string SensorLost { get; set; } + public string DataStopped { get; set; } + public string BatteryLow { get; set; } + public string DataSpike { get; set; } + public string Upgrade { get; set; } + } +} diff --git a/CumulusMX/CumulusMX.csproj b/CumulusMX/CumulusMX.csproj index c4c3be8f..28a49ecd 100644 --- a/CumulusMX/CumulusMX.csproj +++ b/CumulusMX/CumulusMX.csproj @@ -1,284 +1,285 @@ - - - - - Debug - AnyCPU - {67A70E28-25C7-4C7F-BD7B-959AE6834B2C} - Exe - Properties - CumulusMX - CumulusMX - v4.5.2 - 512 - 8de6c3ed - false - - C:\Users\mcrossley\Code\CumulusMX-Dist\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 3064 - 3.3.1.3064 - false - true - true - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - MinimumRecommendedRules.ruleset - - - - - AnyCPU - none - true - bin\Release\ - TRACE - prompt - 4 - ".pdb"="" - false - - - - FA8B5CBB94229C9648BA8B533426B5DB50CB03F8 - - - CumulusMX_TemporaryKey.pfx - - - false - - - false - - - - icon.ico - - - LocalIntranet - - - - - - true - - - - ..\packages\Portable.BouncyCastle.1.8.10\lib\net40\BouncyCastle.Crypto.dll - - - ..\packages\FluentFTP.32.3.1\lib\net45\FluentFTP.dll - - - ..\packages\HidSharp.2.1.0\lib\net35\HidSharp.dll - - - False - ..\packages\linqtotwitter.3.1.1\lib\net45\LinqToTwitter.AspNet.dll - - - False - ..\packages\linqtotwitter.3.1.1\lib\net45\LinqToTwitterPcl.dll - - - ..\packages\MailKit.2.11.1\lib\net45\MailKit.dll - - - ..\packages\MimeKit.2.11.0\lib\net45\MimeKit.dll - - - ..\packages\MQTTnet.3.0.15\lib\net452\MQTTnet.dll - - - ..\packages\MySqlConnector.1.3.7\lib\net45\MySqlConnector.dll - - - ..\packages\SSH.NET.2016.1.0\lib\net40\Renci.SshNet.dll - - - ..\packages\ServiceStack.Text.5.10.4\lib\net45\ServiceStack.Text.dll - - - - ..\packages\System.Buffers.4.5.1\lib\netstandard1.1\System.Buffers.dll - - - - - - - - ..\packages\System.Memory.4.5.4\lib\netstandard1.1\System.Memory.dll - - - - ..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll - - - ..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll - - - False - - - ..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll - - - ..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll - - - ..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll - - - ..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll - - - ..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll - - - - - - ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll - - - - - - - - - - - False - ..\..\CumulusMX\CumulusMX\lib\Tmds.MDns.dll - - - ..\packages\EmbedIO.2.1.1\lib\net452\Unosquare.Labs.EmbedIO.dll - - - ..\packages\Unosquare.Swan.Lite.0.38.0\lib\net452\Unosquare.Swan.Lite.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Component - - - CumulusService.cs - - - Component - - - - - - - - - - - - - - - - - - - - - - - - - - Designer - - - - - - - - False - .NET Framework 3.5 SP1 - false - - - - - - - - - PreserveNewest - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - + + + + + Debug + AnyCPU + {67A70E28-25C7-4C7F-BD7B-959AE6834B2C} + Exe + Properties + CumulusMX + CumulusMX + v4.5.2 + 512 + 8de6c3ed + false + + C:\Users\mcrossley\Code\CumulusMX-Dist\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 3064 + 3.3.1.3064 + false + true + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + MinimumRecommendedRules.ruleset + + + + + AnyCPU + none + true + bin\Release\ + TRACE + prompt + 4 + ".pdb"="" + false + + + + FA8B5CBB94229C9648BA8B533426B5DB50CB03F8 + + + CumulusMX_TemporaryKey.pfx + + + false + + + false + + + + icon.ico + + + LocalIntranet + + + + + + true + + + + ..\packages\Portable.BouncyCastle.1.8.10\lib\net40\BouncyCastle.Crypto.dll + + + ..\packages\FluentFTP.32.3.1\lib\net45\FluentFTP.dll + + + ..\packages\HidSharp.2.1.0\lib\net35\HidSharp.dll + + + False + ..\packages\linqtotwitter.3.1.1\lib\net45\LinqToTwitter.AspNet.dll + + + False + ..\packages\linqtotwitter.3.1.1\lib\net45\LinqToTwitterPcl.dll + + + ..\packages\MailKit.2.11.1\lib\net45\MailKit.dll + + + ..\packages\MimeKit.2.11.0\lib\net45\MimeKit.dll + + + ..\packages\MQTTnet.3.0.15\lib\net452\MQTTnet.dll + + + ..\packages\MySqlConnector.1.3.7\lib\net45\MySqlConnector.dll + + + ..\packages\SSH.NET.2016.1.0\lib\net40\Renci.SshNet.dll + + + ..\packages\ServiceStack.Text.5.10.4\lib\net45\ServiceStack.Text.dll + + + + ..\packages\System.Buffers.4.5.1\lib\netstandard1.1\System.Buffers.dll + + + + + + + + ..\packages\System.Memory.4.5.4\lib\netstandard1.1\System.Memory.dll + + + + ..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll + + + ..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll + + + False + + + ..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll + + + ..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll + + + ..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll + + + ..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll + + + + + + ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll + + + + + + + + + + + False + ..\..\CumulusMX\CumulusMX\lib\Tmds.MDns.dll + + + ..\packages\EmbedIO.2.1.1\lib\net452\Unosquare.Labs.EmbedIO.dll + + + ..\packages\Unosquare.Swan.Lite.0.38.0\lib\net452\Unosquare.Swan.Lite.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Component + + + CumulusService.cs + + + Component + + + + + + + + + + + + + + + + + + + + + + + + + + Designer + + + + + + + + False + .NET Framework 3.5 SP1 + false + + + + + + + + + PreserveNewest + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/CumulusMX/DavisWllStation.cs b/CumulusMX/DavisWllStation.cs index 54097709..df48bb93 100644 --- a/CumulusMX/DavisWllStation.cs +++ b/CumulusMX/DavisWllStation.cs @@ -1,3187 +1,3188 @@ -using ServiceStack; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Globalization; -using System.IO.Ports; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Sockets; -using System.Reflection; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Timers; -using Tmds.MDns; -using Unosquare.Swan; - -namespace CumulusMX -{ - internal class DavisWllStation : WeatherStation - { - private string ipaddr; - private int port; - private int duration; - private readonly System.Timers.Timer tmrRealtime; - private readonly System.Timers.Timer tmrCurrent; - private readonly System.Timers.Timer tmrBroadcastWatchdog; - private readonly System.Timers.Timer tmrHealth; - private readonly object threadSafer = new object(); - private static readonly SemaphoreSlim WebReq = new SemaphoreSlim(1); - private bool startupDayResetIfRequired = true; - private bool savedUseSpeedForAvgCalc; - private bool savedCalculatePeakGust; - private int maxArchiveRuns = 1; - private static readonly HttpClientHandler HistoricHttpHandler = new HttpClientHandler(); - private readonly HttpClient wlHttpClient = new HttpClient(HistoricHttpHandler); - private readonly HttpClient dogsBodyClient = new HttpClient(); - private readonly bool checkWllGustValues; - private bool broadcastReceived; - private int weatherLinkArchiveInterval = 16 * 60; // Used to get historic Health, 16 minutes in seconds only for initial fetch after load - private bool wllVoltageLow; - private bool stop; - private readonly List sensorList = new List(); - private readonly bool useWeatherLinkDotCom = true; - - public DavisWllStation(Cumulus cumulus) : base(cumulus) - { - cumulus.Manufacturer = cumulus.DAVIS; - calculaterainrate = false; - //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"); - - // Override the ServiceStack Deserialization function - // Check which format provided, attempt to parse as datetime or return minValue. - // Formats to use for the different date kinds - string utcTimeFormat = "yyyy-MM-dd'T'HH:mm:ss.fff'Z'"; - string localTimeFormat = "yyyy-MM-dd'T'HH:mm:ss"; - - - ServiceStack.Text.JsConfig.DeSerializeFn = datetimeStr => - { - if (string.IsNullOrWhiteSpace(datetimeStr)) - { - return DateTime.MinValue; - } - - if (datetimeStr.EndsWith("Z") && - DateTime.TryParseExact(datetimeStr, utcTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out DateTime resultUtc)) - { - return resultUtc; - } - else if (!datetimeStr.EndsWith("Z") && - DateTime.TryParseExact(datetimeStr, localTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out DateTime resultLocal)) - { - return resultLocal; - } - - return DateTime.MinValue; - }; - - tmrRealtime = new System.Timers.Timer(); - tmrCurrent = new System.Timers.Timer(); - tmrBroadcastWatchdog = new System.Timers.Timer(); - tmrHealth = new System.Timers.Timer(); - - wlHttpClient.Timeout = TimeSpan.FromSeconds(20); // 20 seconds for internet queries - - // used for kicking real time, and getting current conditions - dogsBodyClient.Timeout = TimeSpan.FromSeconds(10); // 10 seconds for local queries - dogsBodyClient.DefaultRequestHeaders.Add("Connection", "close"); - - // If the user is using the default 10 minute Wind gust, always use gust data from the WLL - simple - if (cumulus.StationOptions.PeakGustMinutes == 10) - { - CalcRecentMaxGust = false; - checkWllGustValues = false; - } - else if (cumulus.StationOptions.PeakGustMinutes > 10) - { - // If the user period is greater that 10 minutes, then Cumulus must calculate Gust values - // but we can check the WLL 10 min gust value in case we missed a gust - CalcRecentMaxGust = true; - checkWllGustValues = true; - } - else - { - // User period is less than 10 minutes, so we cannot use the station 10 min gust values - CalcRecentMaxGust = true; - checkWllGustValues = false; - } - - - // Sanity check - do we have all the info we need? - if (string.IsNullOrEmpty(cumulus.WllApiKey) && string.IsNullOrEmpty(cumulus.WllApiSecret)) - { - // The basic API details have not been supplied - cumulus.LogMessage("WLL - No WeatherLink.com API configuration supplied, just going to work locally"); - cumulus.LogMessage("WLL - Cannot start historic downloads or retrieve health data"); - cumulus.LogConsoleMessage("*** No WeatherLink.com API details supplied. Cannot start historic downloads or retrieve health data"); - useWeatherLinkDotCom = false; - } - else if (string.IsNullOrEmpty(cumulus.WllApiKey) || string.IsNullOrEmpty(cumulus.WllApiSecret)) - { - // One of the API details is missing - if (string.IsNullOrEmpty(cumulus.WllApiKey)) - { - cumulus.LogMessage("WLL - Missing WeatherLink.com API Key"); - cumulus.LogConsoleMessage("*** Missing WeatherLink.com API Key. Cannot start historic downloads or retrieve health data"); - } - else - { - cumulus.LogMessage("WLL - Missing WeatherLink.com API Secret"); - cumulus.LogConsoleMessage("*** Missing WeatherLink.com API Secret. Cannot start historic downloads or retrieve health data"); - } - useWeatherLinkDotCom = false; - } - - if (useWeatherLinkDotCom) - { - // Get wl.com status - GetSystemStatus(); - } - - // Perform Station ID checks - If we have API deatils! - // If the Station ID is missing, this will populate it if the user only has one station associated with the API key - if (useWeatherLinkDotCom && cumulus.WllStationId < 10) - { - var msg = "No WeatherLink API station ID in the cumulus.ini file"; - cumulus.LogMessage(msg); - cumulus.LogConsoleMessage(msg); - - GetAvailableStationIds(true); - } - else if (useWeatherLinkDotCom) - { - GetAvailableStationIds(false); - } - - // Sanity check the station id - if (useWeatherLinkDotCom && cumulus.WllStationId < 10) - { - // API details supplied, but Station Id is still invalid - do not start the station up. - cumulus.LogMessage("WLL - The WeatherLink.com API is enabled, but no Station Id has been configured, not starting the station. Please correct this and restart Cumulus"); - cumulus.LogConsoleMessage("The WeatherLink.com API is enabled, but no Station Id has been configured. Please correct this and restart Cumulus"); - return; - } - - - // Now get the sensors associated with this station - if (useWeatherLinkDotCom) - GetAvailableSensors(); - - // 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 - - const string serviceType = "_weatherlinklive._tcp"; - var serviceBrowser = new ServiceBrowser(); - serviceBrowser.ServiceAdded += OnServiceAdded; - serviceBrowser.ServiceRemoved += OnServiceRemoved; - serviceBrowser.ServiceChanged += OnServiceChanged; - serviceBrowser.QueryParameters.QueryInterval = cumulus.WllBroadcastDuration * 1000 * 4; // query at 4x the multicast time (default 20 mins) - - //Console.WriteLine($"Browsing for type: {serviceType}"); - serviceBrowser.StartBrowse(serviceType); - - cumulus.LogMessage("Attempting to find WLL via zero-config..."); - - // short wait for zero-config - Thread.Sleep(1000); - - DateTime tooOld = new DateTime(0); - - if ((cumulus.LastUpdateTime <= tooOld) || !cumulus.UseDataLogger) - { - // there's nothing in the database, so we haven't got a rain counter - // we can't load the history data, so we'll just have to go live - - timerStartNeeded = true; - LoadLastHoursFromDataLogs(cumulus.LastUpdateTime); - //StartLoop(); - DoDayResetIfNeeded(); - DoTrendValues(DateTime.Now); - - cumulus.LogMessage("Starting Davis WLL"); - StartLoop(); - } - else - { - // Read the data from the WL APIv2 - startReadingHistoryData(); - } - } - - public override void Start() - { - try - { - // Wait for the lock - cumulus.LogDebugMessage("Lock: Station waiting for lock"); - Cumulus.syncInit.Wait(); - cumulus.LogDebugMessage("Lock: Station has the lock"); - - // Create a realtime thread to periodically restart broadcasts - GetWllRealtime(null, null); - tmrRealtime.Elapsed += GetWllRealtime; - tmrRealtime.Interval = cumulus.WllBroadcastDuration * 1000 / 3 * 2; // give the multicasts a kick after 2/3 of the duration (default 200 secs) - tmrRealtime.AutoReset = true; - tmrRealtime.Start(); - - // Create a current conditions thread to poll readings every 30 seconds - GetWllCurrent(null, null); - tmrCurrent.Elapsed += GetWllCurrent; - tmrCurrent.Interval = 30 * 1000; // Every 30 seconds - tmrCurrent.AutoReset = true; - tmrCurrent.Start(); - - if (useWeatherLinkDotCom) - { - // Get the archive data health to do the initial value populations - GetWlHistoricHealth(); - // And reset the fetch interval to 2 minutes - weatherLinkArchiveInterval = 2 * 60; - } - - // short wait for realtime response - Thread.Sleep(1200); - - if (port == 0) - { - cumulus.LogMessage("WLL failed to get broadcast port via realtime request, defaulting to 22222"); - port = cumulus.DavisOptions.TCPPort; - } - else if (port != cumulus.DavisOptions.TCPPort) - { - cumulus.LogMessage($"WLL Discovered broacast port ({port}) is not the same as in the config ({cumulus.DavisOptions.TCPPort}), resetting config to match"); - cumulus.DavisOptions.TCPPort = port; - cumulus.WriteIniFile(); - } - - // Create a broadcast listener - Task.Run(() => - { - using (var udpClient = new UdpClient()) - { - udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, port)); - udpClient.Client.ReceiveTimeout = 4000; // We should get a message every 2.5 seconds - var from = new IPEndPoint(0, 0); - - while (!stop) - { - try - { - var jsonBtye = udpClient.Receive(ref from); - var jsonStr = Encoding.UTF8.GetString(jsonBtye); - if (!stop) // we may be waiting for a broadcast when a shutdown is started - { - DecodeBroadcast(jsonStr); - } - } - catch (SocketException exp) - { - if (exp.SocketErrorCode == SocketError.TimedOut) - { - multicastsBad++; - var msg = string.Format("WLL: Missed a WLL broadcast message. Percentage good packets {0:F2}% - ({1},{2})", (multicastsGood / (float)(multicastsBad + multicastsGood) * 100), multicastsBad, multicastsGood); - cumulus.LogDebugMessage(msg); - } - else - { - cumulus.LogMessage($"WLL: UDP socket exception: {exp.Message}"); - } - } - } - udpClient.Close(); - cumulus.LogMessage("WLL broadcast listener stopped"); - } - }); - - cumulus.LogMessage($"WLL Now listening on broadcast port {port}"); - - // Start a broadcast watchdog to warn if WLL broadcast messages are not being received - tmrBroadcastWatchdog.Elapsed += BroadcastTimeout; - tmrBroadcastWatchdog.Interval = 1000 * 30; // timeout after 30 seconds - tmrBroadcastWatchdog.AutoReset = true; - tmrBroadcastWatchdog.Start(); - - if (useWeatherLinkDotCom) - { - // get the health data every 15 minutes - tmrHealth.Elapsed += HealthTimerTick; - tmrHealth.Interval = 60 * 1000; // Tick every minute - tmrHealth.AutoReset = true; - tmrHealth.Start(); - } - } - catch (ThreadAbortException) - { - } - finally - { - cumulus.LogDebugMessage("Lock: Station releasing lock"); - Cumulus.syncInit.Release(); - } - } - - public override void Stop() - { - cumulus.LogMessage("Closing WLL connections"); - try - { - stop = true; - tmrRealtime.Stop(); - tmrCurrent.Stop(); - tmrBroadcastWatchdog.Stop(); - tmrHealth.Stop(); - StopMinuteTimer(); - } - catch - { - cumulus.LogMessage("Error stopping station timers"); - } - } - - private async void GetWllRealtime(object source, ElapsedEventArgs e) - { - var retry = 2; - - cumulus.LogDebugMessage("GetWllRealtime: GetWllRealtime waiting for lock"); - WebReq.Wait(); - cumulus.LogDebugMessage("GetWllRealtime: GetWllRealtime has the lock"); - - // The WLL will error if already responding to a request from another device, so add a retry - do - { - // Call asynchronous network methods in a try/catch block to handle exceptions - try - { - string ip; - - lock (threadSafer) - { - ip = cumulus.DavisOptions.IPAddr; - } - - if (CheckIpValid(ip)) - { - var urlRealtime = "http://" + ip + "/v1/real_time?duration=" + cumulus.WllBroadcastDuration; - - cumulus.LogDebugMessage($"GetWllRealtime: Sending GET realtime request to WLL: {urlRealtime} ..."); - - string responseBody; - - using (HttpResponseMessage response = await dogsBodyClient.GetAsync(urlRealtime)) - { - responseBody = await response.Content.ReadAsStringAsync(); - responseBody = responseBody.TrimEnd('\r', '\n'); - - cumulus.LogDataMessage("GetWllRealtime: WLL response: " + responseBody); - } - - var respJson = responseBody.FromJson(); - var err = string.IsNullOrEmpty(respJson.error) ? "OK" : respJson.error; - port = respJson.data.broadcast_port; - duration = respJson.data.duration; - cumulus.LogDebugMessage($"GetWllRealtime: GET response Code: {err}, Port: {port}"); - if (cumulus.WllBroadcastDuration != duration) - { - cumulus.LogMessage($"GetWllRealtime: WLL broadcast duration {duration} does not match requested duration {cumulus.WllBroadcastDuration}, continuing to use {cumulus.WllBroadcastDuration}"); - } - if (cumulus.WllBroadcastPort != port) - { - cumulus.LogMessage($"GetWllRealtime: WLL broadcast port {port} does not match default {cumulus.WllBroadcastPort}, resetting to {port}"); - cumulus.WllBroadcastPort = port; - } - } - else - { - cumulus.LogMessage($"GetWllRealtime: Invalid IP address: {ip}"); - } - retry = 0; - } - catch (Exception exp) - { - retry--; - cumulus.LogDebugMessage("GetRealtime: Exception Caught!"); - cumulus.LogDebugMessage($"GetWllRealtime: Message :{exp.Message}"); - Thread.Sleep(2000); - } - } while (retry > 0); - - cumulus.LogDebugMessage("GetWllRealtime: Releasing lock"); - WebReq.Release(); - } - - private async void GetWllCurrent(object source, ElapsedEventArgs e) - { - string ip; - int retry = 1; - - lock (threadSafer) - { - ip = cumulus.DavisOptions.IPAddr; - } - - if (CheckIpValid(ip)) - { - var urlCurrent = $"http://{ip}/v1/current_conditions"; - - cumulus.LogDebugMessage("GetWllCurrent: Waiting for lock"); - WebReq.Wait(); - cumulus.LogDebugMessage("GetWllCurrent: Has the lock"); - - // The WLL will error if already responding to a request from another device, so add a retry - do - { - cumulus.LogDebugMessage($"GetWllCurrent: Sending GET current conditions request {retry} to WLL: {urlCurrent} ..."); - try - { - string responseBody; - using (HttpResponseMessage response = await dogsBodyClient.GetAsync(urlCurrent)) - { - response.EnsureSuccessStatusCode(); - responseBody = await response.Content.ReadAsStringAsync(); - cumulus.LogDataMessage($"GetWllCurrent: response - {responseBody}"); - } - - DecodeCurrent(responseBody); - if (startupDayResetIfRequired) - { - DoDayResetIfNeeded(); - startupDayResetIfRequired = false; - } - retry = 9; - } - catch (Exception ex) - { - retry++; - cumulus.LogMessage("GetWllCurrent: Error processing WLL response"); - if (ex.InnerException == null) - cumulus.LogMessage($"GetWllCurrent: Error: {ex.Message}"); - else - cumulus.LogMessage($"GetWllCurrent: Error: {ex.InnerException.Message}"); - Thread.Sleep(1000); - } - } while (retry < 3); - - cumulus.LogDebugMessage("GetWllCurrent: Releasing lock"); - WebReq.Release(); - } - else - { - cumulus.LogMessage($"GetWllCurrent: Invalid IP address: {ip}"); - } - } - - private void DecodeBroadcast(string broadcastJson) - { - try - { - cumulus.LogDataMessage("WLL Broadcast: " + broadcastJson); - - // sanity check - if (broadcastJson.StartsWith("{\"did\":")) - { - var json = broadcastJson.FromJson(); - // The WLL sends the timestamp in Unix ticks, and in UTC - // rather than rely on the WLL clock being correct, we will use our local time - var dateTime = DateTime.Now; - foreach (var rec in json.conditions) - { - // Wind - All values in MPH - /* Available fields: - * rec["wind_speed_last"] - * rec["wind_dir_last"] - * rec["wind_speed_hi_last_10_min"] - * rec["wind_dir_at_hi_speed_last_10_min"] - */ - if (cumulus.WllPrimaryWind == rec.txid) - { - try - { - // WLL BUG/FEATURE: The WLL sends a null wind direction for calm when the avg speed falls to zero, we use zero - int windDir = rec.wind_dir_last ?? 0; - - // No average in the broadcast data, so use last value from current - allow for calibration - DoWind(ConvertWindMPHToUser(rec.wind_speed_last), windDir, WindAverage / cumulus.Calib.WindSpeed.Mult, dateTime); - - var gust = ConvertWindMPHToUser(rec.wind_speed_hi_last_10_min); - var gustCal = gust * cumulus.Calib.WindGust.Mult; - if (checkWllGustValues) - { - if (gustCal > RecentMaxGust) - { - // See if the station 10 min high speed is higher than our current 10-min max - // ie we missed the high gust - - // Check for spikes, and set highs - if (CheckHighGust(gustCal, rec.wind_dir_at_hi_speed_last_10_min, dateTime)) - { - cumulus.LogDebugMessage("Set max gust from broadcast 10 min high value: " + gustCal.ToString(cumulus.WindFormat) + " was: " + RecentMaxGust.ToString(cumulus.WindFormat)); - - // add to recent values so normal calculation includes this value - WindRecent[nextwind].Gust = gust; // use uncalibrated value - WindRecent[nextwind].Speed = WindAverage / cumulus.Calib.WindSpeed.Mult; - WindRecent[nextwind].Timestamp = dateTime; - nextwind = (nextwind + 1) % MaxWindRecent; - - RecentMaxGust = gustCal; - } - } - } - else if (!CalcRecentMaxGust) - { - // Check for spikes, and set highs - if (CheckHighGust(gustCal, rec.wind_dir_at_hi_speed_last_10_min, dateTime)) - { - RecentMaxGust = gustCal; - } - } - } - catch (Exception ex) - { - cumulus.LogMessage($"WLL broadcast: Error in wind speed found on TxId {rec.txid}"); - cumulus.LogDebugMessage($"WLL broadcast: Exception: {ex.Message}"); - } - } - - // Rain - /* - * All fields are *tip counts* - * Available fields: - * rec["rain_size"] - 0: Reseverved, 1: 0.01", 2: 0.2mm, 3: 0.1mm, 4: 0.001" - * rec["rain_rate_last"] - * rec["rain_15_min"] - * rec["rain_60_min"] - * rec["rain_24_hr"] - * rec["rain_storm"] - * rec["rain_storm_start_at"] - * rec["rainfall_daily"] - * rec["rainfall_monthly"] - * rec["rainfall_year"]) - */ - if (cumulus.WllPrimaryRain != rec.txid) continue; - - try - { - var rain = ConvertRainClicksToUser(rec.rainfall_year, rec.rain_size); - var rainrate = ConvertRainClicksToUser(rec.rain_rate_last, rec.rain_size); - - if (rainrate < 0) - { - rainrate = 0; - } - - DoRain(rain, rainrate, dateTime); - } - catch (Exception ex) - { - cumulus.LogMessage($"WLL broadcast: no valid rainfall found on TxId {rec.txid}"); - cumulus.LogDebugMessage($"WLL broadcast: Exception: {ex.Message}"); - } - } - - json = null; - - UpdateStatusPanel(DateTime.Now); - UpdateMQTT(); - - broadcastReceived = true; - DataStopped = false; - cumulus.DataStoppedAlarm.Triggered = false; - multicastsGood++; - } - else - { - multicastsBad++; - var msg = string.Format("WLL broadcast: Invalid payload in message. Percentage good packets {0:F2}% - ({1},{2})", (multicastsGood / (float)(multicastsBad + multicastsGood) * 100), multicastsBad, multicastsGood); - cumulus.LogMessage(msg); - } - } - catch (Exception exp) - { - cumulus.LogDebugMessage("DecodeBroadcast(): Exception Caught!"); - cumulus.LogDebugMessage("Message :" + exp.Message); - multicastsBad++; - var msg = string.Format("WLL broadcast: Error processing broadcast. Percentage good packets {0:F2}% - ({1},{2})", (multicastsGood / (float)(multicastsBad + multicastsGood) * 100), multicastsBad, multicastsGood); - cumulus.LogMessage(msg); - } - } - - private void DecodeCurrent(string currentJson) - { - try - { - // Convert JSON string to an object - WllCurrent json = currentJson.FromJson(); - - // The WLL sends the timestamp in Unix ticks, and in UTC - // rather than rely on the WLL clock being correct, we will use our local time - //var dateTime = FromUnixTime(data.Value("ts")); - var dateTime = DateTime.Now; - var localSensorContactLost = false; - - foreach (var rec in json.data.conditions) - { - // Yuck, we have to find the data type in the string, then we know how to decode it to the correct object type - int start = rec.IndexOf("data_structure_type:") + "data_structure_type:".Length; - int end = rec.IndexOf(",", start); - - int type = int.Parse(rec.Substring(start, end - start)); - string idx = ""; - - switch (type) - { - case 1: // ISS - var data1 = rec.FromJsv(); - - cumulus.LogDebugMessage($"WLL current: found ISS data on TxId {data1.txid}"); - - // Battery - SetTxBatteryStatus(data1.txid, data1.trans_battery_flag); - - if (data1.rx_state == 2) - { - localSensorContactLost = true; - cumulus.LogMessage($"Warning: Sensor contact lost TxId {data1.txid}; ignoring data from this ISS"); - continue; - } - - - // Temperature & Humidity - if (cumulus.WllPrimaryTempHum == data1.txid) - { - /* Available fields - * "temp": 62.7, // most recent valid temperature **(°F)** - * "hum":1.1, // most recent valid humidity **(%RH)** - * "dew_point": -0.3, // **(°F)** - * "wet_bulb":null, // **(°F)** - * "heat_index": 5.5, // **(°F)** - * "wind_chill": 6.0, // **(°F)** - * "thw_index": 5.5, // **(°F)** - * "thsw_index": 5.5, // **(°F)** - */ - - try - { - cumulus.LogDebugMessage($"WLL current: using temp/hum data from TxId {data1.txid}"); - - DoOutdoorHumidity(Convert.ToInt32(data1.hum), dateTime); - - DoOutdoorTemp(ConvertTempFToUser(data1.temp), dateTime); - - DoOutdoorDewpoint(ConvertTempFToUser(data1.dew_point), dateTime); - - if (!cumulus.StationOptions.CalculatedWC) - { - // use wind chill from WLL - DoWindChill(ConvertTempFToUser(data1.wind_chill), dateTime); - } - - //TODO: Wet Bulb? rec["wet_bulb"] - No, we already have humidity - //TODO: Heat Index? rec["heat_index"] - No, Cumulus always calculates HI - } - catch (Exception ex) - { - cumulus.LogMessage($"WLL current: Error processing temperature values on TxId {data1.txid}"); - cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); - } - } - else - { // Check for Extra temperature/humidity settings - for (var tempTxId = 1; tempTxId <= 8; tempTxId++) - { - if (cumulus.WllExtraTempTx[tempTxId - 1] != data1.txid) continue; - - try - { - if (cumulus.WllExtraTempTx[tempTxId - 1] == data1.txid) - { - if (data1.temp == -99) - { - cumulus.LogDebugMessage($"WLL current: no valid Extra temperature value found [{data1.temp}] on TxId {data1.txid}"); - } - else - { - cumulus.LogDebugMessage($"WLL current: using extra temp data from TxId {data1.txid}"); - - DoExtraTemp(ConvertTempFToUser(data1.temp), tempTxId); - } - - if (cumulus.WllExtraHumTx[tempTxId - 1]) - { - DoExtraHum(data1.hum, tempTxId); - } - } - } - catch (Exception ex) - { - cumulus.LogMessage($"WLL current: Error processing Extra temperature/humidity values on TxId {data1.txid}"); - cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); - } - } - } - - // Wind - if (cumulus.WllPrimaryWind == data1.txid) - { - /* - * Available fields - * "wind_speed_last":2, // most recent valid wind speed **(mph)** - * "wind_dir_last":null, // most recent valid wind direction **(°degree)** - * "wind_speed_avg_last_1_min":4 // average wind speed over last 1 min **(mph)** - * "wind_dir_scalar_avg_last_1_min":15 // scalar average wind direction over last 1 min **(°degree)** - * "wind_speed_avg_last_2_min":42606, // average wind speed over last 2 min **(mph)** - * "wind_dir_scalar_avg_last_2_min": 170.7, // scalar average wind direction over last 2 min **(°degree)** - * "wind_speed_hi_last_2_min":8, // maximum wind speed over last 2 min **(mph)** - * "wind_dir_at_hi_speed_last_2_min":0.0, // gust wind direction over last 2 min **(°degree)** - * "wind_speed_avg_last_10_min":42606, // average wind speed over last 10 min **(mph)** - * "wind_dir_scalar_avg_last_10_min": 4822.5, // scalar average wind direction over last 10 min **(°degree)** - * "wind_speed_hi_last_10_min":8, // maximum wind speed over last 10 min **(mph)** - * "wind_dir_at_hi_speed_last_10_min":0.0, // gust wind direction over last 10 min **(°degree)** - */ - try - { - cumulus.LogDebugMessage($"WLL current: using wind data from TxId {data1.txid}"); - - // pesky null values from WLL when it is calm - int wdir = data1.wind_dir_last ?? 0; - double wind = data1.wind_speed_last ?? 0; - double wspdAvg10min = ConvertWindMPHToUser(data1.wind_speed_avg_last_10_min ?? 0); - - DoWind(ConvertWindMPHToUser(wind), wdir, wspdAvg10min, dateTime); - - WindAverage = wspdAvg10min * cumulus.Calib.WindSpeed.Mult; - - // Wind data can be a bit out of date compared to the broadcasts (1 minute update), so only use gust broadcast data - /* - var gust = ConvertWindMPHToUser(data1.wind_speed_hi_last_10_min); - var gustCal = gust * cumulus.Calib.WindGust.Mult; - - if (checkWllGustValues) - { - // See if the current speed is higher than the current 10-min max - // We can then update the figure before the next data packet is read - - if (gustCal > RecentMaxGust) - { - // Check for spikes, and set highs - if (CheckHighGust(gustCal, data1.wind_dir_at_hi_speed_last_10_min, dateTime)) - { - cumulus.LogDebugMessage("Setting max gust from current 10 min value: " + gustCal.ToString(cumulus.WindFormat) + " was: " + RecentMaxGust.ToString(cumulus.WindFormat)); - - // add to recent values so normal calculation includes this value - WindRecent[nextwind].Gust = gust; // use uncalibrated value - WindRecent[nextwind].Speed = WindAverage / cumulus.Calib.WindSpeed.Mult; - WindRecent[nextwind].Timestamp = dateTime; - nextwind = (nextwind + 1) % cumulus.MaxWindRecent; - - RecentMaxGust = gustCal; - } - } - } - else if (!CalcRecentMaxGust) - { - // Check for spikes, and set highs - if (CheckHighGust(gustCal, data1.wind_dir_at_hi_speed_last_10_min, dateTime)) - { - RecentMaxGust = gustCal; - } - } - */ - } - catch (Exception ex) - { - cumulus.LogMessage($"WLL current: Error processing wind speeds on TxId {data1.txid}"); - cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); - } - } - - - // Rainfall - if (cumulus.WllPrimaryRain == data1.txid) - { - /* - * Available fields: - * rec["rain_size"] - 0: Reseverved, 1: 0.01", 2: 0.2mm, 3: 0.1mm, 4: 0.001" - * rec["rain_rate_last"], rec["rain_rate_hi"] - * rec["rainfall_last_15_min"], rec["rain_rate_hi_last_15_min"] - * rec["rainfall_last_60_min"] - * rec["rainfall_last_24_hr"] - * rec["rainfall_daily"] - * rec["rainfall_monthly"] - * rec["rainfall_year"] - * rec["rain_storm"], rec["rain_storm_start_at"] - * rec["rain_storm_last"], rec["rain_storm_last_start_at"], rec["rain_storm_last_end_at"] - */ - - - cumulus.LogDebugMessage($"WLL current: using rain data from TxId {data1.txid}"); - - var tipSize = data1.rain_size; - switch (tipSize) - { - case 1: - if (cumulus.DavisOptions.RainGaugeType != 1) - { - cumulus.LogMessage($"Setting Davis rain tipper size - was {cumulus.DavisOptions.RainGaugeType}, now 1 = 0.01 in"); - cumulus.DavisOptions.RainGaugeType = 1; - cumulus.WriteIniFile(); - } - break; - case 2: - if (cumulus.DavisOptions.RainGaugeType != 0) - { - cumulus.LogMessage($"Setting Davis rain tipper size - was {cumulus.DavisOptions.RainGaugeType}, now 0 = 0.2 mm"); - cumulus.DavisOptions.RainGaugeType = 0; - cumulus.WriteIniFile(); - } - break; - case 3: - if (cumulus.DavisOptions.RainGaugeType != 2) - { - cumulus.LogMessage($"Setting Davis rain tipper size - was {cumulus.DavisOptions.RainGaugeType}, now 0 = 0.1 mm"); - cumulus.DavisOptions.RainGaugeType = 2; - cumulus.WriteIniFile(); - } - break; - case 4: - if (cumulus.DavisOptions.RainGaugeType != 3) - { - cumulus.LogMessage($"Setting Davis rain tipper size - was {cumulus.DavisOptions.RainGaugeType}, now 0 = 0.001 in"); - cumulus.DavisOptions.RainGaugeType = 2; - cumulus.WriteIniFile(); - } - break; - } - - // Rain data can be a bit out of date compared to the broadcasts (1 minute update), so only use storm data - - // All rainfall values supplied as *tip counts* - //double rain = ConvertRainINToUser((double)rec["rainfall_year"]); - //double rainrate = ConvertRainINToUser((double)rec["rain_rate_last"]); - - //if (rainrate < 0) - //{ - // rainrate = 0; - //} - - //DoRain(rain, rainrate, dateTime); - - if (!data1.rain_storm.HasValue || !data1.rain_storm_start_at.HasValue) - { - cumulus.LogDebugMessage("WLL current: No rain storm values present"); - } - else - { - try - { - StormRain = ConvertRainClicksToUser(data1.rain_storm.Value, data1.rain_size) * cumulus.Calib.Rain.Mult; - StartOfStorm = Utils.FromUnixTime(data1.rain_storm_start_at.Value); - } - catch (Exception ex) - { - cumulus.LogMessage($"WLL current: Error processing rain storm values on TxId {data1.txid}"); - cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); - } - } - } - - if (cumulus.WllPrimaryUV == data1.txid) - { - try - { - cumulus.LogDebugMessage($"WLL current: using UV data from TxId {data1.txid}"); - DoUV(data1.uv_index, dateTime); - } - catch (Exception ex) - { - cumulus.LogMessage($"WLL current: Error processing UV value on TxId {data1.txid}"); - cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); - } - } - - if (cumulus.WllPrimarySolar == data1.txid) - { - try - { - cumulus.LogDebugMessage($"WLL current: using solar data from TxId {data1.txid}"); - DoSolarRad(data1.solar_rad, dateTime); - } - catch (Exception ex) - { - cumulus.LogMessage($"WLL current: Error processing Solar value on TxId {data1.txid}"); - cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); - } - } - break; - - 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)** - */ - - var data2 = rec.FromJsv(); - - cumulus.LogDebugMessage($"WLL current: found Leaf/Soil data on TxId {data2.txid}"); - - // Battery - SetTxBatteryStatus(data2.txid, data2.trans_battery_flag); - - if (data2.rx_state == 2) - { - localSensorContactLost = true; - cumulus.LogMessage($"Warning: Sensor contact lost TxId {data2.txid}; ignoring data from this Leaf/Soil transmitter"); - continue; - } - - // For leaf wetness, soil temp/moisture we rely on user configuration, trap any errors - - // Leaf wetness - try - { - if (cumulus.WllExtraLeafTx1 == data2.txid) - { - idx = "wet_leaf_" + cumulus.WllExtraLeafIdx1; - DoLeafWetness((double)data2[idx], 1); - } - if (cumulus.WllExtraLeafTx2 == data2.txid) - { - idx = "wet_leaf_" + cumulus.WllExtraLeafIdx2; - DoLeafWetness((double)data2[idx], 2); - } - } - catch (Exception e) - { - cumulus.LogMessage($"WLL current: Error processung LeafWetness txid={data2.txid}, idx={idx}"); - cumulus.LogDebugMessage($"WLL current: Exception: {e.Message}"); - } - - // Soil moisture - if (cumulus.WllExtraSoilMoistureTx1 == data2.txid) - { - idx = "moist_soil_" + cumulus.WllExtraSoilMoistureIdx1; - try - { - DoSoilMoisture((double)data2[idx], 1); - } - catch (Exception ex) - { - cumulus.LogMessage($"WLL current: Error processing soil moisture #{cumulus.WllExtraSoilMoistureIdx1} on TxId {data2.txid}"); - cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); - } - } - if (cumulus.WllExtraSoilMoistureTx2 == data2.txid) - { - idx = "moist_soil_" + cumulus.WllExtraSoilMoistureIdx2; - try - { - DoSoilMoisture((double)data2[idx], 2); - } - catch (Exception ex) - { - cumulus.LogMessage($"WLL current: Error processing soil moisture #{cumulus.WllExtraSoilMoistureIdx2} on TxId {data2.txid}"); - cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); - } - } - if (cumulus.WllExtraSoilMoistureTx3 == data2.txid) - { - idx = "moist_soil_" + cumulus.WllExtraSoilMoistureIdx3; - try - { - DoSoilMoisture((double)data2[idx], 3); - } - catch (Exception ex) - { - cumulus.LogMessage($"WLL current: Error processing soil moisture #{cumulus.WllExtraSoilMoistureIdx3} on TxId {data2.txid}"); - cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); - } - } - if (cumulus.WllExtraSoilMoistureTx4 == data2.txid) - { - idx = "moist_soil_" + cumulus.WllExtraSoilMoistureIdx4; - try - { - DoSoilMoisture((double)data2[idx], 4); - } - catch (Exception ex) - { - cumulus.LogMessage($"WLL current: Error processing soil moisture #{cumulus.WllExtraSoilMoistureIdx4} on TxId {data2.txid}"); - cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); - } - } - - // SoilTemperature - if (cumulus.WllExtraSoilTempTx1 == data2.txid) - { - idx = "temp_" + cumulus.WllExtraSoilTempIdx1; - try - { - DoSoilTemp(ConvertTempFToUser((double)data2[idx]), 1); - } - catch (Exception ex) - { - cumulus.LogMessage($"WLL current: Error processing extra soil temp #{cumulus.WllExtraSoilTempIdx1} on TxId {data2.txid}"); - cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); - } - } - if (cumulus.WllExtraSoilTempTx2 == data2.txid) - { - idx = "temp_" + cumulus.WllExtraSoilTempIdx2; - try - { - DoSoilTemp(ConvertTempFToUser((double)data2[idx]), 2); - } - catch (Exception ex) - { - cumulus.LogMessage($"WLL current: Error processing extra soil temp #{cumulus.WllExtraSoilTempIdx2} on TxId {data2.txid}"); - cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); - } - } - if (cumulus.WllExtraSoilTempTx3 == data2.txid) - { - idx = "temp_" + cumulus.WllExtraSoilTempIdx3; - try - { - DoSoilTemp(ConvertTempFToUser((double)data2[idx]), 3); - } - catch (Exception ex) - { - cumulus.LogMessage($"WLL current: Error processing extra soil temp #{cumulus.WllExtraSoilTempIdx3} on TxId {data2.txid}"); - cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); - } - } - if (cumulus.WllExtraSoilTempTx4 == data2.txid) - { - idx = "temp_" + cumulus.WllExtraSoilTempIdx4; - try - { - DoSoilTemp(ConvertTempFToUser((double)data2[idx]), 4); - } - catch (Exception ex) - { - cumulus.LogMessage($"WLL current: Error processing extra soil temp #{cumulus.WllExtraSoilTempIdx4} on TxId {data2.txid}"); - cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); - } - } - - // 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"] - */ - - cumulus.LogDebugMessage("WLL current: found Baro data"); - - try - { - var data3 = rec.FromJsv(); - - DoPressure(ConvertPressINHGToUser(data3.bar_sea_level), dateTime); - DoPressTrend("Pressure trend"); - // Altimeter from absolute - StationPressure = ConvertPressINHGToUser(data3.bar_absolute); - // Or do we use calibration? The VP2 code doesn't? - //StationPressure = ConvertPressINHGToUser(rec.Value("bar_absolute")) * cumulus.Calib.Press.Mult + cumulus.Calib.Press.Offset; - AltimeterPressure = ConvertPressMBToUser(StationToAltimeter(ConvertUserPressureToHPa(StationPressure), AltitudeM(cumulus.Altitude))); - } - catch (Exception ex) - { - cumulus.LogMessage("WLL current: Error processing baro data"); - cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); - } - - break; - - case 4: // WLL Temp/Humidity - /* - * Available fields: - * rec["temp_in"] - * rec["hum_in"] - * rec["dew_point_in"] - * rec["heat_index_in"] - */ - - cumulus.LogDebugMessage("WLL current: found Indoor temp/hum data"); - - var data4 = rec.FromJsv(); - - try - { - DoIndoorTemp(ConvertTempFToUser(data4.temp_in)); - } - catch (Exception ex) - { - cumulus.LogMessage("WLL current: Error processing indoor temp data"); - cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); - } - - try - { - DoIndoorHumidity(Convert.ToInt32(data4.hum_in)); - } - catch (Exception ex) - { - cumulus.LogMessage("WLL current: Error processing indoor humidity data"); - cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); - } - - break; - - default: - cumulus.LogDebugMessage($"WLL current: found an unknown tramsmitter type [{type}]!"); - break; - } - - DoForecast(string.Empty, false); - - UpdateStatusPanel(DateTime.Now); - UpdateMQTT(); - } - - // Now we have the primary data, calculate the derived data - if (cumulus.StationOptions.CalculatedWC) - { - if (ConvertUserWindToMS(WindAverage) < 1.5) - { - // wind speed too low, use the temperature - DoWindChill(OutdoorTemperature, dateTime); - } - else - { - // calculate wind chill from calibrated C temp and calibrated wind in KPH - DoWindChill(ConvertTempCToUser(MeteoLib.WindChill(ConvertUserTempToC(OutdoorTemperature), ConvertUserWindToKPH(WindAverage))), dateTime); - } - } - - DoApparentTemp(dateTime); - DoFeelsLike(dateTime); - DoHumidex(dateTime); - - SensorContactLost = localSensorContactLost; - - // If the station isn't using the logger function for WLL - i.e. no API key, then only alarm on Tx battery status - // otherwise, trigger the alarm when we read the Health data which also contains the WLL backup battery status - if (!cumulus.UseDataLogger) - { - cumulus.BatteryLowAlarm.Triggered = TxBatText.Contains("LOW"); - } - } - catch (Exception exp) - { - cumulus.LogDebugMessage("DecodeCurrent: Exception Caught!"); - cumulus.LogDebugMessage("Message :" + exp.Message); - } - } - - private void OnServiceChanged(object sender, ServiceAnnouncementEventArgs e) - { - PrintService('~', e.Announcement); - } - - private void OnServiceRemoved(object sender, ServiceAnnouncementEventArgs e) - { - cumulus.LogMessage("ZeroConfig Service: WLL service has been removed!"); - } - - private void OnServiceAdded(object sender, ServiceAnnouncementEventArgs e) - { - PrintService('+', e.Announcement); - } - - private void PrintService(char startChar, ServiceAnnouncement service) - { - cumulus.LogDebugMessage($"ZeroConf Service: {startChar} '{service.Instance}' on {service.NetworkInterface.Name}"); - cumulus.LogDebugMessage($"\tHost: {service.Hostname} ({string.Join(", ", service.Addresses)})"); - - lock (threadSafer) - { - ipaddr = service.Addresses[0].ToString(); - cumulus.LogMessage($"ZeroConf Service: WLL found, reporting its IP address as: {ipaddr}"); - if (cumulus.DavisOptions.IPAddr != ipaddr) - { - cumulus.LogMessage($"ZeroConf Service: WLL IP address has changed from {cumulus.DavisOptions.IPAddr} to {ipaddr}"); - if (cumulus.WLLAutoUpdateIpAddress) - { - cumulus.LogMessage($"ZeroConf Service: WLL changing Cumulus config to the new IP address {ipaddr}"); - cumulus.DavisOptions.IPAddr = ipaddr; - cumulus.WriteIniFile(); - } - else - { - cumulus.LogMessage($"ZeroConf Service: WLL ignoring new IP address {ipaddr} due to setting WLLAutoUpdateIpAddress"); - } - } - } - } - - private double ConvertRainClicksToUser(double clicks, int size) - { - // 0: Reseverved, 1: 0.01", 2: 0.2mm, 3: 0.1mm, 4: 0.001" - switch (size) - { - case 1: - return ConvertRainINToUser(clicks * 0.01); - case 2: - return ConvertRainMMToUser(clicks * 0.2); - case 3: - return ConvertRainMMToUser(clicks * 0.1); - case 4: - return ConvertRainINToUser(clicks * 0.001); - default: - switch (cumulus.DavisOptions.RainGaugeType) - { - // Hmm, no valid tip size from WLL... - // One click is normally either 0.01 inches or 0.2 mm - // Try the setting in Cumulus.ini - // Rain gauge type not configured, assume from units - case -1 when cumulus.Units.Rain == 0: - return clicks * 0.2; - case -1: - return clicks * 0.01; - // Rain gauge is metric, convert to user unit - case 0: - return ConvertRainMMToUser(clicks * 0.2); - default: - return ConvertRainINToUser(clicks * 0.01); - } - } - } - - private static bool CheckIpValid(string strIp) - { - if (string.IsNullOrEmpty(strIp)) - return false; - // Split string by ".", check that array length is 4 - var arrOctets = strIp.Split('.'); - if (arrOctets.Length != 4) - return false; - - //Check each substring checking that parses to byte - byte result; - return arrOctets.All(strOctet => byte.TryParse(strOctet, out result)); - } - - 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(); - } - - public override void startReadingHistoryData() - { - cumulus.CurrentActivity = "Reading archive data"; - cumulus.LogMessage("WLL history: Reading history data from log files"); - LoadLastHoursFromDataLogs(cumulus.LastUpdateTime); - - cumulus.LogMessage("WLL history: Reading archive data from WeatherLink API"); - bw = new BackgroundWorker(); - //histprog = new historyProgressWindow(); - //histprog.Owner = mainWindow; - //histprog.Show(); - bw.DoWork += bw_ReadHistory; - //bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged); - bw.RunWorkerCompleted += bw_ReadHistoryCompleted; - bw.WorkerReportsProgress = true; - bw.RunWorkerAsync(); - } - - private void bw_ReadHistoryCompleted(object sender, RunWorkerCompletedEventArgs e) - { - cumulus.LogMessage("WLL history: WeatherLink API archive reading thread completed"); - if (e.Error != null) - { - cumulus.LogMessage("WLL history: Archive reading thread apparently terminated with an error: " + e.Error.Message); - } - cumulus.LogMessage("WLL history: Updating highs and lows"); - //using (cumulusEntities dataContext = new cumulusEntities()) - //{ - // UpdateHighsAndLows(dataContext); - //} - cumulus.CurrentActivity = "Normal running"; - - // restore settings - cumulus.StationOptions.UseSpeedForAvgCalc = savedUseSpeedForAvgCalc; - CalcRecentMaxGust = savedCalculatePeakGust; - - StartLoop(); - DoDayResetIfNeeded(); - DoTrendValues(DateTime.Now); - cumulus.StartTimersAndSensors(); - } - - /* - private void bw_DoStart(object sender, DoWorkEventArgs e) - { - cumulus.LogDebugMessage("Lock: Station waiting for lock"); - Cumulus.syncInit.Wait(); - cumulus.LogDebugMessage("Lock: Station has the lock"); - - // Wait a short while for Cumulus initialisation to complete - Thread.Sleep(500); - StartLoop(); - - cumulus.LogDebugMessage("Lock: Station releasing lock"); - Cumulus.syncInit.Release(); - } - */ - - private void bw_ReadHistory(object sender, DoWorkEventArgs e) - { - int archiveRun = 0; - cumulus.LogDebugMessage("Lock: Station waiting for the lock"); - Cumulus.syncInit.Wait(); - cumulus.LogDebugMessage("Lock: Station has the lock"); - - try - { - // set this temporarily, so speed is done from average and not peak gust from logger - savedUseSpeedForAvgCalc = cumulus.StationOptions.UseSpeedForAvgCalc; - cumulus.StationOptions.UseSpeedForAvgCalc = true; - - // same for gust values - savedCalculatePeakGust = CalcRecentMaxGust; - CalcRecentMaxGust = true; - - // Configure a web proxy if required - if (!string.IsNullOrEmpty(cumulus.HTTPProxyName)) - { - HistoricHttpHandler.Proxy = new WebProxy(cumulus.HTTPProxyName, cumulus.HTTPProxyPort); - HistoricHttpHandler.UseProxy = true; - if (!string.IsNullOrEmpty(cumulus.HTTPProxyUser)) - { - HistoricHttpHandler.Credentials = new NetworkCredential(cumulus.HTTPProxyUser, cumulus.HTTPProxyPassword); - } - } - - do - { - GetWlHistoricData(); - archiveRun++; - } while (archiveRun < maxArchiveRuns); - } - catch (Exception ex) - { - cumulus.LogMessage("Exception occurred reading archive data: " + ex.Message); - } - cumulus.LogDebugMessage("Lock: Station releasing the lock"); - Cumulus.syncInit.Release(); - } - - private void GetWlHistoricData() - { - cumulus.LogMessage("GetWlHistoricData: Get WL.com Historic Data"); - - if (cumulus.WllApiKey == string.Empty || cumulus.WllApiSecret == string.Empty) - { - cumulus.LogMessage("GetWlHistoricData: Missing WeatherLink API data in the configuration, aborting!"); - cumulus.LastUpdateTime = DateTime.Now; - return; - } - - if (cumulus.WllStationId < 10) - { - const string msg = "No WeatherLink API station ID in the configuration"; - cumulus.LogMessage(msg); - cumulus.LogConsoleMessage("GetWlHistoricData: " + msg); - - } - - //int passCount; - //const int maxPasses = 4; - - var unixDateTime = Utils.ToUnixTime(DateTime.Now); - var startTime = Utils.ToUnixTime(cumulus.LastUpdateTime); - int endTime = unixDateTime; - int unix24hrs = 24 * 60 * 60; - - // The API call is limited to fetching 24 hours of data - if (unixDateTime - startTime > unix24hrs) - { - // only fetch 24 hours worth of data, and schedule another run to fetch the rest - endTime = startTime + unix24hrs; - maxArchiveRuns++; - } - - cumulus.LogConsoleMessage($"Downloading Historic Data from WL.com from: {cumulus.LastUpdateTime:s} to: {Utils.FromUnixTime(endTime):s}"); - cumulus.LogMessage($"GetWlHistoricData: Downloading Historic Data from WL.com from: {cumulus.LastUpdateTime:s} to: {Utils.FromUnixTime(endTime):s}"); - - SortedDictionary parameters = new SortedDictionary - { - { "api-key", cumulus.WllApiKey }, - { "station-id", cumulus.WllStationId.ToString() }, - { "t", unixDateTime.ToString() }, - { "start-timestamp", startTime.ToString() }, - { "end-timestamp", endTime.ToString() } - }; - - StringBuilder dataStringBuilder = new StringBuilder(); - foreach (KeyValuePair entry in parameters) - { - dataStringBuilder.Append(entry.Key); - dataStringBuilder.Append(entry.Value); - } - - string data = dataStringBuilder.ToString(); - - var apiSignature = WlDotCom.CalculateApiSignature(cumulus.WllApiSecret, data); - - parameters.Remove("station-id"); - parameters.Add("api-signature", apiSignature); - - StringBuilder historicUrl = new StringBuilder(); - historicUrl.Append("https://api.weatherlink.com/v2/historic/" + cumulus.WllStationId + "?"); - foreach (KeyValuePair entry in parameters) - { - historicUrl.Append(entry.Key); - historicUrl.Append("="); - historicUrl.Append(entry.Value); - historicUrl.Append("&"); - } - // remove the trailing "&" - historicUrl.Remove(historicUrl.Length - 1, 1); - - var logUrl = historicUrl.ToString().Replace(cumulus.WllApiKey, "<>"); - cumulus.LogDebugMessage($"WeatherLink URL = {logUrl}"); - - lastDataReadTime = cumulus.LastUpdateTime; - int luhour = lastDataReadTime.Hour; - - int rollHour = Math.Abs(cumulus.GetHourInc()); - - cumulus.LogMessage($"Rollover hour = {rollHour}"); - - bool rolloverdone = luhour == rollHour; - - bool midnightraindone = luhour == 0; - - WlHistory histObj; - int noOfRecs = 0; - WlHistorySensor sensorWithMostRecs; - - try - { - string responseBody; - int responseCode; - - // we want to do this synchronously, so .Result - using (HttpResponseMessage response = wlHttpClient.GetAsync(historicUrl.ToString()).Result) - { - responseBody = responseBody = response.Content.ReadAsStringAsync().Result; - responseCode = (int)response.StatusCode; - cumulus.LogDebugMessage($"GetWlHistoricData: WeatherLink API Historic Response code: {responseCode}"); - cumulus.LogDataMessage($"GetWlHistoricData: WeatherLink API Historic Response: {responseBody}"); - } - - if (responseCode != 200) - { - var historyError = responseBody.FromJson(); - cumulus.LogMessage($"GetWlHistoricData: WeatherLink API Historic Error: {historyError.code}, {historyError.message}"); - cumulus.LogConsoleMessage($" - Error {historyError.code}: {historyError.message}"); - cumulus.LastUpdateTime = Utils.FromUnixTime(endTime); - return; - } - - histObj = responseBody.FromJson(); - - if (responseBody == "{}") - { - cumulus.LogMessage("GetWlHistoricData: WeatherLink API Historic: No data was returned. Check your Device Id."); - cumulus.LogConsoleMessage(" - No historic data available"); - cumulus.LastUpdateTime = Utils.FromUnixTime(endTime); - return; - } - else if (responseBody.StartsWith("{\"sensors\":[{\"lsid\"")) // sanity check - { - // get the sensor data - int idxOfSensorWithMostRecs = 0; - for (var i = 0; i < histObj.sensors.Count; i++) - { - // Find the WLL baro, or internal temp/hum sensors - if (histObj.sensors[i].sensor_type == 242 && histObj.sensors[i].data_structure_type == 13) - { - var recs = histObj.sensors[i].data.Count; - if (recs > noOfRecs) - { - noOfRecs = recs; - idxOfSensorWithMostRecs = i; - } - } - } - sensorWithMostRecs = histObj.sensors[idxOfSensorWithMostRecs]; - - if (noOfRecs == 0) - { - cumulus.LogMessage("GetWlHistoricData: No historic data available"); - cumulus.LogConsoleMessage(" - No historic data available"); - cumulus.LastUpdateTime = Utils.FromUnixTime(endTime); - return; - } - else - { - cumulus.LogMessage($"GetWlHistoricData: Found {noOfRecs} historic records to process"); - } - } - else // No idea what we got, dump it to the log - { - cumulus.LogMessage("GetWlHistoricData: Invalid historic message received"); - cumulus.LogDataMessage("GetWlHistoricData: Received: " + responseBody); - cumulus.LastUpdateTime = Utils.FromUnixTime(endTime); - return; - } - } - catch (Exception ex) - { - cumulus.LogMessage("GetWlHistoricData: Exception: " + ex.Message); - cumulus.LastUpdateTime = Utils.FromUnixTime(endTime); - return; - } - - for (int dataIndex = 0; dataIndex < noOfRecs; dataIndex++) - { - try - { - // Not all sensors may have the same number of records. We are using the WLL to create the historic data, the other sensors (AirLink) may have more or less records! - // For the additional sensors, check if they have the same number of reocrds as the WLL. If they do great, we just process the next record. - // If the sensor has more or less historic records than the WLL, then we find the record (if any) that matches the WLL record timestamp - - - var refData = sensorWithMostRecs.data[dataIndex].FromJsv(); - DecodeHistoric(sensorWithMostRecs.data_structure_type, sensorWithMostRecs.sensor_type, sensorWithMostRecs.data[dataIndex]); - var timestamp = Utils.FromUnixTime(refData.ts); - - foreach (var sensor in histObj.sensors) - { - int sensorType = sensor.sensor_type; - int dataStructureType = sensor.data_structure_type; - int lsid = sensor.lsid; - - if (sensorType == 323 && cumulus.airLinkOut != null) // AirLink Outdoor - { - if (sensor.data.Count != noOfRecs) - { - var found = false; - foreach (var dataRec in sensor.data) - { - var rec = dataRec.FromJsv(); - if (rec.ts == refData.ts) - { - // Pass AirLink historic record to the AirLink module to process - cumulus.airLinkOut.DecodeAlHistoric(dataStructureType, dataRec); - found = true; - break; - } - } - if (!found) - cumulus.LogDebugMessage("GetWlHistoricData: Warning. No outdoor Airlink data for this log interval !!"); - } - else - { - // Pass AirLink historic record to the AirLink module to process - cumulus.airLinkOut.DecodeAlHistoric(dataStructureType, sensor.data[dataIndex]); - } - } - else if (sensorType == 326 && cumulus.airLinkIn != null) // AirLink Indoor - { - if (sensor.data.Count != noOfRecs) - { - var found = false; - foreach (var dataRec in sensor.data) - { - var rec = dataRec.FromJsv(); - - if (rec.ts == refData.ts) - { - // Pass AirLink historic record to the AirLink module to process - cumulus.airLinkIn.DecodeAlHistoric(dataStructureType, dataRec); - found = true; - break; - } - } - if (!found) - cumulus.LogDebugMessage("GetWlHistoricData: Warning. No indoor Airlink data for this log interval !!"); - } - else - { - // Pass AirLink historic record to the AirLink module to process - cumulus.airLinkIn.DecodeAlHistoric(dataStructureType, sensor.data[dataIndex]); - } - } - else if (sensorType != 504 && sensorType != 506 && lsid != sensorWithMostRecs.lsid) - { - if (sensor.data.Count > dataIndex) - { - DecodeHistoric(dataStructureType, sensorType, sensor.data[dataIndex]); - // sensor 504 (WLL info) does not always contain a full set of records, so grab the timestamp from a 'real' sensor - } - } - } - - var h = timestamp.Hour; - - if (cumulus.StationOptions.LogExtraSensors) - { - cumulus.DoExtraLogFile(timestamp); - } - - if (cumulus.airLinkOut != null || cumulus.airLinkIn != null) - { - cumulus.DoAirLinkLogFile(timestamp); - } - - // Now we have the primary data, calculate the derived data - if (cumulus.StationOptions.CalculatedWC) - { - // DoWindChill does all the required checks and conversions - DoWindChill(OutdoorTemperature, timestamp); - } - - DoApparentTemp(timestamp); - DoFeelsLike(timestamp); - DoHumidex(timestamp); - - // Log all the data - cumulus.DoLogFile(timestamp, false); - cumulus.LogMessage("GetWlHistoricData: Log file entry written"); - - AddLastHourDataEntry(timestamp, Raincounter, OutdoorTemperature); - AddLast3HourDataEntry(timestamp, Pressure, OutdoorTemperature); - AddGraphDataEntry(timestamp, Raincounter, RainToday, RainRate, OutdoorTemperature, OutdoorDewpoint, ApparentTemperature, WindChill, HeatIndex, - IndoorTemperature, Pressure, WindAverage, RecentMaxGust, AvgBearing, Bearing, OutdoorHumidity, IndoorHumidity, SolarRad, CurrentSolarMax, UV, FeelsLike, Humidex); - AddRecentDataEntry(timestamp, WindAverage, RecentMaxGust, WindLatest, Bearing, AvgBearing, OutdoorTemperature, WindChill, OutdoorDewpoint, HeatIndex, - OutdoorHumidity, Pressure, RainToday, SolarRad, UV, Raincounter, FeelsLike, Humidex); - RemoveOldLHData(timestamp); - RemoveOldL3HData(timestamp); - RemoveOldGraphData(timestamp); - DoTrendValues(timestamp); - UpdateStatusPanel(timestamp); - cumulus.AddToWebServiceLists(timestamp); - - // if outside rollover hour, rollover yet to be done - if (h != rollHour) - { - rolloverdone = false; - } - - // In rollover hour and rollover not yet done - if ((h == rollHour) && !rolloverdone) - { - // do rollover - cumulus.LogMessage("GetWlHistoricData: Day rollover " + timestamp.ToShortTimeString()); - DayReset(timestamp); - rolloverdone = true; - } - - // Not in midnight hour, midnight rain yet to be done - if (h != 0) - { - midnightraindone = false; - } - - // In midnight hour and midnight rain (and sun) not yet done - if ((h == 0) && !midnightraindone) - { - ResetMidnightRain(timestamp); - ResetSunshineHours(); - midnightraindone = true; - } - - - if (!Program.service) - Console.Write("\r - processed " + (((double)dataIndex + 1) / noOfRecs).ToString("P0")); - cumulus.LogMessage($"GetWlHistoricData: {dataIndex + 1} of {noOfRecs} archive entries processed"); - } - catch (Exception ex) - { - cumulus.LogMessage("GetWlHistoricData: Exception: " + ex.Message); - } - } - - if (!Program.service) - Console.WriteLine(""); // flush the progress line - } - - private void DecodeHistoric(int dataType, int sensorType, string json) - { - // The WLL sends the timestamp in Unix ticks, and in UTC - - try - { - switch (dataType) - { - case 11: // ISS data - var data11 = json.FromJsv(); - var recordTs = Utils.FromUnixTime(data11.ts); - //weatherLinkArchiveInterval = data.Value("arch_int"); - - // Temperature & Humidity - if (cumulus.WllPrimaryTempHum == data11.tx_id) - { - /* - * Avaialable fields - * "cooling_degree_days" - * "dew_point_hi_at" - * "dew_point_hi" - * "dew_point_last" - * "dew_point_lo_at" - * "dew_point_lo" - * "heat_index_hi_at" - * "heat_index_hi" - * "heat_index_last" - * "heating_degree_days" - * "hum_hi_at" - * "hum_hi" - * "hum_last" - * "hum_lo_at" - * "hum_lo" - * "temp_avg" - * "temp_hi_at" - * "temp_last" - * "temp_lo_at" - * "temp_lo" - * "temp_max" - * "wind_chill_last" - * "wind_chill_lo_at" - * "wind_chill_lo" - */ - - DateTime ts; - - try - { - if (data11.temp_last == -99) - { - cumulus.LogMessage($"WL.com historic: no valid Primary temperature value found [-99] on TxId {data11.tx_id}"); - } - else - { - cumulus.LogDebugMessage($"WL.com historic: using temp/hum data from TxId {data11.tx_id}"); - - // do high temp - ts = Utils.FromUnixTime(data11.temp_hi_at); - DoOutdoorTemp(ConvertTempFToUser(data11.temp_hi), ts); - // do low temp - ts = Utils.FromUnixTime(data11.temp_lo_at); - DoOutdoorTemp(ConvertTempFToUser(data11.temp_lo), ts); - // do last temp - DoOutdoorTemp(ConvertTempFToUser(data11.temp_last), recordTs); - - // set the values for daily average, arch_int is in seconds, but always whole minutes - tempsamplestoday += data11.arch_int / 60; - TempTotalToday += ConvertTempFToUser(data11.temp_avg) * data11.arch_int / 60; - - // update chill hours - if (OutdoorTemperature < cumulus.ChillHourThreshold) - { - // add 1 minute to chill hours - ChillHours += (data11.arch_int / 60.0); - } - - // update heating/cooling degree days - UpdateDegreeDays(data11.arch_int / 60); - } - } - catch (Exception ex) - { - cumulus.LogMessage($"WL.com historic: Error processing Primary temperature value on TxId {data11.tx_id}. Error: {ex.Message}"); - } - - try - { - // do high humidty - ts = Utils.FromUnixTime(data11.hum_hi_at); - DoOutdoorHumidity(Convert.ToInt32(data11.hum_hi), ts); - // do low humidity - ts = Utils.FromUnixTime(data11.hum_lo_at); - DoOutdoorHumidity(Convert.ToInt32(data11.hum_lo), ts); - // do current humidity - DoOutdoorHumidity(Convert.ToInt32(data11.hum_last), recordTs); - } - catch (Exception ex) - { - cumulus.LogMessage($"WL.com historic: Error processing Primary humidity value on TxId {data11.tx_id}. Error: {ex.Message}"); - } - - try - { - // do high DP - ts = Utils.FromUnixTime(data11.dew_point_hi_at); - DoOutdoorDewpoint(ConvertTempFToUser(data11.dew_point_hi), ts); - // do low DP - ts = Utils.FromUnixTime(data11.dew_point_lo_at); - DoOutdoorDewpoint(ConvertTempFToUser(data11.dew_point_lo), ts); - // do last DP - DoOutdoorDewpoint(ConvertTempFToUser(data11.dew_point_last), recordTs); - } - catch (Exception ex) - { - cumulus.LogMessage($"WL.com historic: Error processing dew point value on TxId {data11.tx_id}. Error: {ex.Message}"); - } - - if (!cumulus.StationOptions.CalculatedWC) - { - // use wind chill from WLL - otherwise we calculate it at the end of processing the historic record when we have all the data - try - { - // do low WC - ts = Utils.FromUnixTime(data11.wind_chill_lo_at); - DoWindChill(ConvertTempFToUser(data11.wind_chill_lo), ts); - // do last WC - DoWindChill(ConvertTempFToUser(data11.wind_chill_last), recordTs); - } - catch (Exception ex) - { - cumulus.LogMessage($"WL.com historic: Error processing wind chill value on TxId {data11.tx_id}. Error: {ex.Message}"); - } - } - } - else - { // Check for Extra temperature/humidity settings - for (var tempTxId = 1; tempTxId <= 8; tempTxId++) - { - if (cumulus.WllExtraTempTx[tempTxId - 1] != data11.tx_id) continue; - - try - { - if (data11.temp_last == -99) - { - cumulus.LogDebugMessage($"WL.com historic: no valid Extra temperature value on TxId {data11.tx_id}"); - } - else - { - cumulus.LogDebugMessage($"WL.com historic: using extra temp data from TxId {data11.tx_id}"); - - DoExtraTemp(ConvertTempFToUser(data11.temp_last), tempTxId); - } - } - catch (Exception ex) - { - cumulus.LogMessage($"WL.com historic: Error processing extra temp value on TxId {data11.tx_id}"); - cumulus.LogDebugMessage($"WL.com historic: Exception {ex.Message}"); - } - - if (!cumulus.WllExtraHumTx[tempTxId - 1]) continue; - - try - { - DoExtraHum(data11.hum_last, tempTxId); - } - catch (Exception ex) - { - cumulus.LogMessage($"WL.com historic: Error processing extra humidity value on TxId {data11.tx_id}. Error: {ex.Message}"); - } - } - } - - // Wind - if (cumulus.WllPrimaryWind == data11.tx_id) - { - /* - * Available fields - * "wind_dir_of_prevail" - * "wind_run" - * "wind_speed_avg" - * "wind_speed_hi_at" - * "wind_speed_hi_dir" - * "wind_speed_hi" - */ - - try - { - cumulus.LogDebugMessage($"WL.com historic: using wind data from TxId {data11.tx_id}"); - DoWind(ConvertWindMPHToUser(data11.wind_speed_hi), data11.wind_speed_hi_dir, ConvertWindMPHToUser(data11.wind_speed_avg), recordTs); - - WindAverage = ConvertWindMPHToUser(data11.wind_speed_avg) * cumulus.Calib.WindSpeed.Mult; - - // add in 'archivePeriod' minutes worth of wind speed to windrun - int interval = data11.arch_int / 60; - WindRunToday += ((WindAverage * WindRunHourMult[cumulus.Units.Wind] * interval) / 60.0); - } - catch (Exception ex) - { - cumulus.LogMessage($"WL.com historic: Error processing wind values on TxId {data11.tx_id}. Error: {ex.Message}"); - } - } - - // Rainfall - if (cumulus.WllPrimaryRain == data11.tx_id) - { - /* - * Available fields: - * "rain_rate_hi_at" - * "rain_rate_hi_clicks" - * "rain_rate_hi_in" - * "rain_rate_hi_mm" - * "rain_size" - * "rainfall_clicks" - * "rainfall_in" - * "rainfall_mm" - */ - - cumulus.LogDebugMessage($"WL.com historic: using rain data from TxId {data11.tx_id}"); - - // The WL API v2 does not provide any running totals for rainfall, only :( - // So we will have to add the interval data to the running total and hope it all works out! - - try - { - var rain = ConvertRainClicksToUser(data11.rainfall_clicks, data11.rain_size); - var rainrate = ConvertRainClicksToUser(data11.rain_rate_hi_clicks, data11.rain_size); - if (rain > 0) - { - cumulus.LogDebugMessage($"WL.com historic: Adding rain {rain.ToString(cumulus.RainFormat)}"); - } - rain += Raincounter; - - if (rainrate < 0) - { - rainrate = 0; - } - - DoRain(rain, rainrate, recordTs); - } - catch (Exception ex) - { - cumulus.LogMessage($"WL.com historic: Error processing rain data on TxId {data11.tx_id}. Error:{ex.Message}"); - } - - } - - // UV - if (cumulus.WllPrimaryUV == data11.tx_id) - { - /* - * Available fields - * "uv_dose" - * "uv_index_avg" - * "uv_index_hi_at" - * "uv_index_hi" - * "uv_volt_last" - */ - try - { - cumulus.LogDebugMessage($"WL.com historic: using UV data from TxId {data11.tx_id}"); - - DoUV(data11.uv_index_avg, recordTs); - } - catch (Exception ex) - { - cumulus.LogMessage($"WL.com historic: Error processing UV value on TxId {data11.tx_id}. Error: {ex.Message}"); - } - - } - - // Solar - if (cumulus.WllPrimarySolar == data11.tx_id) - { - /* - * Available fields - * "solar_energy" - * "solar_rad_avg" - * "solar_rad_hi_at" - * "solar_rad_hi" - * "solar_rad_volt_last" - * "solar_volt_last" - * "et" - */ - try - { - cumulus.LogDebugMessage($"WL.com historic: using solar data from TxId {data11.tx_id}"); - DoSolarRad(data11.solar_rad_avg, recordTs); - } - catch (Exception ex) - { - cumulus.LogMessage($"WL.com historic: Error processing Solar value on TxId {data11.tx_id}. Error: {ex.Message}"); - } - } - break; - - case 13: // Non-ISS data - switch (sensorType) - { - case 56: // Soil + Leaf - var data13 = json.FromJsv(); - - string idx = ""; - /* - * Leaf Wetness - * Avaialable fields - * "wet_leaf_at_1" - * "wet_leaf_hi_1" - * "wet_leaf_hi_2": - * "wet_leaf_hi_at_1" - * "wet_leaf_hi_at_2" - * "wet_leaf_last_1" - * "wet_leaf_last_2" - * "wet_leaf_last_volt_1" - * "wet_leaf_last_volt_2" - * "wet_leaf_lo_1" - * "wet_leaf_lo_2" - * "wet_leaf_lo_at_2" - * "wet_leaf_min_1" - * "wet_leaf_min_2" - */ - - cumulus.LogDebugMessage($"WL.com historic: found Leaf/Soil data on TxId {data13.tx_id}"); - - // We are relying on user configuration here, trap any errors - try - { - if (cumulus.WllExtraLeafTx1 == data13.tx_id) - { - idx = "wet_leaf_last_" + cumulus.WllExtraLeafIdx1; - DoLeafWetness((double)data13[idx], 1); - } - if (cumulus.WllExtraLeafTx2 == data13.tx_id) - { - idx = "wet_leaf_last_" + cumulus.WllExtraLeafIdx2; - DoLeafWetness((double)data13[idx], 2); - } - } - catch (Exception e) - { - cumulus.LogMessage($"Error, DecodeHistoric, LeafWetness txid={data13.tx_id}, idx={idx}: {e.Message}"); - } - /* - * Soil Moisture - * Avaialable fields - * "moist_soil_hi_1" - * "moist_soil_hi_2" - * "moist_soil_hi_3" - * "moist_soil_hi_4" - * "moist_soil_hi_at_1" - * "moist_soil_hi_at_2" - * "moist_soil_hi_at_3" - * "moist_soil_hi_at_4" - * "moist_soil_last_1" - * "moist_soil_last_2" - * "moist_soil_last_3" - * "moist_soil_last_4" - * "moist_soil_last_volt_1" - * "moist_soil_last_volt_2" - * "moist_soil_last_volt_3" - * "moist_soil_last_volt_4" - * "moist_soil_lo_1" - * "moist_soil_lo_2" - * "moist_soil_lo_3" - * "moist_soil_lo_4" - * "moist_soil_lo_at_1" - * "moist_soil_lo_at_2" - * "moist_soil_lo_at_3" - * "moist_soil_lo_at_4" - */ - - try - { - if (cumulus.WllExtraSoilMoistureTx1 == data13.tx_id) - { - idx = "moist_soil_last_" + cumulus.WllExtraSoilMoistureIdx1; - if (data13[idx] == null) - { - cumulus.LogDebugMessage($"WL.com historic: no valid soil moisture #{cumulus.WllExtraSoilMoistureIdx1} on TxId {data13.tx_id}"); - } - else - { - DoSoilMoisture((double)data13[idx], 1); - } - } - if (cumulus.WllExtraSoilMoistureTx2 == data13.tx_id) - { - idx = "moist_soil_last_" + cumulus.WllExtraSoilMoistureIdx2; - if (data13[idx] == null) - { - cumulus.LogDebugMessage($"WL.com historic: no valid soil moisture #{cumulus.WllExtraSoilMoistureIdx2} on TxId {data13.tx_id}"); - } - else - { - DoSoilMoisture((double)data13[idx], 2); - } - } - if (cumulus.WllExtraSoilMoistureTx3 == data13.tx_id) - { - idx = "moist_soil_last_" + cumulus.WllExtraSoilMoistureIdx3; - if (data13[idx] == null) - { - cumulus.LogDebugMessage($"WL.com historic: no valid soil moisture #{cumulus.WllExtraSoilMoistureIdx3} on TxId {data13.tx_id}"); - } - else - { - DoSoilMoisture((double)data13[idx], 3); - } - } - if (cumulus.WllExtraSoilMoistureTx4 == data13.tx_id) - { - idx = "moist_soil_last_" + cumulus.WllExtraSoilMoistureIdx4; - if (data13[idx] == null) - { - cumulus.LogDebugMessage($"WL.com historic: no valid soil moisture #{cumulus.WllExtraSoilMoistureIdx4} on TxId {data13.tx_id}"); - } - else - { - DoSoilMoisture((double)data13[idx], 4); - } - } - } - catch (Exception e) - { - cumulus.LogMessage($"Error, DecodeHistoric, SoilMoisture txid={data13.tx_id}, idx={idx}: {e.Message}"); - } - - /* - * Soil Temperature - * Avaialble fields - * "temp_hi_1" - * "temp_hi_2" - * "temp_hi_3" - * "temp_hi_4" - * "temp_hi_at_1" - * "temp_hi_at_2" - * "temp_hi_at_3" - * "temp_hi_at_4" - * "temp_last_1" - * "temp_last_2" - * "temp_last_3" - * "temp_last_4" - * "temp_last_volt_1" - * "temp_last_volt_2" - * "temp_last_volt_3" - * "temp_last_volt_4" - * "temp_lo_1" - * "temp_lo_2" - * "temp_lo_3" - * "temp_lo_4" - * "temp_lo_at_1" - * "temp_lo_at_2" - * "temp_lo_at_3" - * "temp_lo_at_4" - */ - - try - { - if (cumulus.WllExtraSoilTempTx1 == data13.tx_id) - { - idx = "temp_last_" + cumulus.WllExtraSoilTempIdx1; - if (data13[idx] == null) - { - cumulus.LogDebugMessage($"WL.com historic: no valid extra soil temp #{cumulus.WllExtraSoilTempIdx1} on TxId {data13.tx_id}"); - } - else - { - DoSoilTemp(ConvertTempFToUser((double)data13[idx]), 1); - } - } - if (cumulus.WllExtraSoilTempTx2 == data13.tx_id) - { - idx = "temp_last_" + cumulus.WllExtraSoilTempIdx2; - if (data13[idx] == null) - { - cumulus.LogDebugMessage($"WL.com historic: no valid extra soil temp #{cumulus.WllExtraSoilTempIdx2} on TxId {data13.tx_id}"); - } - else - { - DoSoilTemp(ConvertTempFToUser((double)data13[idx]), 2); - } - } - if (cumulus.WllExtraSoilTempTx3 == data13.tx_id) - { - idx = "temp_last_" + cumulus.WllExtraSoilTempIdx3; - if (data13[idx] == null) - { - cumulus.LogDebugMessage($"WL.com historic: no valid extra soil temp #{cumulus.WllExtraSoilTempIdx3} on TxId {data13.tx_id}"); - } - else - { - DoSoilTemp(ConvertTempFToUser((double)data13[idx]), 3); - } - } - if (cumulus.WllExtraSoilTempTx4 == data13.tx_id) - { - idx = "temp_last_" + cumulus.WllExtraSoilTempIdx4; - if (data13[idx] == null) - { - cumulus.LogDebugMessage($"WL.com historic: no valid extra soil temp #{cumulus.WllExtraSoilTempIdx4} on TxId {data13.tx_id}"); - } - else - { - DoSoilTemp(ConvertTempFToUser((double)data13[idx]), 4); - } - } - } - catch (Exception e) - { - cumulus.LogMessage($"Error, DecodeHistoric, SoilTemp txid={data13.tx_id}, idx={idx}: {e.Message}"); - } - - break; - - case 242: // Baro - /* - * Available fields - * "bar_absolute" - * "bar_hi_at" - * "bar_sea_level" - * "arch_int" - * "bar_lo" - * "bar_hi" - * "bar_lo_at" - */ - // log the current value - cumulus.LogDebugMessage("WL.com historic: found Baro data"); - try - { - var data13baro = json.FromJsv(); - // check the high - var ts = Utils.FromUnixTime(data13baro.bar_hi_at); - DoPressure(ConvertPressINHGToUser(data13baro.bar_hi), ts); - // check the low - ts = Utils.FromUnixTime(data13baro.bar_lo_at); - DoPressure(ConvertPressINHGToUser(data13baro.bar_lo), ts); - // leave it at current value - ts = Utils.FromUnixTime(data13baro.ts); - DoPressure(ConvertPressINHGToUser(data13baro.bar_sea_level), ts); - DoPressTrend("Pressure trend"); - // Altimeter from absolute - StationPressure = ConvertPressINHGToUser(data13baro.bar_absolute); - // Or do we use calibration? The VP2 code doesn't? - //StationPressure = ConvertPressINHGToUser(data.Value("bar_absolute")) * cumulus.Calib.Press.Mult + cumulus.Calib.Press.Offset; - AltimeterPressure = ConvertPressMBToUser(StationToAltimeter(ConvertUserPressureToHPa(StationPressure), AltitudeM(cumulus.Altitude))); - } - catch (Exception ex) - { - cumulus.LogMessage($"WL.com historic: Error processing baro reading. Error: {ex.Message}"); - } - break; - - case 243: // Inside temp/hum - /* - * Avilable fields - * "dew_point_in" - * "heat_index_in" - * "hum_in_hi" - * "hum_in_hi_at" - * "hum_in_last" - * "hum_in_lo" - * "hum_in_lo_at" - * "temp_in_hi" - * "temp_in_hi_at" - * "temp_in_last" - * "temp_in_lo" - * "temp_in_lo_at" - */ - cumulus.LogDebugMessage("WL.com historic: found inside temp/hum data"); - - var data13temp = json.FromJsv(); - try - { - DoIndoorTemp(ConvertTempFToUser(data13temp.temp_in_last)); - } - catch (Exception ex) - { - cumulus.LogMessage($"WL.com historic: Error processing temp-in reading. Error: {ex.Message}]"); - } - - - try - { - DoIndoorHumidity(Convert.ToInt32(data13temp.hum_in_last)); - } - catch (Exception ex) - { - cumulus.LogDebugMessage($"WLL current: Error processing humidity-in. Error: {ex.Message}]"); - } - - break; - - default: - cumulus.LogDebugMessage($"WL.com historic: found an unknown sensor type [{sensorType}]!"); - break; - - } - break; - - case 17: // AirLink - break; - - default: - cumulus.LogDebugMessage($"WL.com historic: found an unknown data structure type [{dataType}]!"); - break; - } - } - catch (Exception e) - { - cumulus.LogMessage($"Error, DecodeHistoric, DataType={dataType}, SensorType={sensorType}: " + e.Message); - } - } - - private void DecodeWlApiHealth(WlHistorySensor sensor, bool startingup) - { - if (sensor.data.Count == 0) - { - if (sensor.data_structure_type == 15) - { - cumulus.LogDebugMessage("WLL Health - did not find any health data for WLL device"); - } - else if (sensor.data_structure_type == 11) - { - cumulus.LogDebugMessage("WLL Health - did not find health data for ISS device"); - } - return; - } - - if (sensor.data_structure_type == 15) - { - /* WLL Device - * - * Available fields - * "battery_voltage" - * "bgn" - historic only - * "bluetooth_version" - historic only - * "bootloader_version" - * "dns_type_used" - historic only - * "espressif_version" - * "firmware_version" - * "health_version" - * "input_voltage" - * "ip_address_type" - * "ip_v4_address" - * "ip_v4_gateway" - * "ip_v4_netmask" - * "link_uptime" - * "local_api_queries" - * "network_error" - * "network_type": - * "radio_version" - * "rapid_records_sent" - * "rx_bytes" - * "touchpad_wakeups" - * "tx_bytes" - * "uptime" - * "wifi_rssi" - * "ts" - historic only - */ - - cumulus.LogDebugMessage("WLL Health - found health data for WLL device"); - - try - { - var data15 = sensor.data.Last().FromJsv(); - - var dat = Utils.FromUnixTime(data15.firmware_version); - DavisFirmwareVersion = dat.ToUniversalTime().ToString("yyyy-MM-dd"); - - var battV = data15.battery_voltage / 1000.0; - ConBatText = battV.ToString("F2"); - if (battV < 5.2) - { - wllVoltageLow = true; - cumulus.LogMessage($"WLL WARNING: Backup battery voltage is low = {battV:0.##}V"); - } - else - { - wllVoltageLow = false; - cumulus.LogDebugMessage($"WLL Battery Voltage = {battV:0.##}V"); - } - var inpV = data15.input_voltage / 1000.0; - ConSupplyVoltageText = inpV.ToString("F2"); - if (inpV < 4.0) - { - cumulus.LogMessage($"WLL WARNING: Input voltage is low = {inpV:0.##}V"); - } - else - { - cumulus.LogDebugMessage($"WLL Input Voltage = {inpV:0.##}V"); - } - var upt = TimeSpan.FromSeconds(data15.uptime); - var uptStr = string.Format("{0}d:{1:D2}h:{2:D2}m:{3:D2}s", - (int)upt.TotalDays, - upt.Hours, - upt.Minutes, - upt.Seconds); - cumulus.LogDebugMessage("WLL Uptime = " + uptStr); - - // Only present if WiFi attached - if (data15.wifi_rssi.HasValue) - { - DavisTxRssi[0] = data15.wifi_rssi.Value; - cumulus.LogDebugMessage("WLL WiFi RSSI = " + DavisTxRssi[0] + "dB"); - } - - upt = TimeSpan.FromSeconds(data15.link_uptime); - uptStr = string.Format("{0}d:{1:D2}h:{2:D2}m:{3:D2}s", - (int)upt.TotalDays, - upt.Hours, - upt.Minutes, - upt.Seconds); - cumulus.LogDebugMessage("WLL Link Uptime = " + uptStr); - } - catch (Exception ex) - { - cumulus.LogMessage($"WL.com historic: Error processing WLL health. Error: {ex.Message}"); - DavisFirmwareVersion = "???"; - } - - if (startingup) - { - cumulus.LogMessage("WLL FW version = " + DavisFirmwareVersion); - } - else - { - cumulus.LogDebugMessage("WLL FW version = " + DavisFirmwareVersion); - } - } - else if (sensor.data_structure_type == 11) - { - /* ISS - * Available fields of interest to health - * "afc": -1 - * "error_packets": 0 - * "good_packets_streak": 602 - * "reception": 100 - * "resynchs": 0 - * "rssi": -60 - * "supercap_volt_last": null - * "trans_battery_flag": 0 - * "trans_battery": null - * "tx_id": 2 - */ - - try - { - var data11 = sensor.data.Last().FromJsv(); - - cumulus.LogDebugMessage("WLL Health - found health data for ISS device TxId = " + data11.tx_id); - - // Save the archive interval - //weatherLinkArchiveInterval = data.Value("arch_int"); - - // Check battery state 0=Good, 1=Low - SetTxBatteryStatus(data11.tx_id, data11.trans_battery_flag); - if (data11.trans_battery_flag == 1) - { - cumulus.LogMessage($"WLL WARNING: Battery voltage is low in TxId {data11.tx_id}"); - } - else - { - cumulus.LogDebugMessage($"WLL Health: ISS {data11.tx_id}: Battery state is OK"); - } - - //DavisTotalPacketsReceived[txid] = ; // Do not have a value for this - DavisTotalPacketsMissed[data11.tx_id] = data11.error_packets; - DavisNumCRCerrors[data11.tx_id] = data11.error_packets; - DavisNumberOfResynchs[data11.tx_id] = data11.resynchs; - DavisMaxInARow[data11.tx_id] = data11.good_packets_streak; - DavisReceptionPct[data11.tx_id] = data11.reception; - DavisTxRssi[data11.tx_id] = data11.rssi; - - cumulus.LogDebugMessage($"WLL Health: IIS {data11.tx_id}: Errors={DavisTotalPacketsMissed[data11.tx_id]}, CRCs={DavisNumCRCerrors[data11.tx_id]}, Resyncs={DavisNumberOfResynchs[data11.tx_id]}, Streak={DavisMaxInARow[data11.tx_id]}, %={DavisReceptionPct[data11.tx_id]}, RSSI={DavisTxRssi[data11.tx_id]}"); - } - catch (Exception ex) - { - cumulus.LogMessage($"WLL Health: Error processing transmitter health. Error: {ex.Message}"); - } - } - } - - private void HealthTimerTick(object source, ElapsedEventArgs e) - { - // Only run every 15 minutes - // The WLL only reports its health every 15 mins, on the hour, :15, :30 and :45 - // We run at :01, :16, :31, :46 to allow time for wl.com to generate the stats - if (DateTime.Now.Minute % 15 == 1) - { - GetWlHistoricHealth(); - var msg = string.Format("WLL: Percentage good packets received from WLL {0:F2}% - ({1},{2})", (multicastsGood / (float)(multicastsBad + multicastsGood) * 100), multicastsBad, multicastsGood); - cumulus.LogMessage(msg); - } - } - - // Extracts health infomation from the last archive record - private void GetWlHistoricHealth() - { - cumulus.LogMessage("WLL Health: Get WL.com Historic Data"); - - if (cumulus.WllApiKey == string.Empty || cumulus.WllApiSecret == string.Empty) - { - cumulus.LogMessage("WLL Health: Missing WeatherLink API data in the cumulus.ini file, aborting!"); - return; - } - - if (cumulus.WllStationId < 10) - { - const string msg = "No WeatherLink API station ID in the cumulus.ini file"; - cumulus.LogConsoleMessage("GetWlHistoricHealth: " + msg); - cumulus.LogMessage($"WLL Health: {msg}, aborting!"); - return; - } - - var unixDateTime = Utils.ToUnixTime(DateTime.Now); - var startTime = unixDateTime - weatherLinkArchiveInterval; - int endTime = unixDateTime; - - cumulus.LogDebugMessage($"WLL Health: Downloading the historic record from WL.com from: {Utils.FromUnixTime(startTime):s} to: {Utils.FromUnixTime(endTime):s}"); - - SortedDictionary parameters = new SortedDictionary - { - { "api-key", cumulus.WllApiKey }, - { "station-id", cumulus.WllStationId.ToString() }, - { "t", unixDateTime.ToString() }, - { "start-timestamp", startTime.ToString() }, - { "end-timestamp", endTime.ToString() } - }; - - StringBuilder dataStringBuilder = new StringBuilder(); - foreach (KeyValuePair entry in parameters) - { - dataStringBuilder.Append(entry.Key); - dataStringBuilder.Append(entry.Value); - } - - string data = dataStringBuilder.ToString(); - - var apiSignature = WlDotCom.CalculateApiSignature(cumulus.WllApiSecret, data); - - parameters.Remove("station-id"); - parameters.Add("api-signature", apiSignature); - - StringBuilder historicUrl = new StringBuilder(); - historicUrl.Append("https://api.weatherlink.com/v2/historic/" + cumulus.WllStationId + "?"); - foreach (KeyValuePair entry in parameters) - { - historicUrl.Append(entry.Key); - historicUrl.Append("="); - historicUrl.Append(entry.Value); - historicUrl.Append("&"); - } - // remove the trailing "&" - historicUrl.Remove(historicUrl.Length - 1, 1); - - var logUrl = historicUrl.ToString().Replace(cumulus.WllApiKey, "<>"); - cumulus.LogDebugMessage($"WLL Health: WeatherLink URL = {logUrl}"); - - try - { - // we want to do this synchronously, so .Result - WlHistory histObj; - string responseBody; - int responseCode; - - using (HttpResponseMessage response = wlHttpClient.GetAsync(historicUrl.ToString()).Result) - { - responseBody = response.Content.ReadAsStringAsync().Result; - responseCode = (int)response.StatusCode; - cumulus.LogDataMessage($"WLL Health: WeatherLink API Response: {responseCode} - {responseBody}"); - } - - if (responseCode != 200) - { - var errObj = responseBody.FromJson(); - cumulus.LogMessage($"WLL Health: WeatherLink API Error: {errObj.code}, {errObj.message}"); - // Get wl.com status - GetSystemStatus(); - return; - } - - if (responseBody == "{}") - { - cumulus.LogMessage("WLL Health: WeatherLink API: No data was returned. Check your Device Id."); - cumulus.LastUpdateTime = Utils.FromUnixTime(endTime); - // Get wl.com status - GetSystemStatus(); - return; - } - - if (!responseBody.StartsWith("{\"sensors\":[{\"lsid\"")) // sanity check - { - // No idea what we got, dump it to the log - cumulus.LogMessage("WLL Health: Invalid historic message received"); - cumulus.LogDataMessage("WLL Health: Received: " + responseBody); - return; - } - - histObj = responseBody.FromJson(); - - // get the sensor data - if (histObj.sensors.Count == 0) - { - cumulus.LogMessage("WLL Health: No historic data available"); - return; - } - else - { - cumulus.LogDebugMessage($"WLL Health: Found {histObj.sensors.Count} sensor records to process"); - } - - - try - { - // Sensor types we are interested in... - // 504 = WLL Health - // 506 = AirLink Health - - // Get the LSID of the health station associated with each device - //var wllHealthLsid = GetWlHistoricHealthLsid(cumulus.WllParentId, 504); - var alInHealthLsid = GetWlHistoricHealthLsid(cumulus.airLinkInLsid, 506); - var alOutHealthLsid = GetWlHistoricHealthLsid(cumulus.airLinkOutLsid, 506); - - foreach (var sensor in histObj.sensors) - { - var sensorType = sensor.sensor_type; - var dataStructureType = sensor.data_structure_type; - var lsid = sensor.lsid; - - switch (sensorType) - { - // AirLink Outdoor - case 506 when lsid == alOutHealthLsid: - // Pass AirLink historic record to the AirLink module to process - if (cumulus.airLinkOut != null) - cumulus.airLinkOut.DecodeWlApiHealth(sensor, true); - break; - // AirLink Indoor - case 506 when lsid == alInHealthLsid: - // Pass AirLink historic record to the AirLink module to process - if (cumulus.airLinkIn != null) - cumulus.airLinkIn.DecodeWlApiHealth(sensor, true); - break; - default: - if (sensorType == 504 || dataStructureType == 11) - { - // Either a WLL (504) or ISS (data type = 11) record - DecodeWlApiHealth(sensor, true); - } - break; - } - } - } - catch (Exception ex) - { - cumulus.LogMessage("WLL Health: exception: " + ex.Message); - } - cumulus.BatteryLowAlarm.Triggered = TxBatText.Contains("LOW") || wllVoltageLow; - } - catch (Exception ex) - { - cumulus.LogMessage("WLL Health: exception: " + ex.Message); - } - - } - - // Finds all stations associated with this API - // Return true if only 1 result is found, else return false - private void GetAvailableStationIds(bool logToConsole = false) - { - var unixDateTime = Utils.ToUnixTime(DateTime.Now); - - if (cumulus.WllApiKey == string.Empty || cumulus.WllApiSecret == string.Empty) - { - cumulus.LogMessage("WLLStations: Missing WeatherLink API data in the cumulus.ini file, aborting!"); - return; - } - - SortedDictionary parameters = new SortedDictionary - { - { "api-key", cumulus.WllApiKey }, - { "t", unixDateTime.ToString() } - }; - - StringBuilder dataStringBuilder = new StringBuilder(); - foreach (KeyValuePair entry in parameters) - { - dataStringBuilder.Append(entry.Key); - dataStringBuilder.Append(entry.Value); - } - string header = dataStringBuilder.ToString(); - - var apiSignature = WlDotCom.CalculateApiSignature(cumulus.WllApiSecret, header); - parameters.Add("api-signature", apiSignature); - - StringBuilder stationsUrl = new StringBuilder(); - stationsUrl.Append("https://api.weatherlink.com/v2/stations?"); - foreach (KeyValuePair entry in parameters) - { - stationsUrl.Append(entry.Key); - stationsUrl.Append("="); - stationsUrl.Append(entry.Value); - stationsUrl.Append("&"); - } - // remove the trailing "&" - stationsUrl.Remove(stationsUrl.Length - 1, 1); - - var logUrl = stationsUrl.ToString().Replace(cumulus.WllApiKey, "<>"); - cumulus.LogDebugMessage($"WLLStations: URL = {logUrl}"); - - try - { - // We want to do this synchronously - string responseBody; - int responseCode; - - using (HttpResponseMessage response = wlHttpClient.GetAsync(stationsUrl.ToString()).Result) - { - responseBody = response.Content.ReadAsStringAsync().Result; - responseCode = (int)response.StatusCode; - cumulus.LogDebugMessage($"WLLStations: WeatherLink API Response: {responseCode}: {responseBody}"); - } - - if (responseCode != 200) - { - var errObj = responseBody.FromJson(); - cumulus.LogMessage($"WLLStations: WeatherLink API Error: {errObj.code} - {errObj.message}"); - return; - } - - var stationsObj = responseBody.FromJson(); - - foreach (var station in stationsObj.stations) - { - cumulus.LogMessage($"WLLStations: Found WeatherLink station id = {station.station_id}, name = {station.station_name}"); - if (stationsObj.stations.Count > 1 && logToConsole) - { - cumulus.LogConsoleMessage($" - Found WeatherLink station id = {station.station_id}, name = {station.station_name}, active = {station.active}"); - } - if (station.station_id == cumulus.WllStationId) - { - cumulus.LogDebugMessage($"WLLStations: Setting WLL parent ID = {station.gateway_id}"); - cumulus.WllParentId = station.gateway_id; - - if (station.recording_interval != cumulus.logints[cumulus.DataLogInterval]) - { - cumulus.LogMessage($"WLLStations: - Cumulus log interval {cumulus.logints[cumulus.DataLogInterval]} does not match this WeatherLink stations log interval {station.recording_interval}"); - } - } - } - if (stationsObj.stations.Count > 1 && cumulus.WllStationId < 10) - { - if (logToConsole) - cumulus.LogConsoleMessage(" - Enter the required station id from the above list into your WLL configuration to enable history downloads."); - } - else if (stationsObj.stations.Count == 1 && cumulus.WllStationId != stationsObj.stations[0].station_id) - { - cumulus.LogMessage($"WLLStations: Only found 1 WeatherLink station, using id = {stationsObj.stations[0].station_id}"); - cumulus.WllStationId = stationsObj.stations[0].station_id; - // And save it to the config file - cumulus.WriteIniFile(); - - cumulus.LogDebugMessage($"WLLStations: Setting WLL parent ID = {stationsObj.stations[0].gateway_id}"); - cumulus.WllParentId = stationsObj.stations[0].gateway_id; - return; - } - } - catch (Exception ex) - { - cumulus.LogDebugMessage("WLLStations: WeatherLink API exception: " + ex.Message); - } - return; - } - - private void GetAvailableSensors() - { - var unixDateTime = Utils.ToUnixTime(DateTime.Now); - - if (cumulus.WllApiKey == string.Empty || cumulus.WllApiSecret == string.Empty) - { - cumulus.LogMessage("GetAvailableSensors: WeatherLink API data is missing in the configuration, aborting!"); - return; - } - - if (cumulus.WllStationId < 10) - { - cumulus.LogMessage("GetAvailableSensors: No WeatherLink API station ID has been configured, aborting!"); - return; - } - - SortedDictionary parameters = new SortedDictionary - { - { "api-key", cumulus.WllApiKey }, - { "t", unixDateTime.ToString() } - }; - - StringBuilder dataStringBuilder = new StringBuilder(); - foreach (KeyValuePair entry in parameters) - { - dataStringBuilder.Append(entry.Key); - dataStringBuilder.Append(entry.Value); - } - string header = dataStringBuilder.ToString(); - - var apiSignature = WlDotCom.CalculateApiSignature(cumulus.WllApiSecret, header); - parameters.Add("api-signature", apiSignature); - - StringBuilder stationsUrl = new StringBuilder(); - stationsUrl.Append("https://api.weatherlink.com/v2/sensors?"); - foreach (KeyValuePair entry in parameters) - { - stationsUrl.Append(entry.Key); - stationsUrl.Append("="); - stationsUrl.Append(entry.Value); - stationsUrl.Append("&"); - } - // remove the trailing "&" - stationsUrl.Remove(stationsUrl.Length - 1, 1); - - var logUrl = stationsUrl.ToString().Replace(cumulus.WllApiKey, "<>"); - cumulus.LogDebugMessage($"GetAvailableSensors: URL = {logUrl}"); - - WlSensorList sensorsObj = new WlSensorList(); - - try - { - // We want to do this synchronously - string responseBody; - int responseCode; - - using (HttpResponseMessage response = wlHttpClient.GetAsync(stationsUrl.ToString()).Result) - { - responseBody = response.Content.ReadAsStringAsync().Result; - responseCode = (int)response.StatusCode; - cumulus.LogDebugMessage($"GetAvailableSensors: WeatherLink API Response: {responseCode}: {responseBody}"); - } - - if (responseCode != 200) - { - var errObj = responseBody.FromJson(); - cumulus.LogMessage($"GetAvailableSensors: WeatherLink API Error: {errObj.code} - {errObj.message}"); - return; - } - - sensorsObj = responseBody.FromJson(); - } - catch (Exception ex) - { - cumulus.LogDebugMessage("GetAvailableSensors: WeatherLink API exception: " + ex.Message); - } - - // Sensor types we are interested in... - // 323 = Outdoor AirLink - // 326 = Indoor AirLink - // 504 = WLL Health - // 506 = AirLink Health - var types = new[] { 45, 323, 326, 504, 506 }; - foreach (var sensor in sensorsObj.sensors) - { - try - { - cumulus.LogDebugMessage($"GetAvailableSensors: Found WeatherLink Sensor type={sensor.sensor_type}, lsid={sensor.lsid}, station_id={sensor.station_id}, name={sensor.product_name}, parentId={sensor.parent_device_id}, parent={sensor.parent_device_name}"); - - if (types.Contains(sensor.sensor_type) || sensor.category == "ISS") - { - var wlSensor = new WlSensor(sensor.sensor_type, sensor.lsid, sensor.parent_device_id, sensor.product_name, sensor.parent_device_name); - sensorList.Add(wlSensor); - if (wlSensor.SensorType == 323 && sensor.station_id == cumulus.AirLinkOutStationId) - { - cumulus.LogDebugMessage($"GetAvailableSensors: Setting AirLink Outdoor LSID to {wlSensor.LSID}"); - cumulus.airLinkOutLsid = wlSensor.LSID; - } - else if (wlSensor.SensorType == 326 && sensor.station_id == cumulus.AirLinkInStationId) - { - cumulus.LogDebugMessage($"GetAvailableSensors: Setting AirLink Indoor LSID to {wlSensor.LSID}"); - cumulus.airLinkInLsid = wlSensor.LSID; - } - } - } - catch (Exception ex) - { - cumulus.LogDebugMessage("GetAvailableSensors: Processing sensors exception: " + ex.Message); - } - } - } - - private void BroadcastTimeout(object source, ElapsedEventArgs e) - { - if (broadcastReceived) - { - broadcastReceived = false; - DataStopped = false; - cumulus.DataStoppedAlarm.Triggered = false; - } - else - { - cumulus.LogMessage($"ERROR: No broadcast data received from the WLL for {tmrBroadcastWatchdog.Interval / 1000} seconds"); - DataStopped = true; - cumulus.DataStoppedAlarm.Triggered = true; - // Try and give the broadcasts a kick in case the last command did not get through - GetWllRealtime(null, null); - } - } - - private int GetWlHistoricHealthLsid(int id, int type) - { - try - { - var sensor = sensorList.FirstOrDefault(i => i.LSID == id || i.ParentID == id); - if (sensor != null) - { - var health = sensorList.FirstOrDefault(i => i.ParentID == sensor.ParentID && i.SensorType == type); - if (health != null) - { - return health.LSID; - } - } - } - catch - { } - return 0; - } - - private void GetSystemStatus() - { - WlComSystemStatus status; - try - { - string responseBody; - int responseCode; - - cumulus.LogDebugMessage("GetSystemStatus: Getting WeatherLink.com system status"); - - // we want to do this synchronously, so .Result - using (HttpResponseMessage response = wlHttpClient.GetAsync("https://0886445102835570.hostedstatus.com/1.0/status/600712dea9c1290530967bc6").Result) - { - responseBody = responseBody = response.Content.ReadAsStringAsync().Result; - responseCode = (int)response.StatusCode; - cumulus.LogDebugMessage($"GetSystemStatus: WeatherLink.com system status Response code: {responseCode}"); - cumulus.LogDataMessage($"GetSystemStatus: WeatherLink.com system status Response: {responseBody}"); - } - - if (responseCode != 200) - { - cumulus.LogMessage($"GetSystemStatus: WeatherLink.com system status Error: {responseCode}"); - cumulus.LogConsoleMessage($" - Error {responseCode}"); - return; - } - - status = responseBody.FromJson(); - - if (responseBody == "{}") - { - cumulus.LogMessage("GetSystemStatus: WeatherLink.com system status: No data was returned."); - return; - } - else if (status != null) - { - var msg = $"Weatherlink.com overall System Status: '{status.result.status_overall.status}', Updated: {status.result.status_overall.updated}"; - if (status.result.status_overall.status_code != 100) - { - msg += "Error: "; - cumulus.LogMessage(msg); - Console.WriteLine(msg); - } - else - { - cumulus.LogDebugMessage(msg); - } - // If we are not OK, then find what isn't working - if (status.result.status_overall.status_code != 100) - { - foreach (var subSys in status.result.status) - { - msg = $" wl.com system: {subSys.name}, status: {subSys.status}, updated: {subSys.updated}"; - cumulus.LogMessage(msg); - Console.WriteLine(msg); - } - } - } - else - { - cumulus.LogMessage("GetSystemStatus: Something went wrong!"); - } - - } - catch (Exception ex) - { - cumulus.LogMessage("GetSystemStatus: Exception: " + ex); - return; - } - - return; - } - - private class WllBroadcast - { - public string did { get; set; } - public int ts { get; set; } - public List conditions { get; set; } - } - - private class WllBroadcastRec - { - public string lsid { get; set; } - public int txid { get; set; } - public double wind_speed_last { get; set; } - public int? wind_dir_last { get; set; } - public int rain_size { get; set; } - public double rain_rate_last { get; set; } - public int rain_15_min { get; set; } - public int rain_60_min { get; set; } - public int rain_24_hr { get; set; } - public int rain_storm { get; set; } - public long rain_storm_start_at { get; set; } - public int rainfall_daily { get; set; } - public int rainfall_monthly { get; set; } - public int rainfall_year { get; set; } - public double wind_speed_hi_last_10_min { get; set; } - public int wind_dir_at_hi_speed_last_10_min { get; set; } - } - - // Response from WLL when asked to start multicasting - private class WllBroadcastReqResponse - { - public WllBroadcastReqResponseData data { get; set; } - public string error { get; set; } - } - - private class WllBroadcastReqResponseData - { - public int broadcast_port { get; set; } - public int duration { get; set; } - } - - private class WllCurrent - { - public WllCurrentDevice data { get; set; } - public string error { get; set; } - } - - private class WllCurrentDevice - { - public string did { get; set; } - public long ts { get; set; } - public List conditions { get; set; } // We have no clue what these structures are going to be ahead of time - } - - private class WllCurrentType1 - { - public int lsid { get; set; } - public int data_structure_type { get; set; } - public int txid { get; set; } - public double temp { get; set; } - public double hum { get; set; } - public double dew_point { get; set; } - public double heat_index { get; set; } - public double wind_chill { get; set; } - public double thw_index { get; set; } - public double thsw_index { get; set; } - public double? wind_speed_last { get; set; } - public int? wind_dir_last { get; set; } - public double wind_speed_avg_last_1_min { get; set; } - public double wind_dir_scalar_avg_last_1_min { get; set; } - public double wind_speed_avg_last_2_min { get; set; } - public double wind_dir_scalar_avg_last_2_min { get; set; } - public double wind_speed_hi_last_2_min { get; set; } - public int wind_dir_at_hi_speed_last_2_min { get; set; } - public double? wind_speed_avg_last_10_min { get; set; } - public double wind_dir_scalar_avg_last_10_min { get; set; } - public double wind_speed_hi_last_10_min { get; set; } - public int wind_dir_at_hi_speed_last_10_min { get; set; } - public int rain_size { get; set; } - public double rain_rate_last { get; set; } - public double rain_rate_hi { get; set; } - public double rainfall_last_15_min { get; set; } - public double rain_rate_hi_last_15_min { get; set; } - public double rainfall_last_60_min { get; set; } - public double rainfall_last_24_hr { get; set; } - public int? rain_storm { get; set; } - public long? rain_storm_start_at { get; set; } - public int solar_rad { get; set; } - public double uv_index { get; set; } - public int rx_state { get; set; } - public uint trans_battery_flag { get; set; } - public int rainfall_daily { get; set; } - public int rainfall_monthly { get; set; } - public int rainfall_year { get; set; } - public int rain_storm_last { get; set; } - public long rain_storm_last_start_at { get; set; } - public long rain_storm_last_end_at { get; set; } - } - - private class WllCurrentType2 - { - public int lsid { get; set; } - public int data_structure_type { get; set; } - public int txid { get; set; } - public double temp_1 { get; set; } - public double temp_2 { get; set; } - public double temp_3 { get; set; } - public double temp_4 { get; set; } - public double moist_soil_1 { get; set; } - public double moist_soil_2 { get; set; } - public double moist_soil_3 { get; set; } - public double moist_soil_4 { get; set; } - public double wet_leaf_1 { get; set; } - public double wet_leaf_2 { get; set; } - public int rx_state { get; set; } - public uint trans_battery_flag { get; set; } - public object this[string name] - { - get - { - Type myType = typeof(WllCurrentType2); - PropertyInfo myPropInfo = myType.GetProperty(name); - return myPropInfo.GetValue(this, null); - } - } - } - - // WLL Current Baro - private class WllCurrentType3 - { - public int lsid { get; set; } - public int data_structure_type { get; set; } - public double bar_sea_level { get; set; } - public double bar_trend { get; set; } - public double bar_absolute { get; set; } - } - - // WLL Current internal temp/hum - private class WllCurrentType4 - { - public int lsid { get; set; } - public int data_structure_type { get; set; } - public double temp_in { get; set; } - public double hum_in { get; set; } - public double dew_point_in { get; set; } - public double heat_index_in { get; set; } - } - } +using ServiceStack; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.IO.Ports; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Sockets; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Timers; +using Tmds.MDns; +using Unosquare.Swan; + +namespace CumulusMX +{ + internal class DavisWllStation : WeatherStation + { + private string ipaddr; + private int port; + private int duration; + private readonly System.Timers.Timer tmrRealtime; + private readonly System.Timers.Timer tmrCurrent; + private readonly System.Timers.Timer tmrBroadcastWatchdog; + private readonly System.Timers.Timer tmrHealth; + private readonly object threadSafer = new object(); + private static readonly SemaphoreSlim WebReq = new SemaphoreSlim(1); + private bool startupDayResetIfRequired = true; + private bool savedUseSpeedForAvgCalc; + private bool savedCalculatePeakGust; + private int maxArchiveRuns = 1; + private static readonly HttpClientHandler HistoricHttpHandler = new HttpClientHandler(); + private readonly HttpClient wlHttpClient = new HttpClient(HistoricHttpHandler); + private readonly HttpClient dogsBodyClient = new HttpClient(); + private readonly bool checkWllGustValues; + private bool broadcastReceived; + private int weatherLinkArchiveInterval = 16 * 60; // Used to get historic Health, 16 minutes in seconds only for initial fetch after load + private bool wllVoltageLow; + private bool stop; + private readonly List sensorList = new List(); + private readonly bool useWeatherLinkDotCom = true; + + public DavisWllStation(Cumulus cumulus) : base(cumulus) + { + cumulus.Manufacturer = cumulus.DAVIS; + calculaterainrate = false; + //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"); + + // Override the ServiceStack Deserialization function + // Check which format provided, attempt to parse as datetime or return minValue. + // Formats to use for the different date kinds + string utcTimeFormat = "yyyy-MM-dd'T'HH:mm:ss.fff'Z'"; + string localTimeFormat = "yyyy-MM-dd'T'HH:mm:ss"; + + + ServiceStack.Text.JsConfig.DeSerializeFn = datetimeStr => + { + if (string.IsNullOrWhiteSpace(datetimeStr)) + { + return DateTime.MinValue; + } + + if (datetimeStr.EndsWith("Z") && + DateTime.TryParseExact(datetimeStr, utcTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out DateTime resultUtc)) + { + return resultUtc; + } + else if (!datetimeStr.EndsWith("Z") && + DateTime.TryParseExact(datetimeStr, localTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out DateTime resultLocal)) + { + return resultLocal; + } + + return DateTime.MinValue; + }; + + tmrRealtime = new System.Timers.Timer(); + tmrCurrent = new System.Timers.Timer(); + tmrBroadcastWatchdog = new System.Timers.Timer(); + tmrHealth = new System.Timers.Timer(); + + wlHttpClient.Timeout = TimeSpan.FromSeconds(20); // 20 seconds for internet queries + + // used for kicking real time, and getting current conditions + dogsBodyClient.Timeout = TimeSpan.FromSeconds(10); // 10 seconds for local queries + dogsBodyClient.DefaultRequestHeaders.Add("Connection", "close"); + + // If the user is using the default 10 minute Wind gust, always use gust data from the WLL - simple + if (cumulus.StationOptions.PeakGustMinutes == 10) + { + CalcRecentMaxGust = false; + checkWllGustValues = false; + } + else if (cumulus.StationOptions.PeakGustMinutes > 10) + { + // If the user period is greater that 10 minutes, then Cumulus must calculate Gust values + // but we can check the WLL 10 min gust value in case we missed a gust + CalcRecentMaxGust = true; + checkWllGustValues = true; + } + else + { + // User period is less than 10 minutes, so we cannot use the station 10 min gust values + CalcRecentMaxGust = true; + checkWllGustValues = false; + } + + + // Sanity check - do we have all the info we need? + if (string.IsNullOrEmpty(cumulus.WllApiKey) && string.IsNullOrEmpty(cumulus.WllApiSecret)) + { + // The basic API details have not been supplied + cumulus.LogMessage("WLL - No WeatherLink.com API configuration supplied, just going to work locally"); + cumulus.LogMessage("WLL - Cannot start historic downloads or retrieve health data"); + cumulus.LogConsoleMessage("*** No WeatherLink.com API details supplied. Cannot start historic downloads or retrieve health data"); + useWeatherLinkDotCom = false; + } + else if (string.IsNullOrEmpty(cumulus.WllApiKey) || string.IsNullOrEmpty(cumulus.WllApiSecret)) + { + // One of the API details is missing + if (string.IsNullOrEmpty(cumulus.WllApiKey)) + { + cumulus.LogMessage("WLL - Missing WeatherLink.com API Key"); + cumulus.LogConsoleMessage("*** Missing WeatherLink.com API Key. Cannot start historic downloads or retrieve health data"); + } + else + { + cumulus.LogMessage("WLL - Missing WeatherLink.com API Secret"); + cumulus.LogConsoleMessage("*** Missing WeatherLink.com API Secret. Cannot start historic downloads or retrieve health data"); + } + useWeatherLinkDotCom = false; + } + + if (useWeatherLinkDotCom) + { + // Get wl.com status + GetSystemStatus(); + } + + // Perform Station ID checks - If we have API deatils! + // If the Station ID is missing, this will populate it if the user only has one station associated with the API key + if (useWeatherLinkDotCom && cumulus.WllStationId < 10) + { + var msg = "No WeatherLink API station ID in the cumulus.ini file"; + cumulus.LogMessage(msg); + cumulus.LogConsoleMessage(msg); + + GetAvailableStationIds(true); + } + else if (useWeatherLinkDotCom) + { + GetAvailableStationIds(false); + } + + // Sanity check the station id + if (useWeatherLinkDotCom && cumulus.WllStationId < 10) + { + // API details supplied, but Station Id is still invalid - do not start the station up. + cumulus.LogMessage("WLL - The WeatherLink.com API is enabled, but no Station Id has been configured, not starting the station. Please correct this and restart Cumulus"); + cumulus.LogConsoleMessage("The WeatherLink.com API is enabled, but no Station Id has been configured. Please correct this and restart Cumulus"); + return; + } + + + // Now get the sensors associated with this station + if (useWeatherLinkDotCom) + GetAvailableSensors(); + + // 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 + + const string serviceType = "_weatherlinklive._tcp"; + var serviceBrowser = new ServiceBrowser(); + serviceBrowser.ServiceAdded += OnServiceAdded; + serviceBrowser.ServiceRemoved += OnServiceRemoved; + serviceBrowser.ServiceChanged += OnServiceChanged; + serviceBrowser.QueryParameters.QueryInterval = cumulus.WllBroadcastDuration * 1000 * 4; // query at 4x the multicast time (default 20 mins) + + //Console.WriteLine($"Browsing for type: {serviceType}"); + serviceBrowser.StartBrowse(serviceType); + + cumulus.LogMessage("Attempting to find WLL via zero-config..."); + + // short wait for zero-config + Thread.Sleep(1000); + + DateTime tooOld = new DateTime(0); + + if ((cumulus.LastUpdateTime <= tooOld) || !cumulus.UseDataLogger) + { + // there's nothing in the database, so we haven't got a rain counter + // we can't load the history data, so we'll just have to go live + + timerStartNeeded = true; + LoadLastHoursFromDataLogs(cumulus.LastUpdateTime); + //StartLoop(); + DoDayResetIfNeeded(); + DoTrendValues(DateTime.Now); + + cumulus.LogMessage("Starting Davis WLL"); + StartLoop(); + } + else + { + // Read the data from the WL APIv2 + startReadingHistoryData(); + } + } + + public override void Start() + { + try + { + // Wait for the lock + cumulus.LogDebugMessage("Lock: Station waiting for lock"); + Cumulus.syncInit.Wait(); + cumulus.LogDebugMessage("Lock: Station has the lock"); + + // Create a realtime thread to periodically restart broadcasts + GetWllRealtime(null, null); + tmrRealtime.Elapsed += GetWllRealtime; + tmrRealtime.Interval = cumulus.WllBroadcastDuration * 1000 / 3 * 2; // give the multicasts a kick after 2/3 of the duration (default 200 secs) + tmrRealtime.AutoReset = true; + tmrRealtime.Start(); + + // Create a current conditions thread to poll readings every 30 seconds + GetWllCurrent(null, null); + tmrCurrent.Elapsed += GetWllCurrent; + tmrCurrent.Interval = 30 * 1000; // Every 30 seconds + tmrCurrent.AutoReset = true; + tmrCurrent.Start(); + + if (useWeatherLinkDotCom) + { + // Get the archive data health to do the initial value populations + GetWlHistoricHealth(); + // And reset the fetch interval to 2 minutes + weatherLinkArchiveInterval = 2 * 60; + } + + // short wait for realtime response + Thread.Sleep(1200); + + if (port == 0) + { + cumulus.LogMessage("WLL failed to get broadcast port via realtime request, defaulting to 22222"); + port = cumulus.DavisOptions.TCPPort; + } + else if (port != cumulus.DavisOptions.TCPPort) + { + cumulus.LogMessage($"WLL Discovered broacast port ({port}) is not the same as in the config ({cumulus.DavisOptions.TCPPort}), resetting config to match"); + cumulus.DavisOptions.TCPPort = port; + cumulus.WriteIniFile(); + } + + // Create a broadcast listener + Task.Run(() => + { + using (var udpClient = new UdpClient()) + { + udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, port)); + udpClient.Client.ReceiveTimeout = 4000; // We should get a message every 2.5 seconds + var from = new IPEndPoint(0, 0); + + while (!stop) + { + try + { + var jsonBtye = udpClient.Receive(ref from); + var jsonStr = Encoding.UTF8.GetString(jsonBtye); + if (!stop) // we may be waiting for a broadcast when a shutdown is started + { + DecodeBroadcast(jsonStr); + } + } + catch (SocketException exp) + { + if (exp.SocketErrorCode == SocketError.TimedOut) + { + multicastsBad++; + var msg = string.Format("WLL: Missed a WLL broadcast message. Percentage good packets {0:F2}% - ({1},{2})", (multicastsGood / (float)(multicastsBad + multicastsGood) * 100), multicastsBad, multicastsGood); + cumulus.LogDebugMessage(msg); + } + else + { + cumulus.LogMessage($"WLL: UDP socket exception: {exp.Message}"); + } + } + } + udpClient.Close(); + cumulus.LogMessage("WLL broadcast listener stopped"); + } + }); + + cumulus.LogMessage($"WLL Now listening on broadcast port {port}"); + + // Start a broadcast watchdog to warn if WLL broadcast messages are not being received + tmrBroadcastWatchdog.Elapsed += BroadcastTimeout; + tmrBroadcastWatchdog.Interval = 1000 * 30; // timeout after 30 seconds + tmrBroadcastWatchdog.AutoReset = true; + tmrBroadcastWatchdog.Start(); + + if (useWeatherLinkDotCom) + { + // get the health data every 15 minutes + tmrHealth.Elapsed += HealthTimerTick; + tmrHealth.Interval = 60 * 1000; // Tick every minute + tmrHealth.AutoReset = true; + tmrHealth.Start(); + } + } + catch (ThreadAbortException) + { + } + finally + { + cumulus.LogDebugMessage("Lock: Station releasing lock"); + Cumulus.syncInit.Release(); + } + } + + public override void Stop() + { + cumulus.LogMessage("Closing WLL connections"); + try + { + stop = true; + tmrRealtime.Stop(); + tmrCurrent.Stop(); + tmrBroadcastWatchdog.Stop(); + tmrHealth.Stop(); + StopMinuteTimer(); + } + catch + { + cumulus.LogMessage("Error stopping station timers"); + } + } + + private async void GetWllRealtime(object source, ElapsedEventArgs e) + { + var retry = 2; + + cumulus.LogDebugMessage("GetWllRealtime: GetWllRealtime waiting for lock"); + WebReq.Wait(); + cumulus.LogDebugMessage("GetWllRealtime: GetWllRealtime has the lock"); + + // The WLL will error if already responding to a request from another device, so add a retry + do + { + // Call asynchronous network methods in a try/catch block to handle exceptions + try + { + string ip; + + lock (threadSafer) + { + ip = cumulus.DavisOptions.IPAddr; + } + + if (CheckIpValid(ip)) + { + var urlRealtime = "http://" + ip + "/v1/real_time?duration=" + cumulus.WllBroadcastDuration; + + cumulus.LogDebugMessage($"GetWllRealtime: Sending GET realtime request to WLL: {urlRealtime} ..."); + + string responseBody; + + using (HttpResponseMessage response = await dogsBodyClient.GetAsync(urlRealtime)) + { + responseBody = await response.Content.ReadAsStringAsync(); + responseBody = responseBody.TrimEnd('\r', '\n'); + + cumulus.LogDataMessage("GetWllRealtime: WLL response: " + responseBody); + } + + var respJson = responseBody.FromJson(); + var err = string.IsNullOrEmpty(respJson.error) ? "OK" : respJson.error; + port = respJson.data.broadcast_port; + duration = respJson.data.duration; + cumulus.LogDebugMessage($"GetWllRealtime: GET response Code: {err}, Port: {port}"); + if (cumulus.WllBroadcastDuration != duration) + { + cumulus.LogMessage($"GetWllRealtime: WLL broadcast duration {duration} does not match requested duration {cumulus.WllBroadcastDuration}, continuing to use {cumulus.WllBroadcastDuration}"); + } + if (cumulus.WllBroadcastPort != port) + { + cumulus.LogMessage($"GetWllRealtime: WLL broadcast port {port} does not match default {cumulus.WllBroadcastPort}, resetting to {port}"); + cumulus.WllBroadcastPort = port; + } + } + else + { + cumulus.LogMessage($"GetWllRealtime: Invalid IP address: {ip}"); + } + retry = 0; + } + catch (Exception exp) + { + retry--; + cumulus.LogDebugMessage("GetRealtime: Exception Caught!"); + cumulus.LogDebugMessage($"GetWllRealtime: Message :{exp.Message}"); + Thread.Sleep(2000); + } + } while (retry > 0); + + cumulus.LogDebugMessage("GetWllRealtime: Releasing lock"); + WebReq.Release(); + } + + private async void GetWllCurrent(object source, ElapsedEventArgs e) + { + string ip; + int retry = 1; + + lock (threadSafer) + { + ip = cumulus.DavisOptions.IPAddr; + } + + if (CheckIpValid(ip)) + { + var urlCurrent = $"http://{ip}/v1/current_conditions"; + + cumulus.LogDebugMessage("GetWllCurrent: Waiting for lock"); + WebReq.Wait(); + cumulus.LogDebugMessage("GetWllCurrent: Has the lock"); + + // The WLL will error if already responding to a request from another device, so add a retry + do + { + cumulus.LogDebugMessage($"GetWllCurrent: Sending GET current conditions request {retry} to WLL: {urlCurrent} ..."); + try + { + string responseBody; + using (HttpResponseMessage response = await dogsBodyClient.GetAsync(urlCurrent)) + { + response.EnsureSuccessStatusCode(); + responseBody = await response.Content.ReadAsStringAsync(); + cumulus.LogDataMessage($"GetWllCurrent: response - {responseBody}"); + } + + DecodeCurrent(responseBody); + if (startupDayResetIfRequired) + { + DoDayResetIfNeeded(); + startupDayResetIfRequired = false; + } + retry = 9; + } + catch (Exception ex) + { + retry++; + cumulus.LogMessage("GetWllCurrent: Error processing WLL response"); + if (ex.InnerException == null) + cumulus.LogMessage($"GetWllCurrent: Error: {ex.Message}"); + else + cumulus.LogMessage($"GetWllCurrent: Error: {ex.InnerException.Message}"); + Thread.Sleep(1000); + } + } while (retry < 3); + + cumulus.LogDebugMessage("GetWllCurrent: Releasing lock"); + WebReq.Release(); + } + else + { + cumulus.LogMessage($"GetWllCurrent: Invalid IP address: {ip}"); + } + } + + private void DecodeBroadcast(string broadcastJson) + { + try + { + cumulus.LogDataMessage("WLL Broadcast: " + broadcastJson); + + // sanity check + if (broadcastJson.StartsWith("{\"did\":")) + { + var json = broadcastJson.FromJson(); + // The WLL sends the timestamp in Unix ticks, and in UTC + // rather than rely on the WLL clock being correct, we will use our local time + var dateTime = DateTime.Now; + foreach (var rec in json.conditions) + { + // Wind - All values in MPH + /* Available fields: + * rec["wind_speed_last"] + * rec["wind_dir_last"] + * rec["wind_speed_hi_last_10_min"] + * rec["wind_dir_at_hi_speed_last_10_min"] + */ + if (cumulus.WllPrimaryWind == rec.txid) + { + try + { + // WLL BUG/FEATURE: The WLL sends a null wind direction for calm when the avg speed falls to zero, we use zero + int windDir = rec.wind_dir_last ?? 0; + + // No average in the broadcast data, so use last value from current - allow for calibration + DoWind(ConvertWindMPHToUser(rec.wind_speed_last), windDir, WindAverage / cumulus.Calib.WindSpeed.Mult, dateTime); + + var gust = ConvertWindMPHToUser(rec.wind_speed_hi_last_10_min); + var gustCal = gust * cumulus.Calib.WindGust.Mult; + if (checkWllGustValues) + { + if (gustCal > RecentMaxGust) + { + // See if the station 10 min high speed is higher than our current 10-min max + // ie we missed the high gust + + // Check for spikes, and set highs + if (CheckHighGust(gustCal, rec.wind_dir_at_hi_speed_last_10_min, dateTime)) + { + cumulus.LogDebugMessage("Set max gust from broadcast 10 min high value: " + gustCal.ToString(cumulus.WindFormat) + " was: " + RecentMaxGust.ToString(cumulus.WindFormat)); + + // add to recent values so normal calculation includes this value + WindRecent[nextwind].Gust = gust; // use uncalibrated value + WindRecent[nextwind].Speed = WindAverage / cumulus.Calib.WindSpeed.Mult; + WindRecent[nextwind].Timestamp = dateTime; + nextwind = (nextwind + 1) % MaxWindRecent; + + RecentMaxGust = gustCal; + } + } + } + else if (!CalcRecentMaxGust) + { + // Check for spikes, and set highs + if (CheckHighGust(gustCal, rec.wind_dir_at_hi_speed_last_10_min, dateTime)) + { + RecentMaxGust = gustCal; + } + } + } + catch (Exception ex) + { + cumulus.LogMessage($"WLL broadcast: Error in wind speed found on TxId {rec.txid}"); + cumulus.LogDebugMessage($"WLL broadcast: Exception: {ex.Message}"); + } + } + + // Rain + /* + * All fields are *tip counts* + * Available fields: + * rec["rain_size"] - 0: Reseverved, 1: 0.01", 2: 0.2mm, 3: 0.1mm, 4: 0.001" + * rec["rain_rate_last"] + * rec["rain_15_min"] + * rec["rain_60_min"] + * rec["rain_24_hr"] + * rec["rain_storm"] + * rec["rain_storm_start_at"] + * rec["rainfall_daily"] + * rec["rainfall_monthly"] + * rec["rainfall_year"]) + */ + if (cumulus.WllPrimaryRain != rec.txid) continue; + + try + { + var rain = ConvertRainClicksToUser(rec.rainfall_year, rec.rain_size); + var rainrate = ConvertRainClicksToUser(rec.rain_rate_last, rec.rain_size); + + if (rainrate < 0) + { + rainrate = 0; + } + + DoRain(rain, rainrate, dateTime); + } + catch (Exception ex) + { + cumulus.LogMessage($"WLL broadcast: no valid rainfall found on TxId {rec.txid}"); + cumulus.LogDebugMessage($"WLL broadcast: Exception: {ex.Message}"); + } + } + + json = null; + + UpdateStatusPanel(DateTime.Now); + UpdateMQTT(); + + broadcastReceived = true; + DataStopped = false; + cumulus.DataStoppedAlarm.Triggered = false; + multicastsGood++; + } + else + { + multicastsBad++; + var msg = string.Format("WLL broadcast: Invalid payload in message. Percentage good packets {0:F2}% - ({1},{2})", (multicastsGood / (float)(multicastsBad + multicastsGood) * 100), multicastsBad, multicastsGood); + cumulus.LogMessage(msg); + } + } + catch (Exception exp) + { + cumulus.LogDebugMessage("DecodeBroadcast(): Exception Caught!"); + cumulus.LogDebugMessage("Message :" + exp.Message); + multicastsBad++; + var msg = string.Format("WLL broadcast: Error processing broadcast. Percentage good packets {0:F2}% - ({1},{2})", (multicastsGood / (float)(multicastsBad + multicastsGood) * 100), multicastsBad, multicastsGood); + cumulus.LogMessage(msg); + } + } + + private void DecodeCurrent(string currentJson) + { + try + { + // Convert JSON string to an object + WllCurrent json = currentJson.FromJson(); + + // The WLL sends the timestamp in Unix ticks, and in UTC + // rather than rely on the WLL clock being correct, we will use our local time + //var dateTime = FromUnixTime(data.Value("ts")); + var dateTime = DateTime.Now; + var localSensorContactLost = false; + + foreach (var rec in json.data.conditions) + { + // Yuck, we have to find the data type in the string, then we know how to decode it to the correct object type + int start = rec.IndexOf("data_structure_type:") + "data_structure_type:".Length; + int end = rec.IndexOf(",", start); + + int type = int.Parse(rec.Substring(start, end - start)); + string idx = ""; + + switch (type) + { + case 1: // ISS + var data1 = rec.FromJsv(); + + cumulus.LogDebugMessage($"WLL current: found ISS data on TxId {data1.txid}"); + + // Battery + SetTxBatteryStatus(data1.txid, data1.trans_battery_flag); + + if (data1.rx_state == 2) + { + localSensorContactLost = true; + cumulus.LogMessage($"Warning: Sensor contact lost TxId {data1.txid}; ignoring data from this ISS"); + continue; + } + + + // Temperature & Humidity + if (cumulus.WllPrimaryTempHum == data1.txid) + { + /* Available fields + * "temp": 62.7, // most recent valid temperature **(°F)** + * "hum":1.1, // most recent valid humidity **(%RH)** + * "dew_point": -0.3, // **(°F)** + * "wet_bulb":null, // **(°F)** + * "heat_index": 5.5, // **(°F)** + * "wind_chill": 6.0, // **(°F)** + * "thw_index": 5.5, // **(°F)** + * "thsw_index": 5.5, // **(°F)** + */ + + try + { + cumulus.LogDebugMessage($"WLL current: using temp/hum data from TxId {data1.txid}"); + + DoOutdoorHumidity(Convert.ToInt32(data1.hum), dateTime); + + DoOutdoorTemp(ConvertTempFToUser(data1.temp), dateTime); + + DoOutdoorDewpoint(ConvertTempFToUser(data1.dew_point), dateTime); + + if (!cumulus.StationOptions.CalculatedWC) + { + // use wind chill from WLL + DoWindChill(ConvertTempFToUser(data1.wind_chill), dateTime); + } + + //TODO: Wet Bulb? rec["wet_bulb"] - No, we already have humidity + //TODO: Heat Index? rec["heat_index"] - No, Cumulus always calculates HI + } + catch (Exception ex) + { + cumulus.LogMessage($"WLL current: Error processing temperature values on TxId {data1.txid}"); + cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); + } + } + else + { // Check for Extra temperature/humidity settings + for (var tempTxId = 1; tempTxId <= 8; tempTxId++) + { + if (cumulus.WllExtraTempTx[tempTxId - 1] != data1.txid) continue; + + try + { + if (cumulus.WllExtraTempTx[tempTxId - 1] == data1.txid) + { + if (data1.temp == -99) + { + cumulus.LogDebugMessage($"WLL current: no valid Extra temperature value found [{data1.temp}] on TxId {data1.txid}"); + } + else + { + cumulus.LogDebugMessage($"WLL current: using extra temp data from TxId {data1.txid}"); + + DoExtraTemp(ConvertTempFToUser(data1.temp), tempTxId); + } + + if (cumulus.WllExtraHumTx[tempTxId - 1]) + { + DoExtraHum(data1.hum, tempTxId); + } + } + } + catch (Exception ex) + { + cumulus.LogMessage($"WLL current: Error processing Extra temperature/humidity values on TxId {data1.txid}"); + cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); + } + } + } + + // Wind + if (cumulus.WllPrimaryWind == data1.txid) + { + /* + * Available fields + * "wind_speed_last":2, // most recent valid wind speed **(mph)** + * "wind_dir_last":null, // most recent valid wind direction **(°degree)** + * "wind_speed_avg_last_1_min":4 // average wind speed over last 1 min **(mph)** + * "wind_dir_scalar_avg_last_1_min":15 // scalar average wind direction over last 1 min **(°degree)** + * "wind_speed_avg_last_2_min":42606, // average wind speed over last 2 min **(mph)** + * "wind_dir_scalar_avg_last_2_min": 170.7, // scalar average wind direction over last 2 min **(°degree)** + * "wind_speed_hi_last_2_min":8, // maximum wind speed over last 2 min **(mph)** + * "wind_dir_at_hi_speed_last_2_min":0.0, // gust wind direction over last 2 min **(°degree)** + * "wind_speed_avg_last_10_min":42606, // average wind speed over last 10 min **(mph)** + * "wind_dir_scalar_avg_last_10_min": 4822.5, // scalar average wind direction over last 10 min **(°degree)** + * "wind_speed_hi_last_10_min":8, // maximum wind speed over last 10 min **(mph)** + * "wind_dir_at_hi_speed_last_10_min":0.0, // gust wind direction over last 10 min **(°degree)** + */ + try + { + cumulus.LogDebugMessage($"WLL current: using wind data from TxId {data1.txid}"); + + // pesky null values from WLL when it is calm + int wdir = data1.wind_dir_last ?? 0; + double wind = data1.wind_speed_last ?? 0; + double wspdAvg10min = ConvertWindMPHToUser(data1.wind_speed_avg_last_10_min ?? 0); + + DoWind(ConvertWindMPHToUser(wind), wdir, wspdAvg10min, dateTime); + + WindAverage = wspdAvg10min * cumulus.Calib.WindSpeed.Mult; + + // Wind data can be a bit out of date compared to the broadcasts (1 minute update), so only use gust broadcast data + /* + var gust = ConvertWindMPHToUser(data1.wind_speed_hi_last_10_min); + var gustCal = gust * cumulus.Calib.WindGust.Mult; + + if (checkWllGustValues) + { + // See if the current speed is higher than the current 10-min max + // We can then update the figure before the next data packet is read + + if (gustCal > RecentMaxGust) + { + // Check for spikes, and set highs + if (CheckHighGust(gustCal, data1.wind_dir_at_hi_speed_last_10_min, dateTime)) + { + cumulus.LogDebugMessage("Setting max gust from current 10 min value: " + gustCal.ToString(cumulus.WindFormat) + " was: " + RecentMaxGust.ToString(cumulus.WindFormat)); + + // add to recent values so normal calculation includes this value + WindRecent[nextwind].Gust = gust; // use uncalibrated value + WindRecent[nextwind].Speed = WindAverage / cumulus.Calib.WindSpeed.Mult; + WindRecent[nextwind].Timestamp = dateTime; + nextwind = (nextwind + 1) % cumulus.MaxWindRecent; + + RecentMaxGust = gustCal; + } + } + } + else if (!CalcRecentMaxGust) + { + // Check for spikes, and set highs + if (CheckHighGust(gustCal, data1.wind_dir_at_hi_speed_last_10_min, dateTime)) + { + RecentMaxGust = gustCal; + } + } + */ + } + catch (Exception ex) + { + cumulus.LogMessage($"WLL current: Error processing wind speeds on TxId {data1.txid}"); + cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); + } + } + + + // Rainfall + if (cumulus.WllPrimaryRain == data1.txid) + { + /* + * Available fields: + * rec["rain_size"] - 0: Reseverved, 1: 0.01", 2: 0.2mm, 3: 0.1mm, 4: 0.001" + * rec["rain_rate_last"], rec["rain_rate_hi"] + * rec["rainfall_last_15_min"], rec["rain_rate_hi_last_15_min"] + * rec["rainfall_last_60_min"] + * rec["rainfall_last_24_hr"] + * rec["rainfall_daily"] + * rec["rainfall_monthly"] + * rec["rainfall_year"] + * rec["rain_storm"], rec["rain_storm_start_at"] + * rec["rain_storm_last"], rec["rain_storm_last_start_at"], rec["rain_storm_last_end_at"] + */ + + + cumulus.LogDebugMessage($"WLL current: using rain data from TxId {data1.txid}"); + + var tipSize = data1.rain_size; + switch (tipSize) + { + case 1: + if (cumulus.DavisOptions.RainGaugeType != 1) + { + cumulus.LogMessage($"Setting Davis rain tipper size - was {cumulus.DavisOptions.RainGaugeType}, now 1 = 0.01 in"); + cumulus.DavisOptions.RainGaugeType = 1; + cumulus.WriteIniFile(); + } + break; + case 2: + if (cumulus.DavisOptions.RainGaugeType != 0) + { + cumulus.LogMessage($"Setting Davis rain tipper size - was {cumulus.DavisOptions.RainGaugeType}, now 0 = 0.2 mm"); + cumulus.DavisOptions.RainGaugeType = 0; + cumulus.WriteIniFile(); + } + break; + case 3: + if (cumulus.DavisOptions.RainGaugeType != 2) + { + cumulus.LogMessage($"Setting Davis rain tipper size - was {cumulus.DavisOptions.RainGaugeType}, now 0 = 0.1 mm"); + cumulus.DavisOptions.RainGaugeType = 2; + cumulus.WriteIniFile(); + } + break; + case 4: + if (cumulus.DavisOptions.RainGaugeType != 3) + { + cumulus.LogMessage($"Setting Davis rain tipper size - was {cumulus.DavisOptions.RainGaugeType}, now 0 = 0.001 in"); + cumulus.DavisOptions.RainGaugeType = 2; + cumulus.WriteIniFile(); + } + break; + } + + // Rain data can be a bit out of date compared to the broadcasts (1 minute update), so only use storm data + + // All rainfall values supplied as *tip counts* + //double rain = ConvertRainINToUser((double)rec["rainfall_year"]); + //double rainrate = ConvertRainINToUser((double)rec["rain_rate_last"]); + + //if (rainrate < 0) + //{ + // rainrate = 0; + //} + + //DoRain(rain, rainrate, dateTime); + + if (!data1.rain_storm.HasValue || !data1.rain_storm_start_at.HasValue) + { + cumulus.LogDebugMessage("WLL current: No rain storm values present"); + } + else + { + try + { + StormRain = ConvertRainClicksToUser(data1.rain_storm.Value, data1.rain_size) * cumulus.Calib.Rain.Mult; + StartOfStorm = Utils.FromUnixTime(data1.rain_storm_start_at.Value); + } + catch (Exception ex) + { + cumulus.LogMessage($"WLL current: Error processing rain storm values on TxId {data1.txid}"); + cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); + } + } + } + + if (cumulus.WllPrimaryUV == data1.txid) + { + try + { + cumulus.LogDebugMessage($"WLL current: using UV data from TxId {data1.txid}"); + DoUV(data1.uv_index, dateTime); + } + catch (Exception ex) + { + cumulus.LogMessage($"WLL current: Error processing UV value on TxId {data1.txid}"); + cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); + } + } + + if (cumulus.WllPrimarySolar == data1.txid) + { + try + { + cumulus.LogDebugMessage($"WLL current: using solar data from TxId {data1.txid}"); + DoSolarRad(data1.solar_rad, dateTime); + } + catch (Exception ex) + { + cumulus.LogMessage($"WLL current: Error processing Solar value on TxId {data1.txid}"); + cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); + } + } + break; + + 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)** + */ + + var data2 = rec.FromJsv(); + + cumulus.LogDebugMessage($"WLL current: found Leaf/Soil data on TxId {data2.txid}"); + + // Battery + SetTxBatteryStatus(data2.txid, data2.trans_battery_flag); + + if (data2.rx_state == 2) + { + localSensorContactLost = true; + cumulus.LogMessage($"Warning: Sensor contact lost TxId {data2.txid}; ignoring data from this Leaf/Soil transmitter"); + continue; + } + + // For leaf wetness, soil temp/moisture we rely on user configuration, trap any errors + + // Leaf wetness + try + { + if (cumulus.WllExtraLeafTx1 == data2.txid) + { + idx = "wet_leaf_" + cumulus.WllExtraLeafIdx1; + DoLeafWetness((double)data2[idx], 1); + } + if (cumulus.WllExtraLeafTx2 == data2.txid) + { + idx = "wet_leaf_" + cumulus.WllExtraLeafIdx2; + DoLeafWetness((double)data2[idx], 2); + } + } + catch (Exception e) + { + cumulus.LogMessage($"WLL current: Error processung LeafWetness txid={data2.txid}, idx={idx}"); + cumulus.LogDebugMessage($"WLL current: Exception: {e.Message}"); + } + + // Soil moisture + if (cumulus.WllExtraSoilMoistureTx1 == data2.txid) + { + idx = "moist_soil_" + cumulus.WllExtraSoilMoistureIdx1; + try + { + DoSoilMoisture((double)data2[idx], 1); + } + catch (Exception ex) + { + cumulus.LogMessage($"WLL current: Error processing soil moisture #{cumulus.WllExtraSoilMoistureIdx1} on TxId {data2.txid}"); + cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); + } + } + if (cumulus.WllExtraSoilMoistureTx2 == data2.txid) + { + idx = "moist_soil_" + cumulus.WllExtraSoilMoistureIdx2; + try + { + DoSoilMoisture((double)data2[idx], 2); + } + catch (Exception ex) + { + cumulus.LogMessage($"WLL current: Error processing soil moisture #{cumulus.WllExtraSoilMoistureIdx2} on TxId {data2.txid}"); + cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); + } + } + if (cumulus.WllExtraSoilMoistureTx3 == data2.txid) + { + idx = "moist_soil_" + cumulus.WllExtraSoilMoistureIdx3; + try + { + DoSoilMoisture((double)data2[idx], 3); + } + catch (Exception ex) + { + cumulus.LogMessage($"WLL current: Error processing soil moisture #{cumulus.WllExtraSoilMoistureIdx3} on TxId {data2.txid}"); + cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); + } + } + if (cumulus.WllExtraSoilMoistureTx4 == data2.txid) + { + idx = "moist_soil_" + cumulus.WllExtraSoilMoistureIdx4; + try + { + DoSoilMoisture((double)data2[idx], 4); + } + catch (Exception ex) + { + cumulus.LogMessage($"WLL current: Error processing soil moisture #{cumulus.WllExtraSoilMoistureIdx4} on TxId {data2.txid}"); + cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); + } + } + + // SoilTemperature + if (cumulus.WllExtraSoilTempTx1 == data2.txid) + { + idx = "temp_" + cumulus.WllExtraSoilTempIdx1; + try + { + DoSoilTemp(ConvertTempFToUser((double)data2[idx]), 1); + } + catch (Exception ex) + { + cumulus.LogMessage($"WLL current: Error processing extra soil temp #{cumulus.WllExtraSoilTempIdx1} on TxId {data2.txid}"); + cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); + } + } + if (cumulus.WllExtraSoilTempTx2 == data2.txid) + { + idx = "temp_" + cumulus.WllExtraSoilTempIdx2; + try + { + DoSoilTemp(ConvertTempFToUser((double)data2[idx]), 2); + } + catch (Exception ex) + { + cumulus.LogMessage($"WLL current: Error processing extra soil temp #{cumulus.WllExtraSoilTempIdx2} on TxId {data2.txid}"); + cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); + } + } + if (cumulus.WllExtraSoilTempTx3 == data2.txid) + { + idx = "temp_" + cumulus.WllExtraSoilTempIdx3; + try + { + DoSoilTemp(ConvertTempFToUser((double)data2[idx]), 3); + } + catch (Exception ex) + { + cumulus.LogMessage($"WLL current: Error processing extra soil temp #{cumulus.WllExtraSoilTempIdx3} on TxId {data2.txid}"); + cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); + } + } + if (cumulus.WllExtraSoilTempTx4 == data2.txid) + { + idx = "temp_" + cumulus.WllExtraSoilTempIdx4; + try + { + DoSoilTemp(ConvertTempFToUser((double)data2[idx]), 4); + } + catch (Exception ex) + { + cumulus.LogMessage($"WLL current: Error processing extra soil temp #{cumulus.WllExtraSoilTempIdx4} on TxId {data2.txid}"); + cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); + } + } + + // 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"] + */ + + cumulus.LogDebugMessage("WLL current: found Baro data"); + + try + { + var data3 = rec.FromJsv(); + + DoPressure(ConvertPressINHGToUser(data3.bar_sea_level), dateTime); + DoPressTrend("Pressure trend"); + // Altimeter from absolute + StationPressure = ConvertPressINHGToUser(data3.bar_absolute); + // Or do we use calibration? The VP2 code doesn't? + //StationPressure = ConvertPressINHGToUser(rec.Value("bar_absolute")) * cumulus.Calib.Press.Mult + cumulus.Calib.Press.Offset; + AltimeterPressure = ConvertPressMBToUser(StationToAltimeter(ConvertUserPressureToHPa(StationPressure), AltitudeM(cumulus.Altitude))); + } + catch (Exception ex) + { + cumulus.LogMessage("WLL current: Error processing baro data"); + cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); + } + + break; + + case 4: // WLL Temp/Humidity + /* + * Available fields: + * rec["temp_in"] + * rec["hum_in"] + * rec["dew_point_in"] + * rec["heat_index_in"] + */ + + cumulus.LogDebugMessage("WLL current: found Indoor temp/hum data"); + + var data4 = rec.FromJsv(); + + try + { + DoIndoorTemp(ConvertTempFToUser(data4.temp_in)); + } + catch (Exception ex) + { + cumulus.LogMessage("WLL current: Error processing indoor temp data"); + cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); + } + + try + { + DoIndoorHumidity(Convert.ToInt32(data4.hum_in)); + } + catch (Exception ex) + { + cumulus.LogMessage("WLL current: Error processing indoor humidity data"); + cumulus.LogDebugMessage($"WLL current: Exception: {ex.Message}"); + } + + break; + + default: + cumulus.LogDebugMessage($"WLL current: found an unknown tramsmitter type [{type}]!"); + break; + } + + DoForecast(string.Empty, false); + + UpdateStatusPanel(DateTime.Now); + UpdateMQTT(); + } + + // Now we have the primary data, calculate the derived data + if (cumulus.StationOptions.CalculatedWC) + { + if (ConvertUserWindToMS(WindAverage) < 1.5) + { + // wind speed too low, use the temperature + DoWindChill(OutdoorTemperature, dateTime); + } + else + { + // calculate wind chill from calibrated C temp and calibrated wind in KPH + DoWindChill(ConvertTempCToUser(MeteoLib.WindChill(ConvertUserTempToC(OutdoorTemperature), ConvertUserWindToKPH(WindAverage))), dateTime); + } + } + + DoApparentTemp(dateTime); + DoFeelsLike(dateTime); + DoHumidex(dateTime); + + SensorContactLost = localSensorContactLost; + + // If the station isn't using the logger function for WLL - i.e. no API key, then only alarm on Tx battery status + // otherwise, trigger the alarm when we read the Health data which also contains the WLL backup battery status + if (!cumulus.UseDataLogger) + { + cumulus.BatteryLowAlarm.Triggered = TxBatText.Contains("LOW"); + } + } + catch (Exception exp) + { + cumulus.LogDebugMessage("DecodeCurrent: Exception Caught!"); + cumulus.LogDebugMessage("Message :" + exp.Message); + } + } + + private void OnServiceChanged(object sender, ServiceAnnouncementEventArgs e) + { + PrintService('~', e.Announcement); + } + + private void OnServiceRemoved(object sender, ServiceAnnouncementEventArgs e) + { + cumulus.LogMessage("ZeroConfig Service: WLL service has been removed!"); + } + + private void OnServiceAdded(object sender, ServiceAnnouncementEventArgs e) + { + PrintService('+', e.Announcement); + } + + private void PrintService(char startChar, ServiceAnnouncement service) + { + cumulus.LogDebugMessage($"ZeroConf Service: {startChar} '{service.Instance}' on {service.NetworkInterface.Name}"); + cumulus.LogDebugMessage($"\tHost: {service.Hostname} ({string.Join(", ", service.Addresses)})"); + + lock (threadSafer) + { + ipaddr = service.Addresses[0].ToString(); + cumulus.LogMessage($"ZeroConf Service: WLL found, reporting its IP address as: {ipaddr}"); + if (cumulus.DavisOptions.IPAddr != ipaddr) + { + cumulus.LogMessage($"ZeroConf Service: WLL IP address has changed from {cumulus.DavisOptions.IPAddr} to {ipaddr}"); + if (cumulus.WLLAutoUpdateIpAddress) + { + cumulus.LogMessage($"ZeroConf Service: WLL changing Cumulus config to the new IP address {ipaddr}"); + cumulus.DavisOptions.IPAddr = ipaddr; + cumulus.WriteIniFile(); + } + else + { + cumulus.LogMessage($"ZeroConf Service: WLL ignoring new IP address {ipaddr} due to setting WLLAutoUpdateIpAddress"); + } + } + } + } + + private double ConvertRainClicksToUser(double clicks, int size) + { + // 0: Reseverved, 1: 0.01", 2: 0.2mm, 3: 0.1mm, 4: 0.001" + switch (size) + { + case 1: + return ConvertRainINToUser(clicks * 0.01); + case 2: + return ConvertRainMMToUser(clicks * 0.2); + case 3: + return ConvertRainMMToUser(clicks * 0.1); + case 4: + return ConvertRainINToUser(clicks * 0.001); + default: + switch (cumulus.DavisOptions.RainGaugeType) + { + // Hmm, no valid tip size from WLL... + // One click is normally either 0.01 inches or 0.2 mm + // Try the setting in Cumulus.ini + // Rain gauge type not configured, assume from units + case -1 when cumulus.Units.Rain == 0: + return clicks * 0.2; + case -1: + return clicks * 0.01; + // Rain gauge is metric, convert to user unit + case 0: + return ConvertRainMMToUser(clicks * 0.2); + default: + return ConvertRainINToUser(clicks * 0.01); + } + } + } + + private static bool CheckIpValid(string strIp) + { + if (string.IsNullOrEmpty(strIp)) + return false; + // Split string by ".", check that array length is 4 + var arrOctets = strIp.Split('.'); + if (arrOctets.Length != 4) + return false; + + //Check each substring checking that parses to byte + byte result; + return arrOctets.All(strOctet => byte.TryParse(strOctet, out result)); + } + + 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(); + } + + public override void startReadingHistoryData() + { + cumulus.CurrentActivity = "Reading archive data"; + cumulus.LogMessage("WLL history: Reading history data from log files"); + LoadLastHoursFromDataLogs(cumulus.LastUpdateTime); + + cumulus.LogMessage("WLL history: Reading archive data from WeatherLink API"); + bw = new BackgroundWorker(); + //histprog = new historyProgressWindow(); + //histprog.Owner = mainWindow; + //histprog.Show(); + bw.DoWork += bw_ReadHistory; + //bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged); + bw.RunWorkerCompleted += bw_ReadHistoryCompleted; + bw.WorkerReportsProgress = true; + bw.RunWorkerAsync(); + } + + private void bw_ReadHistoryCompleted(object sender, RunWorkerCompletedEventArgs e) + { + cumulus.LogMessage("WLL history: WeatherLink API archive reading thread completed"); + if (e.Error != null) + { + cumulus.LogMessage("WLL history: Archive reading thread apparently terminated with an error: " + e.Error.Message); + } + cumulus.LogMessage("WLL history: Updating highs and lows"); + //using (cumulusEntities dataContext = new cumulusEntities()) + //{ + // UpdateHighsAndLows(dataContext); + //} + cumulus.CurrentActivity = "Normal running"; + + // restore settings + cumulus.StationOptions.UseSpeedForAvgCalc = savedUseSpeedForAvgCalc; + CalcRecentMaxGust = savedCalculatePeakGust; + + StartLoop(); + DoDayResetIfNeeded(); + DoTrendValues(DateTime.Now); + cumulus.StartTimersAndSensors(); + } + + /* + private void bw_DoStart(object sender, DoWorkEventArgs e) + { + cumulus.LogDebugMessage("Lock: Station waiting for lock"); + Cumulus.syncInit.Wait(); + cumulus.LogDebugMessage("Lock: Station has the lock"); + + // Wait a short while for Cumulus initialisation to complete + Thread.Sleep(500); + StartLoop(); + + cumulus.LogDebugMessage("Lock: Station releasing lock"); + Cumulus.syncInit.Release(); + } + */ + + private void bw_ReadHistory(object sender, DoWorkEventArgs e) + { + int archiveRun = 0; + cumulus.LogDebugMessage("Lock: Station waiting for the lock"); + Cumulus.syncInit.Wait(); + cumulus.LogDebugMessage("Lock: Station has the lock"); + + try + { + // set this temporarily, so speed is done from average and not peak gust from logger + savedUseSpeedForAvgCalc = cumulus.StationOptions.UseSpeedForAvgCalc; + cumulus.StationOptions.UseSpeedForAvgCalc = true; + + // same for gust values + savedCalculatePeakGust = CalcRecentMaxGust; + CalcRecentMaxGust = true; + + // Configure a web proxy if required + if (!string.IsNullOrEmpty(cumulus.HTTPProxyName)) + { + HistoricHttpHandler.Proxy = new WebProxy(cumulus.HTTPProxyName, cumulus.HTTPProxyPort); + HistoricHttpHandler.UseProxy = true; + if (!string.IsNullOrEmpty(cumulus.HTTPProxyUser)) + { + HistoricHttpHandler.Credentials = new NetworkCredential(cumulus.HTTPProxyUser, cumulus.HTTPProxyPassword); + } + } + + do + { + GetWlHistoricData(); + archiveRun++; + } while (archiveRun < maxArchiveRuns); + } + catch (Exception ex) + { + cumulus.LogMessage("Exception occurred reading archive data: " + ex.Message); + } + cumulus.LogDebugMessage("Lock: Station releasing the lock"); + Cumulus.syncInit.Release(); + } + + private void GetWlHistoricData() + { + cumulus.LogMessage("GetWlHistoricData: Get WL.com Historic Data"); + + if (cumulus.WllApiKey == string.Empty || cumulus.WllApiSecret == string.Empty) + { + cumulus.LogMessage("GetWlHistoricData: Missing WeatherLink API data in the configuration, aborting!"); + cumulus.LastUpdateTime = DateTime.Now; + return; + } + + if (cumulus.WllStationId < 10) + { + const string msg = "No WeatherLink API station ID in the configuration"; + cumulus.LogMessage(msg); + cumulus.LogConsoleMessage("GetWlHistoricData: " + msg); + + } + + //int passCount; + //const int maxPasses = 4; + + var unixDateTime = Utils.ToUnixTime(DateTime.Now); + var startTime = Utils.ToUnixTime(cumulus.LastUpdateTime); + int endTime = unixDateTime; + int unix24hrs = 24 * 60 * 60; + + // The API call is limited to fetching 24 hours of data + if (unixDateTime - startTime > unix24hrs) + { + // only fetch 24 hours worth of data, and schedule another run to fetch the rest + endTime = startTime + unix24hrs; + maxArchiveRuns++; + } + + cumulus.LogConsoleMessage($"Downloading Historic Data from WL.com from: {cumulus.LastUpdateTime:s} to: {Utils.FromUnixTime(endTime):s}"); + cumulus.LogMessage($"GetWlHistoricData: Downloading Historic Data from WL.com from: {cumulus.LastUpdateTime:s} to: {Utils.FromUnixTime(endTime):s}"); + + SortedDictionary parameters = new SortedDictionary + { + { "api-key", cumulus.WllApiKey }, + { "station-id", cumulus.WllStationId.ToString() }, + { "t", unixDateTime.ToString() }, + { "start-timestamp", startTime.ToString() }, + { "end-timestamp", endTime.ToString() } + }; + + StringBuilder dataStringBuilder = new StringBuilder(); + foreach (KeyValuePair entry in parameters) + { + dataStringBuilder.Append(entry.Key); + dataStringBuilder.Append(entry.Value); + } + + string data = dataStringBuilder.ToString(); + + var apiSignature = WlDotCom.CalculateApiSignature(cumulus.WllApiSecret, data); + + parameters.Remove("station-id"); + parameters.Add("api-signature", apiSignature); + + StringBuilder historicUrl = new StringBuilder(); + historicUrl.Append("https://api.weatherlink.com/v2/historic/" + cumulus.WllStationId + "?"); + foreach (KeyValuePair entry in parameters) + { + historicUrl.Append(entry.Key); + historicUrl.Append("="); + historicUrl.Append(entry.Value); + historicUrl.Append("&"); + } + // remove the trailing "&" + historicUrl.Remove(historicUrl.Length - 1, 1); + + var logUrl = historicUrl.ToString().Replace(cumulus.WllApiKey, "<>"); + cumulus.LogDebugMessage($"WeatherLink URL = {logUrl}"); + + lastDataReadTime = cumulus.LastUpdateTime; + int luhour = lastDataReadTime.Hour; + + int rollHour = Math.Abs(cumulus.GetHourInc()); + + cumulus.LogMessage($"Rollover hour = {rollHour}"); + + bool rolloverdone = luhour == rollHour; + + bool midnightraindone = luhour == 0; + + WlHistory histObj; + int noOfRecs = 0; + WlHistorySensor sensorWithMostRecs; + + try + { + string responseBody; + int responseCode; + + // we want to do this synchronously, so .Result + using (HttpResponseMessage response = wlHttpClient.GetAsync(historicUrl.ToString()).Result) + { + responseBody = responseBody = response.Content.ReadAsStringAsync().Result; + responseCode = (int)response.StatusCode; + cumulus.LogDebugMessage($"GetWlHistoricData: WeatherLink API Historic Response code: {responseCode}"); + cumulus.LogDataMessage($"GetWlHistoricData: WeatherLink API Historic Response: {responseBody}"); + } + + if (responseCode != 200) + { + var historyError = responseBody.FromJson(); + cumulus.LogMessage($"GetWlHistoricData: WeatherLink API Historic Error: {historyError.code}, {historyError.message}"); + cumulus.LogConsoleMessage($" - Error {historyError.code}: {historyError.message}"); + cumulus.LastUpdateTime = Utils.FromUnixTime(endTime); + return; + } + + histObj = responseBody.FromJson(); + + if (responseBody == "{}") + { + cumulus.LogMessage("GetWlHistoricData: WeatherLink API Historic: No data was returned. Check your Device Id."); + cumulus.LogConsoleMessage(" - No historic data available"); + cumulus.LastUpdateTime = Utils.FromUnixTime(endTime); + return; + } + else if (responseBody.StartsWith("{\"sensors\":[{\"lsid\"")) // sanity check + { + // get the sensor data + int idxOfSensorWithMostRecs = 0; + for (var i = 0; i < histObj.sensors.Count; i++) + { + // Find the WLL baro, or internal temp/hum sensors + if (histObj.sensors[i].sensor_type == 242 && histObj.sensors[i].data_structure_type == 13) + { + var recs = histObj.sensors[i].data.Count; + if (recs > noOfRecs) + { + noOfRecs = recs; + idxOfSensorWithMostRecs = i; + } + } + } + sensorWithMostRecs = histObj.sensors[idxOfSensorWithMostRecs]; + + if (noOfRecs == 0) + { + cumulus.LogMessage("GetWlHistoricData: No historic data available"); + cumulus.LogConsoleMessage(" - No historic data available"); + cumulus.LastUpdateTime = Utils.FromUnixTime(endTime); + return; + } + else + { + cumulus.LogMessage($"GetWlHistoricData: Found {noOfRecs} historic records to process"); + } + } + else // No idea what we got, dump it to the log + { + cumulus.LogMessage("GetWlHistoricData: Invalid historic message received"); + cumulus.LogDataMessage("GetWlHistoricData: Received: " + responseBody); + cumulus.LastUpdateTime = Utils.FromUnixTime(endTime); + return; + } + } + catch (Exception ex) + { + cumulus.LogMessage("GetWlHistoricData: Exception: " + ex.Message); + cumulus.LastUpdateTime = Utils.FromUnixTime(endTime); + return; + } + + for (int dataIndex = 0; dataIndex < noOfRecs; dataIndex++) + { + try + { + // Not all sensors may have the same number of records. We are using the WLL to create the historic data, the other sensors (AirLink) may have more or less records! + // For the additional sensors, check if they have the same number of reocrds as the WLL. If they do great, we just process the next record. + // If the sensor has more or less historic records than the WLL, then we find the record (if any) that matches the WLL record timestamp + + + var refData = sensorWithMostRecs.data[dataIndex].FromJsv(); + DecodeHistoric(sensorWithMostRecs.data_structure_type, sensorWithMostRecs.sensor_type, sensorWithMostRecs.data[dataIndex]); + var timestamp = Utils.FromUnixTime(refData.ts); + + foreach (var sensor in histObj.sensors) + { + int sensorType = sensor.sensor_type; + int dataStructureType = sensor.data_structure_type; + int lsid = sensor.lsid; + + if (sensorType == 323 && cumulus.airLinkOut != null) // AirLink Outdoor + { + if (sensor.data.Count != noOfRecs) + { + var found = false; + foreach (var dataRec in sensor.data) + { + var rec = dataRec.FromJsv(); + if (rec.ts == refData.ts) + { + // Pass AirLink historic record to the AirLink module to process + cumulus.airLinkOut.DecodeAlHistoric(dataStructureType, dataRec); + found = true; + break; + } + } + if (!found) + cumulus.LogDebugMessage("GetWlHistoricData: Warning. No outdoor Airlink data for this log interval !!"); + } + else + { + // Pass AirLink historic record to the AirLink module to process + cumulus.airLinkOut.DecodeAlHistoric(dataStructureType, sensor.data[dataIndex]); + } + } + else if (sensorType == 326 && cumulus.airLinkIn != null) // AirLink Indoor + { + if (sensor.data.Count != noOfRecs) + { + var found = false; + foreach (var dataRec in sensor.data) + { + var rec = dataRec.FromJsv(); + + if (rec.ts == refData.ts) + { + // Pass AirLink historic record to the AirLink module to process + cumulus.airLinkIn.DecodeAlHistoric(dataStructureType, dataRec); + found = true; + break; + } + } + if (!found) + cumulus.LogDebugMessage("GetWlHistoricData: Warning. No indoor Airlink data for this log interval !!"); + } + else + { + // Pass AirLink historic record to the AirLink module to process + cumulus.airLinkIn.DecodeAlHistoric(dataStructureType, sensor.data[dataIndex]); + } + } + else if (sensorType != 504 && sensorType != 506 && lsid != sensorWithMostRecs.lsid) + { + if (sensor.data.Count > dataIndex) + { + DecodeHistoric(dataStructureType, sensorType, sensor.data[dataIndex]); + // sensor 504 (WLL info) does not always contain a full set of records, so grab the timestamp from a 'real' sensor + } + } + } + + var h = timestamp.Hour; + + if (cumulus.StationOptions.LogExtraSensors) + { + cumulus.DoExtraLogFile(timestamp); + } + + if (cumulus.airLinkOut != null || cumulus.airLinkIn != null) + { + cumulus.DoAirLinkLogFile(timestamp); + } + + // Now we have the primary data, calculate the derived data + if (cumulus.StationOptions.CalculatedWC) + { + // DoWindChill does all the required checks and conversions + DoWindChill(OutdoorTemperature, timestamp); + } + + DoApparentTemp(timestamp); + DoFeelsLike(timestamp); + DoHumidex(timestamp); + + // Log all the data + cumulus.DoLogFile(timestamp, false); + cumulus.LogMessage("GetWlHistoricData: Log file entry written"); + + AddLastHourDataEntry(timestamp, Raincounter, OutdoorTemperature); + AddLast3HourDataEntry(timestamp, Pressure, OutdoorTemperature); + AddGraphDataEntry(timestamp, Raincounter, RainToday, RainRate, OutdoorTemperature, OutdoorDewpoint, ApparentTemperature, WindChill, HeatIndex, + IndoorTemperature, Pressure, WindAverage, RecentMaxGust, AvgBearing, Bearing, OutdoorHumidity, IndoorHumidity, SolarRad, CurrentSolarMax, UV, FeelsLike, Humidex); + AddRecentDataEntry(timestamp, WindAverage, RecentMaxGust, WindLatest, Bearing, AvgBearing, OutdoorTemperature, WindChill, OutdoorDewpoint, HeatIndex, + OutdoorHumidity, Pressure, RainToday, SolarRad, UV, Raincounter, FeelsLike, Humidex); + RemoveOldLHData(timestamp); + RemoveOldL3HData(timestamp); + RemoveOldGraphData(timestamp); + DoTrendValues(timestamp); + UpdateStatusPanel(timestamp); + cumulus.AddToWebServiceLists(timestamp); + + // if outside rollover hour, rollover yet to be done + if (h != rollHour) + { + rolloverdone = false; + } + + // In rollover hour and rollover not yet done + if ((h == rollHour) && !rolloverdone) + { + // do rollover + cumulus.LogMessage("GetWlHistoricData: Day rollover " + timestamp.ToShortTimeString()); + DayReset(timestamp); + rolloverdone = true; + } + + // Not in midnight hour, midnight rain yet to be done + if (h != 0) + { + midnightraindone = false; + } + + // In midnight hour and midnight rain (and sun) not yet done + if ((h == 0) && !midnightraindone) + { + ResetMidnightRain(timestamp); + ResetSunshineHours(); + midnightraindone = true; + } + + + if (!Program.service) + Console.Write("\r - processed " + (((double)dataIndex + 1) / noOfRecs).ToString("P0")); + cumulus.LogMessage($"GetWlHistoricData: {dataIndex + 1} of {noOfRecs} archive entries processed"); + } + catch (Exception ex) + { + cumulus.LogMessage("GetWlHistoricData: Exception: " + ex.Message); + } + } + + if (!Program.service) + Console.WriteLine(""); // flush the progress line + } + + private void DecodeHistoric(int dataType, int sensorType, string json) + { + // The WLL sends the timestamp in Unix ticks, and in UTC + + try + { + switch (dataType) + { + case 11: // ISS data + var data11 = json.FromJsv(); + var recordTs = Utils.FromUnixTime(data11.ts); + //weatherLinkArchiveInterval = data.Value("arch_int"); + + // Temperature & Humidity + if (cumulus.WllPrimaryTempHum == data11.tx_id) + { + /* + * Avaialable fields + * "cooling_degree_days" + * "dew_point_hi_at" + * "dew_point_hi" + * "dew_point_last" + * "dew_point_lo_at" + * "dew_point_lo" + * "heat_index_hi_at" + * "heat_index_hi" + * "heat_index_last" + * "heating_degree_days" + * "hum_hi_at" + * "hum_hi" + * "hum_last" + * "hum_lo_at" + * "hum_lo" + * "temp_avg" + * "temp_hi_at" + * "temp_last" + * "temp_lo_at" + * "temp_lo" + * "temp_max" + * "wind_chill_last" + * "wind_chill_lo_at" + * "wind_chill_lo" + */ + + DateTime ts; + + try + { + if (data11.temp_last == -99) + { + cumulus.LogMessage($"WL.com historic: no valid Primary temperature value found [-99] on TxId {data11.tx_id}"); + } + else + { + cumulus.LogDebugMessage($"WL.com historic: using temp/hum data from TxId {data11.tx_id}"); + + // do high temp + ts = Utils.FromUnixTime(data11.temp_hi_at); + DoOutdoorTemp(ConvertTempFToUser(data11.temp_hi), ts); + // do low temp + ts = Utils.FromUnixTime(data11.temp_lo_at); + DoOutdoorTemp(ConvertTempFToUser(data11.temp_lo), ts); + // do last temp + DoOutdoorTemp(ConvertTempFToUser(data11.temp_last), recordTs); + + // set the values for daily average, arch_int is in seconds, but always whole minutes + tempsamplestoday += data11.arch_int / 60; + TempTotalToday += ConvertTempFToUser(data11.temp_avg) * data11.arch_int / 60; + + // update chill hours + if (OutdoorTemperature < cumulus.ChillHourThreshold) + { + // add 1 minute to chill hours + ChillHours += (data11.arch_int / 60.0); + } + + // update heating/cooling degree days + UpdateDegreeDays(data11.arch_int / 60); + } + } + catch (Exception ex) + { + cumulus.LogMessage($"WL.com historic: Error processing Primary temperature value on TxId {data11.tx_id}. Error: {ex.Message}"); + } + + try + { + // do high humidty + ts = Utils.FromUnixTime(data11.hum_hi_at); + DoOutdoorHumidity(Convert.ToInt32(data11.hum_hi), ts); + // do low humidity + ts = Utils.FromUnixTime(data11.hum_lo_at); + DoOutdoorHumidity(Convert.ToInt32(data11.hum_lo), ts); + // do current humidity + DoOutdoorHumidity(Convert.ToInt32(data11.hum_last), recordTs); + } + catch (Exception ex) + { + cumulus.LogMessage($"WL.com historic: Error processing Primary humidity value on TxId {data11.tx_id}. Error: {ex.Message}"); + } + + try + { + // do high DP + ts = Utils.FromUnixTime(data11.dew_point_hi_at); + DoOutdoorDewpoint(ConvertTempFToUser(data11.dew_point_hi), ts); + // do low DP + ts = Utils.FromUnixTime(data11.dew_point_lo_at); + DoOutdoorDewpoint(ConvertTempFToUser(data11.dew_point_lo), ts); + // do last DP + DoOutdoorDewpoint(ConvertTempFToUser(data11.dew_point_last), recordTs); + } + catch (Exception ex) + { + cumulus.LogMessage($"WL.com historic: Error processing dew point value on TxId {data11.tx_id}. Error: {ex.Message}"); + } + + if (!cumulus.StationOptions.CalculatedWC) + { + // use wind chill from WLL - otherwise we calculate it at the end of processing the historic record when we have all the data + try + { + // do low WC + ts = Utils.FromUnixTime(data11.wind_chill_lo_at); + DoWindChill(ConvertTempFToUser(data11.wind_chill_lo), ts); + // do last WC + DoWindChill(ConvertTempFToUser(data11.wind_chill_last), recordTs); + } + catch (Exception ex) + { + cumulus.LogMessage($"WL.com historic: Error processing wind chill value on TxId {data11.tx_id}. Error: {ex.Message}"); + } + } + } + else + { // Check for Extra temperature/humidity settings + for (var tempTxId = 1; tempTxId <= 8; tempTxId++) + { + if (cumulus.WllExtraTempTx[tempTxId - 1] != data11.tx_id) continue; + + try + { + if (data11.temp_last == -99) + { + cumulus.LogDebugMessage($"WL.com historic: no valid Extra temperature value on TxId {data11.tx_id}"); + } + else + { + cumulus.LogDebugMessage($"WL.com historic: using extra temp data from TxId {data11.tx_id}"); + + DoExtraTemp(ConvertTempFToUser(data11.temp_last), tempTxId); + } + } + catch (Exception ex) + { + cumulus.LogMessage($"WL.com historic: Error processing extra temp value on TxId {data11.tx_id}"); + cumulus.LogDebugMessage($"WL.com historic: Exception {ex.Message}"); + } + + if (!cumulus.WllExtraHumTx[tempTxId - 1]) continue; + + try + { + DoExtraHum(data11.hum_last, tempTxId); + } + catch (Exception ex) + { + cumulus.LogMessage($"WL.com historic: Error processing extra humidity value on TxId {data11.tx_id}. Error: {ex.Message}"); + } + } + } + + // Wind + if (cumulus.WllPrimaryWind == data11.tx_id) + { + /* + * Available fields + * "wind_dir_of_prevail" + * "wind_run" + * "wind_speed_avg" + * "wind_speed_hi_at" + * "wind_speed_hi_dir" + * "wind_speed_hi" + */ + + try + { + cumulus.LogDebugMessage($"WL.com historic: using wind data from TxId {data11.tx_id}"); + DoWind(ConvertWindMPHToUser(data11.wind_speed_hi), data11.wind_speed_hi_dir, ConvertWindMPHToUser(data11.wind_speed_avg), recordTs); + + WindAverage = ConvertWindMPHToUser(data11.wind_speed_avg) * cumulus.Calib.WindSpeed.Mult; + + // add in 'archivePeriod' minutes worth of wind speed to windrun + int interval = data11.arch_int / 60; + WindRunToday += ((WindAverage * WindRunHourMult[cumulus.Units.Wind] * interval) / 60.0); + } + catch (Exception ex) + { + cumulus.LogMessage($"WL.com historic: Error processing wind values on TxId {data11.tx_id}. Error: {ex.Message}"); + } + } + + // Rainfall + if (cumulus.WllPrimaryRain == data11.tx_id) + { + /* + * Available fields: + * "rain_rate_hi_at" + * "rain_rate_hi_clicks" + * "rain_rate_hi_in" + * "rain_rate_hi_mm" + * "rain_size" + * "rainfall_clicks" + * "rainfall_in" + * "rainfall_mm" + */ + + cumulus.LogDebugMessage($"WL.com historic: using rain data from TxId {data11.tx_id}"); + + // The WL API v2 does not provide any running totals for rainfall, only :( + // So we will have to add the interval data to the running total and hope it all works out! + + try + { + var rain = ConvertRainClicksToUser(data11.rainfall_clicks, data11.rain_size); + var rainrate = ConvertRainClicksToUser(data11.rain_rate_hi_clicks, data11.rain_size); + if (rain > 0) + { + cumulus.LogDebugMessage($"WL.com historic: Adding rain {rain.ToString(cumulus.RainFormat)}"); + } + rain += Raincounter; + + if (rainrate < 0) + { + rainrate = 0; + } + + DoRain(rain, rainrate, recordTs); + } + catch (Exception ex) + { + cumulus.LogMessage($"WL.com historic: Error processing rain data on TxId {data11.tx_id}. Error:{ex.Message}"); + } + + } + + // UV + if (cumulus.WllPrimaryUV == data11.tx_id) + { + /* + * Available fields + * "uv_dose" + * "uv_index_avg" + * "uv_index_hi_at" + * "uv_index_hi" + * "uv_volt_last" + */ + try + { + cumulus.LogDebugMessage($"WL.com historic: using UV data from TxId {data11.tx_id}"); + + DoUV(data11.uv_index_avg, recordTs); + } + catch (Exception ex) + { + cumulus.LogMessage($"WL.com historic: Error processing UV value on TxId {data11.tx_id}. Error: {ex.Message}"); + } + + } + + // Solar + if (cumulus.WllPrimarySolar == data11.tx_id) + { + /* + * Available fields + * "solar_energy" + * "solar_rad_avg" + * "solar_rad_hi_at" + * "solar_rad_hi" + * "solar_rad_volt_last" + * "solar_volt_last" + * "et" + */ + try + { + cumulus.LogDebugMessage($"WL.com historic: using solar data from TxId {data11.tx_id}"); + DoSolarRad(data11.solar_rad_avg, recordTs); + } + catch (Exception ex) + { + cumulus.LogMessage($"WL.com historic: Error processing Solar value on TxId {data11.tx_id}. Error: {ex.Message}"); + } + } + break; + + case 13: // Non-ISS data + switch (sensorType) + { + case 56: // Soil + Leaf + var data13 = json.FromJsv(); + + string idx = ""; + /* + * Leaf Wetness + * Avaialable fields + * "wet_leaf_at_1" + * "wet_leaf_hi_1" + * "wet_leaf_hi_2": + * "wet_leaf_hi_at_1" + * "wet_leaf_hi_at_2" + * "wet_leaf_last_1" + * "wet_leaf_last_2" + * "wet_leaf_last_volt_1" + * "wet_leaf_last_volt_2" + * "wet_leaf_lo_1" + * "wet_leaf_lo_2" + * "wet_leaf_lo_at_2" + * "wet_leaf_min_1" + * "wet_leaf_min_2" + */ + + cumulus.LogDebugMessage($"WL.com historic: found Leaf/Soil data on TxId {data13.tx_id}"); + + // We are relying on user configuration here, trap any errors + try + { + if (cumulus.WllExtraLeafTx1 == data13.tx_id) + { + idx = "wet_leaf_last_" + cumulus.WllExtraLeafIdx1; + DoLeafWetness((double)data13[idx], 1); + } + if (cumulus.WllExtraLeafTx2 == data13.tx_id) + { + idx = "wet_leaf_last_" + cumulus.WllExtraLeafIdx2; + DoLeafWetness((double)data13[idx], 2); + } + } + catch (Exception e) + { + cumulus.LogMessage($"Error, DecodeHistoric, LeafWetness txid={data13.tx_id}, idx={idx}: {e.Message}"); + } + /* + * Soil Moisture + * Avaialable fields + * "moist_soil_hi_1" + * "moist_soil_hi_2" + * "moist_soil_hi_3" + * "moist_soil_hi_4" + * "moist_soil_hi_at_1" + * "moist_soil_hi_at_2" + * "moist_soil_hi_at_3" + * "moist_soil_hi_at_4" + * "moist_soil_last_1" + * "moist_soil_last_2" + * "moist_soil_last_3" + * "moist_soil_last_4" + * "moist_soil_last_volt_1" + * "moist_soil_last_volt_2" + * "moist_soil_last_volt_3" + * "moist_soil_last_volt_4" + * "moist_soil_lo_1" + * "moist_soil_lo_2" + * "moist_soil_lo_3" + * "moist_soil_lo_4" + * "moist_soil_lo_at_1" + * "moist_soil_lo_at_2" + * "moist_soil_lo_at_3" + * "moist_soil_lo_at_4" + */ + + try + { + if (cumulus.WllExtraSoilMoistureTx1 == data13.tx_id) + { + idx = "moist_soil_last_" + cumulus.WllExtraSoilMoistureIdx1; + if (data13[idx] == null) + { + cumulus.LogDebugMessage($"WL.com historic: no valid soil moisture #{cumulus.WllExtraSoilMoistureIdx1} on TxId {data13.tx_id}"); + } + else + { + DoSoilMoisture((double)data13[idx], 1); + } + } + if (cumulus.WllExtraSoilMoistureTx2 == data13.tx_id) + { + idx = "moist_soil_last_" + cumulus.WllExtraSoilMoistureIdx2; + if (data13[idx] == null) + { + cumulus.LogDebugMessage($"WL.com historic: no valid soil moisture #{cumulus.WllExtraSoilMoistureIdx2} on TxId {data13.tx_id}"); + } + else + { + DoSoilMoisture((double)data13[idx], 2); + } + } + if (cumulus.WllExtraSoilMoistureTx3 == data13.tx_id) + { + idx = "moist_soil_last_" + cumulus.WllExtraSoilMoistureIdx3; + if (data13[idx] == null) + { + cumulus.LogDebugMessage($"WL.com historic: no valid soil moisture #{cumulus.WllExtraSoilMoistureIdx3} on TxId {data13.tx_id}"); + } + else + { + DoSoilMoisture((double)data13[idx], 3); + } + } + if (cumulus.WllExtraSoilMoistureTx4 == data13.tx_id) + { + idx = "moist_soil_last_" + cumulus.WllExtraSoilMoistureIdx4; + if (data13[idx] == null) + { + cumulus.LogDebugMessage($"WL.com historic: no valid soil moisture #{cumulus.WllExtraSoilMoistureIdx4} on TxId {data13.tx_id}"); + } + else + { + DoSoilMoisture((double)data13[idx], 4); + } + } + } + catch (Exception e) + { + cumulus.LogMessage($"Error, DecodeHistoric, SoilMoisture txid={data13.tx_id}, idx={idx}: {e.Message}"); + } + + /* + * Soil Temperature + * Avaialble fields + * "temp_hi_1" + * "temp_hi_2" + * "temp_hi_3" + * "temp_hi_4" + * "temp_hi_at_1" + * "temp_hi_at_2" + * "temp_hi_at_3" + * "temp_hi_at_4" + * "temp_last_1" + * "temp_last_2" + * "temp_last_3" + * "temp_last_4" + * "temp_last_volt_1" + * "temp_last_volt_2" + * "temp_last_volt_3" + * "temp_last_volt_4" + * "temp_lo_1" + * "temp_lo_2" + * "temp_lo_3" + * "temp_lo_4" + * "temp_lo_at_1" + * "temp_lo_at_2" + * "temp_lo_at_3" + * "temp_lo_at_4" + */ + + try + { + if (cumulus.WllExtraSoilTempTx1 == data13.tx_id) + { + idx = "temp_last_" + cumulus.WllExtraSoilTempIdx1; + if (data13[idx] == null) + { + cumulus.LogDebugMessage($"WL.com historic: no valid extra soil temp #{cumulus.WllExtraSoilTempIdx1} on TxId {data13.tx_id}"); + } + else + { + DoSoilTemp(ConvertTempFToUser((double)data13[idx]), 1); + } + } + if (cumulus.WllExtraSoilTempTx2 == data13.tx_id) + { + idx = "temp_last_" + cumulus.WllExtraSoilTempIdx2; + if (data13[idx] == null) + { + cumulus.LogDebugMessage($"WL.com historic: no valid extra soil temp #{cumulus.WllExtraSoilTempIdx2} on TxId {data13.tx_id}"); + } + else + { + DoSoilTemp(ConvertTempFToUser((double)data13[idx]), 2); + } + } + if (cumulus.WllExtraSoilTempTx3 == data13.tx_id) + { + idx = "temp_last_" + cumulus.WllExtraSoilTempIdx3; + if (data13[idx] == null) + { + cumulus.LogDebugMessage($"WL.com historic: no valid extra soil temp #{cumulus.WllExtraSoilTempIdx3} on TxId {data13.tx_id}"); + } + else + { + DoSoilTemp(ConvertTempFToUser((double)data13[idx]), 3); + } + } + if (cumulus.WllExtraSoilTempTx4 == data13.tx_id) + { + idx = "temp_last_" + cumulus.WllExtraSoilTempIdx4; + if (data13[idx] == null) + { + cumulus.LogDebugMessage($"WL.com historic: no valid extra soil temp #{cumulus.WllExtraSoilTempIdx4} on TxId {data13.tx_id}"); + } + else + { + DoSoilTemp(ConvertTempFToUser((double)data13[idx]), 4); + } + } + } + catch (Exception e) + { + cumulus.LogMessage($"Error, DecodeHistoric, SoilTemp txid={data13.tx_id}, idx={idx}: {e.Message}"); + } + + break; + + case 242: // Baro + /* + * Available fields + * "bar_absolute" + * "bar_hi_at" + * "bar_sea_level" + * "arch_int" + * "bar_lo" + * "bar_hi" + * "bar_lo_at" + */ + // log the current value + cumulus.LogDebugMessage("WL.com historic: found Baro data"); + try + { + var data13baro = json.FromJsv(); + // check the high + var ts = Utils.FromUnixTime(data13baro.bar_hi_at); + DoPressure(ConvertPressINHGToUser(data13baro.bar_hi), ts); + // check the low + ts = Utils.FromUnixTime(data13baro.bar_lo_at); + DoPressure(ConvertPressINHGToUser(data13baro.bar_lo), ts); + // leave it at current value + ts = Utils.FromUnixTime(data13baro.ts); + DoPressure(ConvertPressINHGToUser(data13baro.bar_sea_level), ts); + DoPressTrend("Pressure trend"); + // Altimeter from absolute + StationPressure = ConvertPressINHGToUser(data13baro.bar_absolute); + // Or do we use calibration? The VP2 code doesn't? + //StationPressure = ConvertPressINHGToUser(data.Value("bar_absolute")) * cumulus.Calib.Press.Mult + cumulus.Calib.Press.Offset; + AltimeterPressure = ConvertPressMBToUser(StationToAltimeter(ConvertUserPressureToHPa(StationPressure), AltitudeM(cumulus.Altitude))); + } + catch (Exception ex) + { + cumulus.LogMessage($"WL.com historic: Error processing baro reading. Error: {ex.Message}"); + } + break; + + case 243: // Inside temp/hum + /* + * Avilable fields + * "dew_point_in" + * "heat_index_in" + * "hum_in_hi" + * "hum_in_hi_at" + * "hum_in_last" + * "hum_in_lo" + * "hum_in_lo_at" + * "temp_in_hi" + * "temp_in_hi_at" + * "temp_in_last" + * "temp_in_lo" + * "temp_in_lo_at" + */ + cumulus.LogDebugMessage("WL.com historic: found inside temp/hum data"); + + var data13temp = json.FromJsv(); + try + { + DoIndoorTemp(ConvertTempFToUser(data13temp.temp_in_last)); + } + catch (Exception ex) + { + cumulus.LogMessage($"WL.com historic: Error processing temp-in reading. Error: {ex.Message}]"); + } + + + try + { + DoIndoorHumidity(Convert.ToInt32(data13temp.hum_in_last)); + } + catch (Exception ex) + { + cumulus.LogDebugMessage($"WLL current: Error processing humidity-in. Error: {ex.Message}]"); + } + + break; + + default: + cumulus.LogDebugMessage($"WL.com historic: found an unknown sensor type [{sensorType}]!"); + break; + + } + break; + + case 17: // AirLink + break; + + default: + cumulus.LogDebugMessage($"WL.com historic: found an unknown data structure type [{dataType}]!"); + break; + } + } + catch (Exception e) + { + cumulus.LogMessage($"Error, DecodeHistoric, DataType={dataType}, SensorType={sensorType}: " + e.Message); + } + } + + private void DecodeWlApiHealth(WlHistorySensor sensor, bool startingup) + { + if (sensor.data.Count == 0) + { + if (sensor.data_structure_type == 15) + { + cumulus.LogDebugMessage("WLL Health - did not find any health data for WLL device"); + } + else if (sensor.data_structure_type == 11) + { + cumulus.LogDebugMessage("WLL Health - did not find health data for ISS device"); + } + return; + } + + if (sensor.data_structure_type == 15) + { + /* WLL Device + * + * Available fields + * "battery_voltage" + * "bgn" - historic only + * "bluetooth_version" - historic only + * "bootloader_version" + * "dns_type_used" - historic only + * "espressif_version" + * "firmware_version" + * "health_version" + * "input_voltage" + * "ip_address_type" + * "ip_v4_address" + * "ip_v4_gateway" + * "ip_v4_netmask" + * "link_uptime" + * "local_api_queries" + * "network_error" + * "network_type": + * "radio_version" + * "rapid_records_sent" + * "rx_bytes" + * "touchpad_wakeups" + * "tx_bytes" + * "uptime" + * "wifi_rssi" + * "ts" - historic only + */ + + cumulus.LogDebugMessage("WLL Health - found health data for WLL device"); + + try + { + var data15 = sensor.data.Last().FromJsv(); + + var dat = Utils.FromUnixTime(data15.firmware_version); + DavisFirmwareVersion = dat.ToUniversalTime().ToString("yyyy-MM-dd"); + + var battV = data15.battery_voltage / 1000.0; + ConBatText = battV.ToString("F2"); + if (battV < 5.2) + { + wllVoltageLow = true; + cumulus.LogMessage($"WLL WARNING: Backup battery voltage is low = {battV:0.##}V"); + } + else + { + wllVoltageLow = false; + cumulus.LogDebugMessage($"WLL Battery Voltage = {battV:0.##}V"); + } + var inpV = data15.input_voltage / 1000.0; + ConSupplyVoltageText = inpV.ToString("F2"); + if (inpV < 4.0) + { + cumulus.LogMessage($"WLL WARNING: Input voltage is low = {inpV:0.##}V"); + } + else + { + cumulus.LogDebugMessage($"WLL Input Voltage = {inpV:0.##}V"); + } + var upt = TimeSpan.FromSeconds(data15.uptime); + var uptStr = string.Format("{0}d:{1:D2}h:{2:D2}m:{3:D2}s", + (int)upt.TotalDays, + upt.Hours, + upt.Minutes, + upt.Seconds); + cumulus.LogDebugMessage("WLL Uptime = " + uptStr); + + // Only present if WiFi attached + if (data15.wifi_rssi.HasValue) + { + DavisTxRssi[0] = data15.wifi_rssi.Value; + cumulus.LogDebugMessage("WLL WiFi RSSI = " + DavisTxRssi[0] + "dB"); + } + + upt = TimeSpan.FromSeconds(data15.link_uptime); + uptStr = string.Format("{0}d:{1:D2}h:{2:D2}m:{3:D2}s", + (int)upt.TotalDays, + upt.Hours, + upt.Minutes, + upt.Seconds); + cumulus.LogDebugMessage("WLL Link Uptime = " + uptStr); + } + catch (Exception ex) + { + cumulus.LogMessage($"WL.com historic: Error processing WLL health. Error: {ex.Message}"); + DavisFirmwareVersion = "???"; + } + + if (startingup) + { + cumulus.LogMessage("WLL FW version = " + DavisFirmwareVersion); + } + else + { + cumulus.LogDebugMessage("WLL FW version = " + DavisFirmwareVersion); + } + } + else if (sensor.data_structure_type == 11) + { + /* ISS + * Available fields of interest to health + * "afc": -1 + * "error_packets": 0 + * "good_packets_streak": 602 + * "reception": 100 + * "resynchs": 0 + * "rssi": -60 + * "supercap_volt_last": null + * "trans_battery_flag": 0 + * "trans_battery": null + * "tx_id": 2 + */ + + try + { + var data11 = sensor.data.Last().FromJsv(); + + cumulus.LogDebugMessage("WLL Health - found health data for ISS device TxId = " + data11.tx_id); + + // Save the archive interval + //weatherLinkArchiveInterval = data.Value("arch_int"); + + // Check battery state 0=Good, 1=Low + SetTxBatteryStatus(data11.tx_id, data11.trans_battery_flag); + if (data11.trans_battery_flag == 1) + { + cumulus.LogMessage($"WLL WARNING: Battery voltage is low in TxId {data11.tx_id}"); + } + else + { + cumulus.LogDebugMessage($"WLL Health: ISS {data11.tx_id}: Battery state is OK"); + } + + //DavisTotalPacketsReceived[txid] = ; // Do not have a value for this + DavisTotalPacketsMissed[data11.tx_id] = data11.error_packets; + DavisNumCRCerrors[data11.tx_id] = data11.error_packets; + DavisNumberOfResynchs[data11.tx_id] = data11.resynchs; + DavisMaxInARow[data11.tx_id] = data11.good_packets_streak; + DavisReceptionPct[data11.tx_id] = data11.reception; + DavisTxRssi[data11.tx_id] = data11.rssi; + + cumulus.LogDebugMessage($"WLL Health: IIS {data11.tx_id}: Errors={DavisTotalPacketsMissed[data11.tx_id]}, CRCs={DavisNumCRCerrors[data11.tx_id]}, Resyncs={DavisNumberOfResynchs[data11.tx_id]}, Streak={DavisMaxInARow[data11.tx_id]}, %={DavisReceptionPct[data11.tx_id]}, RSSI={DavisTxRssi[data11.tx_id]}"); + } + catch (Exception ex) + { + cumulus.LogMessage($"WLL Health: Error processing transmitter health. Error: {ex.Message}"); + } + } + } + + private void HealthTimerTick(object source, ElapsedEventArgs e) + { + // Only run every 15 minutes + // The WLL only reports its health every 15 mins, on the hour, :15, :30 and :45 + // We run at :01, :16, :31, :46 to allow time for wl.com to generate the stats + if (DateTime.Now.Minute % 15 == 1) + { + GetWlHistoricHealth(); + var msg = string.Format("WLL: Percentage good packets received from WLL {0:F2}% - ({1},{2})", (multicastsGood / (float)(multicastsBad + multicastsGood) * 100), multicastsBad, multicastsGood); + cumulus.LogMessage(msg); + } + } + + // Extracts health infomation from the last archive record + private void GetWlHistoricHealth() + { + cumulus.LogMessage("WLL Health: Get WL.com Historic Data"); + + if (cumulus.WllApiKey == string.Empty || cumulus.WllApiSecret == string.Empty) + { + cumulus.LogMessage("WLL Health: Missing WeatherLink API data in the cumulus.ini file, aborting!"); + return; + } + + if (cumulus.WllStationId < 10) + { + const string msg = "No WeatherLink API station ID in the cumulus.ini file"; + cumulus.LogConsoleMessage("GetWlHistoricHealth: " + msg); + cumulus.LogMessage($"WLL Health: {msg}, aborting!"); + return; + } + + var unixDateTime = Utils.ToUnixTime(DateTime.Now); + var startTime = unixDateTime - weatherLinkArchiveInterval; + int endTime = unixDateTime; + + cumulus.LogDebugMessage($"WLL Health: Downloading the historic record from WL.com from: {Utils.FromUnixTime(startTime):s} to: {Utils.FromUnixTime(endTime):s}"); + + SortedDictionary parameters = new SortedDictionary + { + { "api-key", cumulus.WllApiKey }, + { "station-id", cumulus.WllStationId.ToString() }, + { "t", unixDateTime.ToString() }, + { "start-timestamp", startTime.ToString() }, + { "end-timestamp", endTime.ToString() } + }; + + StringBuilder dataStringBuilder = new StringBuilder(); + foreach (KeyValuePair entry in parameters) + { + dataStringBuilder.Append(entry.Key); + dataStringBuilder.Append(entry.Value); + } + + string data = dataStringBuilder.ToString(); + + var apiSignature = WlDotCom.CalculateApiSignature(cumulus.WllApiSecret, data); + + parameters.Remove("station-id"); + parameters.Add("api-signature", apiSignature); + + StringBuilder historicUrl = new StringBuilder(); + historicUrl.Append("https://api.weatherlink.com/v2/historic/" + cumulus.WllStationId + "?"); + foreach (KeyValuePair entry in parameters) + { + historicUrl.Append(entry.Key); + historicUrl.Append("="); + historicUrl.Append(entry.Value); + historicUrl.Append("&"); + } + // remove the trailing "&" + historicUrl.Remove(historicUrl.Length - 1, 1); + + var logUrl = historicUrl.ToString().Replace(cumulus.WllApiKey, "<>"); + cumulus.LogDebugMessage($"WLL Health: WeatherLink URL = {logUrl}"); + + try + { + // we want to do this synchronously, so .Result + WlHistory histObj; + string responseBody; + int responseCode; + + using (HttpResponseMessage response = wlHttpClient.GetAsync(historicUrl.ToString()).Result) + { + responseBody = response.Content.ReadAsStringAsync().Result; + responseCode = (int)response.StatusCode; + cumulus.LogDataMessage($"WLL Health: WeatherLink API Response: {responseCode} - {responseBody}"); + } + + if (responseCode != 200) + { + var errObj = responseBody.FromJson(); + cumulus.LogMessage($"WLL Health: WeatherLink API Error: {errObj.code}, {errObj.message}"); + // Get wl.com status + GetSystemStatus(); + return; + } + + if (responseBody == "{}") + { + cumulus.LogMessage("WLL Health: WeatherLink API: No data was returned. Check your Device Id."); + cumulus.LastUpdateTime = Utils.FromUnixTime(endTime); + // Get wl.com status + GetSystemStatus(); + return; + } + + if (!responseBody.StartsWith("{\"sensors\":[{\"lsid\"")) // sanity check + { + // No idea what we got, dump it to the log + cumulus.LogMessage("WLL Health: Invalid historic message received"); + cumulus.LogDataMessage("WLL Health: Received: " + responseBody); + return; + } + + histObj = responseBody.FromJson(); + + // get the sensor data + if (histObj.sensors.Count == 0) + { + cumulus.LogMessage("WLL Health: No historic data available"); + return; + } + else + { + cumulus.LogDebugMessage($"WLL Health: Found {histObj.sensors.Count} sensor records to process"); + } + + + try + { + // Sensor types we are interested in... + // 504 = WLL Health + // 506 = AirLink Health + + // Get the LSID of the health station associated with each device + //var wllHealthLsid = GetWlHistoricHealthLsid(cumulus.WllParentId, 504); + var alInHealthLsid = GetWlHistoricHealthLsid(cumulus.airLinkInLsid, 506); + var alOutHealthLsid = GetWlHistoricHealthLsid(cumulus.airLinkOutLsid, 506); + + foreach (var sensor in histObj.sensors) + { + var sensorType = sensor.sensor_type; + var dataStructureType = sensor.data_structure_type; + var lsid = sensor.lsid; + + switch (sensorType) + { + // AirLink Outdoor + case 506 when lsid == alOutHealthLsid: + // Pass AirLink historic record to the AirLink module to process + if (cumulus.airLinkOut != null) + cumulus.airLinkOut.DecodeWlApiHealth(sensor, true); + break; + // AirLink Indoor + case 506 when lsid == alInHealthLsid: + // Pass AirLink historic record to the AirLink module to process + if (cumulus.airLinkIn != null) + cumulus.airLinkIn.DecodeWlApiHealth(sensor, true); + break; + default: + if (sensorType == 504 || dataStructureType == 11) + { + // Either a WLL (504) or ISS (data type = 11) record + DecodeWlApiHealth(sensor, true); + } + break; + } + } + } + catch (Exception ex) + { + cumulus.LogMessage("WLL Health: exception: " + ex.Message); + } + cumulus.BatteryLowAlarm.Triggered = TxBatText.Contains("LOW") || wllVoltageLow; + } + catch (Exception ex) + { + cumulus.LogMessage("WLL Health: exception: " + ex.Message); + } + + } + + // Finds all stations associated with this API + // Return true if only 1 result is found, else return false + private void GetAvailableStationIds(bool logToConsole = false) + { + var unixDateTime = Utils.ToUnixTime(DateTime.Now); + + if (cumulus.WllApiKey == string.Empty || cumulus.WllApiSecret == string.Empty) + { + cumulus.LogMessage("WLLStations: Missing WeatherLink API data in the cumulus.ini file, aborting!"); + return; + } + + SortedDictionary parameters = new SortedDictionary + { + { "api-key", cumulus.WllApiKey }, + { "t", unixDateTime.ToString() } + }; + + StringBuilder dataStringBuilder = new StringBuilder(); + foreach (KeyValuePair entry in parameters) + { + dataStringBuilder.Append(entry.Key); + dataStringBuilder.Append(entry.Value); + } + string header = dataStringBuilder.ToString(); + + var apiSignature = WlDotCom.CalculateApiSignature(cumulus.WllApiSecret, header); + parameters.Add("api-signature", apiSignature); + + StringBuilder stationsUrl = new StringBuilder(); + stationsUrl.Append("https://api.weatherlink.com/v2/stations?"); + foreach (KeyValuePair entry in parameters) + { + stationsUrl.Append(entry.Key); + stationsUrl.Append("="); + stationsUrl.Append(entry.Value); + stationsUrl.Append("&"); + } + // remove the trailing "&" + stationsUrl.Remove(stationsUrl.Length - 1, 1); + + var logUrl = stationsUrl.ToString().Replace(cumulus.WllApiKey, "<>"); + cumulus.LogDebugMessage($"WLLStations: URL = {logUrl}"); + + try + { + // We want to do this synchronously + string responseBody; + int responseCode; + + using (HttpResponseMessage response = wlHttpClient.GetAsync(stationsUrl.ToString()).Result) + { + responseBody = response.Content.ReadAsStringAsync().Result; + responseCode = (int)response.StatusCode; + cumulus.LogDebugMessage($"WLLStations: WeatherLink API Response: {responseCode}: {responseBody}"); + } + + if (responseCode != 200) + { + var errObj = responseBody.FromJson(); + cumulus.LogMessage($"WLLStations: WeatherLink API Error: {errObj.code} - {errObj.message}"); + return; + } + + var stationsObj = responseBody.FromJson(); + + foreach (var station in stationsObj.stations) + { + cumulus.LogMessage($"WLLStations: Found WeatherLink station id = {station.station_id}, name = {station.station_name}"); + if (stationsObj.stations.Count > 1 && logToConsole) + { + cumulus.LogConsoleMessage($" - Found WeatherLink station id = {station.station_id}, name = {station.station_name}, active = {station.active}"); + } + if (station.station_id == cumulus.WllStationId) + { + cumulus.LogDebugMessage($"WLLStations: Setting WLL parent ID = {station.gateway_id}"); + cumulus.WllParentId = station.gateway_id; + + if (station.recording_interval != cumulus.logints[cumulus.DataLogInterval]) + { + cumulus.LogMessage($"WLLStations: - Cumulus log interval {cumulus.logints[cumulus.DataLogInterval]} does not match this WeatherLink stations log interval {station.recording_interval}"); + } + } + } + if (stationsObj.stations.Count > 1 && cumulus.WllStationId < 10) + { + if (logToConsole) + cumulus.LogConsoleMessage(" - Enter the required station id from the above list into your WLL configuration to enable history downloads."); + } + else if (stationsObj.stations.Count == 1 && cumulus.WllStationId != stationsObj.stations[0].station_id) + { + cumulus.LogMessage($"WLLStations: Only found 1 WeatherLink station, using id = {stationsObj.stations[0].station_id}"); + cumulus.WllStationId = stationsObj.stations[0].station_id; + // And save it to the config file + cumulus.WriteIniFile(); + + cumulus.LogDebugMessage($"WLLStations: Setting WLL parent ID = {stationsObj.stations[0].gateway_id}"); + cumulus.WllParentId = stationsObj.stations[0].gateway_id; + return; + } + } + catch (Exception ex) + { + cumulus.LogDebugMessage("WLLStations: WeatherLink API exception: " + ex.Message); + } + return; + } + + private void GetAvailableSensors() + { + var unixDateTime = Utils.ToUnixTime(DateTime.Now); + + if (cumulus.WllApiKey == string.Empty || cumulus.WllApiSecret == string.Empty) + { + cumulus.LogMessage("GetAvailableSensors: WeatherLink API data is missing in the configuration, aborting!"); + return; + } + + if (cumulus.WllStationId < 10) + { + cumulus.LogMessage("GetAvailableSensors: No WeatherLink API station ID has been configured, aborting!"); + return; + } + + SortedDictionary parameters = new SortedDictionary + { + { "api-key", cumulus.WllApiKey }, + { "t", unixDateTime.ToString() } + }; + + StringBuilder dataStringBuilder = new StringBuilder(); + foreach (KeyValuePair entry in parameters) + { + dataStringBuilder.Append(entry.Key); + dataStringBuilder.Append(entry.Value); + } + string header = dataStringBuilder.ToString(); + + var apiSignature = WlDotCom.CalculateApiSignature(cumulus.WllApiSecret, header); + parameters.Add("api-signature", apiSignature); + + StringBuilder stationsUrl = new StringBuilder(); + stationsUrl.Append("https://api.weatherlink.com/v2/sensors?"); + foreach (KeyValuePair entry in parameters) + { + stationsUrl.Append(entry.Key); + stationsUrl.Append("="); + stationsUrl.Append(entry.Value); + stationsUrl.Append("&"); + } + // remove the trailing "&" + stationsUrl.Remove(stationsUrl.Length - 1, 1); + + var logUrl = stationsUrl.ToString().Replace(cumulus.WllApiKey, "<>"); + cumulus.LogDebugMessage($"GetAvailableSensors: URL = {logUrl}"); + + WlSensorList sensorsObj = new WlSensorList(); + + try + { + // We want to do this synchronously + string responseBody; + int responseCode; + + using (HttpResponseMessage response = wlHttpClient.GetAsync(stationsUrl.ToString()).Result) + { + responseBody = response.Content.ReadAsStringAsync().Result; + responseCode = (int)response.StatusCode; + cumulus.LogDebugMessage($"GetAvailableSensors: WeatherLink API Response: {responseCode}: {responseBody}"); + } + + if (responseCode != 200) + { + var errObj = responseBody.FromJson(); + cumulus.LogMessage($"GetAvailableSensors: WeatherLink API Error: {errObj.code} - {errObj.message}"); + return; + } + + sensorsObj = responseBody.FromJson(); + } + catch (Exception ex) + { + cumulus.LogDebugMessage("GetAvailableSensors: WeatherLink API exception: " + ex.Message); + } + + // Sensor types we are interested in... + // 323 = Outdoor AirLink + // 326 = Indoor AirLink + // 504 = WLL Health + // 506 = AirLink Health + var types = new[] { 45, 323, 326, 504, 506 }; + foreach (var sensor in sensorsObj.sensors) + { + try + { + cumulus.LogDebugMessage($"GetAvailableSensors: Found WeatherLink Sensor type={sensor.sensor_type}, lsid={sensor.lsid}, station_id={sensor.station_id}, name={sensor.product_name}, parentId={sensor.parent_device_id}, parent={sensor.parent_device_name}"); + + if (types.Contains(sensor.sensor_type) || sensor.category == "ISS") + { + var wlSensor = new WlSensor(sensor.sensor_type, sensor.lsid, sensor.parent_device_id, sensor.product_name, sensor.parent_device_name); + sensorList.Add(wlSensor); + if (wlSensor.SensorType == 323 && sensor.station_id == cumulus.AirLinkOutStationId) + { + cumulus.LogDebugMessage($"GetAvailableSensors: Setting AirLink Outdoor LSID to {wlSensor.LSID}"); + cumulus.airLinkOutLsid = wlSensor.LSID; + } + else if (wlSensor.SensorType == 326 && sensor.station_id == cumulus.AirLinkInStationId) + { + cumulus.LogDebugMessage($"GetAvailableSensors: Setting AirLink Indoor LSID to {wlSensor.LSID}"); + cumulus.airLinkInLsid = wlSensor.LSID; + } + } + } + catch (Exception ex) + { + cumulus.LogDebugMessage("GetAvailableSensors: Processing sensors exception: " + ex.Message); + } + } + } + + private void BroadcastTimeout(object source, ElapsedEventArgs e) + { + if (broadcastReceived) + { + broadcastReceived = false; + DataStopped = false; + cumulus.DataStoppedAlarm.Triggered = false; + } + else + { + cumulus.LogMessage($"ERROR: No broadcast data received from the WLL for {tmrBroadcastWatchdog.Interval / 1000} seconds"); + DataStopped = true; + cumulus.DataStoppedAlarm.LastError = $"No broadcast data received from the WLL for {tmrBroadcastWatchdog.Interval / 1000} seconds"; + cumulus.DataStoppedAlarm.Triggered = true; + // Try and give the broadcasts a kick in case the last command did not get through + GetWllRealtime(null, null); + } + } + + private int GetWlHistoricHealthLsid(int id, int type) + { + try + { + var sensor = sensorList.FirstOrDefault(i => i.LSID == id || i.ParentID == id); + if (sensor != null) + { + var health = sensorList.FirstOrDefault(i => i.ParentID == sensor.ParentID && i.SensorType == type); + if (health != null) + { + return health.LSID; + } + } + } + catch + { } + return 0; + } + + private void GetSystemStatus() + { + WlComSystemStatus status; + try + { + string responseBody; + int responseCode; + + cumulus.LogDebugMessage("GetSystemStatus: Getting WeatherLink.com system status"); + + // we want to do this synchronously, so .Result + using (HttpResponseMessage response = wlHttpClient.GetAsync("https://0886445102835570.hostedstatus.com/1.0/status/600712dea9c1290530967bc6").Result) + { + responseBody = responseBody = response.Content.ReadAsStringAsync().Result; + responseCode = (int)response.StatusCode; + cumulus.LogDebugMessage($"GetSystemStatus: WeatherLink.com system status Response code: {responseCode}"); + cumulus.LogDataMessage($"GetSystemStatus: WeatherLink.com system status Response: {responseBody}"); + } + + if (responseCode != 200) + { + cumulus.LogMessage($"GetSystemStatus: WeatherLink.com system status Error: {responseCode}"); + cumulus.LogConsoleMessage($" - Error {responseCode}"); + return; + } + + status = responseBody.FromJson(); + + if (responseBody == "{}") + { + cumulus.LogMessage("GetSystemStatus: WeatherLink.com system status: No data was returned."); + return; + } + else if (status != null) + { + var msg = $"Weatherlink.com overall System Status: '{status.result.status_overall.status}', Updated: {status.result.status_overall.updated}"; + if (status.result.status_overall.status_code != 100) + { + msg += "Error: "; + cumulus.LogMessage(msg); + Console.WriteLine(msg); + } + else + { + cumulus.LogDebugMessage(msg); + } + // If we are not OK, then find what isn't working + if (status.result.status_overall.status_code != 100) + { + foreach (var subSys in status.result.status) + { + msg = $" wl.com system: {subSys.name}, status: {subSys.status}, updated: {subSys.updated}"; + cumulus.LogMessage(msg); + Console.WriteLine(msg); + } + } + } + else + { + cumulus.LogMessage("GetSystemStatus: Something went wrong!"); + } + + } + catch (Exception ex) + { + cumulus.LogMessage("GetSystemStatus: Exception: " + ex); + return; + } + + return; + } + + private class WllBroadcast + { + public string did { get; set; } + public int ts { get; set; } + public List conditions { get; set; } + } + + private class WllBroadcastRec + { + public string lsid { get; set; } + public int txid { get; set; } + public double wind_speed_last { get; set; } + public int? wind_dir_last { get; set; } + public int rain_size { get; set; } + public double rain_rate_last { get; set; } + public int rain_15_min { get; set; } + public int rain_60_min { get; set; } + public int rain_24_hr { get; set; } + public int rain_storm { get; set; } + public long rain_storm_start_at { get; set; } + public int rainfall_daily { get; set; } + public int rainfall_monthly { get; set; } + public int rainfall_year { get; set; } + public double wind_speed_hi_last_10_min { get; set; } + public int wind_dir_at_hi_speed_last_10_min { get; set; } + } + + // Response from WLL when asked to start multicasting + private class WllBroadcastReqResponse + { + public WllBroadcastReqResponseData data { get; set; } + public string error { get; set; } + } + + private class WllBroadcastReqResponseData + { + public int broadcast_port { get; set; } + public int duration { get; set; } + } + + private class WllCurrent + { + public WllCurrentDevice data { get; set; } + public string error { get; set; } + } + + private class WllCurrentDevice + { + public string did { get; set; } + public long ts { get; set; } + public List conditions { get; set; } // We have no clue what these structures are going to be ahead of time + } + + private class WllCurrentType1 + { + public int lsid { get; set; } + public int data_structure_type { get; set; } + public int txid { get; set; } + public double temp { get; set; } + public double hum { get; set; } + public double dew_point { get; set; } + public double heat_index { get; set; } + public double wind_chill { get; set; } + public double thw_index { get; set; } + public double thsw_index { get; set; } + public double? wind_speed_last { get; set; } + public int? wind_dir_last { get; set; } + public double wind_speed_avg_last_1_min { get; set; } + public double wind_dir_scalar_avg_last_1_min { get; set; } + public double wind_speed_avg_last_2_min { get; set; } + public double wind_dir_scalar_avg_last_2_min { get; set; } + public double wind_speed_hi_last_2_min { get; set; } + public int wind_dir_at_hi_speed_last_2_min { get; set; } + public double? wind_speed_avg_last_10_min { get; set; } + public double wind_dir_scalar_avg_last_10_min { get; set; } + public double wind_speed_hi_last_10_min { get; set; } + public int wind_dir_at_hi_speed_last_10_min { get; set; } + public int rain_size { get; set; } + public double rain_rate_last { get; set; } + public double rain_rate_hi { get; set; } + public double rainfall_last_15_min { get; set; } + public double rain_rate_hi_last_15_min { get; set; } + public double rainfall_last_60_min { get; set; } + public double rainfall_last_24_hr { get; set; } + public int? rain_storm { get; set; } + public long? rain_storm_start_at { get; set; } + public int solar_rad { get; set; } + public double uv_index { get; set; } + public int rx_state { get; set; } + public uint trans_battery_flag { get; set; } + public int rainfall_daily { get; set; } + public int rainfall_monthly { get; set; } + public int rainfall_year { get; set; } + public int rain_storm_last { get; set; } + public long rain_storm_last_start_at { get; set; } + public long rain_storm_last_end_at { get; set; } + } + + private class WllCurrentType2 + { + public int lsid { get; set; } + public int data_structure_type { get; set; } + public int txid { get; set; } + public double temp_1 { get; set; } + public double temp_2 { get; set; } + public double temp_3 { get; set; } + public double temp_4 { get; set; } + public double moist_soil_1 { get; set; } + public double moist_soil_2 { get; set; } + public double moist_soil_3 { get; set; } + public double moist_soil_4 { get; set; } + public double wet_leaf_1 { get; set; } + public double wet_leaf_2 { get; set; } + public int rx_state { get; set; } + public uint trans_battery_flag { get; set; } + public object this[string name] + { + get + { + Type myType = typeof(WllCurrentType2); + PropertyInfo myPropInfo = myType.GetProperty(name); + return myPropInfo.GetValue(this, null); + } + } + } + + // WLL Current Baro + private class WllCurrentType3 + { + public int lsid { get; set; } + public int data_structure_type { get; set; } + public double bar_sea_level { get; set; } + public double bar_trend { get; set; } + public double bar_absolute { get; set; } + } + + // WLL Current internal temp/hum + private class WllCurrentType4 + { + public int lsid { get; set; } + public int data_structure_type { get; set; } + public double temp_in { get; set; } + public double hum_in { get; set; } + public double dew_point_in { get; set; } + public double heat_index_in { get; set; } + } + } } \ No newline at end of file diff --git a/CumulusMX/EmailSender.cs b/CumulusMX/EmailSender.cs index 894479a3..42534627 100644 --- a/CumulusMX/EmailSender.cs +++ b/CumulusMX/EmailSender.cs @@ -1,180 +1,197 @@ -using System; -using MailKit.Net.Smtp; -using MimeKit; -using System.Text.RegularExpressions; -using MailKit; -using System.Threading; - -namespace CumulusMX -{ - public class EmailSender - { - static readonly Regex ValidEmailRegex = CreateValidEmailRegex(); - private static SemaphoreSlim _writeLock; - private readonly Cumulus cumulus; - - public EmailSender(Cumulus cumulus) - { - this.cumulus = cumulus; - _writeLock = new SemaphoreSlim(1); - } - - - public async void SendEmail(string[] to, string from, string subject, string message, bool isHTML) - { - try - { - cumulus.LogDebugMessage($"SendEmail: Waiting for lock..."); - await _writeLock.WaitAsync(); - cumulus.LogDebugMessage($"SendEmail: Has the lock"); - - cumulus.LogDebugMessage($"SendEmail: Sending email, to [{string.Join("; ", to)}], subject [{subject}], body [{message}]..."); - - var m = new MimeMessage(); - m.From.Add(new MailboxAddress("", from)); - foreach (var addr in to) - { - m.To.Add(new MailboxAddress("", addr)); - } - m.Subject = subject; - - BodyBuilder bodyBuilder = new BodyBuilder(); - if (isHTML) - { - bodyBuilder.HtmlBody = message; - } - else - { - bodyBuilder.TextBody = message; - } - - m.Body = bodyBuilder.ToMessageBody(); - - using (SmtpClient client = cumulus.SmtpOptions.Logging ? new SmtpClient(new ProtocolLogger("MXdiags/smtp.log")) : new SmtpClient()) - { - await client.ConnectAsync(cumulus.SmtpOptions.Server, cumulus.SmtpOptions.Port, (MailKit.Security.SecureSocketOptions)cumulus.SmtpOptions.SslOption); - - // Note: since we don't have an OAuth2 token, disable - // the XOAUTH2 authentication mechanism. - client.AuthenticationMechanisms.Remove("XOAUTH2"); - - if (cumulus.SmtpOptions.RequiresAuthentication) - { - await client.AuthenticateAsync(cumulus.SmtpOptions.User, cumulus.SmtpOptions.Password); - //client.Authenticate(cumulus.SmtpOptions.User, cumulus.SmtpOptions.Password); - } - - await client.SendAsync(m); - client.Disconnect(true); - } - } - catch (Exception e) - { - cumulus.LogMessage("SendEmail: Error - " + e); - - } - finally - { - cumulus.LogDebugMessage($"SendEmail: Releasing lock..."); - _writeLock.Release(); - } - } - - public string SendTestEmail(string[] to, string from, string subject, string message, bool isHTML) - { - string retVal; - - try - { - cumulus.LogDebugMessage($"SendEmail: Waiting for lock..."); - _writeLock.Wait(); - cumulus.LogDebugMessage($"SendEmail: Has the lock"); - - cumulus.LogDebugMessage($"SendEmail: Sending Test email, to [{string.Join("; ", to)}], subject [{subject}], body [{message}]..."); - - var m = new MimeMessage(); - m.From.Add(new MailboxAddress("", from)); - foreach (var addr in to) - { - m.To.Add(new MailboxAddress("", addr)); - } - m.Subject = subject; - - BodyBuilder bodyBuilder = new BodyBuilder(); - if (isHTML) - { - bodyBuilder.HtmlBody = message; - } - else - { - bodyBuilder.TextBody = message; - } - - m.Body = bodyBuilder.ToMessageBody(); - - using (SmtpClient client = cumulus.SmtpOptions.Logging ? new SmtpClient(new ProtocolLogger("MXdiags/smtp.log")) : new SmtpClient()) - { - client.Connect(cumulus.SmtpOptions.Server, cumulus.SmtpOptions.Port, (MailKit.Security.SecureSocketOptions)cumulus.SmtpOptions.SslOption); - //client.Connect(cumulus.SmtpOptions.Server, cumulus.SmtpOptions.Port, MailKit.Security.SecureSocketOptions.StartTlsWhenAvailable); - - // Note: since we don't have an OAuth2 token, disable - // the XOAUTH2 authentication mechanism. - client.AuthenticationMechanisms.Remove("XOAUTH2"); - - if (cumulus.SmtpOptions.RequiresAuthentication) - { - client.Authenticate(cumulus.SmtpOptions.User, cumulus.SmtpOptions.Password); - //client.Authenticate(cumulus.SmtpOptions.User, cumulus.SmtpOptions.Password); - } - - client.Send(m); - client.Disconnect(true); - } - - retVal = "OK"; - } - catch (Exception e) - { - cumulus.LogMessage("SendEmail: Error - " + e); - retVal = e.Message; - } - finally - { - cumulus.LogDebugMessage($"SendEmail: Releasing lock..."); - _writeLock.Release(); - } - - return retVal; - } - - - private static Regex CreateValidEmailRegex() - { - string validEmailPattern = @"^(?!\.)(""([^""\r\\]|\\[""\r\\])*""|" - + @"([-a-z0-9!#$%&'*+/=?^_`{|}~]|(? datalist; - - //private readonly int maxHistoryEntries; - private int prevaddr = -1; - private int prevraintotal = -1; - private int ignoreraincount; - private int synchroPhase; - private DateTime previousSensorClock; - private DateTime previousStationClock; - private bool synchronising; - //private DateTime lastraintip; - //private int raininlasttip = 0; - private int interval = 0; - private int followinginterval = 0; - //private readonly double[] WindRunHourMult = {3.6, 1.0, 1.0, 1.0}; - private readonly Timer tmrDataRead; - private int readCounter; - private bool hadfirstsyncdata; - private readonly byte[] prevdata = new byte[16]; - private readonly int foEntrysize; - private readonly int foMaxAddr; - //private int FOmaxhistoryentries; - private readonly bool hasSolar; - private bool readingData = false; - - const int DefaultVid = 0x1941; - const int DefaultPid = 0x8021; - - internal FOStation(Cumulus cumulus) : base(cumulus) - { - cumulus.Manufacturer = cumulus.EW; - var data = new byte[32]; - - tmrDataRead = new Timer(); - - calculaterainrate = true; - - hasSolar = cumulus.StationType == StationTypes.FineOffsetSolar; - - if (hasSolar) - { - foEntrysize = 0x14; - foMaxAddr = 0xFFEC; - //maxHistoryEntries = 3264; - } - else - { - foEntrysize = 0x10; - foMaxAddr = 0xFFF0; - //maxHistoryEntries = 4080; - } - - do - { - if (OpenHidDevice()) - { - // Get the block of data containing the logging interval - cumulus.LogMessage("Reading station logging interval"); - if (ReadAddress(0x10, data)) - { - int logint = data[0]; - - if (logint != cumulus.logints[cumulus.DataLogInterval]) - { - var msg = $"Warning, your console logging interval ({logint} mins) does not match the Cumulus logging interval ({cumulus.logints[cumulus.DataLogInterval]} mins)"; - cumulus.LogConsoleMessage(msg); - cumulus.LogMessage(msg); - if (cumulus.FineOffsetOptions.SetLoggerInterval) - { - WriteAddress(0x10, (byte)cumulus.logints[cumulus.DataLogInterval]); // write the logging new logging interval - WriteAddress(0x1A, 0xAA); // tell the station to read the new parameter - do - { - Thread.Sleep(1000); // sleep to let it reconfigure - ReadAddress(0x10, data); - } while (data[9] != 0); - } - } - } - - // Get the block of data containing the abs and rel pressures - cumulus.LogMessage("Reading station pressure offset"); - - double relpressure = (((data[17] & 0x3f) * 256) + data[16]) / 10.0f; - double abspressure = (((data[19] & 0x3f) * 256) + data[18]) / 10.0f; - pressureOffset = relpressure - abspressure; - cumulus.LogMessage("Rel pressure = " + relpressure); - cumulus.LogMessage("Abs pressure = " + abspressure); - cumulus.LogMessage("Calculated Offset = " + pressureOffset); - if (cumulus.EwOptions.PressOffset < 9999.0) - { - cumulus.LogMessage("Ignoring calculated offset, using offset value from cumulus.ini file"); - cumulus.LogMessage("EWpressureoffset = " + cumulus.EwOptions.PressOffset); - pressureOffset = cumulus.EwOptions.PressOffset; - } - - // Read the data from the logger - startReadingHistoryData(); - } - else - { - // pause for 10 seconds then try again - Thread.Sleep(10000); - } - } while (hidDevice == null || stream == null || !stream.CanRead); - } - - public override void startReadingHistoryData() - { - cumulus.CurrentActivity = "Getting archive data"; - //lastArchiveTimeUTC = getLastArchiveTime(); - - LoadLastHoursFromDataLogs(cumulus.LastUpdateTime); - - bw = new BackgroundWorker(); - bw.DoWork += bw_DoWork; - bw.RunWorkerCompleted += bw_RunWorkerCompleted; - bw.WorkerReportsProgress = true; - bw.RunWorkerAsync(); - } - - public override void Stop() - { - cumulus.LogMessage("Stopping data read timer"); - tmrDataRead.Stop(); - cumulus.LogMessage("Stopping minute timer"); - StopMinuteTimer(); - cumulus.LogMessage("Nulling hidDevice"); - hidDevice = null; - cumulus.LogMessage("Exit FOStation.Stop()"); - } - - private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) - { - cumulus.CurrentActivity = "Normal running"; - cumulus.LogMessage("Archive reading thread completed"); - Start(); - DoDayResetIfNeeded(); - DoTrendValues(DateTime.Now); - cumulus.StartTimersAndSensors(); - } - - private void bw_DoWork(object sender, DoWorkEventArgs e) - { - //var ci = new CultureInfo("en-GB"); - //System.Threading.Thread.CurrentThread.CurrentCulture = ci; - cumulus.LogDebugMessage("Lock: Station waiting for the lock"); - Cumulus.syncInit.Wait(); - cumulus.LogDebugMessage("Lock: Station has the lock"); - try - { - getAndProcessHistoryData(); - } - catch (Exception ex) - { - cumulus.LogMessage("Exception occurred reading archive data: " + ex.Message); - } - cumulus.LogDebugMessage("Lock: Station releasing the lock"); - Cumulus.syncInit.Release(); - } - - public override void getAndProcessHistoryData() - { - var data = new byte[32]; - cumulus.LogMessage("Current culture: " + CultureInfo.CurrentCulture.DisplayName); - //DateTime now = DateTime.Now; - cumulus.LogMessage(DateTime.Now.ToString("G")); - cumulus.LogMessage("Start reading history data"); - cumulus.LogConsoleMessage("Downloading Archive Data"); - DateTime timestamp = DateTime.Now; - //LastUpdateTime = DateTime.Now; // lastArchiveTimeUTC.ToLocalTime(); - cumulus.LogMessage("Last Update = " + cumulus.LastUpdateTime); - cumulus.LogDebugMessage("Reading fixed memory block"); - if (!ReadAddress(0, data)) - { - return; - } - - // get address of current location - int addr = data[31] * 256 + data[30]; - int previousaddress = addr; - - // get the number of logger entries the console has recorded - int logEntries = data[28] * 256 + data[27]; - cumulus.LogDebugMessage($"Console has {logEntries} log entries"); - - cumulus.LogMessage("Reading current address " + addr.ToString("X4")); - if (!ReadAddress(addr, data)) - { - return; - } - - bool moredata = true; - - datalist = new List(); - - while (moredata) - { - followinginterval = interval; - interval = data[0]; - cumulus.LogDebugMessage($"This logger record interval = {interval} mins"); - - // calculate timestamp of previous history data - timestamp = timestamp.AddMinutes(-interval); - - if ((interval != 255) && (timestamp > cumulus.LastUpdateTime) && (datalist.Count < logEntries)) - { - // Test if the current address has changed - cumulus.LogDebugMessage("Reading fixed memory block"); - if (!ReadAddress(0, data)) - { - return; - } - var newAddr = data[31] * 256 + data[30]; - if (newAddr != previousaddress) - { - // The current logger address has changed, pause to allow console to sort itself out - cumulus.LogDebugMessage("Console logger location changed, pausing for a sort while"); - previousaddress = newAddr; - Thread.Sleep(2000); - } - - // Read previous data - addr -= foEntrysize; - if (addr < 0x100) - { - addr = foMaxAddr; // wrap around - } - - cumulus.LogMessage("Read logger entry for " + timestamp + " address " + addr.ToString("X4")); - if (!ReadAddress(addr, data)) - { - return; - } - cumulus.LogDebugMessage("Logger Data block: " + BitConverter.ToString(data, 0, foEntrysize)); - - // add history data to collection - - var histData = new HistoryData(); - - - histData.timestamp = timestamp; - histData.interval = interval; - histData.followinginterval = followinginterval; - histData.inHum = data[1] == 255 ? 10 : data[1]; - histData.outHum = data[4] == 255 ? 10 : data[4]; - double outtemp = (data[5] + (data[6] & 0x7F)*256)/10.0f; - var sign = (byte) (data[6] & 0x80); - if (sign == 0x80) outtemp = -outtemp; - if (outtemp > -200) histData.outTemp = outtemp; - histData.windGust = (data[10] + ((data[11] & 0xF0)*16))/10.0f; - histData.windSpeed = (data[9] + ((data[11] & 0x0F)*256))/10.0f; - histData.windBearing = (int) (data[12]*22.5f); - - histData.rainCounter = data[13] + (data[14]*256); - - double intemp = (data[2] + (data[3] & 0x7F)*256)/10.0f; - sign = (byte) (data[3] & 0x80); - if (sign == 0x80) intemp = -intemp; - histData.inTemp = intemp; - // Get pressure and convert to sea level - histData.pressure = (data[7] + (data[8]*256))/10.0f + pressureOffset; - histData.SensorContactLost = (data[15] & 0x40) == 0x40; - if (hasSolar) - { - histData.uvVal = data[19]; - histData.solarVal = (data[16] + (data[17]*256) + (data[18]*65536))/10.0; - } - - datalist.Add(histData); - - bw.ReportProgress(datalist.Count, "collecting"); - } - else - { - moredata = false; - } - } - - cumulus.LogMessage("Completed read of history data from the console"); - cumulus.LogMessage("Number of history entries = " + datalist.Count); - - if (datalist.Count > 0) - { - ProcessHistoryData(); - } - - //using (cumulusEntities dataContext = new cumulusEntities()) - //{ - // UpdateHighsAndLows(dataContext); - //} - } - - private void ProcessHistoryData() - { - int totalentries = datalist.Count; - - cumulus.LogMessage("Processing history data, number of entries = " + totalentries); - - int rollHour = Math.Abs(cumulus.GetHourInc()); - int luhour = cumulus.LastUpdateTime.Hour; - bool rolloverdone = luhour == rollHour; - bool midnightraindone = luhour == 0; - - while (datalist.Count > 0) - { - HistoryData historydata = datalist[datalist.Count - 1]; - - DateTime timestamp = historydata.timestamp; - - cumulus.LogMessage("Processing data for " + timestamp); - - int h = timestamp.Hour; - - // if outside rollover hour, rollover yet to be done - if (h != rollHour) - { - rolloverdone = false; - } - - // In rollover hour and rollover not yet done - if (h == rollHour && !rolloverdone) - { - // do rollover - cumulus.LogMessage("Day rollover " + timestamp.ToShortTimeString()); - DayReset(timestamp); - - rolloverdone = true; - } - - // Not in midnight hour, midnight rain yet to be done - if (h != 0) - { - midnightraindone = false; - } - - // In midnight hour and midnight rain (and sun) not yet done - if (h == 0 && !midnightraindone) - { - ResetMidnightRain(timestamp); - ResetSunshineHours(); - midnightraindone = true; - } - - // Indoor Humidity ====================================================== - if (historydata.inHum > 100 || historydata.inHum < 0) - { - // 255 is the overflow value, when RH gets below 10% - ignore - cumulus.LogMessage("Ignoring bad data: inhum = " + historydata.inHum); - } - else - { - DoIndoorHumidity(historydata.inHum); - } - - // Indoor Temperature =================================================== - if (historydata.inTemp < -50 || historydata.inTemp > 50) - { - cumulus.LogMessage("Ignoring bad data: intemp = " + historydata.inTemp); - } - else - { - DoIndoorTemp(ConvertTempCToUser(historydata.inTemp)); - } - - // Pressure ============================================================= - - if ((historydata.pressure < cumulus.EwOptions.MinPressMB) || (historydata.pressure > cumulus.EwOptions.MaxPressMB)) - { - cumulus.LogMessage("Ignoring bad data: pressure = " + historydata.pressure); - cumulus.LogMessage(" offset = " + pressureOffset); - } - else - { - DoPressure(ConvertPressMBToUser(historydata.pressure), timestamp); - } - - if (historydata.SensorContactLost) - { - cumulus.LogMessage("Sensor contact lost; ignoring outdoor data"); - } - else - { - // Outdoor Humidity ===================================================== - if (historydata.outHum > 100 || historydata.outHum < 0) - { - // 255 is the overflow value, when RH gets below 10% - ignore - cumulus.LogMessage("Ignoring bad data: outhum = " + historydata.outHum); - } - else - { - DoOutdoorHumidity(historydata.outHum, timestamp); - } - - // Wind ================================================================= - if (historydata.windGust > 60 || historydata.windGust < 0) - { - cumulus.LogMessage("Ignoring bad data: gust = " + historydata.windGust); - } - else if (historydata.windSpeed > 60 || historydata.windSpeed < 0) - { - cumulus.LogMessage("Ignoring bad data: speed = " + historydata.windSpeed); - } - else - { - DoWind(ConvertWindMSToUser(historydata.windGust), historydata.windBearing, ConvertWindMSToUser(historydata.windSpeed), timestamp); - } - - // Outdoor Temperature ================================================== - if (historydata.outTemp < -50 || historydata.outTemp > 70) - { - cumulus.LogMessage("Ignoring bad data: outtemp = " + historydata.outTemp); - } - else - { - DoOutdoorTemp(ConvertTempCToUser(historydata.outTemp), timestamp); - // add in 'archivePeriod' minutes worth of temperature to the temp samples - tempsamplestoday += historydata.interval; - TempTotalToday += (OutdoorTemperature*historydata.interval); - } - - // update chill hours - if (OutdoorTemperature < cumulus.ChillHourThreshold) - { - // add 1 minute to chill hours - ChillHours += (historydata.interval / 60.0); - } - - var raindiff = prevraintotal == -1 ? 0 : historydata.rainCounter - prevraintotal; - - // record time of last rain tip, to use in - // normal running rain rate calc NB rain rate calc not currently used - /* - if (raindiff > 0) - { - lastraintip = timestamp; - - raininlasttip = raindiff; - } - else - { - lastraintip = DateTime.MinValue; - - raininlasttip = 0; - } - */ - double rainrate; - - if (raindiff > 100) - { - cumulus.LogMessage("Warning: large increase in rain gauge tip count: " + raindiff); - rainrate = 0; - } - else - { - if (historydata.interval > 0) - { - rainrate = ConvertRainMMToUser((raindiff * 0.3) * (60.0 / historydata.interval)); - } - else - { - rainrate = 0; - } - } - - DoRain(ConvertRainMMToUser(historydata.rainCounter*0.3), rainrate, timestamp); - - prevraintotal = historydata.rainCounter; - - OutdoorDewpoint = ConvertTempCToUser(MeteoLib.DewPoint(ConvertUserTempToC(OutdoorTemperature), OutdoorHumidity)); - - CheckForDewpointHighLow(timestamp); - - // calculate wind chill - - if (ConvertUserWindToMS(WindAverage) < 1.5) - { - DoWindChill(OutdoorTemperature, timestamp); - } - else - { - // calculate wind chill from calibrated C temp and calibrated win in KPH - DoWindChill(ConvertTempCToUser(MeteoLib.WindChill(ConvertUserTempToC(OutdoorTemperature), ConvertUserWindToKPH(WindAverage))), timestamp); - } - - DoApparentTemp(timestamp); - DoFeelsLike(timestamp); - DoHumidex(timestamp); - - if (hasSolar) - { - if (historydata.uvVal == 255) - { - // ignore - } - else if (historydata.uvVal < 0) - DoUV(0, timestamp); - else if (historydata.uvVal > 16) - DoUV(16, timestamp); - else - DoUV(historydata.uvVal, timestamp); - - if ((historydata.solarVal >= 0) && (historydata.solarVal <= 300000)) - { - DoSolarRad((int) Math.Floor(historydata.solarVal*cumulus.LuxToWM2), timestamp); - - // add in archive period worth of sunshine, if sunny - if ((SolarRad > CurrentSolarMax*cumulus.SunThreshold/100) && (SolarRad >= cumulus.SolarMinimum)) - SunshineHours += (historydata.interval/60.0); - - LightValue = historydata.solarVal; - } - } - } - // add in 'following interval' minutes worth of wind speed to windrun - cumulus.LogMessage("Windrun: " + WindAverage.ToString(cumulus.WindFormat) + cumulus.Units.WindText + " for " + historydata.followinginterval + " minutes = " + - (WindAverage*WindRunHourMult[cumulus.Units.Wind]*historydata.followinginterval/60.0).ToString(cumulus.WindRunFormat) + cumulus.Units.WindRunText); - - WindRunToday += (WindAverage*WindRunHourMult[cumulus.Units.Wind]*historydata.followinginterval/60.0); - - // update heating/cooling degree days - UpdateDegreeDays(historydata.interval); - - // update dominant wind bearing - CalculateDominantWindBearing(Bearing, WindAverage, historydata.interval); - - CheckForWindrunHighLow(timestamp); - - bw.ReportProgress((totalentries - datalist.Count)*100/totalentries, "processing"); - - //UpdateDatabase(timestamp.ToUniversalTime(), historydata.interval, false); - - cumulus.DoLogFile(timestamp,false); - if (cumulus.StationOptions.LogExtraSensors) - { - cumulus.DoExtraLogFile(timestamp); - } - - AddLastHourDataEntry(timestamp, Raincounter, OutdoorTemperature); - AddGraphDataEntry(timestamp, Raincounter, RainToday, RainRate, OutdoorTemperature, OutdoorDewpoint, ApparentTemperature, WindChill, HeatIndex, - IndoorTemperature, Pressure, WindAverage, RecentMaxGust, AvgBearing, Bearing, OutdoorHumidity, IndoorHumidity, SolarRad, CurrentSolarMax, UV, FeelsLike, Humidex); - AddLast3HourDataEntry(timestamp, Pressure, OutdoorTemperature); - AddRecentDataEntry(timestamp, WindAverage, RecentMaxGust, WindLatest, Bearing, AvgBearing, OutdoorTemperature, WindChill, OutdoorDewpoint, HeatIndex, - OutdoorHumidity, Pressure, RainToday, SolarRad, UV, Raincounter, FeelsLike, Humidex); - RemoveOldLHData(timestamp); - RemoveOldL3HData(timestamp); - RemoveOldGraphData(timestamp); - DoTrendValues(timestamp); - UpdatePressureTrendString(); - UpdateStatusPanel(timestamp); - cumulus.AddToWebServiceLists(timestamp); - datalist.RemoveAt(datalist.Count - 1); - } - cumulus.LogMessage("End processing history data"); - } - - /// - /// Read and process data in a loop, sleeping between reads - /// - public override void Start() - { - tmrDataRead.Elapsed += DataReadTimerTick; - tmrDataRead.Interval = 10000; - tmrDataRead.Enabled = true; - } - - private bool OpenHidDevice() - { - var devicelist = DeviceList.Local; - - int vid = (cumulus.FineOffsetOptions.VendorID < 0 ? DefaultVid : cumulus.FineOffsetOptions.VendorID); - int pid = (cumulus.FineOffsetOptions.ProductID < 0 ? DefaultPid : cumulus.FineOffsetOptions.ProductID); - - cumulus.LogMessage("Looking for Fine Offset station, VendorID=0x" + vid.ToString("X4") + " ProductID=0x" + pid.ToString("X4")); - cumulus.LogConsoleMessage("Looking for Fine Offset station"); - - hidDevice = devicelist.GetHidDeviceOrNull(vendorID: vid, productID: pid); - - if (hidDevice != null) - { - cumulus.LogMessage("Fine Offset station found"); - cumulus.LogConsoleMessage("Fine Offset station found"); - - if (hidDevice.TryOpen(out stream)) - { - cumulus.LogMessage("Stream opened"); - cumulus.LogConsoleMessage("Connected to station"); - stream.Flush(); - return true; - } - else - { - cumulus.LogMessage("Stream open failed"); - return false; - } - } - else - { - cumulus.LogMessage("*** Fine Offset station not found ***"); - cumulus.LogMessage("Found the following USB HID Devices..."); - int cnt = 0; - foreach (HidDevice device in devicelist.GetHidDevices()) - { - cumulus.LogMessage($" {device}"); - cnt++; - } - - if (cnt == 0) - { - cumulus.LogMessage("No USB HID devices found!"); - } - - return false; - } - } - - - /// - /// Read the 32 bytes starting at 'address' - /// - /// The address of the data - /// Where to return the data - private bool ReadAddress(int address, byte[] buff) - { - //cumulus.LogMessage("Reading address " + address.ToString("X6")); - var lowbyte = (byte) (address & 0xFF); - var highbyte = (byte) (address >> 8); - - // Returns 9-byte usb packet, with report ID in first byte - var response = new byte[9]; - const int responseLength = 9; - const int startByte = 1; - - var request = new byte[] {0, 0xa1, highbyte, lowbyte, 0x20, 0xa1, highbyte, lowbyte, 0x20}; - - int ptr = 0; - - if (hidDevice == null) - { - DataStopped = true; - cumulus.DataStoppedAlarm.Triggered = true; - return false; - } - - //response = device.WriteRead(0x00, request); - try - { - stream.Write(request); - } - catch (Exception ex) - { - cumulus.LogConsoleMessage("Error sending command to station - it may need resetting"); - cumulus.LogMessage(ex.Message); - cumulus.LogMessage("Error sending command to station - it may need resetting"); - DataStopped = true; - cumulus.DataStoppedAlarm.Triggered = true; - return false; - } - - Thread.Sleep(cumulus.FineOffsetOptions.ReadTime); - for (int i = 1; i < 5; i++) - { - //cumulus.LogMessage("Reading 8 bytes"); - try - { - stream.Read(response, 0, responseLength); - } - catch (Exception ex) - { - cumulus.LogConsoleMessage("Error reading data from station - it may need resetting"); - cumulus.LogMessage(ex.Message); - cumulus.LogMessage("Error reading data from station - it may need resetting"); - DataStopped = true; - cumulus.DataStoppedAlarm.Triggered = true; - return false; - } - - var recData = " Data" + i + ": " + BitConverter.ToString(response, startByte, responseLength - startByte); - for (int j = startByte; j < responseLength; j++) - { - buff[ptr++] = response[j]; - } - cumulus.LogDataMessage(recData); - } - return true; - } - - private bool WriteAddress(int address, byte val) - { - var addrlowbyte = (byte)(address & 0xFF); - var addrhighbyte = (byte)(address >> 8); - - var request = new byte[] { 0, 0xa2, addrhighbyte, addrlowbyte, 0x20, 0xa2, val, 0, 0x20 }; - - if (hidDevice == null) - { - return false; - } - - //response = device.WriteRead(0x00, request); - try - { - stream.Write(request); - } - catch (Exception ex) - { - cumulus.LogConsoleMessage("Error sending command to station - it may need resetting"); - cumulus.LogMessage(ex.Message); - cumulus.LogMessage("Error sending command to station - it may need resetting"); - DataStopped = true; - cumulus.DataStoppedAlarm.Triggered = true; - return false; - } - - return true; - } - - private void DataReadTimerTick(object state, ElapsedEventArgs elapsedEventArgs) - { - if (DataStopped) - { - cumulus.LogMessage("Attempting to reopen the USB device..."); - // We are not getting any data from the station, try reopening the USB connection - if (stream != null) - { - try - { - stream.Close(); - } - catch { } - } - - if (!OpenHidDevice()) - { - cumulus.LogMessage("Failed to reopen the USB device"); - return; - } - } - - if (!readingData) - { - readingData = true; - GetAndProcessData(); - readingData = false; - } - } - - /// - /// Read current data and process it - /// - private void GetAndProcessData() - { - // Curr Reading Loc - // 0 Time Since Last Save - // 1 Hum In - // 2 Temp In - // 3 " - // 4 Hum Out - // 5 Temp Out - // 6 " - // 7 Pressure - // 8 " - // 9 Wind Speed m/s - // 10 Wind Gust m/s - // 11 Speed and Gust top nibbles (Gust top nibble) - // 12 Wind Dir - // 13 Rain counter - // 14 " - // 15 status - - // 16 Solar (Lux) - // 17 " - // 18 " - // 19 UV - - //var ci = new CultureInfo("en-GB"); - //System.Threading.Thread.CurrentThread.CurrentCulture = ci; - - var data = new byte[32]; - - if (cumulus.FineOffsetOptions.SyncReads && !synchronising) - { - if ((DateTime.Now - FOSensorClockTime).TotalDays > 1) - { - // (re)synchronise data reads to try to avoid USB lockup problem - - StartSynchronising(); - - return; - } - - // Check that were not within N seconds of the station updating memory - bool sensorclockOK = ((int)(Math.Floor((DateTime.Now - FOSensorClockTime).TotalSeconds))%48 >= (cumulus.FineOffsetOptions.ReadAvoidPeriod - 1)) && - ((int)(Math.Floor((DateTime.Now - FOSensorClockTime).TotalSeconds))%48 <= (47 - cumulus.FineOffsetOptions.ReadAvoidPeriod)); - bool stationclockOK = ((int)(Math.Floor((DateTime.Now - FOStationClockTime).TotalSeconds))%60 >= (cumulus.FineOffsetOptions.ReadAvoidPeriod - 1)) && - ((int)(Math.Floor((DateTime.Now - FOStationClockTime).TotalSeconds))%60 <= (59 - cumulus.FineOffsetOptions.ReadAvoidPeriod)); - - if (!sensorclockOK || !stationclockOK) - { - if (!sensorclockOK) - { - cumulus.LogDebugMessage("Within "+cumulus.FineOffsetOptions.ReadAvoidPeriod +" seconds of sensor data change, skipping read"); - } - - if (!stationclockOK) - { - cumulus.LogDebugMessage("Within " + cumulus.FineOffsetOptions.ReadAvoidPeriod + " seconds of station clock minute change, skipping read"); - } - - return; - } - } - - // get the block of memory containing the current data location - - cumulus.LogDataMessage("Reading first block"); - if (!ReadAddress(0, data)) - { - return; - } - - int addr = (data[31]*256) + data[30]; - - cumulus.LogDataMessage("First block read, addr = " + addr.ToString("X4")); - - if (addr != prevaddr) - { - // location has changed, skip this read to give it chance to update - //cumulus.LogMessage("Location changed, skipping"); - cumulus.LogDebugMessage("Address changed"); - cumulus.LogDebugMessage("addr=" + addr.ToString("X4") + " previous=" + prevaddr.ToString("X4")); - - if (synchroPhase == 2) - { - FOStationClockTime = DateTime.Now; - StopSynchronising(); - } - - prevaddr = addr; - return; - } - else - { - cumulus.LogDataMessage("Reading data, addr = " + addr.ToString("X4")); - - if (!ReadAddress(addr, data)) - { - return; - } - - cumulus.LogDataMessage("Data read - " + BitConverter.ToString(data)); - - DateTime now = DateTime.Now; - - if (synchronising) - { - if (synchroPhase == 1) - { - // phase 1 - sensor clock - bool datachanged = false; - // ReadCounter determines whether we actually process the data (every 10 seconds) - readCounter++; - if (hadfirstsyncdata) - { - for (int i = 0; i < 16; i++) - { - if (prevdata[i] != data[i]) - { - datachanged = true; - } - } - - if (datachanged) - { - FOSensorClockTime = DateTime.Now; - synchroPhase = 2; - } - } - } - else - { - // Phase 2 - station clock - readCounter++; - } - } - - hadfirstsyncdata = true; - for (int i = 0; i < 16; i++) - { - prevdata[i] = data[i]; - } - - if (!synchronising || (readCounter%20) == 0) - { - LatestFOReading = addr.ToString("X4") + ": " + BitConverter.ToString(data, 0, 16); - cumulus.LogDataMessage(LatestFOReading); - - // Indoor Humidity ==================================================== - int inhum = data[1]; - if (inhum > 100 || inhum < 0) - { - // bad value - cumulus.LogMessage("Ignoring bad data: inhum = " + inhum); - } - else - { - // 255 is the overflow value, when RH gets below 10% - use 10% - if (inhum == 255) - { - inhum = 10; - } - - if (inhum > 0) - { - DoIndoorHumidity(inhum); - } - } - - // Indoor temperature =============================================== - double intemp = ((data[2]) + (data[3] & 0x7F)*256)/10.0f; - var sign = (byte) (data[3] & 0x80); - if (sign == 0x80) - { - intemp = -intemp; - } - - if (intemp < -50 || intemp > 50) - { - cumulus.LogMessage("Ignoring bad data: intemp = " + intemp); - } - else - { - DoIndoorTemp(ConvertTempCToUser(intemp)); - } - - // Pressure ========================================================= - double pressure = (data[7] + ((data[8] & 0x3f)*256))/10.0f + pressureOffset; - - if (pressure < cumulus.EwOptions.MinPressMB || pressure > cumulus.EwOptions.MaxPressMB) - { - // bad value - cumulus.LogMessage("Ignoring bad data: pressure = " + pressure); - cumulus.LogMessage(" offset = " + pressureOffset); - } - else - { - DoPressure(ConvertPressMBToUser(pressure), now); - // Get station pressure in hPa by subtracting offset and calibrating - // EWpressure offset is difference between rel and abs in hPa - // PressOffset is user calibration in user units. - pressure = (pressure - pressureOffset) * ConvertUserPressureToHPa(cumulus.Calib.Press.Mult) + ConvertUserPressureToHPa(cumulus.Calib.Press.Offset); - StationPressure = ConvertPressMBToUser(pressure); - - UpdatePressureTrendString(); - UpdateStatusPanel(now); - UpdateMQTT(); - DoForecast(string.Empty, false); - } - var status = data[15]; - if ((status & 0x40) != 0) - { - SensorContactLost = true; - cumulus.LogMessage("Sensor contact lost; ignoring outdoor data"); - } - else - { - SensorContactLost = false; - - // Outdoor Humidity =================================================== - int outhum = data[4]; - if (outhum > 100 || outhum < 0) - { - // bad value - cumulus.LogMessage("Ignoring bad data: outhum = " + outhum); - } - else - { - // 255 is the overflow value, when RH gets below 10% - use 10% - if (outhum == 255) - { - outhum = 10; - } - - if (outhum > 0) - { - DoOutdoorHumidity(outhum, now); - } - } - - // Wind ============================================================= - double gust = (data[10] + ((data[11] & 0xF0)*16))/10.0f; - double windspeed = (data[9] + ((data[11] & 0x0F)*256))/10.0f; - var winddir = (int) (data[12]*22.5f); - - if (gust > 60 || gust < 0) - { - // bad value - cumulus.LogMessage("Ignoring bad data: gust = " + gust); - } - else if (windspeed > 60 || windspeed < 0) - { - // bad value - cumulus.LogMessage("Ignoring bad data: speed = " + gust); - } - else - { - DoWind(ConvertWindMSToUser(gust), winddir, ConvertWindMSToUser(windspeed), now); - } - - // Outdoor Temperature ============================================== - double outtemp = ((data[5]) + (data[6] & 0x7F)*256)/10.0f; - sign = (byte) (data[6] & 0x80); - if (sign == 0x80) outtemp = -outtemp; - - if (outtemp < -50 || outtemp > 70) - { - // bad value - cumulus.LogMessage("Ignoring bad data: outtemp = " + outtemp); - } - else - { - DoOutdoorTemp(ConvertTempCToUser(outtemp), now); - - // Use current humidity for dewpoint - if (OutdoorHumidity > 0) - { - OutdoorDewpoint = ConvertTempCToUser(MeteoLib.DewPoint(ConvertUserTempToC(OutdoorTemperature), OutdoorHumidity)); - - CheckForDewpointHighLow(now); - } - - // calculate wind chill - // The 'global average speed will have been determined by the call of DoWind - // so use that in the wind chill calculation - double avgspeedKPH = ConvertUserWindToKPH(WindAverage); - - // windinMPH = calibwind * 2.23693629; - // calculate wind chill from calibrated C temp and calibrated win in KPH - double val = MeteoLib.WindChill(ConvertUserTempToC(OutdoorTemperature), avgspeedKPH); - - DoWindChill(ConvertTempCToUser(val), now); - - DoApparentTemp(now); - DoFeelsLike(now); - DoHumidex(now); - } - - // Rain ============================================================ - int raintot = data[13] + (data[14]*256); - if (prevraintotal == -1) - { - // first reading - prevraintotal = raintot; - cumulus.LogMessage("Rain total count from station = " + raintot); - } - - int raindiff = Math.Abs(raintot - prevraintotal); - - if (raindiff > cumulus.EwOptions.MaxRainTipDiff) - { - cumulus.LogMessage("Warning: large difference in rain gauge tip count: " + raindiff); - - ignoreraincount++; - - if (ignoreraincount == 6) - { - cumulus.LogMessage("Six consecutive readings; accepting value. Adjusting start of day figure to compensate"); - raindaystart += (raindiff*0.3); - // adjust current rain total counter - Raincounter += (raindiff*0.3); - cumulus.LogMessage("Setting raindaystart to " + raindaystart); - ignoreraincount = 0; - } - else - { - cumulus.LogMessage("Ignoring reading " + ignoreraincount); - } - } - else - { - ignoreraincount = 0; - } - - if (ignoreraincount == 0) - { - DoRain(ConvertRainMMToUser(raintot*0.3), -1, now); - prevraintotal = raintot; - } - - // Solar/UV - if (hasSolar) - { - LightValue = (data[16] + (data[17]*256) + (data[18]*65536))/10.0; - - if (LightValue < 300000) - { - DoSolarRad((int) (LightValue*cumulus.LuxToWM2), now); - } - - int UVreading = data[19]; - - if (UVreading != 255) - { - DoUV(UVreading, now); - } - } - } - if (cumulus.SensorAlarm.Enabled) - { - cumulus.SensorAlarm.Triggered = SensorContactLost; - } - } - } - } - - private void StartSynchronising() - { - previousSensorClock = FOSensorClockTime; - previousStationClock = FOStationClockTime; - synchronising = true; - synchroPhase = 1; - hadfirstsyncdata = false; - readCounter = 0; - cumulus.LogMessage("Start Synchronising"); - - tmrDataRead.Interval = 500; // half a second - } - - private void StopSynchronising() - { - int secsdiff; - - synchronising = false; - synchroPhase = 0; - if (previousSensorClock == DateTime.MinValue) - { - cumulus.LogMessage("Sensor clock " + FOSensorClockTime.ToLongTimeString()); - } - else - { - secsdiff = (int) Math.Floor((FOSensorClockTime - previousSensorClock).TotalSeconds)%48; - if (secsdiff > 24) - { - secsdiff = 48 - secsdiff; - } - cumulus.LogMessage("Sensor clock " + FOSensorClockTime.ToLongTimeString() + " drift = " + secsdiff + " seconds"); - } - - if (previousStationClock == DateTime.MinValue) - { - cumulus.LogMessage("Station clock " + FOStationClockTime.ToLongTimeString()); - } - - else - { - secsdiff = (int) Math.Floor((FOStationClockTime - previousStationClock).TotalSeconds)%60; - if (secsdiff > 30) - { - secsdiff = 60 - secsdiff; - } - cumulus.LogMessage("Station clock " + FOStationClockTime.ToLongTimeString() + " drift = " + secsdiff + " seconds"); - } - tmrDataRead.Interval = 10000; // 10 seconds - tmrDataRead.Enabled = false; - // sleep 5 secs to get out of sync with station clock minute change - Thread.Sleep(5000); - - tmrDataRead.Enabled = true; - - cumulus.LogMessage("Stop Synchronising"); - } - - //public double EWpressureoffset { get; set; } - - private class HistoryData - { - public int inHum; - - public double inTemp; - public int interval; - public int outHum; - - public double outTemp; - - public double pressure; - - public int rainCounter; - public DateTime timestamp; - public int windBearing; - public double windGust; - - public double windSpeed; - public int uvVal; - public double solarVal; - public bool SensorContactLost; - public int followinginterval; - } - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.IO.Ports; +using System.Threading; +using System.Timers; +using HidSharp; +using Timer = System.Timers.Timer; + +namespace CumulusMX +{ + internal class FOStation : WeatherStation + { + //private IDevice[] stations; + //private IDevice device; + + private readonly double pressureOffset; + private HidDevice hidDevice; + private HidStream stream; + private List datalist; + + //private readonly int maxHistoryEntries; + private int prevaddr = -1; + private int prevraintotal = -1; + private int ignoreraincount; + private int synchroPhase; + private DateTime previousSensorClock; + private DateTime previousStationClock; + private bool synchronising; + //private DateTime lastraintip; + //private int raininlasttip = 0; + private int interval = 0; + private int followinginterval = 0; + //private readonly double[] WindRunHourMult = {3.6, 1.0, 1.0, 1.0}; + private readonly Timer tmrDataRead; + private int readCounter; + private bool hadfirstsyncdata; + private readonly byte[] prevdata = new byte[16]; + private readonly int foEntrysize; + private readonly int foMaxAddr; + //private int FOmaxhistoryentries; + private readonly bool hasSolar; + private bool readingData = false; + + const int DefaultVid = 0x1941; + const int DefaultPid = 0x8021; + + internal FOStation(Cumulus cumulus) : base(cumulus) + { + cumulus.Manufacturer = cumulus.EW; + var data = new byte[32]; + + tmrDataRead = new Timer(); + + calculaterainrate = true; + + hasSolar = cumulus.StationType == StationTypes.FineOffsetSolar; + + if (hasSolar) + { + foEntrysize = 0x14; + foMaxAddr = 0xFFEC; + //maxHistoryEntries = 3264; + } + else + { + foEntrysize = 0x10; + foMaxAddr = 0xFFF0; + //maxHistoryEntries = 4080; + } + + do + { + if (OpenHidDevice()) + { + // Get the block of data containing the logging interval + cumulus.LogMessage("Reading station logging interval"); + if (ReadAddress(0x10, data)) + { + int logint = data[0]; + + if (logint != cumulus.logints[cumulus.DataLogInterval]) + { + var msg = $"Warning, your console logging interval ({logint} mins) does not match the Cumulus logging interval ({cumulus.logints[cumulus.DataLogInterval]} mins)"; + cumulus.LogConsoleMessage(msg); + cumulus.LogMessage(msg); + if (cumulus.FineOffsetOptions.SetLoggerInterval) + { + WriteAddress(0x10, (byte)cumulus.logints[cumulus.DataLogInterval]); // write the logging new logging interval + WriteAddress(0x1A, 0xAA); // tell the station to read the new parameter + do + { + Thread.Sleep(1000); // sleep to let it reconfigure + ReadAddress(0x10, data); + } while (data[9] != 0); + } + } + } + + // Get the block of data containing the abs and rel pressures + cumulus.LogMessage("Reading station pressure offset"); + + double relpressure = (((data[17] & 0x3f) * 256) + data[16]) / 10.0f; + double abspressure = (((data[19] & 0x3f) * 256) + data[18]) / 10.0f; + pressureOffset = relpressure - abspressure; + cumulus.LogMessage("Rel pressure = " + relpressure); + cumulus.LogMessage("Abs pressure = " + abspressure); + cumulus.LogMessage("Calculated Offset = " + pressureOffset); + if (cumulus.EwOptions.PressOffset < 9999.0) + { + cumulus.LogMessage("Ignoring calculated offset, using offset value from cumulus.ini file"); + cumulus.LogMessage("EWpressureoffset = " + cumulus.EwOptions.PressOffset); + pressureOffset = cumulus.EwOptions.PressOffset; + } + + // Read the data from the logger + startReadingHistoryData(); + } + else + { + // pause for 10 seconds then try again + Thread.Sleep(10000); + } + } while (hidDevice == null || stream == null || !stream.CanRead); + } + + public override void startReadingHistoryData() + { + cumulus.CurrentActivity = "Getting archive data"; + //lastArchiveTimeUTC = getLastArchiveTime(); + + LoadLastHoursFromDataLogs(cumulus.LastUpdateTime); + + bw = new BackgroundWorker(); + bw.DoWork += bw_DoWork; + bw.RunWorkerCompleted += bw_RunWorkerCompleted; + bw.WorkerReportsProgress = true; + bw.RunWorkerAsync(); + } + + public override void Stop() + { + cumulus.LogMessage("Stopping data read timer"); + tmrDataRead.Stop(); + cumulus.LogMessage("Stopping minute timer"); + StopMinuteTimer(); + cumulus.LogMessage("Nulling hidDevice"); + hidDevice = null; + cumulus.LogMessage("Exit FOStation.Stop()"); + } + + private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) + { + cumulus.CurrentActivity = "Normal running"; + cumulus.LogMessage("Archive reading thread completed"); + Start(); + DoDayResetIfNeeded(); + DoTrendValues(DateTime.Now); + cumulus.StartTimersAndSensors(); + } + + private void bw_DoWork(object sender, DoWorkEventArgs e) + { + //var ci = new CultureInfo("en-GB"); + //System.Threading.Thread.CurrentThread.CurrentCulture = ci; + cumulus.LogDebugMessage("Lock: Station waiting for the lock"); + Cumulus.syncInit.Wait(); + cumulus.LogDebugMessage("Lock: Station has the lock"); + try + { + getAndProcessHistoryData(); + } + catch (Exception ex) + { + cumulus.LogMessage("Exception occurred reading archive data: " + ex.Message); + } + cumulus.LogDebugMessage("Lock: Station releasing the lock"); + Cumulus.syncInit.Release(); + } + + public override void getAndProcessHistoryData() + { + var data = new byte[32]; + cumulus.LogMessage("Current culture: " + CultureInfo.CurrentCulture.DisplayName); + //DateTime now = DateTime.Now; + cumulus.LogMessage(DateTime.Now.ToString("G")); + cumulus.LogMessage("Start reading history data"); + cumulus.LogConsoleMessage("Downloading Archive Data"); + DateTime timestamp = DateTime.Now; + //LastUpdateTime = DateTime.Now; // lastArchiveTimeUTC.ToLocalTime(); + cumulus.LogMessage("Last Update = " + cumulus.LastUpdateTime); + cumulus.LogDebugMessage("Reading fixed memory block"); + if (!ReadAddress(0, data)) + { + return; + } + + // get address of current location + int addr = data[31] * 256 + data[30]; + int previousaddress = addr; + + // get the number of logger entries the console has recorded + int logEntries = data[28] * 256 + data[27]; + cumulus.LogDebugMessage($"Console has {logEntries} log entries"); + + cumulus.LogMessage("Reading current address " + addr.ToString("X4")); + if (!ReadAddress(addr, data)) + { + return; + } + + bool moredata = true; + + datalist = new List(); + + while (moredata) + { + followinginterval = interval; + interval = data[0]; + cumulus.LogDebugMessage($"This logger record interval = {interval} mins"); + + // calculate timestamp of previous history data + timestamp = timestamp.AddMinutes(-interval); + + if ((interval != 255) && (timestamp > cumulus.LastUpdateTime) && (datalist.Count < logEntries)) + { + // Test if the current address has changed + cumulus.LogDebugMessage("Reading fixed memory block"); + if (!ReadAddress(0, data)) + { + return; + } + var newAddr = data[31] * 256 + data[30]; + if (newAddr != previousaddress) + { + // The current logger address has changed, pause to allow console to sort itself out + cumulus.LogDebugMessage("Console logger location changed, pausing for a sort while"); + previousaddress = newAddr; + Thread.Sleep(2000); + } + + // Read previous data + addr -= foEntrysize; + if (addr < 0x100) + { + addr = foMaxAddr; // wrap around + } + + cumulus.LogMessage("Read logger entry for " + timestamp + " address " + addr.ToString("X4")); + if (!ReadAddress(addr, data)) + { + return; + } + cumulus.LogDebugMessage("Logger Data block: " + BitConverter.ToString(data, 0, foEntrysize)); + + // add history data to collection + + var histData = new HistoryData(); + + + histData.timestamp = timestamp; + histData.interval = interval; + histData.followinginterval = followinginterval; + histData.inHum = data[1] == 255 ? 10 : data[1]; + histData.outHum = data[4] == 255 ? 10 : data[4]; + double outtemp = (data[5] + (data[6] & 0x7F)*256)/10.0f; + var sign = (byte) (data[6] & 0x80); + if (sign == 0x80) outtemp = -outtemp; + if (outtemp > -200) histData.outTemp = outtemp; + histData.windGust = (data[10] + ((data[11] & 0xF0)*16))/10.0f; + histData.windSpeed = (data[9] + ((data[11] & 0x0F)*256))/10.0f; + histData.windBearing = (int) (data[12]*22.5f); + + histData.rainCounter = data[13] + (data[14]*256); + + double intemp = (data[2] + (data[3] & 0x7F)*256)/10.0f; + sign = (byte) (data[3] & 0x80); + if (sign == 0x80) intemp = -intemp; + histData.inTemp = intemp; + // Get pressure and convert to sea level + histData.pressure = (data[7] + (data[8]*256))/10.0f + pressureOffset; + histData.SensorContactLost = (data[15] & 0x40) == 0x40; + if (hasSolar) + { + histData.uvVal = data[19]; + histData.solarVal = (data[16] + (data[17]*256) + (data[18]*65536))/10.0; + } + + datalist.Add(histData); + + bw.ReportProgress(datalist.Count, "collecting"); + } + else + { + moredata = false; + } + } + + cumulus.LogMessage("Completed read of history data from the console"); + cumulus.LogMessage("Number of history entries = " + datalist.Count); + + if (datalist.Count > 0) + { + ProcessHistoryData(); + } + + //using (cumulusEntities dataContext = new cumulusEntities()) + //{ + // UpdateHighsAndLows(dataContext); + //} + } + + private void ProcessHistoryData() + { + int totalentries = datalist.Count; + + cumulus.LogMessage("Processing history data, number of entries = " + totalentries); + + int rollHour = Math.Abs(cumulus.GetHourInc()); + int luhour = cumulus.LastUpdateTime.Hour; + bool rolloverdone = luhour == rollHour; + bool midnightraindone = luhour == 0; + + while (datalist.Count > 0) + { + HistoryData historydata = datalist[datalist.Count - 1]; + + DateTime timestamp = historydata.timestamp; + + cumulus.LogMessage("Processing data for " + timestamp); + + int h = timestamp.Hour; + + // if outside rollover hour, rollover yet to be done + if (h != rollHour) + { + rolloverdone = false; + } + + // In rollover hour and rollover not yet done + if (h == rollHour && !rolloverdone) + { + // do rollover + cumulus.LogMessage("Day rollover " + timestamp.ToShortTimeString()); + DayReset(timestamp); + + rolloverdone = true; + } + + // Not in midnight hour, midnight rain yet to be done + if (h != 0) + { + midnightraindone = false; + } + + // In midnight hour and midnight rain (and sun) not yet done + if (h == 0 && !midnightraindone) + { + ResetMidnightRain(timestamp); + ResetSunshineHours(); + midnightraindone = true; + } + + // Indoor Humidity ====================================================== + if (historydata.inHum > 100 || historydata.inHum < 0) + { + // 255 is the overflow value, when RH gets below 10% - ignore + cumulus.LogMessage("Ignoring bad data: inhum = " + historydata.inHum); + } + else + { + DoIndoorHumidity(historydata.inHum); + } + + // Indoor Temperature =================================================== + if (historydata.inTemp < -50 || historydata.inTemp > 50) + { + cumulus.LogMessage("Ignoring bad data: intemp = " + historydata.inTemp); + } + else + { + DoIndoorTemp(ConvertTempCToUser(historydata.inTemp)); + } + + // Pressure ============================================================= + + if ((historydata.pressure < cumulus.EwOptions.MinPressMB) || (historydata.pressure > cumulus.EwOptions.MaxPressMB)) + { + cumulus.LogMessage("Ignoring bad data: pressure = " + historydata.pressure); + cumulus.LogMessage(" offset = " + pressureOffset); + } + else + { + DoPressure(ConvertPressMBToUser(historydata.pressure), timestamp); + } + + if (historydata.SensorContactLost) + { + cumulus.LogMessage("Sensor contact lost; ignoring outdoor data"); + } + else + { + // Outdoor Humidity ===================================================== + if (historydata.outHum > 100 || historydata.outHum < 0) + { + // 255 is the overflow value, when RH gets below 10% - ignore + cumulus.LogMessage("Ignoring bad data: outhum = " + historydata.outHum); + } + else + { + DoOutdoorHumidity(historydata.outHum, timestamp); + } + + // Wind ================================================================= + if (historydata.windGust > 60 || historydata.windGust < 0) + { + cumulus.LogMessage("Ignoring bad data: gust = " + historydata.windGust); + } + else if (historydata.windSpeed > 60 || historydata.windSpeed < 0) + { + cumulus.LogMessage("Ignoring bad data: speed = " + historydata.windSpeed); + } + else + { + DoWind(ConvertWindMSToUser(historydata.windGust), historydata.windBearing, ConvertWindMSToUser(historydata.windSpeed), timestamp); + } + + // Outdoor Temperature ================================================== + if (historydata.outTemp < -50 || historydata.outTemp > 70) + { + cumulus.LogMessage("Ignoring bad data: outtemp = " + historydata.outTemp); + } + else + { + DoOutdoorTemp(ConvertTempCToUser(historydata.outTemp), timestamp); + // add in 'archivePeriod' minutes worth of temperature to the temp samples + tempsamplestoday += historydata.interval; + TempTotalToday += (OutdoorTemperature*historydata.interval); + } + + // update chill hours + if (OutdoorTemperature < cumulus.ChillHourThreshold) + { + // add 1 minute to chill hours + ChillHours += (historydata.interval / 60.0); + } + + var raindiff = prevraintotal == -1 ? 0 : historydata.rainCounter - prevraintotal; + + // record time of last rain tip, to use in + // normal running rain rate calc NB rain rate calc not currently used + /* + if (raindiff > 0) + { + lastraintip = timestamp; + + raininlasttip = raindiff; + } + else + { + lastraintip = DateTime.MinValue; + + raininlasttip = 0; + } + */ + double rainrate; + + if (raindiff > 100) + { + cumulus.LogMessage("Warning: large increase in rain gauge tip count: " + raindiff); + rainrate = 0; + } + else + { + if (historydata.interval > 0) + { + rainrate = ConvertRainMMToUser((raindiff * 0.3) * (60.0 / historydata.interval)); + } + else + { + rainrate = 0; + } + } + + DoRain(ConvertRainMMToUser(historydata.rainCounter*0.3), rainrate, timestamp); + + prevraintotal = historydata.rainCounter; + + OutdoorDewpoint = ConvertTempCToUser(MeteoLib.DewPoint(ConvertUserTempToC(OutdoorTemperature), OutdoorHumidity)); + + CheckForDewpointHighLow(timestamp); + + // calculate wind chill + + if (ConvertUserWindToMS(WindAverage) < 1.5) + { + DoWindChill(OutdoorTemperature, timestamp); + } + else + { + // calculate wind chill from calibrated C temp and calibrated win in KPH + DoWindChill(ConvertTempCToUser(MeteoLib.WindChill(ConvertUserTempToC(OutdoorTemperature), ConvertUserWindToKPH(WindAverage))), timestamp); + } + + DoApparentTemp(timestamp); + DoFeelsLike(timestamp); + DoHumidex(timestamp); + + if (hasSolar) + { + if (historydata.uvVal == 255) + { + // ignore + } + else if (historydata.uvVal < 0) + DoUV(0, timestamp); + else if (historydata.uvVal > 16) + DoUV(16, timestamp); + else + DoUV(historydata.uvVal, timestamp); + + if ((historydata.solarVal >= 0) && (historydata.solarVal <= 300000)) + { + DoSolarRad((int) Math.Floor(historydata.solarVal*cumulus.LuxToWM2), timestamp); + + // add in archive period worth of sunshine, if sunny + if ((SolarRad > CurrentSolarMax*cumulus.SunThreshold/100) && (SolarRad >= cumulus.SolarMinimum)) + SunshineHours += (historydata.interval/60.0); + + LightValue = historydata.solarVal; + } + } + } + // add in 'following interval' minutes worth of wind speed to windrun + cumulus.LogMessage("Windrun: " + WindAverage.ToString(cumulus.WindFormat) + cumulus.Units.WindText + " for " + historydata.followinginterval + " minutes = " + + (WindAverage*WindRunHourMult[cumulus.Units.Wind]*historydata.followinginterval/60.0).ToString(cumulus.WindRunFormat) + cumulus.Units.WindRunText); + + WindRunToday += (WindAverage*WindRunHourMult[cumulus.Units.Wind]*historydata.followinginterval/60.0); + + // update heating/cooling degree days + UpdateDegreeDays(historydata.interval); + + // update dominant wind bearing + CalculateDominantWindBearing(Bearing, WindAverage, historydata.interval); + + CheckForWindrunHighLow(timestamp); + + bw.ReportProgress((totalentries - datalist.Count)*100/totalentries, "processing"); + + //UpdateDatabase(timestamp.ToUniversalTime(), historydata.interval, false); + + cumulus.DoLogFile(timestamp,false); + if (cumulus.StationOptions.LogExtraSensors) + { + cumulus.DoExtraLogFile(timestamp); + } + + AddLastHourDataEntry(timestamp, Raincounter, OutdoorTemperature); + AddGraphDataEntry(timestamp, Raincounter, RainToday, RainRate, OutdoorTemperature, OutdoorDewpoint, ApparentTemperature, WindChill, HeatIndex, + IndoorTemperature, Pressure, WindAverage, RecentMaxGust, AvgBearing, Bearing, OutdoorHumidity, IndoorHumidity, SolarRad, CurrentSolarMax, UV, FeelsLike, Humidex); + AddLast3HourDataEntry(timestamp, Pressure, OutdoorTemperature); + AddRecentDataEntry(timestamp, WindAverage, RecentMaxGust, WindLatest, Bearing, AvgBearing, OutdoorTemperature, WindChill, OutdoorDewpoint, HeatIndex, + OutdoorHumidity, Pressure, RainToday, SolarRad, UV, Raincounter, FeelsLike, Humidex); + RemoveOldLHData(timestamp); + RemoveOldL3HData(timestamp); + RemoveOldGraphData(timestamp); + DoTrendValues(timestamp); + UpdatePressureTrendString(); + UpdateStatusPanel(timestamp); + cumulus.AddToWebServiceLists(timestamp); + datalist.RemoveAt(datalist.Count - 1); + } + cumulus.LogMessage("End processing history data"); + } + + /// + /// Read and process data in a loop, sleeping between reads + /// + public override void Start() + { + tmrDataRead.Elapsed += DataReadTimerTick; + tmrDataRead.Interval = 10000; + tmrDataRead.Enabled = true; + } + + private bool OpenHidDevice() + { + var devicelist = DeviceList.Local; + + int vid = (cumulus.FineOffsetOptions.VendorID < 0 ? DefaultVid : cumulus.FineOffsetOptions.VendorID); + int pid = (cumulus.FineOffsetOptions.ProductID < 0 ? DefaultPid : cumulus.FineOffsetOptions.ProductID); + + cumulus.LogMessage("Looking for Fine Offset station, VendorID=0x" + vid.ToString("X4") + " ProductID=0x" + pid.ToString("X4")); + cumulus.LogConsoleMessage("Looking for Fine Offset station"); + + hidDevice = devicelist.GetHidDeviceOrNull(vendorID: vid, productID: pid); + + if (hidDevice != null) + { + cumulus.LogMessage("Fine Offset station found"); + cumulus.LogConsoleMessage("Fine Offset station found"); + + if (hidDevice.TryOpen(out stream)) + { + cumulus.LogMessage("Stream opened"); + cumulus.LogConsoleMessage("Connected to station"); + stream.Flush(); + return true; + } + else + { + cumulus.LogMessage("Stream open failed"); + return false; + } + } + else + { + cumulus.LogMessage("*** Fine Offset station not found ***"); + cumulus.LogMessage("Found the following USB HID Devices..."); + int cnt = 0; + foreach (HidDevice device in devicelist.GetHidDevices()) + { + cumulus.LogMessage($" {device}"); + cnt++; + } + + if (cnt == 0) + { + cumulus.LogMessage("No USB HID devices found!"); + } + + return false; + } + } + + + /// + /// Read the 32 bytes starting at 'address' + /// + /// The address of the data + /// Where to return the data + private bool ReadAddress(int address, byte[] buff) + { + //cumulus.LogMessage("Reading address " + address.ToString("X6")); + var lowbyte = (byte) (address & 0xFF); + var highbyte = (byte) (address >> 8); + + // Returns 9-byte usb packet, with report ID in first byte + var response = new byte[9]; + const int responseLength = 9; + const int startByte = 1; + + var request = new byte[] {0, 0xa1, highbyte, lowbyte, 0x20, 0xa1, highbyte, lowbyte, 0x20}; + + int ptr = 0; + + if (hidDevice == null) + { + DataStopped = true; + cumulus.DataStoppedAlarm.LastError = "USB device no longer detected"; + cumulus.DataStoppedAlarm.Triggered = true; + return false; + } + + //response = device.WriteRead(0x00, request); + try + { + stream.Write(request); + } + catch (Exception ex) + { + cumulus.LogConsoleMessage("Error sending command to station - it may need resetting"); + cumulus.LogMessage(ex.Message); + cumulus.LogMessage("Error sending command to station - it may need resetting"); + DataStopped = true; + cumulus.DataStoppedAlarm.LastError = "Error reading data from station - it may need resetting. " + ex.Message; + cumulus.DataStoppedAlarm.Triggered = true; + return false; + } + + Thread.Sleep(cumulus.FineOffsetOptions.ReadTime); + for (int i = 1; i < 5; i++) + { + //cumulus.LogMessage("Reading 8 bytes"); + try + { + stream.Read(response, 0, responseLength); + } + catch (Exception ex) + { + cumulus.LogConsoleMessage("Error reading data from station - it may need resetting"); + cumulus.LogMessage(ex.Message); + cumulus.LogMessage("Error reading data from station - it may need resetting"); + DataStopped = true; + cumulus.DataStoppedAlarm.LastError = "Error reading data from station - it may need resetting. " + ex.Message; + cumulus.DataStoppedAlarm.Triggered = true; + return false; + } + + var recData = " Data" + i + ": " + BitConverter.ToString(response, startByte, responseLength - startByte); + for (int j = startByte; j < responseLength; j++) + { + buff[ptr++] = response[j]; + } + cumulus.LogDataMessage(recData); + } + return true; + } + + private bool WriteAddress(int address, byte val) + { + var addrlowbyte = (byte)(address & 0xFF); + var addrhighbyte = (byte)(address >> 8); + + var request = new byte[] { 0, 0xa2, addrhighbyte, addrlowbyte, 0x20, 0xa2, val, 0, 0x20 }; + + if (hidDevice == null) + { + return false; + } + + //response = device.WriteRead(0x00, request); + try + { + stream.Write(request); + } + catch (Exception ex) + { + cumulus.LogConsoleMessage("Error sending command to station - it may need resetting"); + cumulus.LogMessage(ex.Message); + cumulus.LogMessage("Error sending command to station - it may need resetting"); + DataStopped = true; + cumulus.DataStoppedAlarm.LastError = "Error sending command to station - it may need resetting: " + ex.Message; + cumulus.DataStoppedAlarm.Triggered = true; + return false; + } + + return true; + } + + private void DataReadTimerTick(object state, ElapsedEventArgs elapsedEventArgs) + { + if (DataStopped) + { + cumulus.LogMessage("Attempting to reopen the USB device..."); + // We are not getting any data from the station, try reopening the USB connection + if (stream != null) + { + try + { + stream.Close(); + } + catch { } + } + + if (!OpenHidDevice()) + { + cumulus.LogMessage("Failed to reopen the USB device"); + return; + } + } + + if (!readingData) + { + readingData = true; + GetAndProcessData(); + readingData = false; + } + } + + /// + /// Read current data and process it + /// + private void GetAndProcessData() + { + // Curr Reading Loc + // 0 Time Since Last Save + // 1 Hum In + // 2 Temp In + // 3 " + // 4 Hum Out + // 5 Temp Out + // 6 " + // 7 Pressure + // 8 " + // 9 Wind Speed m/s + // 10 Wind Gust m/s + // 11 Speed and Gust top nibbles (Gust top nibble) + // 12 Wind Dir + // 13 Rain counter + // 14 " + // 15 status + + // 16 Solar (Lux) + // 17 " + // 18 " + // 19 UV + + //var ci = new CultureInfo("en-GB"); + //System.Threading.Thread.CurrentThread.CurrentCulture = ci; + + var data = new byte[32]; + + if (cumulus.FineOffsetOptions.SyncReads && !synchronising) + { + if ((DateTime.Now - FOSensorClockTime).TotalDays > 1) + { + // (re)synchronise data reads to try to avoid USB lockup problem + + StartSynchronising(); + + return; + } + + // Check that were not within N seconds of the station updating memory + bool sensorclockOK = ((int)(Math.Floor((DateTime.Now - FOSensorClockTime).TotalSeconds))%48 >= (cumulus.FineOffsetOptions.ReadAvoidPeriod - 1)) && + ((int)(Math.Floor((DateTime.Now - FOSensorClockTime).TotalSeconds))%48 <= (47 - cumulus.FineOffsetOptions.ReadAvoidPeriod)); + bool stationclockOK = ((int)(Math.Floor((DateTime.Now - FOStationClockTime).TotalSeconds))%60 >= (cumulus.FineOffsetOptions.ReadAvoidPeriod - 1)) && + ((int)(Math.Floor((DateTime.Now - FOStationClockTime).TotalSeconds))%60 <= (59 - cumulus.FineOffsetOptions.ReadAvoidPeriod)); + + if (!sensorclockOK || !stationclockOK) + { + if (!sensorclockOK) + { + cumulus.LogDebugMessage("Within "+cumulus.FineOffsetOptions.ReadAvoidPeriod +" seconds of sensor data change, skipping read"); + } + + if (!stationclockOK) + { + cumulus.LogDebugMessage("Within " + cumulus.FineOffsetOptions.ReadAvoidPeriod + " seconds of station clock minute change, skipping read"); + } + + return; + } + } + + // get the block of memory containing the current data location + + cumulus.LogDataMessage("Reading first block"); + if (!ReadAddress(0, data)) + { + return; + } + + int addr = (data[31]*256) + data[30]; + + cumulus.LogDataMessage("First block read, addr = " + addr.ToString("X4")); + + if (addr != prevaddr) + { + // location has changed, skip this read to give it chance to update + //cumulus.LogMessage("Location changed, skipping"); + cumulus.LogDebugMessage("Address changed"); + cumulus.LogDebugMessage("addr=" + addr.ToString("X4") + " previous=" + prevaddr.ToString("X4")); + + if (synchroPhase == 2) + { + FOStationClockTime = DateTime.Now; + StopSynchronising(); + } + + prevaddr = addr; + return; + } + else + { + cumulus.LogDataMessage("Reading data, addr = " + addr.ToString("X4")); + + if (!ReadAddress(addr, data)) + { + return; + } + + cumulus.LogDataMessage("Data read - " + BitConverter.ToString(data)); + + DateTime now = DateTime.Now; + + if (synchronising) + { + if (synchroPhase == 1) + { + // phase 1 - sensor clock + bool datachanged = false; + // ReadCounter determines whether we actually process the data (every 10 seconds) + readCounter++; + if (hadfirstsyncdata) + { + for (int i = 0; i < 16; i++) + { + if (prevdata[i] != data[i]) + { + datachanged = true; + } + } + + if (datachanged) + { + FOSensorClockTime = DateTime.Now; + synchroPhase = 2; + } + } + } + else + { + // Phase 2 - station clock + readCounter++; + } + } + + hadfirstsyncdata = true; + for (int i = 0; i < 16; i++) + { + prevdata[i] = data[i]; + } + + if (!synchronising || (readCounter%20) == 0) + { + LatestFOReading = addr.ToString("X4") + ": " + BitConverter.ToString(data, 0, 16); + cumulus.LogDataMessage(LatestFOReading); + + // Indoor Humidity ==================================================== + int inhum = data[1]; + if (inhum > 100 || inhum < 0) + { + // bad value + cumulus.LogMessage("Ignoring bad data: inhum = " + inhum); + } + else + { + // 255 is the overflow value, when RH gets below 10% - use 10% + if (inhum == 255) + { + inhum = 10; + } + + if (inhum > 0) + { + DoIndoorHumidity(inhum); + } + } + + // Indoor temperature =============================================== + double intemp = ((data[2]) + (data[3] & 0x7F)*256)/10.0f; + var sign = (byte) (data[3] & 0x80); + if (sign == 0x80) + { + intemp = -intemp; + } + + if (intemp < -50 || intemp > 50) + { + cumulus.LogMessage("Ignoring bad data: intemp = " + intemp); + } + else + { + DoIndoorTemp(ConvertTempCToUser(intemp)); + } + + // Pressure ========================================================= + double pressure = (data[7] + ((data[8] & 0x3f)*256))/10.0f + pressureOffset; + + if (pressure < cumulus.EwOptions.MinPressMB || pressure > cumulus.EwOptions.MaxPressMB) + { + // bad value + cumulus.LogMessage("Ignoring bad data: pressure = " + pressure); + cumulus.LogMessage(" offset = " + pressureOffset); + } + else + { + DoPressure(ConvertPressMBToUser(pressure), now); + // Get station pressure in hPa by subtracting offset and calibrating + // EWpressure offset is difference between rel and abs in hPa + // PressOffset is user calibration in user units. + pressure = (pressure - pressureOffset) * ConvertUserPressureToHPa(cumulus.Calib.Press.Mult) + ConvertUserPressureToHPa(cumulus.Calib.Press.Offset); + StationPressure = ConvertPressMBToUser(pressure); + + UpdatePressureTrendString(); + UpdateStatusPanel(now); + UpdateMQTT(); + DoForecast(string.Empty, false); + } + var status = data[15]; + if ((status & 0x40) != 0) + { + SensorContactLost = true; + cumulus.LogMessage("Sensor contact lost; ignoring outdoor data"); + } + else + { + SensorContactLost = false; + + // Outdoor Humidity =================================================== + int outhum = data[4]; + if (outhum > 100 || outhum < 0) + { + // bad value + cumulus.LogMessage("Ignoring bad data: outhum = " + outhum); + } + else + { + // 255 is the overflow value, when RH gets below 10% - use 10% + if (outhum == 255) + { + outhum = 10; + } + + if (outhum > 0) + { + DoOutdoorHumidity(outhum, now); + } + } + + // Wind ============================================================= + double gust = (data[10] + ((data[11] & 0xF0)*16))/10.0f; + double windspeed = (data[9] + ((data[11] & 0x0F)*256))/10.0f; + var winddir = (int) (data[12]*22.5f); + + if (gust > 60 || gust < 0) + { + // bad value + cumulus.LogMessage("Ignoring bad data: gust = " + gust); + } + else if (windspeed > 60 || windspeed < 0) + { + // bad value + cumulus.LogMessage("Ignoring bad data: speed = " + gust); + } + else + { + DoWind(ConvertWindMSToUser(gust), winddir, ConvertWindMSToUser(windspeed), now); + } + + // Outdoor Temperature ============================================== + double outtemp = ((data[5]) + (data[6] & 0x7F)*256)/10.0f; + sign = (byte) (data[6] & 0x80); + if (sign == 0x80) outtemp = -outtemp; + + if (outtemp < -50 || outtemp > 70) + { + // bad value + cumulus.LogMessage("Ignoring bad data: outtemp = " + outtemp); + } + else + { + DoOutdoorTemp(ConvertTempCToUser(outtemp), now); + + // Use current humidity for dewpoint + if (OutdoorHumidity > 0) + { + OutdoorDewpoint = ConvertTempCToUser(MeteoLib.DewPoint(ConvertUserTempToC(OutdoorTemperature), OutdoorHumidity)); + + CheckForDewpointHighLow(now); + } + + // calculate wind chill + // The 'global average speed will have been determined by the call of DoWind + // so use that in the wind chill calculation + double avgspeedKPH = ConvertUserWindToKPH(WindAverage); + + // windinMPH = calibwind * 2.23693629; + // calculate wind chill from calibrated C temp and calibrated win in KPH + double val = MeteoLib.WindChill(ConvertUserTempToC(OutdoorTemperature), avgspeedKPH); + + DoWindChill(ConvertTempCToUser(val), now); + + DoApparentTemp(now); + DoFeelsLike(now); + DoHumidex(now); + } + + // Rain ============================================================ + int raintot = data[13] + (data[14]*256); + if (prevraintotal == -1) + { + // first reading + prevraintotal = raintot; + cumulus.LogMessage("Rain total count from station = " + raintot); + } + + int raindiff = Math.Abs(raintot - prevraintotal); + + if (raindiff > cumulus.EwOptions.MaxRainTipDiff) + { + cumulus.LogMessage("Warning: large difference in rain gauge tip count: " + raindiff); + + ignoreraincount++; + + if (ignoreraincount == 6) + { + cumulus.LogMessage("Six consecutive readings; accepting value. Adjusting start of day figure to compensate"); + raindaystart += (raindiff*0.3); + // adjust current rain total counter + Raincounter += (raindiff*0.3); + cumulus.LogMessage("Setting raindaystart to " + raindaystart); + ignoreraincount = 0; + } + else + { + cumulus.LogMessage("Ignoring reading " + ignoreraincount); + } + } + else + { + ignoreraincount = 0; + } + + if (ignoreraincount == 0) + { + DoRain(ConvertRainMMToUser(raintot*0.3), -1, now); + prevraintotal = raintot; + } + + // Solar/UV + if (hasSolar) + { + LightValue = (data[16] + (data[17]*256) + (data[18]*65536))/10.0; + + if (LightValue < 300000) + { + DoSolarRad((int) (LightValue*cumulus.LuxToWM2), now); + } + + int UVreading = data[19]; + + if (UVreading != 255) + { + DoUV(UVreading, now); + } + } + } + if (cumulus.SensorAlarm.Enabled) + { + cumulus.SensorAlarm.Triggered = SensorContactLost; + } + } + } + } + + private void StartSynchronising() + { + previousSensorClock = FOSensorClockTime; + previousStationClock = FOStationClockTime; + synchronising = true; + synchroPhase = 1; + hadfirstsyncdata = false; + readCounter = 0; + cumulus.LogMessage("Start Synchronising"); + + tmrDataRead.Interval = 500; // half a second + } + + private void StopSynchronising() + { + int secsdiff; + + synchronising = false; + synchroPhase = 0; + if (previousSensorClock == DateTime.MinValue) + { + cumulus.LogMessage("Sensor clock " + FOSensorClockTime.ToLongTimeString()); + } + else + { + secsdiff = (int) Math.Floor((FOSensorClockTime - previousSensorClock).TotalSeconds)%48; + if (secsdiff > 24) + { + secsdiff = 48 - secsdiff; + } + cumulus.LogMessage("Sensor clock " + FOSensorClockTime.ToLongTimeString() + " drift = " + secsdiff + " seconds"); + } + + if (previousStationClock == DateTime.MinValue) + { + cumulus.LogMessage("Station clock " + FOStationClockTime.ToLongTimeString()); + } + + else + { + secsdiff = (int) Math.Floor((FOStationClockTime - previousStationClock).TotalSeconds)%60; + if (secsdiff > 30) + { + secsdiff = 60 - secsdiff; + } + cumulus.LogMessage("Station clock " + FOStationClockTime.ToLongTimeString() + " drift = " + secsdiff + " seconds"); + } + tmrDataRead.Interval = 10000; // 10 seconds + tmrDataRead.Enabled = false; + // sleep 5 secs to get out of sync with station clock minute change + Thread.Sleep(5000); + + tmrDataRead.Enabled = true; + + cumulus.LogMessage("Stop Synchronising"); + } + + //public double EWpressureoffset { get; set; } + + private class HistoryData + { + public int inHum; + + public double inTemp; + public int interval; + public int outHum; + + public double outTemp; + + public double pressure; + + public int rainCounter; + public DateTime timestamp; + public int windBearing; + public double windGust; + + public double windSpeed; + public int uvVal; + public double solarVal; + public bool SensorContactLost; + public int followinginterval; + } + } +} diff --git a/CumulusMX/GW1000Station.cs b/CumulusMX/GW1000Station.cs index 77ba31d6..4ae22920 100644 --- a/CumulusMX/GW1000Station.cs +++ b/CumulusMX/GW1000Station.cs @@ -1,1840 +1,1851 @@ -using System; -using System.ComponentModel; -using System.IO.Ports; -using System.Net.Sockets; -using System.Text; -using System.Threading; -using System.Runtime.InteropServices; -using System.Net; -using System.Timers; -using System.Collections.Generic; -using System.Linq; - -namespace CumulusMX -{ - internal class GW1000Station : WeatherStation - { - private string ipaddr; - private string macaddr; - private const int AtPort = 45000; - private int lastMinute; - private bool tenMinuteChanged = true; - - private TcpClient socket; - private NetworkStream stream; - private bool connectedOk = false; - private bool dataReceived = false; - - private readonly System.Timers.Timer tmrDataWatchdog; - private bool stop = false; - - private Version fwVersion; - - private string mainSensor; - - private enum Commands : byte { - // General order - CMD_WRITE_SSID = 0x11,// send router SSID and Password to wifi module - CMD_BROADCAST = 0x12,//looking for device inside network. Returned data size is 2 Byte - CMD_READ_ECOWITT = 0x1E,// read setting for Ecowitt.net - CMD_WRITE_ECOWITT = 0x1F, // write back setting for Ecowitt.net - CMD_READ_WUNDERGROUND = 0x20,// read back setting for Wunderground - CMD_WRITE_WUNDERGROUND = 0x21, // write back setting for Wunderground - CMD_READ_WOW = 0x22, // read setting for WeatherObservationsWebsite - CMD_WRITE_WOW = 0x23, // write back setting for WeatherObservationsWebsite - CMD_READ_WEATHERCLOUD = 0x24,// read setting for Weathercloud - CMD_WRITE_WEATHERCLOUD = 0x25, // write back setting for Weathercloud - CMD_READ_SATION_MAC = 0x26,// read module MAC - CMD_READ_CUSTOMIZED = 0x2A,// read setting for Customized sever - CMD_WRITE_CUSTOMIZED = 0x2B, // write back customized sever setting - CMD_WRITE_UPDATE = 0x43,// update firmware - CMD_READ_FIRMWARE_VERSION = 0x50,// read back firmware version - CMD_READ_USER_PATH = 0x51, - CMD_WRITE_USER_PATH = 0x52, - // the following commands are only valid for GW1000 and WH2650: - CMD_GW1000_LIVEDATA = 0x27, // read current,return size is 2 Byte - CMD_GET_SOILHUMIAD = 0x28,// read Soilmoisture Sensor calibration parameter - CMD_SET_SOILHUMIAD = 0x29, // write back Soilmoisture Sensor calibration parameter - CMD_GET_MulCH_OFFSET = 0x2C, // read multi channel sensor OFFSET value - CMD_SET_MulCH_OFFSET = 0x2D, // write back multi sensor OFFSET value - CMD_GET_PM25_OFFSET = 0x2E, // read PM2.5OFFSET value - CMD_SET_PM25_OFFSET = 0x2F, // write back PM2.5OFFSET value - CMD_READ_SSSS = 0x30,// read sensor setup ( sensor frequency, wh24/wh65 sensor) - CMD_WRITE_SSSS = 0x31,// write back sensor setup - CMD_READ_RAINDATA = 0x34,// read rain data - CMD_WRITE_RAINDATA = 0x35, // write back rain data - CMD_READ_GAIN = 0x36, // read rain gain - CMD_WRITE_GAIN = 0x37, // write back rain gain - CMD_READ_CALIBRATION = 0x38,// read multiple parameter offset( refer to command description below in detail) - CMD_WRITE_CALIBRATION = 0x39,// write back multiple parameter offset - CMD_READ_SENSOR_ID = 0x3A,// read Sensors ID - CMD_WRITE_SENSOR_ID = 0x3B, // write back Sensors ID - CMD_READ_SENSOR_ID_NEW = 0x3C, - CMD_WRITE_REBOOT = 0x40,// system rebset - CMD_WRITE_RESET = 0x41,// system default setting reset - CMD_GET_CO2_OFFSET = 0x53, - CMD_SET_CO2_OFFSET = 0x54 - } - - private enum CommandRespSize : int - { - CMD_WRITE_SSID = 1, - CMD_BROADCAST = 2, - CMD_READ_ECOWITT = 1, - CMD_WRITE_ECOWITT = 1, - CMD_READ_WUNDERGROUND = 1, - CMD_WRITE_WUNDERGROUND = 1, - CMD_READ_WOW = 1, - CMD_WRITE_WOW = 1, - CMD_READ_WEATHERCLOUD = 1, - CMD_WRITE_WEATHERCLOUD = 1, - CMD_READ_SATION_MAC = 1, - CMD_READ_CUSTOMIZED = 1, - CMD_WRITE_CUSTOMIZED = 1, - CMD_WRITE_UPDATE = 1, - CMD_READ_FIRMWARE_VERSION = 1, - CMD_READ_USER_PATH = 1, - CMD_WRITE_USER_PATH = 1, - // the following commands are only valid for GW1000 and WH2650: - CMD_GW1000_LIVEDATA = 2, - CMD_GET_SOILHUMIAD = 1, - CMD_SET_SOILHUMIAD = 1, - CMD_GET_MulCH_OFFSET = 1, - CMD_SET_MulCH_OFFSET = 1, - CMD_GET_PM25_OFFSET = 1, - CMD_SET_PM25_OFFSET = 1, - CMD_READ_SSSS = 1, - CMD_WRITE_SSSS = 1, - CMD_READ_RAINDATA = 1, - CMD_WRITE_RAINDATA = 1, - CMD_READ_GAIN = 1, - CMD_WRITE_GAIN = 1, - CMD_READ_CALIBRATION = 1, - CMD_WRITE_CALIBRATION = 1, - CMD_READ_SENSOR_ID = 1, - CMD_WRITE_SENSOR_ID = 1, - CMD_WRITE_REBOOT = 1, - CMD_WRITE_RESET = 1, - CMD_READ_SENSOR_ID_NEW = 2 - } - - [Flags] private enum SigSen : byte - { - Wh40 = 1 << 4, - Wh26 = 1 << 5, - Wh25 = 1 << 6, - Wh24 = 1 << 7 - } - - [Flags] private enum Wh31Ch : byte - { - Ch1 = 1 << 0, - Ch2 = 1 << 1, - Ch3 = 1 << 2, - Ch4 = 1 << 3, - Ch5 = 1 << 4, - Ch6 = 1 << 5, - Ch7 = 1 << 6, - Ch8 = 1 << 7 - } - - - /* - private enum _wh41_ch : UInt16 - { - ch1 = 15 << 0, - ch2 = 15 << 4, - ch3 = 15 << 8, - ch4 = 15 << 12 - } - */ - - [Flags] private enum Wh51Ch : UInt32 - { - Ch1 = 1 << 0, - Ch2 = 1 << 1, - Ch3 = 1 << 2, - Ch4 = 1 << 3, - Ch5 = 1 << 4, - Ch6 = 1 << 5, - Ch7 = 1 << 6, - Ch8 = 1 << 7, - Ch9 = 1 << 8, - Ch10 = 1 << 9, - Ch11 = 1 << 10, - Ch12 = 1 << 11, - Ch13 = 1 << 12, - Ch14 = 1 << 13, - Ch15 = 1 << 14, - Ch16 = 1 << 15 - } - - private enum Wh55Ch : UInt32 - { - Ch1 = 15 << 0, - Ch2 = 15 << 4, - Ch3 = 15 << 8, - Ch4 = 15 << 12 - } - - private enum SensorIds - { - Wh65, // 0 - Wh68, // 1 - Wh80, // 2 - Wh40, // 3 - Wh25, // 4 - Wh26, // 5 - Wh31Ch1, // 6 - Wh31Ch2, // 7 - Wh31Ch3, // 8 - Wh31Ch4, // 9 - Wh31Ch5, // 10 - Wh31Ch6, // 11 - Wh31Ch7, // 12 - Wh31Ch8, // 13 - Wh51Ch1, // 14 - Wh51Ch2, // 15 - Wh51Ch3, // 16 - Wh51Ch4, // 17 - Wh51Ch5, // 18 - Wh51Ch6, // 19 - Wh51Ch7, // 20 - Wh51Ch8, // 21 - Wh41Ch1, // 22 - Wh41Ch2, // 23 - Wh41Ch3, // 24 - Wh41Ch4, // 25 - Wh57, // 26 - Wh55Ch1, // 27 - Wh55Ch2, // 28 - Wh55Ch3, // 29 - Wh55Ch4, // 30 - Wh34Ch1, // 31 - Wh34Ch2, // 32 - Wh34Ch3, // 33 - Wh34Ch4, // 34 - Wh34Ch5, // 35 - Wh34Ch6, // 36 - Wh34Ch7, // 37 - Wh34Ch8, // 38 - Wh45, // 39 - Wh35Ch1, // 40 - Wh35Ch2, // 41 - Wh35Ch3, // 42 - Wh35Ch4, // 43 - Wh35Ch5, // 44 - Wh35Ch6, // 45 - Wh35Ch7, // 46 - Wh35Ch8 // 47 - }; - - public GW1000Station(Cumulus cumulus) : base(cumulus) - { - - cumulus.Manufacturer = cumulus.ECOWITT; - cumulus.AirQualityUnitText = "µg/m³"; - cumulus.SoilMoistureUnitText = "%"; - // GW1000 does not provide average wind speeds - cumulus.StationOptions.UseWind10MinAve = true; - cumulus.StationOptions.UseSpeedForAvgCalc = false; - - LightningTime = DateTime.MinValue; - LightningDistance = 999; - - tmrDataWatchdog = new System.Timers.Timer(); - - // GW1000 does not send DP, so force MX to calculate it - cumulus.StationOptions.CalculatedDP = true; - - ipaddr = cumulus.Gw1000IpAddress; - macaddr = cumulus.Gw1000MacAddress; - - if (!DoDiscovery()) - { - return; - } - - cumulus.LogMessage("Using IP address = " + ipaddr + " Port = " + AtPort); - socket = OpenTcpPort(); - - connectedOk = socket != null; - - if (connectedOk) - { - cumulus.LogMessage("Connected OK"); - cumulus.LogConsoleMessage("Connected to station"); - } - else - { - cumulus.LogMessage("Not Connected"); - cumulus.LogConsoleMessage("Unable to connect to station"); - } - - if (connectedOk) - { - // Get the firmware version as check we are communicating - GW1000FirmwareVersion = GetFirmwareVersion(); - cumulus.LogMessage($"GW1000 firmware version: {GW1000FirmwareVersion}"); - var fwString = GW1000FirmwareVersion.Split(new string[] { "_V" }, StringSplitOptions.None); - fwVersion = new Version(fwString[1]); - - GetSystemInfo(); - - GetSensorIdsNew(); - } - - timerStartNeeded = true; - LoadLastHoursFromDataLogs(cumulus.LastUpdateTime); - DoTrendValues(DateTime.Now); - - // WLL does not provide a forecast string, so use the Cumulus forecast - cumulus.UseCumulusForecast = true; - - cumulus.LogMessage("Starting GW1000"); - - StartLoop(); - bw = new BackgroundWorker(); - } - - private TcpClient OpenTcpPort() - { - TcpClient client = null; - int attempt = 0; - - // Creating the new TCP socket effectively opens it - specify IP address or domain name and port - while (attempt < 5 && client == null) - { - attempt++; - cumulus.LogDebugMessage("GW1000 Connect attempt " + attempt); - try - { - client = new TcpClient(ipaddr, AtPort); - - if (!client.Connected) - { - try - { - client.Close(); - } - catch - { } - client = null; - } - - Thread.Sleep(1000); - } - catch - { - //MessageBox.Show(ex.Message); - } - } - - // Set the timeout of the underlying stream - if (client != null) - { - stream = client.GetStream(); - stream.ReadTimeout = 2500; - cumulus.LogDebugMessage("GW1000 reconnected"); - } - else - { - cumulus.LogDebugMessage("GW1000 connect failed"); - } - - return client; - } - - public override void Start() - { - // Wait for the lock - cumulus.LogDebugMessage("Lock: Station waiting for lock"); - Cumulus.syncInit.Wait(); - cumulus.LogDebugMessage("Lock: Station has the lock"); - - cumulus.LogMessage("Start normal reading loop"); - - cumulus.LogDebugMessage("Lock: Station releasing lock"); - Cumulus.syncInit.Release(); - - tenMinuteChanged = true; - lastMinute = DateTime.Now.Minute; - - // Start a broadcast watchdog to warn if messages are not being received - tmrDataWatchdog.Elapsed += DataTimeout; - tmrDataWatchdog.Interval = 1000 * 30; // timeout after 30 seconds - tmrDataWatchdog.AutoReset = true; - tmrDataWatchdog.Start(); - - try - { - while (!stop) - { - if (connectedOk) - { - GetLiveData(); - - // at the start of every 10 minutes to trigger battery status check - var minute = DateTime.Now.Minute; - if (minute != lastMinute) - { - lastMinute = minute; - if ((minute % 10) == 0) - { - GetSensorIdsNew(); - } - } - } - else - { - cumulus.LogMessage("Attempting to reconnect to GW1000..."); - socket = OpenTcpPort(); - connectedOk = socket != null; - if (connectedOk) - { - cumulus.LogMessage("Reconnected to GW1000"); - GetLiveData(); - } - } - Thread.Sleep(1000 * 10); - } - } - // Catch the ThreadAbortException - catch (ThreadAbortException) - { - } - finally - { - if (socket != null) - { - socket.GetStream().WriteByte(10); - socket.Close(); - } - } - } - - public override void Stop() - { - cumulus.LogMessage("Closing connection"); - try - { - stop = true; - socket.GetStream().WriteByte(10); - socket.Close(); - tmrDataWatchdog.Stop(); - StopMinuteTimer(); - } - catch - { - } - } - - private void bw_DoStart(object sender, DoWorkEventArgs e) - { - cumulus.LogDebugMessage("Lock: Station waiting for lock"); - Cumulus.syncInit.Wait(); - cumulus.LogDebugMessage("Lock: Station has the lock"); - - // Wait a short while for Cumulus initialisation to complete - Thread.Sleep(500); - StartLoop(); - - cumulus.LogDebugMessage("Lock: Station releasing lock"); - Cumulus.syncInit.Release(); - } - - private Discovery DiscoverGW1000() - { - // We only want unique IP addresses - var discovered = new Discovery(); - const int broadcastPort = 46000; - const int clientPort = 59387; - - - try - { - using (var client = new UdpClient()) - using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) - { - var recvEp = new IPEndPoint(IPAddress.Any, clientPort); - var sendEp = new IPEndPoint(IPAddress.Broadcast, broadcastPort); - var sendBytes = new byte[] { 0xff, 0xff, 0x12, 0x03, 0x15 }; - - socket.ReceiveTimeout = 500; - socket.EnableBroadcast = true; - socket.Bind(recvEp); - - - var receivedBytes = new byte[200]; - - var endTime = DateTime.Now.AddSeconds(5); - - client.EnableBroadcast = true; - client.Send(sendBytes, sendBytes.Length, sendEp); - - string[] namesToCheck = { "GW1000", "WH2650", "EasyWeather", "AMBWeather", "WS1900", "WN1900" }; - - do - { - try - { - socket.Receive(receivedBytes, 0, receivedBytes.Length, SocketFlags.None); - //receivedBytes = client.Receive(ref recvEp); - string ipAddr = $"{receivedBytes[11]}.{receivedBytes[12]}.{receivedBytes[13]}.{receivedBytes[14]}"; - int nameLen = receivedBytes[17]; - var nameArr = new byte[nameLen]; - var macArr = new byte[6]; - - Array.Copy(receivedBytes, 18, nameArr, 0, nameLen); - var name = Encoding.Default.GetString(nameArr); - - Array.Copy(receivedBytes, 5, macArr, 0, 6); - var macHex = BitConverter.ToString(macArr).Replace('-', ':'); - - if (namesToCheck.Any((name.Split('-')[0]).StartsWith) && ipAddr.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries).Length == 4) - { - IPAddress ipAddr2; - if (IPAddress.TryParse(ipAddr, out ipAddr2)) - { - if (!discovered.IP.Contains(ipAddr)) - { - cumulus.LogDebugMessage($"Discovered GW1000 device: Name={name}, IP={ipAddr}, MAC={macHex}"); - discovered.IP.Add(ipAddr); - discovered.Name.Add(name); - discovered.Mac.Add(macHex); - } - } - } - else - { - cumulus.LogDebugMessage($"Discovered an unsupported device: Name={name}, IP={ipAddr}, MAC={macHex}"); - } - } - catch { } - } while (DateTime.Now < endTime); - } - } - catch (Exception ex) - { - cumulus.LogMessage("An error occured during GW1000 auto-discovery"); - cumulus.LogMessage("Error: " + ex.Message); - } - - return discovered; - } - - private bool DoDiscovery() - { - if (cumulus.Gw1000AutoUpdateIpAddress || string.IsNullOrWhiteSpace(cumulus.Gw1000IpAddress)) - { - var msg = "Running GW-1000 auto-discovery..."; - cumulus.LogMessage(msg); - - var discoveredDevices = DiscoverGW1000(); - - if (discoveredDevices.IP.Count == 0) - { - // We didn't find anything on the network - msg = "Failed to discover any GW1000 devices"; - cumulus.LogMessage(msg); - cumulus.LogConsoleMessage(msg); - } - else if (discoveredDevices.IP.Count == 1 && (string.IsNullOrEmpty(macaddr) || discoveredDevices.Mac[0] == macaddr)) - { - cumulus.LogDebugMessage("Discovered one GW1000 device"); - // If only one device is discovered, and its MAC address matches (or our MAC is blank), then just use it - if (cumulus.Gw1000IpAddress != discoveredDevices.IP[0]) - { - cumulus.LogMessage("Discovered a new IP address for the GW1000 that does not match our current one"); - cumulus.LogMessage($"Changing previous IP address: {ipaddr} to {discoveredDevices.IP[0]}"); - ipaddr = discoveredDevices.IP[0].Trim(); - cumulus.Gw1000IpAddress = ipaddr; - if (discoveredDevices.Mac[0] != macaddr) - { - cumulus.Gw1000MacAddress = discoveredDevices.Mac[0]; - } - cumulus.WriteIniFile(); - } - } - else if (discoveredDevices.Mac.Contains(macaddr)) - { - // Multiple devices discovered, but we have a MAC address match - - cumulus.LogDebugMessage("Matching GW-1000 MAC address found on the network"); - - var idx = discoveredDevices.Mac.IndexOf(macaddr); - - if (discoveredDevices.IP[idx] != ipaddr) - { - cumulus.LogMessage("Discovered a new IP address for the GW1000 that does not match our current one"); - cumulus.LogMessage($"Changing previous IP address: {ipaddr} to {discoveredDevices.IP[idx]}"); - ipaddr = discoveredDevices.IP[idx]; - cumulus.Gw1000IpAddress = ipaddr; - cumulus.WriteIniFile(); - } - } - else - { - // Multiple devices discovered, and we do not have a clue! - - string iplist = ""; - msg = "Discovered more than one potential GW1000 device."; - cumulus.LogMessage(msg); - cumulus.LogConsoleMessage(msg); - msg = "Please select the IP address from the list and enter it manually into the configuration"; - cumulus.LogMessage(msg); - cumulus.LogConsoleMessage(msg); - for (var i = 0; i < discoveredDevices.IP.Count; i++) - { - iplist += discoveredDevices.IP[i] + " "; - } - msg = " discovered IPs = " + iplist; - cumulus.LogMessage(msg); - cumulus.LogConsoleMessage(msg); - } - } - - if (string.IsNullOrWhiteSpace(ipaddr)) - { - var msg = "No IP address configured or discovered for your GW1000, please remedy and restart Cumulus MX"; - cumulus.LogMessage(msg); - cumulus.LogConsoleMessage(msg); - return false; - } - - return true; - } - - private string GetFirmwareVersion() - { - var response = "???"; - cumulus.LogMessage("Reading firmware version"); - - var data = DoCommand(Commands.CMD_READ_FIRMWARE_VERSION); - if (null != data && data.Length > 0) - { - response = Encoding.ASCII.GetString(data, 5, data[4]); - } - return response; - } - - /* - private bool GetSensorIds() - { - cumulus.LogMessage("Reading sensor ids"); - - var data = DoCommand(Commands.CMD_READ_SENSOR_ID); - - // expected response - // 0 - 0xff - header - // 1 - 0xff - header - // 2 - 0x3A - sensor id command - // 3 - 0x?? - size of response - // 4 - wh65 - // 5-8 - wh65 id - // 9 - wh65 signal - // 10 - wh65 battery - // 11 - wh68 - // ... etc - // (??) - 0x?? - checksum - - if (null != data && data.Length > 200) - { - for (int i = 4; i < data[3]; i += 7) - { - PrintSensorInfo(data, i); - } - return true; - } - else - { - return false; - } - } - - private void PrintSensorInfo(byte[] data, int idx) - { - // expected response - // 0 - 0xff - header - // 1 - 0xff - header - // 2 - 0x3A - sensor id command - // 3 - 0x?? - size of response - // 4 - wh65 - // 5-8 - wh65 id - // 9 - wh65 signal - // 10 - wh65 battery - // 11 - wh68 - // ... etc - // (??) - 0x?? - checksum - - var id = ConvertBigEndianUInt32(data, idx + 1); - var type = Enum.GetName(typeof(SensorIds), data[idx]).ToUpper(); - - if (string.IsNullOrEmpty(type)) - { - type = $"unknown type = {id}"; - } - switch (id) - { - 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; - } - } - */ - - private bool GetSensorIdsNew() - { - cumulus.LogMessage("Reading sensor ids"); - - var data = DoCommand(Commands.CMD_READ_SENSOR_ID_NEW); - - // expected response - // 0 - 0xff - header - // 1 - 0xff - header - // 2 - 0x3C - sensor id command - // 3-4 - 0x?? - size of response - // 5 - wh65 - // 6-9 - wh65 id - // 10 - wh65 battery - // 11 - wh65 signal 0-4 - // 12 - wh68 - // ... etc - // (??) - 0x?? - checksum - - var batteryLow = false; - - if (null != data && data.Length > 200) - { - var len = ConvertBigEndianUInt16(data, 3); - - for (int i = 5; i < len; i += 7) - { - if (PrintSensorInfoNew(data, i)) - { - batteryLow = true; - } - } - - cumulus.BatteryLowAlarm.Triggered = batteryLow; - - return true; - } - else - { - return false; - } - } - - private bool PrintSensorInfoNew(byte[] data, int idx) - { - // expected response - // 0 - 0xff - header - // 1 - 0xff - header - // 2 - 0x3C - sensor id command - // 3-4 - 0x?? - size of response - // 5 - wh65 0 - // 6-9 - wh65 id 1-4 - // 10 - wh65 battery 5 - // 11 - wh65 signal 0-4 6 - // 12 - wh68 - // ... etc - // (??) - 0x?? - checksum - - var batteryLow = false; - - try - { - var id = ConvertBigEndianUInt32(data, idx + 1); - var type = Enum.GetName(typeof(SensorIds), data[idx]).ToUpper(); - var battPos = idx + 5; - var sigPos = idx + 6; - if (string.IsNullOrEmpty(type)) - { - type = $"unknown type = {id}"; - } - // Wh65 could be a Wh65 or a Wh24, we found out using the System Info command - if (type == "WH65") - { - type = mainSensor; - } - - switch (id) - { - case 0xFFFFFFFE: - cumulus.LogDebugMessage($" - {type} sensor = disabled"); - return false; - case 0xFFFFFFFF: - cumulus.LogDebugMessage($" - {type} sensor = registering"); - return false; - default: - //cumulus.LogDebugMessage($" - {type} sensor id = {id} signal = {data[sigPos]} battery = {data[battPos]}"); - break; - } - - string batt; - switch (type) - { - case "WH40": // WH40 does not send any battery info :( - batt = "n/a"; - break; - - case "WH65": - case "WH24": - case "WH26": - batt = TestBattery1(data[battPos], 1); // 0 or 1 - break; - - case "WH68": - case "WH80": - case string wh34 when wh34.StartsWith("WH34"): // ch 1-8 - case string wh35 when wh35.StartsWith("WH35"): // ch 1-8 - double battV = data[battPos] * 0.02; - batt = $"{battV:f1}V ({TestBattery4S(data[battPos])})"; // volts, low = 1.2V - break; - - case string wh31 when wh31.StartsWith("WH31"): // ch 1-8 - case string wh51 when wh51.StartsWith("WH51"): // ch 1-8 - batt = $"{data[battPos]} ({TestBattery1(data[battPos], 1)})"; - break; - - case "WH25": - case "WH45": - case "WH57": - case string wh41 when wh41.StartsWith("WH41"): // ch 1-4 - case string wh55 when wh55.StartsWith("WH55"): // ch 1-4 - batt = $"{data[battPos]} ({TestBattery3(data[battPos])})"; // 0-5, low = 1 - break; - - default: - batt = "???"; - break; - } - - if (batt.Contains("Low")) - batteryLow = true; - - cumulus.LogDebugMessage($" - {type} sensor id = {id} signal = {data[sigPos]} battery = {batt}"); - } - catch (Exception ex) - { - cumulus.LogMessage("PrintSensorInfoNew: Error - " + ex.Message); - } - - return batteryLow; - } - - private void GetLiveData() - { - cumulus.LogDebugMessage("Reading live data"); - - // set a flag at the start of every 10 minutes to trigger battery status check - var minute = DateTime.Now.Minute; - if (minute != lastMinute) - { - lastMinute = minute; - tenMinuteChanged = (minute % 10) == 0; - } - - byte[] data = DoCommand(Commands.CMD_GW1000_LIVEDATA); - - // sample data = in-temp, in-hum, abs-baro, rel-baro, temp, hum, dir, speed, gust, light, UV uW, UV-I, rain-rate, rain-day, rain-week, rain-month, rain-year, PM2.5, PM-ch1, Soil-1, temp-2, hum-2, temp-3, hum-3, batt - //byte[] data = new byte[] { 0xFF,0xFF,0x27,0x00,0x5D,0x01,0x00,0x83,0x06,0x55,0x08,0x26,0xE7,0x09,0x26,0xDC,0x02,0x00,0x5D,0x07,0x61,0x0A,0x00,0x89,0x0B,0x00,0x19,0x0C,0x00,0x25,0x15,0x00,0x00,0x00,0x00,0x16,0x00,0x00,0x17,0x00,0x0E,0x00,0x3C,0x10,0x00,0x1E,0x11,0x01,0x4A,0x12,0x00,0x00,0x02,0x68,0x13,0x00,0x00,0x14,0xDC,0x2A,0x01,0x90,0x4D,0x00,0xE3,0x2C,0x34,0x1B,0x00,0xD3,0x23,0x3C,0x1C,0x00,0x60,0x24,0x5A,0x4C,0x04,0x00,0x00,0x00,0xFF,0x5C,0xFF,0x00,0xF4,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0xBA }; - //byte[] data = new byte[] { 0xFF, 0xFF, 0x27, 0x00, 0x6D, 0x01, 0x00, 0x96, 0x06, 0x3C, 0x08, 0x27, 0x00, 0x09, 0x27, 0x49, 0x02, 0x00, 0x16, 0x07, 0x61, 0x0A, 0x00, 0x62, 0x0B, 0x00, 0x00, 0x0C, 0x00, 0x06, 0x15, 0x00, 0x01, 0x7D, 0x40, 0x16, 0x00, 0x00, 0x17, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x11, 0x00, 0xF7, 0x12, 0x00, 0x00, 0x01, 0x5C, 0x13, 0x00, 0x00, 0x15, 0x54, 0x2A, 0x06, 0x40, 0x4D, 0x00, 0xAB, 0x1A, 0xFF, 0x3E, 0x22, 0x39, 0x1B, 0x00, 0x3D, 0x23, 0x51, 0x1C, 0x00, 0xA0, 0x24, 0x45, 0x1D, 0x00, 0xA4, 0x25, 0x3C, 0x1E, 0x00, 0x9D, 0x26, 0x3E, 0x4C, 0x04, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xA4, 0x00, 0xF4, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x19, 0x00, 0x1A, 0x8F }; - //byte[] data = new byte[] { 0xFF, 0xFF, 0x27, 0x00, 0x6D, 0x01, 0x00, 0xF8, 0x06, 0x35, 0x08, 0x27, 0xD6, 0x09, 0x27, 0xE1, 0x02, 0x00, 0xD2, 0x07, 0x5E, 0x0A, 0x00, 0x79, 0x0B, 0x00, 0x05, 0x0C, 0x00, 0x05, 0x15, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x02, 0x17, 0x00, 0x2A, 0x01, 0x71, 0x4D, 0x00, 0xC4, 0x1A, 0x00, 0xE4, 0x22, 0x3B, 0x4C, 0x05, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x06, 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x19, 0x00, 0x33, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x11, 0x00, 0x0D, 0x12, 0x00, 0x00, 0x01, 0x0B, 0x13, 0x00, 0x00, 0x3A, 0x62, 0x0D, 0x00, 0x00, 0x70, 0x00, 0xED, 0x3A, 0x00, 0x2B, 0x00, 0x11, 0x00, 0x1E, 0x00, 0x0D, 0x03, 0x7B, 0x03, 0xD2, 0x06, 0x02 }; - - // expected response - // 0 - 0xff - header - // 1 - 0xff - // 2 - 0x27 - live data command - // 3 - 0x?? - size of response1 - // 4 - 0x?? - size of response2 - // 5-X - data - NOTE format is Bigendian - // Y - 0x?? - checksum - - try - { - if (null != data && data.Length > 16) - { - // now decode it - Int16 tempInt16; - UInt16 tempUint16; - UInt32 tempUint32; - var idx = 5; - var dateTime = DateTime.Now; - var size = ConvertBigEndianUInt16(data, 3); - - double windSpeedLast = -999, rainRateLast = -999, rainLast = -999, gustLast = -999, gustLastCal = -999; - int windDirLast = -999; - double outdoortemp = -999; - - bool batteryLow = false; - - // We check the new value against what we have already, if older then ignore it! - double newLightningDistance = 999; - var newLightningTime = new DateTime(1900, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - - do - { - int chan; - switch (data[idx++]) - { - case 0x01: //Indoor Temperature (℃) - tempInt16 = ConvertBigEndianInt16(data, idx); - DoIndoorTemp(ConvertTempCToUser(tempInt16 / 10.0)); - idx += 2; - break; - case 0x02: //Outdoor Temperature (℃) - tempInt16 = ConvertBigEndianInt16(data, idx); - // do not process temperature here as if "MX calculates DP" is enabled, we have not yet read the humidity value. Have to do it at the end. - outdoortemp = tempInt16 / 10.0; - idx += 2; - break; - case 0x03: //Dew point (℃) - tempInt16 = ConvertBigEndianInt16(data, idx); - DoOutdoorDewpoint(ConvertTempCToUser(tempInt16 / 10.0), dateTime); - idx += 2; - break; - case 0x04: //Wind chill (℃) - tempInt16 = ConvertBigEndianInt16(data, idx); - DoWindChill(ConvertTempCToUser(tempInt16 / 10.0), dateTime); - idx += 2; - break; - case 0x05: //Heat index (℃) - // cumulus calculates this - idx += 2; - break; - case 0x06: //Indoor Humidity(%) - DoIndoorHumidity(data[idx]); - idx += 1; - break; - case 0x07: //Outdoor Humidity (%) - DoOutdoorHumidity(data[idx], dateTime); - idx += 1; - break; - case 0x08: //Absolute Barometric (hpa) - idx += 2; - break; - case 0x09: //Relative Barometric (hpa) - 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); - idx += 2; - break; - case 0x0B: //Wind Speed (m/s) - windSpeedLast = ConvertWindMSToUser(ConvertBigEndianUInt16(data, idx) / 10.0); - idx += 2; - break; - case 0x0C: // Gust speed (m/s) - gustLast = ConvertWindMSToUser(ConvertBigEndianUInt16(data, idx) / 10.0); - gustLastCal = gustLast * cumulus.Calib.WindGust.Mult; - idx += 2; - break; - case 0x0D: //Rain Event (mm) - //TODO: add rain event total - idx += 2; - break; - case 0x0E: //Rain Rate (mm/h) - rainRateLast = ConvertRainMMToUser(ConvertBigEndianUInt16(data, idx) / 10.0); - idx += 2; - break; - case 0x0F: //Rain hour (mm) - idx += 2; - break; - case 0x10: //Rain Day (mm) - idx += 2; - break; - case 0x11: //Rain Week (mm) - idx += 2; - break; - case 0x12: //Rain Month (mm) - idx += 4; - break; - case 0x13: //Rain Year (mm) - rainLast = ConvertRainMMToUser(ConvertBigEndianUInt32(data, idx) / 10.0); - idx += 4; - break; - case 0x14: //Rain Totals (mm) - idx += 4; - break; - case 0x15: //Light (lux) - // Save the Lux value - LightValue = ConvertBigEndianUInt32(data, idx) / 10.0; - // convert Lux to W/m2 - approximately! - DoSolarRad((int)(LightValue * cumulus.LuxToWM2), dateTime); - idx += 4; - break; - case 0x16: //UV (µW/cm²) - what use is this! - idx += 2; - break; - case 0x17: //UVI (0-15 index) - DoUV(data[idx], dateTime); - idx += 1; - break; - case 0x18: //Date and time - idx += 7; - break; - case 0x19: //Day max wind(m/s) - idx += 2; - break; - case 0x1A: //Temperature 1(℃) - case 0x1B: //Temperature 2(℃) - case 0x1C: //Temperature 3(℃) - case 0x1D: //Temperature 4(℃) - case 0x1E: //Temperature 5(℃) - case 0x1F: //Temperature 6(℃) - case 0x20: //Temperature 7(℃) - case 0x21: //Temperature 8(℃) - chan = data[idx - 1] - 0x1A + 1; - tempInt16 = ConvertBigEndianInt16(data, idx); - DoExtraTemp(ConvertTempCToUser(tempInt16 / 10.0), chan); - idx += 2; - break; - case 0x22: //Humidity 1, 0-100% - case 0x23: //Humidity 2, 0-100% - case 0x24: //Humidity 3, 0-100% - case 0x25: //Humidity 4, 0-100% - case 0x26: //Humidity 5, 0-100% - case 0x27: //Humidity 6, 0-100% - case 0x28: //Humidity 7, 0-100% - case 0x29: //Humidity 9, 0-100% - chan = data[idx - 1] - 0x22 + 1; - DoExtraHum(data[idx], chan); - idx += 1; - break; - case 0x2B: //Soil Temperature1 (℃) - case 0x2D: //Soil Temperature2 (℃) - case 0x2F: //Soil Temperature3 (℃) - case 0x31: //Soil Temperature4 (℃) - case 0x33: //Soil Temperature5 (℃) - case 0x35: //Soil Temperature6 (℃) - case 0x37: //Soil Temperature7 (℃) - case 0x39: //Soil Temperature8 (℃) - case 0x3B: //Soil Temperature9 (℃) - case 0x3D: //Soil Temperature10 (℃) - case 0x3F: //Soil Temperature11 (℃) - case 0x41: //Soil Temperature12 (℃) - case 0x43: //Soil Temperature13 (℃) - case 0x45: //Soil Temperature14 (℃) - case 0x47: //Soil Temperature15 (℃) - case 0x49: //Soil Temperature16 (℃) - // figure out the channel number - chan = data[idx - 1] - 0x2B + 2; // -> 2,4,6,8... - chan /= 2; // -> 1,2,3,4... - tempInt16 = ConvertBigEndianInt16(data, idx); - DoSoilTemp(ConvertTempCToUser(tempInt16 / 10.0), chan); - idx += 2; - break; - case 0x2C: //Soil Moisture1 (%) - case 0x2E: //Soil Moisture2 (%) - case 0x30: //Soil Moisture3 (%) - case 0x32: //Soil Moisture4 (%) - case 0x34: //Soil Moisture5 (%) - case 0x36: //Soil Moisture6 (%) - case 0x38: //Soil Moisture7 (%) - case 0x3A: //Soil Moisture8 (%) - case 0x3C: //Soil Moisture9 (%) - case 0x3E: //Soil Moisture10 (%) - case 0x40: //Soil Moisture11 (%) - case 0x42: //Soil Moisture12 (%) - case 0x44: //Soil Moisture13 (%) - case 0x46: //Soil Moisture14 (%) - case 0x48: //Soil Moisture15 (%) - case 0x4A: //Soil Moisture16 (%) - // figure out the channel number - chan = data[idx - 1] - 0x2C + 2; // -> 2,4,6,8... - chan /= 2; // -> 1,2,3,4... - DoSoilMoisture(data[idx], chan); - idx += 1; - break; - case 0x4C: //All sensor lowbatt 16 char - // This has been deprecated since v1.6.5 - now use CMD_READ_SENSOR_ID_NEW - if (tenMinuteChanged && fwVersion.CompareTo(new Version("1.6.5")) >= 0) - { - batteryLow = batteryLow || DoBatteryStatus(data, idx); - } - idx += 16; - break; - case 0x2A: //PM2.5 Air Quality Sensor(μg/m3) - tempUint16 = ConvertBigEndianUInt16(data, idx); - DoAirQuality(tempUint16 / 10.0, 1); - idx += 2; - break; - case 0x4D: //for pm25_ch1 - case 0x4E: //for pm25_ch2 - case 0x4F: //for pm25_ch3 - case 0x50: //for pm25_ch4 - chan = data[idx - 1] - 0x4D + 1; - tempUint16 = ConvertBigEndianUInt16(data, idx); - DoAirQualityAvg(tempUint16 / 10.0, chan); - idx += 2; - break; - case 0x51: //PM2.5 ch_2 Air Quality Sensor(μg/m3) - 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); - DoAirQuality(tempUint16 / 10.0, chan); - idx += 2; - break; - case 0x58: //Leak ch1 - case 0x59: //Leak ch2 - case 0x5A: //Leak ch3 - case 0x5B: //Leak ch4 - chan = data[idx - 1] - 0x58 + 1; - DoLeakSensor(data[idx], chan); - idx += 1; - break; - case 0x60: //Lightning dist (1-40km) - // Sends a default value of 255km until the first strike is detected - newLightningDistance = data[idx] == 0xFF ? 999 : ConvertKmtoUserUnits(data[idx]); - idx += 1; - break; - case 0x61: //Lightning time (UTC) - // Sends a default value until the first strike is detected of 0xFFFFFFFF - tempUint32 = ConvertBigEndianUInt32(data, idx); - if (tempUint32 == 0xFFFFFFFF) - { - newLightningTime = new DateTime(1900, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - } - else - { - var dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - dtDateTime = dtDateTime.AddSeconds(tempUint32).ToLocalTime(); - //cumulus.LogDebugMessage($"Lightning time={dtDateTime}"); - newLightningTime = dtDateTime; - } - idx += 4; - break; - case 0x62: //Lightning strikes today - tempUint32 = ConvertBigEndianUInt32(data, idx); - //cumulus.LogDebugMessage($"Lightning count={tempUint32}"); - LightningStrikesToday = (int)tempUint32; - idx += 4; - break; - // user temp = WH34 8 channel Soil or Water temperature sensors - case 0x63: // user temp ch1 (°C) - case 0x64: // user temp ch2 (°C) - case 0x65: // user temp ch3 (°C) - case 0x66: // user temp ch4 (°C) - case 0x67: // user temp ch5 (°C) - case 0x68: // user temp ch6 (°C) - case 0x69: // user temp ch7 (°C) - case 0x6A: // user temp ch8 (°C) - chan = data[idx - 1] - 0x63 + 2; // -> 2,4,6,8... - chan /= 2; // -> 1,2,3,4... - tempInt16 = ConvertBigEndianInt16(data, idx); - DoUserTemp(ConvertTempCToUser(tempInt16 / 10.0), chan); - - // Firmware version 1.5.9 uses 2 data bytes, 1.6.0+ uses 3 data bytes - if (fwVersion.CompareTo(new Version("1.6.0")) >= 0) - { - if (tenMinuteChanged) - { - var volts = TestBattery4V(data[idx + 2]); - if (volts <= 1.2) - { - batteryLow = true; - cumulus.LogMessage($"WH34 channel #{chan} battery LOW = {volts}V"); - } - else - { - cumulus.LogDebugMessage($"WH34 channel #{chan} battery OK = {volts}V"); - } - } - idx += 3; - } - else - { - idx += 2; - } - break; - case 0x6B: //WH34 User temperature battery (8 channels) - No longer used in firmware 1.6.0+ - if (tenMinuteChanged) - { - batteryLow = batteryLow || DoWH34BatteryStatus(data, idx); - } - idx += 8; - break; - case 0x70: // WH45 CO₂ - batteryLow = batteryLow || DoCO2Decode(data, idx); - idx += 16; - break; - case 0x71: // Ambient ONLY - AQI - //TODO: Not doing anything with this yet - //idx += 2; // SEEMS TO BE VARIABLE - cumulus.LogDebugMessage("Found a device 0x71 - Ambient AQI. No decode for this yet"); - // We will have lost our place now, so bail out - idx = size; - break; - case 0x72: // WH35 Leaf Wetness ch1 - case 0x73: // WH35 Leaf Wetness ch2 - case 0x74: // WH35 Leaf Wetness ch3 - case 0x75: // WH35 Leaf Wetness ch4 - case 0x76: // WH35 Leaf Wetness ch5 - case 0x77: // WH35 Leaf Wetness ch6 - case 0x78: // WH35 Leaf Wetness ch7 - case 0x79: // WH35 Leaf Wetness ch8 - chan = data[idx - 1] - 0x72 + 2; // -> 2,4,6,8... - chan /= 2; // -> 1,2,3,4... - DoLeafWetness(data[idx], chan); - idx += 1; - break; - default: - cumulus.LogDebugMessage($"Error: Unknown sensor id found = {data[idx - 1]}, at position = {idx - 1}"); - // We will have lost our place now, so bail out - idx = size; - break; - } - } while (idx < size); - - // Now do the stuff that requires more than one input parameter - - // Only set the lightning time/distance if it is newer than what we already have - the GW1000 seems to reset this value - if (newLightningTime > LightningTime) - { - LightningTime = newLightningTime; - if (newLightningDistance != 999) - LightningDistance = newLightningDistance; - } - - // Process outdoor temperature here, as GW1000 currently does not supply Dew Point so we have to calculate it in DoOutdoorTemp() - if (outdoortemp > -999) - DoOutdoorTemp(ConvertTempCToUser(outdoortemp), dateTime); - - // Same for extra T/H sensors - for (var i = 1; i <= 8; i++) - { - if (ExtraHum[i] > 0) - { - var dp = MeteoLib.DewPoint(ConvertUserTempToC(ExtraTemp[i]), ExtraHum[i]); - ExtraDewPoint[i] = ConvertTempCToUser(dp); - } - } - - if (tenMinuteChanged) tenMinuteChanged = false; - - - //cumulus.BatteryLowAlarm.Triggered = batteryLow; - - // No average in the live data, so use last value from cumulus - if (windSpeedLast > -999 && windDirLast > -999) - { - DoWind(windSpeedLast, windDirLast, WindAverage / cumulus.Calib.WindSpeed.Mult, dateTime); - //DoWind(windSpeedLast, windDirLast, windSpeedLast, dateTime); - } - - if (gustLastCal > RecentMaxGust) - { - cumulus.LogDebugMessage("Setting max gust from current value: " + gustLastCal.ToString(cumulus.WindFormat)); - CheckHighGust(gustLastCal, windDirLast, dateTime); - - // add to recent values so normal calculation includes this value - WindRecent[nextwind].Gust = gustLast; // use uncalibrated value - WindRecent[nextwind].Speed = WindAverage / cumulus.Calib.WindSpeed.Mult; - WindRecent[nextwind].Timestamp = dateTime; - nextwind = (nextwind + 1) % MaxWindRecent; - - RecentMaxGust = gustLastCal; - } - - if (rainLast > -999 && rainRateLast > -999) - { - DoRain(rainLast, rainRateLast, dateTime); - } - - if (outdoortemp > -999) - { - if (ConvertUserWindToMS(WindAverage) < 1.5) - { - DoWindChill(OutdoorTemperature, dateTime); - } - else - { - // calculate wind chill from calibrated C temp and calibrated wind in KPH - DoWindChill(ConvertTempCToUser(MeteoLib.WindChill(ConvertUserTempToC(OutdoorTemperature), ConvertUserWindToKPH(WindAverage))), dateTime); - } - - DoApparentTemp(dateTime); - DoFeelsLike(dateTime); - DoHumidex(dateTime); - } - - DoForecast("", false); - - UpdateStatusPanel(dateTime); - UpdateMQTT(); - - dataReceived = true; - DataStopped = false; - cumulus.DataStoppedAlarm.Triggered = false; - } - else - { - cumulus.LogMessage("GetLiveData: Invalid response"); - } - } - catch (Exception ex) - { - cumulus.LogMessage("GetLiveData: Error - " + ex.Message); - } - } - - private void GetSystemInfo() - { - cumulus.LogMessage("Reading GW1000 system info"); - - var data = DoCommand(Commands.CMD_READ_SSSS); - - // expected response - // 0 - 0xff - header - // 1 - 0xff - header - // 2 - 0x30 - system info - // 3 - 0x?? - size of response - // 4 - frequency - 0=433, 1=868MHz, 2=915MHz, 3=920MHz - // 5 - sensor type - 0=WH24, 1=WH65 - // 6-9 - UTC time - // 10 - timezone index (?) - // 11 - DST 0-1 - false/true - // 12 - 0x?? - checksum - - if (data.Length != 13) - { - cumulus.LogMessage("Unexpected response to System Info!"); - return; - } - try - { - string freq; - if (data[4] == 0) - freq = "433MHz"; - else if (data[4] == 1) - freq = "868MHz"; - else if (data[4] == 2) - freq = "915MHz"; - else if (data[4] == 3) - freq = "920MHz"; - else - freq = $"Unknown [{data[4]}]"; - - mainSensor = data[5] == 0 ? "WH24" : "WH65"; - - var unix = ConvertBigEndianUInt32(data, 6); - var date = Utils.FromUnixTime(unix); - var dst = data[11] != 0; - - cumulus.LogMessage($"GW1000 Info: freqency: {freq}, main sensor: {mainSensor}, date/time: {date:F}, Automatic DST adjustment: {dst}"); - } - catch (Exception ex) - { - cumulus.LogMessage("Error processing System Info: " + ex.Message); - } - } - - private byte[] DoCommand(Commands command) - { - var buffer = new byte[2028]; - var bytesRead = 0; - var cmdName = command.ToString(); - - var payload = new CommandPayload(command); - var tmrComm = new CommTimer(); - - var bytes = payload.Serialise(); - - try - { - stream.Write(bytes, 0, bytes.Length); - - tmrComm.Start(1000); - - while (tmrComm.timedout == false) - { - if (stream.DataAvailable) - { - while (stream.DataAvailable) - { - // Read the current character - var ch = stream.ReadByte(); - if (ch > -1) - { - buffer[bytesRead] = (byte)ch; - bytesRead++; - //cumulus.LogMessage("Received " + ch.ToString("X2")); - } - } - tmrComm.Stop(); - } - else - { - Thread.Sleep(20); - } - } - - // Check the response is to our command and checksum is OK - if (bytesRead == 0 || buffer[2] != (byte)command || !ChecksumOk(buffer, (int)Enum.Parse(typeof(CommandRespSize), cmdName))) - { - if (bytesRead > 0) - { - cumulus.LogMessage($"DoCommand({cmdName}): Invalid response"); - cumulus.LogDebugMessage($"command resp={buffer[2]}, checksum=" + (ChecksumOk(buffer, (int)Enum.Parse(typeof(CommandRespSize), cmdName)) ? "OK" : "BAD")); - cumulus.LogDataMessage("Received 0x" + BitConverter.ToString(buffer, 0, bytesRead - 1)); - } - else - { - cumulus.LogMessage($"DoCommand({cmdName}): No response received"); - } - return null; - } - else - { - cumulus.LogDebugMessage($"DoCommand({cmdName}): Valid response"); - } - } - catch (Exception ex) - { - cumulus.LogMessage($"DoCommand({cmdName}): Error - " + ex.Message); - connectedOk = socket.Connected; - } - // Copy the data we want out of the buffer - if (bytesRead > 0) - { - var data = new byte[bytesRead]; - Array.Copy(buffer, data, data.Length); - cumulus.LogDataMessage("Received 0x" + BitConverter.ToString(data)); - return data; - } - - return null; - } - - private bool DoCO2Decode(byte[] data, int index) - { - bool batteryLow = false; - int idx = index; - cumulus.LogDebugMessage("WH45 CO₂: Decoding..."); - //CO2Data co2Data = (CO2Data)RawDeserialize(data, index, typeof(CO2Data)); - - try - { - CO2_temperature = ConvertTempCToUser(ConvertBigEndianInt16(data, idx) / 10.0); - idx += 2; - CO2_humidity = data[idx++]; - CO2_pm10 = ConvertBigEndianUInt16(data, idx) / 10.0; - idx += 2; - CO2_pm10_24h = ConvertBigEndianUInt16(data, idx) / 10.0; - idx += 2; - CO2_pm2p5 = ConvertBigEndianUInt16(data, idx) / 10.0; - idx += 2; - CO2_pm2p5_24h = ConvertBigEndianUInt16(data, idx) / 10.0; - idx += 2; - CO2 = ConvertBigEndianUInt16(data, idx); - idx += 2; - CO2_24h = ConvertBigEndianUInt16(data, idx); - idx += 2; - var batt = TestBattery3(data[idx]); - var msg = $"WH45 CO₂: temp={CO2_temperature.ToString(cumulus.TempFormat)}, hum={CO2_humidity}, pm10={CO2_pm10:F1}, pm10_24h={CO2_pm10_24h:F1}, pm2.5={CO2_pm2p5:F1}, pm2.5_24h={CO2_pm2p5_24h:F1}, CO₂={CO2}, CO₂_24h={CO2_24h}"; - if (tenMinuteChanged) - { - if (batt == "Low") - { - batteryLow = true; - msg += $", Battery={batt}"; - } - else - { - msg += $", Battery={batt}"; - } - } - cumulus.LogDebugMessage(msg); - } - catch (Exception ex) - { - cumulus.LogMessage("DoCO2Decode: Error - " + ex.Message); - } - - return batteryLow; - } - - private bool DoBatteryStatus(byte[] data, int index) - { - bool batteryLow = false; - BatteryStatus status = (BatteryStatus)RawDeserialize(data, index, typeof(BatteryStatus)); - cumulus.LogDebugMessage("battery status..."); - - var str = "singles>" + - " wh24=" + TestBattery1(status.single, (byte)SigSen.Wh24) + - " wh25=" + TestBattery1(status.single, (byte)SigSen.Wh25) + - " wh26=" + TestBattery1(status.single, (byte)SigSen.Wh26) + - " wh40=" + TestBattery1(status.single, (byte)SigSen.Wh40); - if (str.Contains("Low")) - { - batteryLow = true; - cumulus.LogMessage(str); - } - else - cumulus.LogDebugMessage(str); - - str = "wh31>" + - " ch1=" + TestBattery1(status.wh31, (byte)Wh31Ch.Ch1) + - " ch2=" + TestBattery1(status.wh31, (byte)Wh31Ch.Ch2) + - " ch3=" + TestBattery1(status.wh31, (byte)Wh31Ch.Ch3) + - " ch4=" + TestBattery1(status.wh31, (byte)Wh31Ch.Ch4) + - " ch5=" + TestBattery1(status.wh31, (byte)Wh31Ch.Ch5) + - " ch6=" + TestBattery1(status.wh31, (byte)Wh31Ch.Ch6) + - " ch7=" + TestBattery1(status.wh31, (byte)Wh31Ch.Ch7) + - " ch8=" + TestBattery1(status.wh31, (byte)Wh31Ch.Ch8); - if (str.Contains("Low")) - { - batteryLow = true; - cumulus.LogMessage(str); - } - else - cumulus.LogDebugMessage(str); - - str = "wh41>" + - " ch1=" + TestBattery2(status.wh41, 0x0F) + - " ch2=" + TestBattery2((UInt16)(status.wh41 >> 4), 0x0F) + - " ch3=" + TestBattery2((UInt16)(status.wh41 >> 8), 0x0F) + - " ch4=" + TestBattery2((UInt16)(status.wh41 >> 12), 0x0F); - if (str.Contains("Low")) - { - batteryLow = true; - cumulus.LogMessage(str); - } - else - cumulus.LogDebugMessage(str); - - str = "wh51>" + - " ch1=" + TestBattery1(status.wh51, (byte)Wh51Ch.Ch1) + - " ch2=" + TestBattery1(status.wh31, (byte)Wh51Ch.Ch2) + - " ch3=" + TestBattery1(status.wh31, (byte)Wh51Ch.Ch3) + - " ch4=" + TestBattery1(status.wh31, (byte)Wh51Ch.Ch4) + - " ch5=" + TestBattery1(status.wh31, (byte)Wh51Ch.Ch5) + - " ch6=" + TestBattery1(status.wh31, (byte)Wh51Ch.Ch6) + - " ch7=" + TestBattery1(status.wh31, (byte)Wh51Ch.Ch7) + - " ch8=" + TestBattery1(status.wh31, (byte)Wh51Ch.Ch8); - if (str.Contains("Low")) - { - batteryLow = true; - cumulus.LogMessage(str); - } - else - cumulus.LogDebugMessage(str); - - str = "wh57> " + TestBattery3(status.wh57); - if (str.Contains("Low")) - { - batteryLow = true; - cumulus.LogMessage(str); - } - else - cumulus.LogDebugMessage(str); - - str = "wh68> " + TestBattery4S(status.wh68) + " - " + TestBattery4V(status.wh68) + "V"; - if (str.Contains("Low")) - { - batteryLow = true; - cumulus.LogMessage(str); - } - else - cumulus.LogDebugMessage(str); - - str = "wh80> " + TestBattery4S(status.wh80) + " - " + TestBattery4V(status.wh80) + "V"; - if (str.Contains("Low")) - { - batteryLow = true; - cumulus.LogMessage(str); - } - else - cumulus.LogDebugMessage(str); - - str = "wh45> " + TestBattery3(status.wh45); - if (str.Contains("Low")) - { - batteryLow = true; - cumulus.LogMessage(str); - } - else - cumulus.LogDebugMessage(str); - - str = "wh55>" + - " ch1=" + TestBattery3(status.wh55_ch1) + - " ch2=" + TestBattery3(status.wh55_ch2) + - " ch3=" + TestBattery3(status.wh55_ch3) + - " ch4=" + TestBattery3(status.wh55_ch4); - if (str.Contains("Low")) - { - batteryLow = true; - cumulus.LogMessage(str); - } - else - cumulus.LogDebugMessage(str); - - return batteryLow; - } - - private bool DoWH34BatteryStatus(byte[] data, int index) - { - // No longer used in firmware 1.6.0+ - cumulus.LogDebugMessage("WH34 battery status..."); - var str = "wh34>" + - " ch1=" + TestBattery3(data[index + 1]) + - " ch2=" + TestBattery3(data[index + 2]) + - " ch3=" + TestBattery3(data[index + 3]) + - " ch4=" + TestBattery3(data[index + 4]) + - " ch5=" + TestBattery3(data[index + 5]) + - " ch6=" + TestBattery3(data[index + 6]) + - " ch7=" + TestBattery3(data[index + 7]) + - " ch8=" + TestBattery3(data[index + 8]); - - cumulus.LogDebugMessage(str); - - return str.Contains("Low"); - } - - private static string TestBattery1(byte value, byte mask) - { - return (value & mask) == 0 ? "OK" : "Low"; - } - - private static string TestBattery1(UInt16 value, UInt16 mask) - { - return (value & mask) == 0 ? "OK" : "Low"; - } - - private static string TestBattery2(UInt16 value, UInt16 mask) - { - return (value & mask) > 1 ? "OK" : "Low"; - } - - private static string TestBattery3(byte value) - { - return value > 1 ? "OK" : "Low"; - } - private static double TestBattery4V(byte value) - { - return value * 0.02; - } - private static string TestBattery4S(byte value) - { - return value * 0.02 > 1.2 ? "OK" : "Low"; - } - - private static object RawDeserialize(byte[] rawData, int position, Type anyType) - { - int rawsize = Marshal.SizeOf(anyType); - if (rawsize > rawData.Length) - return null; - IntPtr buffer = Marshal.AllocHGlobal(rawsize); - Marshal.Copy(rawData, position, buffer, rawsize); - object retobj = Marshal.PtrToStructure(buffer, anyType); - Marshal.FreeHGlobal(buffer); - return retobj; - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] - private struct CommandPayload - { - private readonly ushort Header; - private readonly byte Command; - private readonly byte Size; - //public byte[] Data; - private readonly byte Checksum; - - //public CommandPayload(byte command, byte[] data) : this() - public CommandPayload(Commands command) : this() - { - //ushort header; - Header = 0xffff; - Command = (byte)command; - Size = (byte)(Marshal.SizeOf(typeof(CommandPayload)) - 3); - Checksum = (byte)(Command + Size); - } - // This will be serialised in little endian format - public byte[] Serialise() - { - // allocate a byte array for the struct data - var buffer = new byte[Marshal.SizeOf(typeof(CommandPayload))]; - - // Allocate a GCHandle and get the array pointer - var gch = GCHandle.Alloc(buffer, GCHandleType.Pinned); - var pBuffer = gch.AddrOfPinnedObject(); - - // copy data from struct to array and unpin the gc pointer - Marshal.StructureToPtr(this, pBuffer, false); - gch.Free(); - - return buffer; - } - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] - private struct BatteryStatus - { - public byte single; - public byte wh31; - public UInt16 wh51; - public byte wh57; - public byte wh68; - public byte wh80; - public byte wh45; - public UInt16 wh41; - public byte wh55_ch1; - public byte wh55_ch2; - public byte wh55_ch3; - public byte wh55_ch4; - } - - /* - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] - private struct BatteryStatusWH34 - { - public byte single; - public byte ch1; - public byte ch2; - public byte ch3; - public byte ch4; - public byte ch5; - public byte ch6; - public byte ch7; - public byte ch8; - } - */ - - /* - private struct SensorInfo - { - string type; - int id; - int signal; - int battery; - bool present; - } - */ - - /* - private class Sensors - { - SensorInfo single { get; set; } - SensorInfo wh26; - SensorInfo wh31; - SensorInfo wh40; - SensorInfo wh41; - SensorInfo wh51; - SensorInfo wh65; - SensorInfo wh68; - SensorInfo wh80; - public Sensors() - { - } - } - - private struct CO2Data - { - public Int16 temp; // °C x10 - public byte hum; // % - public UInt16 pm10; // ug/m3 x10 - public UInt16 pm10_24hr; // ug/m3 x10 - public UInt16 pm2p5; // ug/m3 x10 - public UInt16 pm2p5_24hr; // ug/m3 x10 - public UInt16 co2; // ppm - public UInt16 co2_24hr; // ppm - public byte batt; // 0-5 - } - */ - - private class Discovery - { - public List IP { get; set; } - public List Name { get; set; } - public List Mac { get; set; } - - public Discovery() - { - IP = new List(); - Name = new List(); - Mac = new List(); - } - } - - private bool ChecksumOk(byte[] data, int lengthBytes) - { - ushort size; - - // general response 1 byte size 2 byte size - // 0 - 0xff - header 0 - 0xff - header - // 1 - 0xff 1 - 0xff - // 2 - command 2 - command - // 3 - total size of response 3 - size1 - // 4-X - data 4 - size2 - // X+1 - checksum 5-X - data - // X+1 - checksum - - if (lengthBytes == 1) - { - size = (ushort)data[3]; - } - else - { - size = ConvertBigEndianUInt16(data, 3); - } - - byte checksum = (byte)(data[2] + data[3]); - for (var i = 4; i <= size; i++) - { - checksum += data[i]; - } - - if (checksum != data[size + 1]) - { - cumulus.LogMessage("Bad checksum"); - return false; - } - - return true; - } - - private static UInt16 ConvertBigEndianUInt16(byte[] array, int start) - { - return (UInt16)(array[start] << 8 | array[start+1]); - } - - private static Int16 ConvertBigEndianInt16(byte[] array, int start) - { - return (Int16)((array[start] << 8) + array[start + 1]); - } - - private static UInt32 ConvertBigEndianUInt32(byte[] array, int start) - { - return (UInt32)(array[start++] << 24 | array[start++] << 16 | array[start++] << 8 | array[start]); - } - - private void DataTimeout(object source, ElapsedEventArgs e) - { - if (dataReceived) - { - dataReceived = false; - DataStopped = false; - cumulus.DataStoppedAlarm.Triggered = false; - } - else - { - cumulus.LogMessage($"ERROR: No data received from the GW1000 for {tmrDataWatchdog.Interval / 1000} seconds"); - DataStopped = true; - cumulus.DataStoppedAlarm.Triggered = true; - DoDiscovery(); - } - } - } -} +using System; +using System.ComponentModel; +using System.IO.Ports; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Runtime.InteropServices; +using System.Net; +using System.Timers; +using System.Collections.Generic; +using System.Linq; + +namespace CumulusMX +{ + internal class GW1000Station : WeatherStation + { + private string ipaddr; + private string macaddr; + private const int AtPort = 45000; + private int lastMinute; + private bool tenMinuteChanged = true; + + private TcpClient socket; + private NetworkStream stream; + private bool connectedOk = false; + private bool dataReceived = false; + + private readonly System.Timers.Timer tmrDataWatchdog; + private bool stop = false; + + private Version fwVersion; + + private string mainSensor; + + private enum Commands : byte { + // General order + CMD_WRITE_SSID = 0x11,// send router SSID and Password to wifi module + CMD_BROADCAST = 0x12,//looking for device inside network. Returned data size is 2 Byte + CMD_READ_ECOWITT = 0x1E,// read setting for Ecowitt.net + CMD_WRITE_ECOWITT = 0x1F, // write back setting for Ecowitt.net + CMD_READ_WUNDERGROUND = 0x20,// read back setting for Wunderground + CMD_WRITE_WUNDERGROUND = 0x21, // write back setting for Wunderground + CMD_READ_WOW = 0x22, // read setting for WeatherObservationsWebsite + CMD_WRITE_WOW = 0x23, // write back setting for WeatherObservationsWebsite + CMD_READ_WEATHERCLOUD = 0x24,// read setting for Weathercloud + CMD_WRITE_WEATHERCLOUD = 0x25, // write back setting for Weathercloud + CMD_READ_SATION_MAC = 0x26,// read module MAC + CMD_READ_CUSTOMIZED = 0x2A,// read setting for Customized sever + CMD_WRITE_CUSTOMIZED = 0x2B, // write back customized sever setting + CMD_WRITE_UPDATE = 0x43,// update firmware + CMD_READ_FIRMWARE_VERSION = 0x50,// read back firmware version + CMD_READ_USER_PATH = 0x51, + CMD_WRITE_USER_PATH = 0x52, + // the following commands are only valid for GW1000 and WH2650: + CMD_GW1000_LIVEDATA = 0x27, // read current,return size is 2 Byte + CMD_GET_SOILHUMIAD = 0x28,// read Soilmoisture Sensor calibration parameter + CMD_SET_SOILHUMIAD = 0x29, // write back Soilmoisture Sensor calibration parameter + CMD_GET_MulCH_OFFSET = 0x2C, // read multi channel sensor OFFSET value + CMD_SET_MulCH_OFFSET = 0x2D, // write back multi sensor OFFSET value + CMD_GET_PM25_OFFSET = 0x2E, // read PM2.5OFFSET value + CMD_SET_PM25_OFFSET = 0x2F, // write back PM2.5OFFSET value + CMD_READ_SSSS = 0x30,// read sensor setup ( sensor frequency, wh24/wh65 sensor) + CMD_WRITE_SSSS = 0x31,// write back sensor setup + CMD_READ_RAINDATA = 0x34,// read rain data + CMD_WRITE_RAINDATA = 0x35, // write back rain data + CMD_READ_GAIN = 0x36, // read rain gain + CMD_WRITE_GAIN = 0x37, // write back rain gain + CMD_READ_CALIBRATION = 0x38,// read multiple parameter offset( refer to command description below in detail) + CMD_WRITE_CALIBRATION = 0x39,// write back multiple parameter offset + CMD_READ_SENSOR_ID = 0x3A,// read Sensors ID + CMD_WRITE_SENSOR_ID = 0x3B, // write back Sensors ID + CMD_READ_SENSOR_ID_NEW = 0x3C, + CMD_WRITE_REBOOT = 0x40,// system rebset + CMD_WRITE_RESET = 0x41,// system default setting reset + CMD_GET_CO2_OFFSET = 0x53, + CMD_SET_CO2_OFFSET = 0x54 + } + + private enum CommandRespSize : int + { + CMD_WRITE_SSID = 1, + CMD_BROADCAST = 2, + CMD_READ_ECOWITT = 1, + CMD_WRITE_ECOWITT = 1, + CMD_READ_WUNDERGROUND = 1, + CMD_WRITE_WUNDERGROUND = 1, + CMD_READ_WOW = 1, + CMD_WRITE_WOW = 1, + CMD_READ_WEATHERCLOUD = 1, + CMD_WRITE_WEATHERCLOUD = 1, + CMD_READ_SATION_MAC = 1, + CMD_READ_CUSTOMIZED = 1, + CMD_WRITE_CUSTOMIZED = 1, + CMD_WRITE_UPDATE = 1, + CMD_READ_FIRMWARE_VERSION = 1, + CMD_READ_USER_PATH = 1, + CMD_WRITE_USER_PATH = 1, + // the following commands are only valid for GW1000 and WH2650: + CMD_GW1000_LIVEDATA = 2, + CMD_GET_SOILHUMIAD = 1, + CMD_SET_SOILHUMIAD = 1, + CMD_GET_MulCH_OFFSET = 1, + CMD_SET_MulCH_OFFSET = 1, + CMD_GET_PM25_OFFSET = 1, + CMD_SET_PM25_OFFSET = 1, + CMD_READ_SSSS = 1, + CMD_WRITE_SSSS = 1, + CMD_READ_RAINDATA = 1, + CMD_WRITE_RAINDATA = 1, + CMD_READ_GAIN = 1, + CMD_WRITE_GAIN = 1, + CMD_READ_CALIBRATION = 1, + CMD_WRITE_CALIBRATION = 1, + CMD_READ_SENSOR_ID = 1, + CMD_WRITE_SENSOR_ID = 1, + CMD_WRITE_REBOOT = 1, + CMD_WRITE_RESET = 1, + CMD_READ_SENSOR_ID_NEW = 2 + } + + [Flags] private enum SigSen : byte + { + Wh40 = 1 << 4, + Wh26 = 1 << 5, + Wh25 = 1 << 6, + Wh24 = 1 << 7 + } + + [Flags] private enum Wh31Ch : byte + { + Ch1 = 1 << 0, + Ch2 = 1 << 1, + Ch3 = 1 << 2, + Ch4 = 1 << 3, + Ch5 = 1 << 4, + Ch6 = 1 << 5, + Ch7 = 1 << 6, + Ch8 = 1 << 7 + } + + + /* + private enum _wh41_ch : UInt16 + { + ch1 = 15 << 0, + ch2 = 15 << 4, + ch3 = 15 << 8, + ch4 = 15 << 12 + } + */ + + [Flags] private enum Wh51Ch : UInt32 + { + Ch1 = 1 << 0, + Ch2 = 1 << 1, + Ch3 = 1 << 2, + Ch4 = 1 << 3, + Ch5 = 1 << 4, + Ch6 = 1 << 5, + Ch7 = 1 << 6, + Ch8 = 1 << 7, + Ch9 = 1 << 8, + Ch10 = 1 << 9, + Ch11 = 1 << 10, + Ch12 = 1 << 11, + Ch13 = 1 << 12, + Ch14 = 1 << 13, + Ch15 = 1 << 14, + Ch16 = 1 << 15 + } + + private enum Wh55Ch : UInt32 + { + Ch1 = 15 << 0, + Ch2 = 15 << 4, + Ch3 = 15 << 8, + Ch4 = 15 << 12 + } + + private enum SensorIds + { + Wh65, // 0 + Wh68, // 1 + Wh80, // 2 + Wh40, // 3 + Wh25, // 4 + Wh26, // 5 + Wh31Ch1, // 6 + Wh31Ch2, // 7 + Wh31Ch3, // 8 + Wh31Ch4, // 9 + Wh31Ch5, // 10 + Wh31Ch6, // 11 + Wh31Ch7, // 12 + Wh31Ch8, // 13 + Wh51Ch1, // 14 + Wh51Ch2, // 15 + Wh51Ch3, // 16 + Wh51Ch4, // 17 + Wh51Ch5, // 18 + Wh51Ch6, // 19 + Wh51Ch7, // 20 + Wh51Ch8, // 21 + Wh41Ch1, // 22 + Wh41Ch2, // 23 + Wh41Ch3, // 24 + Wh41Ch4, // 25 + Wh57, // 26 + Wh55Ch1, // 27 + Wh55Ch2, // 28 + Wh55Ch3, // 29 + Wh55Ch4, // 30 + Wh34Ch1, // 31 + Wh34Ch2, // 32 + Wh34Ch3, // 33 + Wh34Ch4, // 34 + Wh34Ch5, // 35 + Wh34Ch6, // 36 + Wh34Ch7, // 37 + Wh34Ch8, // 38 + Wh45, // 39 + Wh35Ch1, // 40 + Wh35Ch2, // 41 + Wh35Ch3, // 42 + Wh35Ch4, // 43 + Wh35Ch5, // 44 + Wh35Ch6, // 45 + Wh35Ch7, // 46 + Wh35Ch8 // 47 + }; + + public GW1000Station(Cumulus cumulus) : base(cumulus) + { + + cumulus.Manufacturer = cumulus.ECOWITT; + cumulus.AirQualityUnitText = "µg/m³"; + cumulus.SoilMoistureUnitText = "%"; + // GW1000 does not provide average wind speeds + cumulus.StationOptions.UseWind10MinAve = true; + cumulus.StationOptions.UseSpeedForAvgCalc = false; + + LightningTime = DateTime.MinValue; + LightningDistance = 999; + + tmrDataWatchdog = new System.Timers.Timer(); + + // GW1000 does not send DP, so force MX to calculate it + cumulus.StationOptions.CalculatedDP = true; + + ipaddr = cumulus.Gw1000IpAddress; + macaddr = cumulus.Gw1000MacAddress; + + if (!DoDiscovery()) + { + return; + } + + cumulus.LogMessage("Using IP address = " + ipaddr + " Port = " + AtPort); + socket = OpenTcpPort(); + + connectedOk = socket != null; + + if (connectedOk) + { + cumulus.LogMessage("Connected OK"); + cumulus.LogConsoleMessage("Connected to station"); + } + else + { + cumulus.LogMessage("Not Connected"); + cumulus.LogConsoleMessage("Unable to connect to station"); + } + + if (connectedOk) + { + // Get the firmware version as check we are communicating + GW1000FirmwareVersion = GetFirmwareVersion(); + cumulus.LogMessage($"GW1000 firmware version: {GW1000FirmwareVersion}"); + var fwString = GW1000FirmwareVersion.Split(new string[] { "_V" }, StringSplitOptions.None); + fwVersion = new Version(fwString[1]); + + GetSystemInfo(); + + GetSensorIdsNew(); + } + + timerStartNeeded = true; + LoadLastHoursFromDataLogs(cumulus.LastUpdateTime); + DoTrendValues(DateTime.Now); + + // WLL does not provide a forecast string, so use the Cumulus forecast + cumulus.UseCumulusForecast = true; + + cumulus.LogMessage("Starting GW1000"); + + StartLoop(); + bw = new BackgroundWorker(); + } + + private TcpClient OpenTcpPort() + { + TcpClient client = null; + int attempt = 0; + + // Creating the new TCP socket effectively opens it - specify IP address or domain name and port + while (attempt < 5 && client == null) + { + attempt++; + cumulus.LogDebugMessage("GW1000 Connect attempt " + attempt); + try + { + client = new TcpClient(ipaddr, AtPort); + + if (!client.Connected) + { + try + { + client.Close(); + } + catch + { } + client = null; + } + + Thread.Sleep(1000); + } + catch + { + //MessageBox.Show(ex.Message); + } + } + + // Set the timeout of the underlying stream + if (client != null) + { + stream = client.GetStream(); + stream.ReadTimeout = 2500; + cumulus.LogDebugMessage("GW1000 reconnected"); + } + else + { + cumulus.LogDebugMessage("GW1000 connect failed"); + } + + return client; + } + + public override void Start() + { + // Wait for the lock + cumulus.LogDebugMessage("Lock: Station waiting for lock"); + Cumulus.syncInit.Wait(); + cumulus.LogDebugMessage("Lock: Station has the lock"); + + cumulus.LogMessage("Start normal reading loop"); + + cumulus.LogDebugMessage("Lock: Station releasing lock"); + Cumulus.syncInit.Release(); + + tenMinuteChanged = true; + lastMinute = DateTime.Now.Minute; + + // Start a broadcast watchdog to warn if messages are not being received + tmrDataWatchdog.Elapsed += DataTimeout; + tmrDataWatchdog.Interval = 1000 * 30; // timeout after 30 seconds + tmrDataWatchdog.AutoReset = true; + tmrDataWatchdog.Start(); + + try + { + while (!stop) + { + if (connectedOk) + { + GetLiveData(); + + // at the start of every 10 minutes to trigger battery status check + var minute = DateTime.Now.Minute; + if (minute != lastMinute) + { + lastMinute = minute; + if ((minute % 10) == 0) + { + GetSensorIdsNew(); + } + } + } + else + { + cumulus.LogMessage("Attempting to reconnect to GW1000..."); + socket = OpenTcpPort(); + connectedOk = socket != null; + if (connectedOk) + { + cumulus.LogMessage("Reconnected to GW1000"); + GetLiveData(); + } + } + Thread.Sleep(1000 * 10); + } + } + // Catch the ThreadAbortException + catch (ThreadAbortException) + { + } + finally + { + if (socket != null) + { + socket.GetStream().WriteByte(10); + socket.Close(); + } + } + } + + public override void Stop() + { + cumulus.LogMessage("Closing connection"); + try + { + stop = true; + socket.GetStream().WriteByte(10); + socket.Close(); + tmrDataWatchdog.Stop(); + StopMinuteTimer(); + } + catch + { + } + } + + private void bw_DoStart(object sender, DoWorkEventArgs e) + { + cumulus.LogDebugMessage("Lock: Station waiting for lock"); + Cumulus.syncInit.Wait(); + cumulus.LogDebugMessage("Lock: Station has the lock"); + + // Wait a short while for Cumulus initialisation to complete + Thread.Sleep(500); + StartLoop(); + + cumulus.LogDebugMessage("Lock: Station releasing lock"); + Cumulus.syncInit.Release(); + } + + private Discovery DiscoverGW1000() + { + // We only want unique IP addresses + var discovered = new Discovery(); + const int broadcastPort = 46000; + const int clientPort = 59387; + + + try + { + using (var client = new UdpClient()) + using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) + { + var recvEp = new IPEndPoint(IPAddress.Any, clientPort); + var sendEp = new IPEndPoint(IPAddress.Broadcast, broadcastPort); + var sendBytes = new byte[] { 0xff, 0xff, 0x12, 0x03, 0x15 }; + + socket.ReceiveTimeout = 500; + socket.EnableBroadcast = true; + socket.Bind(recvEp); + + + var receivedBytes = new byte[200]; + + var endTime = DateTime.Now.AddSeconds(5); + + client.EnableBroadcast = true; + client.Send(sendBytes, sendBytes.Length, sendEp); + + string[] namesToCheck = { "GW1000", "WH2650", "EasyWeather", "AMBWeather", "WS1900", "WN1900" }; + + do + { + try + { + socket.Receive(receivedBytes, 0, receivedBytes.Length, SocketFlags.None); + //receivedBytes = client.Receive(ref recvEp); + string ipAddr = $"{receivedBytes[11]}.{receivedBytes[12]}.{receivedBytes[13]}.{receivedBytes[14]}"; + int nameLen = receivedBytes[17]; + var nameArr = new byte[nameLen]; + var macArr = new byte[6]; + + Array.Copy(receivedBytes, 18, nameArr, 0, nameLen); + var name = Encoding.Default.GetString(nameArr); + + Array.Copy(receivedBytes, 5, macArr, 0, 6); + var macHex = BitConverter.ToString(macArr).Replace('-', ':'); + + if (namesToCheck.Any((name.Split('-')[0]).StartsWith) && ipAddr.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries).Length == 4) + { + IPAddress ipAddr2; + if (IPAddress.TryParse(ipAddr, out ipAddr2)) + { + if (!discovered.IP.Contains(ipAddr)) + { + cumulus.LogDebugMessage($"Discovered GW1000 device: Name={name}, IP={ipAddr}, MAC={macHex}"); + discovered.IP.Add(ipAddr); + discovered.Name.Add(name); + discovered.Mac.Add(macHex); + } + } + } + else + { + cumulus.LogDebugMessage($"Discovered an unsupported device: Name={name}, IP={ipAddr}, MAC={macHex}"); + } + } + catch { } + } while (DateTime.Now < endTime); + } + } + catch (Exception ex) + { + cumulus.LogMessage("An error occured during GW1000 auto-discovery"); + cumulus.LogMessage("Error: " + ex.Message); + } + + return discovered; + } + + private bool DoDiscovery() + { + if (cumulus.Gw1000AutoUpdateIpAddress || string.IsNullOrWhiteSpace(cumulus.Gw1000IpAddress)) + { + var msg = "Running GW-1000 auto-discovery..."; + cumulus.LogMessage(msg); + + var discoveredDevices = DiscoverGW1000(); + + if (discoveredDevices.IP.Count == 0) + { + // We didn't find anything on the network + msg = "Failed to discover any GW1000 devices"; + cumulus.LogMessage(msg); + cumulus.LogConsoleMessage(msg); + } + else if (discoveredDevices.IP.Count == 1 && (string.IsNullOrEmpty(macaddr) || discoveredDevices.Mac[0] == macaddr)) + { + cumulus.LogDebugMessage("Discovered one GW1000 device"); + // If only one device is discovered, and its MAC address matches (or our MAC is blank), then just use it + if (cumulus.Gw1000IpAddress != discoveredDevices.IP[0]) + { + cumulus.LogMessage("Discovered a new IP address for the GW1000 that does not match our current one"); + cumulus.LogMessage($"Changing previous IP address: {ipaddr} to {discoveredDevices.IP[0]}"); + ipaddr = discoveredDevices.IP[0].Trim(); + cumulus.Gw1000IpAddress = ipaddr; + if (discoveredDevices.Mac[0] != macaddr) + { + cumulus.Gw1000MacAddress = discoveredDevices.Mac[0]; + } + cumulus.WriteIniFile(); + } + } + else if (discoveredDevices.Mac.Contains(macaddr)) + { + // Multiple devices discovered, but we have a MAC address match + + cumulus.LogDebugMessage("Matching GW-1000 MAC address found on the network"); + + var idx = discoveredDevices.Mac.IndexOf(macaddr); + + if (discoveredDevices.IP[idx] != ipaddr) + { + cumulus.LogMessage("Discovered a new IP address for the GW1000 that does not match our current one"); + cumulus.LogMessage($"Changing previous IP address: {ipaddr} to {discoveredDevices.IP[idx]}"); + ipaddr = discoveredDevices.IP[idx]; + cumulus.Gw1000IpAddress = ipaddr; + cumulus.WriteIniFile(); + } + } + else + { + // Multiple devices discovered, and we do not have a clue! + + string iplist = ""; + msg = "Discovered more than one potential GW1000 device."; + cumulus.LogMessage(msg); + cumulus.LogConsoleMessage(msg); + msg = "Please select the IP address from the list and enter it manually into the configuration"; + cumulus.LogMessage(msg); + cumulus.LogConsoleMessage(msg); + for (var i = 0; i < discoveredDevices.IP.Count; i++) + { + iplist += discoveredDevices.IP[i] + " "; + } + msg = " discovered IPs = " + iplist; + cumulus.LogMessage(msg); + cumulus.LogConsoleMessage(msg); + } + } + + if (string.IsNullOrWhiteSpace(ipaddr)) + { + var msg = "No IP address configured or discovered for your GW1000, please remedy and restart Cumulus MX"; + cumulus.LogMessage(msg); + cumulus.LogConsoleMessage(msg); + return false; + } + + return true; + } + + private string GetFirmwareVersion() + { + var response = "???"; + cumulus.LogMessage("Reading firmware version"); + + var data = DoCommand(Commands.CMD_READ_FIRMWARE_VERSION); + if (null != data && data.Length > 0) + { + response = Encoding.ASCII.GetString(data, 5, data[4]); + } + return response; + } + + /* + private bool GetSensorIds() + { + cumulus.LogMessage("Reading sensor ids"); + + var data = DoCommand(Commands.CMD_READ_SENSOR_ID); + + // expected response + // 0 - 0xff - header + // 1 - 0xff - header + // 2 - 0x3A - sensor id command + // 3 - 0x?? - size of response + // 4 - wh65 + // 5-8 - wh65 id + // 9 - wh65 signal + // 10 - wh65 battery + // 11 - wh68 + // ... etc + // (??) - 0x?? - checksum + + if (null != data && data.Length > 200) + { + for (int i = 4; i < data[3]; i += 7) + { + PrintSensorInfo(data, i); + } + return true; + } + else + { + return false; + } + } + + private void PrintSensorInfo(byte[] data, int idx) + { + // expected response + // 0 - 0xff - header + // 1 - 0xff - header + // 2 - 0x3A - sensor id command + // 3 - 0x?? - size of response + // 4 - wh65 + // 5-8 - wh65 id + // 9 - wh65 signal + // 10 - wh65 battery + // 11 - wh68 + // ... etc + // (??) - 0x?? - checksum + + var id = ConvertBigEndianUInt32(data, idx + 1); + var type = Enum.GetName(typeof(SensorIds), data[idx]).ToUpper(); + + if (string.IsNullOrEmpty(type)) + { + type = $"unknown type = {id}"; + } + switch (id) + { + 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; + } + } + */ + + private bool GetSensorIdsNew() + { + cumulus.LogMessage("Reading sensor ids"); + + var data = DoCommand(Commands.CMD_READ_SENSOR_ID_NEW); + + // expected response + // 0 - 0xff - header + // 1 - 0xff - header + // 2 - 0x3C - sensor id command + // 3-4 - 0x?? - size of response + // 5 - wh65 + // 6-9 - wh65 id + // 10 - wh65 battery + // 11 - wh65 signal 0-4 + // 12 - wh68 + // ... etc + // (??) - 0x?? - checksum + + var batteryLow = false; + + try + { + if (null != data && data.Length > 200) + { + var len = ConvertBigEndianUInt16(data, 3); + + // Only loop as far as last record (7 bytes) minus the checksum byte + for (int i = 5; i < len - 7; i += 7) + { + if (PrintSensorInfoNew(data, i)) + { + batteryLow = true; + } + } + + cumulus.BatteryLowAlarm.Triggered = batteryLow; + + return true; + } + else + { + return false; + } + } + catch (Exception ex) + { + cumulus.LogMessage("GetSensorIdsNew: Unexpected error - " + ex.Message); + // no idea, so report battery as good + return false; + } + } + + private bool PrintSensorInfoNew(byte[] data, int idx) + { + // expected response + // 0 - 0xff - header + // 1 - 0xff - header + // 2 - 0x3C - sensor id command + // 3-4 - 0x?? - size of response + // 5 - wh65 0 + // 6-9 - wh65 id 1-4 + // 10 - wh65 battery 5 + // 11 - wh65 signal 0-4 6 + // 12 - wh68 + // ... etc + // (??) - 0x?? - checksum + + var batteryLow = false; + + try + { + var id = ConvertBigEndianUInt32(data, idx + 1); + var type = Enum.GetName(typeof(SensorIds), data[idx]).ToUpper(); + var battPos = idx + 5; + var sigPos = idx + 6; + if (string.IsNullOrEmpty(type)) + { + type = $"unknown type = {id}"; + } + // Wh65 could be a Wh65 or a Wh24, we found out using the System Info command + if (type == "WH65") + { + type = mainSensor; + } + + switch (id) + { + case 0xFFFFFFFE: + cumulus.LogDebugMessage($" - {type} sensor = disabled"); + return false; + case 0xFFFFFFFF: + cumulus.LogDebugMessage($" - {type} sensor = registering"); + return false; + default: + //cumulus.LogDebugMessage($" - {type} sensor id = {id} signal = {data[sigPos]} battery = {data[battPos]}"); + break; + } + + string batt; + switch (type) + { + case "WH40": // WH40 does not send any battery info :( + batt = "n/a"; + break; + + case "WH65": + case "WH24": + case "WH26": + batt = TestBattery1(data[battPos], 1); // 0 or 1 + break; + + case "WH68": + case "WH80": + case string wh34 when wh34.StartsWith("WH34"): // ch 1-8 + case string wh35 when wh35.StartsWith("WH35"): // ch 1-8 + double battV = data[battPos] * 0.02; + batt = $"{battV:f1}V ({TestBattery4S(data[battPos])})"; // volts, low = 1.2V + break; + + case string wh31 when wh31.StartsWith("WH31"): // ch 1-8 + case string wh51 when wh51.StartsWith("WH51"): // ch 1-8 + batt = $"{data[battPos]} ({TestBattery1(data[battPos], 1)})"; + break; + + case "WH25": + case "WH45": + case "WH57": + case string wh41 when wh41.StartsWith("WH41"): // ch 1-4 + case string wh55 when wh55.StartsWith("WH55"): // ch 1-4 + batt = $"{data[battPos]} ({TestBattery3(data[battPos])})"; // 0-5, low = 1 + break; + + default: + batt = "???"; + break; + } + + if (batt.Contains("Low")) + batteryLow = true; + + cumulus.LogDebugMessage($" - {type} sensor id = {id} signal = {data[sigPos]} battery = {batt}"); + } + catch (Exception ex) + { + cumulus.LogMessage("PrintSensorInfoNew: Error - " + ex.Message); + } + + return batteryLow; + } + + private void GetLiveData() + { + cumulus.LogDebugMessage("Reading live data"); + + // set a flag at the start of every 10 minutes to trigger battery status check + var minute = DateTime.Now.Minute; + if (minute != lastMinute) + { + lastMinute = minute; + tenMinuteChanged = (minute % 10) == 0; + } + + byte[] data = DoCommand(Commands.CMD_GW1000_LIVEDATA); + + // sample data = in-temp, in-hum, abs-baro, rel-baro, temp, hum, dir, speed, gust, light, UV uW, UV-I, rain-rate, rain-day, rain-week, rain-month, rain-year, PM2.5, PM-ch1, Soil-1, temp-2, hum-2, temp-3, hum-3, batt + //byte[] data = new byte[] { 0xFF,0xFF,0x27,0x00,0x5D,0x01,0x00,0x83,0x06,0x55,0x08,0x26,0xE7,0x09,0x26,0xDC,0x02,0x00,0x5D,0x07,0x61,0x0A,0x00,0x89,0x0B,0x00,0x19,0x0C,0x00,0x25,0x15,0x00,0x00,0x00,0x00,0x16,0x00,0x00,0x17,0x00,0x0E,0x00,0x3C,0x10,0x00,0x1E,0x11,0x01,0x4A,0x12,0x00,0x00,0x02,0x68,0x13,0x00,0x00,0x14,0xDC,0x2A,0x01,0x90,0x4D,0x00,0xE3,0x2C,0x34,0x1B,0x00,0xD3,0x23,0x3C,0x1C,0x00,0x60,0x24,0x5A,0x4C,0x04,0x00,0x00,0x00,0xFF,0x5C,0xFF,0x00,0xF4,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0xBA }; + //byte[] data = new byte[] { 0xFF, 0xFF, 0x27, 0x00, 0x6D, 0x01, 0x00, 0x96, 0x06, 0x3C, 0x08, 0x27, 0x00, 0x09, 0x27, 0x49, 0x02, 0x00, 0x16, 0x07, 0x61, 0x0A, 0x00, 0x62, 0x0B, 0x00, 0x00, 0x0C, 0x00, 0x06, 0x15, 0x00, 0x01, 0x7D, 0x40, 0x16, 0x00, 0x00, 0x17, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x11, 0x00, 0xF7, 0x12, 0x00, 0x00, 0x01, 0x5C, 0x13, 0x00, 0x00, 0x15, 0x54, 0x2A, 0x06, 0x40, 0x4D, 0x00, 0xAB, 0x1A, 0xFF, 0x3E, 0x22, 0x39, 0x1B, 0x00, 0x3D, 0x23, 0x51, 0x1C, 0x00, 0xA0, 0x24, 0x45, 0x1D, 0x00, 0xA4, 0x25, 0x3C, 0x1E, 0x00, 0x9D, 0x26, 0x3E, 0x4C, 0x04, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xA4, 0x00, 0xF4, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x19, 0x00, 0x1A, 0x8F }; + //byte[] data = new byte[] { 0xFF, 0xFF, 0x27, 0x00, 0x6D, 0x01, 0x00, 0xF8, 0x06, 0x35, 0x08, 0x27, 0xD6, 0x09, 0x27, 0xE1, 0x02, 0x00, 0xD2, 0x07, 0x5E, 0x0A, 0x00, 0x79, 0x0B, 0x00, 0x05, 0x0C, 0x00, 0x05, 0x15, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x02, 0x17, 0x00, 0x2A, 0x01, 0x71, 0x4D, 0x00, 0xC4, 0x1A, 0x00, 0xE4, 0x22, 0x3B, 0x4C, 0x05, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x06, 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x19, 0x00, 0x33, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x11, 0x00, 0x0D, 0x12, 0x00, 0x00, 0x01, 0x0B, 0x13, 0x00, 0x00, 0x3A, 0x62, 0x0D, 0x00, 0x00, 0x70, 0x00, 0xED, 0x3A, 0x00, 0x2B, 0x00, 0x11, 0x00, 0x1E, 0x00, 0x0D, 0x03, 0x7B, 0x03, 0xD2, 0x06, 0x02 }; + + // expected response + // 0 - 0xff - header + // 1 - 0xff + // 2 - 0x27 - live data command + // 3 - 0x?? - size of response1 + // 4 - 0x?? - size of response2 + // 5-X - data - NOTE format is Bigendian + // Y - 0x?? - checksum + + try + { + if (null != data && data.Length > 16) + { + // now decode it + Int16 tempInt16; + UInt16 tempUint16; + UInt32 tempUint32; + var idx = 5; + var dateTime = DateTime.Now; + var size = ConvertBigEndianUInt16(data, 3); + + double windSpeedLast = -999, rainRateLast = -999, rainLast = -999, gustLast = -999, gustLastCal = -999; + int windDirLast = -999; + double outdoortemp = -999; + + bool batteryLow = false; + + // We check the new value against what we have already, if older then ignore it! + double newLightningDistance = 999; + var newLightningTime = new DateTime(1900, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + + do + { + int chan; + switch (data[idx++]) + { + case 0x01: //Indoor Temperature (℃) + tempInt16 = ConvertBigEndianInt16(data, idx); + DoIndoorTemp(ConvertTempCToUser(tempInt16 / 10.0)); + idx += 2; + break; + case 0x02: //Outdoor Temperature (℃) + tempInt16 = ConvertBigEndianInt16(data, idx); + // do not process temperature here as if "MX calculates DP" is enabled, we have not yet read the humidity value. Have to do it at the end. + outdoortemp = tempInt16 / 10.0; + idx += 2; + break; + case 0x03: //Dew point (℃) + tempInt16 = ConvertBigEndianInt16(data, idx); + DoOutdoorDewpoint(ConvertTempCToUser(tempInt16 / 10.0), dateTime); + idx += 2; + break; + case 0x04: //Wind chill (℃) + tempInt16 = ConvertBigEndianInt16(data, idx); + DoWindChill(ConvertTempCToUser(tempInt16 / 10.0), dateTime); + idx += 2; + break; + case 0x05: //Heat index (℃) + // cumulus calculates this + idx += 2; + break; + case 0x06: //Indoor Humidity(%) + DoIndoorHumidity(data[idx]); + idx += 1; + break; + case 0x07: //Outdoor Humidity (%) + DoOutdoorHumidity(data[idx], dateTime); + idx += 1; + break; + case 0x08: //Absolute Barometric (hpa) + idx += 2; + break; + case 0x09: //Relative Barometric (hpa) + 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); + idx += 2; + break; + case 0x0B: //Wind Speed (m/s) + windSpeedLast = ConvertWindMSToUser(ConvertBigEndianUInt16(data, idx) / 10.0); + idx += 2; + break; + case 0x0C: // Gust speed (m/s) + gustLast = ConvertWindMSToUser(ConvertBigEndianUInt16(data, idx) / 10.0); + gustLastCal = gustLast * cumulus.Calib.WindGust.Mult; + idx += 2; + break; + case 0x0D: //Rain Event (mm) + //TODO: add rain event total + idx += 2; + break; + case 0x0E: //Rain Rate (mm/h) + rainRateLast = ConvertRainMMToUser(ConvertBigEndianUInt16(data, idx) / 10.0); + idx += 2; + break; + case 0x0F: //Rain hour (mm) + idx += 2; + break; + case 0x10: //Rain Day (mm) + idx += 2; + break; + case 0x11: //Rain Week (mm) + idx += 2; + break; + case 0x12: //Rain Month (mm) + idx += 4; + break; + case 0x13: //Rain Year (mm) + rainLast = ConvertRainMMToUser(ConvertBigEndianUInt32(data, idx) / 10.0); + idx += 4; + break; + case 0x14: //Rain Totals (mm) + idx += 4; + break; + case 0x15: //Light (lux) + // Save the Lux value + LightValue = ConvertBigEndianUInt32(data, idx) / 10.0; + // convert Lux to W/m2 - approximately! + DoSolarRad((int)(LightValue * cumulus.LuxToWM2), dateTime); + idx += 4; + break; + case 0x16: //UV (µW/cm²) - what use is this! + idx += 2; + break; + case 0x17: //UVI (0-15 index) + DoUV(data[idx], dateTime); + idx += 1; + break; + case 0x18: //Date and time + idx += 7; + break; + case 0x19: //Day max wind(m/s) + idx += 2; + break; + case 0x1A: //Temperature 1(℃) + case 0x1B: //Temperature 2(℃) + case 0x1C: //Temperature 3(℃) + case 0x1D: //Temperature 4(℃) + case 0x1E: //Temperature 5(℃) + case 0x1F: //Temperature 6(℃) + case 0x20: //Temperature 7(℃) + case 0x21: //Temperature 8(℃) + chan = data[idx - 1] - 0x1A + 1; + tempInt16 = ConvertBigEndianInt16(data, idx); + DoExtraTemp(ConvertTempCToUser(tempInt16 / 10.0), chan); + idx += 2; + break; + case 0x22: //Humidity 1, 0-100% + case 0x23: //Humidity 2, 0-100% + case 0x24: //Humidity 3, 0-100% + case 0x25: //Humidity 4, 0-100% + case 0x26: //Humidity 5, 0-100% + case 0x27: //Humidity 6, 0-100% + case 0x28: //Humidity 7, 0-100% + case 0x29: //Humidity 9, 0-100% + chan = data[idx - 1] - 0x22 + 1; + DoExtraHum(data[idx], chan); + idx += 1; + break; + case 0x2B: //Soil Temperature1 (℃) + case 0x2D: //Soil Temperature2 (℃) + case 0x2F: //Soil Temperature3 (℃) + case 0x31: //Soil Temperature4 (℃) + case 0x33: //Soil Temperature5 (℃) + case 0x35: //Soil Temperature6 (℃) + case 0x37: //Soil Temperature7 (℃) + case 0x39: //Soil Temperature8 (℃) + case 0x3B: //Soil Temperature9 (℃) + case 0x3D: //Soil Temperature10 (℃) + case 0x3F: //Soil Temperature11 (℃) + case 0x41: //Soil Temperature12 (℃) + case 0x43: //Soil Temperature13 (℃) + case 0x45: //Soil Temperature14 (℃) + case 0x47: //Soil Temperature15 (℃) + case 0x49: //Soil Temperature16 (℃) + // figure out the channel number + chan = data[idx - 1] - 0x2B + 2; // -> 2,4,6,8... + chan /= 2; // -> 1,2,3,4... + tempInt16 = ConvertBigEndianInt16(data, idx); + DoSoilTemp(ConvertTempCToUser(tempInt16 / 10.0), chan); + idx += 2; + break; + case 0x2C: //Soil Moisture1 (%) + case 0x2E: //Soil Moisture2 (%) + case 0x30: //Soil Moisture3 (%) + case 0x32: //Soil Moisture4 (%) + case 0x34: //Soil Moisture5 (%) + case 0x36: //Soil Moisture6 (%) + case 0x38: //Soil Moisture7 (%) + case 0x3A: //Soil Moisture8 (%) + case 0x3C: //Soil Moisture9 (%) + case 0x3E: //Soil Moisture10 (%) + case 0x40: //Soil Moisture11 (%) + case 0x42: //Soil Moisture12 (%) + case 0x44: //Soil Moisture13 (%) + case 0x46: //Soil Moisture14 (%) + case 0x48: //Soil Moisture15 (%) + case 0x4A: //Soil Moisture16 (%) + // figure out the channel number + chan = data[idx - 1] - 0x2C + 2; // -> 2,4,6,8... + chan /= 2; // -> 1,2,3,4... + DoSoilMoisture(data[idx], chan); + idx += 1; + break; + case 0x4C: //All sensor lowbatt 16 char + // This has been deprecated since v1.6.5 - now use CMD_READ_SENSOR_ID_NEW + if (tenMinuteChanged && fwVersion.CompareTo(new Version("1.6.5")) >= 0) + { + batteryLow = batteryLow || DoBatteryStatus(data, idx); + } + idx += 16; + break; + case 0x2A: //PM2.5 Air Quality Sensor(μg/m3) + tempUint16 = ConvertBigEndianUInt16(data, idx); + DoAirQuality(tempUint16 / 10.0, 1); + idx += 2; + break; + case 0x4D: //for pm25_ch1 + case 0x4E: //for pm25_ch2 + case 0x4F: //for pm25_ch3 + case 0x50: //for pm25_ch4 + chan = data[idx - 1] - 0x4D + 1; + tempUint16 = ConvertBigEndianUInt16(data, idx); + DoAirQualityAvg(tempUint16 / 10.0, chan); + idx += 2; + break; + case 0x51: //PM2.5 ch_2 Air Quality Sensor(μg/m3) + 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); + DoAirQuality(tempUint16 / 10.0, chan); + idx += 2; + break; + case 0x58: //Leak ch1 + case 0x59: //Leak ch2 + case 0x5A: //Leak ch3 + case 0x5B: //Leak ch4 + chan = data[idx - 1] - 0x58 + 1; + DoLeakSensor(data[idx], chan); + idx += 1; + break; + case 0x60: //Lightning dist (1-40km) + // Sends a default value of 255km until the first strike is detected + newLightningDistance = data[idx] == 0xFF ? 999 : ConvertKmtoUserUnits(data[idx]); + idx += 1; + break; + case 0x61: //Lightning time (UTC) + // Sends a default value until the first strike is detected of 0xFFFFFFFF + tempUint32 = ConvertBigEndianUInt32(data, idx); + if (tempUint32 == 0xFFFFFFFF) + { + newLightningTime = new DateTime(1900, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + } + else + { + var dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + dtDateTime = dtDateTime.AddSeconds(tempUint32).ToLocalTime(); + //cumulus.LogDebugMessage($"Lightning time={dtDateTime}"); + newLightningTime = dtDateTime; + } + idx += 4; + break; + case 0x62: //Lightning strikes today + tempUint32 = ConvertBigEndianUInt32(data, idx); + //cumulus.LogDebugMessage($"Lightning count={tempUint32}"); + LightningStrikesToday = (int)tempUint32; + idx += 4; + break; + // user temp = WH34 8 channel Soil or Water temperature sensors + case 0x63: // user temp ch1 (°C) + case 0x64: // user temp ch2 (°C) + case 0x65: // user temp ch3 (°C) + case 0x66: // user temp ch4 (°C) + case 0x67: // user temp ch5 (°C) + case 0x68: // user temp ch6 (°C) + case 0x69: // user temp ch7 (°C) + case 0x6A: // user temp ch8 (°C) + chan = data[idx - 1] - 0x63 + 2; // -> 2,4,6,8... + chan /= 2; // -> 1,2,3,4... + tempInt16 = ConvertBigEndianInt16(data, idx); + DoUserTemp(ConvertTempCToUser(tempInt16 / 10.0), chan); + + // Firmware version 1.5.9 uses 2 data bytes, 1.6.0+ uses 3 data bytes + if (fwVersion.CompareTo(new Version("1.6.0")) >= 0) + { + if (tenMinuteChanged) + { + var volts = TestBattery4V(data[idx + 2]); + if (volts <= 1.2) + { + batteryLow = true; + cumulus.LogMessage($"WH34 channel #{chan} battery LOW = {volts}V"); + } + else + { + cumulus.LogDebugMessage($"WH34 channel #{chan} battery OK = {volts}V"); + } + } + idx += 3; + } + else + { + idx += 2; + } + break; + case 0x6B: //WH34 User temperature battery (8 channels) - No longer used in firmware 1.6.0+ + if (tenMinuteChanged) + { + batteryLow = batteryLow || DoWH34BatteryStatus(data, idx); + } + idx += 8; + break; + case 0x70: // WH45 CO₂ + batteryLow = batteryLow || DoCO2Decode(data, idx); + idx += 16; + break; + case 0x71: // Ambient ONLY - AQI + //TODO: Not doing anything with this yet + //idx += 2; // SEEMS TO BE VARIABLE + cumulus.LogDebugMessage("Found a device 0x71 - Ambient AQI. No decode for this yet"); + // We will have lost our place now, so bail out + idx = size; + break; + case 0x72: // WH35 Leaf Wetness ch1 + case 0x73: // WH35 Leaf Wetness ch2 + case 0x74: // WH35 Leaf Wetness ch3 + case 0x75: // WH35 Leaf Wetness ch4 + case 0x76: // WH35 Leaf Wetness ch5 + case 0x77: // WH35 Leaf Wetness ch6 + case 0x78: // WH35 Leaf Wetness ch7 + case 0x79: // WH35 Leaf Wetness ch8 + chan = data[idx - 1] - 0x72 + 2; // -> 2,4,6,8... + chan /= 2; // -> 1,2,3,4... + DoLeafWetness(data[idx], chan); + idx += 1; + break; + default: + cumulus.LogDebugMessage($"Error: Unknown sensor id found = {data[idx - 1]}, at position = {idx - 1}"); + // We will have lost our place now, so bail out + idx = size; + break; + } + } while (idx < size); + + // Now do the stuff that requires more than one input parameter + + // Only set the lightning time/distance if it is newer than what we already have - the GW1000 seems to reset this value + if (newLightningTime > LightningTime) + { + LightningTime = newLightningTime; + if (newLightningDistance != 999) + LightningDistance = newLightningDistance; + } + + // Process outdoor temperature here, as GW1000 currently does not supply Dew Point so we have to calculate it in DoOutdoorTemp() + if (outdoortemp > -999) + DoOutdoorTemp(ConvertTempCToUser(outdoortemp), dateTime); + + // Same for extra T/H sensors + for (var i = 1; i <= 8; i++) + { + if (ExtraHum[i] > 0) + { + var dp = MeteoLib.DewPoint(ConvertUserTempToC(ExtraTemp[i]), ExtraHum[i]); + ExtraDewPoint[i] = ConvertTempCToUser(dp); + } + } + + if (tenMinuteChanged) tenMinuteChanged = false; + + + //cumulus.BatteryLowAlarm.Triggered = batteryLow; + + // No average in the live data, so use last value from cumulus + if (windSpeedLast > -999 && windDirLast > -999) + { + DoWind(windSpeedLast, windDirLast, WindAverage / cumulus.Calib.WindSpeed.Mult, dateTime); + //DoWind(windSpeedLast, windDirLast, windSpeedLast, dateTime); + } + + if (gustLastCal > RecentMaxGust) + { + cumulus.LogDebugMessage("Setting max gust from current value: " + gustLastCal.ToString(cumulus.WindFormat)); + CheckHighGust(gustLastCal, windDirLast, dateTime); + + // add to recent values so normal calculation includes this value + WindRecent[nextwind].Gust = gustLast; // use uncalibrated value + WindRecent[nextwind].Speed = WindAverage / cumulus.Calib.WindSpeed.Mult; + WindRecent[nextwind].Timestamp = dateTime; + nextwind = (nextwind + 1) % MaxWindRecent; + + RecentMaxGust = gustLastCal; + } + + if (rainLast > -999 && rainRateLast > -999) + { + DoRain(rainLast, rainRateLast, dateTime); + } + + if (outdoortemp > -999) + { + if (ConvertUserWindToMS(WindAverage) < 1.5) + { + DoWindChill(OutdoorTemperature, dateTime); + } + else + { + // calculate wind chill from calibrated C temp and calibrated wind in KPH + DoWindChill(ConvertTempCToUser(MeteoLib.WindChill(ConvertUserTempToC(OutdoorTemperature), ConvertUserWindToKPH(WindAverage))), dateTime); + } + + DoApparentTemp(dateTime); + DoFeelsLike(dateTime); + DoHumidex(dateTime); + } + + DoForecast("", false); + + UpdateStatusPanel(dateTime); + UpdateMQTT(); + + dataReceived = true; + DataStopped = false; + cumulus.DataStoppedAlarm.Triggered = false; + } + else + { + cumulus.LogMessage("GetLiveData: Invalid response"); + } + } + catch (Exception ex) + { + cumulus.LogMessage("GetLiveData: Error - " + ex.Message); + } + } + + private void GetSystemInfo() + { + cumulus.LogMessage("Reading GW1000 system info"); + + var data = DoCommand(Commands.CMD_READ_SSSS); + + // expected response + // 0 - 0xff - header + // 1 - 0xff - header + // 2 - 0x30 - system info + // 3 - 0x?? - size of response + // 4 - frequency - 0=433, 1=868MHz, 2=915MHz, 3=920MHz + // 5 - sensor type - 0=WH24, 1=WH65 + // 6-9 - UTC time + // 10 - timezone index (?) + // 11 - DST 0-1 - false/true + // 12 - 0x?? - checksum + + if (data.Length != 13) + { + cumulus.LogMessage("Unexpected response to System Info!"); + return; + } + try + { + string freq; + if (data[4] == 0) + freq = "433MHz"; + else if (data[4] == 1) + freq = "868MHz"; + else if (data[4] == 2) + freq = "915MHz"; + else if (data[4] == 3) + freq = "920MHz"; + else + freq = $"Unknown [{data[4]}]"; + + mainSensor = data[5] == 0 ? "WH24" : "WH65"; + + var unix = ConvertBigEndianUInt32(data, 6); + var date = Utils.FromUnixTime(unix); + var dst = data[11] != 0; + + cumulus.LogMessage($"GW1000 Info: freqency: {freq}, main sensor: {mainSensor}, date/time: {date:F}, Automatic DST adjustment: {dst}"); + } + catch (Exception ex) + { + cumulus.LogMessage("Error processing System Info: " + ex.Message); + } + } + + private byte[] DoCommand(Commands command) + { + var buffer = new byte[2028]; + var bytesRead = 0; + var cmdName = command.ToString(); + + var payload = new CommandPayload(command); + var tmrComm = new CommTimer(); + + var bytes = payload.Serialise(); + + try + { + stream.Write(bytes, 0, bytes.Length); + + tmrComm.Start(1000); + + while (tmrComm.timedout == false) + { + if (stream.DataAvailable) + { + while (stream.DataAvailable) + { + // Read the current character + var ch = stream.ReadByte(); + if (ch > -1) + { + buffer[bytesRead] = (byte)ch; + bytesRead++; + //cumulus.LogMessage("Received " + ch.ToString("X2")); + } + } + tmrComm.Stop(); + } + else + { + Thread.Sleep(20); + } + } + + // Check the response is to our command and checksum is OK + if (bytesRead == 0 || buffer[2] != (byte)command || !ChecksumOk(buffer, (int)Enum.Parse(typeof(CommandRespSize), cmdName))) + { + if (bytesRead > 0) + { + cumulus.LogMessage($"DoCommand({cmdName}): Invalid response"); + cumulus.LogDebugMessage($"command resp={buffer[2]}, checksum=" + (ChecksumOk(buffer, (int)Enum.Parse(typeof(CommandRespSize), cmdName)) ? "OK" : "BAD")); + cumulus.LogDataMessage("Received 0x" + BitConverter.ToString(buffer, 0, bytesRead - 1)); + } + else + { + cumulus.LogMessage($"DoCommand({cmdName}): No response received"); + } + return null; + } + else + { + cumulus.LogDebugMessage($"DoCommand({cmdName}): Valid response"); + } + } + catch (Exception ex) + { + cumulus.LogMessage($"DoCommand({cmdName}): Error - " + ex.Message); + connectedOk = socket.Connected; + } + // Copy the data we want out of the buffer + if (bytesRead > 0) + { + var data = new byte[bytesRead]; + Array.Copy(buffer, data, data.Length); + cumulus.LogDataMessage("Received 0x" + BitConverter.ToString(data)); + return data; + } + + return null; + } + + private bool DoCO2Decode(byte[] data, int index) + { + bool batteryLow = false; + int idx = index; + cumulus.LogDebugMessage("WH45 CO₂: Decoding..."); + //CO2Data co2Data = (CO2Data)RawDeserialize(data, index, typeof(CO2Data)); + + try + { + CO2_temperature = ConvertTempCToUser(ConvertBigEndianInt16(data, idx) / 10.0); + idx += 2; + CO2_humidity = data[idx++]; + CO2_pm10 = ConvertBigEndianUInt16(data, idx) / 10.0; + idx += 2; + CO2_pm10_24h = ConvertBigEndianUInt16(data, idx) / 10.0; + idx += 2; + CO2_pm2p5 = ConvertBigEndianUInt16(data, idx) / 10.0; + idx += 2; + CO2_pm2p5_24h = ConvertBigEndianUInt16(data, idx) / 10.0; + idx += 2; + CO2 = ConvertBigEndianUInt16(data, idx); + idx += 2; + CO2_24h = ConvertBigEndianUInt16(data, idx); + idx += 2; + var batt = TestBattery3(data[idx]); + var msg = $"WH45 CO₂: temp={CO2_temperature.ToString(cumulus.TempFormat)}, hum={CO2_humidity}, pm10={CO2_pm10:F1}, pm10_24h={CO2_pm10_24h:F1}, pm2.5={CO2_pm2p5:F1}, pm2.5_24h={CO2_pm2p5_24h:F1}, CO₂={CO2}, CO₂_24h={CO2_24h}"; + if (tenMinuteChanged) + { + if (batt == "Low") + { + batteryLow = true; + msg += $", Battery={batt}"; + } + else + { + msg += $", Battery={batt}"; + } + } + cumulus.LogDebugMessage(msg); + } + catch (Exception ex) + { + cumulus.LogMessage("DoCO2Decode: Error - " + ex.Message); + } + + return batteryLow; + } + + private bool DoBatteryStatus(byte[] data, int index) + { + bool batteryLow = false; + BatteryStatus status = (BatteryStatus)RawDeserialize(data, index, typeof(BatteryStatus)); + cumulus.LogDebugMessage("battery status..."); + + var str = "singles>" + + " wh24=" + TestBattery1(status.single, (byte)SigSen.Wh24) + + " wh25=" + TestBattery1(status.single, (byte)SigSen.Wh25) + + " wh26=" + TestBattery1(status.single, (byte)SigSen.Wh26) + + " wh40=" + TestBattery1(status.single, (byte)SigSen.Wh40); + if (str.Contains("Low")) + { + batteryLow = true; + cumulus.LogMessage(str); + } + else + cumulus.LogDebugMessage(str); + + str = "wh31>" + + " ch1=" + TestBattery1(status.wh31, (byte)Wh31Ch.Ch1) + + " ch2=" + TestBattery1(status.wh31, (byte)Wh31Ch.Ch2) + + " ch3=" + TestBattery1(status.wh31, (byte)Wh31Ch.Ch3) + + " ch4=" + TestBattery1(status.wh31, (byte)Wh31Ch.Ch4) + + " ch5=" + TestBattery1(status.wh31, (byte)Wh31Ch.Ch5) + + " ch6=" + TestBattery1(status.wh31, (byte)Wh31Ch.Ch6) + + " ch7=" + TestBattery1(status.wh31, (byte)Wh31Ch.Ch7) + + " ch8=" + TestBattery1(status.wh31, (byte)Wh31Ch.Ch8); + if (str.Contains("Low")) + { + batteryLow = true; + cumulus.LogMessage(str); + } + else + cumulus.LogDebugMessage(str); + + str = "wh41>" + + " ch1=" + TestBattery2(status.wh41, 0x0F) + + " ch2=" + TestBattery2((UInt16)(status.wh41 >> 4), 0x0F) + + " ch3=" + TestBattery2((UInt16)(status.wh41 >> 8), 0x0F) + + " ch4=" + TestBattery2((UInt16)(status.wh41 >> 12), 0x0F); + if (str.Contains("Low")) + { + batteryLow = true; + cumulus.LogMessage(str); + } + else + cumulus.LogDebugMessage(str); + + str = "wh51>" + + " ch1=" + TestBattery1(status.wh51, (byte)Wh51Ch.Ch1) + + " ch2=" + TestBattery1(status.wh31, (byte)Wh51Ch.Ch2) + + " ch3=" + TestBattery1(status.wh31, (byte)Wh51Ch.Ch3) + + " ch4=" + TestBattery1(status.wh31, (byte)Wh51Ch.Ch4) + + " ch5=" + TestBattery1(status.wh31, (byte)Wh51Ch.Ch5) + + " ch6=" + TestBattery1(status.wh31, (byte)Wh51Ch.Ch6) + + " ch7=" + TestBattery1(status.wh31, (byte)Wh51Ch.Ch7) + + " ch8=" + TestBattery1(status.wh31, (byte)Wh51Ch.Ch8); + if (str.Contains("Low")) + { + batteryLow = true; + cumulus.LogMessage(str); + } + else + cumulus.LogDebugMessage(str); + + str = "wh57> " + TestBattery3(status.wh57); + if (str.Contains("Low")) + { + batteryLow = true; + cumulus.LogMessage(str); + } + else + cumulus.LogDebugMessage(str); + + str = "wh68> " + TestBattery4S(status.wh68) + " - " + TestBattery4V(status.wh68) + "V"; + if (str.Contains("Low")) + { + batteryLow = true; + cumulus.LogMessage(str); + } + else + cumulus.LogDebugMessage(str); + + str = "wh80> " + TestBattery4S(status.wh80) + " - " + TestBattery4V(status.wh80) + "V"; + if (str.Contains("Low")) + { + batteryLow = true; + cumulus.LogMessage(str); + } + else + cumulus.LogDebugMessage(str); + + str = "wh45> " + TestBattery3(status.wh45); + if (str.Contains("Low")) + { + batteryLow = true; + cumulus.LogMessage(str); + } + else + cumulus.LogDebugMessage(str); + + str = "wh55>" + + " ch1=" + TestBattery3(status.wh55_ch1) + + " ch2=" + TestBattery3(status.wh55_ch2) + + " ch3=" + TestBattery3(status.wh55_ch3) + + " ch4=" + TestBattery3(status.wh55_ch4); + if (str.Contains("Low")) + { + batteryLow = true; + cumulus.LogMessage(str); + } + else + cumulus.LogDebugMessage(str); + + return batteryLow; + } + + private bool DoWH34BatteryStatus(byte[] data, int index) + { + // No longer used in firmware 1.6.0+ + cumulus.LogDebugMessage("WH34 battery status..."); + var str = "wh34>" + + " ch1=" + TestBattery3(data[index + 1]) + + " ch2=" + TestBattery3(data[index + 2]) + + " ch3=" + TestBattery3(data[index + 3]) + + " ch4=" + TestBattery3(data[index + 4]) + + " ch5=" + TestBattery3(data[index + 5]) + + " ch6=" + TestBattery3(data[index + 6]) + + " ch7=" + TestBattery3(data[index + 7]) + + " ch8=" + TestBattery3(data[index + 8]); + + cumulus.LogDebugMessage(str); + + return str.Contains("Low"); + } + + private static string TestBattery1(byte value, byte mask) + { + return (value & mask) == 0 ? "OK" : "Low"; + } + + private static string TestBattery1(UInt16 value, UInt16 mask) + { + return (value & mask) == 0 ? "OK" : "Low"; + } + + private static string TestBattery2(UInt16 value, UInt16 mask) + { + return (value & mask) > 1 ? "OK" : "Low"; + } + + private static string TestBattery3(byte value) + { + return value > 1 ? "OK" : "Low"; + } + private static double TestBattery4V(byte value) + { + return value * 0.02; + } + private static string TestBattery4S(byte value) + { + return value * 0.02 > 1.2 ? "OK" : "Low"; + } + + private static object RawDeserialize(byte[] rawData, int position, Type anyType) + { + int rawsize = Marshal.SizeOf(anyType); + if (rawsize > rawData.Length) + return null; + IntPtr buffer = Marshal.AllocHGlobal(rawsize); + Marshal.Copy(rawData, position, buffer, rawsize); + object retobj = Marshal.PtrToStructure(buffer, anyType); + Marshal.FreeHGlobal(buffer); + return retobj; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + private struct CommandPayload + { + private readonly ushort Header; + private readonly byte Command; + private readonly byte Size; + //public byte[] Data; + private readonly byte Checksum; + + //public CommandPayload(byte command, byte[] data) : this() + public CommandPayload(Commands command) : this() + { + //ushort header; + Header = 0xffff; + Command = (byte)command; + Size = (byte)(Marshal.SizeOf(typeof(CommandPayload)) - 3); + Checksum = (byte)(Command + Size); + } + // This will be serialised in little endian format + public byte[] Serialise() + { + // allocate a byte array for the struct data + var buffer = new byte[Marshal.SizeOf(typeof(CommandPayload))]; + + // Allocate a GCHandle and get the array pointer + var gch = GCHandle.Alloc(buffer, GCHandleType.Pinned); + var pBuffer = gch.AddrOfPinnedObject(); + + // copy data from struct to array and unpin the gc pointer + Marshal.StructureToPtr(this, pBuffer, false); + gch.Free(); + + return buffer; + } + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + private struct BatteryStatus + { + public byte single; + public byte wh31; + public UInt16 wh51; + public byte wh57; + public byte wh68; + public byte wh80; + public byte wh45; + public UInt16 wh41; + public byte wh55_ch1; + public byte wh55_ch2; + public byte wh55_ch3; + public byte wh55_ch4; + } + + /* + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + private struct BatteryStatusWH34 + { + public byte single; + public byte ch1; + public byte ch2; + public byte ch3; + public byte ch4; + public byte ch5; + public byte ch6; + public byte ch7; + public byte ch8; + } + */ + + /* + private struct SensorInfo + { + string type; + int id; + int signal; + int battery; + bool present; + } + */ + + /* + private class Sensors + { + SensorInfo single { get; set; } + SensorInfo wh26; + SensorInfo wh31; + SensorInfo wh40; + SensorInfo wh41; + SensorInfo wh51; + SensorInfo wh65; + SensorInfo wh68; + SensorInfo wh80; + public Sensors() + { + } + } + + private struct CO2Data + { + public Int16 temp; // °C x10 + public byte hum; // % + public UInt16 pm10; // ug/m3 x10 + public UInt16 pm10_24hr; // ug/m3 x10 + public UInt16 pm2p5; // ug/m3 x10 + public UInt16 pm2p5_24hr; // ug/m3 x10 + public UInt16 co2; // ppm + public UInt16 co2_24hr; // ppm + public byte batt; // 0-5 + } + */ + + private class Discovery + { + public List IP { get; set; } + public List Name { get; set; } + public List Mac { get; set; } + + public Discovery() + { + IP = new List(); + Name = new List(); + Mac = new List(); + } + } + + private bool ChecksumOk(byte[] data, int lengthBytes) + { + ushort size; + + // general response 1 byte size 2 byte size + // 0 - 0xff - header 0 - 0xff - header + // 1 - 0xff 1 - 0xff + // 2 - command 2 - command + // 3 - total size of response 3 - size1 + // 4-X - data 4 - size2 + // X+1 - checksum 5-X - data + // X+1 - checksum + + if (lengthBytes == 1) + { + size = (ushort)data[3]; + } + else + { + size = ConvertBigEndianUInt16(data, 3); + } + + byte checksum = (byte)(data[2] + data[3]); + for (var i = 4; i <= size; i++) + { + checksum += data[i]; + } + + if (checksum != data[size + 1]) + { + cumulus.LogMessage("Bad checksum"); + return false; + } + + return true; + } + + private static UInt16 ConvertBigEndianUInt16(byte[] array, int start) + { + return (UInt16)(array[start] << 8 | array[start+1]); + } + + private static Int16 ConvertBigEndianInt16(byte[] array, int start) + { + return (Int16)((array[start] << 8) + array[start + 1]); + } + + private static UInt32 ConvertBigEndianUInt32(byte[] array, int start) + { + return (UInt32)(array[start++] << 24 | array[start++] << 16 | array[start++] << 8 | array[start]); + } + + private void DataTimeout(object source, ElapsedEventArgs e) + { + if (dataReceived) + { + dataReceived = false; + DataStopped = false; + cumulus.DataStoppedAlarm.Triggered = false; + } + else + { + cumulus.LogMessage($"ERROR: No data received from the GW1000 for {tmrDataWatchdog.Interval / 1000} seconds"); + DataStopped = true; + cumulus.DataStoppedAlarm.LastError = $"No data received from the GW1000 for {tmrDataWatchdog.Interval / 1000} seconds"; + cumulus.DataStoppedAlarm.Triggered = true; + DoDiscovery(); + } + } + } +} diff --git a/CumulusMX/MysqlSettings.cs b/CumulusMX/MysqlSettings.cs index f03b805a..e7a04ea5 100644 --- a/CumulusMX/MysqlSettings.cs +++ b/CumulusMX/MysqlSettings.cs @@ -1,357 +1,360 @@ -using System; -using System.IO; -using System.Net; -using MySqlConnector; -using ServiceStack; -using Unosquare.Labs.EmbedIO; - -namespace CumulusMX -{ - public class MysqlSettings - { - private readonly Cumulus cumulus; - private readonly string optionsFile; - private readonly string schemaFile; - - public MysqlSettings(Cumulus cumulus) - { - this.cumulus = cumulus; - optionsFile = cumulus.AppDir + "interface" + Path.DirectorySeparatorChar + "json" + Path.DirectorySeparatorChar + "MySqlOptions.json"; - schemaFile = cumulus.AppDir + "interface" + Path.DirectorySeparatorChar + "json" + Path.DirectorySeparatorChar + "MySqlSchema.json"; - } - - public string GetAlpacaFormData() - { - var server = new JsonMysqlSettingsServer() - { - database = cumulus.MySqlConnSettings.Database, - host = cumulus.MySqlConnSettings.Server, - pass = cumulus.MySqlConnSettings.Password, - port = cumulus.MySqlConnSettings.Port, - user = cumulus.MySqlConnSettings.UserID - }; - - var monthly = new JsonMysqlSettingsMonthly() - { - table = cumulus.MySqlMonthlyTable - }; - - var reten = cumulus.MySqlRealtimeRetention.Split(' '); - var retenVal = string.IsNullOrEmpty(reten[0]) ? 7 : int.Parse(reten[0]); - var retenUnit = reten.Length > 1 && !string.IsNullOrEmpty(reten[1]) ? reten[1].ToUpper().TrimEnd('S') : "DAY"; - - var realtime = new JsonMysqlSettingsRealtime() - { - retentionVal = retenVal, - retentionUnit = retenUnit, - table = cumulus.MySqlRealtimeTable - }; - - var dayfile = new JsonMysqlSettingsDayfile() - { - table = cumulus.MySqlDayfileTable - }; - - var customseconds = new JsonMysqlSettingsCustomSeconds() - { - command = cumulus.CustomMySqlSecondsCommandString, - interval = cumulus.CustomMySqlSecondsInterval - }; - - var customminutes = new JsonMysqlSettingsCustomMinutes() - { - command = cumulus.CustomMySqlMinutesCommandString, - intervalindex = cumulus.CustomMySqlMinutesIntervalIndex - }; - - var customrollover = new JsonMysqlSettingsCustomRollover() - { - command = cumulus.CustomMySqlRolloverCommandString, - }; - - var data = new JsonMysqlSettings() - { - accessible = cumulus.ProgramOptions.EnableAccessibility, - server = server, - monthenabled = cumulus.MonthlyMySqlEnabled, - monthly = monthly, - realtimeenabled = cumulus.RealtimeMySqlEnabled, - realtime = realtime, - dayenabled = cumulus.DayfileMySqlEnabled, - dayfile = dayfile, - custsecsenabled = cumulus.CustomMySqlSecondsEnabled, - customseconds = customseconds, - custminsenabled = cumulus.CustomMySqlMinutesEnabled, - customminutes = customminutes, - custrollenabled = cumulus.CustomMySqlRolloverEnabled, - customrollover = customrollover - }; - - return data.ToJson(); - } - - public string GetAlpacaFormOptions() - { - using (StreamReader sr = new StreamReader(optionsFile)) - { - string json = sr.ReadToEnd(); - return json; - } - } - - public string GetAlpacaFormSchema() - { - using (StreamReader sr = new StreamReader(schemaFile)) - { - string json = sr.ReadToEnd(); - return json; - } - } - - //public object UpdateMysqlConfig(HttpListenerContext context) - public object UpdateConfig(IHttpContext context) - { - string json = ""; - JsonMysqlSettings settings; - try - { - var data = new StreamReader(context.Request.InputStream).ReadToEnd(); - - // Start at char 5 to skip the "json:" prefix - json = WebUtility.UrlDecode(data.Substring(5)); - - // de-serialize it to the settings structure - settings = json.FromJson(); - } - catch (Exception ex) - { - var msg = "Error deserializing MySQL Settings JSON: " + ex.Message; - cumulus.LogMessage(msg); - cumulus.LogDebugMessage("MySQL Data: " + json); - context.Response.StatusCode = 500; - return msg; - } - - - // process the settings - try - { - cumulus.LogMessage("Updating MySQL settings"); - - // server - cumulus.MySqlConnSettings.Server = settings.server.host; - if (settings.server.port > 0 && settings.server.port < 65536) - { - cumulus.MySqlConnSettings.Port = settings.server.port; - } - else - { - cumulus.MySqlConnSettings.Port = 3306; - } - cumulus.MySqlConnSettings.Database = settings.server.database; - cumulus.MySqlConnSettings.UserID = settings.server.user; - cumulus.MySqlConnSettings.Password = settings.server.pass; - //monthly - cumulus.MonthlyMySqlEnabled = settings.monthenabled; - if (cumulus.MonthlyMySqlEnabled) - { - cumulus.MySqlMonthlyTable = String.IsNullOrWhiteSpace(settings.monthly.table) ? "Monthly" : settings.monthly.table; - } - //realtime - cumulus.RealtimeMySqlEnabled = settings.realtimeenabled; - if (cumulus.RealtimeMySqlEnabled) - { - cumulus.MySqlRealtimeRetention = settings.realtime.retentionVal + " " + settings.realtime.retentionUnit; - cumulus.MySqlRealtimeTable = String.IsNullOrWhiteSpace(settings.realtime.table) ? "Realtime" : settings.realtime.table; - } - //dayfile - cumulus.DayfileMySqlEnabled = settings.dayenabled; - if (cumulus.DayfileMySqlEnabled) - { - cumulus.MySqlDayfileTable = String.IsNullOrWhiteSpace(settings.dayfile.table) ? "Dayfile" : settings.dayfile.table; - } - // custom seconds - cumulus.CustomMySqlSecondsEnabled = settings.custsecsenabled; - if (cumulus.CustomMySqlSecondsEnabled) - { - cumulus.CustomMySqlSecondsCommandString = settings.customseconds.command; - cumulus.CustomMySqlSecondsInterval = settings.customseconds.interval; - } - // custom minutes - cumulus.CustomMySqlMinutesEnabled = settings.custminsenabled; - if (cumulus.CustomMySqlMinutesEnabled) - { - cumulus.CustomMySqlMinutesCommandString = settings.customminutes.command; - cumulus.CustomMySqlMinutesIntervalIndex = settings.customminutes.intervalindex; - if (cumulus.CustomMySqlMinutesIntervalIndex >= 0 && cumulus.CustomMySqlMinutesIntervalIndex < cumulus.FactorsOf60.Length) - { - cumulus.CustomMySqlMinutesInterval = cumulus.FactorsOf60[cumulus.CustomMySqlMinutesIntervalIndex]; - } - else - { - cumulus.CustomMySqlMinutesInterval = 10; - } - } - // custom rollover - cumulus.CustomMySqlRolloverEnabled = settings.custrollenabled; - if (cumulus.CustomMySqlRolloverEnabled) - { - cumulus.CustomMySqlRolloverCommandString = settings.customrollover.command; - } - - // Save the settings - cumulus.WriteIniFile(); - - cumulus.MonthlyMySqlConn.ConnectionString = cumulus.MySqlConnSettings.ToString(); - - cumulus.SetMonthlySqlCreateString(); - cumulus.SetStartOfMonthlyInsertSQL(); - - cumulus.SetDayfileSqlCreateString(); - cumulus.SetStartOfDayfileInsertSQL(); - - cumulus.RealtimeSqlConn.ConnectionString = cumulus.MySqlConnSettings.ToString(); - - cumulus.SetRealtimeSqlCreateString(); - cumulus.SetStartOfRealtimeInsertSQL(); - - cumulus.CustomMysqlSecondsConn.ConnectionString = cumulus.MySqlConnSettings.ToString(); - cumulus.CustomMysqlSecondsTimer.Interval = cumulus.CustomMySqlSecondsInterval*1000; - cumulus.CustomMysqlSecondsTimer.Enabled = cumulus.CustomMySqlSecondsEnabled; - - cumulus.CustomMysqlMinutesConn.ConnectionString = cumulus.MySqlConnSettings.ToString(); - - cumulus.CustomMysqlRolloverConn.ConnectionString = cumulus.MySqlConnSettings.ToString(); - - context.Response.StatusCode = 200; - } - catch (Exception ex) - { - var msg = "Error processing settings: " + ex.Message; - cumulus.LogMessage(msg); - context.Response.StatusCode = 500; - return msg; - } - return "success"; - } - - private string CreateMySQLTable(string createSQL) - { - string res; - using (var mySqlConn = new MySqlConnection(cumulus.MySqlConnSettings.ToString())) - using (MySqlCommand cmd = new MySqlCommand(createSQL, mySqlConn)) - { - cumulus.LogMessage($"MySQL Create Table: {createSQL}"); - - try - { - mySqlConn.Open(); - int aff = cmd.ExecuteNonQuery(); - cumulus.LogMessage($"MySQL Create Table: {aff} items were affected."); - res = "Database table created successfully"; - } - catch (Exception ex) - { - cumulus.LogMessage("MySQL Create Table: Error encountered during MySQL operation."); - cumulus.LogMessage(ex.Message); - res = "Error: " + ex.Message; - } - finally - { - try - { - mySqlConn.Close(); - } - catch - { } - } - } - return res; - } - - //public string CreateMonthlySQL(HttpListenerContext context) - public string CreateMonthlySQL(IHttpContext context) - { - context.Response.StatusCode = 200; - string json = "{\"result\":\"" + CreateMySQLTable(cumulus.CreateMonthlySQL) + "\"}"; - return json; - } - - //public string CreateDayfileSQL(HttpListenerContext context) - public string CreateDayfileSQL(IHttpContext context) - { - context.Response.StatusCode = 200; - string json = "{\"result\":\"" + CreateMySQLTable(cumulus.CreateDayfileSQL) + "\"}"; - return json; - } - - //public string CreateRealtimeSQL(HttpListenerContext context) - public string CreateRealtimeSQL(IHttpContext context) - { - context.Response.StatusCode = 200; - string json = "{\"result\":\"" + CreateMySQLTable(cumulus.CreateRealtimeSQL) + "\"}"; - return json; - } - } - - public class JsonMysqlSettings - { - public bool accessible {get; set;} - public JsonMysqlSettingsServer server { get; set; } - public bool monthenabled { get; set; } - public JsonMysqlSettingsMonthly monthly { get; set; } - public bool realtimeenabled { get; set; } - public JsonMysqlSettingsRealtime realtime { get; set; } - public bool dayenabled { get; set; } - public JsonMysqlSettingsDayfile dayfile { get; set; } - public bool custsecsenabled { get; set; } - public JsonMysqlSettingsCustomSeconds customseconds { get; set; } - public bool custminsenabled { get; set; } - public JsonMysqlSettingsCustomMinutes customminutes { get; set; } - public bool custrollenabled { get; set; } - public JsonMysqlSettingsCustomRollover customrollover { get; set; } - } - - public class JsonMysqlSettingsServer - { - public string host { get; set; } - public uint port { get; set; } - public string user { get; set; } - public string pass { get; set; } - public string database { get; set; } - } - - public class JsonMysqlSettingsMonthly - { - public string table { get; set; } - } - - public class JsonMysqlSettingsRealtime - { - public string table { get; set; } - public int retentionVal { get; set; } - public string retentionUnit { get; set; } - } - - public class JsonMysqlSettingsDayfile - { - public string table { get; set; } - } - - public class JsonMysqlSettingsCustomSeconds - { - public string command { get; set; } - public int interval { get; set; } - } - - public class JsonMysqlSettingsCustomMinutes - { - public string command { get; set; } - public int intervalindex { get; set; } - } - - public class JsonMysqlSettingsCustomRollover - { - public string command { get; set; } - } -} +using System; +using System.IO; +using System.Net; +using MySqlConnector; +using ServiceStack; +using Unosquare.Labs.EmbedIO; + +namespace CumulusMX +{ + public class MysqlSettings + { + private readonly Cumulus cumulus; + private readonly string optionsFile; + private readonly string schemaFile; + + public MysqlSettings(Cumulus cumulus) + { + this.cumulus = cumulus; + optionsFile = cumulus.AppDir + "interface" + Path.DirectorySeparatorChar + "json" + Path.DirectorySeparatorChar + "MySqlOptions.json"; + schemaFile = cumulus.AppDir + "interface" + Path.DirectorySeparatorChar + "json" + Path.DirectorySeparatorChar + "MySqlSchema.json"; + } + + public string GetAlpacaFormData() + { + var server = new JsonMysqlSettingsServer() + { + database = cumulus.MySqlConnSettings.Database, + host = cumulus.MySqlConnSettings.Server, + pass = cumulus.MySqlConnSettings.Password, + port = cumulus.MySqlConnSettings.Port, + user = cumulus.MySqlConnSettings.UserID + }; + + var monthly = new JsonMysqlSettingsMonthly() + { + table = cumulus.MySqlMonthlyTable + }; + + var reten = cumulus.MySqlRealtimeRetention.Split(' '); + var retenVal = string.IsNullOrEmpty(reten[0]) ? 7 : int.Parse(reten[0]); + var retenUnit = reten.Length > 1 && !string.IsNullOrEmpty(reten[1]) ? reten[1].ToUpper().TrimEnd('S') : "DAY"; + + var realtime = new JsonMysqlSettingsRealtime() + { + retentionVal = retenVal, + retentionUnit = retenUnit, + table = cumulus.MySqlRealtimeTable, + limit1min = cumulus.RealtimeMySql1MinLimit + }; + + var dayfile = new JsonMysqlSettingsDayfile() + { + table = cumulus.MySqlDayfileTable + }; + + var customseconds = new JsonMysqlSettingsCustomSeconds() + { + command = cumulus.CustomMySqlSecondsCommandString, + interval = cumulus.CustomMySqlSecondsInterval + }; + + var customminutes = new JsonMysqlSettingsCustomMinutes() + { + command = cumulus.CustomMySqlMinutesCommandString, + intervalindex = cumulus.CustomMySqlMinutesIntervalIndex + }; + + var customrollover = new JsonMysqlSettingsCustomRollover() + { + command = cumulus.CustomMySqlRolloverCommandString, + }; + + var data = new JsonMysqlSettings() + { + accessible = cumulus.ProgramOptions.EnableAccessibility, + server = server, + monthenabled = cumulus.MonthlyMySqlEnabled, + monthly = monthly, + realtimeenabled = cumulus.RealtimeMySqlEnabled, + realtime = realtime, + dayenabled = cumulus.DayfileMySqlEnabled, + dayfile = dayfile, + custsecsenabled = cumulus.CustomMySqlSecondsEnabled, + customseconds = customseconds, + custminsenabled = cumulus.CustomMySqlMinutesEnabled, + customminutes = customminutes, + custrollenabled = cumulus.CustomMySqlRolloverEnabled, + customrollover = customrollover + }; + + return data.ToJson(); + } + + public string GetAlpacaFormOptions() + { + using (StreamReader sr = new StreamReader(optionsFile)) + { + string json = sr.ReadToEnd(); + return json; + } + } + + public string GetAlpacaFormSchema() + { + using (StreamReader sr = new StreamReader(schemaFile)) + { + string json = sr.ReadToEnd(); + return json; + } + } + + //public object UpdateMysqlConfig(HttpListenerContext context) + public object UpdateConfig(IHttpContext context) + { + string json = ""; + JsonMysqlSettings settings; + try + { + var data = new StreamReader(context.Request.InputStream).ReadToEnd(); + + // Start at char 5 to skip the "json:" prefix + json = WebUtility.UrlDecode(data.Substring(5)); + + // de-serialize it to the settings structure + settings = json.FromJson(); + } + catch (Exception ex) + { + var msg = "Error deserializing MySQL Settings JSON: " + ex.Message; + cumulus.LogMessage(msg); + cumulus.LogDebugMessage("MySQL Data: " + json); + context.Response.StatusCode = 500; + return msg; + } + + + // process the settings + try + { + cumulus.LogMessage("Updating MySQL settings"); + + // server + cumulus.MySqlConnSettings.Server = settings.server.host; + if (settings.server.port > 0 && settings.server.port < 65536) + { + cumulus.MySqlConnSettings.Port = settings.server.port; + } + else + { + cumulus.MySqlConnSettings.Port = 3306; + } + cumulus.MySqlConnSettings.Database = settings.server.database; + cumulus.MySqlConnSettings.UserID = settings.server.user; + cumulus.MySqlConnSettings.Password = settings.server.pass; + //monthly + cumulus.MonthlyMySqlEnabled = settings.monthenabled; + if (cumulus.MonthlyMySqlEnabled) + { + cumulus.MySqlMonthlyTable = String.IsNullOrWhiteSpace(settings.monthly.table) ? "Monthly" : settings.monthly.table; + } + //realtime + cumulus.RealtimeMySqlEnabled = settings.realtimeenabled; + if (cumulus.RealtimeMySqlEnabled) + { + cumulus.MySqlRealtimeRetention = settings.realtime.retentionVal + " " + settings.realtime.retentionUnit; + cumulus.MySqlRealtimeTable = String.IsNullOrWhiteSpace(settings.realtime.table) ? "Realtime" : settings.realtime.table; + cumulus.RealtimeMySql1MinLimit = settings.realtime.limit1min; + } + //dayfile + cumulus.DayfileMySqlEnabled = settings.dayenabled; + if (cumulus.DayfileMySqlEnabled) + { + cumulus.MySqlDayfileTable = String.IsNullOrWhiteSpace(settings.dayfile.table) ? "Dayfile" : settings.dayfile.table; + } + // custom seconds + cumulus.CustomMySqlSecondsEnabled = settings.custsecsenabled; + if (cumulus.CustomMySqlSecondsEnabled) + { + cumulus.CustomMySqlSecondsCommandString = settings.customseconds.command; + cumulus.CustomMySqlSecondsInterval = settings.customseconds.interval; + } + // custom minutes + cumulus.CustomMySqlMinutesEnabled = settings.custminsenabled; + if (cumulus.CustomMySqlMinutesEnabled) + { + cumulus.CustomMySqlMinutesCommandString = settings.customminutes.command; + cumulus.CustomMySqlMinutesIntervalIndex = settings.customminutes.intervalindex; + if (cumulus.CustomMySqlMinutesIntervalIndex >= 0 && cumulus.CustomMySqlMinutesIntervalIndex < cumulus.FactorsOf60.Length) + { + cumulus.CustomMySqlMinutesInterval = cumulus.FactorsOf60[cumulus.CustomMySqlMinutesIntervalIndex]; + } + else + { + cumulus.CustomMySqlMinutesInterval = 10; + } + } + // custom rollover + cumulus.CustomMySqlRolloverEnabled = settings.custrollenabled; + if (cumulus.CustomMySqlRolloverEnabled) + { + cumulus.CustomMySqlRolloverCommandString = settings.customrollover.command; + } + + // Save the settings + cumulus.WriteIniFile(); + + cumulus.MonthlyMySqlConn.ConnectionString = cumulus.MySqlConnSettings.ToString(); + + cumulus.SetMonthlySqlCreateString(); + cumulus.SetStartOfMonthlyInsertSQL(); + + cumulus.SetDayfileSqlCreateString(); + cumulus.SetStartOfDayfileInsertSQL(); + + cumulus.RealtimeSqlConn.ConnectionString = cumulus.MySqlConnSettings.ToString(); + + cumulus.SetRealtimeSqlCreateString(); + cumulus.SetStartOfRealtimeInsertSQL(); + + cumulus.CustomMysqlSecondsConn.ConnectionString = cumulus.MySqlConnSettings.ToString(); + cumulus.CustomMysqlSecondsTimer.Interval = cumulus.CustomMySqlSecondsInterval*1000; + cumulus.CustomMysqlSecondsTimer.Enabled = cumulus.CustomMySqlSecondsEnabled; + + cumulus.CustomMysqlMinutesConn.ConnectionString = cumulus.MySqlConnSettings.ToString(); + + cumulus.CustomMysqlRolloverConn.ConnectionString = cumulus.MySqlConnSettings.ToString(); + + context.Response.StatusCode = 200; + } + catch (Exception ex) + { + var msg = "Error processing settings: " + ex.Message; + cumulus.LogMessage(msg); + context.Response.StatusCode = 500; + return msg; + } + return "success"; + } + + private string CreateMySQLTable(string createSQL) + { + string res; + using (var mySqlConn = new MySqlConnection(cumulus.MySqlConnSettings.ToString())) + using (MySqlCommand cmd = new MySqlCommand(createSQL, mySqlConn)) + { + cumulus.LogMessage($"MySQL Create Table: {createSQL}"); + + try + { + mySqlConn.Open(); + int aff = cmd.ExecuteNonQuery(); + cumulus.LogMessage($"MySQL Create Table: {aff} items were affected."); + res = "Database table created successfully"; + } + catch (Exception ex) + { + cumulus.LogMessage("MySQL Create Table: Error encountered during MySQL operation."); + cumulus.LogMessage(ex.Message); + res = "Error: " + ex.Message; + } + finally + { + try + { + mySqlConn.Close(); + } + catch + { } + } + } + return res; + } + + //public string CreateMonthlySQL(HttpListenerContext context) + public string CreateMonthlySQL(IHttpContext context) + { + context.Response.StatusCode = 200; + string json = "{\"result\":\"" + CreateMySQLTable(cumulus.CreateMonthlySQL) + "\"}"; + return json; + } + + //public string CreateDayfileSQL(HttpListenerContext context) + public string CreateDayfileSQL(IHttpContext context) + { + context.Response.StatusCode = 200; + string json = "{\"result\":\"" + CreateMySQLTable(cumulus.CreateDayfileSQL) + "\"}"; + return json; + } + + //public string CreateRealtimeSQL(HttpListenerContext context) + public string CreateRealtimeSQL(IHttpContext context) + { + context.Response.StatusCode = 200; + string json = "{\"result\":\"" + CreateMySQLTable(cumulus.CreateRealtimeSQL) + "\"}"; + return json; + } + } + + public class JsonMysqlSettings + { + public bool accessible {get; set;} + public JsonMysqlSettingsServer server { get; set; } + public bool monthenabled { get; set; } + public JsonMysqlSettingsMonthly monthly { get; set; } + public bool realtimeenabled { get; set; } + public JsonMysqlSettingsRealtime realtime { get; set; } + public bool dayenabled { get; set; } + public JsonMysqlSettingsDayfile dayfile { get; set; } + public bool custsecsenabled { get; set; } + public JsonMysqlSettingsCustomSeconds customseconds { get; set; } + public bool custminsenabled { get; set; } + public JsonMysqlSettingsCustomMinutes customminutes { get; set; } + public bool custrollenabled { get; set; } + public JsonMysqlSettingsCustomRollover customrollover { get; set; } + } + + public class JsonMysqlSettingsServer + { + public string host { get; set; } + public uint port { get; set; } + public string user { get; set; } + public string pass { get; set; } + public string database { get; set; } + } + + public class JsonMysqlSettingsMonthly + { + public string table { get; set; } + } + + public class JsonMysqlSettingsRealtime + { + public string table { get; set; } + public int retentionVal { get; set; } + public string retentionUnit { get; set; } + public bool limit1min { get; set; } + } + + public class JsonMysqlSettingsDayfile + { + public string table { get; set; } + } + + public class JsonMysqlSettingsCustomSeconds + { + public string command { get; set; } + public int interval { get; set; } + } + + public class JsonMysqlSettingsCustomMinutes + { + public string command { get; set; } + public int intervalindex { get; set; } + } + + public class JsonMysqlSettingsCustomRollover + { + public string command { get; set; } + } +} diff --git a/CumulusMX/NOAAReports.cs b/CumulusMX/NOAAReports.cs index e0d57710..e02f9dda 100644 --- a/CumulusMX/NOAAReports.cs +++ b/CumulusMX/NOAAReports.cs @@ -1,111 +1,178 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace CumulusMX -{ - public class NOAAReports - { - private readonly Cumulus cumulus; - //private WeatherStation station; - private List report; - //private string[] report; - private string noaafile; - - public NOAAReports(Cumulus cumulus) - //public NOAAReports() - { - this.cumulus = cumulus; - //this.station = station; - } - - public List GenerateNoaaYearReport(int year) - { - NOAA noaa = new NOAA(cumulus); - DateTime noaats = new DateTime(year, 1, 1); - - cumulus.LogMessage("Creating NOAA yearly report"); - report = noaa.CreateYearlyReport(noaats); - try - { - // If not using UTF, then we have to convert the character set - var utf8WithoutBom = new System.Text.UTF8Encoding(false); - var encoding = cumulus.NOAAUseUTF8 ? utf8WithoutBom : System.Text.Encoding.GetEncoding("iso-8859-1"); - var reportName = noaats.ToString(cumulus.NOAAYearFileFormat); - noaafile = cumulus.ReportPath + reportName; - cumulus.LogMessage("Saving yearly NOAA report as " + noaafile); - File.WriteAllLines(noaafile, report, encoding); - } - catch (Exception e) - { - cumulus.LogMessage($"Error creating NOAA yearly report: {e.Message}"); - throw; - } - return report; - } - - public List GenerateNoaaMonthReport(int year, int month) - { - NOAA noaa = new NOAA(cumulus); - DateTime noaats = new DateTime(year, month, 1); - - cumulus.LogMessage("Creating NOAA monthly report"); - var report = noaa.CreateMonthlyReport(noaats); - var reportName = String.Empty; - try - { - // If not using UTF, then we have to convert the character set - var utf8WithoutBom = new System.Text.UTF8Encoding(false); - var encoding = cumulus.NOAAUseUTF8 ? utf8WithoutBom : System.Text.Encoding.GetEncoding("iso-8859-1"); - reportName = noaats.ToString(cumulus.NOAAMonthFileFormat); - noaafile = cumulus.ReportPath + reportName; - cumulus.LogMessage("Saving monthly NOAA report as " + noaafile); - File.WriteAllLines(noaafile, report, encoding); - } - catch (Exception e) - { - cumulus.LogMessage($"Error creating NOAA yearly report '{reportName}': {e.Message}"); - throw; - } - return report; - } - - public List GetNoaaYearReport(int year) - { - DateTime noaats = new DateTime(year, 1, 1); - var reportName = String.Empty; - try - { - reportName = noaats.ToString(cumulus.NOAAYearFileFormat); - noaafile = cumulus.ReportPath + reportName; - report = File.Exists(noaafile) ? new List(File.ReadAllLines(noaafile)) : new List { "That report does not exist" }; - } - catch (Exception e) - { - cumulus.LogMessage($"Error getting NOAA yearly report '{reportName}': {e.Message}"); - report = new List { "Something went wrong!" }; - } - return report; - } - - public List GetNoaaMonthReport(int year, int month) - { - DateTime noaats = new DateTime(year, month, 1); - var reportName = String.Empty; - try - { - reportName = noaats.ToString(cumulus.NOAAMonthFileFormat); - noaafile = cumulus.ReportPath + reportName; - var encoding = cumulus.NOAAUseUTF8 ? Encoding.GetEncoding("utf-8") : Encoding.GetEncoding("iso-8859-1"); - report = File.Exists(noaafile) ? new List (File.ReadAllLines(noaafile, encoding)) : new List { "That report does not exist" }; - } - catch (Exception e) - { - cumulus.LogMessage($"Error getting NOAA monthly report '{reportName}': {e.Message}"); - report = new List { "Something went wrong!" }; - } - return report; - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace CumulusMX +{ + public class NOAAReports + { + private readonly Cumulus cumulus; + //private WeatherStation station; + private List report; + //private string[] report; + private string noaafile; + + public NOAAReports(Cumulus cumulus) + //public NOAAReports() + { + this.cumulus = cumulus; + //this.station = station; + } + + public List GenerateNoaaYearReport(int year) + { + NOAA noaa = new NOAA(cumulus); + DateTime noaats = new DateTime(year, 1, 1); + + cumulus.LogMessage("Creating NOAA yearly report"); + report = noaa.CreateYearlyReport(noaats); + try + { + // If not using UTF, then we have to convert the character set + var utf8WithoutBom = new System.Text.UTF8Encoding(false); + var encoding = cumulus.NOAAUseUTF8 ? utf8WithoutBom : System.Text.Encoding.GetEncoding("iso-8859-1"); + var reportName = noaats.ToString(cumulus.NOAAYearFileFormat); + noaafile = cumulus.ReportPath + reportName; + cumulus.LogMessage("Saving yearly NOAA report as " + noaafile); + File.WriteAllLines(noaafile, report, encoding); + } + catch (Exception e) + { + cumulus.LogMessage($"Error creating NOAA yearly report: {e.Message}"); + throw; + } + return report; + } + + public List GenerateNoaaMonthReport(int year, int month) + { + NOAA noaa = new NOAA(cumulus); + DateTime noaats = new DateTime(year, month, 1); + + cumulus.LogMessage("Creating NOAA monthly report"); + var report = noaa.CreateMonthlyReport(noaats); + var reportName = String.Empty; + try + { + // If not using UTF, then we have to convert the character set + var utf8WithoutBom = new System.Text.UTF8Encoding(false); + var encoding = cumulus.NOAAUseUTF8 ? utf8WithoutBom : System.Text.Encoding.GetEncoding("iso-8859-1"); + reportName = noaats.ToString(cumulus.NOAAMonthFileFormat); + noaafile = cumulus.ReportPath + reportName; + cumulus.LogMessage("Saving monthly NOAA report as " + noaafile); + File.WriteAllLines(noaafile, report, encoding); + } + catch (Exception e) + { + cumulus.LogMessage($"Error creating NOAA yearly report '{reportName}': {e.Message}"); + throw; + } + return report; + } + + public List GetNoaaYearReport(int year) + { + DateTime noaats = new DateTime(year, 1, 1); + var reportName = String.Empty; + try + { + reportName = noaats.ToString(cumulus.NOAAYearFileFormat); + noaafile = cumulus.ReportPath + reportName; + report = File.Exists(noaafile) ? new List(File.ReadAllLines(noaafile)) : new List { "That report does not exist" }; + } + catch (Exception e) + { + cumulus.LogMessage($"Error getting NOAA yearly report '{reportName}': {e.Message}"); + report = new List { "Something went wrong!" }; + } + return report; + } + + public List GetNoaaMonthReport(int year, int month) + { + DateTime noaats = new DateTime(year, month, 1); + var reportName = String.Empty; + try + { + reportName = noaats.ToString(cumulus.NOAAMonthFileFormat); + noaafile = cumulus.ReportPath + reportName; + var encoding = cumulus.NOAAUseUTF8 ? Encoding.GetEncoding("utf-8") : Encoding.GetEncoding("iso-8859-1"); + report = File.Exists(noaafile) ? new List (File.ReadAllLines(noaafile, encoding)) : new List { "That report does not exist" }; + } + catch (Exception e) + { + cumulus.LogMessage($"Error getting NOAA monthly report '{reportName}': {e.Message}"); + report = new List { "Something went wrong!" }; + } + return report; + } + + public string GetLastNoaaYearReportFilename(DateTime dat, bool fullPath) + { + // First determine the date for the logfile. + // If we're using 9am rollover, the date should be 9 hours (10 in summer) + // before 'Now' + // This assumes that the caller has already subtracted a day if required + DateTime logfiledate; + + if (cumulus.RolloverHour == 0) + { + logfiledate = dat.AddDays(-1); + } + else + { + TimeZone tz = TimeZone.CurrentTimeZone; + + if (cumulus.Use10amInSummer && tz.IsDaylightSavingTime(dat)) + { + // Locale is currently on Daylight (summer) time + logfiledate = dat.AddHours(-10); + } + else + { + // Locale is currently on Standard time or unknown + logfiledate = dat.AddHours(-9); + } + } + + if (fullPath) + return cumulus.ReportPath + logfiledate.ToString(cumulus.NOAAYearFileFormat); + else + return logfiledate.ToString(cumulus.NOAAYearFileFormat); + } + + public string GetLastNoaaMonthReportFilename (DateTime dat, bool fullPath) + { + // First determine the date for the logfile. + // If we're using 9am rollover, the date should be 9 hours (10 in summer) + // before 'Now' + // This assumes that the caller has already subtracted a day if required + DateTime logfiledate; + + if (cumulus.RolloverHour == 0) + { + logfiledate = dat.AddDays(-1); + } + else + { + TimeZone tz = TimeZone.CurrentTimeZone; + + if (cumulus.Use10amInSummer && tz.IsDaylightSavingTime(dat)) + { + // Locale is currently on Daylight (summer) time + logfiledate = dat.AddHours(-10); + } + else + { + // Locale is currently on Standard time or unknown + logfiledate = dat.AddHours(-9); + } + } + if (fullPath) + return cumulus.ReportPath + logfiledate.AddHours(-1).ToString(cumulus.NOAAMonthFileFormat); + else + return logfiledate.AddHours(-1).ToString(cumulus.NOAAMonthFileFormat); + } + } +} diff --git a/CumulusMX/WeatherStation.cs b/CumulusMX/WeatherStation.cs index 2c8b3a9f..bd684ff4 100644 --- a/CumulusMX/WeatherStation.cs +++ b/CumulusMX/WeatherStation.cs @@ -1,12384 +1,12391 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.IO.Ports; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Reflection; -using System.Runtime.Serialization.Json; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Timers; -using MySqlConnector; -using SQLite; -using Timer = System.Timers.Timer; -using ServiceStack.Text; -using System.Web; - -namespace CumulusMX -{ - internal abstract class WeatherStation - { - public struct TWindRecent - { - public double Gust; // uncalibrated "gust" as read from station - public double Speed; // uncalibrated "speed" as read from station - public DateTime Timestamp; - } - - public struct TWindVec - { - public double X; - public double Y; - public int Bearing; - public DateTime Timestamp; - } - - private readonly Object monthIniThreadLock = new Object(); - public readonly Object yearIniThreadLock = new Object(); - public readonly Object alltimeIniThreadLock = new Object(); - public readonly Object monthlyalltimeIniThreadLock = new Object(); - - // holds all time highs and lows - public AllTimeRecords AllTime = new AllTimeRecords(); - - // holds monthly all time highs and lows - private AllTimeRecords[] monthlyRecs = new AllTimeRecords[13]; - public AllTimeRecords[] MonthlyRecs - { - get - { - if (monthlyRecs == null) - { - monthlyRecs = new AllTimeRecords[13]; - } - - return monthlyRecs; - } - } - - public struct dayfilerec - { - public DateTime Date; - public double HighGust; - public int HighGustBearing; - public DateTime HighGustTime; - public double LowTemp; - public DateTime LowTempTime; - public double HighTemp; - public DateTime HighTempTime; - public double LowPress; - public DateTime LowPressTime; - public double HighPress; - public DateTime HighPressTime; - public double HighRainRate; - public DateTime HighRainRateTime; - public double TotalRain; - public double AvgTemp; - public double WindRun; - public double HighAvgWind; - public DateTime HighAvgWindTime; - public int LowHumidity; - public DateTime LowHumidityTime; - public int HighHumidity; - public DateTime HighHumidityTime; - public double ET; - public double SunShineHours; - public double HighHeatIndex; - public DateTime HighHeatIndexTime; - public double HighAppTemp; - public DateTime HighAppTempTime; - public double LowAppTemp; - public DateTime LowAppTempTime; - public double HighHourlyRain; - public DateTime HighHourlyRainTime; - public double LowWindChill; - public DateTime LowWindChillTime; - public double HighDewPoint; - public DateTime HighDewPointTime; - public double LowDewPoint; - public DateTime LowDewPointTime; - public int DominantWindBearing; - public double HeatingDegreeDays; - public double CoolingDegreeDays; - public int HighSolar; - public DateTime HighSolarTime; - public double HighUv; - public DateTime HighUvTime; - public double HighFeelsLike; - public DateTime HighFeelsLikeTime; - public double LowFeelsLike; - public DateTime LowFeelsLikeTime; - public double HighHumidex; - public DateTime HighHumidexTime; - } - - public List DayFile = new List(); - - - // this month highs and lows - public AllTimeRecords ThisMonth = new AllTimeRecords(); - - public AllTimeRecords ThisYear = new AllTimeRecords(); - - - //public DateTime lastArchiveTimeUTC; - - public string LatestFOReading { get; set; } - - //public int LastDailySummaryOADate; - - public Cumulus cumulus; - - private int lastMinute; - private int lastHour; - - public bool[] WMR928ChannelPresent = new[] { false, false, false, false }; - public bool[] WMR928ExtraTempValueOnly = new[] { false, false, false, false }; - public double[] WMR928ExtraTempValues = new[] { 0.0, 0.0, 0.0, 0.0 }; - public double[] WMR928ExtraDPValues = new[] { 0.0, 0.0, 0.0, 0.0 }; - public int[] WMR928ExtraHumValues = new[] { 0, 0, 0, 0 }; - - - public DateTime AlltimeRecordTimestamp { get; set; } - - //private ProgressWindow progressWindow; - - //public historyProgressWindow histprog; - public BackgroundWorker bw; - - //public bool importingData = false; - - public bool calculaterainrate = false; - - protected List buffer = new List(); - - private readonly List Last3HourDataList = new List(); - private readonly List LastHourDataList = new List(); - private readonly List GraphDataList = new List(); - private readonly List Last10MinWindList = new List(); -// private readonly List RecentDailyDataList = new List(); - - public WeatherDataCollection weatherDataCollection = new WeatherDataCollection(); - - // Current values - - public double THWIndex = 0; - public double THSWIndex = 0; - - public double raindaystart = 0.0; - public double Raincounter = 0.0; - public bool gotraindaystart = false; - protected double prevraincounter = 0.0; - - public struct DailyHighLow - { - public double HighGust; - public int HighGustBearing; - public DateTime HighGustTime; - public double HighWind; - public DateTime HighWindTime; - public double HighTemp; - public DateTime HighTempTime; - public double LowTemp; - public DateTime LowTempTime; - public double TempRange; - public double HighAppTemp; - public DateTime HighAppTempTime; - public double LowAppTemp; - public DateTime LowAppTempTime; - public double HighFeelsLike; - public DateTime HighFeelsLikeTime; - public double LowFeelsLike; - public DateTime LowFeelsLikeTime; - public double HighHumidex; - public DateTime HighHumidexTime; - public double HighPress; - public DateTime HighPressTime; - public double LowPress; - public DateTime LowPressTime; - public double HighRainRate; - public DateTime HighRainRateTime; - public double HighHourlyRain; - public DateTime HighHourlyRainTime; - public int HighHumidity; - public DateTime HighHumidityTime; - public int LowHumidity; - public DateTime LowHumidityTime; - public double HighHeatIndex; - public DateTime HighHeatIndexTime; - public double LowWindChill; - public DateTime LowWindChillTime; - public double HighDewPoint; - public DateTime HighDewPointTime; - public double LowDewPoint; - public DateTime LowDewPointTime; - public double HighSolar; - public DateTime HighSolarTime; - public double HighUv; - public DateTime HighUvTime; - - }; - - // today highs and lows - public DailyHighLow HiLoToday = new DailyHighLow() - { - HighTemp = -500, - HighAppTemp = -500, - HighFeelsLike = -500, - HighHumidex = -500, - HighHeatIndex = -500, - HighDewPoint = -500, - LowTemp = 999, - LowAppTemp = 999, - LowFeelsLike = 999, - LowWindChill = 999, - LowDewPoint = 999, - LowPress = 9999, - LowHumidity = 100 - }; - - // yesterdays highs and lows - public DailyHighLow HiLoYest = new DailyHighLow() - { - HighTemp = -500, - HighAppTemp = -500, - HighFeelsLike = -500, - HighHumidex = -500, - HighHeatIndex = -500, - HighDewPoint = -500, - LowTemp = 999, - LowAppTemp = 999, - LowFeelsLike = 999, - LowWindChill = 999, - LowDewPoint = 999, - LowPress = 9999, - LowHumidity = 100 - }; - - - public int IndoorBattStatus; - public int WindBattStatus; - public int RainBattStatus; - public int TempBattStatus; - public int UVBattStatus; - - public double[] WMR200ExtraDPValues { get; set; } - - public bool[] WMR200ChannelPresent { get; set; } - - public double[] WMR200ExtraHumValues { get; set; } - - public double[] WMR200ExtraTempValues { get; set; } - - public DateTime lastDataReadTime; - public bool haveReadData = false; - - public bool ExtraSensorsDetected = false; - - // Should Cumulus find the peak gust? - // This gets set to false for Davis stations after logger download - // if 10-minute gust period is in use, so we use the Davis value instead. - public bool CalcRecentMaxGust = true; - - public SerialPort comport; - - //private TextWriterTraceListener myTextListener; - - private Thread t; - - public Timer secondTimer; - public double presstrendval; - public double temptrendval; - - public int multicastsGood, multicastsBad; - - public bool timerStartNeeded = false; - - private readonly DateTime versionCheckTime; - - public SQLiteConnection RecentDataDb; - // Extra sensors - - public double SolarElevation; - - public double SolarFactor = -1; // used to adjust solar transmission factor (range 0-1), disabled = -1 - - public bool WindReadyToPlot = false; - public bool TempReadyToPlot = false; - private bool first_temp = true; - public double RG11RainYesterday { get; set; } - - public abstract void Start(); - - public WeatherStation(Cumulus cumulus) - { - // save the reference to the owner - this.cumulus = cumulus; - - // initialise the monthly array of records - element zero is not used - for (var i = 1; i <= 12; i++) - { - MonthlyRecs[i] = new AllTimeRecords(); - } - - CumulusForecast = cumulus.ForecastNotAvailable; - wsforecast = cumulus.ForecastNotAvailable; - - ExtraTemp = new double[11]; - ExtraHum = new double[11]; - ExtraDewPoint = new double[11]; - UserTemp = new double[9]; - - windcounts = new double[16]; - WindRecent = new TWindRecent[MaxWindRecent]; - WindVec = new TWindVec[MaxWindRecent]; - - ReadTodayFile(); - ReadYesterdayFile(); - ReadAlltimeIniFile(); - ReadMonthlyAlltimeIniFile(); - ReadMonthIniFile(); - ReadYearIniFile(); - - GetRainCounter(); - GetRainFallTotals(); - - RecentDataDb = new SQLiteConnection(":memory:", true); - RecentDataDb.CreateTable(); - - var rnd = new Random(); - versionCheckTime = new DateTime(1, 1, 1, rnd.Next(0, 23), rnd.Next(0, 59), 0); - } - - private void GetRainCounter() - { - // Find today's rain so far from last record in log file - bool midnightrainfound = false; - //string LogFile = cumulus.Datapath + cumulus.LastUpdateTime.ToString("MMMyy") + "log.txt"; - string LogFile = cumulus.GetLogFileName(cumulus.LastUpdateTime); - double raincount = 0; - string logdate = "00/00/00"; - string prevlogdate = "00/00/00"; - string listSep = CultureInfo.CurrentCulture.TextInfo.ListSeparator; - string todaydatestring = cumulus.LastUpdateTime.ToString("dd/MM/yy"); - - cumulus.LogMessage("Finding raintoday from logfile " + LogFile); - cumulus.LogMessage("Expecting listsep=" + listSep + " decimal=" + CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator); - - if (File.Exists(LogFile)) - { - int linenum = 0; - try - { - using (var sr = new StreamReader(LogFile)) - { - // now process each record to get the last "raintoday" figure - do - { - string Line = sr.ReadLine(); - linenum++; - var st = new List(Regex.Split(Line, listSep)); - if (st.Count > 0) - { - RainToday = Double.Parse(st[9]); - // get date of this entry - logdate = st[0]; - if (!midnightrainfound) - { - if (logdate != prevlogdate) - { - if (todaydatestring == logdate) - { - // this is the first entry of a new day AND the new day is today - midnightrainfound = true; - cumulus.LogMessage("Midnight rain found in the following entry:"); - cumulus.LogMessage(Line); - raincount = Double.Parse(st[11]); - } - } - } - prevlogdate = logdate; - } - } while (!sr.EndOfStream); - } - } - catch (Exception E) - { - cumulus.LogMessage("Error on line " + linenum + " of " + LogFile + ": " + E.Message); - } - } - - if (midnightrainfound) - { - if ((logdate.Substring(0, 2) == "01") && (logdate.Substring(3, 2) == cumulus.RainSeasonStart.ToString("D2")) && (cumulus.Manufacturer == cumulus.DAVIS)) - { - // special case: rain counter is about to be reset - //TODO: MC: Hmm are there issues here, what if the console clock is wrong and it does not reset for another hour, or it already reset and we have had rain since? - var month = CultureInfo.InvariantCulture.DateTimeFormat.GetMonthName(cumulus.RainSeasonStart); - cumulus.LogMessage($"Special case, Davis station on 1st of {month}. Set midnight rain count to zero"); - midnightraincount = 0; - } - else - { - cumulus.LogMessage("Midnight rain found, setting midnight rain count = " + raincount); - midnightraincount = raincount; - } - } - else - { - cumulus.LogMessage("Midnight rain not found, setting midnight count to raindaystart = " + raindaystart); - midnightraincount = raindaystart; - } - - // If we do not have a rain counter value for start of day from Today.ini, then use the midnight counter - if (initialiseRainCounterOnFirstData) - { - Raincounter = midnightraincount + (RainToday / cumulus.Calib.Rain.Mult); - } - else - { - // Otherwise use the counter value from today.ini plus total so far today to infer the counter value - Raincounter = raindaystart + (RainToday / cumulus.Calib.Rain.Mult); - } - - cumulus.LogMessage("Checking rain counter = " + Raincounter); - if (Raincounter < 0) - { - cumulus.LogMessage("Rain counter negative, setting to zero"); - Raincounter = 0; - } - else - { - cumulus.LogMessage("Rain counter set to = " + Raincounter); - } - } - - public DateTime ddmmyyStrToDate(string d) - { - // Converts a date string in UK order to a DateTime - // Horrible hack, but we have localised separators, but UK sequence, so localised parsing may fail - string[] date = d.Split(new string[] { CultureInfo.CurrentCulture.DateTimeFormat.DateSeparator }, StringSplitOptions.None); - - int D = Convert.ToInt32(date[0]); - int M = Convert.ToInt32(date[1]); - int Y = Convert.ToInt32(date[2]); - if (Y > 70) - { - Y += 1900; - } - else - { - Y += 2000; - } - - return new DateTime(Y, M, D); - } - - public DateTime ddmmyyhhmmStrToDate(string d, string t) - { - // Converts a date string in UK order to a DateTime - // Horrible hack, but we have localised separators, but UK sequence, so localised parsing may fail - string[] date = d.Split(new string[] { CultureInfo.CurrentCulture.DateTimeFormat.DateSeparator }, StringSplitOptions.None); - string[] time = t.Split(new string[] { CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator }, StringSplitOptions.None); - - int D = Convert.ToInt32(date[0]); - int M = Convert.ToInt32(date[1]); - int Y = Convert.ToInt32(date[2]); - - // Double check - just in case we get a four digit year! - if (Y < 1900) - { - Y += Y > 70 ? 1900 : 2000; - } - int h = Convert.ToInt32(time[0]); - int m = Convert.ToInt32(time[1]); - - return new DateTime(Y, M, D, h, m, 0); - } - - public void GetRainFallTotals() - { - cumulus.LogMessage("Getting rain totals, rain season start = " + cumulus.RainSeasonStart); - rainthismonth = 0; - rainthisyear = 0; - int linenum = 0; - // get today"s date for month check; allow for 0900 rollover - var hourInc = cumulus.GetHourInc(); - var ModifiedNow = DateTime.Now.AddHours(hourInc); - // avoid any funny locale peculiarities on date formats - string Today = ModifiedNow.ToString("dd/MM/yy", CultureInfo.InvariantCulture); - cumulus.LogMessage("Today = " + Today); - // get today's date offset by rain season start for year check - int offsetYearToday = ModifiedNow.AddMonths(-(cumulus.RainSeasonStart - 1)).Year; - - if (File.Exists(cumulus.DayFileName)) - { - try - { - using (var sr = new StreamReader(cumulus.DayFileName)) - { - do - { - string Line = sr.ReadLine(); - linenum++; - var st = new List(Regex.Split(Line, CultureInfo.CurrentCulture.TextInfo.ListSeparator)); - - if (st.Count > 0) - { - string datestr = st[0]; - DateTime loggedDate = ddmmyyStrToDate(datestr); - int offsetLoggedYear = loggedDate.AddMonths(-(cumulus.RainSeasonStart - 1)).Year; - // This year? - if (offsetLoggedYear == offsetYearToday) - { - rainthisyear += Double.Parse(st[14]); - } - // This month? - if ((loggedDate.Month == ModifiedNow.Month) && (loggedDate.Year == ModifiedNow.Year)) - { - rainthismonth += Double.Parse(st[14]); - } - } - } while (!sr.EndOfStream); - } - } - catch (Exception ex) - { - cumulus.LogMessage("GetRainfallTotals: Error on line " + linenum + " of dayfile.txt: " + ex.Message); - } - - cumulus.LogMessage("Rainthismonth from dayfile.txt: " + rainthismonth); - cumulus.LogMessage("Rainthisyear from dayfile.txt: " + rainthisyear); - } - - // Add in year-to-date rain (if necessary) - if (cumulus.YTDrainyear == Convert.ToInt32(Today.Substring(6, 2)) + 2000) - { - cumulus.LogMessage("Adding YTD rain: " + cumulus.YTDrain); - rainthisyear += cumulus.YTDrain; - cumulus.LogMessage("Rainthisyear: " + rainthisyear); - } - } - - public void ReadTodayFile() - { - if (!File.Exists(cumulus.TodayIniFile)) - { - FirstRun = true; - } - - IniFile ini = new IniFile(cumulus.TodayIniFile); - - cumulus.LogConsoleMessage("Today.ini = " + cumulus.TodayIniFile); - - var todayfiledate = ini.GetValue("General", "Date", "00/00/00"); - var timestampstr = ini.GetValue("General", "Timestamp", DateTime.Now.ToString("s")); - - cumulus.LogConsoleMessage("Last update=" + timestampstr); - - cumulus.LastUpdateTime = DateTime.Parse(timestampstr); - cumulus.LogMessage("Last update time from today.ini: " + cumulus.LastUpdateTime); - - DateTime currentMonthTS = cumulus.LastUpdateTime.AddHours(cumulus.GetHourInc()); - - int defaultyear = currentMonthTS.Year; - int defaultmonth = currentMonthTS.Month; - int defaultday = currentMonthTS.Day; - - CurrentYear = ini.GetValue("General", "CurrentYear", defaultyear); - CurrentMonth = ini.GetValue("General", "CurrentMonth", defaultmonth); - CurrentDay = ini.GetValue("General", "CurrentDay", defaultday); - - cumulus.LogMessage("Read today file: Date = " + todayfiledate + ", LastUpdateTime = " + cumulus.LastUpdateTime + ", Month = " + CurrentMonth); - - LastRainTip = ini.GetValue("Rain", "LastTip", "0000-00-00 00:00"); - - FOSensorClockTime = ini.GetValue("FineOffset", "FOSensorClockTime", DateTime.MinValue); - FOStationClockTime = ini.GetValue("FineOffset", "FOStationClockTime", DateTime.MinValue); - if (cumulus.FineOffsetOptions.SyncReads) - { - cumulus.LogMessage("Sensor clock " + FOSensorClockTime.ToLongTimeString()); - cumulus.LogMessage("Station clock " + FOStationClockTime.ToLongTimeString()); - } - ConsecutiveRainDays = ini.GetValue("Rain", "ConsecutiveRainDays", 0); - ConsecutiveDryDays = ini.GetValue("Rain", "ConsecutiveDryDays", 0); - - AnnualETTotal = ini.GetValue("ET", "Annual", 0.0); - StartofdayET = ini.GetValue("ET", "Startofday", -1.0); - if (StartofdayET < 0) - { - cumulus.LogMessage("ET not initialised"); - noET = true; - } - ChillHours = ini.GetValue("Temp", "ChillHours", 0.0); - - // NOAA report names - cumulus.NOAALatestMonthlyReport = ini.GetValue("NOAA", "LatestMonthlyReport", ""); - cumulus.NOAALatestYearlyReport = ini.GetValue("NOAA", "LatestYearlyReport", ""); - - // Solar - HiLoToday.HighSolar = ini.GetValue("Solar", "HighSolarRad", 0.0); - HiLoToday.HighSolarTime = ini.GetValue("Solar", "HighSolarRadTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); - HiLoToday.HighUv = ini.GetValue("Solar", "HighUV", 0.0); - HiLoToday.HighUvTime = ini.GetValue("Solar", "HighUVTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); - StartOfDaySunHourCounter = ini.GetValue("Solar", "SunStart", -9999.0); - RG11RainToday = ini.GetValue("Rain", "RG11Today", 0.0); - - // Wind - HiLoToday.HighWind = ini.GetValue("Wind", "Speed", 0.0); - HiLoToday.HighWindTime = ini.GetValue("Wind", "SpTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); - HiLoToday.HighGust = ini.GetValue("Wind", "Gust", 0.0); - HiLoToday.HighGustTime = ini.GetValue("Wind", "Time", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); - HiLoToday.HighGustBearing = ini.GetValue("Wind", "Bearing", 0); - WindRunToday = ini.GetValue("Wind", "Windrun", 0.0); - DominantWindBearing = ini.GetValue("Wind", "DominantWindBearing", 0); - DominantWindBearingMinutes = ini.GetValue("Wind", "DominantWindBearingMinutes", 0); - DominantWindBearingX = ini.GetValue("Wind", "DominantWindBearingX", 0.0); - DominantWindBearingY = ini.GetValue("Wind", "DominantWindBearingY", 0.0); - // Temperature - HiLoToday.LowTemp = ini.GetValue("Temp", "Low", 999.0); - HiLoToday.LowTempTime = ini.GetValue("Temp", "LTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); - HiLoToday.HighTemp = ini.GetValue("Temp", "High", -999.0); - HiLoToday.HighTempTime = ini.GetValue("Temp", "HTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); - if ((HiLoToday.HighTemp > -400) && (HiLoToday.LowTemp < 400)) - HiLoToday.TempRange = HiLoToday.HighTemp - HiLoToday.LowTemp; - else - HiLoToday.TempRange = 0; - TempTotalToday = ini.GetValue("Temp", "Total", 0.0); - tempsamplestoday = ini.GetValue("Temp", "Samples", 1); - HeatingDegreeDays = ini.GetValue("Temp", "HeatingDegreeDays", 0.0); - CoolingDegreeDays = ini.GetValue("Temp", "CoolingDegreeDays", 0.0); - GrowingDegreeDaysThisYear1 = ini.GetValue("Temp", "GrowingDegreeDaysThisYear1", 0.0); - GrowingDegreeDaysThisYear2 = ini.GetValue("Temp", "GrowingDegreeDaysThisYear2", 0.0); - // PressureHighDewpoint - HiLoToday.LowPress = ini.GetValue("Pressure", "Low", 9999.0); - HiLoToday.LowPressTime = ini.GetValue("Pressure", "LTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); - HiLoToday.HighPress = ini.GetValue("Pressure", "High", 0.0); - HiLoToday.HighPressTime = ini.GetValue("Pressure", "HTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); - // rain - HiLoToday.HighRainRate = ini.GetValue("Rain", "High", 0.0); - HiLoToday.HighRainRateTime = ini.GetValue("Rain", "HTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); - HiLoToday.HighHourlyRain = ini.GetValue("Rain", "HourlyHigh", 0.0); - HiLoToday.HighHourlyRainTime = ini.GetValue("Rain", "HHourlyTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); - raindaystart = ini.GetValue("Rain", "Start", -1.0); - cumulus.LogMessage($"ReadTodayfile: Rain day start = {raindaystart}"); - RainYesterday = ini.GetValue("Rain", "Yesterday", 0.0); - if (raindaystart >= 0) - { - cumulus.LogMessage("ReadTodayfile: set initialiseRainCounterOnFirstData false"); - initialiseRainCounterOnFirstData = false; - } - // humidity - HiLoToday.LowHumidity = ini.GetValue("Humidity", "Low", 100); - HiLoToday.HighHumidity = ini.GetValue("Humidity", "High", 0); - HiLoToday.LowHumidityTime = ini.GetValue("Humidity", "LTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); - HiLoToday.HighHumidityTime = ini.GetValue("Humidity", "HTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); - // Solar - SunshineHours = ini.GetValue("Solar", "SunshineHours", 0.0); - SunshineToMidnight = ini.GetValue("Solar", "SunshineHoursToMidnight", 0.0); - // heat index - HiLoToday.HighHeatIndex = ini.GetValue("HeatIndex", "High", -999.0); - HiLoToday.HighHeatIndexTime = ini.GetValue("HeatIndex", "HTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); - // Apparent temp - HiLoToday.HighAppTemp = ini.GetValue("AppTemp", "High", -999.0); - HiLoToday.HighAppTempTime = ini.GetValue("AppTemp", "HTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); - HiLoToday.LowAppTemp = ini.GetValue("AppTemp", "Low", 999.0); - HiLoToday.LowAppTempTime = ini.GetValue("AppTemp", "LTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); - // wind chill - HiLoToday.LowWindChill = ini.GetValue("WindChill", "Low", 999.0); - HiLoToday.LowWindChillTime = ini.GetValue("WindChill", "LTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); - // Dew point - HiLoToday.HighDewPoint = ini.GetValue("Dewpoint", "High", -999.0); - HiLoToday.HighDewPointTime = ini.GetValue("Dewpoint", "HTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); - HiLoToday.LowDewPoint = ini.GetValue("Dewpoint", "Low", 999.0); - HiLoToday.LowDewPointTime = ini.GetValue("Dewpoint", "LTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); - // Feels like - HiLoToday.HighFeelsLike = ini.GetValue("FeelsLike", "High", -999.0); - HiLoToday.HighFeelsLikeTime = ini.GetValue("FeelsLike", "HTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); - HiLoToday.LowFeelsLike = ini.GetValue("FeelsLike", "Low", 999.0); - HiLoToday.LowFeelsLikeTime = ini.GetValue("FeelsLike", "LTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); - // Humidex - HiLoToday.HighHumidex = ini.GetValue("Humidex", "High", -999.0); - HiLoToday.HighHumidexTime = ini.GetValue("Humidex", "HTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); - - // Records - AlltimeRecordTimestamp = ini.GetValue("Records", "Alltime", DateTime.MinValue); - } - - public void WriteTodayFile(DateTime timestamp, bool Log) - { - try - { - var hourInc = cumulus.GetHourInc(); - - IniFile ini = new IniFile(cumulus.TodayIniFile); - - // Date - ini.SetValue("General", "Date", timestamp.AddHours(hourInc).ToShortDateString()); - // Timestamp - ini.SetValue("General", "Timestamp", cumulus.LastUpdateTime.ToString("s")); - ini.SetValue("General", "CurrentYear", CurrentYear); - ini.SetValue("General", "CurrentMonth", CurrentMonth); - ini.SetValue("General", "CurrentDay", CurrentDay); - // Wind - ini.SetValue("Wind", "Speed", HiLoToday.HighWind); - ini.SetValue("Wind", "SpTime", HiLoToday.HighWindTime.ToString("HH:mm")); - ini.SetValue("Wind", "Gust", HiLoToday.HighGust); - ini.SetValue("Wind", "Time", HiLoToday.HighGustTime.ToString("HH:mm")); - ini.SetValue("Wind", "Bearing", HiLoToday.HighGustBearing); - ini.SetValue("Wind", "Direction", CompassPoint(HiLoToday.HighGustBearing)); - ini.SetValue("Wind", "Windrun", WindRunToday); - ini.SetValue("Wind", "DominantWindBearing", DominantWindBearing); - ini.SetValue("Wind", "DominantWindBearingMinutes", DominantWindBearingMinutes); - ini.SetValue("Wind", "DominantWindBearingX", DominantWindBearingX); - ini.SetValue("Wind", "DominantWindBearingY", DominantWindBearingY); - // Temperature - ini.SetValue("Temp", "Low", HiLoToday.LowTemp); - ini.SetValue("Temp", "LTime", HiLoToday.LowTempTime.ToString("HH:mm")); - ini.SetValue("Temp", "High", HiLoToday.HighTemp); - ini.SetValue("Temp", "HTime", HiLoToday.HighTempTime.ToString("HH:mm")); - ini.SetValue("Temp", "Total", TempTotalToday); - ini.SetValue("Temp", "Samples", tempsamplestoday); - ini.SetValue("Temp", "ChillHours", ChillHours); - ini.SetValue("Temp", "HeatingDegreeDays", HeatingDegreeDays); - ini.SetValue("Temp", "CoolingDegreeDays", CoolingDegreeDays); - ini.SetValue("Temp", "GrowingDegreeDaysThisYear1", GrowingDegreeDaysThisYear1); - ini.SetValue("Temp", "GrowingDegreeDaysThisYear2", GrowingDegreeDaysThisYear2); - // Pressure - ini.SetValue("Pressure", "Low", HiLoToday.LowPress); - ini.SetValue("Pressure", "LTime", HiLoToday.LowPressTime.ToString("HH:mm")); - ini.SetValue("Pressure", "High", HiLoToday.HighPress); - ini.SetValue("Pressure", "HTime", HiLoToday.HighPressTime.ToString("HH:mm")); - // rain - ini.SetValue("Rain", "High", HiLoToday.HighRainRate); - ini.SetValue("Rain", "HTime", HiLoToday.HighRainRateTime.ToString("HH:mm")); - ini.SetValue("Rain", "HourlyHigh", HiLoToday.HighHourlyRain); - ini.SetValue("Rain", "HHourlyTime", HiLoToday.HighHourlyRainTime.ToString("HH:mm")); - ini.SetValue("Rain", "Start", raindaystart); - ini.SetValue("Rain", "Yesterday", RainYesterday); - ini.SetValue("Rain", "LastTip", LastRainTip); - ini.SetValue("Rain", "ConsecutiveRainDays", ConsecutiveRainDays); - ini.SetValue("Rain", "ConsecutiveDryDays", ConsecutiveDryDays); - ini.SetValue("Rain", "RG11Today", RG11RainToday); - // ET - ini.SetValue("ET", "Annual", AnnualETTotal); - ini.SetValue("ET", "Startofday", StartofdayET); - // humidity - ini.SetValue("Humidity", "Low", HiLoToday.LowHumidity); - ini.SetValue("Humidity", "High", HiLoToday.HighHumidity); - ini.SetValue("Humidity", "LTime", HiLoToday.LowHumidityTime.ToString("HH:mm")); - ini.SetValue("Humidity", "HTime", HiLoToday.HighHumidityTime.ToString("HH:mm")); - // Solar - ini.SetValue("Solar", "SunshineHours", SunshineHours); - ini.SetValue("Solar", "SunshineHoursToMidnight", SunshineToMidnight); - // heat index - ini.SetValue("HeatIndex", "High", HiLoToday.HighHeatIndex); - ini.SetValue("HeatIndex", "HTime", HiLoToday.HighHeatIndexTime.ToString("HH:mm")); - // App temp - ini.SetValue("AppTemp", "Low", HiLoToday.LowAppTemp); - ini.SetValue("AppTemp", "LTime", HiLoToday.LowAppTempTime.ToString("HH:mm")); - ini.SetValue("AppTemp", "High", HiLoToday.HighAppTemp); - ini.SetValue("AppTemp", "HTime", HiLoToday.HighAppTempTime.ToString("HH:mm")); - // Feels like - ini.SetValue("FeelsLike", "Low", HiLoToday.LowFeelsLike); - ini.SetValue("FeelsLike", "LTime", HiLoToday.LowFeelsLikeTime.ToString("HH:mm")); - ini.SetValue("FeelsLike", "High", HiLoToday.HighFeelsLike); - ini.SetValue("FeelsLike", "HTime", HiLoToday.HighFeelsLikeTime.ToString("HH:mm")); - // Humidex - ini.SetValue("Humidex", "High", HiLoToday.HighHumidex); - ini.SetValue("Humidex", "HTime", HiLoToday.HighHumidexTime.ToString("HH:mm")); - // wind chill - ini.SetValue("WindChill", "Low", HiLoToday.LowWindChill); - ini.SetValue("WindChill", "LTime", HiLoToday.LowWindChillTime.ToString("HH:mm")); - // Dewpoint - ini.SetValue("Dewpoint", "Low", HiLoToday.LowDewPoint); - ini.SetValue("Dewpoint", "LTime", HiLoToday.LowDewPointTime.ToString("HH:mm")); - ini.SetValue("Dewpoint", "High", HiLoToday.HighDewPoint); - ini.SetValue("Dewpoint", "HTime", HiLoToday.HighDewPointTime.ToString("HH:mm")); - - // NOAA report names - ini.SetValue("NOAA", "LatestMonthlyReport", cumulus.NOAALatestMonthlyReport); - ini.SetValue("NOAA", "LatestYearlyReport", cumulus.NOAALatestYearlyReport); - - // Solar - ini.SetValue("Solar", "HighSolarRad", HiLoToday.HighSolar); - ini.SetValue("Solar", "HighSolarRadTime", HiLoToday.HighSolarTime.ToString("HH:mm")); - ini.SetValue("Solar", "HighUV", HiLoToday.HighUv); - ini.SetValue("Solar", "HighUVTime", HiLoToday.HighUvTime.ToString("HH:mm")); - ini.SetValue("Solar", "SunStart", StartOfDaySunHourCounter); - - // Special Fine Offset data - ini.SetValue("FineOffset", "FOSensorClockTime", FOSensorClockTime); - ini.SetValue("FineOffset", "FOStationClockTime", FOStationClockTime); - - // Records - ini.SetValue("Records", "Alltime", AlltimeRecordTimestamp); - - if (Log) - { - cumulus.LogMessage("Writing today.ini, LastUpdateTime = " + cumulus.LastUpdateTime + " raindaystart = " + raindaystart.ToString() + " rain counter = " + - Raincounter.ToString()); - - if (cumulus.FineOffsetStation) - { - cumulus.LogMessage("Latest reading: " + LatestFOReading); - } - else if (cumulus.StationType == StationTypes.Instromet) - { - cumulus.LogMessage("Latest reading: " + cumulus.LatestImetReading); - } - } - - ini.Flush(); - } - catch (Exception ex) - { - cumulus.LogDebugMessage("Error writing today.ini: " + ex.Message); - } - } - - /// - /// calculate the start of today in UTC - /// - /// timestamp of start of today UTC - /* - private DateTime StartOfTodayUTC() - { - DateTime now = DateTime.Now; - - int y = now.Year; - int m = now.Month; - int d = now.Day; - - return new DateTime(y, m, d, 0, 0, 0).ToUniversalTime(); - } - */ - - /// - /// calculate the start of yesterday in UTC - /// - /// timestamp of start of yesterday UTC - /* - private DateTime StartOfYesterdayUTC() - { - DateTime yesterday = DateTime.Now.AddDays(-1); - - int y = yesterday.Year; - int m = yesterday.Month; - int d = yesterday.Day; - - return new DateTime(y, m, d, 0, 0, 0).ToUniversalTime(); - } - */ - - /// - /// calculate the start of this year in UTC - /// - /// timestamp of start of year in UTC - /* - private DateTime StartOfYearUTC() - { - DateTime now = DateTime.Now; - int y = now.Year; - - return new DateTime(y, 1, 1, 0, 0, 0).ToUniversalTime(); - } - */ - - /// - /// calculate the start of this month in UTC - /// - /// timestamp of start of month in UTC - /* - private DateTime StartOfMonthUTC() - { - DateTime now = DateTime.Now; - int y = now.Year; - int m = now.Month; - - return new DateTime(y, m, 1, 0, 0, 0).ToUniversalTime(); - } - */ - - /// - /// calculate the start of this year in OAdate - /// - /// timestamp of start of year in OAdate - /* - private int StartOfYearOADate() - { - DateTime now = DateTime.Now; - int y = now.Year; - - return (int) new DateTime(y, 1, 1, 0, 0, 0).ToOADate(); - } - */ - - /// - /// calculate the start of this month in OADate - /// - /// timestamp of start of month in OADate - /* - private int StartOfMonthOADate() - { - DateTime now = DateTime.Now; - int y = now.Year; - int m = now.Month; - - return (int) new DateTime(y, m, 1, 0, 0, 0).ToOADate(); - } - */ - - /// - /// Indoor temperature in C - /// - public double IndoorTemperature { get; set; } = 0; - - /// - /// Solar Radiation in W/m2 - /// - public double SolarRad { get; set; } = 0; - - /// - /// UV index - /// - public double UV { get; set; } = 0; - - public void UpdatePressureTrendString() - { - double threeHourlyPressureChangeMb = 0; - - switch (cumulus.Units.Press) - { - case 0: - case 1: - threeHourlyPressureChangeMb = presstrendval * 3; - break; - case 2: - threeHourlyPressureChangeMb = presstrendval * 3 / 0.0295333727; - break; - } - - if (threeHourlyPressureChangeMb > 6) Presstrendstr = cumulus.Risingveryrapidly; - else if (threeHourlyPressureChangeMb > 3.5) Presstrendstr = cumulus.Risingquickly; - else if (threeHourlyPressureChangeMb > 1.5) Presstrendstr = cumulus.Rising; - else if (threeHourlyPressureChangeMb > 0.1) Presstrendstr = cumulus.Risingslowly; - else if (threeHourlyPressureChangeMb > -0.1) Presstrendstr = cumulus.Steady; - else if (threeHourlyPressureChangeMb > -1.5) Presstrendstr = cumulus.Fallingslowly; - else if (threeHourlyPressureChangeMb > -3.5) Presstrendstr = cumulus.Falling; - else if (threeHourlyPressureChangeMb > -6) Presstrendstr = cumulus.Fallingquickly; - else - Presstrendstr = cumulus.Fallingveryrapidly; - } - - public string Presstrendstr { get; set; } - - public void CheckMonthlyAlltime(string index, double value, bool higher, DateTime timestamp) - { - lock (monthlyalltimeIniThreadLock) - { - bool recordbroken; - - // Make the delta relate to the precision for dervied values such as feels like - string[] derivedVals = { "HighHeatIndex", "HighAppTemp", "LowAppTemp", "LowChill", "HighHumidex", "HighDewPoint", "LowDewPoint", "HighFeelsLike", "LowFeelsLike" }; - - double epsilon = derivedVals.Contains(index) ? Math.Pow(10, -cumulus.TempDPlaces) : 0.001; // required difference for new record - - int month; - int day; - int year; - - - // Determine month day and year - if (cumulus.RolloverHour == 0) - { - month = timestamp.Month; - day = timestamp.Day; - year = timestamp.Year; - } - else - { - TimeZone tz = TimeZone.CurrentTimeZone; - DateTime adjustedTS; - - if (cumulus.Use10amInSummer && tz.IsDaylightSavingTime(timestamp)) - { - // Locale is currently on Daylight (summer) time - adjustedTS = timestamp.AddHours(-10); - } - else - { - // Locale is currently on Standard time or unknown - adjustedTS = timestamp.AddHours(-9); - } - - month = adjustedTS.Month; - day = adjustedTS.Day; - year = adjustedTS.Year; - } - - AllTimeRec rec = MonthlyRecs[month][index]; - - double oldvalue = rec.Val; - //DateTime oldts = monthlyrecarray[index, month].timestamp; - - if (higher) - { - // check new value is higher than existing record - recordbroken = (value - oldvalue >= epsilon); - } - else - { - // check new value is lower than existing record - recordbroken = (oldvalue - value >= epsilon); - } - - if (recordbroken) - { - // records which apply to whole days or months need their timestamps adjusting - if ((index == "MonthlyRain") || (index == "DailyRain")) - { - DateTime CurrentMonthTS = new DateTime(year, month, day); - SetMonthlyAlltime(rec, value, CurrentMonthTS); - } - else - { - SetMonthlyAlltime(rec, value, timestamp); - } - } - } - } - - private string FormatDateTime(string fmt, DateTime timestamp) - { - return timestamp.ToString(fmt); - } - - - public int CurrentDay { get; set; } - - public int CurrentMonth { get; set; } - - public int CurrentYear { get; set; } - - /// - /// Indoor relative humidity in % - /// - public int IndoorHumidity { get; set; } = 0; - - /// - /// Sea-level pressure - /// - public double Pressure { get; set; } = 0; - - public double StationPressure { get; set; } - - public string Forecast { get; set; } = "Forecast: "; - - /// - /// Outdoor temp - /// - public double OutdoorTemperature { get; set; } = 0; - - /// - /// Outdoor dew point - /// - public double OutdoorDewpoint { get; set; } = 0; - - /// - /// Wind chill - /// - public double WindChill { get; set; } = 0; - - /// - /// Outdoor relative humidity in % - /// - public int OutdoorHumidity { get; set; } = 0; - - /// - /// Apparent temperature - /// - public double ApparentTemperature { get; set; } - - /// - /// Heat index - /// - public double HeatIndex { get; set; } = 0; - - /// - /// Humidex - /// - public double Humidex { get; set; } = 0; - - /// - /// Feels like (JAG/TI) - /// - public double FeelsLike { get; set; } = 0; - - - /// - /// Latest wind speed/gust - /// - public double WindLatest { get; set; } = 0; - - /// - /// Average wind speed - /// - public double WindAverage { get; set; } = 0; - - /// - /// Peak wind gust in last 10 minutes - /// - public double RecentMaxGust { get; set; } = 0; - - /// - /// Wind direction in degrees - /// - public int Bearing { get; set; } = 0; - - /// - /// Wind direction as compass points - /// - public string BearingText { get; set; } = "---"; - - /// - /// Wind direction in degrees - /// - public int AvgBearing { get; set; } = 0; - - /// - /// Wind direction as compass points - /// - public string AvgBearingText { get; set; } = "---"; - - /// - /// Rainfall today - /// - public double RainToday { get; set; } = 0; - - /// - /// Rain this month - /// - public double RainMonth { get; set; } = 0; - - /// - /// Rain this year - /// - public double RainYear { get; set; } = 0; - - /// - /// Current rain rate - /// - public double RainRate { get; set; } = 0; - - public double ET { get; set; } - - public double LightValue { get; set; } - - public double HeatingDegreeDays { get; set; } - - public double CoolingDegreeDays { get; set; } - - public double GrowingDegreeDaysThisYear1 { get; set; } - public double GrowingDegreeDaysThisYear2 { get; set; } - - public int tempsamplestoday { get; set; } - - public double TempTotalToday { get; set; } - - public double ChillHours { get; set; } - - public double midnightraincount { get; set; } - - public int MidnightRainResetDay { get; set; } - - - public DateTime lastSpikeRemoval = DateTime.MinValue; - private double previousPress = 9999; - public double previousGust = 999; - private double previousWind = 999; - private int previousHum = 999; - private double previousTemp = 999; - - - public void UpdateDegreeDays(int interval) - { - if (OutdoorTemperature < cumulus.NOAAheatingthreshold) - { - HeatingDegreeDays += (((cumulus.NOAAheatingthreshold - OutdoorTemperature) * interval) / 1440); - } - if (OutdoorTemperature > cumulus.NOAAcoolingthreshold) - { - CoolingDegreeDays += (((OutdoorTemperature - cumulus.NOAAcoolingthreshold) * interval) / 1440); - } - } - - /// - /// Wind run for today - /// - public double WindRunToday { get; set; } = 0; - - /// - /// Extra Temps - /// - public double[] ExtraTemp { get; set; } - - /// - /// User allocated Temps - /// - public double[] UserTemp { get; set; } - - /// - /// Extra Humidity - /// - public double[] ExtraHum { get; set; } - - /// - /// Extra dewpoint - /// - public double[] ExtraDewPoint { get; set; } - - /// - /// Soil Temp 1 in C - /// - public double SoilTemp1 { get; set; } - - /// - /// Soil Temp 2 in C - /// - public double SoilTemp2 { get; set; } - - /// - /// Soil Temp 3 in C - /// - public double SoilTemp3 { get; set; } - - /// - /// Soil Temp 4 in C - /// - public double SoilTemp4 { get; set; } - public double SoilTemp5 { get; set; } - public double SoilTemp6 { get; set; } - public double SoilTemp7 { get; set; } - public double SoilTemp8 { get; set; } - public double SoilTemp9 { get; set; } - public double SoilTemp10 { get; set; } - public double SoilTemp11 { get; set; } - public double SoilTemp12 { get; set; } - public double SoilTemp13 { get; set; } - public double SoilTemp14 { get; set; } - public double SoilTemp15 { get; set; } - public double SoilTemp16 { get; set; } - - public double RainYesterday { get; set; } - - public double RainLastHour { get; set; } - - public int SoilMoisture1 { get; set; } - - public int SoilMoisture2 { get; set; } - - public int SoilMoisture3 { get; set; } - - public int SoilMoisture4 { get; set; } - - public int SoilMoisture5 { get; set; } - - public int SoilMoisture6 { get; set; } - - public int SoilMoisture7 { get; set; } - - public int SoilMoisture8 { get; set; } - - public int SoilMoisture9 { get; set; } - - public int SoilMoisture10 { get; set; } - - public int SoilMoisture11 { get; set; } - - public int SoilMoisture12 { get; set; } - - public int SoilMoisture13 { get; set; } - - public int SoilMoisture14 { get; set; } - - public int SoilMoisture15 { get; set; } - - public int SoilMoisture16 { get; set; } - - public double AirQuality1 { get; set; } - public double AirQuality2 { get; set; } - public double AirQuality3 { get; set; } - public double AirQuality4 { get; set; } - public double AirQualityAvg1 { get; set; } - public double AirQualityAvg2 { get; set; } - public double AirQualityAvg3 { get; set; } - public double AirQualityAvg4 { get; set; } - - public int CO2 { get; set; } - public int CO2_24h { get; set; } - public double CO2_pm2p5 { get; set; } - public double CO2_pm2p5_24h { get; set; } - public double CO2_pm10 { get; set; } - public double CO2_pm10_24h { get; set; } - public double CO2_temperature { get; set; } - public double CO2_humidity { get; set; } - - public int LeakSensor1 { get; set; } - public int LeakSensor2 { get; set; } - public int LeakSensor3 { get; set; } - public int LeakSensor4 { get; set; } - - public double LightningDistance { get; set; } - public DateTime LightningTime { get; set; } - public int LightningStrikesToday { get; set; } - - public double LeafTemp1 { get; set; } - public double LeafTemp2 { get; set; } - public double LeafTemp3 { get; set; } - public double LeafTemp4 { get; set; } - public double LeafTemp5 { get; set; } - public double LeafTemp6 { get; set; } - public double LeafTemp7 { get; set; } - public double LeafTemp8 { get; set; } - - public int LeafWetness1 { get; set; } - public int LeafWetness2 { get; set; } - public int LeafWetness3 { get; set; } - public int LeafWetness4 { get; set; } - public int LeafWetness5 { get; set; } - public int LeafWetness6 { get; set; } - public int LeafWetness7 { get; set; } - public int LeafWetness8 { get; set; } - - public double SunshineHours { get; set; } = 0; - - public double YestSunshineHours { get; set; } = 0; - - public double SunshineToMidnight { get; set; } - - public double SunHourCounter { get; set; } - - public double StartOfDaySunHourCounter { get; set; } - - public double CurrentSolarMax { get; set; } - - public double RG11RainToday { get; set; } - - public double RainSinceMidnight { get; set; } - - /// - /// Checks whether a new day has started and does a rollover if necessary - /// - /// - /* - public void CheckForRollover(int oadate) - { - if (oadate != LastDailySummaryOADate) - { - DoRollover(); - } - } - */ - - /* - private void DoRollover() - { - //throw new NotImplementedException(); - } - */ - - /// - /// - /// - /// - /// - /// Difference in minutes - /* - private int TimeDiff(DateTime later, DateTime earlier) - { - TimeSpan diff = later - earlier; - - return (int) Math.Round(diff.TotalMinutes); - } - */ - - public void StartMinuteTimer() - { - lastMinute = DateTime.Now.Minute; - lastHour = DateTime.Now.Hour; - secondTimer = new Timer(500); - secondTimer.Elapsed += SecondTimer; - secondTimer.Start(); - } - - public void StopMinuteTimer() - { - if (secondTimer != null) secondTimer.Stop(); - } - - public void SecondTimer(object sender, ElapsedEventArgs e) - { - var timeNow = DateTime.Now; // b3085 change to using a single fixed point in time to make it independent of how long the process takes - - if (timeNow.Minute != lastMinute) - { - lastMinute = timeNow.Minute; - - if ((timeNow.Minute % 10) == 0) - { - TenMinuteChanged(); - } - - if (timeNow.Hour != lastHour) - { - lastHour = timeNow.Hour; - HourChanged(timeNow); - } - - MinuteChanged(timeNow); - - if (DataStopped) - { - // No data coming in, do not do anything else - return; - } - } - - if ((int)timeNow.TimeOfDay.TotalMilliseconds % 2500 <= 500) - { - // send current data to websocket every 3 seconds - try - { - StringBuilder windRoseData = new StringBuilder(80); - - lock (windcounts) - { - windRoseData.Append((windcounts[0] * cumulus.Calib.WindGust.Mult).ToString(cumulus.WindFormat, CultureInfo.InvariantCulture)); - - for (var i = 1; i < cumulus.NumWindRosePoints; i++) - { - windRoseData.Append(","); - windRoseData.Append((windcounts[i] * cumulus.Calib.WindGust.Mult).ToString(cumulus.WindFormat, CultureInfo.InvariantCulture)); - } - } - - string stormRainStart = StartOfStorm == DateTime.MinValue ? "-----" : StartOfStorm.ToString("d"); - - var data = new DataStruct(cumulus, OutdoorTemperature, OutdoorHumidity, TempTotalToday / tempsamplestoday, IndoorTemperature, OutdoorDewpoint, WindChill, IndoorHumidity, - Pressure, WindLatest, WindAverage, RecentMaxGust, WindRunToday, Bearing, AvgBearing, RainToday, RainYesterday, RainMonth, RainYear, RainRate, - RainLastHour, HeatIndex, Humidex, ApparentTemperature, temptrendval, presstrendval, HiLoToday.HighGust, HiLoToday.HighGustTime.ToString("HH:mm"), HiLoToday.HighWind, - HiLoToday.HighGustBearing, cumulus.Units.WindText, BearingRangeFrom10, BearingRangeTo10, windRoseData.ToString(), HiLoToday.HighTemp, HiLoToday.LowTemp, - HiLoToday.HighTempTime.ToString("HH:mm"), HiLoToday.LowTempTime.ToString("HH:mm"), HiLoToday.HighPress, HiLoToday.LowPress, HiLoToday.HighPressTime.ToString("HH:mm"), - HiLoToday.LowPressTime.ToString("HH:mm"), HiLoToday.HighRainRate, HiLoToday.HighRainRateTime.ToString("HH:mm"), HiLoToday.HighHumidity, HiLoToday.LowHumidity, - HiLoToday.HighHumidityTime.ToString("HH:mm"), HiLoToday.LowHumidityTime.ToString("HH:mm"), cumulus.Units.PressText, cumulus.Units.TempText, cumulus.Units.RainText, - HiLoToday.HighDewPoint, HiLoToday.LowDewPoint, HiLoToday.HighDewPointTime.ToString("HH:mm"), HiLoToday.LowDewPointTime.ToString("HH:mm"), HiLoToday.LowWindChill, - HiLoToday.LowWindChillTime.ToString("HH:mm"), (int)SolarRad, (int)HiLoToday.HighSolar, HiLoToday.HighSolarTime.ToString("HH:mm"), UV, HiLoToday.HighUv, - HiLoToday.HighUvTime.ToString("HH:mm"), forecaststr, getTimeString(cumulus.SunRiseTime), getTimeString(cumulus.SunSetTime), - getTimeString(cumulus.MoonRiseTime), getTimeString(cumulus.MoonSetTime), HiLoToday.HighHeatIndex, HiLoToday.HighHeatIndexTime.ToString("HH:mm"), HiLoToday.HighAppTemp, - HiLoToday.LowAppTemp, HiLoToday.HighAppTempTime.ToString("HH:mm"), HiLoToday.LowAppTempTime.ToString("HH:mm"), (int)CurrentSolarMax, - AllTime.HighPress.Val, AllTime.LowPress.Val, SunshineHours, CompassPoint(DominantWindBearing), LastRainTip, - HiLoToday.HighHourlyRain, HiLoToday.HighHourlyRainTime.ToString("HH:mm"), "F" + cumulus.Beaufort(HiLoToday.HighWind), "F" + cumulus.Beaufort(WindAverage), cumulus.BeaufortDesc(WindAverage), - LastDataReadTimestamp.ToString("HH:mm:ss"), DataStopped, StormRain, stormRainStart, CloudBase, cumulus.CloudBaseInFeet ? "ft" : "m", RainLast24Hour, - cumulus.LowTempAlarm.Triggered, cumulus.HighTempAlarm.Triggered, cumulus.TempChangeAlarm.UpTriggered, cumulus.TempChangeAlarm.DownTriggered, cumulus.HighRainTodayAlarm.Triggered, cumulus.HighRainRateAlarm.Triggered, - cumulus.LowPressAlarm.Triggered, cumulus.HighPressAlarm.Triggered, cumulus.PressChangeAlarm.UpTriggered, cumulus.PressChangeAlarm.DownTriggered, cumulus.HighGustAlarm.Triggered, cumulus.HighWindAlarm.Triggered, - cumulus.SensorAlarm.Triggered, cumulus.BatteryLowAlarm.Triggered, cumulus.SpikeAlarm.Triggered, cumulus.UpgradeAlarm.Triggered, - cumulus.HttpUploadAlarm.Triggered, cumulus.MySqlUploadAlarm.Triggered, - FeelsLike, HiLoToday.HighFeelsLike, HiLoToday.HighFeelsLikeTime.ToString("HH:mm"), HiLoToday.LowFeelsLike, HiLoToday.LowFeelsLikeTime.ToString("HH:mm"), - HiLoToday.HighHumidex, HiLoToday.HighHumidexTime.ToString("HH:mm")); - - //var json = jss.Serialize(data); - - var ser = new DataContractJsonSerializer(typeof(DataStruct)); - - var stream = new MemoryStream(); - - ser.WriteObject(stream, data); - - stream.Position = 0; - - WebSocket.SendMessage(new StreamReader(stream).ReadToEnd()); - } - catch (Exception ex) - { - cumulus.LogMessage(ex.Message); - } - } - } - - private string getTimeString(DateTime time) - { - if (time <= DateTime.MinValue) - { - return "-----"; - } - else - { - return time.ToString("HH:mm"); - } - } - - private string getTimeString(TimeSpan timespan) - { - try - { - if (timespan.TotalSeconds < 0) - { - return "-----"; - } - - DateTime dt = DateTime.MinValue.Add(timespan); - - return getTimeString(dt); - } - catch (Exception e) - { - cumulus.LogMessage($"getTimeString: Exception caught - {e.Message}"); - return "-----"; - } - } - - private void HourChanged(DateTime now) - { - cumulus.LogMessage("Hour changed: " + now.Hour); - cumulus.DoSunriseAndSunset(); - cumulus.DoMoonImage(); - - if (cumulus.HourlyForecast) - { - DoForecast("", true); - } - - if (DataStopped) - { - // No data coming in, do not do anything else - return; - } - - - if (now.Hour == 0) - { - ResetMidnightRain(now); - //RecalcSolarFactor(now); - } - - int rollHour = Math.Abs(cumulus.GetHourInc()); - - if (now.Hour == rollHour) - { - DayReset(now); - cumulus.BackupData(true, now); - } - - if (now.Hour == 0) - { - ResetSunshineHours(); - } - - RemoveOldRecentData(now); - } - - private void RemoveOldRecentData(DateTime ts) - { - var deleteTime = ts.AddDays(-7); - - RecentDataDb.Execute("delete from RecentData where Timestamp < ?", deleteTime); - } - - private void ClearAlarms() - { - if (cumulus.DataStoppedAlarm.Latch && cumulus.DataStoppedAlarm.Triggered && DateTime.Now > cumulus.DataStoppedAlarm.TriggeredTime.AddHours(cumulus.DataStoppedAlarm.LatchHours)) - cumulus.DataStoppedAlarm.Triggered = false; - - if (cumulus.BatteryLowAlarm.Latch && cumulus.BatteryLowAlarm.Triggered && DateTime.Now > cumulus.BatteryLowAlarm.TriggeredTime.AddHours(cumulus.BatteryLowAlarm.LatchHours)) - cumulus.BatteryLowAlarm.Triggered = false; - - if (cumulus.SensorAlarm.Latch && cumulus.SensorAlarm.Triggered && DateTime.Now > cumulus.SensorAlarm.TriggeredTime.AddHours(cumulus.SensorAlarm.LatchHours)) - cumulus.SensorAlarm.Triggered = false; - - if (cumulus.SpikeAlarm.Latch && cumulus.SpikeAlarm.Triggered && DateTime.Now > cumulus.SpikeAlarm.TriggeredTime.AddHours(cumulus.SpikeAlarm.LatchHours)) - cumulus.SpikeAlarm.Triggered = false; - - if (cumulus.UpgradeAlarm.Latch && cumulus.UpgradeAlarm.Triggered && DateTime.Now > cumulus.UpgradeAlarm.TriggeredTime.AddHours(cumulus.UpgradeAlarm.LatchHours)) - cumulus.UpgradeAlarm.Triggered = false; - - if (cumulus.HttpUploadAlarm.Latch && cumulus.HttpUploadAlarm.Triggered && DateTime.Now > cumulus.HttpUploadAlarm.TriggeredTime.AddHours(cumulus.HttpUploadAlarm.LatchHours)) - cumulus.HttpUploadAlarm.Triggered = false; - - if (cumulus.MySqlUploadAlarm.Latch && cumulus.MySqlUploadAlarm.Triggered && DateTime.Now > cumulus.MySqlUploadAlarm.TriggeredTime.AddHours(cumulus.MySqlUploadAlarm.LatchHours)) - cumulus.MySqlUploadAlarm.Triggered = false; - - if (cumulus.HighWindAlarm.Latch && cumulus.HighWindAlarm.Triggered && DateTime.Now > cumulus.HighWindAlarm.TriggeredTime.AddHours(cumulus.HighWindAlarm.LatchHours)) - cumulus.HighWindAlarm.Triggered = false; - - if (cumulus.HighGustAlarm.Latch && cumulus.HighGustAlarm.Triggered && DateTime.Now > cumulus.HighGustAlarm.TriggeredTime.AddHours(cumulus.HighGustAlarm.LatchHours)) - cumulus.HighGustAlarm.Triggered = false; - - if (cumulus.HighRainRateAlarm.Latch && cumulus.HighRainRateAlarm.Triggered && DateTime.Now > cumulus.HighRainRateAlarm.TriggeredTime.AddHours(cumulus.HighRainRateAlarm.LatchHours)) - cumulus.HighRainRateAlarm.Triggered = false; - - if (cumulus.HighRainTodayAlarm.Latch && cumulus.HighRainTodayAlarm.Triggered && DateTime.Now > cumulus.HighRainTodayAlarm.TriggeredTime.AddHours(cumulus.HighRainTodayAlarm.LatchHours)) - cumulus.HighRainTodayAlarm.Triggered = false; - - if (cumulus.HighPressAlarm.Latch && cumulus.HighPressAlarm.Triggered && DateTime.Now > cumulus.HighPressAlarm.TriggeredTime.AddHours(cumulus.HighPressAlarm.LatchHours)) - cumulus.HighPressAlarm.Triggered = false; - - if (cumulus.LowPressAlarm.Latch && cumulus.LowPressAlarm.Triggered && DateTime.Now > cumulus.LowPressAlarm.TriggeredTime.AddHours(cumulus.LowPressAlarm.LatchHours)) - cumulus.LowPressAlarm.Triggered = false; - - if (cumulus.HighTempAlarm.Latch && cumulus.HighTempAlarm.Triggered && DateTime.Now > cumulus.HighTempAlarm.TriggeredTime.AddHours(cumulus.HighTempAlarm.LatchHours)) - cumulus.HighTempAlarm.Triggered = false; - - if (cumulus.LowTempAlarm.Latch && cumulus.LowTempAlarm.Triggered && DateTime.Now > cumulus.LowTempAlarm.TriggeredTime.AddHours(cumulus.LowTempAlarm.LatchHours)) - cumulus.LowTempAlarm.Triggered = false; - - if (cumulus.TempChangeAlarm.Latch && cumulus.TempChangeAlarm.UpTriggered && DateTime.Now > cumulus.TempChangeAlarm.UpTriggeredTime.AddHours(cumulus.TempChangeAlarm.LatchHours)) - cumulus.TempChangeAlarm.UpTriggered = false; - - if (cumulus.TempChangeAlarm.Latch && cumulus.TempChangeAlarm.DownTriggered && DateTime.Now > cumulus.TempChangeAlarm.DownTriggeredTime.AddHours(cumulus.TempChangeAlarm.LatchHours)) - cumulus.TempChangeAlarm.DownTriggered = false; - - if (cumulus.PressChangeAlarm.Latch && cumulus.PressChangeAlarm.UpTriggered && DateTime.Now > cumulus.PressChangeAlarm.UpTriggeredTime.AddHours(cumulus.PressChangeAlarm.LatchHours)) - cumulus.PressChangeAlarm.UpTriggered = false; - - if (cumulus.PressChangeAlarm.Latch && cumulus.PressChangeAlarm.DownTriggered && DateTime.Now > cumulus.PressChangeAlarm.DownTriggeredTime.AddHours(cumulus.PressChangeAlarm.LatchHours)) - cumulus.PressChangeAlarm.DownTriggered = false; - } - - private void MinuteChanged(DateTime now) - { - CheckForDataStopped(); - - if (!DataStopped) - { - CurrentSolarMax = AstroLib.SolarMax(now, cumulus.Longitude, cumulus.Latitude, AltitudeM(cumulus.Altitude), out SolarElevation, cumulus.RStransfactor, cumulus.BrasTurbidity, cumulus.SolarCalc); - if (((Pressure > 0) && TempReadyToPlot && WindReadyToPlot) || cumulus.StationOptions.NoSensorCheck) - { - // increment wind run by one minute's worth of average speed - - WindRunToday += WindAverage * WindRunHourMult[cumulus.Units.Wind] / 60.0; - - CheckForWindrunHighLow(now); - - CalculateDominantWindBearing(AvgBearing, WindAverage, 1); - - if (OutdoorTemperature < cumulus.ChillHourThreshold) - { - // add 1 minute to chill hours - ChillHours += 1.0 / 60.0; - } - - // update sunshine hours - if (cumulus.UseBlakeLarsen) - { - ReadBlakeLarsenData(); - } - else if ((SolarRad > (CurrentSolarMax * cumulus.SunThreshold / 100.0)) && (SolarRad >= cumulus.SolarMinimum)) - { - SunshineHours += 1.0 / 60.0; - } - - // update heating/cooling degree days - UpdateDegreeDays(1); - - weatherDataCollection.Add(new WeatherData - { - //station = this, - DT = System.DateTime.Now, - WindSpeed = WindLatest, - WindAverage = WindAverage, - OutdoorTemp = OutdoorTemperature, - Pressure = Pressure, - Raintotal = RainToday - }); - - while (weatherDataCollection[0].DT < now.AddHours(-1)) - { - weatherDataCollection.RemoveAt(0); - } - - if (!first_temp) - { - // update temperature average items - tempsamplestoday++; - TempTotalToday += OutdoorTemperature; - } - - AddLastHourDataEntry(now, Raincounter, OutdoorTemperature); - RemoveOldLHData(now); - AddLast3HourDataEntry(now, Pressure, OutdoorTemperature); - RemoveOldL3HData(now); - AddGraphDataEntry(now, Raincounter, RainToday, RainRate, OutdoorTemperature, OutdoorDewpoint, ApparentTemperature, WindChill, HeatIndex, - IndoorTemperature, Pressure, WindAverage, RecentMaxGust, AvgBearing, Bearing, OutdoorHumidity, IndoorHumidity, SolarRad, CurrentSolarMax, UV, FeelsLike, Humidex); - RemoveOldGraphData(now); - DoTrendValues(now); - AddRecentDataEntry(now, WindAverage, RecentMaxGust, WindLatest, Bearing, AvgBearing, OutdoorTemperature, WindChill, OutdoorDewpoint, HeatIndex, OutdoorHumidity, - Pressure, RainToday, SolarRad, UV, Raincounter, FeelsLike, Humidex); - - if (now.Minute % cumulus.logints[cumulus.DataLogInterval] == 0) - { - cumulus.DoLogFile(now, true); - - if (cumulus.StationOptions.LogExtraSensors) - { - cumulus.DoExtraLogFile(now); - } - - if (cumulus.AirLinkInEnabled || cumulus.AirLinkOutEnabled) - { - cumulus.DoAirLinkLogFile(now); - } - } - - // Custom MySQL update - minutes interval - if (cumulus.CustomMySqlMinutesEnabled && now.Minute % cumulus.CustomMySqlMinutesInterval == 0) - { - _ = cumulus.CustomMysqlMinutesTimerTick(); - } - - // Custom HTTP update - minutes interval - if (cumulus.CustomHttpMinutesEnabled && now.Minute % cumulus.CustomHttpMinutesInterval == 0) - { - cumulus.CustomHttpMinutesUpdate(); - } - - if (cumulus.WebIntervalEnabled && cumulus.SynchronisedWebUpdate && (now.Minute % cumulus.UpdateInterval == 0)) - { - if (cumulus.WebUpdating == 1) - { - // Skip this update interval - cumulus.LogMessage("Warning, previous web update is still in progress, first chance, skipping this interval"); - cumulus.WebUpdating++; - } - else if (cumulus.WebUpdating >= 2) - { - cumulus.LogMessage("Warning, previous web update is still in progress,second chance, aborting connection"); - if (cumulus.ftpThread.ThreadState == System.Threading.ThreadState.Running) - cumulus.ftpThread.Abort(); - cumulus.LogMessage("Trying new web update"); - cumulus.WebUpdating = 1; - cumulus.ftpThread = new Thread(cumulus.DoHTMLFiles); - cumulus.ftpThread.IsBackground = true; - cumulus.ftpThread.Start(); - } - else - { - cumulus.WebUpdating = 1; - cumulus.ftpThread = new Thread(cumulus.DoHTMLFiles); - cumulus.ftpThread.IsBackground = true; - cumulus.ftpThread.Start(); - } - } - - if (cumulus.Wund.Enabled && (now.Minute % cumulus.Wund.Interval == 0) && cumulus.Wund.SynchronisedUpdate && !String.IsNullOrWhiteSpace(cumulus.Wund.ID)) - { - cumulus.UpdateWunderground(now); - } - - if (cumulus.Windy.Enabled && (now.Minute % cumulus.Windy.Interval == 0) && !String.IsNullOrWhiteSpace(cumulus.Windy.ApiKey)) - { - cumulus.UpdateWindy(now); - } - - if (cumulus.WindGuru.Enabled && (now.Minute % cumulus.WindGuru.Interval == 0) && !String.IsNullOrWhiteSpace(cumulus.WindGuru.ID)) - { - cumulus.UpdateWindGuru(now); - } - - if (cumulus.AWEKAS.Enabled && (now.Minute % ((double)cumulus.AWEKAS.Interval / 60) == 0) && cumulus.AWEKAS.SynchronisedUpdate && !String.IsNullOrWhiteSpace(cumulus.AWEKAS.ID)) - { - cumulus.UpdateAwekas(now); - } - - if (cumulus.WCloud.Enabled && (now.Minute % cumulus.WCloud.Interval == 0) && !String.IsNullOrWhiteSpace(cumulus.WCloud.ID)) - { - cumulus.UpdateWCloud(now); - } - - if (cumulus.OpenWeatherMap.Enabled && (now.Minute % cumulus.OpenWeatherMap.Interval == 0) && !string.IsNullOrWhiteSpace(cumulus.OpenWeatherMap.ID)) - { - cumulus.UpdateOpenWeatherMap(now); - } - - if (cumulus.PWS.Enabled && (now.Minute % cumulus.PWS.Interval == 0) && !String.IsNullOrWhiteSpace(cumulus.PWS.ID) && !String.IsNullOrWhiteSpace(cumulus.PWS.PW)) - { - cumulus.UpdatePWSweather(now); - } - - if (cumulus.WOW.Enabled && (now.Minute % cumulus.WOW.Interval == 0) && !String.IsNullOrWhiteSpace(cumulus.WOW.ID) && !String.IsNullOrWhiteSpace(cumulus.WOW.PW)) - { - cumulus.UpdateWOW(now); - } - - if (cumulus.APRS.Enabled && (now.Minute % cumulus.APRS.Interval == 0) && !String.IsNullOrWhiteSpace(cumulus.APRS.ID)) - { - UpdateAPRS(); - } - - if (cumulus.Twitter.Enabled && (now.Minute % cumulus.Twitter.Interval == 0) && !String.IsNullOrWhiteSpace(cumulus.Twitter.ID) && !String.IsNullOrWhiteSpace(cumulus.Twitter.PW)) - { - cumulus.UpdateTwitter(); - } - - if (cumulus.xapEnabled) - { - using (Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) - { - IPEndPoint iep1 = new IPEndPoint(IPAddress.Broadcast, cumulus.xapPort); - - byte[] data = Encoding.ASCII.GetBytes(cumulus.xapHeartbeat); - - sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1); - - sock.SendTo(data, iep1); - - var timeUTC = now.ToUniversalTime().ToString("HH:mm"); - var dateISO = now.ToUniversalTime().ToString("yyyyMMdd"); - - var xapReport = new StringBuilder("", 1024); - xapReport.Append("xap-header\n{\nv=12\nhop=1\n"); - xapReport.Append($"uid=FF{cumulus.xapUID}00\n"); - xapReport.Append("class=weather.report\n"); - xapReport.Append($"source={cumulus.xapsource}\n"); - xapReport.Append("}\n"); - xapReport.Append("weather.report\n{\n"); - xapReport.Append($"UTC={timeUTC}\nDATE={dateISO}\n"); - xapReport.Append($"WindM={ConvertUserWindToMPH(WindAverage):F1}\n"); - xapReport.Append($"WindK={ConvertUserWindToKPH(WindAverage):F1}\n"); - xapReport.Append($"WindGustsM={ConvertUserWindToMPH(RecentMaxGust):F1}\n"); - xapReport.Append($"WindGustsK={ConvertUserWindToKPH(RecentMaxGust):F1}\n"); - xapReport.Append($"WindDirD={Bearing}\n"); - xapReport.Append($"WindDirC={AvgBearing}\n"); - xapReport.Append($"TempC={ConvertUserTempToC(OutdoorTemperature):F1}\n"); - xapReport.Append($"TempF={ConvertUserTempToF(OutdoorTemperature):F1}\n"); - xapReport.Append($"DewC={ConvertUserTempToC(OutdoorDewpoint):F1}\n"); - xapReport.Append($"DewF={ConvertUserTempToF(OutdoorDewpoint):F1}\n"); - xapReport.Append($"AirPressure={ConvertUserPressToMB(Pressure):F1}\n"); - xapReport.Append($"Rain={ConvertUserRainToMM(RainToday):F1}\n"); - xapReport.Append("}"); - - data = Encoding.ASCII.GetBytes(xapReport.ToString()); - - sock.SendTo(data, iep1); - - sock.Close(); - } - } - - var wxfile = cumulus.StdWebFiles.SingleOrDefault(item => item.LocalFileName == "wxnow.txt"); - if (wxfile.Create) - { - CreateWxnowFile(); - } - } - else - { - cumulus.LogMessage("Minimum data set of pressure, temperature, and wind is not available and NoSensorCheck is not enabled. Skip processing"); - } - } - - // Check for a new version of Cumulus once a day - if (now.Minute == versionCheckTime.Minute && now.Hour == versionCheckTime.Hour) - { - cumulus.LogMessage("Checking for latest Cumulus MX version..."); - cumulus.GetLatestVersion(); - } - - // If not on windows, check for CPU temp - if (Type.GetType("Mono.Runtime") != null && File.Exists("/sys/class/thermal/thermal_zone0/temp")) - { - try - { - var raw = File.ReadAllText(@"/sys/class/thermal/thermal_zone0/temp"); - cumulus.CPUtemp = ConvertTempCToUser(double.Parse(raw) / 1000); - cumulus.LogDebugMessage($"Current CPU temp = {cumulus.CPUtemp.ToString(cumulus.TempFormat)}{cumulus.Units.TempText}"); - } - catch (Exception ex) - { - cumulus.LogDebugMessage($"Error reading CPU temperature - {ex.Message}"); - } - } - } - - private void TenMinuteChanged() - { - cumulus.DoMoonPhase(); - cumulus.MoonAge = MoonriseMoonset.MoonAge(); - - cumulus.RotateLogFiles(); - - ClearAlarms(); - } - - private void CheckForDataStopped() - { - // Check whether we have read data since the last clock minute. - if ((LastDataReadTimestamp != DateTime.MinValue) && (LastDataReadTimestamp == SavedLastDataReadTimestamp) && (LastDataReadTimestamp < DateTime.Now)) - { - // Data input appears to have has stopped - DataStopped = true; - cumulus.DataStoppedAlarm.Triggered = true; - /*if (RestartIfDataStops) - { - cumulus.LogMessage("*** Data input appears to have stopped, restarting"); - ApplicationExec(ParamStr(0), '', SW_SHOW); - TerminateProcess(GetCurrentProcess, 0); - }*/ - if (cumulus.ReportDataStoppedErrors) - { - cumulus.LogMessage("*** Data input appears to have stopped"); - } - } - else - { - DataStopped = false; - cumulus.DataStoppedAlarm.Triggered = false; - } - - // save the time that data was last read so we can check in a minute's time that it's changed - SavedLastDataReadTimestamp = LastDataReadTimestamp; - } - - private void ReadBlakeLarsenData() - { - var blFile = cumulus.AppDir + "SRsunshine.dat"; - - if (File.Exists(blFile)) - { - using (var sr = new StreamReader(blFile)) - { - try - { - string line = sr.ReadLine(); - SunshineHours = double.Parse(line, CultureInfo.InvariantCulture.NumberFormat); - sr.ReadLine(); - sr.ReadLine(); - line = sr.ReadLine(); - IsSunny = (line == "True"); - } - catch (Exception ex) - { - cumulus.LogMessage("Error reading SRsunshine.dat: " + ex.Message); - } - } - } - } - - /* - internal void UpdateDatabase(DateTime timestamp, int interval, bool updateHighsAndLows) - // Add an entry to the database - { - double raininterval; - - if (prevraincounter == 0) - { - raininterval = 0; - } - else - { - raininterval = Raincounter - prevraincounter; - } - - //using (cumulusEntities dataContext = new cumulusEntities()) - //{ - // dataContext.AddToStandardData(newdata); - - // // Submit the change to the database. - // try - // { - // dataContext.SaveChanges(); - // } - // catch (Exception ex) - // { - // Trace.WriteLine(ex.ToString()); - // Trace.Flush(); - // } - - // reset highs and lows since last update - loOutdoorTemperature = OutdoorTemperature; - hiOutdoorTemperature = OutdoorTemperature; - loIndoorTemperature = IndoorTemperature; - hiIndoorTemperature = IndoorTemperature; - loIndoorHumidity = IndoorHumidity; - hiIndoorHumidity = IndoorHumidity; - loOutdoorHumidity = OutdoorHumidity; - hiOutdoorHumidity = OutdoorHumidity; - loPressure = Pressure; - hiPressure = Pressure; - hiWind = WindAverage; - hiGust = WindLatest; - hiWindBearing = Bearing; - hiGustBearing = Bearing; - prevraincounter = Raincounter; - hiRainRate = RainRate; - hiDewPoint = OutdoorDewpoint; - loDewPoint = OutdoorDewpoint; - hiHeatIndex = HeatIndex; - hiHumidex = Humidex; - loWindChill = WindChill; - hiApparentTemperature = ApparentTemperature; - loApparentTemperature = ApparentTemperature; - } - */ - - private long DateTimeToUnix(DateTime timestamp) - { - var timeSpan = (timestamp - new DateTime(1970, 1, 1, 0, 0, 0)); - return (long)timeSpan.TotalSeconds; - } - - /* - private long DateTimeToJS(DateTime timestamp) - { - return DateTimeToUnix(timestamp) * 1000; - } - */ - - public void CreateGraphDataFiles() - { - // Chart data for Highcharts graphs - string json = ""; - for (var i = 0; i < cumulus.GraphDataFiles.Length; i++) - { - // We double up the meaning of .FtpRequired to creation as well. - // The FtpRequired flag is only cleared for the config files that are pretty static so it is pointless - // recreating them every update too. - if (cumulus.GraphDataFiles[i].Create && cumulus.GraphDataFiles[i].CreateRequired) - { - switch (cumulus.GraphDataFiles[i].LocalFileName) - { - case "graphconfig.json": - json = GetGraphConfig(); - break; - case "availabledata.json": - json = GetAvailGraphData(); - break; - case "tempdata.json": - json = GetTempGraphData(); - break; - case "pressdata.json": - json = GetPressGraphData(); - break; - case "winddata.json": - json = GetWindGraphData(); - break; - case "wdirdata.json": - json = GetWindDirGraphData(); - break; - case "humdata.json": - json = GetHumGraphData(); - break; - case "raindata.json": - json = GetRainGraphData(); - break; - case "dailyrain.json": - json = GetDailyRainGraphData(); - break; - case "dailytemp.json": - json = GetDailyTempGraphData(); - break; - case "solardata.json": - json = GetSolarGraphData(); - break; - case "sunhours.json": - json = GetSunHoursGraphData(); - break; - case "airquality.json": - json = GetAqGraphData(); - break; - } - - try - { - var dest = cumulus.GraphDataFiles[i].LocalPath + cumulus.GraphDataFiles[i].LocalFileName; - using (var file = new StreamWriter(dest, false)) - { - file.WriteLine(json); - file.Close(); - } - - // The config files only need creating once per change - if (cumulus.GraphDataFiles[i].LocalFileName == "availabledata.json" || cumulus.GraphDataFiles[i].LocalFileName == "graphconfig.json") - { - cumulus.GraphDataFiles[i].CreateRequired = false; - } - } - catch (Exception ex) - { - cumulus.LogMessage($"Error writing {cumulus.GraphDataFiles[i].LocalFileName}: {ex}"); - } - } - } - } - - public void CreateEodGraphDataFiles() - { - string json = ""; - for (var i = 0; i < cumulus.GraphDataEodFiles.Length; i++) - { - if (cumulus.GraphDataEodFiles[i].Create) - { - switch (cumulus.GraphDataEodFiles[i].LocalFileName) - { - case "alldailytempdata.json": - json = GetAllDailyTempGraphData(); - break; - case "alldailypressdata.json": - json = GetAllDailyPressGraphData(); - break; - case "alldailywinddata.json": - json = GetAllDailyWindGraphData(); - break; - case "alldailyhumdata.json": - json = GetAllDailyHumGraphData(); - break; - case "alldailyraindata.json": - json = GetAllDailyRainGraphData(); - break; - case "alldailysolardata.json": - json = GetAllDailySolarGraphData(); - break; - case "alldailydegdaydata.json": - json = GetAllDegreeDaysGraphData(); - break; - case "alltempsumdata.json": - json = GetAllTempSumGraphData(); - break; - } - - try - { - var dest = cumulus.GraphDataEodFiles[i].LocalPath + cumulus.GraphDataEodFiles[i].LocalFileName; - using (var file = new StreamWriter(dest, false)) - { - file.WriteLine(json); - file.Close(); - } - // Now set the flag that upload is required (if enabled) - cumulus.GraphDataEodFiles[i].FtpRequired = true; - } - catch (Exception ex) - { - cumulus.LogMessage($"Error writing {cumulus.GraphDataEodFiles[i].LocalFileName}: {ex}"); - } - } - } - } - - public string GetSolarGraphData() - { - var InvC = new CultureInfo(""); - var sb = new StringBuilder("{"); - - lock (GraphDataList) - { - if (cumulus.GraphOptions.UVVisible) - { - sb.Append("\"UV\":["); - for (var i = 0; i < GraphDataList.Count; i++) - { - sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].uvindex.ToString(cumulus.UVFormat, InvC)}]"); - if (i < GraphDataList.Count - 1) - sb.Append(","); - } - sb.Append("]"); - } - - if (cumulus.GraphOptions.SolarVisible) - { - if (cumulus.GraphOptions.UVVisible) - { - sb.Append(","); - } - - sb.Append("\"SolarRad\":["); - for (var i = 0; i < GraphDataList.Count; i++) - { - sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{(int)GraphDataList[i].solarrad}]"); - if (i < GraphDataList.Count - 1) - sb.Append(","); - } - - - sb.Append("],\"CurrentSolarMax\":["); - for (var i = 0; i < GraphDataList.Count; i++) - { - sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{(int)GraphDataList[i].solarmax}]"); - if (i < GraphDataList.Count - 1) - sb.Append(","); - } - sb.Append("]"); - } - } - sb.Append("}"); - return sb.ToString(); - } - - public string GetRainGraphData() - { - var InvC = new CultureInfo(""); - var sb = new StringBuilder("{\"rfall\":["); - lock (GraphDataList) - { - for (var i = 0; i < GraphDataList.Count; i++) - { - sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].RainToday.ToString(cumulus.RainFormat, InvC)}]"); - if (i < GraphDataList.Count - 1) - sb.Append(","); - } - - sb.Append("],\"rrate\":["); - for (var i = 0; i < GraphDataList.Count; i++) - { - sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].rainrate.ToString(cumulus.RainFormat, InvC)}]"); - if (i < GraphDataList.Count - 1) - sb.Append(","); - } - } - sb.Append("]}"); - return sb.ToString(); - } - - public string GetHumGraphData() - { - var sb = new StringBuilder("{", 10240); - var append = false; - lock (GraphDataList) - { - if (cumulus.GraphOptions.OutHumVisible) - { - sb.Append("\"hum\":["); - for (var i = 0; i < GraphDataList.Count; i++) - { - sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].humidity}]"); - if (i < GraphDataList.Count - 1) - sb.Append(","); - } - sb.Append("]"); - append = true; - } - - if (cumulus.GraphOptions.InHumVisible) - { - if (append) - { - sb.Append(","); - } - sb.Append("\"inhum\":["); - for (var i = 0; i < GraphDataList.Count; i++) - { - sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].inhumidity}]"); - if (i < GraphDataList.Count - 1) - sb.Append(","); - } - sb.Append("]"); - } - } - sb.Append("}"); - return sb.ToString(); - } - - public string GetWindDirGraphData() - { - var sb = new StringBuilder("{\"bearing\":["); - lock (GraphDataList) - { - for (var i = 0; i < GraphDataList.Count; i++) - { - sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].winddir}]"); - if (i < GraphDataList.Count - 1) - sb.Append(","); - } - - sb.Append("],\"avgbearing\":["); - for (var i = 0; i < GraphDataList.Count; i++) - { - sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].avgwinddir}]"); - if (i < GraphDataList.Count - 1) - sb.Append(","); - } - } - sb.Append("]}"); - return sb.ToString(); - } - - public string GetWindGraphData() - { - var InvC = new CultureInfo(""); - var sb = new StringBuilder("{\"wgust\":["); - lock (GraphDataList) - { - for (var i = 0; i < GraphDataList.Count; i++) - { - sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].windgust.ToString(cumulus.WindFormat, InvC)}]"); - if (i < GraphDataList.Count - 1) - sb.Append(","); - } - - sb.Append("],\"wspeed\":["); - for (var i = 0; i < GraphDataList.Count; i++) - { - sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].windspeed.ToString(cumulus.WindAvgFormat, InvC)}]"); - if (i < GraphDataList.Count - 1) - sb.Append(","); - } - } - sb.Append("]}"); - return sb.ToString(); - } - - public string GetPressGraphData() - { - var InvC = new CultureInfo(""); - StringBuilder sb = new StringBuilder("{\"press\":["); - lock (GraphDataList) - { - for (var i = 0; i < GraphDataList.Count; i++) - { - sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].pressure.ToString(cumulus.PressFormat, InvC)}]"); - - if (i < GraphDataList.Count - 1) - sb.Append(","); - } - } - sb.Append("]}"); - return sb.ToString(); - } - - public string GetTempGraphData() - { - var InvC = new CultureInfo(""); - var append = false; - StringBuilder sb = new StringBuilder("{", 10240); - lock (GraphDataList) - { - if (cumulus.GraphOptions.InTempVisible) - { - if (append) - { - sb.Append(","); - } - sb.Append("\"intemp\":["); - for (var i = 0; i < GraphDataList.Count; i++) - { - sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].insidetemp.ToString(cumulus.TempFormat, InvC)}]"); - if (i < GraphDataList.Count - 1) - sb.Append(","); - } - sb.Append("]"); - append = true; - } - - if (cumulus.GraphOptions.DPVisible) - { - if (append) - { - sb.Append(","); - } - sb.Append("\"dew\":["); - for (var i = 0; i < GraphDataList.Count; i++) - { - sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].dewpoint.ToString(cumulus.TempFormat, InvC)}]"); - if (i < GraphDataList.Count - 1) - sb.Append(","); - } - sb.Append("]"); - append = true; - } - - if (cumulus.GraphOptions.AppTempVisible) - { - if (append) - { - sb.Append(","); - } - sb.Append("\"apptemp\":["); - for (var i = 0; i < GraphDataList.Count; i++) - { - sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].apptemp.ToString(cumulus.TempFormat, InvC)}]"); - if (i < GraphDataList.Count - 1) - sb.Append(","); - } - sb.Append("]"); - append = true; - } - - if (cumulus.GraphOptions.FeelsLikeVisible) - { - if (append) - { - sb.Append(","); - } - sb.Append("\"feelslike\":["); - for (var i = 0; i < GraphDataList.Count; i++) - { - sb.Append("[" + DateTimeToUnix(GraphDataList[i].timestamp) * 1000 + "," + GraphDataList[i].feelslike.ToString(cumulus.TempFormat, InvC) + "]"); - if (i < GraphDataList.Count - 1) - sb.Append(","); - } - sb.Append("]"); - append = true; - } - - if (cumulus.GraphOptions.WCVisible) - { - if (append) - { - sb.Append(","); - } - sb.Append("\"wchill\":["); - for (var i = 0; i < GraphDataList.Count; i++) - { - sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].windchill.ToString(cumulus.TempFormat, InvC)}]"); - if (i < GraphDataList.Count - 1) - sb.Append(","); - } - sb.Append("]"); - append = true; - } - - if (cumulus.GraphOptions.HIVisible) - { - if (append) - { - sb.Append(","); - } - sb.Append("\"heatindex\":["); - for (var i = 0; i < GraphDataList.Count; i++) - { - sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].heatindex.ToString(cumulus.TempFormat, InvC)}]"); - if (i < GraphDataList.Count - 1) - sb.Append(","); - } - sb.Append("]"); - append = true; - } - - if (cumulus.GraphOptions.TempVisible) - { - if (append) - { - sb.Append(","); - } - sb.Append("\"temp\":["); - for (var i = 0; i < GraphDataList.Count; i++) - { - sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].temperature.ToString(cumulus.TempFormat, InvC)}]"); - if (i < GraphDataList.Count - 1) - sb.Append(","); - } - sb.Append("]"); - append = true; - } - - if (cumulus.GraphOptions.HumidexVisible) - { - if (append) - { - sb.Append(","); - } - sb.Append("\"humidex\":["); - for (var i = 0; i < GraphDataList.Count; i++) - { - sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].humidex.ToString(cumulus.TempFormat, InvC)}]"); - if (i < GraphDataList.Count - 1) - sb.Append(","); - } - sb.Append("]"); - append = true; - } - } - sb.Append("}"); - return sb.ToString(); - } - - public string GetAqGraphData() - { - var InvC = new CultureInfo(""); - var sb = new StringBuilder("{"); - // Check if we are to generate AQ data at all. Only if a primary sensor is defined and it isn't the Indoor AirLink - if (cumulus.StationOptions.PrimaryAqSensor > (int)Cumulus.PrimaryAqSensor.Undefined - && cumulus.StationOptions.PrimaryAqSensor != (int)Cumulus.PrimaryAqSensor.AirLinkIndoor) - { - sb.Append("\"pm2p5\":["); - lock (GraphDataList) - { - for (var i = 0; i < GraphDataList.Count; i++) - { - var val = GraphDataList[i].pm2p5 == -1 ? "null" : GraphDataList[i].pm2p5.ToString("F1", InvC); - sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{val}]"); - if (i < GraphDataList.Count - 1) - sb.Append(","); - } - sb.Append("]"); - // Only the AirLink and Ecowitt CO2 servers provide PM10 values at the moment - if (cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.AirLinkOutdoor || - cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.AirLinkIndoor || - cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.EcowittCO2) - { - sb.Append(",\"pm10\":["); - for (var i = 0; i < GraphDataList.Count; i++) - { - var val = GraphDataList[i].pm10 == -1 ? "null" : GraphDataList[i].pm10.ToString("F1", InvC); - sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{val}]"); - if (i < GraphDataList.Count - 1) - sb.Append(","); - } - sb.Append("]"); - } - } - } - - sb.Append("}"); - return sb.ToString(); - } - - public void AddRecentDataEntry(DateTime timestamp, double windAverage, double recentMaxGust, double windLatest, int bearing, int avgBearing, double outsidetemp, - double windChill, double dewpoint, double heatIndex, int humidity, double pressure, double rainToday, double solarRad, double uv, double rainCounter, double feelslike, double humidex) - { - try - { - RecentDataDb.InsertOrReplace(new RecentData() - { - Timestamp = timestamp, - DewPoint = dewpoint, - HeatIndex = heatIndex, - Humidity = humidity, - OutsideTemp = outsidetemp, - Pressure = pressure, - RainToday = rainToday, - SolarRad = (int)solarRad, - UV = uv, - WindAvgDir = avgBearing, - WindGust = recentMaxGust, - WindLatest = windLatest, - WindChill = windChill, - WindDir = bearing, - WindSpeed = windAverage, - raincounter = rainCounter, - FeelsLike = feelslike, - Humidex = humidex - }); - } - catch (Exception ex) - { - cumulus.LogDebugMessage("AddRecentDataEntry: " + ex.Message); - } - } - - private void CreateWxnowFile() - { - // Jun 01 2003 08:07 - // 272/000g006t069r010p030P020h61b10150 - - // 272 - wind direction - 272 degrees - // 010 - wind speed - 10 mph - - // g015 - wind gust - 15 mph - // t069 - temperature - 69 degrees F - // r010 - rain in last hour in hundredths of an inch - 0.1 inches - // p030 - rain in last 24 hours in hundredths of an inch - 0.3 inches - // P020 - rain since midnight in hundredths of an inch - 0.2 inches - // h61 - humidity 61% (00 = 100%) - // b10153 - barometric pressure in tenths of a millibar - 1015.3 millibars - - var filename = cumulus.AppDir + cumulus.WxnowFile; - var timestamp = DateTime.Now.ToString(@"MMM dd yyyy HH\:mm"); - - int mphwind = Convert.ToInt32(ConvertUserWindToMPH(WindAverage)); - int mphgust = Convert.ToInt32(ConvertUserWindToMPH(RecentMaxGust)); - // ftemp = trunc(TempF(OutsideTemp)); - string ftempstr = APRStemp(OutdoorTemperature); - int in100rainlasthour = Convert.ToInt32(ConvertUserRainToIn(RainLastHour) * 100); - int in100rainlast24hours = Convert.ToInt32(ConvertUserRainToIn(RainLast24Hour) * 100); - int in100raintoday; - if (cumulus.RolloverHour == 0) - // use today's rain for safety - in100raintoday = Convert.ToInt32(ConvertUserRainToIn(RainToday) * 100); - else - // 0900 day, use midnight calculation - in100raintoday = Convert.ToInt32(ConvertUserRainToIn(RainSinceMidnight) * 100); - int mb10press = Convert.ToInt32(ConvertUserPressToMB(AltimeterPressure) * 10); - // For 100% humidity, send zero. For zero humidity, send 1 - int hum; - if (OutdoorHumidity == 0) - hum = 1; - else if (OutdoorHumidity == 100) - hum = 0; - else - hum = OutdoorHumidity; - - string data = String.Format("{0:000}/{1:000}g{2:000}t{3}r{4:000}p{5:000}P{6:000}h{7:00}b{8:00000}", AvgBearing, mphwind, mphgust, ftempstr, in100rainlasthour, - in100rainlast24hours, in100raintoday, hum, mb10press); - - if (cumulus.APRS.SendSolar) - { - data += APRSsolarradStr(SolarRad); - } - - using (StreamWriter file = new StreamWriter(filename, false)) - { - file.WriteLine(timestamp); - file.WriteLine(data); - file.Close(); - } - } - - private string APRSsolarradStr(double solarRad) - { - if (solarRad < 1000) - { - return 'L' + (Convert.ToInt32(solarRad)).ToString("D3"); - } - else - { - return 'l' + (Convert.ToInt32(solarRad - 1000)).ToString("D3"); - } - } - - private string APRStemp(double temp) - { - // input is in TempUnit units, convert to F for APRS - // and return three digits - int num; - - if (cumulus.Units.Temp == 0) - { - num = Convert.ToInt32(((temp * 1.8) + 32)); - } - - else - { - num = Convert.ToInt32(temp); - } - - if (num < 0) - { - num = -num; - return '-' + num.ToString("00"); - } - else - { - return num.ToString("000"); - } - } - - private double ConvertUserRainToIn(double value) - { - if (cumulus.Units.Rain == 1) - { - return value; - } - else - { - return value / 25.4; - } - } - - public double ConvertUserWindToMPH(double value) - { - switch (cumulus.Units.Wind) - { - case 0: - return value * 2.23693629; - case 1: - return value; - case 2: - return value * 0.621371; - case 3: - return value * 1.15077945; - default: - return 0; - } - } - - public double ConvertUserWindToKnots(double value) - { - switch (cumulus.Units.Wind) - { - case 0: - return value * 1.943844; - case 1: - return value * 0.8689758; - case 2: - return value * 0.5399565; - case 3: - return value; - default: - return 0; - } - } - - - public void ResetSunshineHours() // called at midnight irrespective of rollover time - { - YestSunshineHours = SunshineHours; - - cumulus.LogMessage("Reset sunshine hours, yesterday = " + YestSunshineHours); - - SunshineToMidnight = SunshineHours; - SunshineHours = 0; - StartOfDaySunHourCounter = SunHourCounter; - WriteYesterdayFile(); - } - - /* - private void RecalcSolarFactor(DateTime now) // called at midnight irrespective of rollover time - { - if (cumulus.SolarFactorSummer > 0 && cumulus.SolarFactorWinter > 0) - { - // Calculate the solar factor from the day of the year - // Use a cosine of the difference between summer and winter values - int doy = now.DayOfYear; - // take summer solistice as June 21 or December 21 (N & S hemispheres) - ignore leap years - // sol = day 172 (North) - // sol = day 355 (South) - int sol = cumulus.Latitude >= 0 ? 172 : 355; - int daysSinceSol = (doy - sol) % 365; - double multiplier = Math.Cos((daysSinceSol / 365) * 2 * Math.PI); // range +1/-1 - SolarFactor = (multiplier + 1) / 2; // bring it into the range 0-1 - } - else - { - SolarFactor = -1; - } - } - */ - - public void SwitchToNormalRunning() - { - cumulus.CurrentActivity = "Normal running"; - - DoDayResetIfNeeded(); - DoTrendValues(DateTime.Now); - cumulus.StartTimersAndSensors(); - } - - public void ResetMidnightRain(DateTime timestamp) - { - int mrrday = timestamp.Day; - - int mrrmonth = timestamp.Month; - - if (mrrday != MidnightRainResetDay) - { - midnightraincount = Raincounter; - RainSinceMidnight = 0; - MidnightRainResetDay = mrrday; - cumulus.LogMessage("Midnight rain reset, count = " + Raincounter + " time = " + timestamp.ToShortTimeString()); - if ((mrrday == 1) && (mrrmonth == 1) && (cumulus.StationType == StationTypes.VantagePro)) - { - // special case: rain counter is about to be reset - cumulus.LogMessage("Special case, Davis station on 1st Jan. Set midnight rain count to zero"); - midnightraincount = 0; - } - } - } - - public void DoIndoorHumidity(int hum) - { - IndoorHumidity = hum; - HaveReadData = true; - } - - public void DoIndoorTemp(double temp) - { - IndoorTemperature = temp + cumulus.Calib.InTemp.Offset; - HaveReadData = true; - } - - public void DoOutdoorHumidity(int humpar, DateTime timestamp) - { - // Spike check - if ((previousHum != 999) && (Math.Abs(humpar - previousHum) > cumulus.Spike.HumidityDiff)) - { - cumulus.LogSpikeRemoval("Humidity difference greater than specified; reading ignored"); - cumulus.LogSpikeRemoval($"NewVal={humpar} OldVal={previousHum} SpikeHumidityDiff={cumulus.Spike.HumidityDiff:F1}"); - lastSpikeRemoval = DateTime.Now; - cumulus.SpikeAlarm.Triggered = true; - return; - } - previousHum = humpar; - - if ((humpar >= 98) && cumulus.StationOptions.Humidity98Fix) - { - OutdoorHumidity = 100; - } - else - { - OutdoorHumidity = humpar; - } - - // apply offset and multipliers and round. This is different to C1, which truncates. I'm not sure why C1 does that - OutdoorHumidity = (int)Math.Round((OutdoorHumidity * OutdoorHumidity * cumulus.Calib.Hum.Mult2) + (OutdoorHumidity * cumulus.Calib.Hum.Mult) + cumulus.Calib.Hum.Offset); - - if (OutdoorHumidity < 0) - { - OutdoorHumidity = 0; - } - if (OutdoorHumidity > 100) - { - OutdoorHumidity = 100; - } - - if (OutdoorHumidity > HiLoToday.HighHumidity) - { - HiLoToday.HighHumidity = OutdoorHumidity; - HiLoToday.HighHumidityTime = timestamp; - WriteTodayFile(timestamp, false); - } - if (OutdoorHumidity < HiLoToday.LowHumidity) - { - HiLoToday.LowHumidity = OutdoorHumidity; - HiLoToday.LowHumidityTime = timestamp; - WriteTodayFile(timestamp, false); - } - if (OutdoorHumidity > ThisMonth.HighHumidity.Val) - { - ThisMonth.HighHumidity.Val = OutdoorHumidity; - ThisMonth.HighHumidity.Ts = timestamp; - WriteMonthIniFile(); - } - if (OutdoorHumidity < ThisMonth.LowHumidity.Val) - { - ThisMonth.LowHumidity.Val = OutdoorHumidity; - ThisMonth.LowHumidity.Ts = timestamp; - WriteMonthIniFile(); - } - if (OutdoorHumidity > ThisYear.HighHumidity.Val) - { - ThisYear.HighHumidity.Val = OutdoorHumidity; - ThisYear.HighHumidity.Ts = timestamp; - WriteYearIniFile(); - } - if (OutdoorHumidity < ThisYear.LowHumidity.Val) - { - ThisYear.LowHumidity.Val = OutdoorHumidity; - ThisYear.LowHumidity.Ts = timestamp; - WriteYearIniFile(); - } - if (OutdoorHumidity > AllTime.HighHumidity.Val) - { - SetAlltime(AllTime.HighHumidity, OutdoorHumidity, timestamp); - } - CheckMonthlyAlltime("HighHumidity", OutdoorHumidity, true, timestamp); - if (OutdoorHumidity < AllTime.LowHumidity.Val) - { - SetAlltime(AllTime.LowHumidity, OutdoorHumidity, timestamp); - } - CheckMonthlyAlltime("LowHumidity", OutdoorHumidity, false, timestamp); - HaveReadData = true; - } - - public double CalibrateTemp(double temp) - { - return (temp * temp * cumulus.Calib.Temp.Mult2) + (temp * cumulus.Calib.Temp.Mult) + cumulus.Calib.Temp.Offset; - } - - public void DoOutdoorTemp(double temp, DateTime timestamp) - { - // Spike removal is in Celsius - var tempC = ConvertUserTempToC(temp); - if (((Math.Abs(tempC - previousTemp) > cumulus.Spike.TempDiff) && (previousTemp != 999)) || - tempC >= cumulus.Limit.TempHigh || tempC <= cumulus.Limit.TempLow) - { - lastSpikeRemoval = DateTime.Now; - cumulus.SpikeAlarm.Triggered = true; - cumulus.LogSpikeRemoval("Temp difference greater than specified; reading ignored"); - cumulus.LogSpikeRemoval($"NewVal={tempC.ToString(cumulus.TempFormat)} OldVal={previousTemp.ToString(cumulus.TempFormat)} SpikeTempDiff={cumulus.Spike.TempDiff.ToString(cumulus.TempFormat)} HighLimit={cumulus.Limit.TempHigh.ToString(cumulus.TempFormat)} LowLimit={cumulus.Limit.TempLow.ToString(cumulus.TempFormat)}"); - return; - } - previousTemp = tempC; - - // UpdateStatusPanel; - // update global temp - OutdoorTemperature = CalibrateTemp(temp); - - double tempinF = ConvertUserTempToF(OutdoorTemperature); - double tempinC = ConvertUserTempToC(OutdoorTemperature); - - first_temp = false; - - // Does this reading set any records or trigger any alarms? - if (OutdoorTemperature > AllTime.HighTemp.Val) - SetAlltime(AllTime.HighTemp, OutdoorTemperature, timestamp); - - cumulus.HighTempAlarm.Triggered = DoAlarm(OutdoorTemperature, cumulus.HighTempAlarm.Value, cumulus.HighTempAlarm.Enabled, true); - - if (OutdoorTemperature < AllTime.LowTemp.Val) - SetAlltime(AllTime.LowTemp, OutdoorTemperature, timestamp); - - cumulus.LowTempAlarm.Triggered = DoAlarm(OutdoorTemperature, cumulus.LowTempAlarm.Value, cumulus.LowTempAlarm.Enabled, false); - - CheckMonthlyAlltime("HighTemp", OutdoorTemperature, true, timestamp); - CheckMonthlyAlltime("LowTemp", OutdoorTemperature, false, timestamp); - - if (OutdoorTemperature > HiLoToday.HighTemp) - { - HiLoToday.HighTemp = OutdoorTemperature; - HiLoToday.HighTempTime = timestamp; - WriteTodayFile(timestamp, false); - } - - if (OutdoorTemperature < HiLoToday.LowTemp) - { - HiLoToday.LowTemp = OutdoorTemperature; - HiLoToday.LowTempTime = timestamp; - WriteTodayFile(timestamp, false); - } - - if (OutdoorTemperature > ThisMonth.HighTemp.Val) - { - ThisMonth.HighTemp.Val = OutdoorTemperature; - ThisMonth.HighTemp.Ts = timestamp; - WriteMonthIniFile(); - } - - if (OutdoorTemperature < ThisMonth.LowTemp.Val) - { - ThisMonth.LowTemp.Val = OutdoorTemperature; - ThisMonth.LowTemp.Ts = timestamp; - WriteMonthIniFile(); - } - - if (OutdoorTemperature > ThisYear.HighTemp.Val) - { - ThisYear.HighTemp.Val = OutdoorTemperature; - ThisYear.HighTemp.Ts = timestamp; - WriteYearIniFile(); - } - - if (OutdoorTemperature < ThisYear.LowTemp.Val) - { - ThisYear.LowTemp.Val = OutdoorTemperature; - ThisYear.LowTemp.Ts = timestamp; - WriteYearIniFile(); - } - - // Calculate temperature range - HiLoToday.TempRange = HiLoToday.HighTemp - HiLoToday.LowTemp; - - if ((cumulus.StationOptions.CalculatedDP || cumulus.DavisStation) && (OutdoorHumidity != 0) && (!cumulus.FineOffsetStation)) - { - // Calculate DewPoint. - // dewpoint = TempinC + ((0.13 * TempinC) + 13.6) * Ln(humidity / 100); - OutdoorDewpoint = ConvertTempCToUser(MeteoLib.DewPoint(tempinC, OutdoorHumidity)); - - CheckForDewpointHighLow(timestamp); - } - - // Calculate cloudbase - if (cumulus.CloudBaseInFeet) - { - CloudBase = (int)Math.Floor(((tempinF - ConvertUserTempToF(OutdoorDewpoint)) / 4.4) * 1000); - if (CloudBase < 0) - CloudBase = 0; - } - else - { - CloudBase = (int)Math.Floor((((tempinF - ConvertUserTempToF(OutdoorDewpoint)) / 4.4) * 1000) / 3.2808399); - if (CloudBase < 0) - CloudBase = 0; - } - - HeatIndex = ConvertTempCToUser(MeteoLib.HeatIndex(tempinC, OutdoorHumidity)); - - if (HeatIndex > HiLoToday.HighHeatIndex) - { - HiLoToday.HighHeatIndex = HeatIndex; - HiLoToday.HighHeatIndexTime = timestamp; - WriteTodayFile(timestamp, false); - } - - if (HeatIndex > ThisMonth.HighHeatIndex.Val) - { - ThisMonth.HighHeatIndex.Val = HeatIndex; - ThisMonth.HighHeatIndex.Ts = timestamp; - WriteMonthIniFile(); - } - - if (HeatIndex > ThisYear.HighHeatIndex.Val) - { - ThisYear.HighHeatIndex.Val = HeatIndex; - ThisYear.HighHeatIndex.Ts = timestamp; - WriteYearIniFile(); - } - - if (HeatIndex > AllTime.HighHeatIndex.Val) - SetAlltime(AllTime.HighHeatIndex, HeatIndex, timestamp); - - CheckMonthlyAlltime("HighHeatIndex", HeatIndex, true, timestamp); - - //DoApparentTemp(timestamp); - - // Find estimated wet bulb temp. First time this is called, required variables may not have been set up yet - try - { - WetBulb = ConvertTempCToUser(MeteoLib.CalculateWetBulbC(tempinC, ConvertUserTempToC(OutdoorDewpoint), ConvertUserPressToMB(Pressure))); - } - catch - { - WetBulb = OutdoorTemperature; - } - - TempReadyToPlot = true; - HaveReadData = true; - } - - public void DoApparentTemp(DateTime timestamp) - { - // Calculates Apparent Temperature - // See http://www.bom.gov.au/info/thermal_stress/#atapproximation - - // don't try to calculate apparent if we haven't yet had wind and temp readings - //if (TempReadyToPlot && WindReadyToPlot) - //{ - //ApparentTemperature = - //ConvertTempCToUser(ConvertUserTempToC(OutdoorTemperature) + (0.33 * MeteoLib.ActualVapourPressure(ConvertUserTempToC(OutdoorTemperature), OutdoorHumidity)) - - // (0.7 * ConvertUserWindToMS(WindAverage)) - 4); - ApparentTemperature = ConvertTempCToUser(MeteoLib.ApparentTemperature(ConvertUserTempToC(OutdoorTemperature), ConvertUserWindToMS(WindAverage), OutdoorHumidity)); - - - // we will tag on the THW Index here - THWIndex = ConvertTempCToUser(MeteoLib.THWIndex(ConvertUserTempToC(OutdoorTemperature), OutdoorHumidity, ConvertUserWindToKPH(WindAverage))); - - if (ApparentTemperature > HiLoToday.HighAppTemp) - { - HiLoToday.HighAppTemp = ApparentTemperature; - HiLoToday.HighAppTempTime = timestamp; - WriteTodayFile(timestamp, false); - } - - if (ApparentTemperature < HiLoToday.LowAppTemp) - { - HiLoToday.LowAppTemp = ApparentTemperature; - HiLoToday.LowAppTempTime = timestamp; - WriteTodayFile(timestamp, false); - } - - if (ApparentTemperature > ThisMonth.HighAppTemp.Val) - { - ThisMonth.HighAppTemp.Val = ApparentTemperature; - ThisMonth.HighAppTemp.Ts = timestamp; - WriteMonthIniFile(); - } - - if (ApparentTemperature < ThisMonth.LowAppTemp.Val) - { - ThisMonth.LowAppTemp.Val = ApparentTemperature; - ThisMonth.LowAppTemp.Ts = timestamp; - WriteMonthIniFile(); - } - - if (ApparentTemperature > ThisYear.HighAppTemp.Val) - { - ThisYear.HighAppTemp.Val = ApparentTemperature; - ThisYear.HighAppTemp.Ts = timestamp; - WriteYearIniFile(); - } - - if (ApparentTemperature < ThisYear.LowAppTemp.Val) - { - ThisYear.LowAppTemp.Val = ApparentTemperature; - ThisYear.LowAppTemp.Ts = timestamp; - WriteYearIniFile(); - } - - if (ApparentTemperature > AllTime.HighAppTemp.Val) - SetAlltime(AllTime.HighAppTemp, ApparentTemperature, timestamp); - - if (ApparentTemperature < AllTime.LowAppTemp.Val) - SetAlltime(AllTime.LowAppTemp, ApparentTemperature, timestamp); - - CheckMonthlyAlltime("HighAppTemp", ApparentTemperature, true, timestamp); - CheckMonthlyAlltime("LowAppTemp", ApparentTemperature, false, timestamp); - //} - } - - public void DoWindChill(double chillpar, DateTime timestamp) - { - bool chillvalid = true; - - if (cumulus.StationOptions.CalculatedWC) - { - // don"t try to calculate windchill if we haven"t yet had wind and temp readings - if (TempReadyToPlot && WindReadyToPlot) - { - double TempinC = ConvertUserTempToC(OutdoorTemperature); - double windinKPH = ConvertUserWindToKPH(WindAverage); - WindChill = ConvertTempCToUser(MeteoLib.WindChill(TempinC, windinKPH)); - } - else - { - chillvalid = false; - } - } - else - { - WindChill = chillpar; - } - - if (chillvalid) - { - //WindChillReadyToPlot = true; - - if (WindChill < HiLoToday.LowWindChill) - { - HiLoToday.LowWindChill = WindChill; - HiLoToday.LowWindChillTime = timestamp; - WriteTodayFile(timestamp, false); - } - - if (WindChill < ThisMonth.LowChill.Val) - { - ThisMonth.LowChill.Val = WindChill; - ThisMonth.LowChill.Ts = timestamp; - WriteMonthIniFile(); - } - - if (WindChill < ThisYear.LowChill.Val) - { - ThisYear.LowChill.Val = WindChill; - ThisYear.LowChill.Ts = timestamp; - WriteYearIniFile(); - } - - // All time wind chill - if (WindChill < AllTime.LowChill.Val) - { - SetAlltime(AllTime.LowChill, WindChill, timestamp); - } - - CheckMonthlyAlltime("LowChill", WindChill, false, timestamp); - } - } - - public void DoFeelsLike(DateTime timestamp) - { - FeelsLike = ConvertTempCToUser(MeteoLib.FeelsLike(ConvertUserTempToC(OutdoorTemperature), ConvertUserWindToKPH(WindAverage), OutdoorHumidity)); - - if (FeelsLike > HiLoToday.HighFeelsLike) - { - HiLoToday.HighFeelsLike = FeelsLike; - HiLoToday.HighFeelsLikeTime = timestamp; - WriteTodayFile(timestamp, false); - } - - if (FeelsLike < HiLoToday.LowFeelsLike) - { - HiLoToday.LowFeelsLike = FeelsLike; - HiLoToday.LowFeelsLikeTime = timestamp; - WriteTodayFile(timestamp, false); - } - - if (FeelsLike > ThisMonth.HighFeelsLike.Val) - { - ThisMonth.HighFeelsLike.Val = FeelsLike; - ThisMonth.HighFeelsLike.Ts = timestamp; - WriteMonthIniFile(); - } - - if (FeelsLike < ThisMonth.LowFeelsLike.Val) - { - ThisMonth.LowFeelsLike.Val = FeelsLike; - ThisMonth.LowFeelsLike.Ts = timestamp; - WriteMonthIniFile(); - } - - if (FeelsLike > ThisYear.HighFeelsLike.Val) - { - ThisYear.HighFeelsLike.Val = FeelsLike; - ThisYear.HighFeelsLike.Ts = timestamp; - WriteYearIniFile(); - } - - if (FeelsLike < ThisYear.LowFeelsLike.Val) - { - ThisYear.LowFeelsLike.Val = FeelsLike; - ThisYear.LowFeelsLike.Ts = timestamp; - WriteYearIniFile(); - } - - if (FeelsLike > AllTime.HighFeelsLike.Val) - SetAlltime(AllTime.HighFeelsLike, FeelsLike, timestamp); - - if (FeelsLike < AllTime.LowFeelsLike.Val) - SetAlltime(AllTime.LowFeelsLike, FeelsLike, timestamp); - - CheckMonthlyAlltime("HighFeelsLike", FeelsLike, true, timestamp); - CheckMonthlyAlltime("LowFeelsLike", FeelsLike, false, timestamp); - } - - public void DoHumidex(DateTime timestamp) - { - Humidex = MeteoLib.Humidex(ConvertUserTempToC(OutdoorTemperature), OutdoorHumidity); - - if (Humidex > HiLoToday.HighHumidex) - { - HiLoToday.HighHumidex = Humidex; - HiLoToday.HighHumidexTime = timestamp; - WriteTodayFile(timestamp, false); - } - - if (Humidex > ThisMonth.HighHumidex.Val) - { - ThisMonth.HighHumidex.Val = Humidex; - ThisMonth.HighHumidex.Ts = timestamp; - WriteMonthIniFile(); - } - - if (Humidex > ThisYear.HighHumidex.Val) - { - ThisYear.HighHumidex.Val = Humidex; - ThisYear.HighHumidex.Ts = timestamp; - WriteYearIniFile(); - } - - if (Humidex > AllTime.HighHumidex.Val) - SetAlltime(AllTime.HighHumidex, Humidex, timestamp); - - CheckMonthlyAlltime("HighHumidex", Humidex, true, timestamp); - } - - public void CheckForWindrunHighLow(DateTime timestamp) - { - DateTime adjustedtimestamp = timestamp.AddHours(cumulus.GetHourInc()); - - if (WindRunToday > ThisMonth.HighWindRun.Val) - { - ThisMonth.HighWindRun.Val = WindRunToday; - ThisMonth.HighWindRun.Ts = adjustedtimestamp; - WriteMonthIniFile(); - } - - if (WindRunToday > ThisYear.HighWindRun.Val) - { - ThisYear.HighWindRun.Val = WindRunToday; - ThisYear.HighWindRun.Ts = adjustedtimestamp; - WriteYearIniFile(); - } - - if (WindRunToday > AllTime.HighWindRun.Val) - { - SetAlltime(AllTime.HighWindRun, WindRunToday, adjustedtimestamp); - } - - CheckMonthlyAlltime("HighWindRun", WindRunToday, true, adjustedtimestamp); - } - - public void CheckForDewpointHighLow(DateTime timestamp) - { - if (OutdoorDewpoint > HiLoToday.HighDewPoint) - { - HiLoToday.HighDewPoint = OutdoorDewpoint; - HiLoToday.HighDewPointTime = timestamp; - WriteTodayFile(timestamp, false); - } - if (OutdoorDewpoint < HiLoToday.LowDewPoint) - { - HiLoToday.LowDewPoint = OutdoorDewpoint; - HiLoToday.LowDewPointTime = timestamp; - WriteTodayFile(timestamp, false); - } - if (OutdoorDewpoint > ThisMonth.HighDewPoint.Val) - { - ThisMonth.HighDewPoint.Val = OutdoorDewpoint; - ThisMonth.HighDewPoint.Ts = timestamp; - WriteMonthIniFile(); - } - if (OutdoorDewpoint < ThisMonth.LowDewPoint.Val) - { - ThisMonth.LowDewPoint.Val = OutdoorDewpoint; - ThisMonth.LowDewPoint.Ts = timestamp; - WriteMonthIniFile(); - } - if (OutdoorDewpoint > ThisYear.HighDewPoint.Val) - { - ThisYear.HighDewPoint.Val = OutdoorDewpoint; - ThisYear.HighDewPoint.Ts = timestamp; - WriteYearIniFile(); - } - if (OutdoorDewpoint < ThisYear.LowDewPoint.Val) - { - ThisYear.LowDewPoint.Val = OutdoorDewpoint; - ThisYear.LowDewPoint.Ts = timestamp; - WriteYearIniFile(); - } - ; - if (OutdoorDewpoint > AllTime.HighDewPoint.Val) - { - SetAlltime(AllTime.HighDewPoint, OutdoorDewpoint, timestamp); - } - if (OutdoorDewpoint < AllTime.LowDewPoint.Val) - SetAlltime(AllTime.LowDewPoint, OutdoorDewpoint, timestamp); - - CheckMonthlyAlltime("HighDewPoint", OutdoorDewpoint, true, timestamp); - CheckMonthlyAlltime("LowDewPoint", OutdoorDewpoint, false, timestamp); - } - - public void DoPressure(double sl, DateTime timestamp) - { - // Spike removal is in mb/hPa - var pressMB = ConvertUserPressToMB(sl); - if (((Math.Abs(pressMB - previousPress) > cumulus.Spike.PressDiff) && (previousPress != 9999)) || - pressMB >= cumulus.Limit.PressHigh || pressMB <= cumulus.Limit.PressLow) - { - cumulus.LogSpikeRemoval("Pressure difference greater than specified; reading ignored"); - cumulus.LogSpikeRemoval($"NewVal={pressMB:F1} OldVal={previousPress:F1} SpikePressDiff={cumulus.Spike.PressDiff:F1} HighLimit={cumulus.Limit.PressHigh:F1} LowLimit={cumulus.Limit.PressLow:F1}"); - lastSpikeRemoval = DateTime.Now; - cumulus.SpikeAlarm.Triggered = true; - return; - } - - previousPress = pressMB; - - Pressure = sl * cumulus.Calib.Press.Mult + cumulus.Calib.Press.Offset; - if (cumulus.Manufacturer == cumulus.DAVIS) - { - if (!cumulus.DavisOptions.UseLoop2) - { - // Loop2 data not available, just use sea level (for now, anyway) - AltimeterPressure = Pressure; - } - } - else - { - if (cumulus.Manufacturer == cumulus.OREGONUSB) - { - AltimeterPressure = ConvertPressMBToUser(StationToAltimeter(ConvertUserPressureToHPa(StationPressure), AltitudeM(cumulus.Altitude))); - } - else - { - // For all other stations, altimeter is same as sea-level - AltimeterPressure = Pressure; - } - } - - first_press = false; - - if (Pressure > AllTime.HighPress.Val) - { - SetAlltime(AllTime.HighPress, Pressure, timestamp); - } - - cumulus.HighPressAlarm.Triggered = DoAlarm(Pressure, cumulus.HighPressAlarm.Value, cumulus.HighPressAlarm.Enabled, true); - - if (Pressure < AllTime.LowPress.Val) - { - SetAlltime(AllTime.LowPress, Pressure, timestamp); - } - - cumulus.LowPressAlarm.Triggered = DoAlarm(Pressure, cumulus.LowPressAlarm.Value, cumulus.LowPressAlarm.Enabled, false); - CheckMonthlyAlltime("LowPress", Pressure, false, timestamp); - CheckMonthlyAlltime("HighPress", Pressure, true, timestamp); - - if (Pressure > HiLoToday.HighPress) - { - HiLoToday.HighPress = Pressure; - HiLoToday.HighPressTime = timestamp; - WriteTodayFile(timestamp, false); - } - - if (Pressure < HiLoToday.LowPress) - { - HiLoToday.LowPress = Pressure; - HiLoToday.LowPressTime = timestamp; - WriteTodayFile(timestamp, false); - } - - if (Pressure > ThisMonth.HighPress.Val) - { - ThisMonth.HighPress.Val = Pressure; - ThisMonth.HighPress.Ts = timestamp; - WriteMonthIniFile(); - } - - if (Pressure < ThisMonth.LowPress.Val) - { - ThisMonth.LowPress.Val = Pressure; - ThisMonth.LowPress.Ts = timestamp; - WriteMonthIniFile(); - } - - if (Pressure > ThisYear.HighPress.Val) - { - ThisYear.HighPress.Val = Pressure; - ThisYear.HighPress.Ts = timestamp; - WriteYearIniFile(); - } - - if (Pressure < ThisYear.LowPress.Val) - { - ThisYear.LowPress.Val = Pressure; - ThisYear.LowPress.Ts = timestamp; - WriteYearIniFile(); - } - - PressReadyToPlot = true; - HaveReadData = true; - } - - protected void DoPressTrend(string trend) - { - if (cumulus.StationOptions.UseCumulusPresstrendstr || cumulus.Manufacturer == cumulus.DAVIS) - { - UpdatePressureTrendString(); - } - else - { - Presstrendstr = trend; - } - } - - public void DoRain(double total, double rate, DateTime timestamp) - { - DateTime readingTS = timestamp.AddHours(cumulus.GetHourInc()); - - //cumulus.LogDebugMessage($"DoRain: counter={total}, rate={rate}; RainToday={RainToday}, StartOfDay={raindaystart}"); - - // Spike removal is in mm - var rainRateMM = ConvertUserRainToMM(rate); - if (rainRateMM > cumulus.Spike.MaxRainRate) - { - cumulus.LogSpikeRemoval("Rain rate greater than specified; reading ignored"); - cumulus.LogSpikeRemoval($"Rate value = {rainRateMM:F2} SpikeMaxRainRate = {cumulus.Spike.MaxRainRate:F2}"); - lastSpikeRemoval = DateTime.Now; - cumulus.SpikeAlarm.Triggered = true; - return; - } - - if ((CurrentDay != readingTS.Day) || (CurrentMonth != readingTS.Month) || (CurrentYear != readingTS.Year)) - { - // A reading has apparently arrived at the start of a new day, but before we have done the rollover - // Ignore it, as otherwise it may cause a new monthly record to be logged using last month's total - return; - } - - var previoustotal = Raincounter; - - double raintipthreshold = 0; ; - if (cumulus.Manufacturer == cumulus.DAVIS) // Davis can have either 0.2mm or 0.01in buckets, and the user could select to measure in mm or inches! - { - // If the bucket size is set, use that, otherwise infer from rain units - var bucketSize = cumulus.DavisOptions.RainGaugeType == -1 ? cumulus.Units.Rain : cumulus.DavisOptions.RainGaugeType; - - switch (bucketSize) - { - case 0: // 0.2 mm tips - // mm/mm (0.2) or mm/in (0.00787) - raintipthreshold = cumulus.Units.Rain == 0 ? 0.19 : 0.006; - break; - case 1: // 0.01 inch tips - // in/mm (0.254) or in/in (0.01) - raintipthreshold = cumulus.Units.Rain == 0 ? 0.2 : 0.009; - break; - case 2: // 0.01 mm tips - // mm/mm (0.1) or mm/in (0.0394) - raintipthreshold = cumulus.Units.Rain == 0 ? 0.09 : 0.003; - break; - case 3: // 0.001 inch tips - // in/mm (0.0254) or in/in (0.001) - raintipthreshold = cumulus.Units.Rain == 0 ? 0.02 : 0.0009; - break; - } - } - else - { - if (cumulus.Units.Rain == 0) - { - // mm - raintipthreshold = cumulus.Manufacturer == cumulus.INSTROMET ? 0.009 : 0.09; - } - else - { - // in - raintipthreshold = cumulus.Manufacturer == cumulus.INSTROMET ? 0.0003 : 0.009; - } - } - - Raincounter = total; - - //first_rain = false; - if (initialiseRainCounterOnFirstData) - { - raindaystart = Raincounter; - midnightraincount = Raincounter; - cumulus.LogMessage(" First rain data, raindaystart = " + raindaystart); - - initialiseRainCounterOnFirstData = false; - WriteTodayFile(timestamp, false); - HaveReadData = true; - return; - } - - // Has the rain total in the station been reset? - // raindaystart greater than current total, allow for rounding - if (raindaystart - Raincounter > 0.1) - { - if (FirstChanceRainReset) - // second consecutive reading with reset value - { - cumulus.LogMessage(" ****Rain counter reset confirmed: raindaystart = " + raindaystart + ", Raincounter = " + Raincounter); - - // set the start of day figure so it reflects the rain - // so far today - raindaystart = Raincounter - (RainToday / cumulus.Calib.Rain.Mult); - cumulus.LogMessage("Setting raindaystart to " + raindaystart); - - midnightraincount = Raincounter; - - FirstChanceRainReset = false; - } - else - { - cumulus.LogMessage(" ****Rain reset? First chance: raindaystart = " + raindaystart + ", Raincounter = " + Raincounter); - - // reset the counter to ignore this reading - Raincounter = previoustotal; - cumulus.LogMessage("Leaving counter at " + Raincounter); - - FirstChanceRainReset = true; - } - } - else - { - FirstChanceRainReset = false; - } - - if (rate > -1) - // Do rain rate - { - // scale rainfall rate - RainRate = rate * cumulus.Calib.Rain.Mult; - - if (RainRate > AllTime.HighRainRate.Val) - SetAlltime(AllTime.HighRainRate, RainRate, timestamp); - - CheckMonthlyAlltime("HighRainRate", RainRate, true, timestamp); - - cumulus.HighRainRateAlarm.Triggered = DoAlarm(RainRate, cumulus.HighRainRateAlarm.Value, cumulus.HighRainRateAlarm.Enabled, true); - - if (RainRate > HiLoToday.HighRainRate) - { - HiLoToday.HighRainRate = RainRate; - HiLoToday.HighRainRateTime = timestamp; - WriteTodayFile(timestamp, false); - } - - if (RainRate > ThisMonth.HighRainRate.Val) - { - ThisMonth.HighRainRate.Val = RainRate; - ThisMonth.HighRainRate.Ts = timestamp; - WriteMonthIniFile(); - } - - if (RainRate > ThisYear.HighRainRate.Val) - { - ThisYear.HighRainRate.Val = RainRate; - ThisYear.HighRainRate.Ts = timestamp; - WriteYearIniFile(); - } - } - - if (!FirstChanceRainReset) - { - // Has a tip occured? - if (total - previoustotal > raintipthreshold) - { - // rain has occurred - LastRainTip = timestamp.ToString("yyyy-MM-dd HH:mm"); - } - - // Calculate today"s rainfall - RainToday = Raincounter - raindaystart; - //cumulus.LogDebugMessage("Uncalibrated RainToday = " + RainToday); - - // scale for calibration - RainToday *= cumulus.Calib.Rain.Mult; - - // Calculate rain since midnight for Wunderground etc - double trendval = Raincounter - midnightraincount; - - // Round value as some values may have been read from log file and already rounded - trendval = Math.Round(trendval, cumulus.RainDPlaces); - - if (trendval < 0) - { - RainSinceMidnight = 0; - } - else - { - RainSinceMidnight = trendval * cumulus.Calib.Rain.Mult; - } - - // rain this month so far - RainMonth = rainthismonth + RainToday; - - // get correct date for rain records - var offsetdate = timestamp.AddHours(cumulus.GetHourInc()); - - // rain this year so far - RainYear = rainthisyear + RainToday; - - if (RainToday > AllTime.DailyRain.Val) - SetAlltime(AllTime.DailyRain, RainToday, offsetdate); - - CheckMonthlyAlltime("DailyRain", RainToday, true, timestamp); - - if (RainToday > ThisMonth.DailyRain.Val) - { - ThisMonth.DailyRain.Val = RainToday; - ThisMonth.DailyRain.Ts = offsetdate; - WriteMonthIniFile(); - } - - if (RainToday > ThisYear.DailyRain.Val) - { - ThisYear.DailyRain.Val = RainToday; - ThisYear.DailyRain.Ts = offsetdate; - WriteYearIniFile(); - } - - if (RainMonth > ThisYear.MonthlyRain.Val) - { - ThisYear.MonthlyRain.Val = RainMonth; - ThisYear.MonthlyRain.Ts = offsetdate; - WriteYearIniFile(); - } - - if (RainMonth > AllTime.MonthlyRain.Val) - SetAlltime(AllTime.MonthlyRain, RainMonth, offsetdate); - - CheckMonthlyAlltime("MonthlyRain", RainMonth, true, timestamp); - - cumulus.HighRainTodayAlarm.Triggered = DoAlarm(RainToday, cumulus.HighRainTodayAlarm.Value, cumulus.HighRainTodayAlarm.Enabled, true); - - // Yesterday"s rain - Scale for units - // rainyest = rainyesterday * RainMult; - - //RainReadyToPlot = true; - } - HaveReadData = true; - } - - public void DoOutdoorDewpoint(double dp, DateTime timestamp) - { - if (!cumulus.StationOptions.CalculatedDP) - { - if (ConvertUserTempToC(dp) <= cumulus.Limit.DewHigh) - { - OutdoorDewpoint = dp; - CheckForDewpointHighLow(timestamp); - } - else - { - lastSpikeRemoval = DateTime.Now; - cumulus.SpikeAlarm.Triggered = true; - cumulus.LogSpikeRemoval($"Dew point greater than limit ({cumulus.Limit.DewHigh.ToString(cumulus.TempFormat)}); reading ignored: {dp.ToString(cumulus.TempFormat)}"); - } - } - } - - public string LastRainTip { get; set; } - - public void DoExtraHum(double hum, int channel) - { - if ((channel > 0) && (channel < 11)) - { - ExtraHum[channel] = (int)hum; - } - } - - public void DoExtraTemp(double temp, int channel) - { - if ((channel > 0) && (channel < 11)) - { - ExtraTemp[channel] = temp; - } - } - - public void DoUserTemp(double temp, int channel) - { - if ((channel > 0) && (channel < 11)) - { - UserTemp[channel] = temp; - } - } - - - public void DoExtraDP(double dp, int channel) - { - if ((channel > 0) && (channel < 11)) - { - ExtraDewPoint[channel] = dp; - } - } - - public void DoForecast(string forecast, bool hourly) - { - // store weather station forecast if available - - if (forecast != "") - { - wsforecast = forecast; - } - - if (!cumulus.UseCumulusForecast) - { - // user wants to display station forecast - forecaststr = wsforecast; - } - // determine whether we need to update the Cumulus forecast; user may have chosen to only update once an hour, but - // we still need to do that once to get an initial forecast - if ((!FirstForecastDone) || (!cumulus.HourlyForecast) || (hourly && cumulus.HourlyForecast)) - { - int bartrend; - if ((presstrendval >= -cumulus.FCPressureThreshold) && (presstrendval <= cumulus.FCPressureThreshold)) - bartrend = 0; - else if (presstrendval < 0) - bartrend = 2; - else - bartrend = 1; - - string windDir; - if (WindAverage < 0.1) - { - windDir = "calm"; - } - else - { - windDir = AvgBearingText; - } - - double lp; - double hp; - if (cumulus.FCpressinMB) - { - lp = cumulus.FClowpress; - hp = cumulus.FChighpress; - } - else - { - lp = cumulus.FClowpress / 0.0295333727; - hp = cumulus.FChighpress / 0.0295333727; - } - - CumulusForecast = BetelCast(ConvertUserPressureToHPa(Pressure), DateTime.Now.Month, windDir, bartrend, cumulus.Latitude > 0, hp, lp); - - if (cumulus.UseCumulusForecast) - { - // user wants to display Cumulus forecast - forecaststr = CumulusForecast; - } - } - - FirstForecastDone = true; - HaveReadData = true; - } - - public string forecaststr { get; set; } - - public string CumulusForecast { get; set; } - - public string wsforecast { get; set; } - - public bool FirstForecastDone = false; - - /// - /// Convert altitude from user units to metres - /// - /// - /// - public double AltitudeM(double altitude) - { - if (cumulus.AltitudeInFeet) - { - return altitude * 0.3048; - } - else - { - return altitude; - } - } - - /// - /// Convert pressure from user units to hPa - /// - /// - /// - public double ConvertUserPressureToHPa(double value) - { - if (cumulus.Units.Press == 2) - return value / 0.0295333727; - else - return value; - } - - public double StationToAltimeter(double pressureHPa, double elevationM) - { - // from MADIS API by NOAA Forecast Systems Lab, see http://madis.noaa.gov/madis_api.html - - double k1 = 0.190284; // discrepency with calculated k1 probably because Smithsonian used less precise gas constant and gravity values - double k2 = 8.4184960528E-5; // (standardLapseRate / standardTempK) * (Power(standardSLP, k1) - return Math.Pow(Math.Pow(pressureHPa - 0.3, k1) + (k2 * elevationM), 1 / k1); - } - - public bool PressReadyToPlot { get; set; } - - public bool first_press { get; set; } - - /* - private string TimeToStrHHMM(DateTime timestamp) - { - return timestamp.ToString("HHmm"); - } - */ - - public bool DoAlarm(double value, double threshold, bool enabled, bool testAbove) - { - if (enabled) - { - if (testAbove) - { - return value > threshold; - } - else - { - return value < threshold; - } - } - return false; - } - - public void DoWind(double gustpar, int bearingpar, double speedpar, DateTime timestamp) - { - // Spike removal is in m/s - var windGustMS = ConvertUserWindToMS(gustpar); - var windAvgMS = ConvertUserWindToMS(speedpar); - - if (((Math.Abs(windGustMS - previousGust) > cumulus.Spike.GustDiff) && (previousGust != 999)) || - ((Math.Abs(windAvgMS - previousWind) > cumulus.Spike.WindDiff) && (previousWind != 999)) || - windGustMS >= cumulus.Limit.WindHigh - ) - { - cumulus.LogSpikeRemoval("Wind or gust difference greater than specified; reading ignored"); - cumulus.LogSpikeRemoval($"Gust: NewVal={windGustMS:F1} OldVal={previousGust:F1} SpikeGustDiff={cumulus.Spike.GustDiff:F1} HighLimit={cumulus.Limit.WindHigh:F1}"); - cumulus.LogSpikeRemoval($"Wind: NewVal={windAvgMS:F1} OldVal={previousWind:F1} SpikeWindDiff={cumulus.Spike.WindDiff:F1}"); - lastSpikeRemoval = DateTime.Now; - cumulus.SpikeAlarm.Triggered = true; - return; - } - - previousGust = windGustMS; - previousWind = windAvgMS; - - // use bearing of zero when calm - if ((Math.Abs(gustpar) < 0.001) && cumulus.StationOptions.UseZeroBearing) - { - Bearing = 0; - } - else - { - Bearing = (bearingpar + (int)cumulus.Calib.WindDir.Offset) % 360; - if (Bearing < 0) - { - Bearing = 360 + Bearing; - } - - if (Bearing == 0) - { - Bearing = 360; - } - } - var uncalibratedgust = gustpar; - calibratedgust = uncalibratedgust * cumulus.Calib.WindGust.Mult; - WindLatest = calibratedgust; - windspeeds[nextwindvalue] = uncalibratedgust; - windbears[nextwindvalue] = Bearing; - - // Recalculate wind rose data - lock (windcounts) - { - for (int i = 0; i < cumulus.NumWindRosePoints; i++) - { - windcounts[i] = 0; - } - - for (int i = 0; i < numwindvalues; i++) - { - int j = (((windbears[i] * 100) + 1125) % 36000) / (int)Math.Floor(cumulus.WindRoseAngle * 100); - windcounts[j] += windspeeds[i]; - } - } - - if (numwindvalues < maxwindvalues) - { - numwindvalues++; - } - - nextwindvalue = (nextwindvalue + 1) % maxwindvalues; - if (calibratedgust > HiLoToday.HighGust) - { - HiLoToday.HighGust = calibratedgust; - HiLoToday.HighGustTime = timestamp; - HiLoToday.HighGustBearing = Bearing; - WriteTodayFile(timestamp, false); - } - if (calibratedgust > ThisMonth.HighGust.Val) - { - ThisMonth.HighGust.Val = calibratedgust; - ThisMonth.HighGust.Ts = timestamp; - WriteMonthIniFile(); - } - if (calibratedgust > ThisYear.HighGust.Val) - { - ThisYear.HighGust.Val = calibratedgust; - ThisYear.HighGust.Ts = timestamp; - WriteYearIniFile(); - } - // All time high gust? - if (calibratedgust > AllTime.HighGust.Val) - { - SetAlltime(AllTime.HighGust, calibratedgust, timestamp); - } - - // check for monthly all time records (and set) - CheckMonthlyAlltime("HighGust", calibratedgust, true, timestamp); - - WindRecent[nextwind].Gust = uncalibratedgust; - WindRecent[nextwind].Speed = speedpar; - WindRecent[nextwind].Timestamp = timestamp; - nextwind = (nextwind + 1) % MaxWindRecent; - if (cumulus.StationOptions.UseWind10MinAve) - { - int numvalues = 0; - double totalwind = 0; - for (int i = 0; i < MaxWindRecent; i++) - { - if (timestamp - WindRecent[i].Timestamp <= cumulus.AvgSpeedTime) - { - numvalues++; - if (cumulus.StationOptions.UseSpeedForAvgCalc) - { - totalwind += WindRecent[i].Speed; - } - else - { - totalwind += WindRecent[i].Gust; - } - } - } - // average the values - WindAverage = totalwind / numvalues; - //cumulus.LogDebugMessage("next=" + nextwind + " wind=" + uncalibratedgust + " tot=" + totalwind + " numv=" + numvalues + " avg=" + WindAverage); - } - else - { - WindAverage = speedpar; - } - - WindAverage *= cumulus.Calib.WindSpeed.Mult; - - cumulus.HighWindAlarm.Triggered = DoAlarm(WindAverage, cumulus.HighWindAlarm.Value, cumulus.HighWindAlarm.Enabled, true); - - - if (CalcRecentMaxGust) - { - // Find recent max gust - double maxgust = 0; - for (int i = 0; i <= MaxWindRecent - 1; i++) - { - if (timestamp - WindRecent[i].Timestamp <= cumulus.PeakGustTime) - { - if (WindRecent[i].Gust > maxgust) - { - maxgust = WindRecent[i].Gust; - } - } - } - RecentMaxGust = maxgust * cumulus.Calib.WindGust.Mult; - } - - cumulus.HighGustAlarm.Triggered = DoAlarm(RecentMaxGust, cumulus.HighGustAlarm.Value, cumulus.HighGustAlarm.Enabled, true); - - if (WindAverage > HiLoToday.HighWind) - { - HiLoToday.HighWind = WindAverage; - HiLoToday.HighWindTime = timestamp; - WriteTodayFile(timestamp, false); - } - if (WindAverage > ThisMonth.HighWind.Val) - { - ThisMonth.HighWind.Val = WindAverage; - ThisMonth.HighWind.Ts = timestamp; - WriteMonthIniFile(); - } - if (WindAverage > ThisYear.HighWind.Val) - { - ThisYear.HighWind.Val = WindAverage; - ThisYear.HighWind.Ts = timestamp; - WriteYearIniFile(); - } - - WindVec[nextwindvec].X = calibratedgust * Math.Sin(DegToRad(Bearing)); - WindVec[nextwindvec].Y = calibratedgust * Math.Cos(DegToRad(Bearing)); - // save timestamp of this reading - WindVec[nextwindvec].Timestamp = timestamp; - // save bearing - WindVec[nextwindvec].Bearing = Bearing; // savedBearing; - // increment index for next reading - nextwindvec = (nextwindvec + 1) % MaxWindRecent; - - // Now add up all the values within the required period - double totalwindX = 0; - double totalwindY = 0; - for (int i = 0; i < MaxWindRecent; i++) - { - if (timestamp - WindVec[i].Timestamp < cumulus.AvgBearingTime) - { - totalwindX += WindVec[i].X; - totalwindY += WindVec[i].Y; - } - } - if (totalwindX == 0) - { - AvgBearing = 0; - } - else - { - AvgBearing = (int)Math.Round(RadToDeg(Math.Atan(totalwindY / totalwindX))); - - if (totalwindX < 0) - { - AvgBearing = 270 - AvgBearing; - } - else - { - AvgBearing = 90 - AvgBearing; - } - - if (AvgBearing == 0) - { - AvgBearing = 360; - } - } - - if ((Math.Abs(WindAverage) < 0.01) && cumulus.StationOptions.UseZeroBearing) - { - AvgBearing = 0; - } - - AvgBearingText = CompassPoint(AvgBearing); - - int diffFrom = 0; - int diffTo = 0; - BearingRangeFrom = AvgBearing; - BearingRangeTo = AvgBearing; - if (AvgBearing != 0) - { - for (int i = 0; i <= MaxWindRecent - 1; i++) - { - if ((timestamp - WindVec[i].Timestamp < cumulus.AvgBearingTime) && (WindVec[i].Bearing != 0)) - { - // this reading was within the last N minutes - int difference = getShortestAngle(AvgBearing, WindVec[i].Bearing); - if ((difference > diffTo)) - { - diffTo = difference; - BearingRangeTo = WindVec[i].Bearing; - // Calculate rounded up value - BearingRangeTo10 = (int)(Math.Ceiling(WindVec[i].Bearing / 10.0) * 10); - } - if ((difference < diffFrom)) - { - diffFrom = difference; - BearingRangeFrom = WindVec[i].Bearing; - BearingRangeFrom10 = (int)(Math.Floor(WindVec[i].Bearing / 10.0) * 10); - } - } - } - } - else - { - BearingRangeFrom10 = 0; - BearingRangeTo10 = 0; - } - - // All time high wind speed? - if (WindAverage > AllTime.HighWind.Val) - { - SetAlltime(AllTime.HighWind, WindAverage, timestamp); - } - - // check for monthly all time records (and set) - CheckMonthlyAlltime("HighWind", WindAverage, true, timestamp); - - WindReadyToPlot = true; - HaveReadData = true; - } - - public void DoUV(double value, DateTime timestamp) - { - UV = (value * cumulus.Calib.UV.Mult) + cumulus.Calib.UV.Offset; - if (UV < 0) - UV = 0; - if (UV > 16) - UV = 16; - - if (UV > HiLoToday.HighUv) - { - HiLoToday.HighUv = UV; - HiLoToday.HighUvTime = timestamp; - } - - HaveReadData = true; - } - - protected void DoSolarRad(int value, DateTime timestamp) - { - SolarRad = (value * cumulus.Calib.Solar.Mult) + cumulus.Calib.Solar.Offset; - // Update display - - if (SolarRad > HiLoToday.HighSolar) - { - HiLoToday.HighSolar = SolarRad; - HiLoToday.HighSolarTime = timestamp; - } - CurrentSolarMax = AstroLib.SolarMax(timestamp, cumulus.Longitude, cumulus.Latitude, AltitudeM(cumulus.Altitude), out SolarElevation, cumulus.RStransfactor, cumulus.BrasTurbidity, cumulus.SolarCalc); - - if (!cumulus.UseBlakeLarsen) - { - IsSunny = (SolarRad > (CurrentSolarMax * cumulus.SunThreshold / 100)) && (SolarRad >= cumulus.SolarMinimum); - } - HaveReadData = true; - } - - protected void DoSunHours(double hrs) - { - if (StartOfDaySunHourCounter < -9998) - { - cumulus.LogMessage("No start of day sun counter. Start counting from now"); - StartOfDaySunHourCounter = hrs; - } - - // Has the counter reset to a value less than we were expecting. Or has it changed by some infeasibly large value? - if (hrs < SunHourCounter || Math.Abs(hrs - SunHourCounter) > 20) - { - // counter reset - cumulus.LogMessage("Sun hour counter reset. Old value = " + SunHourCounter + "New value = " + hrs); - StartOfDaySunHourCounter = hrs - SunshineHours; - } - SunHourCounter = hrs; - SunshineHours = hrs - StartOfDaySunHourCounter; - } - - protected void DoWetBulb(double temp, DateTime timestamp) // Supplied in CELSIUS - - { - WetBulb = ConvertTempCToUser(temp); - WetBulb = (WetBulb * cumulus.Calib.WetBulb.Mult) + cumulus.Calib.WetBulb.Offset; - - // calculate RH - double TempDry = ConvertUserTempToC(OutdoorTemperature); - double Es = MeteoLib.SaturationVapourPressure1980(TempDry); - double Ew = MeteoLib.SaturationVapourPressure1980(temp); - double E = Ew - (0.00066 * (1 + 0.00115 * temp) * (TempDry - temp) * 1013); - int hum = (int)(100 * (E / Es)); - DoOutdoorHumidity(hum, timestamp); - // calculate DP - // Calculate DewPoint - - // dewpoint = TempinC + ((0.13 * TempinC) + 13.6) * Ln(humidity / 100); - OutdoorDewpoint = ConvertTempCToUser(MeteoLib.DewPoint(TempDry, hum)); - - CheckForDewpointHighLow(timestamp); - } - - public bool IsSunny { get; set; } - - public bool HaveReadData { get; set; } = false; - - public void SetAlltime(AllTimeRec rec, double value, DateTime timestamp) - { - lock (alltimeIniThreadLock) - { - double oldvalue = rec.Val; - DateTime oldts = rec.Ts; - - rec.Val = value; - - rec.Ts = timestamp; - - WriteAlltimeIniFile(); - - AlltimeRecordTimestamp = timestamp; - - // add an entry to the log. date/time/value/item/old date/old time/old value - // dates in ISO format, times always have a colon. Example: - // 2010-02-24 05:19 -7.6 "Lowest temperature" 2009-02-09 04:50 -6.5 - var sb = new StringBuilder("New all-time record: New time = ", 100); - sb.Append(FormatDateTime("yyyy-MM-dd HH:mm", rec.Ts)); - sb.Append(", new value = "); - sb.Append(string.Format("{0,7:0.000}", value)); - sb.Append(" \""); - sb.Append(rec.Desc); - sb.Append("\" prev time = "); - sb.Append(FormatDateTime("yyyy-MM-dd HH:mm", oldts)); - sb.Append(", prev value = "); - sb.Append(string.Format("{0,7:0.000}", oldvalue)); - - cumulus.LogMessage(sb.ToString()); - - sb.Append(Environment.NewLine); - File.AppendAllText(cumulus.Alltimelogfile, sb.ToString()); - } - } - - public void SetMonthlyAlltime(AllTimeRec rec, double value, DateTime timestamp) - { - double oldvalue = rec.Val; - DateTime oldts = rec.Ts; - - rec.Val = value; - rec.Ts = timestamp; - - WriteMonthlyAlltimeIniFile(); - - var sb = new StringBuilder("New monthly record: month = ", 200); - sb.Append(timestamp.Month.ToString("D2")); - sb.Append(": New time = "); - sb.Append(FormatDateTime("yyy-MM-dd HH:mm", timestamp)); - sb.Append(", new value = "); - sb.Append(value.ToString("F3")); - sb.Append(" \""); - sb.Append(rec.Desc); - sb.Append("\" prev time = "); - sb.Append(FormatDateTime("yyyy-MM-dd HH:mm", oldts)); - sb.Append(", prev value = "); - sb.Append(oldvalue.ToString("F3")); - - cumulus.LogMessage(sb.ToString()); - - sb.Append(Environment.NewLine); - File.AppendAllText(cumulus.MonthlyAlltimeLogFile, sb.ToString()); - } - - /// - /// Returns the angle from bearing2 to bearing1, in the range -180 to +180 degrees - /// - /// - /// - /// the required angle - private int getShortestAngle(int bearing1, int bearing2) - { - int diff = bearing2 - bearing1; - - if (diff >= 180) - { - // result is obtuse and positive, subtract 360 to go the other way - diff -= 360; - } - else - { - if (diff <= -180) - { - // result is obtuse and negative, add 360 to go the other way - diff += 360; - } - } - return diff; - } - - public int BearingRangeTo10 { get; set; } - - public int BearingRangeFrom10 { get; set; } - - public int BearingRangeTo { get; set; } - - public int BearingRangeFrom { get; set; } - - public const int maxwindvalues = 3600; - - public int[] windbears = new int[maxwindvalues]; - - public int numwindvalues { get; set; } - - public double[] windspeeds = new double[maxwindvalues]; - - public double[] windcounts { get; set; } - - public int nextwindvalue { get; set; } - - public double calibratedgust { get; set; } - - public int nextwind { get; set; } = 0; - - public int nextwindvec { get; set; } = 0; - - public TWindRecent[] WindRecent { get; set; } - - public TWindVec[] WindVec { get; set; } - - - //private bool first_rain = true; - private bool FirstChanceRainReset = false; - public bool initialiseRainCounterOnFirstData = true; - //private bool RainReadyToPlot = false; - private double rainthismonth = 0; - private double rainthisyear = 0; - //private bool WindChillReadyToPlot = false; - public bool noET = false; - private int DayResetDay = 0; - protected bool FirstRun = false; - public const int MaxWindRecent = 720; - protected readonly double[] WindRunHourMult = { 3.6, 1.0, 1.0, 1.0 }; - public DateTime LastDataReadTimestamp = DateTime.MinValue; - public DateTime SavedLastDataReadTimestamp = DateTime.MinValue; - // Create arrays with 9 entries, 0 = VP2, 1-8 = WLL TxIds - public int DavisTotalPacketsReceived = 0; - public int[] DavisTotalPacketsMissed = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - public int[] DavisNumberOfResynchs = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - public int[] DavisMaxInARow = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - public int[] DavisNumCRCerrors = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - public int[] DavisReceptionPct = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - public int[] DavisTxRssi = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - public string DavisFirmwareVersion = "???"; - public string GW1000FirmwareVersion = "???"; - - //private bool manualftp; - - public void WriteYesterdayFile() - { - cumulus.LogMessage("Writing yesterday.ini"); - var hourInc = cumulus.GetHourInc(); - - IniFile ini = new IniFile(cumulus.YesterdayFile); - - ini.SetValue("General", "Date", DateTime.Now.AddHours(hourInc)); - // Wind - ini.SetValue("Wind", "Speed", HiLoYest.HighWind); - ini.SetValue("Wind", "SpTime", HiLoYest.HighWindTime.ToString("HH:mm")); - ini.SetValue("Wind", "Gust", HiLoYest.HighGust); - ini.SetValue("Wind", "Time", HiLoYest.HighGustTime.ToString("HH:mm")); - ini.SetValue("Wind", "Bearing", HiLoYest.HighGustBearing); - ini.SetValue("Wind", "Direction", CompassPoint(HiLoYest.HighGustBearing)); - ini.SetValue("Wind", "Windrun", YesterdayWindRun); - ini.SetValue("Wind", "DominantWindBearing", YestDominantWindBearing); - // Temperature - ini.SetValue("Temp", "Low", HiLoYest.LowTemp); - ini.SetValue("Temp", "LTime", HiLoYest.LowTempTime.ToString("HH:mm")); - ini.SetValue("Temp", "High", HiLoYest.HighTemp); - ini.SetValue("Temp", "HTime", HiLoYest.HighTempTime.ToString("HH:mm")); - ini.SetValue("Temp", "HeatingDegreeDays", YestHeatingDegreeDays); - ini.SetValue("Temp", "CoolingDegreeDays", YestCoolingDegreeDays); - ini.SetValue("Temp", "AvgTemp", YestAvgTemp); - // Pressure - ini.SetValue("Pressure", "Low", HiLoYest.LowPress); - ini.SetValue("Pressure", "LTime", HiLoYest.LowPressTime.ToString("HH:mm")); - ini.SetValue("Pressure", "High", HiLoYest.HighPress); - ini.SetValue("Pressure", "HTime", HiLoYest.HighPressTime.ToString("HH:mm")); - // rain rate - ini.SetValue("Rain", "High", HiLoYest.HighRainRate); - ini.SetValue("Rain", "HTime", HiLoYest.HighRainRateTime.ToString("HH:mm")); - ini.SetValue("Rain", "HourlyHigh", HiLoYest.HighHourlyRain); - ini.SetValue("Rain", "HHourlyTime", HiLoYest.HighHourlyRainTime.ToString("HH:mm")); - ini.SetValue("Rain", "RG11Yesterday", RG11RainYesterday); - // humidity - ini.SetValue("Humidity", "Low", HiLoYest.LowHumidity); - ini.SetValue("Humidity", "High", HiLoYest.HighHumidity); - ini.SetValue("Humidity", "LTime", HiLoYest.LowHumidityTime.ToString("HH:mm")); - ini.SetValue("Humidity", "HTime", HiLoYest.HighHumidityTime.ToString("HH:mm")); - // Solar - ini.SetValue("Solar", "SunshineHours", YestSunshineHours); - // heat index - ini.SetValue("HeatIndex", "High", HiLoYest.HighHeatIndex); - ini.SetValue("HeatIndex", "HTime", HiLoYest.HighHeatIndexTime.ToString("HH:mm")); - // App temp - ini.SetValue("AppTemp", "Low", HiLoYest.LowAppTemp); - ini.SetValue("AppTemp", "LTime", HiLoYest.LowAppTempTime.ToString("HH:mm")); - ini.SetValue("AppTemp", "High", HiLoYest.HighAppTemp); - ini.SetValue("AppTemp", "HTime", HiLoYest.HighAppTempTime.ToString("HH:mm")); - // wind chill - ini.SetValue("WindChill", "Low", HiLoYest.LowWindChill); - ini.SetValue("WindChill", "LTime", HiLoYest.LowWindChillTime.ToString("HH:mm")); - // Dewpoint - ini.SetValue("Dewpoint", "Low", HiLoYest.LowDewPoint); - ini.SetValue("Dewpoint", "LTime", HiLoYest.LowDewPointTime.ToString("HH:mm")); - ini.SetValue("Dewpoint", "High", HiLoYest.HighDewPoint); - ini.SetValue("Dewpoint", "HTime", HiLoYest.HighDewPointTime.ToString("HH:mm")); - // Solar - ini.SetValue("Solar", "HighSolarRad", HiLoYest.HighSolar); - ini.SetValue("Solar", "HighSolarRadTime", HiLoYest.HighSolarTime.ToString("HH:mm")); - ini.SetValue("Solar", "HighUV", HiLoYest.HighUv); - ini.SetValue("Solar", "HighUVTime", HiLoYest.HighUvTime.ToString("HH:mm")); - // Feels like - ini.SetValue("FeelsLike", "Low", HiLoYest.LowFeelsLike); - ini.SetValue("FeelsLike", "LTime", HiLoYest.LowFeelsLikeTime.ToString("HH:mm")); - ini.SetValue("FeelsLike", "High", HiLoYest.HighFeelsLike); - ini.SetValue("FeelsLike", "HTime", HiLoYest.HighFeelsLikeTime.ToString("HH:mm")); - // Humidex - ini.SetValue("Humidex", "High", HiLoYest.HighHumidex); - ini.SetValue("Humidex", "HTime", HiLoYest.HighHumidexTime.ToString("HH:mm")); - - ini.Flush(); - - cumulus.LogMessage("Written yesterday.ini"); - } - - public void ReadYesterdayFile() - { - //var hourInc = cumulus.GetHourInc(); - - IniFile ini = new IniFile(cumulus.YesterdayFile); - - // Wind - HiLoYest.HighWind = ini.GetValue("Wind", "Speed", 0.0); - HiLoYest.HighWindTime = ini.GetValue("Wind", "SpTime", DateTime.MinValue); - HiLoYest.HighGust = ini.GetValue("Wind", "Gust", 0.0); - HiLoYest.HighGustTime = ini.GetValue("Wind", "Time", DateTime.MinValue); - HiLoYest.HighGustBearing = ini.GetValue("Wind", "Bearing", 0); - - YesterdayWindRun = ini.GetValue("Wind", "Windrun", 0.0); - YestDominantWindBearing = ini.GetValue("Wind", "DominantWindBearing", 0); - // Temperature - HiLoYest.LowTemp = ini.GetValue("Temp", "Low", 0.0); - HiLoYest.LowTempTime = ini.GetValue("Temp", "LTime", DateTime.MinValue); - HiLoYest.HighTemp = ini.GetValue("Temp", "High", 0.0); - HiLoYest.HighTempTime = ini.GetValue("Temp", "HTime", DateTime.MinValue); - YestHeatingDegreeDays = ini.GetValue("Temp", "HeatingDegreeDays", 0.0); - YestCoolingDegreeDays = ini.GetValue("Temp", "CoolingDegreeDays", 0.0); - YestAvgTemp = ini.GetValue("Temp", "AvgTemp", 0.0); - HiLoYest.TempRange = HiLoYest.HighTemp - HiLoYest.LowTemp; - // Pressure - HiLoYest.LowPress = ini.GetValue("Pressure", "Low", 0.0); - HiLoYest.LowPressTime = ini.GetValue("Pressure", "LTime", DateTime.MinValue); - HiLoYest.HighPress = ini.GetValue("Pressure", "High", 0.0); - HiLoYest.HighPressTime = ini.GetValue("Pressure", "HTime", DateTime.MinValue); - // rain rate - HiLoYest.HighRainRate = ini.GetValue("Rain", "High", 0.0); - HiLoYest.HighRainRateTime = ini.GetValue("Rain", "HTime", DateTime.MinValue); - HiLoYest.HighHourlyRain = ini.GetValue("Rain", "HourlyHigh", 0.0); - HiLoYest.HighHourlyRainTime = ini.GetValue("Rain", "HHourlyTime", DateTime.MinValue); - RG11RainYesterday = ini.GetValue("Rain", "RG11Yesterday", 0.0); - // humidity - HiLoYest.LowHumidity = ini.GetValue("Humidity", "Low", 0); - HiLoYest.HighHumidity = ini.GetValue("Humidity", "High", 0); - HiLoYest.LowHumidityTime = ini.GetValue("Humidity", "LTime", DateTime.MinValue); - HiLoYest.HighHumidityTime = ini.GetValue("Humidity", "HTime", DateTime.MinValue); - // Solar - YestSunshineHours = ini.GetValue("Solar", "SunshineHours", 0.0); - // heat index - HiLoYest.HighHeatIndex = ini.GetValue("HeatIndex", "High", 0.0); - HiLoYest.HighHeatIndexTime = ini.GetValue("HeatIndex", "HTime", DateTime.MinValue); - // App temp - HiLoYest.LowAppTemp = ini.GetValue("AppTemp", "Low", 0.0); - HiLoYest.LowAppTempTime = ini.GetValue("AppTemp", "LTime", DateTime.MinValue); - HiLoYest.HighAppTemp = ini.GetValue("AppTemp", "High", 0.0); - HiLoYest.HighAppTempTime = ini.GetValue("AppTemp", "HTime", DateTime.MinValue); - // wind chill - HiLoYest.LowWindChill = ini.GetValue("WindChill", "Low", 0.0); - HiLoYest.LowWindChillTime = ini.GetValue("WindChill", "LTime", DateTime.MinValue); - // Dewpoint - HiLoYest.LowDewPoint = ini.GetValue("Dewpoint", "Low", 0.0); - HiLoYest.LowDewPointTime = ini.GetValue("Dewpoint", "LTime", DateTime.MinValue); - HiLoYest.HighDewPoint = ini.GetValue("Dewpoint", "High", 0.0); - HiLoYest.HighDewPointTime = ini.GetValue("Dewpoint", "HTime", DateTime.MinValue); - // Solar - HiLoYest.HighSolar = ini.GetValue("Solar", "HighSolarRad", 0.0); - HiLoYest.HighSolarTime = ini.GetValue("Solar", "HighSolarRadTime", DateTime.MinValue); - HiLoYest.HighUv = ini.GetValue("Solar", "HighUV", 0.0); - HiLoYest.HighUvTime = ini.GetValue("Solar", "HighUVTime", DateTime.MinValue); - // Feels like - HiLoYest.LowFeelsLike = ini.GetValue("FeelsLike", "Low", 0.0); - HiLoYest.LowFeelsLikeTime = ini.GetValue("FeelsLike", "LTime", DateTime.MinValue); - HiLoYest.HighFeelsLike = ini.GetValue("FeelsLike", "High", 0.0); - HiLoYest.HighFeelsLikeTime = ini.GetValue("FeelsLike", "HTime", DateTime.MinValue); - // Humidex - HiLoYest.HighHumidex = ini.GetValue("Humidex", "High", 0.0); - HiLoYest.HighHumidexTime = ini.GetValue("Humidex", "HTime", DateTime.MinValue); - } - - public void DayReset(DateTime timestamp) - { - int drday = timestamp.Day; - DateTime yesterday = timestamp.AddDays(-1); - cumulus.LogMessage("=== Day reset, today = " + drday); - if (drday != DayResetDay) - { - cumulus.LogMessage("=== Day reset for " + yesterday.Date); - - int day = timestamp.Day; - int month = timestamp.Month; - DayResetDay = drday; - - if (cumulus.CustomMySqlRolloverEnabled) - { - _ = cumulus.CustomMysqlRolloverTimerTick(); - } - - if (cumulus.CustomHttpRolloverEnabled) - { - cumulus.CustomHttpRolloverUpdate(); - } - - // First save today"s extremes - DoDayfile(timestamp); - cumulus.LogMessage("Raincounter = " + Raincounter + " Raindaystart = " + raindaystart); - - // Calculate yesterday"s rain, allowing for the multiplier - - // raintotal && raindaystart are not calibrated - RainYesterday = (Raincounter - raindaystart) * cumulus.Calib.Rain.Mult; - cumulus.LogMessage("Rainyesterday (calibrated) set to " + RainYesterday); - - //AddRecentDailyData(timestamp.AddDays(-1), RainYesterday, (cumulus.RolloverHour == 0 ? SunshineHours : SunshineToMidnight), HiLoToday.LowTemp, HiLoToday.HighTemp, YestAvgTemp); - //RemoveOldRecentDailyData(); - - int rdthresh1000; - if (cumulus.RainDayThreshold < 0) - // default - { - if (cumulus.Units.Rain == 0) - { - rdthresh1000 = 200; // 0.2mm *1000 - } - else - { - rdthresh1000 = 10; // 0.01in *1000 - } - } - else - { - rdthresh1000 = Convert.ToInt32(cumulus.RainDayThreshold * 1000.0); - } - - // set up rain yesterday * 1000 for comparison - int ryest1000 = Convert.ToInt32(RainYesterday * 1000.0); - - cumulus.LogMessage("RainDayThreshold = " + cumulus.RainDayThreshold); - cumulus.LogMessage("rdt1000=" + rdthresh1000 + " ry1000=" + ryest1000); - - if (ryest1000 >= rdthresh1000) - { - // It rained yesterday - cumulus.LogMessage("Yesterday was a rain day"); - ConsecutiveRainDays++; - ConsecutiveDryDays = 0; - cumulus.LogMessage("Consecutive rain days = " + ConsecutiveRainDays); - // check for highs - if (ConsecutiveRainDays > ThisMonth.LongestWetPeriod.Val) - { - ThisMonth.LongestWetPeriod.Val = ConsecutiveRainDays; - ThisMonth.LongestWetPeriod.Ts = yesterday; - WriteMonthIniFile(); - } - - if (ConsecutiveRainDays > ThisYear.LongestWetPeriod.Val) - { - ThisYear.LongestWetPeriod.Val = ConsecutiveRainDays; - ThisYear.LongestWetPeriod.Ts = yesterday; - WriteYearIniFile(); - } - - if (ConsecutiveRainDays > AllTime.LongestWetPeriod.Val) - SetAlltime(AllTime.LongestWetPeriod, ConsecutiveRainDays, yesterday); - - CheckMonthlyAlltime("LongestWetPeriod", ConsecutiveRainDays, true, yesterday); - } - else - { - // It didn't rain yesterday - cumulus.LogMessage("Yesterday was a dry day"); - ConsecutiveDryDays++; - ConsecutiveRainDays = 0; - cumulus.LogMessage("Consecutive dry days = " + ConsecutiveDryDays); - - // check for highs - if (ConsecutiveDryDays > ThisMonth.LongestDryPeriod.Val) - { - ThisMonth.LongestDryPeriod.Val = ConsecutiveDryDays; - ThisMonth.LongestDryPeriod.Ts = yesterday; - WriteMonthIniFile(); - } - - if (ConsecutiveDryDays > ThisYear.LongestDryPeriod.Val) - { - ThisYear.LongestDryPeriod.Val = ConsecutiveDryDays; - ThisYear.LongestDryPeriod.Ts = yesterday; - WriteYearIniFile(); - } - - if (ConsecutiveDryDays > AllTime.LongestDryPeriod.Val) - SetAlltime(AllTime.LongestDryPeriod, ConsecutiveDryDays, yesterday); - - CheckMonthlyAlltime("LongestDryPeriod", ConsecutiveDryDays, true, yesterday); - } - - // offset high temp today timestamp to allow for 0900 rollover - int hr; - int mn; - DateTime ts; - try - { - hr = HiLoToday.HighTempTime.Hour; - mn = HiLoToday.HighTempTime.Minute; - ts = timestamp.Date + new TimeSpan(hr, mn, 0); - - if (hr >= cumulus.RolloverHour) - // time is between rollover hour && midnight - // so subtract a day - ts = ts.AddDays(-1); - } - catch - { - ts = timestamp.AddDays(-1); - } - - if (HiLoToday.HighTemp < AllTime.LowMaxTemp.Val) - { - SetAlltime(AllTime.LowMaxTemp, HiLoToday.HighTemp, ts); - } - - CheckMonthlyAlltime("LowMaxTemp", HiLoToday.HighTemp, false, ts); - - if (HiLoToday.HighTemp < ThisMonth.LowMaxTemp.Val) - { - ThisMonth.LowMaxTemp.Val = HiLoToday.HighTemp; - try - { - hr = HiLoToday.HighTempTime.Hour; - mn = HiLoToday.HighTempTime.Minute; - ThisMonth.LowMaxTemp.Ts = timestamp.Date + new TimeSpan(hr, mn, 0); - - if (hr >= cumulus.RolloverHour) - // time is between rollover hour && midnight - // so subtract a day - ThisMonth.LowMaxTemp.Ts = ThisMonth.LowMaxTemp.Ts.AddDays(-1); - } - catch - { - ThisMonth.LowMaxTemp.Ts = timestamp.AddDays(-1); - } - - WriteMonthIniFile(); - } - - if (HiLoToday.HighTemp < ThisYear.LowMaxTemp.Val) - { - ThisYear.LowMaxTemp.Val = HiLoToday.HighTemp; - try - { - hr = HiLoToday.HighTempTime.Hour; - mn = HiLoToday.HighTempTime.Minute; - ThisYear.LowMaxTemp.Ts = timestamp.Date + new TimeSpan(hr, mn, 0); - - if (hr >= cumulus.RolloverHour) - // time is between rollover hour && midnight - // so subtract a day - ThisYear.LowMaxTemp.Ts = ThisYear.LowMaxTemp.Ts.AddDays(-1); - } - catch - { - ThisYear.LowMaxTemp.Ts = timestamp.AddDays(-1); - } - - WriteYearIniFile(); - } - - // offset low temp today timestamp to allow for 0900 rollover - try - { - hr = HiLoToday.LowTempTime.Hour; - mn = HiLoToday.LowTempTime.Minute; - ts = timestamp.Date + new TimeSpan(hr, mn, 0); - - if (hr >= cumulus.RolloverHour) - // time is between rollover hour && midnight - // so subtract a day - ts = ts.AddDays(-1); - } - catch - { - ts = timestamp.AddDays(-1); - } - - if (HiLoToday.LowTemp > AllTime.HighMinTemp.Val) - { - SetAlltime(AllTime.HighMinTemp, HiLoToday.LowTemp, ts); - } - - CheckMonthlyAlltime("HighMinTemp", HiLoToday.LowTemp, true, ts); - - if (HiLoToday.LowTemp > ThisMonth.HighMinTemp.Val) - { - ThisMonth.HighMinTemp.Val = HiLoToday.LowTemp; - try - { - hr = HiLoToday.LowTempTime.Hour; - mn = HiLoToday.LowTempTime.Minute; - ThisMonth.HighMinTemp.Ts = timestamp.Date + new TimeSpan(hr, mn, 0); - - if (hr >= cumulus.RolloverHour) - // time is between rollover hour && midnight - // so subtract a day - ThisMonth.HighMinTemp.Ts = ThisMonth.HighMinTemp.Ts.AddDays(-1); - } - catch - { - ThisMonth.HighMinTemp.Ts = timestamp.AddDays(-1); - } - WriteMonthIniFile(); - } - - if (HiLoToday.LowTemp > ThisYear.HighMinTemp.Val) - { - ThisYear.HighMinTemp.Val = HiLoToday.LowTemp; - try - { - hr = HiLoToday.LowTempTime.Hour; - mn = HiLoToday.LowTempTime.Minute; - ThisYear.HighMinTemp.Ts = timestamp.Date + new TimeSpan(hr, mn, 0); - - if (hr >= cumulus.RolloverHour) - // time is between rollover hour && midnight - // so subtract a day - ThisYear.HighMinTemp.Ts = ThisYear.HighMinTemp.Ts.AddDays(-1); - } - catch - { - ThisYear.HighMinTemp.Ts = timestamp.AddDays(-1); - } - WriteYearIniFile(); - } - - // check temp range for highs && lows - if (HiLoToday.TempRange > AllTime.HighDailyTempRange.Val) - SetAlltime(AllTime.HighDailyTempRange, HiLoToday.TempRange, yesterday); - - if (HiLoToday.TempRange < AllTime.LowDailyTempRange.Val) - SetAlltime(AllTime.LowDailyTempRange, HiLoToday.TempRange, yesterday); - - CheckMonthlyAlltime("HighDailyTempRange", HiLoToday.TempRange, true, yesterday); - CheckMonthlyAlltime("LowDailyTempRange", HiLoToday.TempRange, false, yesterday); - - if (HiLoToday.TempRange > ThisMonth.HighDailyTempRange.Val) - { - ThisMonth.HighDailyTempRange.Val = HiLoToday.TempRange; - ThisMonth.HighDailyTempRange.Ts = yesterday; - WriteMonthIniFile(); - } - - if (HiLoToday.TempRange < ThisMonth.LowDailyTempRange.Val) - { - ThisMonth.LowDailyTempRange.Val = HiLoToday.TempRange; - ThisMonth.LowDailyTempRange.Ts = yesterday; - WriteMonthIniFile(); - } - - if (HiLoToday.TempRange > ThisYear.HighDailyTempRange.Val) - { - ThisYear.HighDailyTempRange.Val = HiLoToday.TempRange; - ThisYear.HighDailyTempRange.Ts = yesterday; - WriteYearIniFile(); - } - - if (HiLoToday.TempRange < ThisYear.LowDailyTempRange.Val) - { - ThisYear.LowDailyTempRange.Val = HiLoToday.TempRange; - ThisYear.LowDailyTempRange.Ts = yesterday; - WriteYearIniFile(); - } - - RG11RainYesterday = RG11RainToday; - RG11RainToday = 0; - - if (day == 1) - { - // new month starting - cumulus.LogMessage(" New month starting - " + month); - - CopyMonthIniFile(timestamp.AddDays(-1)); - - rainthismonth = 0; - - ThisMonth.HighGust.Val = calibratedgust; - ThisMonth.HighWind.Val = WindAverage; - ThisMonth.HighTemp.Val = OutdoorTemperature; - ThisMonth.LowTemp.Val = OutdoorTemperature; - ThisMonth.HighAppTemp.Val = ApparentTemperature; - ThisMonth.LowAppTemp.Val = ApparentTemperature; - ThisMonth.HighFeelsLike.Val = FeelsLike; - ThisMonth.LowFeelsLike.Val = FeelsLike; - ThisMonth.HighHumidex.Val = Humidex; - ThisMonth.HighPress.Val = Pressure; - ThisMonth.LowPress.Val = Pressure; - ThisMonth.HighRainRate.Val = RainRate; - ThisMonth.HourlyRain.Val = RainLastHour; - ThisMonth.DailyRain.Val = 0; - ThisMonth.HighHumidity.Val = OutdoorHumidity; - ThisMonth.LowHumidity.Val = OutdoorHumidity; - ThisMonth.HighHeatIndex.Val = HeatIndex; - ThisMonth.LowChill.Val = WindChill; - ThisMonth.HighMinTemp.Val = -999; - ThisMonth.LowMaxTemp.Val = 999; - ThisMonth.HighDewPoint.Val = OutdoorDewpoint; - ThisMonth.LowDewPoint.Val = OutdoorDewpoint; - ThisMonth.HighWindRun.Val = 0; - ThisMonth.LongestDryPeriod.Val = 0; - ThisMonth.LongestWetPeriod.Val = 0; - ThisMonth.HighDailyTempRange.Val = -999; - ThisMonth.LowDailyTempRange.Val = 999; - - // this month highs && lows - timestamps - ThisMonth.HighGust.Ts = timestamp; - ThisMonth.HighWind.Ts = timestamp; - ThisMonth.HighTemp.Ts = timestamp; - ThisMonth.LowTemp.Ts = timestamp; - ThisMonth.HighAppTemp.Ts = timestamp; - ThisMonth.LowAppTemp.Ts = timestamp; - ThisMonth.HighFeelsLike.Ts = timestamp; - ThisMonth.LowFeelsLike.Ts = timestamp; - ThisMonth.HighHumidex.Ts = timestamp; - ThisMonth.HighPress.Ts = timestamp; - ThisMonth.LowPress.Ts = timestamp; - ThisMonth.HighRainRate.Ts = timestamp; - ThisMonth.HourlyRain.Ts = timestamp; - ThisMonth.DailyRain.Ts = timestamp; - ThisMonth.HighHumidity.Ts = timestamp; - ThisMonth.LowHumidity.Ts = timestamp; - ThisMonth.HighHeatIndex.Ts = timestamp; - ThisMonth.LowChill.Ts = timestamp; - ThisMonth.HighMinTemp.Ts = timestamp; - ThisMonth.LowMaxTemp.Ts = timestamp; - ThisMonth.HighDewPoint.Ts = timestamp; - ThisMonth.LowDewPoint.Ts = timestamp; - ThisMonth.HighWindRun.Ts = timestamp; - ThisMonth.LongestDryPeriod.Ts = timestamp; - ThisMonth.LongestWetPeriod.Ts = timestamp; - ThisMonth.LowDailyTempRange.Ts = timestamp; - ThisMonth.HighDailyTempRange.Ts = timestamp; - } - else - rainthismonth += RainYesterday; - - if ((day == 1) && (month == 1)) - { - // new year starting - cumulus.LogMessage(" New year starting"); - - CopyYearIniFile(timestamp.AddDays(-1)); - - ThisYear.HighGust.Val = calibratedgust; - ThisYear.HighWind.Val = WindAverage; - ThisYear.HighTemp.Val = OutdoorTemperature; - ThisYear.LowTemp.Val = OutdoorTemperature; - ThisYear.HighAppTemp.Val = ApparentTemperature; - ThisYear.LowAppTemp.Val = ApparentTemperature; - ThisYear.HighFeelsLike.Val = FeelsLike; - ThisYear.LowFeelsLike.Val = FeelsLike; - ThisYear.HighHumidex.Val = Humidex; - ThisYear.HighPress.Val = Pressure; - ThisYear.LowPress.Val = Pressure; - ThisYear.HighRainRate.Val = RainRate; - ThisYear.HourlyRain.Val = RainLastHour; - ThisYear.DailyRain.Val = 0; - ThisYear.MonthlyRain.Val = 0; - ThisYear.HighHumidity.Val = OutdoorHumidity; - ThisYear.LowHumidity.Val = OutdoorHumidity; - ThisYear.HighHeatIndex.Val = HeatIndex; - ThisYear.LowChill.Val = WindChill; - ThisYear.HighMinTemp.Val = -999; - ThisYear.LowMaxTemp.Val = 999; - ThisYear.HighDewPoint.Val = OutdoorDewpoint; - ThisYear.LowDewPoint.Val = OutdoorDewpoint; - ThisYear.HighWindRun.Val = 0; - ThisYear.LongestDryPeriod.Val = 0; - ThisYear.LongestWetPeriod.Val = 0; - ThisYear.HighDailyTempRange.Val = -999; - ThisYear.LowDailyTempRange.Val = 999; - - // this Year highs && lows - timestamps - ThisYear.HighGust.Ts = timestamp; - ThisYear.HighWind.Ts = timestamp; - ThisYear.HighTemp.Ts = timestamp; - ThisYear.LowTemp.Ts = timestamp; - ThisYear.HighAppTemp.Ts = timestamp; - ThisYear.LowAppTemp.Ts = timestamp; - ThisYear.HighFeelsLike.Ts = timestamp; - ThisYear.LowFeelsLike.Ts = timestamp; - ThisYear.HighHumidex.Ts = timestamp; - ThisYear.HighPress.Ts = timestamp; - ThisYear.LowPress.Ts = timestamp; - ThisYear.HighRainRate.Ts = timestamp; - ThisYear.HourlyRain.Ts = timestamp; - ThisYear.DailyRain.Ts = timestamp; - ThisYear.MonthlyRain.Ts = timestamp; - ThisYear.HighHumidity.Ts = timestamp; - ThisYear.LowHumidity.Ts = timestamp; - ThisYear.HighHeatIndex.Ts = timestamp; - ThisYear.LowChill.Ts = timestamp; - ThisYear.HighMinTemp.Ts = timestamp; - ThisYear.LowMaxTemp.Ts = timestamp; - ThisYear.HighDewPoint.Ts = timestamp; - ThisYear.LowDewPoint.Ts = timestamp; - ThisYear.HighWindRun.Ts = timestamp; - ThisYear.LongestDryPeriod.Ts = timestamp; - ThisYear.LongestWetPeriod.Ts = timestamp; - ThisYear.HighDailyTempRange.Ts = timestamp; - ThisYear.LowDailyTempRange.Ts = timestamp; - } - - if ((day == 1) && (month == cumulus.RainSeasonStart)) - { - // new year starting - cumulus.LogMessage(" New rain season starting"); - rainthisyear = 0; - } - else - { - rainthisyear += RainYesterday; - } - - if ((day == 1) && (month == cumulus.ChillHourSeasonStart)) - { - // new year starting - cumulus.LogMessage(" Chill hour season starting"); - ChillHours = 0; - } - - if ((day == 1) && (month == cumulus.GrowingYearStarts)) - { - cumulus.LogMessage(" New growing degree day season starting"); - GrowingDegreeDaysThisYear1 = 0; - GrowingDegreeDaysThisYear2 = 0; - } - - GrowingDegreeDaysThisYear1 += MeteoLib.GrowingDegreeDays(ConvertUserTempToC(HiLoToday.HighTemp), ConvertUserTempToC(HiLoToday.LowTemp), ConvertUserTempToC(cumulus.GrowingBase1), cumulus.GrowingCap30C); - GrowingDegreeDaysThisYear2 += MeteoLib.GrowingDegreeDays(ConvertUserTempToC(HiLoToday.HighTemp), ConvertUserTempToC(HiLoToday.LowTemp), ConvertUserTempToC(cumulus.GrowingBase2), cumulus.GrowingCap30C); - - // Now reset all values to the current or default ones - // We may be doing a rollover from the first logger entry, - // && as we do the rollover before processing the entry, the - // current items may not be set up. - - raindaystart = Raincounter; - cumulus.LogMessage("Raindaystart set to " + raindaystart); - - RainToday = 0; - - TempTotalToday = OutdoorTemperature; - tempsamplestoday = 1; - - // Copy today"s high wind settings to yesterday - HiLoYest.HighWind = HiLoToday.HighWind; - HiLoYest.HighWindTime = HiLoToday.HighWindTime; - HiLoYest.HighGust = HiLoToday.HighGust; - HiLoYest.HighGustTime = HiLoToday.HighGustTime; - HiLoYest.HighGustBearing = HiLoToday.HighGustBearing; - - // Reset today"s high wind settings - HiLoToday.HighGust = calibratedgust; - HiLoToday.HighGustBearing = Bearing; - HiLoToday.HighWind = WindAverage; - - HiLoToday.HighWindTime = timestamp; - HiLoToday.HighGustTime = timestamp; - - // Copy today"s high temp settings to yesterday - HiLoYest.HighTemp = HiLoToday.HighTemp; - HiLoYest.HighTempTime = HiLoToday.HighTempTime; - // Reset today"s high temp settings - HiLoToday.HighTemp = OutdoorTemperature; - HiLoToday.HighTempTime = timestamp; - - // Copy today"s low temp settings to yesterday - HiLoYest.LowTemp = HiLoToday.LowTemp; - HiLoYest.LowTempTime = HiLoToday.LowTempTime; - // Reset today"s low temp settings - HiLoToday.LowTemp = OutdoorTemperature; - HiLoToday.LowTempTime = timestamp; - - HiLoYest.TempRange = HiLoToday.TempRange; - HiLoToday.TempRange = 0; - - // Copy today"s low pressure settings to yesterday - HiLoYest.LowPress = HiLoToday.LowPress; - HiLoYest.LowPressTime = HiLoToday.LowPressTime; - // Reset today"s low pressure settings - HiLoToday.LowPress = Pressure; - HiLoToday.LowPressTime = timestamp; - - // Copy today"s high pressure settings to yesterday - HiLoYest.HighPress = HiLoToday.HighPress; - HiLoYest.HighPressTime = HiLoToday.HighPressTime; - // Reset today"s high pressure settings - HiLoToday.HighPress = Pressure; - HiLoToday.HighPressTime = timestamp; - - // Copy today"s high rain rate settings to yesterday - HiLoYest.HighRainRate = HiLoToday.HighRainRate; - HiLoYest.HighRainRateTime = HiLoToday.HighRainRateTime; - // Reset today"s high rain rate settings - HiLoToday.HighRainRate = RainRate; - HiLoToday.HighRainRateTime = timestamp; - - HiLoYest.HighHourlyRain = HiLoToday.HighHourlyRain; - HiLoYest.HighHourlyRainTime = HiLoToday.HighHourlyRainTime; - HiLoToday.HighHourlyRain = RainLastHour; - HiLoToday.HighHourlyRainTime = timestamp; - - YesterdayWindRun = WindRunToday; - WindRunToday = 0; - - YestDominantWindBearing = DominantWindBearing; - - DominantWindBearing = 0; - DominantWindBearingX = 0; - DominantWindBearingY = 0; - DominantWindBearingMinutes = 0; - - YestHeatingDegreeDays = HeatingDegreeDays; - YestCoolingDegreeDays = CoolingDegreeDays; - HeatingDegreeDays = 0; - CoolingDegreeDays = 0; - - // reset startofdayET value - StartofdayET = AnnualETTotal; - cumulus.LogMessage("StartofdayET set to " + StartofdayET); - ET = 0; - - // Humidity - HiLoYest.LowHumidity = HiLoToday.LowHumidity; - HiLoYest.LowHumidityTime = HiLoToday.LowHumidityTime; - HiLoToday.LowHumidity = OutdoorHumidity; - HiLoToday.LowHumidityTime = timestamp; - - HiLoYest.HighHumidity = HiLoToday.HighHumidity; - HiLoYest.HighHumidityTime = HiLoToday.HighHumidityTime; - HiLoToday.HighHumidity = OutdoorHumidity; - HiLoToday.HighHumidityTime = timestamp; - - // heat index - HiLoYest.HighHeatIndex = HiLoToday.HighHeatIndex; - HiLoYest.HighHeatIndexTime = HiLoToday.HighHeatIndexTime; - HiLoToday.HighHeatIndex = HeatIndex; - HiLoToday.HighHeatIndexTime = timestamp; - - // App temp - HiLoYest.HighAppTemp = HiLoToday.HighAppTemp; - HiLoYest.HighAppTempTime = HiLoToday.HighAppTempTime; - HiLoToday.HighAppTemp = ApparentTemperature; - HiLoToday.HighAppTempTime = timestamp; - - HiLoYest.LowAppTemp = HiLoToday.LowAppTemp; - HiLoYest.LowAppTempTime = HiLoToday.LowAppTempTime; - HiLoToday.LowAppTemp = ApparentTemperature; - HiLoToday.LowAppTempTime = timestamp; - - // wind chill - HiLoYest.LowWindChill = HiLoToday.LowWindChill; - HiLoYest.LowWindChillTime = HiLoToday.LowWindChillTime; - HiLoToday.LowWindChill = WindChill; - HiLoToday.LowWindChillTime = timestamp; - - // dew point - HiLoYest.HighDewPoint = HiLoToday.HighDewPoint; - HiLoYest.HighDewPointTime = HiLoToday.HighDewPointTime; - HiLoToday.HighDewPoint = OutdoorDewpoint; - HiLoToday.HighDewPointTime = timestamp; - - HiLoYest.LowDewPoint = HiLoToday.LowDewPoint; - HiLoYest.LowDewPointTime = HiLoToday.LowDewPointTime; - HiLoToday.LowDewPoint = OutdoorDewpoint; - HiLoToday.LowDewPointTime = timestamp; - - // solar - HiLoYest.HighSolar = HiLoToday.HighSolar; - HiLoYest.HighSolarTime = HiLoToday.HighSolarTime; - HiLoToday.HighSolar = SolarRad; - HiLoToday.HighSolarTime = timestamp; - - HiLoYest.HighUv = HiLoToday.HighUv; - HiLoYest.HighUvTime = HiLoToday.HighUvTime; - HiLoToday.HighUv = UV; - HiLoToday.HighUvTime = timestamp; - - // Feels like - HiLoYest.HighFeelsLike = HiLoToday.HighFeelsLike; - HiLoYest.HighFeelsLikeTime = HiLoToday.HighFeelsLikeTime; - HiLoToday.HighFeelsLike = FeelsLike; - HiLoToday.HighFeelsLikeTime = timestamp; - - HiLoYest.LowFeelsLike = HiLoToday.LowFeelsLike; - HiLoYest.LowFeelsLikeTime = HiLoToday.LowFeelsLikeTime; - HiLoToday.LowFeelsLike = FeelsLike; - HiLoToday.LowFeelsLikeTime = timestamp; - - // Humidex - HiLoYest.HighHumidex = HiLoToday.HighHumidex; - HiLoYest.HighHumidexTime = HiLoToday.HighHumidexTime; - HiLoToday.HighHumidex = Humidex; - HiLoToday.HighHumidexTime = timestamp; - - // Save the current values in case of program restart - WriteTodayFile(timestamp, true); - WriteYesterdayFile(); - - if (cumulus.NOAAAutoSave) - { - try - { - NOAA noaa = new NOAA(cumulus); - var utf8WithoutBom = new System.Text.UTF8Encoding(false); - var encoding = cumulus.NOAAUseUTF8 ? utf8WithoutBom : System.Text.Encoding.GetEncoding("iso-8859-1"); - - List report; - - DateTime noaats = timestamp.AddDays(-1); - - // do monthly NOAA report - cumulus.LogMessage("Creating NOAA monthly report for " + noaats.ToLongDateString()); - report = noaa.CreateMonthlyReport(noaats); - cumulus.NOAALatestMonthlyReport = FormatDateTime(cumulus.NOAAMonthFileFormat, noaats); - string noaafile = cumulus.ReportPath + cumulus.NOAALatestMonthlyReport; - cumulus.LogMessage("Saving monthly report as " + noaafile); - File.WriteAllLines(noaafile, report, encoding); - - // do yearly NOAA report - cumulus.LogMessage("Creating NOAA yearly report"); - report = noaa.CreateYearlyReport(noaats); - cumulus.NOAALatestYearlyReport = FormatDateTime(cumulus.NOAAYearFileFormat, noaats); - noaafile = cumulus.ReportPath + cumulus.NOAALatestYearlyReport; - cumulus.LogMessage("Saving yearly report as " + noaafile); - File.WriteAllLines(noaafile, report, encoding); - } - catch (Exception ex) - { - cumulus.LogMessage("Error creating NOAA reports: " + ex.Message); - } - } - - // Do we need to upload NOAA reports on next FTP? - cumulus.NOAANeedFTP = cumulus.NOAAAutoFTP; - - if (cumulus.NOAANeedFTP) - { - cumulus.LogMessage("NOAA reports will be uploaded at next web update"); - } - - // Do the End of day Extra files - // This will set a flag to transfer on next FTP if required - cumulus.DoExtraEndOfDayFiles(); - if (cumulus.EODfilesNeedFTP) - { - cumulus.LogMessage("Extra files will be uploaded at next web update"); - } - - // Do the Daily graph data files - CreateEodGraphDataFiles(); - cumulus.LogMessage("If required the daily graph data files will be uploaded at next web update"); - - - if (!string.IsNullOrEmpty(cumulus.DailyProgram)) - { - cumulus.LogMessage("Executing daily program: " + cumulus.DailyProgram + " params: " + cumulus.DailyParams); - try - { - // Prepare the process to run - ProcessStartInfo start = new ProcessStartInfo(); - // Enter in the command line arguments - start.Arguments = cumulus.DailyParams; - // Enter the executable to run, including the complete path - start.FileName = cumulus.DailyProgram; - // Don"t show a console window - start.CreateNoWindow = true; - // Run the external process - Process.Start(start); - } - catch (Exception ex) - { - cumulus.LogMessage("Error executing external program: " + ex.Message); - } - } - - CurrentDay = timestamp.Day; - CurrentMonth = timestamp.Month; - CurrentYear = timestamp.Year; - cumulus.LogMessage("=== Day reset complete"); - cumulus.LogMessage("Now recording data for day=" + CurrentDay + " month=" + CurrentMonth + " year=" + CurrentYear); - } - else - { - cumulus.LogMessage("=== Day reset already done on day " + drday); - } - } - - private void CopyMonthIniFile(DateTime ts) - { - string year = ts.Year.ToString(); - string month = ts.Month.ToString("D2"); - string savedFile = cumulus.Datapath + "month" + year + month + ".ini"; - cumulus.LogMessage("Saving month.ini file as " + savedFile); - try - { - File.Copy(cumulus.MonthIniFile, savedFile); - } - catch (Exception) - { - // ignore - probably just that it has already been copied - } - } - - private void CopyYearIniFile(DateTime ts) - { - string year = ts.Year.ToString(); - string savedFile = cumulus.Datapath + "year" + year + ".ini"; - cumulus.LogMessage("Saving year.ini file as " + savedFile); - try - { - File.Copy(cumulus.YearIniFile, savedFile); - } - catch (Exception) - { - // ignore - probably just that it has already been copied - } - } - - private void DoDayfile(DateTime timestamp) - { - // Writes an entry to the daily extreme log file. Fields are comma-separated. - // 0 Date in the form dd/mm/yy (the slash may be replaced by a dash in some cases) - // 1 Highest wind gust - // 2 Bearing of highest wind gust - // 3 Time of highest wind gust - // 4 Minimum temperature - // 5 Time of minimum temperature - // 6 Maximum temperature - // 7 Time of maximum temperature - // 8 Minimum sea level pressure - // 9 Time of minimum pressure - // 10 Maximum sea level pressure - // 11 Time of maximum pressure - // 12 Maximum rainfall rate - // 13 Time of maximum rainfall rate - // 14 Total rainfall for the day - // 15 Average temperature for the day - // 16 Total wind run - // 17 Highest average wind speed - // 18 Time of highest average wind speed - // 19 Lowest humidity - // 20 Time of lowest humidity - // 21 Highest humidity - // 22 Time of highest humidity - // 23 Total evapotranspiration - // 24 Total hours of sunshine - // 25 High heat index - // 26 Time of high heat index - // 27 High apparent temperature - // 28 Time of high apparent temperature - // 29 Low apparent temperature - // 30 Time of low apparent temperature - // 31 High hourly rain - // 32 Time of high hourly rain - // 33 Low wind chill - // 34 Time of low wind chill - // 35 High dew point - // 36 Time of high dew point - // 37 Low dew point - // 38 Time of low dew point - // 39 Dominant wind bearing - // 40 Heating degree days - // 41 Cooling degree days - // 42 High solar radiation - // 43 Time of high solar radiation - // 44 High UV Index - // 45 Time of high UV Index - // 46 High Feels like - // 47 Time of high feels like - // 48 Low feels like - // 49 Time of low feels like - // 50 High Humidex - // 51 Time of high Humidex - - double AvgTemp; - if (tempsamplestoday > 0) - AvgTemp = TempTotalToday / tempsamplestoday; - else - AvgTemp = 0; - - // save the value for yesterday - YestAvgTemp = AvgTemp; - - string datestring = timestamp.AddDays(-1).ToString("dd/MM/yy"); ; - // NB this string is just for logging, the dayfile update code is further down - var strb = new StringBuilder(300); - strb.Append(datestring + cumulus.ListSeparator); - strb.Append(HiLoToday.HighGust.ToString(cumulus.WindFormat) + cumulus.ListSeparator); - strb.Append(HiLoToday.HighGustBearing + cumulus.ListSeparator); - strb.Append(HiLoToday.HighGustTime.ToString("HH:mm") + cumulus.ListSeparator); - strb.Append(HiLoToday.LowTemp.ToString(cumulus.TempFormat) + cumulus.ListSeparator); - strb.Append(HiLoToday.LowTempTime.ToString("HH:mm") + cumulus.ListSeparator); - strb.Append(HiLoToday.HighTemp.ToString(cumulus.TempFormat) + cumulus.ListSeparator); - strb.Append(HiLoToday.HighTempTime.ToString("HH:mm") + cumulus.ListSeparator); - strb.Append(HiLoToday.LowPress.ToString(cumulus.PressFormat) + cumulus.ListSeparator); - strb.Append(HiLoToday.LowPressTime.ToString("HH:mm") + cumulus.ListSeparator); - strb.Append(HiLoToday.HighPress.ToString(cumulus.PressFormat) + cumulus.ListSeparator); - strb.Append(HiLoToday.HighPressTime.ToString("HH:mm") + cumulus.ListSeparator); - strb.Append(HiLoToday.HighRainRate.ToString(cumulus.RainFormat) + cumulus.ListSeparator); - strb.Append(HiLoToday.HighRainRateTime.ToString("HH:mm") + cumulus.ListSeparator); - strb.Append(RainToday.ToString(cumulus.RainFormat) + cumulus.ListSeparator); - strb.Append(AvgTemp.ToString(cumulus.TempFormat) + cumulus.ListSeparator); - strb.Append(WindRunToday.ToString("F1") + cumulus.ListSeparator); - strb.Append(HiLoToday.HighWind.ToString(cumulus.WindAvgFormat) + cumulus.ListSeparator); - strb.Append(HiLoToday.HighWindTime.ToString("HH:mm") + cumulus.ListSeparator); - strb.Append(HiLoToday.LowHumidity + cumulus.ListSeparator); - strb.Append(HiLoToday.LowHumidityTime.ToString("HH:mm") + cumulus.ListSeparator); - strb.Append(HiLoToday.HighHumidity + cumulus.ListSeparator); - strb.Append(HiLoToday.HighHumidityTime.ToString("HH:mm") + cumulus.ListSeparator); - strb.Append(ET.ToString(cumulus.ETFormat) + cumulus.ListSeparator); - if (cumulus.RolloverHour == 0) - { - // use existing current sunshinehour count - strb.Append(SunshineHours.ToString(cumulus.SunFormat) + cumulus.ListSeparator); - } - else - { - // for non-midnight rollover, use new item - strb.Append(SunshineToMidnight.ToString(cumulus.SunFormat) + cumulus.ListSeparator); - } - strb.Append(HiLoToday.HighHeatIndex.ToString(cumulus.TempFormat) + cumulus.ListSeparator); - strb.Append(HiLoToday.HighHeatIndexTime.ToString("HH:mm") + cumulus.ListSeparator); - strb.Append(HiLoToday.HighAppTemp.ToString(cumulus.TempFormat) + cumulus.ListSeparator); - strb.Append(HiLoToday.HighAppTempTime.ToString("HH:mm") + cumulus.ListSeparator); - strb.Append(HiLoToday.LowAppTemp.ToString(cumulus.TempFormat) + cumulus.ListSeparator); - strb.Append(HiLoToday.LowAppTempTime.ToString("HH:mm") + cumulus.ListSeparator); - strb.Append(HiLoToday.HighHourlyRain.ToString(cumulus.RainFormat) + cumulus.ListSeparator); - strb.Append(HiLoToday.HighHourlyRainTime.ToString("HH:mm") + cumulus.ListSeparator); - strb.Append(HiLoToday.LowWindChill.ToString(cumulus.TempFormat) + cumulus.ListSeparator); - strb.Append(HiLoToday.LowWindChillTime.ToString("HH:mm") + cumulus.ListSeparator); - strb.Append(HiLoToday.HighDewPoint.ToString(cumulus.TempFormat) + cumulus.ListSeparator); - strb.Append(HiLoToday.HighDewPointTime.ToString("HH:mm") + cumulus.ListSeparator); - strb.Append(HiLoToday.LowDewPoint.ToString(cumulus.TempFormat) + cumulus.ListSeparator); - strb.Append(HiLoToday.LowDewPointTime.ToString("HH:mm") + cumulus.ListSeparator); - strb.Append(DominantWindBearing + cumulus.ListSeparator); - strb.Append(HeatingDegreeDays.ToString("F1") + cumulus.ListSeparator); - strb.Append(CoolingDegreeDays.ToString("F1") + cumulus.ListSeparator); - strb.Append((int)HiLoToday.HighSolar + cumulus.ListSeparator); - strb.Append(HiLoToday.HighSolarTime.ToString("HH:mm") + cumulus.ListSeparator); - strb.Append(HiLoToday.HighUv.ToString(cumulus.UVFormat) + cumulus.ListSeparator); - strb.Append(HiLoToday.HighUvTime.ToString("HH:mm") + cumulus.ListSeparator); - strb.Append(HiLoToday.HighFeelsLike.ToString(cumulus.TempFormat) + cumulus.ListSeparator); - strb.Append(HiLoToday.HighFeelsLikeTime.ToString("HH:mm") + cumulus.ListSeparator); - strb.Append(HiLoToday.LowFeelsLike.ToString(cumulus.TempFormat) + cumulus.ListSeparator); - strb.Append(HiLoToday.LowFeelsLikeTime.ToString("HH:mm") + cumulus.ListSeparator); - strb.Append(HiLoToday.HighHumidex.ToString(cumulus.TempFormat) + cumulus.ListSeparator); - strb.Append(HiLoToday.HighHumidexTime.ToString("HH:mm")); - - cumulus.LogMessage("Dayfile.txt entry:"); - cumulus.LogMessage(strb.ToString()); - - try - { - using (FileStream fs = new FileStream(cumulus.DayFileName, FileMode.Append, FileAccess.Write, FileShare.Read)) - using (StreamWriter file = new StreamWriter(fs)) - { - cumulus.LogMessage("Dayfile.txt opened for writing"); - - if ((HiLoToday.HighTemp < -400) || (HiLoToday.LowTemp > 900)) - { - cumulus.LogMessage("***Error: Daily values are still at default at end of day"); - cumulus.LogMessage("Data not logged to dayfile.txt"); - } - else - { - cumulus.LogMessage("Writing entry to dayfile.txt"); - - file.WriteLine(strb.ToString()); - file.Close(); - } - } - } - catch (Exception ex) - { - cumulus.LogMessage("Error writing to dayfile.txt: " + ex.Message); - } - - // Add a new record to the in memory dayfile data - var newRec = new dayfilerec(); - var tim = timestamp.AddDays(-1); - newRec.Date = new DateTime(tim.Year, tim.Month, tim.Day); - newRec.HighGust = HiLoToday.HighGust; - newRec.HighGustBearing = HiLoToday.HighGustBearing; - newRec.HighGustTime = HiLoToday.HighGustTime; - newRec.LowTemp = HiLoToday.LowTemp; - newRec.LowTempTime = HiLoToday.LowTempTime; - newRec.HighTemp = HiLoToday.HighTemp; - newRec.HighTempTime = HiLoToday.HighTempTime; - newRec.LowPress = HiLoToday.LowPress; - newRec.LowPressTime = HiLoToday.LowPressTime; - newRec.HighPress = HiLoToday.HighPress; - newRec.HighPressTime = HiLoToday.HighPressTime; - newRec.HighRainRate = HiLoToday.HighRainRate; - newRec.HighRainRateTime = HiLoToday.HighRainRateTime; - newRec.TotalRain = RainToday; - newRec.AvgTemp = AvgTemp; - newRec.WindRun = WindRunToday; - newRec.HighAvgWind = HiLoToday.HighWind; - newRec.HighAvgWindTime = HiLoToday.HighWindTime; - newRec.LowHumidity = HiLoToday.LowHumidity; - newRec.LowHumidityTime = HiLoToday.LowHumidityTime; - newRec.HighHumidity = HiLoToday.HighHumidity; - newRec.HighHumidityTime = HiLoToday.HighHumidityTime; - newRec.ET = ET; - newRec.SunShineHours = cumulus.RolloverHour == 0 ? SunshineHours : SunshineToMidnight; - newRec.HighHeatIndex = HiLoToday.HighHeatIndex; - newRec.HighHeatIndexTime = HiLoToday.HighHeatIndexTime; - newRec.HighAppTemp = HiLoToday.HighAppTemp; - newRec.HighAppTempTime = HiLoToday.HighAppTempTime; - newRec.LowAppTemp = HiLoToday.LowAppTemp; - newRec.LowAppTempTime = HiLoToday.LowAppTempTime; - newRec.HighHourlyRain = HiLoToday.HighHourlyRain; - newRec.HighHourlyRainTime = HiLoToday.HighHourlyRainTime; - newRec.LowWindChill = HiLoToday.LowWindChill; - newRec.LowWindChillTime = HiLoToday.LowWindChillTime; - newRec.HighDewPoint = HiLoToday.HighDewPoint; - newRec.HighDewPointTime = HiLoToday.HighDewPointTime; - newRec.LowDewPoint = HiLoToday.LowDewPoint; - newRec.LowDewPointTime = HiLoToday.LowDewPointTime; - newRec.DominantWindBearing = DominantWindBearing; - newRec.HeatingDegreeDays = HeatingDegreeDays; - newRec.CoolingDegreeDays = CoolingDegreeDays; - newRec.HighSolar = (int)HiLoToday.HighSolar; - newRec.HighSolarTime = HiLoToday.HighSolarTime; - newRec.HighUv = HiLoToday.HighUv; - newRec.HighUvTime = HiLoToday.HighUvTime; - newRec.HighFeelsLike = HiLoToday.HighFeelsLike; - newRec.HighFeelsLikeTime = HiLoToday.HighFeelsLikeTime; - newRec.LowFeelsLike = HiLoToday.LowFeelsLike; - newRec.LowFeelsLikeTime = HiLoToday.LowFeelsLikeTime; - newRec.HighHumidex = HiLoToday.HighHumidex; - newRec.HighHumidexTime = HiLoToday.HighHumidexTime; - - DayFile.Add(newRec); - - - - if (cumulus.DayfileMySqlEnabled) - { - var mySqlConn = new MySqlConnection(cumulus.MySqlConnSettings.ToString()); - - var InvC = new CultureInfo(""); - - StringBuilder queryString = new StringBuilder(cumulus.StartOfDayfileInsertSQL, 1024); - queryString.Append(" Values('"); - queryString.Append(timestamp.AddDays(-1).ToString("yy-MM-dd") + "',"); - queryString.Append(HiLoToday.HighGust.ToString(cumulus.WindFormat, InvC) + ","); - queryString.Append(HiLoToday.HighGustBearing + ","); - queryString.Append(HiLoToday.HighGustTime.ToString("\\'HH:mm\\'") + ","); - queryString.Append(HiLoToday.LowTemp.ToString(cumulus.TempFormat, InvC) + ","); - queryString.Append(HiLoToday.LowTempTime.ToString("\\'HH:mm\\'") + ","); - queryString.Append(HiLoToday.HighTemp.ToString(cumulus.TempFormat, InvC) + ","); - queryString.Append(HiLoToday.HighTempTime.ToString("\\'HH:mm\\'") + ","); - queryString.Append(HiLoToday.LowPress.ToString(cumulus.PressFormat, InvC) + ","); - queryString.Append(HiLoToday.LowPressTime.ToString("\\'HH:mm\\'") + ","); - queryString.Append(HiLoToday.HighPress.ToString(cumulus.PressFormat, InvC) + ","); - queryString.Append(HiLoToday.HighPressTime.ToString("\\'HH:mm\\'") + ","); - queryString.Append(HiLoToday.HighRainRate.ToString(cumulus.RainFormat, InvC) + ","); - queryString.Append(HiLoToday.HighRainRateTime.ToString("\\'HH:mm\\'") + ","); - queryString.Append(RainToday.ToString(cumulus.RainFormat, InvC) + ","); - queryString.Append(AvgTemp.ToString(cumulus.TempFormat, InvC) + ","); - queryString.Append(WindRunToday.ToString("F1", InvC) + ","); - queryString.Append(HiLoToday.HighWind.ToString(cumulus.WindAvgFormat, InvC) + ","); - queryString.Append(HiLoToday.HighWindTime.ToString("\\'HH:mm\\'") + ","); - queryString.Append(HiLoToday.LowHumidity + ","); - queryString.Append(HiLoToday.LowHumidityTime.ToString("\\'HH:mm\\'") + ","); - queryString.Append(HiLoToday.HighHumidity + ","); - queryString.Append(HiLoToday.HighHumidityTime.ToString("\\'HH:mm\\'") + ","); - queryString.Append(ET.ToString(cumulus.ETFormat, InvC) + ","); - queryString.Append((cumulus.RolloverHour == 0 ? SunshineHours.ToString(cumulus.SunFormat, InvC) : SunshineToMidnight.ToString(cumulus.SunFormat, InvC)) + ","); - queryString.Append(HiLoToday.HighHeatIndex.ToString(cumulus.TempFormat, InvC) + ","); - queryString.Append(HiLoToday.HighHeatIndexTime.ToString("\\'HH:mm\\'") + ","); - queryString.Append(HiLoToday.HighAppTemp.ToString(cumulus.TempFormat, InvC) + ","); - queryString.Append(HiLoToday.HighAppTempTime.ToString("\\'HH:mm\\'") + ","); - queryString.Append(HiLoToday.LowAppTemp.ToString(cumulus.TempFormat, InvC) + ","); - queryString.Append(HiLoToday.LowAppTempTime.ToString("\\'HH:mm\\'") + ","); - queryString.Append(HiLoToday.HighHourlyRain.ToString(cumulus.RainFormat, InvC) + ","); - queryString.Append(HiLoToday.HighHourlyRainTime.ToString("\\'HH:mm\\'") + ","); - queryString.Append(HiLoToday.LowWindChill.ToString(cumulus.TempFormat, InvC) + ","); - queryString.Append(HiLoToday.LowWindChillTime.ToString("\\'HH:mm\\'") + ","); - queryString.Append(HiLoToday.HighDewPoint.ToString(cumulus.TempFormat, InvC) + ","); - queryString.Append(HiLoToday.HighDewPointTime.ToString("\\'HH:mm\\'") + ","); - queryString.Append(HiLoToday.LowDewPoint.ToString(cumulus.TempFormat, InvC) + ","); - queryString.Append(HiLoToday.LowDewPointTime.ToString("\\'HH:mm\\'") + ","); - queryString.Append(DominantWindBearing + ","); - queryString.Append(HeatingDegreeDays.ToString("F1", InvC) + ","); - queryString.Append(CoolingDegreeDays.ToString("F1", InvC) + ","); - queryString.Append((int)HiLoToday.HighSolar + ","); - queryString.Append(HiLoToday.HighSolarTime.ToString("\\'HH:mm\\'") + ","); - queryString.Append(HiLoToday.HighUv.ToString(cumulus.UVFormat, InvC) + ","); - queryString.Append(HiLoToday.HighUvTime.ToString("\\'HH:mm\\'") + ",'"); - queryString.Append(CompassPoint(HiLoToday.HighGustBearing) + "','"); - queryString.Append(CompassPoint(DominantWindBearing) + "',"); - queryString.Append(HiLoToday.HighFeelsLike.ToString(cumulus.TempFormat, InvC) + ","); - queryString.Append(HiLoToday.HighFeelsLikeTime.ToString("\\'HH:mm\\'") + ","); - queryString.Append(HiLoToday.LowFeelsLike.ToString(cumulus.TempFormat, InvC) + ","); - queryString.Append(HiLoToday.LowFeelsLikeTime.ToString("\\'HH:mm\\'") + ","); - queryString.Append(HiLoToday.HighHumidex.ToString(cumulus.TempFormat, InvC) + ","); - queryString.Append(HiLoToday.HighFeelsLikeTime.ToString("\\'HH:mm\\'")); - - queryString.Append(")"); - - // run the query async so we do not block the main EOD processing - _ = cumulus.MySqlCommandAsync(queryString.ToString(), mySqlConn, "MySQL Dayfile", true, true); - } - } - - /// - /// Calculate checksum of data received from serial port - /// - /// - /// - protected int checksum(List data) - { - int sum = 0; - - for (int i = 0; i < data.Count - 1; i++) - { - sum += data[i]; - } - - return sum % 256; - } - - protected int BCDchartoint(int c) - { - return ((c / 16) * 10) + (c % 16); - } - - /// - /// Convert temp supplied in C to units in use - /// - /// Temp in C - /// Temp in configured units - public double ConvertTempCToUser(double value) - { - if (cumulus.Units.Temp == 1) - { - return MeteoLib.CToF(value); - } - else - { - // C - return value; - } - } - - /// - /// Convert temp supplied in F to units in use - /// - /// Temp in F - /// Temp in configured units - public double ConvertTempFToUser(double value) - { - if (cumulus.Units.Temp == 0) - { - return MeteoLib.FtoC(value); - } - else - { - // F - return value; - } - } - - /// - /// Convert temp supplied in user units to C - /// - /// Temp in configured units - /// Temp in C - public double ConvertUserTempToC(double value) - { - if (cumulus.Units.Temp == 1) - { - return MeteoLib.FtoC(value); - } - else - { - // C - return value; - } - } - - /// - /// Convert temp supplied in user units to F - /// - /// Temp in configured units - /// Temp in F - public double ConvertUserTempToF(double value) - { - if (cumulus.Units.Temp == 1) - { - return value; - } - else - { - // C - return MeteoLib.CToF(value); - } - } - - /// - /// Converts wind supplied in m/s to user units - /// - /// Wind in m/s - /// Wind in configured units - public double ConvertWindMSToUser(double value) - { - switch (cumulus.Units.Wind) - { - case 0: - return value; - case 1: - return value * 2.23693629; - case 2: - return value * 3.6; - case 3: - return value * 1.94384449; - default: - return 0; - } - } - - /// - /// Converts wind supplied in mph to user units - /// - /// Wind in m/s - /// Wind in configured units - public double ConvertWindMPHToUser(double value) - { - switch (cumulus.Units.Wind) - { - case 0: - return value * 0.44704; - case 1: - return value; - case 2: - return value * 1.60934; - case 3: - return value * 0.868976; - default: - return 0; - } - } - - /// - /// Converts wind in user units to m/s - /// - /// - /// - public virtual double ConvertUserWindToMS(double value) - { - switch (cumulus.Units.Wind) - { - case 0: - return value; - case 1: - return value / 2.23693629; - case 2: - return value / 3.6F; - case 3: - return value / 1.94384449; - default: - return 0; - } - } - - /// - /// Converts value in kilometres to distance unit based on users configured wind units - /// - /// - /// Wind in configured units - public double ConvertKmtoUserUnits(double val) - { - switch (cumulus.Units.Wind) - { - case 0: // m/s - case 2: // km/h - return val; - case 1: // mph - return val * 0.621371; - case 3: // knots - return val * 0.539957; - } - return val; - } - - /// - /// Converts windrun supplied in user units to km - /// - /// Windrun in configured units - /// Wind in km - public virtual double ConvertWindRunToKm(double value) - { - switch (cumulus.Units.Wind) - { - case 0: // m/s - case 2: // km/h - return value; - case 1: // mph - return value / 0.621371192; - case 3: // knots - return value / 0.539956803; - default: - return 0; - } - } - - public double ConvertUserWindToKPH(double wind) // input is in Units.Wind units, convert to km/h - { - switch (cumulus.Units.Wind) - { - case 0: // m/s - return wind * 3.6; - case 1: // mph - return wind * 1.609344; - case 2: // kph - return wind; - case 3: // knots - return wind * 1.852; - default: - return wind; - } - } - - /// - /// Converts rain in mm to units in use - /// - /// Rain in mm - /// Rain in configured units - public virtual double ConvertRainMMToUser(double value) - { - return cumulus.Units.Rain == 1 ? value * 0.0393700787 : value; - } - - /// - /// Converts rain in inches to units in use - /// - /// Rain in mm - /// Rain in configured units - public virtual double ConvertRainINToUser(double value) - { - return cumulus.Units.Rain == 1 ? value : value * 25.4; - } - - /// - /// Converts rain in units in use to mm - /// - /// Rain in configured units - /// Rain in mm - public virtual double ConvertUserRainToMM(double value) - { - return cumulus.Units.Rain == 1 ? value / 0.0393700787 : value; - } - - /// - /// Convert pressure in mb to units in use - /// - /// pressure in mb - /// pressure in configured units - public double ConvertPressMBToUser(double value) - { - return cumulus.Units.Press == 2 ? value * 0.0295333727 : value; - } - - /// - /// Convert pressure in inHg to units in use - /// - /// pressure in mb - /// pressure in configured units - public double ConvertPressINHGToUser(double value) - { - return cumulus.Units.Press == 2 ? value : value * 33.8638866667; - } - - /// - /// Convert pressure in units in use to mb - /// - /// pressure in configured units - /// pressure in mb - public double ConvertUserPressToMB(double value) - { - return cumulus.Units.Press == 2 ? value / 0.0295333727 : value; - } - - /// - /// Convert pressure in units in use to inHg - /// - /// pressure in configured units - /// pressure in mb - public double ConvertUserPressToIN(double value) - { - return cumulus.Units.Press == 2 ? value : value * 0.0295333727; - } - - public string CompassPoint(int bearing) - { - return bearing == 0 ? "-" : cumulus.compassp[(((bearing * 100) + 1125) % 36000) / 2250]; - } - - public void StartLoop() - { - t = new Thread(Start) { IsBackground = true }; - t.Start(); - } - - public virtual void getAndProcessHistoryData() - { - } - - public virtual void startReadingHistoryData() - { - } - - /// - /// Calculates average bearing for last 10 minutes - /// - /// - public int CalcAverageBearing() - { - double totalwindX = Last10MinWindList.Sum(o => o.gustX); - double totalwindY = Last10MinWindList.Sum(o => o.gustY); - - if (totalwindX == 0) - { - return 0; - } - - int avgbear = calcavgbear(totalwindX, totalwindY); - - if (avgbear == 0) - { - avgbear = 360; - } - - return avgbear; - } - - private int calcavgbear(double x, double y) - { - var avg = 90 - (int)(RadToDeg(Math.Atan2(y, x))); - if (avg < 0) - { - avg = 360 + avg; - } - - return avg; - } - - /// - /// Adds a new entry to the list of data readings from the last 3 hours - /// - /// - /// - /// - public void AddLast3HourDataEntry(DateTime ts, double press, double temp) - { - Last3HourData last3hourdata = new Last3HourData(ts, press, temp); - - Last3HourDataList.Add(last3hourdata); - } - - /// - /// Adds a new entry to the list of data readings from the last hour - /// - /// - /// - /// - public void AddLastHourDataEntry(DateTime ts, double rain, double temp) - { - LastHourData lasthourdata = new LastHourData(ts, rain, temp); - - LastHourDataList.Add(lasthourdata); - } - - /// - /// Adds a new entry to the list of data readings for the graphs - /// - /// - /// - /// - /// - public void AddGraphDataEntry(DateTime ts, double rain, double raintoday, double rrate, double temp, double dp, double appt, double chill, double heat, double intemp, - double press, double speed, double gust, int avgdir, int wdir, int hum, int inhum, double solar, double smax, double uv, double feels, double humidx) - { - double pm2p5 = -1; - double pm10 = -1; - // Check for Air Quality readings - switch (cumulus.StationOptions.PrimaryAqSensor) - { - case (int)Cumulus.PrimaryAqSensor.AirLinkOutdoor: - if (cumulus.airLinkDataOut != null) - { - pm2p5 = cumulus.airLinkDataOut.pm2p5; - pm10 = cumulus.airLinkDataOut.pm10; - } - break; - case (int)Cumulus.PrimaryAqSensor.AirLinkIndoor: - if (cumulus.airLinkDataIn != null) - { - pm2p5 = cumulus.airLinkDataIn.pm2p5; - pm10 = cumulus.airLinkDataIn.pm10; - } - break; - case (int)Cumulus.PrimaryAqSensor.Ecowitt1: - pm2p5 = AirQuality1; - break; - case (int)Cumulus.PrimaryAqSensor.Ecowitt2: - pm2p5 = AirQuality2; - break; - case (int)Cumulus.PrimaryAqSensor.Ecowitt3: - pm2p5 = AirQuality3; - break; - case (int)Cumulus.PrimaryAqSensor.Ecowitt4: - pm2p5 = AirQuality3; - break; - case (int)Cumulus.PrimaryAqSensor.EcowittCO2: - pm2p5 = CO2_pm2p5; - pm10 = CO2_pm10; - break; - - default: // Not enabled, use invalid values - break; - } - var graphdata = new GraphData(ts, rain, raintoday, rrate, temp, dp, appt, chill, heat, intemp, press, speed, gust, avgdir, wdir, hum, inhum, solar, smax, uv, feels, humidx, pm2p5, pm10); - lock (GraphDataList) - { - GraphDataList.Add(graphdata); - } - } - - public void UpdateGraphDataAqEntry(DateTime ts, double pm2p5, double pm10) - { - try - { - var toUpdate = GraphDataList.Single(x => x.timestamp == ts); - if (toUpdate != null) - { - toUpdate.pm2p5 = pm2p5; - toUpdate.pm10 = pm10; - } - } - catch (InvalidOperationException e) - { - cumulus.LogDebugMessage($"UpdateGraphDataAqEntry: Failed to find a record matching ts: {ts}. Exception: {e.Message}"); - } - catch (Exception e) - { - cumulus.LogMessage($"UpdateGraphDataAqEntry: Exception caught: {e.Message}"); - } - } - - /// - /// Adds a new entry to the list of wind readings from the last 10 minutes - /// - /// - public void AddLast10MinWindEntry(DateTime ts, double windgust, double windspeed, double Xvec, double Yvec) - { - Last10MinWind last10minwind = new Last10MinWind(ts, windgust, windspeed, Xvec, Yvec); - Last10MinWindList.Add(last10minwind); - } - - public double DegToRad(int deg) - { - return deg * Math.PI / 180; - } - - public double RadToDeg(double rad) - { - return rad * 180 / Math.PI; - } - - /* - public double getStartOfDayRainCounter(DateTime timestamp) - { - // TODO: - return -1; - } - */ - - /// - /// Removes entries from Last3HourDataList older than ts - 3 hours - /// - /// - /// - public void RemoveOldL3HData(DateTime ts) - { - DateTime threehoursago = ts.AddHours(-3); - - if (Last3HourDataList.Count > 0) - { - // there are entries to consider - while ((Last3HourDataList.Count > 0) && (Last3HourDataList.First().timestamp < threehoursago)) - { - // the oldest entry is older than 3 hours ago, delete it - Last3HourDataList.RemoveAt(0); - } - } - } - - /// - /// Removes entries from GraphDataList older than ts - 24 hours - /// - /// - /// - public void RemoveOldGraphData(DateTime ts) - { - DateTime graphperiod = ts.AddHours(-cumulus.GraphHours); - lock (GraphDataList) - { - if (GraphDataList.Count > 0) - { - // there are entries to consider - while ((GraphDataList.Count > 0) && (GraphDataList.First().timestamp < graphperiod)) - { - // the oldest entry is older than required, delete it - GraphDataList.RemoveAt(0); - } - } - } - } - - - /// - /// Removes entries from LastHourDataList older than ts - 1 hours - /// - /// - /// - public void RemoveOldLHData(DateTime ts) - { - DateTime onehourago = ts.AddHours(-1); - - if (LastHourDataList.Count > 0) - { - // there are entries to consider - while ((LastHourDataList.Count > 0) && (LastHourDataList.First().timestamp < onehourago)) - { - // the oldest entry is older than 1 hour ago, delete it - LastHourDataList.RemoveAt(0); - } - } - } - - /// - /// Removes entries from Last10MinWindList older than ts - 10 minutes - /// - /// - /// - public void RemoveOld10MinWindData(DateTime ts) - { - DateTime tenminutesago = ts.AddMinutes(-10); - - if (Last10MinWindList.Count > 0) - { - // there are entries to consider - while ((Last10MinWindList.Count > 0) && (Last10MinWindList.First().timestamp < tenminutesago)) - { - // the oldest entry is older than 10 mins ago, delete it - Last10MinWindList.RemoveAt(0); - } - } - } - - public void DoTrendValues(DateTime ts) - { - if (Last3HourDataList.Count > 0) - { - // calculate and display the temp trend - - double firstval = Last3HourDataList.First().temperature; - double lastval = Last3HourDataList.Last().temperature; - - double trendval = (lastval - firstval) / 3.0F; - - temptrendval = trendval; - - cumulus.TempChangeAlarm.UpTriggered = DoAlarm(temptrendval, cumulus.TempChangeAlarm.Value, cumulus.TempChangeAlarm.Enabled, true); - cumulus.TempChangeAlarm.DownTriggered = DoAlarm(temptrendval, cumulus.TempChangeAlarm.Value * -1, cumulus.TempChangeAlarm.Enabled, false); - - if (LastHourDataList.Count > 0) - { - firstval = LastHourDataList.First().temperature; - lastval = LastHourDataList.Last().temperature; - - TempChangeLastHour = lastval - firstval; - - // calculate and display rainfall in last hour - firstval = LastHourDataList.First().raincounter; - lastval = LastHourDataList.Last().raincounter; - - if (lastval < firstval) - { - // rain total has gone down, assume it was reset to zero, just use zero - trendval = 0; - } - else - { - // normal case - trendval = lastval - firstval; - } - - // Round value as some values may have been read from log file and already rounded - trendval = Math.Round(trendval, cumulus.RainDPlaces); - - var tempRainLastHour = trendval * cumulus.Calib.Rain.Mult; - - if (tempRainLastHour > cumulus.Spike.MaxHourlyRain) - { - // ignore - } - else - { - RainLastHour = tempRainLastHour; - - if (RainLastHour > AllTime.HourlyRain.Val) - SetAlltime(AllTime.HourlyRain, RainLastHour, ts); - - CheckMonthlyAlltime("HourlyRain", RainLastHour, true, ts); - - if (RainLastHour > HiLoToday.HighHourlyRain) - { - HiLoToday.HighHourlyRain = RainLastHour; - HiLoToday.HighHourlyRainTime = ts; - WriteTodayFile(ts, false); - } - - if (RainLastHour > ThisMonth.HourlyRain.Val) - { - ThisMonth.HourlyRain.Val = RainLastHour; - ThisMonth.HourlyRain.Ts = ts; - WriteMonthIniFile(); - } - - if (RainLastHour > ThisYear.HourlyRain.Val) - { - ThisYear.HourlyRain.Val = RainLastHour; - ThisYear.HourlyRain.Ts = ts; - WriteYearIniFile(); - } - } - } - - // calculate and display the pressure trend - - firstval = Last3HourDataList.First().pressure; - lastval = Last3HourDataList.Last().pressure; - - // save pressure trend in internal units - presstrendval = (lastval - firstval) / 3.0; - - cumulus.PressChangeAlarm.UpTriggered = DoAlarm(presstrendval, cumulus.PressChangeAlarm.Value, cumulus.PressChangeAlarm.Enabled, true); - cumulus.PressChangeAlarm.DownTriggered = DoAlarm(presstrendval, cumulus.PressChangeAlarm.Value * -1, cumulus.PressChangeAlarm.Enabled, false); - - // Convert for display - trendval = ConvertPressMBToUser(presstrendval); - - if (calculaterainrate) - { - // Station doesn't supply rain rate, calculate one based on rain in last 5 minutes - - DateTime fiveminutesago = ts.AddSeconds(-330); - - var requiredData = from p in LastHourDataList where p.timestamp > fiveminutesago select p; - - var fiveminutedata = requiredData as IList ?? requiredData.ToList(); - if (fiveminutedata.Count() > 1) - { - // we have at least two values to compare - - TimeSpan span = fiveminutedata.Last().timestamp.Subtract(fiveminutedata.First().timestamp); - - double timediffhours = span.TotalHours; - - //cumulus.LogMessage("first time = " + fiveminutedata.First().timestamp + " last time = " + fiveminutedata.Last().timestamp); - //cumulus.LogMessage("timediffhours = " + timediffhours); - - // if less than 5 minutes, use 5 minutes - if (timediffhours < 1.0 / 12.0) - { - timediffhours = 1.0 / 12.0; - } - - double raindiff = Math.Round(fiveminutedata.Last().raincounter, cumulus.RainDPlaces) - Math.Round(fiveminutedata.First().raincounter, cumulus.RainDPlaces); - //cumulus.LogMessage("first value = " + fiveminutedata.First().raincounter + " last value = " + fiveminutedata.Last().raincounter); - //cumulus.LogMessage("raindiff = " + raindiff); - - // Scale the counter values - var tempRainRate = (double)(raindiff / timediffhours) * cumulus.Calib.Rain.Mult; - - if (tempRainRate < 0) - { - tempRainRate = 0; - } - - if (tempRainRate > cumulus.Spike.MaxRainRate) - { - // ignore - } - else - { - RainRate = tempRainRate; - - if (RainRate > AllTime.HighRainRate.Val) - SetAlltime(AllTime.HighRainRate, RainRate, ts); - - CheckMonthlyAlltime("HighRainRate", RainRate, true, ts); - - cumulus.HighRainRateAlarm.Triggered = DoAlarm(RainRate, cumulus.HighRainRateAlarm.Value, cumulus.HighRainRateAlarm.Enabled, true); - - if (RainRate > HiLoToday.HighRainRate) - { - HiLoToday.HighRainRate = RainRate; - HiLoToday.HighRainRateTime = ts; - WriteTodayFile(ts, false); - } - - if (RainRate > ThisMonth.HighRainRate.Val) - { - ThisMonth.HighRainRate.Val = RainRate; - ThisMonth.HighRainRate.Ts = ts; - WriteMonthIniFile(); - } - - if (RainRate > ThisYear.HighRainRate.Val) - { - ThisYear.HighRainRate.Val = RainRate; - ThisYear.HighRainRate.Ts = ts; - WriteYearIniFile(); - } - } - } - } - - // calculate and display rainfall in last 24 hour - var onedayago = ts.AddDays(-1); - var result = RecentDataDb.Query("select * from RecentData where Timestamp >= ? order by Timestamp limit 1", onedayago); - - if (result.Count == 0) - { - // Unable to retrieve rain counter from 24 hours ago - trendval = 0; - } - else - { - firstval = result[0].raincounter; - lastval = Raincounter; - - trendval = lastval - firstval; - // Round value as some values may have been read from log file and already rounded - trendval = Math.Round(trendval, cumulus.RainDPlaces); - - if (trendval < 0) - { - trendval = 0; - } - } - - RainLast24Hour = trendval * cumulus.Calib.Rain.Mult; - } - } - - /* - private double ConvertTempTrendToDisplay(double trendval) - { - double num; - - if (cumulus.TempUnit == 1) - { - num = (trendval*1.8F); - } - else - { - // C - num = trendval; - } - - return num; - } - */ - - public void CalculateDominantWindBearing(int averageBearing, double averageSpeed, int minutes) - { - DominantWindBearingX += (minutes * averageSpeed * Math.Sin(DegToRad(averageBearing))); - DominantWindBearingY += (minutes * averageSpeed * Math.Cos(DegToRad(averageBearing))); - DominantWindBearingMinutes += minutes; - - if (DominantWindBearingX == 0) - { - DominantWindBearing = 0; - } - else - { - try - { - DominantWindBearing = calcavgbear(DominantWindBearingX, DominantWindBearingY); - if (DominantWindBearing == 0) - { - DominantWindBearing = 360; - } - } - catch - { - cumulus.LogMessage("Error in dominant wind direction calculation"); - } - } - - /*if (DominantWindBearingX < 0) - { - DominantWindBearing = 270 - DominantWindBearing; - } - else - { - DominantWindBearing = 90 - DominantWindBearing; - }*/ - } - - public void DoDayResetIfNeeded() - { - int hourInc = cumulus.GetHourInc(); - - if (cumulus.LastUpdateTime.AddHours(hourInc).Date != DateTime.Now.AddHours(hourInc).Date) - { - cumulus.LogMessage("Day reset required"); - DayReset(DateTime.Now); - } - - if (cumulus.LastUpdateTime.Date != DateTime.Now.Date) - { - ResetMidnightRain(DateTime.Now); - ResetSunshineHours(); - //RecalcSolarFactor(DateTime.Now); - } - } - - public int DominantWindBearing { get; set; } - - public int DominantWindBearingMinutes { get; set; } - - public double DominantWindBearingY { get; set; } - - public double DominantWindBearingX { get; set; } - - public double YesterdayWindRun { get; set; } - public double AnnualETTotal { get; set; } - public double StartofdayET { get; set; } - - public int ConsecutiveRainDays { get; set; } - public int ConsecutiveDryDays { get; set; } - public DateTime FOSensorClockTime { get; set; } - public DateTime FOStationClockTime { get; set; } - public double YestAvgTemp { get; set; } - public double AltimeterPressure { get; set; } - public int YestDominantWindBearing { get; set; } - public double RainLast24Hour { get; set; } - public string ConBatText { get; set; } - public string ConSupplyVoltageText { get; set; } - public string TxBatText { get; set; } - - public double YestHeatingDegreeDays { get; set; } - public double YestCoolingDegreeDays { get; set; } - public double TempChangeLastHour { get; set; } - public double WetBulb { get; set; } - public int CloudBase { get; set; } - public double StormRain { get; set; } - public DateTime StartOfStorm { get; set; } - public bool SensorContactLost { get; set; } - public bool DataStopped { get; set; } - public bool IsRaining { get; set; } - - public void LoadLastHoursFromDataLogs(DateTime ts) - { - cumulus.LogMessage("Loading last N hour data from data logs: " + ts); - LoadLastHourFromDataLogs(ts); - LoadLast3HourFromDataLogs(ts); - LoadGraphDataFromDataLogs(ts); - LoadAqGraphDataFromDataLogs(ts); - LoadRecentFromDataLogs(ts); - LoadDayFile(); - //LoadRecentDailyDataFromDayfile(); - LoadRecentWindRose(); - } - - private void LoadRecentFromDataLogs(DateTime ts) - { - // Recent data goes back a week - var datefrom = ts.AddDays(-7); - var dateto = ts; - var entrydate = datefrom; - var filedate = datefrom; - string logFile = cumulus.GetLogFileName(filedate); - bool finished = false; - int numadded = 0; - - cumulus.LogMessage($"LoadRecent: Attempting to load 7 days of entries to recent data list"); - - while (!finished) - { - if (File.Exists(logFile)) - { - int linenum = 0; - int errorCount = 0; - - try - { - using (var sr = new StreamReader(logFile)) - { - do - { - try - { - // process each record in the file - linenum++; - string Line = sr.ReadLine(); - var st = new List(Regex.Split(Line, CultureInfo.CurrentCulture.TextInfo.ListSeparator)); - entrydate = ddmmyyhhmmStrToDate(st[0], st[1]); - - if (entrydate >= datefrom && entrydate <= dateto) - { - // entry is from required period - var raintoday = Convert.ToDouble(st[9]); - var gust = Convert.ToDouble(st[6]); - var speed = Convert.ToDouble(st[5]); - var wlatest = Convert.ToDouble(st[14]); - var bearing = Convert.ToInt32(st[24]); - var avgbearing = Convert.ToInt32(st[7]); - var outsidetemp = Convert.ToDouble(st[2]); - var dewpoint = Convert.ToDouble(st[4]); - var chill = Convert.ToDouble(st[15]); - var heat = Convert.ToDouble(st[16]); - var pressure = Convert.ToDouble(st[10]); - var hum = Convert.ToInt32(st[3]); - var solar = Convert.ToDouble(st[18]); - var uv = Convert.ToDouble(st[17]); - var raincounter = Convert.ToDouble(st[11]); - var feelslike = st.Count > 27 ? Convert.ToDouble(st[27]) : 0; - var humidex = st.Count > 28 ? Convert.ToDouble(st[28]) : 0; - - AddRecentDataEntry(entrydate, speed, gust, wlatest, bearing, avgbearing, outsidetemp, chill, dewpoint, heat, hum, pressure, raintoday, solar, uv, raincounter, feelslike, humidex); - ++numadded; - } - } - catch (Exception e) - { - cumulus.LogMessage($"LoadRecent: Error at line {linenum} of {logFile} : {e.Message}"); - cumulus.LogMessage("Please edit the file to correct the error"); - errorCount++; - if (errorCount >= 10) - { - cumulus.LogMessage($"LoadRecent: Too many errors reading {logFile} - aborting load of graph data"); - } - } - } while (!(sr.EndOfStream || entrydate >= dateto || errorCount >= 10)); - } - } - catch (Exception e) - { - cumulus.LogMessage($"LoadRecent: Error at line {linenum} of {logFile} : {e.Message}"); - cumulus.LogMessage("Please edit the file to correct the error"); - } - } - - if (entrydate >= dateto || filedate > dateto.AddMonths(1)) - { - finished = true; - } - else - { - filedate = filedate.AddMonths(1); - logFile = cumulus.GetLogFileName(filedate); - } - } - cumulus.LogMessage($"LoadRecent: Loaded {numadded} entries to recent data list"); - } - - private void LoadGraphDataFromDataLogs(DateTime ts) - { - var datefrom = ts.AddHours(-cumulus.GraphHours); - var dateto = ts; - var entrydate = datefrom; - var filedate = datefrom; - string logFile = cumulus.GetLogFileName(filedate); - bool finished = false; - - cumulus.LogMessage($"LoadGraphData: Attempting to load {cumulus.GraphHours} hours of entries to graph data list"); - - while (!finished) - { - if (File.Exists(logFile)) - { - int linenum = 0; - int errorCount = 0; - - try - { - using (var sr = new StreamReader(logFile)) - { - do - { - try - { - // process each record in the file - linenum++; - string Line = sr.ReadLine(); - var st = new List(Regex.Split(Line, CultureInfo.CurrentCulture.TextInfo.ListSeparator)); - entrydate = ddmmyyhhmmStrToDate(st[0], st[1]); - - if (entrydate >= datefrom && entrydate <= dateto) - { - // entry is from required period - var raintotal = Convert.ToDouble(st[11]); - var raintoday = Convert.ToDouble(st[9]); - var rainrate = Convert.ToDouble(st[8]); - var gust = Convert.ToDouble(st[6]); - var speed = Convert.ToDouble(st[5]); - var avgbearing = Convert.ToInt32(st[7]); - var bearing = Convert.ToInt32(st[24]); - var outsidetemp = Convert.ToDouble(st[2]); - var dewpoint = Convert.ToDouble(st[4]); - var appt = Convert.ToDouble(st[21]); - var chill = Convert.ToDouble(st[15]); - var heat = Convert.ToDouble(st[16]); - var insidetemp = Convert.ToDouble(st[12]); - var pressure = Convert.ToDouble(st[10]); - var hum = Convert.ToInt32(st[3]); - var inhum = Convert.ToInt32(st[13]); - var solar = Convert.ToDouble(st[18]); - var solarmax = Convert.ToDouble(st[22]); - var uv = Convert.ToDouble(st[17]); - var feels = st.Count > 27 ? Convert.ToDouble(st[27]) : 0.0; - var humidex = st.Count > 28 ? Convert.ToDouble(st[28]) : 0.0; - - AddGraphDataEntry(entrydate, raintotal, raintoday, rainrate, outsidetemp, dewpoint, appt, chill, heat, insidetemp, pressure, speed, gust, - avgbearing, bearing, hum, inhum, solar, solarmax, uv, feels, humidex); - } - } - catch (Exception e) - { - cumulus.LogMessage($"LoadGraphData: Error at line {linenum} of {logFile} : {e.Message}"); - cumulus.LogMessage("LoadGraphData: Please edit the file to correct the error"); - errorCount++; - if (errorCount >= 10) - { - cumulus.LogMessage($"LoadGraphData: Too many errors reading {logFile} - aborting load of graph data"); - } - } - } while (!(sr.EndOfStream || entrydate >= dateto || errorCount >= 10)); - } - } - catch (Exception e) - { - cumulus.LogMessage($"LoadGraphData: Error at line {linenum} of {logFile} : {e.Message}"); - cumulus.LogMessage("Please edit the file to correct the error"); - } - } - - if (entrydate >= dateto || filedate > dateto.AddMonths(1)) - { - finished = true; - } - else - { - filedate = filedate.AddMonths(1); - logFile = cumulus.GetLogFileName(filedate); - } - } - cumulus.LogMessage($"LoadGraphData: Loaded {GraphDataList.Count} entries to graph data list"); - } - - private void LoadAqGraphDataFromDataLogs(DateTime ts) - { - var datefrom = ts.AddHours(-cumulus.GraphHours); - var dateto = ts; - var entrydate = datefrom; - var filedate = datefrom; - string logFile; - bool finished = false; - int updatedCount = 0; - - if (cumulus.StationOptions.PrimaryAqSensor < 0) return; - - cumulus.LogMessage($"LoadAqGraphData: Attempting to load {cumulus.GraphHours} hours of entries to Air Quality graph data"); - - if (cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.AirLinkOutdoor - || cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.AirLinkIndoor) - { - logFile = cumulus.GetAirLinkLogFileName(filedate); - } - else if ((cumulus.StationOptions.PrimaryAqSensor >= (int)Cumulus.PrimaryAqSensor.Ecowitt1 && cumulus.StationOptions.PrimaryAqSensor <= (int)Cumulus.PrimaryAqSensor.Ecowitt4) || - cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.EcowittCO2) // Ecowitt - { - logFile = cumulus.GetExtraLogFileName(filedate); - } - else - { - cumulus.LogMessage($"LoadAqGraphData: Error - The primary AQ sensor is not set to a valid value, currently={cumulus.StationOptions.PrimaryAqSensor}"); - return; - } - - while (!finished) - { - if (File.Exists(logFile)) - { - int linenum = 0; - int errorCount = 0; - - try - { - using (var sr = new StreamReader(logFile)) - { - do - { - try - { - // process each record in the file - linenum++; - string Line = sr.ReadLine(); - var st = new List(Regex.Split(Line, CultureInfo.CurrentCulture.TextInfo.ListSeparator)); - entrydate = ddmmyyhhmmStrToDate(st[0], st[1]); - - if (entrydate >= datefrom && entrydate <= dateto) - { - // entry is from required period - double pm2p5, pm10; - if (cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.AirLinkIndoor) - { - // AirLink Indoor - pm2p5 = Convert.ToDouble(st[5]); - pm10 = Convert.ToDouble(st[10]); - } - else if (cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.AirLinkOutdoor) - { - // AirLink Outdoor - pm2p5 = Convert.ToDouble(st[32]); - pm10 = Convert.ToDouble(st[37]); - } - else if (cumulus.StationOptions.PrimaryAqSensor >= (int)Cumulus.PrimaryAqSensor.Ecowitt1 && cumulus.StationOptions.PrimaryAqSensor <= (int)Cumulus.PrimaryAqSensor.Ecowitt4) - { - // Ecowitt sensor 1-4 - fields 68 -> 71 - pm2p5 = Convert.ToDouble(st[67 + cumulus.StationOptions.PrimaryAqSensor]); - pm10 = -1; - } - else - { - // Ecowitt CO2 sensor - pm2p5 = Convert.ToDouble(st[86]); - pm10 = Convert.ToDouble(st[88]); - } - - UpdateGraphDataAqEntry(entrydate, pm2p5, pm10); - updatedCount++; - } - } - catch (Exception e) - { - cumulus.LogMessage($"LoadAqGraphData: Error at line {linenum} of {logFile} : {e.Message}"); - cumulus.LogMessage("Please edit the file to correct the error"); - errorCount++; - if (errorCount >= 20) - { - cumulus.LogMessage($"LoadAqGraphData: Too many errors reading {logFile} - aborting load of graph data"); - } - } - } while (!(sr.EndOfStream || entrydate >= dateto || errorCount >= 20)); - } - } - catch (Exception e) - { - cumulus.LogMessage($"LoadAqGraphData: Error at line {linenum} of {logFile} : {e.Message}"); - cumulus.LogMessage("Please edit the file to correct the error"); - } - } - - if (entrydate >= dateto || filedate > dateto.AddMonths(1)) - { - finished = true; - } - else - { - filedate = filedate.AddMonths(1); - if (cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.AirLinkOutdoor - || cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.AirLinkIndoor) // AirLink - { - logFile = cumulus.GetAirLinkLogFileName(filedate); - } - else if ((cumulus.StationOptions.PrimaryAqSensor >= (int)Cumulus.PrimaryAqSensor.Ecowitt1 - && cumulus.StationOptions.PrimaryAqSensor <= (int)Cumulus.PrimaryAqSensor.Ecowitt4) - || cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.EcowittCO2) // Ecowitt - { - logFile = cumulus.GetExtraLogFileName(filedate); - } - } - } - cumulus.LogMessage($"LoadAqGraphData: Loaded {updatedCount} entries to graph data list"); - } - - - private void LoadRecentWindRose() - { - // We can now just query the recent data DB as it has been populated from the loags - var datefrom = DateTime.Now.AddHours(-24); - - var result = RecentDataDb.Query("select WindGust, WindDir from RecentData where Timestamp >= ? order by Timestamp", datefrom); - - foreach (var rec in result) - { - windspeeds[nextwindvalue] = rec.WindGust; - windbears[nextwindvalue] = rec.WindDir; - nextwindvalue = (nextwindvalue + 1) % MaxWindRecent; - if (numwindvalues < maxwindvalues) - { - numwindvalues++; - } - } - } - - private void LoadLast3HourFromDataLogs(DateTime ts) - { - var datefrom = ts.AddHours(-3); - var dateto = ts; - var entrydate = datefrom; - var filedate = datefrom; - string logFile = cumulus.GetLogFileName(filedate); - bool finished = false; - - cumulus.LogMessage($"LoadLast3Hour: Attempting to load 3 hour data list"); - - while (!finished) - { - if (File.Exists(logFile)) - { - int linenum = 0; - int errorCount = 0; - try - { - using (var sr = new StreamReader(logFile)) - { - do - { - try - { - // process each record in the file - linenum++; - string Line = sr.ReadLine(); - var st = new List(Regex.Split(Line, CultureInfo.CurrentCulture.TextInfo.ListSeparator)); - entrydate = ddmmyyhhmmStrToDate(st[0], st[1]); - - if (entrydate >= datefrom && entrydate <= dateto) - { - // entry is from required period - var outsidetemp = Convert.ToDouble(st[2]); - var pressure = Convert.ToDouble(st[10]); - - AddLast3HourDataEntry(entrydate, pressure, outsidetemp); - - var gust = Convert.ToDouble(st[14]); - var speed = Convert.ToDouble(st[5]); - var bearing = Convert.ToInt32(st[7]); - - WindRecent[nextwind].Gust = gust; - WindRecent[nextwind].Speed = speed; - WindRecent[nextwind].Timestamp = entrydate; - nextwind = (nextwind + 1) % MaxWindRecent; - - WindVec[nextwindvec].X = gust * Math.Sin(DegToRad(bearing)); - WindVec[nextwindvec].Y = gust * Math.Cos(DegToRad(bearing)); - WindVec[nextwindvec].Timestamp = entrydate; - WindVec[nextwindvec].Bearing = Bearing; // savedBearing; - nextwindvec = (nextwindvec + 1) % MaxWindRecent; - } - } - catch (Exception e) - { - cumulus.LogMessage($"LoadLast3Hour: Error at line {linenum} of {logFile} : {e.Message}"); - cumulus.LogMessage("Please edit the file to correct the error"); - errorCount++; - if (errorCount >= 10) - { - cumulus.LogMessage($"LoadLast3Hour: Too many errors reading {logFile} - aborting load of last hour data"); - } - } - } while (!(sr.EndOfStream || entrydate >= dateto || errorCount >= 10)); - } - } - catch (Exception e) - { - cumulus.LogMessage($"LoadLast3Hour: Error at line {linenum} of {logFile} : {e.Message}"); - cumulus.LogMessage("Please edit the file to correct the error"); - } - } - - if (entrydate >= dateto || filedate > dateto.AddMonths(1)) - { - finished = true; - } - else - { - filedate = filedate.AddMonths(1); - logFile = cumulus.GetLogFileName(filedate); - } - } - cumulus.LogMessage($"LoadLast3Hour: Loaded {Last3HourDataList.Count} entries to last 3 hour data list"); - } - - private void LoadLastHourFromDataLogs(DateTime ts) - { - var datefrom = ts.AddHours(-1); - var dateto = ts; - var entrydate = datefrom; - var filedate = datefrom; - string logFile = cumulus.GetLogFileName(filedate); - bool finished = false; - - cumulus.LogMessage("LoadLastHour: Attempting to load last hour entries"); - - while (!finished) - { - if (File.Exists(logFile)) - { - int linenum = 0; - int errorCount = 0; - - try - { - using (var sr = new StreamReader(logFile)) - { - do - { - try - { - // process each record in the file - linenum++; - string Line = sr.ReadLine(); - var st = new List(Regex.Split(Line, CultureInfo.CurrentCulture.TextInfo.ListSeparator)); - entrydate = ddmmyyhhmmStrToDate(st[0], st[1]); - - if (entrydate >= datefrom && entrydate <= dateto) - { - // entry is from required period - var outsidetemp = Convert.ToDouble(st[2]); - var raintotal = Convert.ToDouble(st[11]); - - AddLastHourDataEntry(entrydate, raintotal, outsidetemp); - } - } - catch (Exception e) - { - cumulus.LogMessage($"LoadLastHour: Error at line {linenum} of {logFile} : {e.Message}"); - cumulus.LogMessage("Please edit the file to correct the error"); - errorCount++; - if (errorCount >= 10) - { - cumulus.LogMessage($"LoadLastHour: Too many errors reading {logFile} - aborting load of last hour data"); - } - } - } while (!(sr.EndOfStream || entrydate >= dateto || errorCount >= 10)); - } - } - catch (Exception e) - { - cumulus.LogMessage($"LoadLastHour: Error at line {linenum} of {logFile} : {e.Message}"); - cumulus.LogMessage("Please edit the file to correct the error"); - } - } - - if (entrydate >= dateto || filedate > dateto.AddMonths(1)) - { - finished = true; - } - else - { - filedate = filedate.AddMonths(1); - logFile = cumulus.GetLogFileName(filedate); - } - } - cumulus.LogMessage($"LoadLastHour: Loaded {LastHourDataList.Count} entries to last hour data list"); - } - - private static DateTime GetDateTime(DateTime date, string time) - { - 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); - } - - public void LoadDayFile() - { - int addedEntries = 0; - - cumulus.LogMessage($"LoadDayFile: Attempting to load the day file"); - if (File.Exists(cumulus.DayFileName)) - { - int linenum = 0; - int errorCount = 0; - - var watch = Stopwatch.StartNew(); - - // Clear the existing list - DayFile.Clear(); - - try - { - using (var sr = new StreamReader(cumulus.DayFileName)) - { - do - { - try - { - // process each record in the file - - linenum++; - string Line = sr.ReadLine(); - DayFile.Add(ParseDayFileRec(Line)); - - addedEntries++; - } - catch (Exception e) - { - cumulus.LogMessage($"LoadDayFile: Error at line {linenum} of {cumulus.DayFileName} : {e.Message}"); - cumulus.LogMessage("Please edit the file to correct the error"); - errorCount++; - if (errorCount >= 20) - { - cumulus.LogMessage($"LoadDayFile: Too many errors reading {cumulus.DayFileName} - aborting load of daily data"); - } - } - } while (!(sr.EndOfStream || errorCount >= 20)); - } - - watch.Stop(); - cumulus.LogDebugMessage($"LoadDayFile: Dayfile parse = {watch.ElapsedMilliseconds} ms"); - - } - catch (Exception e) - { - cumulus.LogMessage($"LoadDayFile: Error at line {linenum} of {cumulus.DayFileName} : {e.Message}"); - cumulus.LogMessage("Please edit the file to correct the error"); - } - cumulus.LogMessage($"LoadDayFile: Loaded {addedEntries} entries to recent daily data list"); - } - else - { - cumulus.LogMessage("LoadDayFile: No Dayfile found - No entries added to recent daily data list"); - } - } - - // errors are caught by the caller - public dayfilerec ParseDayFileRec(string data) - { - var st = new List(Regex.Split(data, CultureInfo.CurrentCulture.TextInfo.ListSeparator)); - double varDbl; - int varInt; - int idx = 0; - - var rec = new dayfilerec(); - try - { - rec.Date = ddmmyyStrToDate(st[idx++]); - rec.HighGust = Convert.ToDouble(st[idx++]); - rec.HighGustBearing = Convert.ToInt32(st[idx++]); - rec.HighGustTime = GetDateTime(rec.Date, st[idx++]); - rec.LowTemp = Convert.ToDouble(st[idx++]); - rec.LowTempTime = GetDateTime(rec.Date, st[idx++]); - rec.HighTemp = Convert.ToDouble(st[idx++]); - rec.HighTempTime = GetDateTime(rec.Date, st[idx++]); - rec.LowPress = Convert.ToDouble(st[idx++]); - rec.LowPressTime = GetDateTime(rec.Date, st[idx++]); - rec.HighPress = Convert.ToDouble(st[idx++]); - rec.HighPressTime = GetDateTime(rec.Date, st[idx++]); - rec.HighRainRate = Convert.ToDouble(st[idx++]); - rec.HighRainRateTime = GetDateTime(rec.Date, st[idx++]); - rec.TotalRain = Convert.ToDouble(st[idx++]); - rec.AvgTemp = Convert.ToDouble(st[idx++]); - - if (st.Count > idx++ && double.TryParse(st[16], out varDbl)) - rec.WindRun = varDbl; - - if (st.Count > idx++ && double.TryParse(st[17], out varDbl)) - rec.HighAvgWind = varDbl; - - if (st.Count > idx++ && st[18].Length == 5) - rec.HighAvgWindTime = GetDateTime(rec.Date, st[18]); - - if (st.Count > idx++ && int.TryParse(st[19], out varInt)) - rec.LowHumidity = varInt; - else - rec.LowHumidity = 9999; - - if (st.Count > idx++ && st[20].Length == 5) - rec.LowHumidityTime = GetDateTime(rec.Date, st[20]); - - if (st.Count > idx++ && int.TryParse(st[21], out varInt)) - rec.HighHumidity = varInt; - else - rec.HighHumidity = -9999; - - if (st.Count > idx++ && st[22].Length == 5) - rec.HighHumidityTime = GetDateTime(rec.Date, st[22]); - - if (st.Count > idx++ && double.TryParse(st[23], out varDbl)) - rec.ET = varDbl; - - if (st.Count > idx++ && double.TryParse(st[24], out varDbl)) - rec.SunShineHours = varDbl; - - if (st.Count > idx++ && double.TryParse(st[25], out varDbl)) - rec.HighHeatIndex = varDbl; - else - rec.HighHeatIndex = -9999; - - if (st.Count > idx++ && st[26].Length == 5) - rec.HighHeatIndexTime = GetDateTime(rec.Date, st[26]); - - if (st.Count > idx++ && double.TryParse(st[27], out varDbl)) - rec.HighAppTemp = varDbl; - else - rec.HighAppTemp = -9999; - - if (st.Count > idx++ && st[28].Length == 5) - rec.HighAppTempTime = GetDateTime(rec.Date, st[28]); - - if (st.Count > idx++ && double.TryParse(st[29], out varDbl)) - rec.LowAppTemp = varDbl; - else - rec.LowAppTemp = 9999; - - if (st.Count > idx++ && st[30].Length == 5) - rec.LowAppTempTime = GetDateTime(rec.Date, st[30]); - - if (st.Count > idx++ && double.TryParse(st[31], out varDbl)) - rec.HighHourlyRain = varDbl; - - if (st.Count > idx++ && st[32].Length == 5) - rec.HighHourlyRainTime = GetDateTime(rec.Date, st[32]); - - if (st.Count > idx++ && double.TryParse(st[33], out varDbl)) - rec.LowWindChill = varDbl; - else - rec.LowWindChill = 9999; - - if (st.Count > idx++ && st[34].Length == 5) - rec.LowWindChillTime = GetDateTime(rec.Date, st[34]); - - if (st.Count > idx++ && double.TryParse(st[35], out varDbl)) - rec.HighDewPoint = varDbl; - else - rec.HighDewPoint = -9999; - - if (st.Count > idx++ && st[36].Length == 5) - rec.HighDewPointTime = GetDateTime(rec.Date, st[36]); - - if (st.Count > idx++ && double.TryParse(st[37], out varDbl)) - rec.LowDewPoint = varDbl; - else - rec.LowDewPoint = 9999; - - if (st.Count > idx++ && st[38].Length == 5) - rec.LowDewPointTime = GetDateTime(rec.Date, st[38]); - - if (st.Count > idx++ && int.TryParse(st[39], out varInt)) - rec.DominantWindBearing = varInt; - - if (st.Count > idx++ && double.TryParse(st[40], out varDbl)) - rec.HeatingDegreeDays = varDbl; - - if (st.Count > idx++ && double.TryParse(st[41], out varDbl)) - rec.CoolingDegreeDays = varDbl; - - if (st.Count > idx++ && int.TryParse(st[42], out varInt)) - rec.HighSolar = varInt; - - if (st.Count > idx++ && st[43].Length == 5) - rec.HighSolarTime = GetDateTime(rec.Date, st[43]); - - if (st.Count > idx++ && double.TryParse(st[44], out varDbl)) - rec.HighUv = varDbl; - - if (st.Count > idx++ && st[45].Length == 5) - rec.HighUvTime = GetDateTime(rec.Date, st[45]); - - if (st.Count > idx++ && double.TryParse(st[46], out varDbl)) - rec.HighFeelsLike = varDbl; - else - rec.HighFeelsLike = -9999; - - if (st.Count > idx++ && st[47].Length == 5) - rec.HighFeelsLikeTime = GetDateTime(rec.Date, st[47]); - - if (st.Count > idx++ && double.TryParse(st[48], out varDbl)) - rec.LowFeelsLike = varDbl; - else - rec.LowFeelsLike = 9999; - - if (st.Count > idx++ && st[49].Length == 5) - rec.LowFeelsLikeTime = GetDateTime(rec.Date, st[49]); - - if (st.Count > idx++ && double.TryParse(st[50], out varDbl)) - rec.HighHumidex = varDbl; - else - rec.HighHumidex = -9999; - - if (st.Count > idx++ && st[51].Length == 5) - rec.HighHumidexTime = GetDateTime(rec.Date, st[51]); - } - catch (Exception ex) - { - cumulus.LogDebugMessage($"ParseDayFileRec: Error at record {idx} - {ex.Message}"); - var e = new Exception($"Error at record {idx} = \"{st[idx-1]}\" - {ex.Message}"); - throw e; - } - return rec; - } - - - protected void UpdateStatusPanel(DateTime timestamp) - { - LastDataReadTimestamp = timestamp; - } - - - protected void UpdateMQTT() - { - if (cumulus.MQTT.EnableDataUpdate) - { - MqttPublisher.UpdateMQTTfeed("DataUpdate"); - } - } - - /// - /// Returns a plus sign if the supplied number is greater than zero, otherwise empty string - /// - /// The number to be tested - /// Plus sign or empty - /* - private string PlusSign(double num) - { - return num > 0 ? "+" : ""; - } - */ - - public void DoET(double value, DateTime timestamp) - { - // Value is annual total - - if (noET) - { - // Start of day ET value not yet set - cumulus.LogMessage("*** First ET reading. Set startofdayET to total: " + value); - StartofdayET = value; - noET = false; - } - - //if ((value == 0) && (StartofdayET > 0)) - if (Math.Round(value, 3) < Math.Round(StartofdayET, 3)) // change b3046 - { - // ET reset - cumulus.LogMessage(String.Format("*** ET Reset *** AnnualET: {0:0.000}, StartofdayET: {1:0.000}, StationET: {2:0.000}, CurrentET: {3:0.000}", AnnualETTotal, StartofdayET, value, ET)); - AnnualETTotal = value; // add b3046 - // set the start of day figure so it reflects the ET - // so far today - StartofdayET = AnnualETTotal - ET; - WriteTodayFile(timestamp, false); - cumulus.LogMessage(String.Format("New ET values. AnnualET: {0:0.000}, StartofdayET: {1:0.000}, StationET: {2:0.000}, CurrentET: {3:0.000}", AnnualETTotal, StartofdayET, value, ET)); - } - else - { - AnnualETTotal = value; - } - - ET = AnnualETTotal - StartofdayET; - - HaveReadData = true; - } - - public void DoSoilMoisture(double value, int index) - { - switch (index) - { - case 1: - SoilMoisture1 = (int)value; - break; - case 2: - SoilMoisture2 = (int)value; - break; - case 3: - SoilMoisture3 = (int)value; - break; - case 4: - SoilMoisture4 = (int)value; - break; - case 5: - SoilMoisture5 = (int)value; - break; - case 6: - SoilMoisture6 = (int)value; - break; - case 7: - SoilMoisture7 = (int)value; - break; - case 8: - SoilMoisture8 = (int)value; - break; - case 9: - SoilMoisture9 = (int)value; - break; - case 10: - SoilMoisture10 = (int)value; - break; - case 11: - SoilMoisture11 = (int)value; - break; - case 12: - SoilMoisture12 = (int)value; - break; - case 13: - SoilMoisture13 = (int)value; - break; - case 14: - SoilMoisture14 = (int)value; - break; - case 15: - SoilMoisture15 = (int)value; - break; - case 16: - SoilMoisture16 = (int)value; - break; - } - } - - public void DoSoilTemp(double value, int index) - { - switch (index) - { - case 1: - SoilTemp1 = value; - break; - case 2: - SoilTemp2 = value; - break; - case 3: - SoilTemp3 = value; - break; - case 4: - SoilTemp4 = value; - break; - case 5: - SoilTemp5 = value; - break; - case 6: - SoilTemp6 = value; - break; - case 7: - SoilTemp7 = value; - break; - case 8: - SoilTemp8 = value; - break; - case 9: - SoilTemp9 = value; - break; - case 10: - SoilTemp10 = value; - break; - case 11: - SoilTemp11 = value; - break; - case 12: - SoilTemp12 = value; - break; - case 13: - SoilTemp13 = value; - break; - case 14: - SoilTemp14 = value; - break; - case 15: - SoilTemp15 = value; - break; - case 16: - SoilTemp16 = value; - break; - } - } - - public void DoAirQuality(double value, int index) - { - switch (index) - { - case 1: - AirQuality1 = value; - break; - case 2: - AirQuality2 = value; - break; - case 3: - AirQuality3 = value; - break; - case 4: - AirQuality4 = value; - break; - } - } - - public void DoAirQualityAvg(double value, int index) - { - switch (index) - { - case 1: - AirQualityAvg1 = value; - break; - case 2: - AirQualityAvg2 = value; - break; - case 3: - AirQualityAvg3 = value; - break; - case 4: - AirQualityAvg4 = value; - break; - } - } - - public void DoLeakSensor(int value, int index) - { - switch (index) - { - case 1: - LeakSensor1 = value; - break; - case 2: - LeakSensor2 = value; - break; - case 3: - LeakSensor3 = value; - break; - case 4: - LeakSensor4 = value; - break; - } - } - - public void DoLeafWetness(double value, int index) - { - switch (index) - { - case 1: - LeafWetness1 = (int)value; - break; - case 2: - LeafWetness2 = (int)value; - break; - case 3: - LeafWetness3 = (int)value; - break; - case 4: - LeafWetness4 = (int)value; - break; - case 5: - LeafWetness5 = (int)value; - break; - case 6: - LeafWetness6 = (int)value; - break; - case 7: - LeafWetness7 = (int)value; - break; - case 8: - LeafWetness8 = (int)value; - break; - } - } - - public void DoLeafTemp(double value, int index) - { - switch (index) - { - case 1: - LeafTemp1 = value; - break; - case 2: - LeafTemp2 = value; - break; - case 3: - LeafTemp3 = value; - break; - case 4: - LeafTemp4 = value; - break; - } - } - - public string BetelCast(double z_hpa, int z_month, string z_wind, int z_trend, bool z_north, double z_baro_top, double z_baro_bottom) - { - double z_range = z_baro_top - z_baro_bottom; - double z_constant = (z_range / 22.0F); - - bool z_summer = (z_month >= 4 && z_month <= 9); // true if "Summer" - - if (z_north) - { - // North hemisphere - if (z_wind == cumulus.compassp[0]) // N - { - z_hpa += 6F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[1]) // NNE - { - z_hpa += 5F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[2]) // NE - { - // z_hpa += 4 ; - z_hpa += 5F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[3]) // ENE - { - z_hpa += 2F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[4]) // E - { - z_hpa -= 0.5F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[5]) // ESE - { - // z_hpa -= 3 ; - z_hpa -= 2F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[6]) // SE - { - z_hpa -= 5F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[7]) // SSE - { - z_hpa -= 8.5F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[8]) // S - { - // z_hpa -= 11 ; - z_hpa -= 12F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[9]) // SSW - { - z_hpa -= 10F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[10]) // SW - { - z_hpa -= 6F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[11]) // WSW - { - z_hpa -= 4.5F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[12]) // W - { - z_hpa -= 3F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[13]) // WNW - { - z_hpa -= 0.5F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[14]) // NW - { - z_hpa += 1.5F / 100 * z_range; - } - else if (z_wind == cumulus.compassp[15]) // NNW - { - z_hpa += 3F / 100F * z_range; - } - if (z_summer) - { - // if Summer - if (z_trend == 1) - { - // rising - z_hpa += 7F / 100F * z_range; - } - else if (z_trend == 2) - { - // falling - z_hpa -= 7F / 100F * z_range; - } - } - } - else - { - // must be South hemisphere - if (z_wind == cumulus.compassp[8]) // S - { - z_hpa += 6F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[9]) // SSW - { - z_hpa += 5F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[10]) // SW - { - // z_hpa += 4 ; - z_hpa += 5F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[11]) // WSW - { - z_hpa += 2F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[12]) // W - { - z_hpa -= 0.5F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[13]) // WNW - { - // z_hpa -= 3 ; - z_hpa -= 2F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[14]) // NW - { - z_hpa -= 5F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[15]) // NNW - { - z_hpa -= 8.5F / 100 * z_range; - } - else if (z_wind == cumulus.compassp[0]) // N - { - // z_hpa -= 11 ; - z_hpa -= 12F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[1]) // NNE - { - z_hpa -= 10F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[2]) // NE - { - z_hpa -= 6F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[3]) // ENE - { - z_hpa -= 4.5F / 100 * z_range; // - } - else if (z_wind == cumulus.compassp[4]) // E - { - z_hpa -= 3F / 100F * z_range; - } - else if (z_wind == cumulus.compassp[5]) // ESE - { - z_hpa -= 0.5F / 100 * z_range; - } - else if (z_wind == cumulus.compassp[6]) // SE - { - z_hpa += 1.5F / 100 * z_range; - } - else if (z_wind == cumulus.compassp[7]) // SSE - { - z_hpa += 3F / 100F * z_range; - } - if (!z_summer) - { - // if Winter - if (z_trend == 1) - { - // rising - z_hpa += 7F / 100F * z_range; - } - else if (z_trend == 2) - { - // falling - z_hpa -= 7F / 100F * z_range; - } - } - } // END North / South - - if (z_hpa == z_baro_top) - { - z_hpa = z_baro_top - 1; - } - - int z_option = (int)Math.Floor((z_hpa - z_baro_bottom) / z_constant); - - StringBuilder z_output = new StringBuilder(100); - if (z_option < 0) - { - z_option = 0; - z_output.Append($"{cumulus.exceptional}, "); - } - if (z_option > 21) - { - z_option = 21; - z_output.Append($"{cumulus.exceptional}, "); - } - - if (z_trend == 1) - { - // rising - Forecastnumber = cumulus.riseOptions[z_option] + 1; - z_output.Append(cumulus.zForecast[cumulus.riseOptions[z_option]]); - } - else if (z_trend == 2) - { - // falling - Forecastnumber = cumulus.fallOptions[z_option] + 1; - z_output.Append(cumulus.zForecast[cumulus.fallOptions[z_option]]); - } - else - { - // must be "steady" - Forecastnumber = cumulus.steadyOptions[z_option] + 1; - z_output.Append(cumulus.zForecast[cumulus.steadyOptions[z_option]]); - } - return z_output.ToString(); - } - - public int Forecastnumber { get; set; } - - /// - /// Takes speed in user units, returns Bft number - /// - /// - /// - public int Beaufort(double speed) - { - double windspeedMS = ConvertUserWindToMS(speed); - if (windspeedMS < 0.3) - return 0; - else if (windspeedMS < 1.6) - return 1; - else if (windspeedMS < 3.4) - return 2; - else if (windspeedMS < 5.5) - return 3; - else if (windspeedMS < 8.0) - return 4; - else if (windspeedMS < 10.8) - return 5; - else if (windspeedMS < 13.9) - return 6; - else if (windspeedMS < 17.2) - return 7; - else if (windspeedMS < 20.8) - return 8; - else if (windspeedMS < 24.5) - return 9; - else if (windspeedMS < 28.5) - return 10; - else if (windspeedMS < 32.7) - return 11; - else return 12; - } - - // This overridden in each station implementation - public abstract void Stop(); - - public void ReadAlltimeIniFile() - { - cumulus.LogMessage(Path.GetFullPath(cumulus.AlltimeIniFile)); - IniFile ini = new IniFile(cumulus.AlltimeIniFile); - - AllTime.HighTemp.Val = ini.GetValue("Temperature", "hightempvalue", -999.0); - AllTime.HighTemp.Ts = ini.GetValue("Temperature", "hightemptime", cumulus.defaultRecordTS); - - AllTime.LowTemp.Val = ini.GetValue("Temperature", "lowtempvalue", 999.0); - AllTime.LowTemp.Ts = ini.GetValue("Temperature", "lowtemptime", cumulus.defaultRecordTS); - - AllTime.LowChill.Val = ini.GetValue("Temperature", "lowchillvalue", 999.0); - AllTime.LowChill.Ts = ini.GetValue("Temperature", "lowchilltime", cumulus.defaultRecordTS); - - AllTime.HighMinTemp.Val = ini.GetValue("Temperature", "highmintempvalue", -999.0); - AllTime.HighMinTemp.Ts = ini.GetValue("Temperature", "highmintemptime", cumulus.defaultRecordTS); - - AllTime.LowMaxTemp.Val = ini.GetValue("Temperature", "lowmaxtempvalue", 999.0); - AllTime.LowMaxTemp.Ts = ini.GetValue("Temperature", "lowmaxtemptime", cumulus.defaultRecordTS); - - AllTime.HighAppTemp.Val = ini.GetValue("Temperature", "highapptempvalue", -999.0); - AllTime.HighAppTemp.Ts = ini.GetValue("Temperature", "highapptemptime", cumulus.defaultRecordTS); - - AllTime.LowAppTemp.Val = ini.GetValue("Temperature", "lowapptempvalue", 999.0); - AllTime.LowAppTemp.Ts = ini.GetValue("Temperature", "lowapptemptime", cumulus.defaultRecordTS); - - AllTime.HighFeelsLike.Val = ini.GetValue("Temperature", "highfeelslikevalue", -999.0); - AllTime.HighFeelsLike.Ts = ini.GetValue("Temperature", "highfeelsliketime", cumulus.defaultRecordTS); - - AllTime.LowFeelsLike.Val = ini.GetValue("Temperature", "lowfeelslikevalue", 999.0); - AllTime.LowFeelsLike.Ts = ini.GetValue("Temperature", "lowfeelsliketime", cumulus.defaultRecordTS); - - AllTime.HighHumidex.Val = ini.GetValue("Temperature", "highhumidexvalue", -999.0); - AllTime.HighHumidex.Ts = ini.GetValue("Temperature", "highhumidextime", cumulus.defaultRecordTS); - - AllTime.HighHeatIndex.Val = ini.GetValue("Temperature", "highheatindexvalue", -999.0); - AllTime.HighHeatIndex.Ts = ini.GetValue("Temperature", "highheatindextime", cumulus.defaultRecordTS); - - AllTime.HighDewPoint.Val = ini.GetValue("Temperature", "highdewpointvalue", -999.0); - AllTime.HighDewPoint.Ts = ini.GetValue("Temperature", "highdewpointtime", cumulus.defaultRecordTS); - - AllTime.LowDewPoint.Val = ini.GetValue("Temperature", "lowdewpointvalue", 999.0); - AllTime.LowDewPoint.Ts = ini.GetValue("Temperature", "lowdewpointtime", cumulus.defaultRecordTS); - - AllTime.HighDailyTempRange.Val = ini.GetValue("Temperature", "hightemprangevalue", 0.0); - AllTime.HighDailyTempRange.Ts = ini.GetValue("Temperature", "hightemprangetime", cumulus.defaultRecordTS); - - AllTime.LowDailyTempRange.Val = ini.GetValue("Temperature", "lowtemprangevalue", 999.0); - AllTime.LowDailyTempRange.Ts = ini.GetValue("Temperature", "lowtemprangetime", cumulus.defaultRecordTS); - - AllTime.HighWind.Val = ini.GetValue("Wind", "highwindvalue", 0.0); - AllTime.HighWind.Ts = ini.GetValue("Wind", "highwindtime", cumulus.defaultRecordTS); - - AllTime.HighGust.Val = ini.GetValue("Wind", "highgustvalue", 0.0); - AllTime.HighGust.Ts = ini.GetValue("Wind", "highgusttime", cumulus.defaultRecordTS); - - AllTime.HighWindRun.Val = ini.GetValue("Wind", "highdailywindrunvalue", 0.0); - AllTime.HighWindRun.Ts = ini.GetValue("Wind", "highdailywindruntime", cumulus.defaultRecordTS); - - AllTime.HighRainRate.Val = ini.GetValue("Rain", "highrainratevalue", 0.0); - AllTime.HighRainRate.Ts = ini.GetValue("Rain", "highrainratetime", cumulus.defaultRecordTS); - - AllTime.DailyRain.Val = ini.GetValue("Rain", "highdailyrainvalue", 0.0); - AllTime.DailyRain.Ts = ini.GetValue("Rain", "highdailyraintime", cumulus.defaultRecordTS); - - AllTime.HourlyRain.Val = ini.GetValue("Rain", "highhourlyrainvalue", 0.0); - AllTime.HourlyRain.Ts = ini.GetValue("Rain", "highhourlyraintime", cumulus.defaultRecordTS); - - AllTime.MonthlyRain.Val = ini.GetValue("Rain", "highmonthlyrainvalue", 0.0); - AllTime.MonthlyRain.Ts = ini.GetValue("Rain", "highmonthlyraintime", cumulus.defaultRecordTS); - - AllTime.LongestDryPeriod.Val = ini.GetValue("Rain", "longestdryperiodvalue", 0); - AllTime.LongestDryPeriod.Ts = ini.GetValue("Rain", "longestdryperiodtime", cumulus.defaultRecordTS); - - AllTime.LongestWetPeriod.Val = ini.GetValue("Rain", "longestwetperiodvalue", 0); - AllTime.LongestWetPeriod.Ts = ini.GetValue("Rain", "longestwetperiodtime", cumulus.defaultRecordTS); - - AllTime.HighPress.Val = ini.GetValue("Pressure", "highpressurevalue", 0.0); - AllTime.HighPress.Ts = ini.GetValue("Pressure", "highpressuretime", cumulus.defaultRecordTS); - - AllTime.LowPress.Val = ini.GetValue("Pressure", "lowpressurevalue", 9999.0); - AllTime.LowPress.Ts = ini.GetValue("Pressure", "lowpressuretime", cumulus.defaultRecordTS); - - AllTime.HighHumidity.Val = ini.GetValue("Humidity", "highhumidityvalue", 0); - AllTime.HighHumidity.Ts = ini.GetValue("Humidity", "highhumiditytime", cumulus.defaultRecordTS); - - AllTime.LowHumidity.Val = ini.GetValue("Humidity", "lowhumidityvalue", 999); - AllTime.LowHumidity.Ts = ini.GetValue("Humidity", "lowhumiditytime", cumulus.defaultRecordTS); - - cumulus.LogMessage("Alltime.ini file read"); - } - - public void WriteAlltimeIniFile() - { - try - { - IniFile ini = new IniFile(cumulus.AlltimeIniFile); - - ini.SetValue("Temperature", "hightempvalue", AllTime.HighTemp.Val); - ini.SetValue("Temperature", "hightemptime", AllTime.HighTemp.Ts); - ini.SetValue("Temperature", "lowtempvalue", AllTime.LowTemp.Val); - ini.SetValue("Temperature", "lowtemptime", AllTime.LowTemp.Ts); - ini.SetValue("Temperature", "lowchillvalue", AllTime.LowChill.Val); - ini.SetValue("Temperature", "lowchilltime", AllTime.LowChill.Ts); - ini.SetValue("Temperature", "highmintempvalue", AllTime.HighMinTemp.Val); - ini.SetValue("Temperature", "highmintemptime", AllTime.HighMinTemp.Ts); - ini.SetValue("Temperature", "lowmaxtempvalue", AllTime.LowMaxTemp.Val); - ini.SetValue("Temperature", "lowmaxtemptime", AllTime.LowMaxTemp.Ts); - ini.SetValue("Temperature", "highapptempvalue", AllTime.HighAppTemp.Val); - ini.SetValue("Temperature", "highapptemptime", AllTime.HighAppTemp.Ts); - ini.SetValue("Temperature", "lowapptempvalue", AllTime.LowAppTemp.Val); - ini.SetValue("Temperature", "lowapptemptime", AllTime.LowAppTemp.Ts); - ini.SetValue("Temperature", "highfeelslikevalue", AllTime.HighFeelsLike.Val); - ini.SetValue("Temperature", "highfeelsliketime", AllTime.HighFeelsLike.Ts); - ini.SetValue("Temperature", "lowfeelslikevalue", AllTime.LowFeelsLike.Val); - ini.SetValue("Temperature", "lowfeelsliketime", AllTime.LowFeelsLike.Ts); - ini.SetValue("Temperature", "highhumidexvalue", AllTime.HighHumidex.Val); - ini.SetValue("Temperature", "highhumidextime", AllTime.HighHumidex.Ts); - ini.SetValue("Temperature", "highheatindexvalue", AllTime.HighHeatIndex.Val); - ini.SetValue("Temperature", "highheatindextime", AllTime.HighHeatIndex.Ts); - ini.SetValue("Temperature", "highdewpointvalue", AllTime.HighDewPoint.Val); - ini.SetValue("Temperature", "highdewpointtime", AllTime.HighDewPoint.Ts); - ini.SetValue("Temperature", "lowdewpointvalue", AllTime.LowDewPoint.Val); - ini.SetValue("Temperature", "lowdewpointtime", AllTime.LowDewPoint.Ts); - ini.SetValue("Temperature", "hightemprangevalue", AllTime.HighDailyTempRange.Val); - ini.SetValue("Temperature", "hightemprangetime", AllTime.HighDailyTempRange.Ts); - ini.SetValue("Temperature", "lowtemprangevalue", AllTime.LowDailyTempRange.Val); - ini.SetValue("Temperature", "lowtemprangetime", AllTime.LowDailyTempRange.Ts); - ini.SetValue("Wind", "highwindvalue", AllTime.HighWind.Val); - ini.SetValue("Wind", "highwindtime", AllTime.HighWind.Ts); - ini.SetValue("Wind", "highgustvalue", AllTime.HighGust.Val); - ini.SetValue("Wind", "highgusttime", AllTime.HighGust.Ts); - ini.SetValue("Wind", "highdailywindrunvalue", AllTime.HighWindRun.Val); - ini.SetValue("Wind", "highdailywindruntime", AllTime.HighWindRun.Ts); - ini.SetValue("Rain", "highrainratevalue", AllTime.HighRainRate.Val); - ini.SetValue("Rain", "highrainratetime", AllTime.HighRainRate.Ts); - ini.SetValue("Rain", "highdailyrainvalue", AllTime.DailyRain.Val); - ini.SetValue("Rain", "highdailyraintime", AllTime.DailyRain.Ts); - ini.SetValue("Rain", "highhourlyrainvalue", AllTime.HourlyRain.Val); - ini.SetValue("Rain", "highhourlyraintime", AllTime.HourlyRain.Ts); - ini.SetValue("Rain", "highmonthlyrainvalue", AllTime.MonthlyRain.Val); - ini.SetValue("Rain", "highmonthlyraintime", AllTime.MonthlyRain.Ts); - ini.SetValue("Rain", "longestdryperiodvalue", AllTime.LongestDryPeriod.Val); - ini.SetValue("Rain", "longestdryperiodtime", AllTime.LongestDryPeriod.Ts); - ini.SetValue("Rain", "longestwetperiodvalue", AllTime.LongestWetPeriod.Val); - ini.SetValue("Rain", "longestwetperiodtime", AllTime.LongestWetPeriod.Ts); - ini.SetValue("Pressure", "highpressurevalue", AllTime.HighPress.Val); - ini.SetValue("Pressure", "highpressuretime", AllTime.HighPress.Ts); - ini.SetValue("Pressure", "lowpressurevalue", AllTime.LowPress.Val); - ini.SetValue("Pressure", "lowpressuretime", AllTime.LowPress.Ts); - ini.SetValue("Humidity", "highhumidityvalue", AllTime.HighHumidity.Val); - ini.SetValue("Humidity", "highhumiditytime", AllTime.HighHumidity.Ts); - ini.SetValue("Humidity", "lowhumidityvalue", AllTime.LowHumidity.Val); - ini.SetValue("Humidity", "lowhumiditytime", AllTime.LowHumidity.Ts); - - ini.Flush(); - } - catch (Exception ex) - { - cumulus.LogMessage("Error writing alltime.ini file: " + ex.Message); - } - } - - public void ReadMonthlyAlltimeIniFile() - { - IniFile ini = new IniFile(cumulus.MonthlyAlltimeIniFile); - for (int month = 1; month <= 12; month++) - { - string monthstr = month.ToString("D2"); - - MonthlyRecs[month].HighTemp.Val = ini.GetValue("Temperature" + monthstr, "hightempvalue", -999.0); - MonthlyRecs[month].HighTemp.Ts = ini.GetValue("Temperature" + monthstr, "hightemptime", cumulus.defaultRecordTS); - - MonthlyRecs[month].LowTemp.Val = ini.GetValue("Temperature" + monthstr, "lowtempvalue", 999.0); - MonthlyRecs[month].LowTemp.Ts = ini.GetValue("Temperature" + monthstr, "lowtemptime", cumulus.defaultRecordTS); - - MonthlyRecs[month].LowChill.Val = ini.GetValue("Temperature" + monthstr, "lowchillvalue", 999.0); - MonthlyRecs[month].LowChill.Ts = ini.GetValue("Temperature" + monthstr, "lowchilltime", cumulus.defaultRecordTS); - - MonthlyRecs[month].HighMinTemp.Val = ini.GetValue("Temperature" + monthstr, "highmintempvalue", -999.0); - MonthlyRecs[month].HighMinTemp.Ts = ini.GetValue("Temperature" + monthstr, "highmintemptime", cumulus.defaultRecordTS); - - MonthlyRecs[month].LowMaxTemp.Val = ini.GetValue("Temperature" + monthstr, "lowmaxtempvalue", 999.0); - MonthlyRecs[month].LowMaxTemp.Ts = ini.GetValue("Temperature" + monthstr, "lowmaxtemptime", cumulus.defaultRecordTS); - - MonthlyRecs[month].HighAppTemp.Val = ini.GetValue("Temperature" + monthstr, "highapptempvalue", -999.0); - MonthlyRecs[month].HighAppTemp.Ts = ini.GetValue("Temperature" + monthstr, "highapptemptime", cumulus.defaultRecordTS); - - MonthlyRecs[month].LowAppTemp.Val = ini.GetValue("Temperature" + monthstr, "lowapptempvalue", 999.0); - MonthlyRecs[month].LowAppTemp.Ts = ini.GetValue("Temperature" + monthstr, "lowapptemptime", cumulus.defaultRecordTS); - - MonthlyRecs[month].HighFeelsLike.Val = ini.GetValue("Temperature" + monthstr, "highfeelslikevalue", -999.0); - MonthlyRecs[month].HighFeelsLike.Ts = ini.GetValue("Temperature" + monthstr, "highfeelsliketime", cumulus.defaultRecordTS); - - MonthlyRecs[month].LowFeelsLike.Val = ini.GetValue("Temperature" + monthstr, "lowfeelslikevalue", 999.0); - MonthlyRecs[month].LowFeelsLike.Ts = ini.GetValue("Temperature" + monthstr, "lowfeelsliketime", cumulus.defaultRecordTS); - - MonthlyRecs[month].HighHumidex.Val = ini.GetValue("Temperature" + monthstr, "highhumidexvalue", -999.0); - MonthlyRecs[month].HighHumidex.Ts = ini.GetValue("Temperature" + monthstr, "highhumidextime", cumulus.defaultRecordTS); - - MonthlyRecs[month].HighHeatIndex.Val = ini.GetValue("Temperature" + monthstr, "highheatindexvalue", -999.0); - MonthlyRecs[month].HighHeatIndex.Ts = ini.GetValue("Temperature" + monthstr, "highheatindextime", cumulus.defaultRecordTS); - - MonthlyRecs[month].HighDewPoint.Val = ini.GetValue("Temperature" + monthstr, "highdewpointvalue", -999.0); - MonthlyRecs[month].HighDewPoint.Ts = ini.GetValue("Temperature" + monthstr, "highdewpointtime", cumulus.defaultRecordTS); - - MonthlyRecs[month].LowDewPoint.Val = ini.GetValue("Temperature" + monthstr, "lowdewpointvalue", 999.0); - MonthlyRecs[month].LowDewPoint.Ts = ini.GetValue("Temperature" + monthstr, "lowdewpointtime", cumulus.defaultRecordTS); - - MonthlyRecs[month].HighDailyTempRange.Val = ini.GetValue("Temperature" + monthstr, "hightemprangevalue", 0.0); - MonthlyRecs[month].HighDailyTempRange.Ts = ini.GetValue("Temperature" + monthstr, "hightemprangetime", cumulus.defaultRecordTS); - - MonthlyRecs[month].LowDailyTempRange.Val = ini.GetValue("Temperature" + monthstr, "lowtemprangevalue", 999.0); - MonthlyRecs[month].LowDailyTempRange.Ts = ini.GetValue("Temperature" + monthstr, "lowtemprangetime", cumulus.defaultRecordTS); - - MonthlyRecs[month].HighWind.Val = ini.GetValue("Wind" + monthstr, "highwindvalue", 0.0); - MonthlyRecs[month].HighWind.Ts = ini.GetValue("Wind" + monthstr, "highwindtime", cumulus.defaultRecordTS); - - MonthlyRecs[month].HighGust.Val = ini.GetValue("Wind" + monthstr, "highgustvalue", 0.0); - MonthlyRecs[month].HighGust.Ts = ini.GetValue("Wind" + monthstr, "highgusttime", cumulus.defaultRecordTS); - - MonthlyRecs[month].HighWindRun.Val = ini.GetValue("Wind" + monthstr, "highdailywindrunvalue", 0.0); - MonthlyRecs[month].HighWindRun.Ts = ini.GetValue("Wind" + monthstr, "highdailywindruntime", cumulus.defaultRecordTS); - - MonthlyRecs[month].HighRainRate.Val = ini.GetValue("Rain" + monthstr, "highrainratevalue", 0.0); - MonthlyRecs[month].HighRainRate.Ts = ini.GetValue("Rain" + monthstr, "highrainratetime", cumulus.defaultRecordTS); - - MonthlyRecs[month].DailyRain.Val = ini.GetValue("Rain" + monthstr, "highdailyrainvalue", 0.0); - MonthlyRecs[month].DailyRain.Ts = ini.GetValue("Rain" + monthstr, "highdailyraintime", cumulus.defaultRecordTS); - - MonthlyRecs[month].HourlyRain.Val = ini.GetValue("Rain" + monthstr, "highhourlyrainvalue", 0.0); - MonthlyRecs[month].HourlyRain.Ts = ini.GetValue("Rain" + monthstr, "highhourlyraintime", cumulus.defaultRecordTS); - - MonthlyRecs[month].MonthlyRain.Val = ini.GetValue("Rain" + monthstr, "highmonthlyrainvalue", 0.0); - MonthlyRecs[month].MonthlyRain.Ts = ini.GetValue("Rain" + monthstr, "highmonthlyraintime", cumulus.defaultRecordTS); - - MonthlyRecs[month].LongestDryPeriod.Val = ini.GetValue("Rain" + monthstr, "longestdryperiodvalue", 0); - MonthlyRecs[month].LongestDryPeriod.Ts = ini.GetValue("Rain" + monthstr, "longestdryperiodtime", cumulus.defaultRecordTS); - - MonthlyRecs[month].LongestWetPeriod.Val = ini.GetValue("Rain" + monthstr, "longestwetperiodvalue", 0); - MonthlyRecs[month].LongestWetPeriod.Ts = ini.GetValue("Rain" + monthstr, "longestwetperiodtime", cumulus.defaultRecordTS); - - MonthlyRecs[month].HighPress.Val = ini.GetValue("Pressure" + monthstr, "highpressurevalue", 0.0); - MonthlyRecs[month].HighPress.Ts = ini.GetValue("Pressure" + monthstr, "highpressuretime", cumulus.defaultRecordTS); - - MonthlyRecs[month].LowPress.Val = ini.GetValue("Pressure" + monthstr, "lowpressurevalue", 9999.0); - MonthlyRecs[month].LowPress.Ts = ini.GetValue("Pressure" + monthstr, "lowpressuretime", cumulus.defaultRecordTS); - - MonthlyRecs[month].HighHumidity.Val = ini.GetValue("Humidity" + monthstr, "highhumidityvalue", 0.0); - MonthlyRecs[month].HighHumidity.Ts = ini.GetValue("Humidity" + monthstr, "highhumiditytime", cumulus.defaultRecordTS); - - MonthlyRecs[month].LowHumidity.Val = ini.GetValue("Humidity" + monthstr, "lowhumidityvalue", 999.0); - MonthlyRecs[month].LowHumidity.Ts = ini.GetValue("Humidity" + monthstr, "lowhumiditytime", cumulus.defaultRecordTS); - } - - cumulus.LogMessage("MonthlyAlltime.ini file read"); - } - - public void WriteMonthlyAlltimeIniFile() - { - try - { - IniFile ini = new IniFile(cumulus.MonthlyAlltimeIniFile); - for (int month = 1; month <= 12; month++) - { - string monthstr = month.ToString("D2"); - - ini.SetValue("Temperature" + monthstr, "hightempvalue", MonthlyRecs[month].HighTemp.Val); - ini.SetValue("Temperature" + monthstr, "hightemptime", MonthlyRecs[month].HighTemp.Ts); - ini.SetValue("Temperature" + monthstr, "lowtempvalue", MonthlyRecs[month].LowTemp.Val); - ini.SetValue("Temperature" + monthstr, "lowtemptime", MonthlyRecs[month].LowTemp.Ts); - ini.SetValue("Temperature" + monthstr, "lowchillvalue", MonthlyRecs[month].LowChill.Val); - ini.SetValue("Temperature" + monthstr, "lowchilltime", MonthlyRecs[month].LowChill.Ts); - ini.SetValue("Temperature" + monthstr, "highmintempvalue", MonthlyRecs[month].HighMinTemp.Val); - ini.SetValue("Temperature" + monthstr, "highmintemptime", MonthlyRecs[month].HighMinTemp.Ts); - ini.SetValue("Temperature" + monthstr, "lowmaxtempvalue", MonthlyRecs[month].LowMaxTemp.Val); - ini.SetValue("Temperature" + monthstr, "lowmaxtemptime", MonthlyRecs[month].LowMaxTemp.Ts); - ini.SetValue("Temperature" + monthstr, "highapptempvalue", MonthlyRecs[month].HighAppTemp.Val); - ini.SetValue("Temperature" + monthstr, "highapptemptime", MonthlyRecs[month].HighAppTemp.Ts); - ini.SetValue("Temperature" + monthstr, "lowapptempvalue", MonthlyRecs[month].LowAppTemp.Val); - ini.SetValue("Temperature" + monthstr, "lowapptemptime", MonthlyRecs[month].LowAppTemp.Ts); - ini.SetValue("Temperature" + monthstr, "highfeelslikevalue", MonthlyRecs[month].HighFeelsLike.Val); - ini.SetValue("Temperature" + monthstr, "highfeelsliketime", MonthlyRecs[month].HighFeelsLike.Ts); - ini.SetValue("Temperature" + monthstr, "lowfeelslikevalue", MonthlyRecs[month].LowFeelsLike.Val); - ini.SetValue("Temperature" + monthstr, "lowfeelsliketime", MonthlyRecs[month].LowFeelsLike.Ts); - ini.SetValue("Temperature" + monthstr, "highhumidexvalue", MonthlyRecs[month].HighHumidex.Val); - ini.SetValue("Temperature" + monthstr, "highhumidextime", MonthlyRecs[month].HighHumidex.Ts); - ini.SetValue("Temperature" + monthstr, "highheatindexvalue", MonthlyRecs[month].HighHeatIndex.Val); - ini.SetValue("Temperature" + monthstr, "highheatindextime", MonthlyRecs[month].HighHeatIndex.Ts); - ini.SetValue("Temperature" + monthstr, "highdewpointvalue", MonthlyRecs[month].HighDewPoint.Val); - ini.SetValue("Temperature" + monthstr, "highdewpointtime", MonthlyRecs[month].HighDewPoint.Ts); - ini.SetValue("Temperature" + monthstr, "lowdewpointvalue", MonthlyRecs[month].LowDewPoint.Val); - ini.SetValue("Temperature" + monthstr, "lowdewpointtime", MonthlyRecs[month].LowDewPoint.Ts); - ini.SetValue("Temperature" + monthstr, "hightemprangevalue", MonthlyRecs[month].HighDailyTempRange.Val); - ini.SetValue("Temperature" + monthstr, "hightemprangetime", MonthlyRecs[month].HighDailyTempRange.Ts); - ini.SetValue("Temperature" + monthstr, "lowtemprangevalue", MonthlyRecs[month].LowDailyTempRange.Val); - ini.SetValue("Temperature" + monthstr, "lowtemprangetime", MonthlyRecs[month].LowDailyTempRange.Ts); - ini.SetValue("Wind" + monthstr, "highwindvalue", MonthlyRecs[month].HighWind.Val); - ini.SetValue("Wind" + monthstr, "highwindtime", MonthlyRecs[month].HighWind.Ts); - ini.SetValue("Wind" + monthstr, "highgustvalue", MonthlyRecs[month].HighGust.Val); - ini.SetValue("Wind" + monthstr, "highgusttime", MonthlyRecs[month].HighGust.Ts); - ini.SetValue("Wind" + monthstr, "highdailywindrunvalue", MonthlyRecs[month].HighWindRun.Val); - ini.SetValue("Wind" + monthstr, "highdailywindruntime", MonthlyRecs[month].HighWindRun.Ts); - ini.SetValue("Rain" + monthstr, "highrainratevalue", MonthlyRecs[month].HighRainRate.Val); - ini.SetValue("Rain" + monthstr, "highrainratetime", MonthlyRecs[month].HighRainRate.Ts); - ini.SetValue("Rain" + monthstr, "highdailyrainvalue", MonthlyRecs[month].DailyRain.Val); - ini.SetValue("Rain" + monthstr, "highdailyraintime", MonthlyRecs[month].DailyRain.Ts); - ini.SetValue("Rain" + monthstr, "highhourlyrainvalue", MonthlyRecs[month].HourlyRain.Val); - ini.SetValue("Rain" + monthstr, "highhourlyraintime", MonthlyRecs[month].HourlyRain.Ts); - ini.SetValue("Rain" + monthstr, "highmonthlyrainvalue", MonthlyRecs[month].MonthlyRain.Val); - ini.SetValue("Rain" + monthstr, "highmonthlyraintime", MonthlyRecs[month].MonthlyRain.Ts); - ini.SetValue("Rain" + monthstr, "longestdryperiodvalue", MonthlyRecs[month].LongestDryPeriod.Val); - ini.SetValue("Rain" + monthstr, "longestdryperiodtime", MonthlyRecs[month].LongestDryPeriod.Ts); - ini.SetValue("Rain" + monthstr, "longestwetperiodvalue", MonthlyRecs[month].LongestWetPeriod.Val); - ini.SetValue("Rain" + monthstr, "longestwetperiodtime", MonthlyRecs[month].LongestWetPeriod.Ts); - ini.SetValue("Pressure" + monthstr, "highpressurevalue", MonthlyRecs[month].HighPress.Val); - ini.SetValue("Pressure" + monthstr, "highpressuretime", MonthlyRecs[month].HighPress.Ts); - ini.SetValue("Pressure" + monthstr, "lowpressurevalue", MonthlyRecs[month].LowPress.Val); - ini.SetValue("Pressure" + monthstr, "lowpressuretime", MonthlyRecs[month].LowPress.Ts); - ini.SetValue("Humidity" + monthstr, "highhumidityvalue", MonthlyRecs[month].HighHumidity.Val); - ini.SetValue("Humidity" + monthstr, "highhumiditytime", MonthlyRecs[month].HighHumidity.Ts); - ini.SetValue("Humidity" + monthstr, "lowhumidityvalue", MonthlyRecs[month].LowHumidity.Val); - ini.SetValue("Humidity" + monthstr, "lowhumiditytime", MonthlyRecs[month].LowHumidity.Ts); - } - ini.Flush(); - } - catch (Exception ex) - { - cumulus.LogMessage("Error writing MonthlyAlltime.ini file: " + ex.Message); - } - } - - public void SetDefaultMonthlyHighsAndLows() - { - // this Month highs and lows - ThisMonth.HighGust.Val = 0; - ThisMonth.HighWind.Val = 0; - ThisMonth.HighTemp.Val = -999; - ThisMonth.LowTemp.Val = 999; - ThisMonth.HighAppTemp.Val = -999; - ThisMonth.LowAppTemp.Val = 999; - ThisMonth.HighFeelsLike.Val = -999; - ThisMonth.LowFeelsLike.Val = 999; - ThisMonth.HighHumidex.Val = -999; - ThisMonth.HighDewPoint.Val = -999; - ThisMonth.LowDewPoint.Val = 999; - ThisMonth.HighPress.Val = 0; - ThisMonth.LowPress.Val = 9999; - ThisMonth.HighRainRate.Val = 0; - ThisMonth.HourlyRain.Val = 0; - ThisMonth.DailyRain.Val = 0; - ThisMonth.HighHumidity.Val = 0; - ThisMonth.LowHumidity.Val = 999; - ThisMonth.HighHeatIndex.Val = -999; - ThisMonth.LowChill.Val = 999; - ThisMonth.HighMinTemp.Val = -999; - ThisMonth.LowMaxTemp.Val = 999; - ThisMonth.HighWindRun.Val = 0; - ThisMonth.LowDailyTempRange.Val = 999; - ThisMonth.HighDailyTempRange.Val = -999; - - // this Month highs and lows - timestamps - ThisMonth.HighGust.Ts = cumulus.defaultRecordTS; - ThisMonth.HighWind.Ts = cumulus.defaultRecordTS; - ThisMonth.HighTemp.Ts = cumulus.defaultRecordTS; - ThisMonth.LowTemp.Ts = cumulus.defaultRecordTS; - ThisMonth.HighAppTemp.Ts = cumulus.defaultRecordTS; - ThisMonth.LowAppTemp.Ts = cumulus.defaultRecordTS; - ThisMonth.HighFeelsLike.Ts = cumulus.defaultRecordTS; - ThisMonth.LowFeelsLike.Ts = cumulus.defaultRecordTS; - ThisMonth.HighHumidex.Ts = cumulus.defaultRecordTS; - ThisMonth.HighDewPoint.Ts = cumulus.defaultRecordTS; - ThisMonth.LowDewPoint.Ts = cumulus.defaultRecordTS; - ThisMonth.HighPress.Ts = cumulus.defaultRecordTS; - ThisMonth.LowPress.Ts = cumulus.defaultRecordTS; - ThisMonth.HighRainRate.Ts = cumulus.defaultRecordTS; - ThisMonth.HourlyRain.Ts = cumulus.defaultRecordTS; - ThisMonth.DailyRain.Ts = cumulus.defaultRecordTS; - ThisMonth.HighHumidity.Ts = cumulus.defaultRecordTS; - ThisMonth.LowHumidity.Ts = cumulus.defaultRecordTS; - ThisMonth.HighHeatIndex.Ts = cumulus.defaultRecordTS; - ThisMonth.LowChill.Ts = cumulus.defaultRecordTS; - ThisMonth.HighMinTemp.Ts = cumulus.defaultRecordTS; - ThisMonth.LowMaxTemp.Ts = cumulus.defaultRecordTS; - ThisMonth.HighWindRun.Ts = cumulus.defaultRecordTS; - ThisMonth.LowDailyTempRange.Ts = cumulus.defaultRecordTS; - ThisMonth.HighDailyTempRange.Ts = cumulus.defaultRecordTS; - } - - public void ReadMonthIniFile() - { - //DateTime timestamp; - - SetDefaultMonthlyHighsAndLows(); - - if (File.Exists(cumulus.MonthIniFile)) - { - //int hourInc = cumulus.GetHourInc(); - - IniFile ini = new IniFile(cumulus.MonthIniFile); - - // Date - //timestamp = ini.GetValue("General", "Date", cumulus.defaultRecordTS); - - ThisMonth.HighWind.Val = ini.GetValue("Wind", "Speed", 0.0); - ThisMonth.HighWind.Ts = ini.GetValue("Wind", "SpTime", cumulus.defaultRecordTS); - ThisMonth.HighGust.Val = ini.GetValue("Wind", "Gust", 0.0); - ThisMonth.HighGust.Ts = ini.GetValue("Wind", "Time", cumulus.defaultRecordTS); - ThisMonth.HighWindRun.Val = ini.GetValue("Wind", "Windrun", 0.0); - ThisMonth.HighWindRun.Ts = ini.GetValue("Wind", "WindrunTime", cumulus.defaultRecordTS); - // Temperature - ThisMonth.LowTemp.Val = ini.GetValue("Temp", "Low", 999.0); - ThisMonth.LowTemp.Ts = ini.GetValue("Temp", "LTime", cumulus.defaultRecordTS); - ThisMonth.HighTemp.Val = ini.GetValue("Temp", "High", -999.0); - ThisMonth.HighTemp.Ts = ini.GetValue("Temp", "HTime", cumulus.defaultRecordTS); - ThisMonth.LowMaxTemp.Val = ini.GetValue("Temp", "LowMax", 999.0); - ThisMonth.LowMaxTemp.Ts = ini.GetValue("Temp", "LMTime", cumulus.defaultRecordTS); - ThisMonth.HighMinTemp.Val = ini.GetValue("Temp", "HighMin", -999.0); - ThisMonth.HighMinTemp.Ts = ini.GetValue("Temp", "HMTime", cumulus.defaultRecordTS); - ThisMonth.LowDailyTempRange.Val = ini.GetValue("Temp", "LowRange", 999.0); - ThisMonth.LowDailyTempRange.Ts = ini.GetValue("Temp", "LowRangeTime", cumulus.defaultRecordTS); - ThisMonth.HighDailyTempRange.Val = ini.GetValue("Temp", "HighRange", -999.0); - ThisMonth.HighDailyTempRange.Ts = ini.GetValue("Temp", "HighRangeTime", cumulus.defaultRecordTS); - // Pressure - ThisMonth.LowPress.Val = ini.GetValue("Pressure", "Low", 9999.0); - ThisMonth.LowPress.Ts = ini.GetValue("Pressure", "LTime", cumulus.defaultRecordTS); - ThisMonth.HighPress.Val = ini.GetValue("Pressure", "High", -9999.0); - ThisMonth.HighPress.Ts = ini.GetValue("Pressure", "HTime", cumulus.defaultRecordTS); - // rain rate - ThisMonth.HighRainRate.Val = ini.GetValue("Rain", "High", 0.0); - ThisMonth.HighRainRate.Ts = ini.GetValue("Rain", "HTime", cumulus.defaultRecordTS); - ThisMonth.HourlyRain.Val = ini.GetValue("Rain", "HourlyHigh", 0.0); - ThisMonth.HourlyRain.Ts = ini.GetValue("Rain", "HHourlyTime", cumulus.defaultRecordTS); - ThisMonth.DailyRain.Val = ini.GetValue("Rain", "DailyHigh", 0.0); - ThisMonth.DailyRain.Ts = ini.GetValue("Rain", "HDailyTime", cumulus.defaultRecordTS); - ThisMonth.LongestDryPeriod.Val = ini.GetValue("Rain", "LongestDryPeriod", 0); - ThisMonth.LongestDryPeriod.Ts = ini.GetValue("Rain", "LongestDryPeriodTime", cumulus.defaultRecordTS); - ThisMonth.LongestWetPeriod.Val = ini.GetValue("Rain", "LongestWetPeriod", 0); - ThisMonth.LongestWetPeriod.Ts = ini.GetValue("Rain", "LongestWetPeriodTime", cumulus.defaultRecordTS); - // humidity - ThisMonth.LowHumidity.Val = ini.GetValue("Humidity", "Low", 999); - ThisMonth.LowHumidity.Ts = ini.GetValue("Humidity", "LTime", cumulus.defaultRecordTS); - ThisMonth.HighHumidity.Val = ini.GetValue("Humidity", "High", -999); - ThisMonth.HighHumidity.Ts = ini.GetValue("Humidity", "HTime", cumulus.defaultRecordTS); - // heat index - ThisMonth.HighHeatIndex.Val = ini.GetValue("HeatIndex", "High", -999.0); - ThisMonth.HighHeatIndex.Ts = ini.GetValue("HeatIndex", "HTime", cumulus.defaultRecordTS); - // App temp - ThisMonth.LowAppTemp.Val = ini.GetValue("AppTemp", "Low", 999.0); - ThisMonth.LowAppTemp.Ts = ini.GetValue("AppTemp", "LTime", cumulus.defaultRecordTS); - ThisMonth.HighAppTemp.Val = ini.GetValue("AppTemp", "High", -999.0); - ThisMonth.HighAppTemp.Ts = ini.GetValue("AppTemp", "HTime", cumulus.defaultRecordTS); - // Dewpoint - ThisMonth.LowDewPoint.Val = ini.GetValue("Dewpoint", "Low", 999.0); - ThisMonth.LowDewPoint.Ts = ini.GetValue("Dewpoint", "LTime", cumulus.defaultRecordTS); - ThisMonth.HighDewPoint.Val = ini.GetValue("Dewpoint", "High", -999.0); - ThisMonth.HighDewPoint.Ts = ini.GetValue("Dewpoint", "HTime", cumulus.defaultRecordTS); - // wind chill - ThisMonth.LowChill.Val = ini.GetValue("WindChill", "Low", 999.0); - ThisMonth.LowChill.Ts = ini.GetValue("WindChill", "LTime", cumulus.defaultRecordTS); - // Feels like temp - ThisMonth.LowFeelsLike.Val = ini.GetValue("FeelsLike", "Low", 999.0); - ThisMonth.LowFeelsLike.Ts = ini.GetValue("FeelsLike", "LTime", cumulus.defaultRecordTS); - ThisMonth.HighFeelsLike.Val = ini.GetValue("FeelsLike", "High", -999.0); - ThisMonth.HighFeelsLike.Ts = ini.GetValue("FeelsLike", "HTime", cumulus.defaultRecordTS); - // Humidex - ThisMonth.HighHumidex.Val = ini.GetValue("Humidex", "High", -999.0); - ThisMonth.HighHumidex.Ts = ini.GetValue("Humidex", "HTime", cumulus.defaultRecordTS); - - cumulus.LogMessage("Month.ini file read"); - } - } - - public void WriteMonthIniFile() - { - cumulus.LogDebugMessage("Writing to Month.ini file"); - lock (monthIniThreadLock) - { - try - { - int hourInc = cumulus.GetHourInc(); - - IniFile ini = new IniFile(cumulus.MonthIniFile); - // Date - ini.SetValue("General", "Date", DateTime.Now.AddHours(hourInc)); - // Wind - ini.SetValue("Wind", "Speed", ThisMonth.HighWind.Val); - ini.SetValue("Wind", "SpTime", ThisMonth.HighWind.Ts); - ini.SetValue("Wind", "Gust", ThisMonth.HighGust.Val); - ini.SetValue("Wind", "Time", ThisMonth.HighGust.Ts); - ini.SetValue("Wind", "Windrun", ThisMonth.HighWindRun.Val); - ini.SetValue("Wind", "WindrunTime", ThisMonth.HighWindRun.Ts); - // Temperature - ini.SetValue("Temp", "Low", ThisMonth.LowTemp.Val); - ini.SetValue("Temp", "LTime", ThisMonth.LowTemp.Ts); - ini.SetValue("Temp", "High", ThisMonth.HighTemp.Val); - ini.SetValue("Temp", "HTime", ThisMonth.HighTemp.Ts); - ini.SetValue("Temp", "LowMax", ThisMonth.LowMaxTemp.Val); - ini.SetValue("Temp", "LMTime", ThisMonth.LowMaxTemp.Ts); - ini.SetValue("Temp", "HighMin", ThisMonth.HighMinTemp.Val); - ini.SetValue("Temp", "HMTime", ThisMonth.HighMinTemp.Ts); - ini.SetValue("Temp", "LowRange", ThisMonth.LowDailyTempRange.Val); - ini.SetValue("Temp", "LowRangeTime", ThisMonth.LowDailyTempRange.Ts); - ini.SetValue("Temp", "HighRange", ThisMonth.HighDailyTempRange.Val); - ini.SetValue("Temp", "HighRangeTime", ThisMonth.HighDailyTempRange.Ts); - // Pressure - ini.SetValue("Pressure", "Low", ThisMonth.LowPress.Val); - ini.SetValue("Pressure", "LTime", ThisMonth.LowPress.Ts); - ini.SetValue("Pressure", "High", ThisMonth.HighPress.Val); - ini.SetValue("Pressure", "HTime", ThisMonth.HighPress.Ts); - // rain - ini.SetValue("Rain", "High", ThisMonth.HighRainRate.Val); - ini.SetValue("Rain", "HTime", ThisMonth.HighRainRate.Ts); - ini.SetValue("Rain", "HourlyHigh", ThisMonth.HourlyRain.Val); - ini.SetValue("Rain", "HHourlyTime", ThisMonth.HourlyRain.Ts); - ini.SetValue("Rain", "DailyHigh", ThisMonth.DailyRain.Val); - ini.SetValue("Rain", "HDailyTime", ThisMonth.DailyRain.Ts); - ini.SetValue("Rain", "LongestDryPeriod", ThisMonth.LongestDryPeriod.Val); - ini.SetValue("Rain", "LongestDryPeriodTime", ThisMonth.LongestDryPeriod.Ts); - ini.SetValue("Rain", "LongestWetPeriod", ThisMonth.LongestWetPeriod.Val); - ini.SetValue("Rain", "LongestWetPeriodTime", ThisMonth.LongestWetPeriod.Ts); - // humidity - ini.SetValue("Humidity", "Low", ThisMonth.LowHumidity.Val); - ini.SetValue("Humidity", "LTime", ThisMonth.LowHumidity.Ts); - ini.SetValue("Humidity", "High", ThisMonth.HighHumidity.Val); - ini.SetValue("Humidity", "HTime", ThisMonth.HighHumidity.Ts); - // heat index - ini.SetValue("HeatIndex", "High", ThisMonth.HighHeatIndex.Val); - ini.SetValue("HeatIndex", "HTime", ThisMonth.HighHeatIndex.Ts); - // App temp - ini.SetValue("AppTemp", "Low", ThisMonth.LowAppTemp.Val); - ini.SetValue("AppTemp", "LTime", ThisMonth.LowAppTemp.Ts); - ini.SetValue("AppTemp", "High", ThisMonth.HighAppTemp.Val); - ini.SetValue("AppTemp", "HTime", ThisMonth.HighAppTemp.Ts); - // Dewpoint - ini.SetValue("Dewpoint", "Low", ThisMonth.LowDewPoint.Val); - ini.SetValue("Dewpoint", "LTime", ThisMonth.LowDewPoint.Ts); - ini.SetValue("Dewpoint", "High", ThisMonth.HighDewPoint.Val); - ini.SetValue("Dewpoint", "HTime", ThisMonth.HighDewPoint.Ts); - // wind chill - ini.SetValue("WindChill", "Low", ThisMonth.LowChill.Val); - ini.SetValue("WindChill", "LTime", ThisMonth.LowChill.Ts); - // feels like - ini.SetValue("FeelsLike", "Low", ThisMonth.LowFeelsLike.Val); - ini.SetValue("FeelsLike", "LTime", ThisMonth.LowFeelsLike.Ts); - ini.SetValue("FeelsLike", "High", ThisMonth.HighFeelsLike.Val); - ini.SetValue("FeelsLike", "HTime", ThisMonth.HighFeelsLike.Ts); - // Humidex - ini.SetValue("Humidex", "High", ThisMonth.HighHumidex.Val); - ini.SetValue("Humidex", "HTime", ThisMonth.HighHumidex.Ts); - - ini.Flush(); - } - catch (Exception ex) - { - cumulus.LogMessage("Error writing month.ini file: " + ex.Message); - } - } - cumulus.LogDebugMessage("End writing to Month.ini file"); - } - - public void ReadYearIniFile() - { - //DateTime timestamp; - - SetDefaultYearlyHighsAndLows(); - - if (File.Exists(cumulus.YearIniFile)) - { - //int hourInc = cumulus.GetHourInc(); - - IniFile ini = new IniFile(cumulus.YearIniFile); - - // Date - //timestamp = ini.GetValue("General", "Date", cumulus.defaultRecordTS); - - ThisYear.HighWind.Val = ini.GetValue("Wind", "Speed", 0.0); - ThisYear.HighWind.Ts = ini.GetValue("Wind", "SpTime", cumulus.defaultRecordTS); - ThisYear.HighGust.Val = ini.GetValue("Wind", "Gust", 0.0); - ThisYear.HighGust.Ts = ini.GetValue("Wind", "Time", cumulus.defaultRecordTS); - ThisYear.HighWindRun.Val = ini.GetValue("Wind", "Windrun", 0.0); - ThisYear.HighWindRun.Ts = ini.GetValue("Wind", "WindrunTime", cumulus.defaultRecordTS); - // Temperature - ThisYear.LowTemp.Val = ini.GetValue("Temp", "Low", 999.0); - ThisYear.LowTemp.Ts = ini.GetValue("Temp", "LTime", cumulus.defaultRecordTS); - ThisYear.HighTemp.Val = ini.GetValue("Temp", "High", -999.0); - ThisYear.HighTemp.Ts = ini.GetValue("Temp", "HTime", cumulus.defaultRecordTS); - ThisYear.LowMaxTemp.Val = ini.GetValue("Temp", "LowMax", 999.0); - ThisYear.LowMaxTemp.Ts = ini.GetValue("Temp", "LMTime", cumulus.defaultRecordTS); - ThisYear.HighMinTemp.Val = ini.GetValue("Temp", "HighMin", -999.0); - ThisYear.HighMinTemp.Ts = ini.GetValue("Temp", "HMTime", cumulus.defaultRecordTS); - ThisYear.LowDailyTempRange.Val = ini.GetValue("Temp", "LowRange", 999.0); - ThisYear.LowDailyTempRange.Ts = ini.GetValue("Temp", "LowRangeTime", cumulus.defaultRecordTS); - ThisYear.HighDailyTempRange.Val = ini.GetValue("Temp", "HighRange", -999.0); - ThisYear.HighDailyTempRange.Ts = ini.GetValue("Temp", "HighRangeTime", cumulus.defaultRecordTS); - // Pressure - ThisYear.LowPress.Val = ini.GetValue("Pressure", "Low", 9999.0); - ThisYear.LowPress.Ts = ini.GetValue("Pressure", "LTime", cumulus.defaultRecordTS); - ThisYear.HighPress.Val = ini.GetValue("Pressure", "High", -9999.0); - ThisYear.HighPress.Ts = ini.GetValue("Pressure", "HTime", cumulus.defaultRecordTS); - // rain rate - ThisYear.HighRainRate.Val = ini.GetValue("Rain", "High", 0.0); - ThisYear.HighRainRate.Ts = ini.GetValue("Rain", "HTime", cumulus.defaultRecordTS); - ThisYear.HourlyRain.Val = ini.GetValue("Rain", "HourlyHigh", 0.0); - ThisYear.HourlyRain.Ts = ini.GetValue("Rain", "HHourlyTime", cumulus.defaultRecordTS); - ThisYear.DailyRain.Val = ini.GetValue("Rain", "DailyHigh", 0.0); - ThisYear.DailyRain.Ts = ini.GetValue("Rain", "HDailyTime", cumulus.defaultRecordTS); - ThisYear.MonthlyRain.Val = ini.GetValue("Rain", "MonthlyHigh", 0.0); - ThisYear.MonthlyRain.Ts = ini.GetValue("Rain", "HMonthlyTime", cumulus.defaultRecordTS); - ThisYear.LongestDryPeriod.Val = ini.GetValue("Rain", "LongestDryPeriod", 0); - ThisYear.LongestDryPeriod.Ts = ini.GetValue("Rain", "LongestDryPeriodTime", cumulus.defaultRecordTS); - ThisYear.LongestWetPeriod.Val = ini.GetValue("Rain", "LongestWetPeriod", 0); - ThisYear.LongestWetPeriod.Ts = ini.GetValue("Rain", "LongestWetPeriodTime", cumulus.defaultRecordTS); - // humidity - ThisYear.LowHumidity.Val = ini.GetValue("Humidity", "Low", 999); - ThisYear.LowHumidity.Ts = ini.GetValue("Humidity", "LTime", cumulus.defaultRecordTS); - ThisYear.HighHumidity.Val = ini.GetValue("Humidity", "High", -999); - ThisYear.HighHumidity.Ts = ini.GetValue("Humidity", "HTime", cumulus.defaultRecordTS); - // heat index - ThisYear.HighHeatIndex.Val = ini.GetValue("HeatIndex", "High", -999.0); - ThisYear.HighHeatIndex.Ts = ini.GetValue("HeatIndex", "HTime", cumulus.defaultRecordTS); - // App temp - ThisYear.LowAppTemp.Val = ini.GetValue("AppTemp", "Low", 999.0); - ThisYear.LowAppTemp.Ts = ini.GetValue("AppTemp", "LTime", cumulus.defaultRecordTS); - ThisYear.HighAppTemp.Val = ini.GetValue("AppTemp", "High", -999.0); - ThisYear.HighAppTemp.Ts = ini.GetValue("AppTemp", "HTime", cumulus.defaultRecordTS); - // Dewpoint - ThisYear.LowDewPoint.Val = ini.GetValue("Dewpoint", "Low", 999.0); - ThisYear.LowDewPoint.Ts = ini.GetValue("Dewpoint", "LTime", cumulus.defaultRecordTS); - ThisYear.HighDewPoint.Val = ini.GetValue("Dewpoint", "High", -999.0); - ThisYear.HighDewPoint.Ts = ini.GetValue("Dewpoint", "HTime", cumulus.defaultRecordTS); - // wind chill - ThisYear.LowChill.Val = ini.GetValue("WindChill", "Low", 999.0); - ThisYear.LowChill.Ts = ini.GetValue("WindChill", "LTime", cumulus.defaultRecordTS); - // Feels like - ThisYear.LowFeelsLike.Val = ini.GetValue("FeelsLike", "Low", 999.0); - ThisYear.LowFeelsLike.Ts = ini.GetValue("FeelsLike", "LTime", cumulus.defaultRecordTS); - ThisYear.HighFeelsLike.Val = ini.GetValue("FeelsLike", "High", -999.0); - ThisYear.HighFeelsLike.Ts = ini.GetValue("FeelsLike", "HTime", cumulus.defaultRecordTS); - // Humidex - ThisYear.HighHumidex.Val = ini.GetValue("Humidex", "High", -999.0); - ThisYear.HighHumidex.Ts = ini.GetValue("Humidex", "HTime", cumulus.defaultRecordTS); - - cumulus.LogMessage("Year.ini file read"); - } - } - - public void WriteYearIniFile() - { - lock (yearIniThreadLock) - { - try - { - int hourInc = cumulus.GetHourInc(); - - IniFile ini = new IniFile(cumulus.YearIniFile); - // Date - ini.SetValue("General", "Date", DateTime.Now.AddHours(hourInc)); - // Wind - ini.SetValue("Wind", "Speed", ThisYear.HighWind.Val); - ini.SetValue("Wind", "SpTime", ThisYear.HighWind.Ts); - ini.SetValue("Wind", "Gust", ThisYear.HighGust.Val); - ini.SetValue("Wind", "Time", ThisYear.HighGust.Ts); - ini.SetValue("Wind", "Windrun", ThisYear.HighWindRun.Val); - ini.SetValue("Wind", "WindrunTime", ThisYear.HighWindRun.Ts); - // Temperature - ini.SetValue("Temp", "Low", ThisYear.LowTemp.Val); - ini.SetValue("Temp", "LTime", ThisYear.LowTemp.Ts); - ini.SetValue("Temp", "High", ThisYear.HighTemp.Val); - ini.SetValue("Temp", "HTime", ThisYear.HighTemp.Ts); - ini.SetValue("Temp", "LowMax", ThisYear.LowMaxTemp.Val); - ini.SetValue("Temp", "LMTime", ThisYear.LowMaxTemp.Ts); - ini.SetValue("Temp", "HighMin", ThisYear.HighMinTemp.Val); - ini.SetValue("Temp", "HMTime", ThisYear.HighMinTemp.Ts); - ini.SetValue("Temp", "LowRange", ThisYear.LowDailyTempRange.Val); - ini.SetValue("Temp", "LowRangeTime", ThisYear.LowDailyTempRange.Ts); - ini.SetValue("Temp", "HighRange", ThisYear.HighDailyTempRange.Val); - ini.SetValue("Temp", "HighRangeTime", ThisYear.HighDailyTempRange.Ts); - // Pressure - ini.SetValue("Pressure", "Low", ThisYear.LowPress.Val); - ini.SetValue("Pressure", "LTime", ThisYear.LowPress.Ts); - ini.SetValue("Pressure", "High", ThisYear.HighPress.Val); - ini.SetValue("Pressure", "HTime", ThisYear.HighPress.Ts); - // rain - ini.SetValue("Rain", "High", ThisYear.HighRainRate.Val); - ini.SetValue("Rain", "HTime", ThisYear.HighRainRate.Ts); - ini.SetValue("Rain", "HourlyHigh", ThisYear.HourlyRain.Val); - ini.SetValue("Rain", "HHourlyTime", ThisYear.HourlyRain.Ts); - ini.SetValue("Rain", "DailyHigh", ThisYear.DailyRain.Val); - ini.SetValue("Rain", "HDailyTime", ThisYear.DailyRain.Ts); - ini.SetValue("Rain", "MonthlyHigh", ThisYear.MonthlyRain.Val); - ini.SetValue("Rain", "HMonthlyTime", ThisYear.MonthlyRain.Ts); - ini.SetValue("Rain", "LongestDryPeriod", ThisYear.LongestDryPeriod.Val); - ini.SetValue("Rain", "LongestDryPeriodTime", ThisYear.LongestDryPeriod.Ts); - ini.SetValue("Rain", "LongestWetPeriod", ThisYear.LongestWetPeriod.Val); - ini.SetValue("Rain", "LongestWetPeriodTime", ThisYear.LongestWetPeriod.Ts); - // humidity - ini.SetValue("Humidity", "Low", ThisYear.LowHumidity.Val); - ini.SetValue("Humidity", "LTime", ThisYear.LowHumidity.Ts); - ini.SetValue("Humidity", "High", ThisYear.HighHumidity.Val); - ini.SetValue("Humidity", "HTime", ThisYear.HighHumidity.Ts); - // heat index - ini.SetValue("HeatIndex", "High", ThisYear.HighHeatIndex.Val); - ini.SetValue("HeatIndex", "HTime", ThisYear.HighHeatIndex.Ts); - // App temp - ini.SetValue("AppTemp", "Low", ThisYear.LowAppTemp.Val); - ini.SetValue("AppTemp", "LTime", ThisYear.LowAppTemp.Ts); - ini.SetValue("AppTemp", "High", ThisYear.HighAppTemp.Val); - ini.SetValue("AppTemp", "HTime", ThisYear.HighAppTemp.Ts); - // Dewpoint - ini.SetValue("Dewpoint", "Low", ThisYear.LowDewPoint.Val); - ini.SetValue("Dewpoint", "LTime", ThisYear.LowDewPoint.Ts); - ini.SetValue("Dewpoint", "High", ThisYear.HighDewPoint.Val); - ini.SetValue("Dewpoint", "HTime", ThisYear.HighDewPoint.Ts); - // wind chill - ini.SetValue("WindChill", "Low", ThisYear.LowChill.Val); - ini.SetValue("WindChill", "LTime", ThisYear.LowChill.Ts); - // Feels like - ini.SetValue("FeelsLike", "Low", ThisYear.LowFeelsLike.Val); - ini.SetValue("FeelsLike", "LTime", ThisYear.LowFeelsLike.Ts); - ini.SetValue("FeelsLike", "High", ThisYear.HighFeelsLike.Val); - ini.SetValue("FeelsLike", "HTime", ThisYear.HighFeelsLike.Ts); - // Humidex - ini.SetValue("Humidex", "High", ThisYear.HighHumidex.Val); - ini.SetValue("Humidex", "HTime", ThisYear.HighHumidex.Ts); - - ini.Flush(); - } - catch (Exception ex) - { - cumulus.LogMessage("Error writing year.ini file: " + ex.Message); - } - } - } - - public void SetDefaultYearlyHighsAndLows() - { - // this Year highs and lows - ThisYear.HighGust.Val = 0; - ThisYear.HighWind.Val = 0; - ThisYear.HighTemp.Val = -999; - ThisYear.LowTemp.Val = 999; - ThisYear.HighAppTemp.Val = -999; - ThisYear.LowAppTemp.Val = 999; - ThisYear.HighFeelsLike.Val = -999; - ThisYear.LowFeelsLike.Val = 999; - ThisYear.HighHumidex.Val = -999; - ThisYear.HighDewPoint.Val = -999; - ThisYear.LowDewPoint.Val = 999; - ThisYear.HighPress.Val = 0; - ThisYear.LowPress.Val = 9999; - ThisYear.HighRainRate.Val = 0; - ThisYear.HourlyRain.Val = 0; - ThisYear.DailyRain.Val = 0; - ThisYear.MonthlyRain.Val = 0; - ThisYear.HighHumidity.Val = 0; - ThisYear.LowHumidity.Val = 999; - ThisYear.HighHeatIndex.Val = -999; - ThisYear.LowChill.Val = 999; - ThisYear.HighMinTemp.Val = -999; - ThisYear.LowMaxTemp.Val = 999; - ThisYear.HighWindRun.Val = 0; - ThisYear.LowDailyTempRange.Val = 999; - ThisYear.HighDailyTempRange.Val = -999; - - // this Year highs and lows - timestamps - ThisYear.HighGust.Ts = cumulus.defaultRecordTS; - ThisYear.HighWind.Ts = cumulus.defaultRecordTS; - ThisYear.HighTemp.Ts = cumulus.defaultRecordTS; - ThisYear.LowTemp.Ts = cumulus.defaultRecordTS; - ThisYear.HighAppTemp.Ts = cumulus.defaultRecordTS; - ThisYear.LowAppTemp.Ts = cumulus.defaultRecordTS; - ThisYear.HighFeelsLike.Ts = cumulus.defaultRecordTS; - ThisYear.LowFeelsLike.Ts = cumulus.defaultRecordTS; - ThisYear.HighHumidex.Ts = cumulus.defaultRecordTS; - ThisYear.HighDewPoint.Ts = cumulus.defaultRecordTS; - ThisYear.LowDewPoint.Ts = cumulus.defaultRecordTS; - ThisYear.HighPress.Ts = cumulus.defaultRecordTS; - ThisYear.LowPress.Ts = cumulus.defaultRecordTS; - ThisYear.HighRainRate.Ts = cumulus.defaultRecordTS; - ThisYear.HourlyRain.Ts = cumulus.defaultRecordTS; - ThisYear.DailyRain.Ts = cumulus.defaultRecordTS; - ThisYear.MonthlyRain.Ts = cumulus.defaultRecordTS; - ThisYear.HighHumidity.Ts = cumulus.defaultRecordTS; - ThisYear.LowHumidity.Ts = cumulus.defaultRecordTS; - ThisYear.HighHeatIndex.Ts = cumulus.defaultRecordTS; - ThisYear.LowChill.Ts = cumulus.defaultRecordTS; - ThisYear.HighMinTemp.Ts = cumulus.defaultRecordTS; - ThisYear.LowMaxTemp.Ts = cumulus.defaultRecordTS; - ThisYear.DailyRain.Ts = cumulus.defaultRecordTS; - ThisYear.LowDailyTempRange.Ts = cumulus.defaultRecordTS; - ThisYear.HighDailyTempRange.Ts = cumulus.defaultRecordTS; - } - - public string GetWCloudURL(out string pwstring, DateTime timestamp) - { - pwstring = cumulus.WCloud.PW; - StringBuilder sb = new StringBuilder($"https://api.weathercloud.net/v01/set?wid={cumulus.WCloud.ID}&key={pwstring}"); - - //Temperature - sb.Append("&tempin=" + (int)Math.Round(ConvertUserTempToC(IndoorTemperature) * 10)); - sb.Append("&temp=" + (int)Math.Round(ConvertUserTempToC(OutdoorTemperature) * 10)); - sb.Append("&chill=" + (int)Math.Round(ConvertUserTempToC(WindChill) * 10)); - sb.Append("&dew=" + (int)Math.Round(ConvertUserTempToC(OutdoorDewpoint) * 10)); - sb.Append("&heat=" + (int)Math.Round(ConvertUserTempToC(HeatIndex) * 10)); - - // Humidity - sb.Append("&humin=" + IndoorHumidity); - sb.Append("&hum=" + OutdoorHumidity); - - // Wind - sb.Append("&wspd=" + (int)Math.Round(ConvertUserWindToMS(WindLatest) * 10)); - sb.Append("&wspdhi=" + (int)Math.Round(ConvertUserWindToMS(RecentMaxGust) * 10)); - sb.Append("&wspdavg=" + (int)Math.Round(ConvertUserWindToMS(WindAverage) * 10)); - - // Wind Direction - sb.Append("&wdir=" + Bearing); - sb.Append("&wdiravg=" + AvgBearing); - - // Pressure - sb.Append("&bar=" + (int)Math.Round(ConvertUserPressToMB(Pressure) * 10)); - - // rain - sb.Append("&rain=" + (int)Math.Round(ConvertUserRainToMM(RainToday) * 10)); - sb.Append("&rainrate=" + (int)Math.Round(ConvertUserRainToMM(RainRate) * 10)); - - // ET - if (cumulus.WCloud.SendSolar && cumulus.Manufacturer == cumulus.DAVIS) - { - sb.Append("&et=" + (int)Math.Round(ConvertUserRainToMM(ET) * 10)); - } - - // solar - if (cumulus.WCloud.SendSolar) - { - sb.Append("&solarrad=" + (int)Math.Round(SolarRad * 10)); - } - - // uv - if (cumulus.WCloud.SendUV) - { - sb.Append("&uvi=" + (int)Math.Round(UV * 10)); - } - - // aq - if (cumulus.WCloud.SendAirQuality) - { - switch (cumulus.StationOptions.PrimaryAqSensor) - { - case (int)Cumulus.PrimaryAqSensor.AirLinkOutdoor: - if (cumulus.airLinkDataOut != null) - { - sb.Append($"&pm25={cumulus.airLinkDataOut.pm2p5:F0}"); - sb.Append($"&pm10={cumulus.airLinkDataOut.pm10:F0}"); - sb.Append($"&aqi={AirQualityIndices.US_EPApm2p5(cumulus.airLinkDataOut.pm2p5_24hr)}"); - } - break; - case (int)Cumulus.PrimaryAqSensor.Ecowitt1: - sb.Append($"&pm25={AirQuality1:F0}"); - sb.Append($"&aqi={AirQualityIndices.US_EPApm2p5(AirQualityAvg1)}"); - break; - case (int)Cumulus.PrimaryAqSensor.Ecowitt2: - sb.Append($"&pm25={AirQuality2:F0}"); - sb.Append($"&aqi={AirQualityIndices.US_EPApm2p5(AirQualityAvg2)}"); - break; - case (int)Cumulus.PrimaryAqSensor.Ecowitt3: - sb.Append($"&pm25={AirQuality3:F0}"); - sb.Append($"&aqi={AirQualityIndices.US_EPApm2p5(AirQualityAvg3)}"); - break; - case (int)Cumulus.PrimaryAqSensor.Ecowitt4: - sb.Append($"&pm25={AirQuality4:F0}"); - sb.Append($"&aqi={AirQualityIndices.US_EPApm2p5(AirQualityAvg4)}"); - break; - case (int)Cumulus.PrimaryAqSensor.EcowittCO2: - sb.Append($"&pm25={CO2_pm2p5:F0}"); - sb.Append($"&pm10={CO2_pm10:F0}"); - sb.Append($"&aqi={AirQualityIndices.US_EPApm2p5(CO2_pm2p5_24h)}"); - break; - } - } - - // soil moisture - if (cumulus.WCloud.SendSoilMoisture) - { - // Weathercloud wants soil moisture in centibar. Davis supplies this, but Ecowitt provide a percentage - int moist = 0; - - switch (cumulus.WCloud.SoilMoistureSensor) - { - case 1: - moist = SoilMoisture1; - break; - case 2: - moist = SoilMoisture2; - break; - case 3: - moist = SoilMoisture3; - break; - case 4: - moist = SoilMoisture4; - break; - case 5: - moist = SoilMoisture5; - break; - case 6: - moist = SoilMoisture6; - break; - case 7: - moist = SoilMoisture7; - break; - case 8: - moist = SoilMoisture8; - break; - case 9: - moist = SoilMoisture9; - break; - case 10: - moist = SoilMoisture10; - break; - case 11: - moist = SoilMoisture11; - break; - case 12: - moist = SoilMoisture12; - break; - case 13: - moist = SoilMoisture13; - break; - case 14: - moist = SoilMoisture14; - break; - case 15: - moist = SoilMoisture15; - break; - case 16: - moist = SoilMoisture16; - break; - } - - if (cumulus.Manufacturer == cumulus.EW) - { - // very! approximate conversion from percentage to cb - moist = (100 - SoilMoisture1) * 2; - } - - sb.Append($"&soilmoist={moist}"); - } - - // leaf wetness - if (cumulus.WCloud.SendLeafWetness) - { - // Weathercloud wants soil moisture in centibar. Davis supplies this, but Ecowitt provide a percentage - int wet = 0; - - switch (cumulus.WCloud.LeafWetnessSensor) - { - case 1: - wet = LeafWetness1; - break; - case 2: - wet = LeafWetness2; - break; - case 3: - wet = LeafWetness3; - break; - case 4: - wet = LeafWetness4; - break; - case 5: - wet = LeafWetness5; - break; - case 6: - wet = LeafWetness6; - break; - case 7: - wet = LeafWetness7; - break; - case 8: - wet = LeafWetness8; - break; - } - - sb.Append($"&leafwet={wet}"); - } - - // time - UTC - sb.Append("&time=" + timestamp.ToUniversalTime().ToString("HHmm")); - - // date - UTC - sb.Append("&date=" + timestamp.ToUniversalTime().ToString("yyyyMMdd")); - - // software identification - //sb.Append("&type=291&ver=" + cumulus.Version); - sb.Append($"&software=Cumulus_MX_v{cumulus.Version}&softwareid=142787ebe716"); - - return sb.ToString(); - } - - public string GetAwekasURLv4(out string pwstring, DateTime timestamp) - { - var InvC = new CultureInfo(""); - string sep = ";"; - - int presstrend; - - // password is passed as a MD5 hash - not very secure, but better than plain text I guess - pwstring = Utils.GetMd5String(cumulus.AWEKAS.PW); - - double threeHourlyPressureChangeMb = 0; - - switch (cumulus.Units.Press) - { - case 0: - case 1: - threeHourlyPressureChangeMb = presstrendval * 3; - break; - case 2: - threeHourlyPressureChangeMb = presstrendval * 3 / 0.0295333727; - break; - } - - if (threeHourlyPressureChangeMb > 6) presstrend = 2; - else if (threeHourlyPressureChangeMb > 3.5) presstrend = 2; - else if (threeHourlyPressureChangeMb > 1.5) presstrend = 1; - else if (threeHourlyPressureChangeMb > 0.1) presstrend = 1; - else if (threeHourlyPressureChangeMb > -0.1) presstrend = 0; - else if (threeHourlyPressureChangeMb > -1.5) presstrend = -1; - else if (threeHourlyPressureChangeMb > -3.5) presstrend = -1; - else if (threeHourlyPressureChangeMb > -6) presstrend = -2; - else - presstrend = -2; - - double AvgTemp; - if (tempsamplestoday > 0) - AvgTemp = TempTotalToday / tempsamplestoday; - else - AvgTemp = 0; - - StringBuilder sb = new StringBuilder("http://data.awekas.at/eingabe_pruefung.php?"); - - var started = false; - - // indoor temp/humidity - if (cumulus.AWEKAS.SendIndoor) - { - sb.Append("indoortemp=" + ConvertUserTempToC(IndoorTemperature).ToString("F1", InvC)); - sb.Append("&indoorhumidity=" + IndoorHumidity); - started = true; - } - - if (cumulus.AWEKAS.SendSoilTemp) - { - if (started) sb.Append("&"); else started = true; - sb.Append("soiltemp1=" + ConvertUserTempToC(SoilTemp1).ToString("F1", InvC)); - sb.Append("&soiltemp2=" + ConvertUserTempToC(SoilTemp2).ToString("F1", InvC)); - sb.Append("&soiltemp3=" + ConvertUserTempToC(SoilTemp3).ToString("F1", InvC)); - sb.Append("&soiltemp4=" + ConvertUserTempToC(SoilTemp4).ToString("F1", InvC)); - } - - if (cumulus.AWEKAS.SendSoilMoisture) - { - if (started) sb.Append("&"); else started = true; - sb.Append("soilmoisture1=" + SoilMoisture1); - sb.Append("&soilmoisture2=" + SoilMoisture2); - sb.Append("&soilmoisture3=" + SoilMoisture3); - sb.Append("&soilmoisture4=" + SoilMoisture4); - } - - if (cumulus.AWEKAS.SendLeafWetness) - { - if (started) sb.Append("&"); else started = true; - sb.Append("leafwetness1=" + LeafWetness1); - sb.Append("&leafwetness2=" + LeafWetness2); - sb.Append("&leafwetness3=" + LeafWetness3); - sb.Append("&leafwetness4=" + LeafWetness4); - } - - if (cumulus.AWEKAS.SendAirQuality) - { - if (started) sb.Append("&"); else started = true; - - switch (cumulus.StationOptions.PrimaryAqSensor) - { - case (int)Cumulus.PrimaryAqSensor.AirLinkOutdoor: - if (cumulus.airLinkDataOut != null) - { - sb.Append($"AqPM1={cumulus.airLinkDataOut.pm1.ToString("F1", InvC)}"); - sb.Append($"&AqPM2.5={cumulus.airLinkDataOut.pm2p5.ToString("F1", InvC)}"); - sb.Append($"&AqPM10={cumulus.airLinkDataOut.pm10.ToString("F1", InvC)}"); - sb.Append($"&AqPM2.5_avg_24h={cumulus.airLinkDataOut.pm2p5_24hr.ToString("F1", InvC)}"); - sb.Append($"&AqPM10_avg_24h={cumulus.airLinkDataOut.pm10_24hr.ToString("F1", InvC)}"); - } - break; - case (int)Cumulus.PrimaryAqSensor.Ecowitt1: - sb.Append($"AqPM2.5={AirQuality1.ToString("F1", InvC)}"); - sb.Append($"&AqPM2.5_avg_24h={AirQualityAvg1.ToString("F1", InvC)}"); - break; - case (int)Cumulus.PrimaryAqSensor.Ecowitt2: - sb.Append($"AqPM2.5={AirQuality2.ToString("F1", InvC)}"); - sb.Append($"&AqPM2.5_avg_24h={AirQualityAvg2.ToString("F1", InvC)}"); - break; - case (int)Cumulus.PrimaryAqSensor.Ecowitt3: - sb.Append($"AqPM2.5={AirQuality3.ToString("F1", InvC)}"); - sb.Append($"&AqPM2.5_avg_24h={AirQualityAvg3.ToString("F1", InvC)}"); - break; - case (int)Cumulus.PrimaryAqSensor.Ecowitt4: - sb.Append($"AqPM2.5={AirQuality4.ToString("F1", InvC)}"); - sb.Append($"&AqPM2.5_avg_24h={AirQualityAvg4.ToString("F1", InvC)}"); - break; - case (int)Cumulus.PrimaryAqSensor.EcowittCO2: - sb.Append($"AqPM2.5={CO2_pm2p5.ToString("F1", InvC)}"); - sb.Append($"&AqPM2.5_avg_24h={CO2_pm2p5_24h.ToString("F1", InvC)}"); - sb.Append($"&AqPM10={CO2_pm10.ToString("F1", InvC)}"); - sb.Append($"&AqPM10_avg_24h={CO2_pm10_24h.ToString("F1", InvC)}"); - break; - } - } - - if (started) sb.Append("&"); - sb.Append("output=json&val="); - - // - // Start of val - // - sb.Append(cumulus.AWEKAS.ID + sep); // 1 - sb.Append(pwstring + sep); // 2 - sb.Append(timestamp.ToString("dd'.'MM'.'yyyy';'HH':'mm") + sep); // 3 + 4 - sb.Append(ConvertUserTempToC(OutdoorTemperature).ToString("F1", InvC) + sep); // 5 - sb.Append(OutdoorHumidity + sep); // 6 - sb.Append(ConvertUserPressToMB(Pressure).ToString("F1", InvC) + sep); // 7 - sb.Append(ConvertUserRainToMM(RainSinceMidnight).ToString("F1", InvC) + sep); // 8 - was RainToday in v2 - sb.Append(ConvertUserWindToKPH(WindAverage).ToString("F1", InvC) + sep); // 9 - sb.Append(AvgBearing + sep); // 10 - sb.Append(sep + sep + sep); // 11/12/13 - condition and warning, snow height - sb.Append(cumulus.AWEKAS.Lang + sep); // 14 - sb.Append(presstrend + sep); // 15 - sb.Append(ConvertUserWindToKPH(RecentMaxGust).ToString("F1", InvC) + sep); // 16 - - if (cumulus.AWEKAS.SendSolar) - sb.Append(SolarRad.ToString("F1", InvC) + sep); // 17 - else - sb.Append(sep); - - if (cumulus.AWEKAS.SendUV) - sb.Append(UV.ToString("F1", InvC) + sep); // 18 - else - sb.Append(sep); - - if (cumulus.AWEKAS.SendSolar) - { - if (cumulus.StationType == StationTypes.FineOffsetSolar) - sb.Append(LightValue.ToString("F0", InvC) + sep); // 19 - else - sb.Append(sep); - - sb.Append(SunshineHours.ToString("F2", InvC) + sep); // 20 - } - else - { - sb.Append(sep + sep); - } - - if (cumulus.AWEKAS.SendSoilTemp) - sb.Append(ConvertUserTempToC(SoilTemp1).ToString("F1", InvC) + sep); // 21 - else - sb.Append(sep); - - sb.Append(ConvertUserRainToMM(RainRate).ToString("F1", InvC) + sep); // 22 - sb.Append("Cum_" + cumulus.Version + sep); // 23 - sb.Append(sep + sep); // 24/25 location for mobile - sb.Append(ConvertUserTempToC(HiLoToday.LowTemp).ToString("F1", InvC) + sep); // 26 - sb.Append(ConvertUserTempToC(AvgTemp).ToString("F1", InvC) + sep); // 27 - sb.Append(ConvertUserTempToC(HiLoToday.HighTemp).ToString("F1", InvC) + sep); // 28 - sb.Append(ConvertUserTempToC(ThisMonth.LowTemp.Val).ToString("F1", InvC) + sep);// 29 - sb.Append(sep); // 30 avg temp this month - sb.Append(ConvertUserTempToC(ThisMonth.HighTemp.Val).ToString("F1", InvC) + sep);// 31 - sb.Append(ConvertUserTempToC(ThisYear.LowTemp.Val).ToString("F1", InvC) + sep); // 32 - sb.Append(sep); // 33 avg temp this year - sb.Append(ConvertUserTempToC(ThisYear.HighTemp.Val).ToString("F1", InvC) + sep);// 34 - sb.Append(HiLoToday.LowHumidity + sep); // 35 - sb.Append(sep); // 36 avg hum today - sb.Append(HiLoToday.HighHumidity + sep); // 37 - sb.Append(ThisMonth.LowHumidity.Val + sep); // 38 - sb.Append(sep); // 39 avg hum this month - sb.Append(ThisMonth.HighHumidity.Val + sep); // 40 - sb.Append(ThisYear.LowHumidity.Val + sep); // 41 - sb.Append(sep); // 42 avg hum this year - sb.Append(ThisYear.HighHumidity.Val + sep); // 43 - sb.Append(ConvertUserPressToMB(HiLoToday.LowPress).ToString("F1", InvC) + sep); // 44 - sb.Append(sep); // 45 avg press today - sb.Append(ConvertUserPressToMB(HiLoToday.HighPress).ToString("F1", InvC) + sep);// 46 - sb.Append(ConvertUserPressToMB(ThisMonth.LowPress.Val).ToString("F1", InvC) + sep); // 47 - sb.Append(sep); // 48 avg press this month - sb.Append(ConvertUserPressToMB(ThisMonth.HighPress.Val).ToString("F1", InvC) + sep); // 49 - sb.Append(ConvertUserPressToMB(ThisYear.LowPress.Val).ToString("F1", InvC) + sep); // 50 - sb.Append(sep); // 51 avg press this year - sb.Append(ConvertUserPressToMB(ThisYear.HighPress.Val).ToString("F1", InvC) + sep); // 52 - sb.Append(sep + sep); // 53/54 min/avg wind today - sb.Append(ConvertUserWindToKPH(HiLoToday.HighWind).ToString("F1", InvC) + sep); // 55 - sb.Append(sep + sep); // 56/57 min/avg wind this month - sb.Append(ConvertUserWindToKPH(ThisMonth.HighWind.Val).ToString("F1", InvC) + sep); // 58 - sb.Append(sep + sep); // 59/60 min/avg wind this year - sb.Append(ConvertUserWindToKPH(ThisYear.HighWind.Val).ToString("F1", InvC) + sep); // 61 - sb.Append(sep + sep); // 62/63 min/avg gust today - sb.Append(ConvertUserWindToKPH(HiLoToday.HighGust).ToString("F1", InvC) + sep); // 64 - sb.Append(sep + sep); // 65/66 min/avg gust this month - sb.Append(ConvertUserWindToKPH(ThisMonth.HighGust.Val).ToString("F1", InvC) + sep); // 67 - sb.Append(sep + sep); // 68/69 min/avg gust this year - sb.Append(ConvertUserWindToKPH(ThisYear.HighGust.Val).ToString("F1", InvC) + sep); // 70 - sb.Append(sep + sep + sep); // 71/72/73 avg wind bearing today/month/year - sb.Append(ConvertUserRainToMM(RainLast24Hour).ToString("F1", InvC) + sep); // 74 - sb.Append(ConvertUserRainToMM(RainMonth).ToString("F1", InvC) + sep); // 75 - sb.Append(ConvertUserRainToMM(RainYear).ToString("F1", InvC) + sep); // 76 - sb.Append(sep); // 77 avg rain rate today - sb.Append(ConvertUserRainToMM(HiLoToday.HighRainRate).ToString("F1", InvC) + sep); // 78 - sb.Append(sep); // 79 avg rain rate this month - sb.Append(ConvertUserRainToMM(ThisMonth.HighRainRate.Val).ToString("F1", InvC) + sep); // 80 - sb.Append(sep); // 81 avg rain rate this year - sb.Append(ConvertUserRainToMM(ThisYear.HighRainRate.Val).ToString("F1", InvC) + sep); // 82 - sb.Append(sep); // 83 avg solar today - if (cumulus.AWEKAS.SendSolar) - sb.Append(HiLoToday.HighSolar.ToString("F1", InvC)); // 84 - else - sb.Append(sep); - - sb.Append(sep + sep); // 85/86 avg/high solar this month - sb.Append(sep + sep); // 87/88 avg/high solar this year - sb.Append(sep); // 89 avg uv today - - if (cumulus.AWEKAS.SendUV) - sb.Append(HiLoToday.HighUv.ToString("F1", InvC)); // 90 - else - sb.Append(sep); - - sb.Append(sep + sep); // 91/92 avg/high uv this month - sb.Append(sep + sep); // 93/94 avg/high uv this year - sb.Append(sep + sep + sep + sep + sep + sep); // 95/96/97/98/99/100 avg/max lux today/month/year - sb.Append(sep + sep); // 101/102 sun hours this month/year - sb.Append(sep + sep + sep + sep + sep + sep + sep + sep + sep); // 103-111 min/avg/max Soil temp today/month/year - // - // End of val fixed structure - // - - return sb.ToString(); - } - - - public string GetWundergroundURL(out string pwstring, DateTime timestamp, bool catchup) - { - // API documentation: https://support.weather.com/s/article/PWS-Upload-Protocol?language=en_US - - var invC = new CultureInfo(""); - - string dateUTC = timestamp.ToUniversalTime().ToString("yyyy'-'MM'-'dd'+'HH'%3A'mm'%3A'ss"); - StringBuilder URL = new StringBuilder(1024); - if (cumulus.Wund.RapidFireEnabled && !catchup) - { - URL.Append("http://rtupdate.wunderground.com/weatherstation/updateweatherstation.php?ID="); - } - else - { - URL.Append("http://weatherstation.wunderground.com/weatherstation/updateweatherstation.php?ID="); - } - - pwstring = $"&PASSWORD={cumulus.Wund.PW}"; - URL.Append(cumulus.Wund.ID); - URL.Append(pwstring); - URL.Append($"&dateutc={dateUTC}"); - StringBuilder Data = new StringBuilder(1024); - if (cumulus.Wund.SendAverage) - { - // send average speed and bearing - Data.Append($"&winddir={AvgBearing}&windspeedmph={WindMPHStr(WindAverage).Replace(',', '.')}"); - } - else - { - // send "instantaneous" speed (i.e. latest) and bearing - Data.Append($"&winddir={Bearing}&windspeedmph={WindMPHStr(WindLatest).Replace(',', '.')}"); - } - Data.Append($"&windgustmph={WindMPHStr(RecentMaxGust).Replace(',', '.')}"); - // may not strictly be a 2 min average! - Data.Append($"&windspdmph_avg2m={WindMPHStr(WindAverage).Replace(',', '.')}"); - Data.Append($"&winddir_avg2m={AvgBearing}"); - Data.Append($"&humidity={OutdoorHumidity}"); - Data.Append($"&tempf={TempFstr(OutdoorTemperature).Replace(',', '.')}"); - Data.Append($"&rainin={RainINstr(RainLastHour).Replace(',', '.')}"); - Data.Append("&dailyrainin="); - if (cumulus.RolloverHour == 0) - { - // use today"s rain - Data.Append(RainINstr(RainToday).Replace(',', '.')); - } - else - { - Data.Append(RainINstr(RainSinceMidnight).Replace(',', '.')); - } - Data.Append($"&baromin={PressINstr(Pressure).Replace(',', '.')}"); - Data.Append($"&dewptf={TempFstr(OutdoorDewpoint).Replace(',', '.')}"); - if (cumulus.Wund.SendUV) - Data.Append($"&UV={UV.ToString(cumulus.UVFormat).Replace(',', '.')}"); - if (cumulus.Wund.SendSolar) - Data.Append($"&solarradiation={SolarRad:F0}"); - if (cumulus.Wund.SendIndoor) - { - Data.Append($"&indoortempf={TempFstr(IndoorTemperature).Replace(',', '.')}"); - Data.Append($"&indoorhumidity={IndoorHumidity}"); - } - // Davis soil and leaf sensors - if (cumulus.Wund.SendSoilTemp1) - Data.Append($"&soiltempf={TempFstr(SoilTemp1).Replace(',', '.')}"); - if (cumulus.Wund.SendSoilTemp2) - Data.Append($"&soiltempf2={TempFstr(SoilTemp2).Replace(',', '.')}"); - if (cumulus.Wund.SendSoilTemp3) - Data.Append($"&soiltempf3={TempFstr(SoilTemp3).Replace(',', '.')}"); - if (cumulus.Wund.SendSoilTemp4) - Data.Append($"&soiltempf4={TempFstr(SoilTemp4).Replace(',', '.')}"); - - if (cumulus.Wund.SendSoilMoisture1) - Data.Append($"&soilmoisture={SoilMoisture1}"); - if (cumulus.Wund.SendSoilMoisture2) - Data.Append($"&soilmoisture2={SoilMoisture2}"); - if (cumulus.Wund.SendSoilMoisture3) - Data.Append($"&soilmoisture3={SoilMoisture3}"); - if (cumulus.Wund.SendSoilMoisture4) - Data.Append($"&soilmoisture4={SoilMoisture4}"); - - if (cumulus.Wund.SendLeafWetness1) - Data.Append($"&leafwetness={LeafWetness1}"); - if (cumulus.Wund.SendLeafWetness2) - Data.Append($"&leafwetness2={LeafWetness2}"); - - if (cumulus.Wund.SendAirQuality && cumulus.StationOptions.PrimaryAqSensor > (int)Cumulus.PrimaryAqSensor.Undefined) - { - switch (cumulus.StationOptions.PrimaryAqSensor) - { - case (int)Cumulus.PrimaryAqSensor.AirLinkOutdoor: - if (cumulus.airLinkDataOut != null) - { - Data.Append($"&AqPM2.5={cumulus.airLinkDataOut.pm2p5:F1}&AqPM10={cumulus.airLinkDataOut.pm10.ToString("F1", invC)}"); - } - break; - case (int)Cumulus.PrimaryAqSensor.Ecowitt1: - Data.Append($"&AqPM2.5={AirQuality1.ToString("F1", invC)}"); - break; - case (int)Cumulus.PrimaryAqSensor.Ecowitt2: - Data.Append($"&AqPM2.5={AirQuality2.ToString("F1", invC)}"); - break; - case (int)Cumulus.PrimaryAqSensor.Ecowitt3: - Data.Append($"&AqPM2.5={AirQuality3.ToString("F1", invC)}"); - break; - case (int)Cumulus.PrimaryAqSensor.Ecowitt4: - Data.Append($"&AqPM2.5={AirQuality4.ToString("F1", invC)}"); - break; - } - } - - Data.Append($"&softwaretype=Cumulus%20v{cumulus.Version}"); - Data.Append("&action=updateraw"); - if (cumulus.Wund.RapidFireEnabled && !catchup) - Data.Append("&realtime=1&rtfreq=5"); - - Data.Replace(",", "."); - URL.Append(Data); - - return URL.ToString(); - } - - public string GetWindyURL(out string apistring, DateTime timestamp) - { - string dateUTC = timestamp.ToUniversalTime().ToString("yyyy'-'MM'-'dd'+'HH':'mm':'ss"); - StringBuilder URL = new StringBuilder("https://stations.windy.com/pws/update/", 1024); - - apistring = cumulus.Windy.ApiKey; - - URL.Append(cumulus.Windy.ApiKey); - URL.Append("?station=" + cumulus.Windy.StationIdx); - URL.Append("&dateutc=" + dateUTC); - StringBuilder Data = new StringBuilder(1024); - Data.Append("&winddir=" + AvgBearing); - Data.Append("&windspeedmph=" + WindMPHStr(WindAverage).Replace(',', '.')); - Data.Append("&windgustmph=" + WindMPHStr(RecentMaxGust).Replace(',', '.')); - Data.Append("&tempf=" + TempFstr(OutdoorTemperature).Replace(',', '.')); - Data.Append("&rainin=" + RainINstr(RainLastHour).Replace(',', '.')); - Data.Append("&baromin=" + PressINstr(Pressure).Replace(',', '.')); - Data.Append("&dewptf=" + TempFstr(OutdoorDewpoint).Replace(',', '.')); - Data.Append("&humidity=" + OutdoorHumidity); - - if (cumulus.Windy.SendUV) - Data.Append("&uv=" + UV.ToString(cumulus.UVFormat).Replace(',', '.')); - if (cumulus.Windy.SendSolar) - Data.Append("&solarradiation=" + SolarRad.ToString("F0")); - - Data.Replace(",", "."); - URL.Append(Data); - - return URL.ToString(); - } - - - // Documentation on the API can be found here... - // https://stations.windguru.cz/upload_api.php - // - public string GetWindGuruURL(out string uidstring, DateTime timestamp) - { - var InvC = new CultureInfo(""); - - string salt = timestamp.ToUnixTime().ToString(); - string hash = Utils.GetMd5String(salt + cumulus.WindGuru.ID + cumulus.WindGuru.PW); - - uidstring = cumulus.WindGuru.ID; - - int numvalues = 0; - double totalwind = 0; - double maxwind = 0; - double minwind = 999; - for (int i = 0; i < MaxWindRecent; i++) - { - if (WindRecent[i].Timestamp >= DateTime.Now.AddMinutes(-cumulus.WindGuru.Interval)) - { - numvalues++; - totalwind += WindRecent[i].Gust; - - if (WindRecent[i].Gust > maxwind) - { - maxwind = WindRecent[i].Gust; - } - - if (WindRecent[i].Gust < minwind) - { - minwind = WindRecent[i].Gust; - } - } - } - // average the values - double avgwind = totalwind / numvalues * cumulus.Calib.WindSpeed.Mult; - - maxwind *= cumulus.Calib.WindGust.Mult; - minwind *= cumulus.Calib.WindGust.Mult; - - - StringBuilder URL = new StringBuilder("http://www.windguru.cz/upload/api.php?", 1024); - - URL.Append("uid=" + HttpUtility.UrlEncode(cumulus.WindGuru.ID)); - URL.Append("&salt=" + salt); - URL.Append("&hash=" + hash); - URL.Append("&interval=" + cumulus.WindGuru.Interval * 60); - URL.Append("&wind_avg=" + ConvertUserWindToKnots(avgwind).ToString("F1", InvC)); - URL.Append("&wind_max=" + ConvertUserWindToKnots(maxwind).ToString("F1", InvC)); - URL.Append("&wind_min=" + ConvertUserWindToKnots(minwind).ToString("F1", InvC)); - URL.Append("&wind_direction=" + AvgBearing); - URL.Append("&temperature=" + ConvertUserTempToC(OutdoorTemperature).ToString("F1", InvC)); - URL.Append("&rh=" + OutdoorHumidity); - URL.Append("&mslp=" + ConvertUserPressureToHPa(Pressure).ToString("F1", InvC)); - if (cumulus.WindGuru.SendRain) - { - URL.Append("&precip=" + ConvertUserRainToMM(RainLastHour).ToString("F1", InvC)); - URL.Append("&precip_interval=3600"); - } - - return URL.ToString(); - } - - public string GetOpenWeatherMapData(DateTime timestamp) - { - StringBuilder sb = new StringBuilder($"[{{\"station_id\":\"{cumulus.OpenWeatherMap.ID}\","); - var invC = new CultureInfo(""); - - sb.Append($"\"dt\":{Utils.ToUnixTime(timestamp)},"); - sb.Append($"\"temperature\":{Math.Round(ConvertUserTempToC(OutdoorTemperature), 1).ToString(invC)},"); - sb.Append($"\"wind_deg\":{AvgBearing},"); - sb.Append($"\"wind_speed\":{Math.Round(ConvertUserWindToMS(WindAverage), 1).ToString(invC)},"); - sb.Append($"\"wind_gust\":{Math.Round(ConvertUserWindToMS(RecentMaxGust), 1).ToString(invC)},"); - sb.Append($"\"pressure\":{Math.Round(ConvertUserPressureToHPa(Pressure), 1).ToString(invC)},"); - sb.Append($"\"humidity\":{OutdoorHumidity},"); - sb.Append($"\"rain_1h\":{Math.Round(ConvertUserRainToMM(RainLastHour), 1).ToString(invC)},"); - sb.Append($"\"rain_24h\":{Math.Round(ConvertUserRainToMM(RainLast24Hour), 1).ToString(invC)}"); - sb.Append("}]"); - - return sb.ToString(); - } - - private string PressINstr(double pressure) - { - var pressIN = ConvertUserPressToIN(pressure); - - return pressIN.ToString("F3"); - } - - private string WindMPHStr(double wind) - { - var windMPH = ConvertUserWindToMPH(wind); - if (cumulus.StationOptions.RoundWindSpeed) - windMPH = Math.Round(windMPH); - - return windMPH.ToString("F1"); - } - - /// - /// Convert rain in user units to inches for WU etc - /// - /// - /// - private string RainINstr(double rain) - { - var rainIN = ConvertUserRainToIn(rain); - - return rainIN.ToString("F2"); - } - - /// - /// Convert temp in user units to F for WU etc - /// - /// - /// - private string TempFstr(double temp) - { - double tempf = ConvertUserTempToF(temp); - - return tempf.ToString("F1"); - } - - public string GetPWSURL(out string pwstring, DateTime timestamp) - { - string dateUTC = timestamp.ToUniversalTime().ToString("yyyy'-'MM'-'dd'+'HH'%3A'mm'%3A'ss"); - StringBuilder URL = new StringBuilder("http://www.pwsweather.com/pwsupdate/pwsupdate.php?ID=", 1024); - - pwstring = "&PASSWORD=" + cumulus.PWS.PW; - URL.Append(cumulus.PWS.ID + pwstring); - URL.Append("&dateutc=" + dateUTC); - - StringBuilder Data = new StringBuilder(1024); - - // send average speed and bearing - Data.Append("&winddir=" + AvgBearing); - Data.Append("&windspeedmph=" + WindMPHStr(WindAverage).Replace(',', '.')); - Data.Append("&windgustmph=" + WindMPHStr(RecentMaxGust).Replace(',', '.')); - Data.Append("&humidity=" + OutdoorHumidity); - Data.Append("&tempf=" + TempFstr(OutdoorTemperature).Replace(',', '.')); - Data.Append("&rainin=" + RainINstr(RainLastHour).Replace(',', '.')); - Data.Append("&dailyrainin="); - if (cumulus.RolloverHour == 0) - { - // use today"s rain - Data.Append(RainINstr(RainToday).Replace(',', '.')); - } - else - { - Data.Append(RainINstr(RainSinceMidnight).Replace(',', '.')); - } - Data.Append("&baromin=" + PressINstr(Pressure).Replace(',', '.')); - Data.Append("&dewptf=" + TempFstr(OutdoorDewpoint).Replace(',', '.')); - if (cumulus.PWS.SendUV) - { - Data.Append("&UV=" + UV.ToString(cumulus.UVFormat).Replace(',', '.')); - } - - if (cumulus.PWS.SendSolar) - { - Data.Append("&solarradiation=" + SolarRad.ToString("F0")); - } - - Data.Append("&softwaretype=Cumulus%20v" + cumulus.Version); - Data.Append("&action=updateraw"); - - Data.Replace(",", "."); - URL.Append(Data); - - return URL.ToString(); - } - - public string GetWOWURL(out string pwstring, DateTime timestamp) - { - string dateUTC = timestamp.ToUniversalTime().ToString("yyyy'-'MM'-'dd'+'HH'%3A'mm'%3A'ss"); - StringBuilder URL = new StringBuilder("http://wow.metoffice.gov.uk/automaticreading?siteid=", 1024); - - pwstring = "&siteAuthenticationKey=" + cumulus.WOW.PW; - URL.Append(cumulus.WOW.ID); - URL.Append(pwstring); - URL.Append("&dateutc=" + dateUTC); - - StringBuilder Data = new StringBuilder(1024); - - // send average speed and bearing - Data.Append("&winddir=" + AvgBearing); - Data.Append("&windspeedmph=" + WindMPHStr(WindAverage).Replace(',', '.')); - Data.Append("&windgustmph=" + WindMPHStr(RecentMaxGust).Replace(',', '.')); - Data.Append("&humidity=" + OutdoorHumidity); - Data.Append("&tempf=" + TempFstr(OutdoorTemperature).Replace(',', '.')); - Data.Append("&rainin=" + RainINstr(RainLastHour).Replace(',', '.')); - Data.Append("&dailyrainin="); - if (cumulus.RolloverHour == 0) - { - // use today"s rain - Data.Append(RainINstr(RainToday).Replace(',', '.')); - } - else - { - Data.Append(RainINstr(RainSinceMidnight).Replace(',', '.')); - } - Data.Append("&baromin=" + PressINstr(Pressure).Replace(',', '.')); - Data.Append("&dewptf=" + TempFstr(OutdoorDewpoint).Replace(',', '.')); - if (cumulus.WOW.SendUV) - { - Data.Append("&UV=" + UV.ToString(cumulus.UVFormat).Replace(',', '.')); - } - if (cumulus.WOW.SendSolar) - { - Data.Append("&solarradiation=" + SolarRad.ToString("F0")); - } - - Data.Append("&softwaretype=Cumulus%20v" + cumulus.Version); - Data.Append("&action=updateraw"); - - Data.Replace(",", "."); - URL.Append(Data); - - return URL.ToString(); - } - - public double ConvertUserRainToIN(double rain) - { - if (cumulus.Units.Rain == 0) - { - return rain * 0.0393700787; - } - else - { - return rain; - } - } - - private string alltimejsonformat(AllTimeRec item, string unit, string valueformat, string dateformat) - { - return $"[\"{item.Desc}\",\"{item.Val.ToString(valueformat)} {unit}\",\"{item.Ts.ToString(dateformat)}\"]"; - } - - public string GetTempRecords() - { - var json = new StringBuilder("{\"data\":[", 2048); - - json.Append(alltimejsonformat(AllTime.HighTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); - json.Append(alltimejsonformat(AllTime.LowTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); - json.Append(alltimejsonformat(AllTime.HighDewPoint, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); - json.Append(alltimejsonformat(AllTime.LowDewPoint, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); - json.Append(alltimejsonformat(AllTime.HighAppTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); - json.Append(alltimejsonformat(AllTime.LowAppTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); - json.Append(alltimejsonformat(AllTime.HighFeelsLike, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); - json.Append(alltimejsonformat(AllTime.LowFeelsLike, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); - json.Append(alltimejsonformat(AllTime.HighHumidex, " ", cumulus.TempFormat, "f") + ","); - json.Append(alltimejsonformat(AllTime.LowChill, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); - json.Append(alltimejsonformat(AllTime.HighHeatIndex, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); - json.Append(alltimejsonformat(AllTime.HighMinTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); - json.Append(alltimejsonformat(AllTime.LowMaxTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); - json.Append(alltimejsonformat(AllTime.HighDailyTempRange, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "D") + ","); - json.Append(alltimejsonformat(AllTime.LowDailyTempRange, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "D")); - json.Append("]}"); - return json.ToString(); - } - - public string GetHumRecords() - { - var json = new StringBuilder("{\"data\":[", 512); - - json.Append(alltimejsonformat(AllTime.HighHumidity, "%", cumulus.HumFormat, "f")); - json.Append(","); - json.Append(alltimejsonformat(AllTime.LowHumidity, "%", cumulus.HumFormat, "f")); - json.Append("]}"); - return json.ToString(); - } - - public string GetPressRecords() - { - var json = new StringBuilder("{\"data\":[", 512); - - json.Append(alltimejsonformat(AllTime.HighPress, cumulus.Units.PressText, cumulus.PressFormat, "f")); - json.Append(","); - json.Append(alltimejsonformat(AllTime.LowPress, cumulus.Units.PressText, cumulus.PressFormat, "f")); - json.Append("]}"); - return json.ToString(); - } - - public string GetWindRecords() - { - var json = new StringBuilder("{\"data\":[", 512); - - json.Append(alltimejsonformat(AllTime.HighGust, cumulus.Units.WindText, cumulus.WindFormat, "f")); - json.Append(","); - json.Append(alltimejsonformat(AllTime.HighWind, cumulus.Units.WindText, cumulus.WindAvgFormat, "f")); - json.Append(","); - json.Append(alltimejsonformat(AllTime.HighWindRun, cumulus.Units.WindRunText, cumulus.WindRunFormat, "D")); - json.Append("]}"); - return json.ToString(); - } - - public string GetRainRecords() - { - var json = new StringBuilder("{\"data\":[", 512); - - json.Append(alltimejsonformat(AllTime.HighRainRate, cumulus.Units.RainText + "/hr", cumulus.RainFormat, "f")); - json.Append(","); - json.Append(alltimejsonformat(AllTime.HourlyRain, cumulus.Units.RainText, cumulus.RainFormat, "f")); - json.Append(","); - json.Append(alltimejsonformat(AllTime.DailyRain, cumulus.Units.RainText, cumulus.RainFormat, "D")); - json.Append(","); - json.Append(alltimejsonformat(AllTime.MonthlyRain, cumulus.Units.RainText, cumulus.RainFormat, "Y")); - json.Append(","); - json.Append(alltimejsonformat(AllTime.LongestDryPeriod, "days", "f0", "D")); - json.Append(","); - json.Append(alltimejsonformat(AllTime.LongestWetPeriod, "days", "f0", "D")); - json.Append("]}"); - return json.ToString(); - } - - private string monthlyjsonformat(AllTimeRec item, string unit, string valueformat, string dateformat) - { - return $"[\"{item.Desc}\",\"{item.Val.ToString(valueformat)} {unit}\",\"{item.Ts.ToString(dateformat)}\"]"; - } - - public string GetMonthlyTempRecords(int month) - { - var json = new StringBuilder("{\"data\":[", 1024); - - json.Append(monthlyjsonformat(MonthlyRecs[month].HighTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthlyjsonformat(MonthlyRecs[month].LowTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthlyjsonformat(MonthlyRecs[month].HighDewPoint, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthlyjsonformat(MonthlyRecs[month].LowDewPoint, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthlyjsonformat(MonthlyRecs[month].HighAppTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthlyjsonformat(MonthlyRecs[month].LowAppTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthlyjsonformat(MonthlyRecs[month].HighFeelsLike, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthlyjsonformat(MonthlyRecs[month].LowFeelsLike, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthlyjsonformat(MonthlyRecs[month].HighHumidex, " ", cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthlyjsonformat(MonthlyRecs[month].LowChill, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthlyjsonformat(MonthlyRecs[month].HighHeatIndex, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthlyjsonformat(MonthlyRecs[month].HighMinTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthlyjsonformat(MonthlyRecs[month].LowMaxTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthlyjsonformat(MonthlyRecs[month].HighDailyTempRange, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "D")); - json.Append(","); - json.Append(monthlyjsonformat(MonthlyRecs[month].LowDailyTempRange, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "D")); - json.Append("]}"); - return json.ToString(); - } - - public string GetMonthlyHumRecords(int month) - { - var json = new StringBuilder("{\"data\":[", 256); - - json.Append(monthlyjsonformat(MonthlyRecs[month].HighHumidity, "%", cumulus.HumFormat, "f")); - json.Append(","); - json.Append(monthlyjsonformat(MonthlyRecs[month].LowHumidity, "%", cumulus.HumFormat, "f")); - json.Append("]}"); - return json.ToString(); - } - - public string GetMonthlyPressRecords(int month) - { - var json = new StringBuilder("{\"data\":[", 256); - - json.Append(monthlyjsonformat(MonthlyRecs[month].HighPress, cumulus.Units.PressText, cumulus.PressFormat, "f")); - json.Append(","); - json.Append(monthlyjsonformat(MonthlyRecs[month].LowPress, cumulus.Units.PressText, cumulus.PressFormat, "f")); - json.Append("]}"); - return json.ToString(); - } - - public string GetMonthlyWindRecords(int month) - { - var json = new StringBuilder("{\"data\":[", 256); - - json.Append(monthlyjsonformat(MonthlyRecs[month].HighGust, cumulus.Units.WindText, cumulus.WindFormat, "f")); - json.Append(","); - json.Append(monthlyjsonformat(MonthlyRecs[month].HighWind, cumulus.Units.WindText, cumulus.WindAvgFormat, "f")); - json.Append(","); - json.Append(monthlyjsonformat(MonthlyRecs[month].HighWindRun, cumulus.Units.WindRunText, cumulus.WindRunFormat, "D")); - json.Append("]}"); - return json.ToString(); - } - - public string GetMonthlyRainRecords(int month) - { - var json = new StringBuilder("{\"data\":[", 512); - - json.Append(monthlyjsonformat(MonthlyRecs[month].HighRainRate, cumulus.Units.RainText + "/hr", cumulus.RainFormat, "f")); - json.Append(","); - json.Append(monthlyjsonformat(MonthlyRecs[month].HourlyRain, cumulus.Units.RainText, cumulus.RainFormat, "f")); - json.Append(","); - json.Append(monthlyjsonformat(MonthlyRecs[month].DailyRain, cumulus.Units.RainText, cumulus.RainFormat, "D")); - json.Append(","); - json.Append(monthlyjsonformat(MonthlyRecs[month].MonthlyRain, cumulus.Units.RainText, cumulus.RainFormat, "Y")); - json.Append(","); - json.Append(monthlyjsonformat(MonthlyRecs[month].LongestDryPeriod, "days", "f0", "D")); - json.Append(","); - json.Append(monthlyjsonformat(MonthlyRecs[month].LongestWetPeriod, "days", "f0", "D")); - json.Append("]}"); - return json.ToString(); - } - - private string monthyearjsonformat(string description, double value, DateTime timestamp, string unit, string valueformat, string dateformat) - { - return $"[\"{description}\",\"{value.ToString(valueformat)} {unit}\",\"{timestamp.ToString(dateformat)}\"]"; - } - - public string GetThisMonthTempRecords() - { - var json = new StringBuilder("{\"data\":[", 1024); - - json.Append(monthyearjsonformat(ThisMonth.HighTemp.Desc, ThisMonth.HighTemp.Val, ThisMonth.HighTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisMonth.LowTemp.Desc, ThisMonth.LowTemp.Val, ThisMonth.LowTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisMonth.HighDewPoint.Desc, ThisMonth.HighDewPoint.Val, ThisMonth.HighDewPoint.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisMonth.LowDewPoint.Desc, ThisMonth.LowDewPoint.Val, ThisMonth.LowDewPoint.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisMonth.HighAppTemp.Desc, ThisMonth.HighAppTemp.Val, ThisMonth.HighAppTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisMonth.LowAppTemp.Desc, ThisMonth.LowAppTemp.Val, ThisMonth.LowAppTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisMonth.HighFeelsLike.Desc, ThisMonth.HighFeelsLike.Val, ThisMonth.HighFeelsLike.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisMonth.LowFeelsLike.Desc, ThisMonth.LowFeelsLike.Val, ThisMonth.LowFeelsLike.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisMonth.HighHumidex.Desc, ThisMonth.HighHumidex.Val, ThisMonth.HighHumidex.Ts, " ", cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisMonth.LowChill.Desc, ThisMonth.LowChill.Val, ThisMonth.LowChill.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisMonth.HighHeatIndex.Desc, ThisMonth.HighHeatIndex.Val, ThisMonth.HighHeatIndex.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisMonth.HighMinTemp.Desc, ThisMonth.HighMinTemp.Val, ThisMonth.HighMinTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisMonth.LowMaxTemp.Desc, ThisMonth.LowMaxTemp.Val, ThisMonth.LowMaxTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisMonth.HighDailyTempRange.Desc, ThisMonth.HighDailyTempRange.Val, ThisMonth.HighDailyTempRange.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "D")); - json.Append(","); - json.Append(monthyearjsonformat(ThisMonth.LowDailyTempRange.Desc, ThisMonth.LowDailyTempRange.Val, ThisMonth.LowDailyTempRange.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "D")); - json.Append("]}"); - return json.ToString(); - } - - public string GetThisMonthHumRecords() - { - var json = new StringBuilder("{\"data\":[", 256); - - json.Append(monthyearjsonformat(ThisMonth.HighHumidity.Desc, ThisMonth.HighHumidity.Val, ThisMonth.HighHumidity.Ts, "%", cumulus.HumFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisMonth.LowHumidity.Desc, ThisMonth.LowHumidity.Val, ThisMonth.LowHumidity.Ts, "%", cumulus.HumFormat, "f")); - json.Append("]}"); - return json.ToString(); - } - - public string GetThisMonthPressRecords() - { - var json = new StringBuilder("{\"data\":[", 256); - - json.Append(monthyearjsonformat(ThisMonth.HighPress.Desc, ThisMonth.HighPress.Val, ThisMonth.HighPress.Ts, cumulus.Units.PressText, cumulus.PressFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisMonth.LowPress.Desc, ThisMonth.LowPress.Val, ThisMonth.LowPress.Ts, cumulus.Units.PressText, cumulus.PressFormat, "f")); - json.Append("]}"); - return json.ToString(); - } - - public string GetThisMonthWindRecords() - { - var json = new StringBuilder("{\"data\":[", 256); - - json.Append(monthyearjsonformat(ThisMonth.HighGust.Desc, ThisMonth.HighGust.Val, ThisMonth.HighGust.Ts, cumulus.Units.WindText, cumulus.WindFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisMonth.HighWind.Desc, ThisMonth.HighWind.Val, ThisMonth.HighWind.Ts, cumulus.Units.WindText, cumulus.WindAvgFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisMonth.HighWindRun.Desc, ThisMonth.HighWindRun.Val, ThisMonth.HighWindRun.Ts, cumulus.Units.WindRunText, cumulus.WindRunFormat, "D")); - json.Append("]}"); - return json.ToString(); - } - - public string GetThisMonthRainRecords() - { - var json = new StringBuilder("{\"data\":[", 512); - - json.Append(monthyearjsonformat(ThisMonth.HighRainRate.Desc, ThisMonth.HighRainRate.Val, ThisMonth.HighRainRate.Ts, cumulus.Units.RainText + "/hr", cumulus.RainFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisMonth.HourlyRain.Desc, ThisMonth.HourlyRain.Val, ThisMonth.HourlyRain.Ts, cumulus.Units.RainText, cumulus.RainFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisMonth.DailyRain.Desc, ThisMonth.DailyRain.Val, ThisMonth.DailyRain.Ts, cumulus.Units.RainText, cumulus.RainFormat, "D")); - json.Append(","); - //json.Append(monthyearjsonformat(ThisMonth.WetMonth.Desc, month, cumulus.Units.RainText, cumulus.RainFormat, "Y")); - //json.Append(","); - json.Append(monthyearjsonformat(ThisMonth.LongestDryPeriod.Desc, ThisMonth.LongestDryPeriod.Val, ThisMonth.LongestDryPeriod.Ts, "days", "f0", "D")); - json.Append(","); - json.Append(monthyearjsonformat(ThisMonth.LongestWetPeriod.Desc, ThisMonth.LongestWetPeriod.Val, ThisMonth.LongestWetPeriod.Ts, "days", "f0", "D")); - json.Append("]}"); - return json.ToString(); - } - - public string GetThisYearTempRecords() - { - var json = new StringBuilder("{\"data\":[", 1024); - - json.Append(monthyearjsonformat(ThisYear.HighTemp.Desc, ThisYear.HighTemp.Val, ThisYear.HighTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisYear.LowTemp.Desc, ThisYear.LowTemp.Val, ThisYear.LowTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisYear.HighDewPoint.Desc, ThisYear.HighDewPoint.Val, ThisYear.HighDewPoint.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisYear.LowDewPoint.Desc, ThisYear.LowDewPoint.Val, ThisYear.LowDewPoint.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisYear.HighAppTemp.Desc, ThisYear.HighAppTemp.Val, ThisYear.HighAppTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisYear.LowAppTemp.Desc, ThisYear.LowAppTemp.Val, ThisYear.LowAppTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisYear.HighFeelsLike.Desc, ThisYear.HighFeelsLike.Val, ThisYear.HighFeelsLike.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisYear.LowFeelsLike.Desc, ThisYear.LowFeelsLike.Val, ThisYear.LowFeelsLike.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisYear.HighHumidex.Desc, ThisYear.HighHumidex.Val, ThisYear.HighHumidex.Ts, " ", cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisYear.LowChill.Desc, ThisYear.LowChill.Val, ThisYear.LowChill.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisYear.HighHeatIndex.Desc, ThisYear.HighHeatIndex.Val, ThisYear.HighHeatIndex.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisYear.HighMinTemp.Desc, ThisYear.HighMinTemp.Val, ThisYear.HighMinTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisYear.LowMaxTemp.Desc, ThisYear.LowMaxTemp.Val, ThisYear.LowMaxTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisYear.HighDailyTempRange.Desc, ThisYear.HighDailyTempRange.Val, ThisYear.HighDailyTempRange.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "D")); - json.Append(","); - json.Append(monthyearjsonformat(ThisYear.LowDailyTempRange.Desc, ThisYear.LowDailyTempRange.Val, ThisYear.LowDailyTempRange.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "D")); - json.Append("]}"); - return json.ToString(); - } - - public string GetThisYearHumRecords() - { - var json = new StringBuilder("{\"data\":[", 256); - - json.Append(monthyearjsonformat(ThisYear.HighHumidity.Desc, ThisYear.HighHumidity.Val, ThisYear.HighHumidity.Ts, "%", cumulus.HumFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisYear.LowHumidity.Desc, ThisYear.LowHumidity.Val, ThisYear.LowHumidity.Ts, "%", cumulus.HumFormat, "f")); - json.Append("]}"); - return json.ToString(); - } - - public string GetThisYearPressRecords() - { - var json = new StringBuilder("{\"data\":[", 256); - - json.Append(monthyearjsonformat(ThisYear.HighPress.Desc, ThisYear.HighPress.Val, ThisYear.HighPress.Ts, cumulus.Units.PressText, cumulus.PressFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisYear.LowPress.Desc, ThisYear.LowPress.Val, ThisYear.LowPress.Ts, cumulus.Units.PressText, cumulus.PressFormat, "f")); - json.Append("]}"); - return json.ToString(); - } - - public string GetThisYearWindRecords() - { - var json = new StringBuilder("{\"data\":[", 256); - - json.Append(monthyearjsonformat(ThisYear.HighGust.Desc, ThisYear.HighGust.Val, ThisYear.HighGust.Ts, cumulus.Units.WindText, cumulus.WindFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisYear.HighWind.Desc, ThisYear.HighWind.Val, ThisYear.HighWind.Ts, cumulus.Units.WindText, cumulus.WindAvgFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisYear.HighWindRun.Desc, ThisYear.HighWindRun.Val, ThisYear.HighWindRun.Ts, cumulus.Units.WindRunText, cumulus.WindRunFormat, "D")); - json.Append("]}"); - return json.ToString(); - } - - public string GetThisYearRainRecords() - { - var json = new StringBuilder("{\"data\":[", 512); - - json.Append(monthyearjsonformat(ThisYear.HighRainRate.Desc, ThisYear.HighRainRate.Val, ThisYear.HighRainRate.Ts, cumulus.Units.RainText + "/hr", cumulus.RainFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisYear.HourlyRain.Desc, ThisYear.HourlyRain.Val, ThisYear.HourlyRain.Ts, cumulus.Units.RainText, cumulus.RainFormat, "f")); - json.Append(","); - json.Append(monthyearjsonformat(ThisYear.DailyRain.Desc, ThisYear.DailyRain.Val, ThisYear.DailyRain.Ts, cumulus.Units.RainText, cumulus.RainFormat, "D")); - json.Append(","); - json.Append(monthyearjsonformat(ThisYear.MonthlyRain.Desc, ThisYear.MonthlyRain.Val, ThisYear.MonthlyRain.Ts, cumulus.Units.RainText, cumulus.RainFormat, "Y")); - json.Append(","); - json.Append(monthyearjsonformat(ThisYear.LongestDryPeriod.Desc, ThisYear.LongestDryPeriod.Val, ThisYear.LongestDryPeriod.Ts, "days", "f0", "D")); - json.Append(","); - json.Append(monthyearjsonformat(ThisYear.LongestWetPeriod.Desc, ThisYear.LongestWetPeriod.Val, ThisYear.LongestWetPeriod.Ts, "days", "f0", "D")); - json.Append("]}"); - return json.ToString(); - } - - public string GetExtraTemp() - { - var json = new StringBuilder("{\"data\":[", 1024); - - for (int sensor = 1; sensor < 11; sensor++) - { - json.Append("[\""); - json.Append(cumulus.ExtraTempCaptions[sensor]); - json.Append("\",\""); - json.Append(ExtraTemp[sensor].ToString(cumulus.TempFormat)); - json.Append("\",\"°"); - json.Append(cumulus.Units.TempText[1].ToString()); - json.Append("\"]"); - - if (sensor < 10) - { - json.Append(","); - } - } - - json.Append("]}"); - return json.ToString(); - } - - public string GetUserTemp() - { - var json = new StringBuilder("{\"data\":[", 1024); - - for (int sensor = 1; sensor < 9; sensor++) - { - json.Append("[\""); - json.Append(cumulus.UserTempCaptions[sensor]); - json.Append("\",\""); - json.Append(UserTemp[sensor].ToString(cumulus.TempFormat)); - json.Append("\",\"°"); - json.Append(cumulus.Units.TempText[1].ToString()); - json.Append("\"]"); - - if (sensor < 8) - { - json.Append(","); - } - } - - json.Append("]}"); - return json.ToString(); - } - - public string GetExtraHum() - { - var json = new StringBuilder("{\"data\":[", 1024); - - for (int sensor = 1; sensor < 11; sensor++) - { - json.Append("[\""); - json.Append(cumulus.ExtraHumCaptions[sensor]); - json.Append("\",\""); - json.Append(ExtraHum[sensor].ToString(cumulus.HumFormat)); - json.Append("\",\"%\"]"); - - if (sensor < 10) - { - json.Append(","); - } - } - - json.Append("]}"); - return json.ToString(); - } - - public string GetExtraDew() - { - var json = new StringBuilder("{\"data\":[", 1024); - - for (int sensor = 1; sensor < 11; sensor++) - { - json.Append("[\""); - json.Append(cumulus.ExtraDPCaptions[sensor]); - json.Append("\",\""); - json.Append(ExtraDewPoint[sensor].ToString(cumulus.TempFormat)); - json.Append("\",\"°"); - json.Append(cumulus.Units.TempText[1].ToString()); - json.Append("\"]"); - - if (sensor < 10) - { - json.Append(","); - } - } - - json.Append("]}"); - return json.ToString(); - } - - public string GetSoilTemp() - { - var json = new StringBuilder("{\"data\":[", 2048); - - json.Append($"[\"{cumulus.SoilTempCaptions[1]}\",\"{SoilTemp1.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); - json.Append($"[\"{cumulus.SoilTempCaptions[2]}\",\"{SoilTemp2.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); - json.Append($"[\"{cumulus.SoilTempCaptions[3]}\",\"{SoilTemp3.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); - json.Append($"[\"{cumulus.SoilTempCaptions[4]}\",\"{SoilTemp4.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); - json.Append($"[\"{cumulus.SoilTempCaptions[5]}\",\"{SoilTemp5.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); - json.Append($"[\"{cumulus.SoilTempCaptions[6]}\",\"{SoilTemp6.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); - json.Append($"[\"{cumulus.SoilTempCaptions[7]}\",\"{SoilTemp7.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); - json.Append($"[\"{cumulus.SoilTempCaptions[8]}\",\"{SoilTemp8.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); - json.Append($"[\"{cumulus.SoilTempCaptions[9]}\",\"{SoilTemp9.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); - json.Append($"[\"{cumulus.SoilTempCaptions[10]}\",\"{SoilTemp10.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); - json.Append($"[\"{cumulus.SoilTempCaptions[11]}\",\"{SoilTemp11.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); - json.Append($"[\"{cumulus.SoilTempCaptions[12]}\",\"{SoilTemp12.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); - json.Append($"[\"{cumulus.SoilTempCaptions[13]}\",\"{SoilTemp13.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); - json.Append($"[\"{cumulus.SoilTempCaptions[14]}\",\"{SoilTemp14.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); - json.Append($"[\"{cumulus.SoilTempCaptions[15]}\",\"{SoilTemp15.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); - json.Append($"[\"{cumulus.SoilTempCaptions[16]}\",\"{SoilTemp16.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"]"); - json.Append("]}"); - return json.ToString(); - } - - public string GetSoilMoisture() - { - var json = new StringBuilder("{\"data\":[", 1024); - - json.Append($"[\"{cumulus.SoilMoistureCaptions[1]}\",\"{SoilMoisture1:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); - json.Append($"[\"{cumulus.SoilMoistureCaptions[2]}\",\"{SoilMoisture2:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); - json.Append($"[\"{cumulus.SoilMoistureCaptions[3]}\",\"{SoilMoisture3:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); - json.Append($"[\"{cumulus.SoilMoistureCaptions[4]}\",\"{SoilMoisture4:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); - json.Append($"[\"{cumulus.SoilMoistureCaptions[5]}\",\"{SoilMoisture5:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); - json.Append($"[\"{cumulus.SoilMoistureCaptions[6]}\",\"{SoilMoisture6:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); - json.Append($"[\"{cumulus.SoilMoistureCaptions[7]}\",\"{SoilMoisture7:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); - json.Append($"[\"{cumulus.SoilMoistureCaptions[8]}\",\"{SoilMoisture8:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); - json.Append($"[\"{cumulus.SoilMoistureCaptions[9]}\",\"{SoilMoisture9:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); - json.Append($"[\"{cumulus.SoilMoistureCaptions[10]}\",\"{SoilMoisture10:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); - json.Append($"[\"{cumulus.SoilMoistureCaptions[11]}\",\"{SoilMoisture11:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); - json.Append($"[\"{cumulus.SoilMoistureCaptions[12]}\",\"{SoilMoisture12:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); - json.Append($"[\"{cumulus.SoilMoistureCaptions[13]}\",\"{SoilMoisture13:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); - json.Append($"[\"{cumulus.SoilMoistureCaptions[14]}\",\"{SoilMoisture14:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); - json.Append($"[\"{cumulus.SoilMoistureCaptions[15]}\",\"{SoilMoisture15:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); - json.Append($"[\"{cumulus.SoilMoistureCaptions[16]}\",\"{SoilMoisture16:F0}\",\"{cumulus.SoilMoistureUnitText}\"]"); - json.Append("]}"); - return json.ToString(); - } - - public string GetAirQuality() - { - var json = new StringBuilder("{\"data\":[", 1024); - - json.Append($"[\"{cumulus.AirQualityCaptions[1]}\",\"{AirQuality1:F1}\",\"{cumulus.AirQualityUnitText}\"],"); - json.Append($"[\"{cumulus.AirQualityCaptions[2]}\",\"{AirQuality2:F1}\",\"{cumulus.AirQualityUnitText}\"],"); - json.Append($"[\"{cumulus.AirQualityCaptions[3]}\",\"{AirQuality3:F1}\",\"{cumulus.AirQualityUnitText}\"],"); - json.Append($"[\"{cumulus.AirQualityCaptions[4]}\",\"{AirQuality4:F1}\",\"{cumulus.AirQualityUnitText}\"],"); - json.Append($"[\"{cumulus.AirQualityAvgCaptions[1]}\",\"{AirQualityAvg1:F1}\",\"{cumulus.AirQualityUnitText}\"],"); - json.Append($"[\"{cumulus.AirQualityAvgCaptions[2]}\",\"{AirQualityAvg2:F1}\",\"{cumulus.AirQualityUnitText}\"],"); - json.Append($"[\"{cumulus.AirQualityAvgCaptions[3]}\",\"{AirQualityAvg3:F1}\",\"{cumulus.AirQualityUnitText}\"],"); - json.Append($"[\"{cumulus.AirQualityAvgCaptions[4]}\",\"{AirQualityAvg4:F1}\",\"{cumulus.AirQualityUnitText}\"]"); - json.Append("]}"); - return json.ToString(); - } - - public string GetCO2sensor() - { - var json = new StringBuilder("{\"data\":[", 1024); - - json.Append($"[\"{cumulus.CO2_CurrentCaption}\",\"{CO2}\",\"{cumulus.CO2UnitText}\"],"); - json.Append($"[\"{cumulus.CO2_24HourCaption}\",\"{CO2_24h}\",\"{cumulus.CO2UnitText}\"],"); - json.Append($"[\"{cumulus.CO2_pm2p5Caption}\",\"{CO2_pm2p5:F1}\",\"{cumulus.AirQualityUnitText}\"],"); - json.Append($"[\"{cumulus.CO2_pm2p5_24hrCaption}\",\"{CO2_pm2p5_24h:F1}\",\"{cumulus.AirQualityUnitText}\"],"); - json.Append($"[\"{cumulus.CO2_pm10Caption}\",\"{CO2_pm10:F1}\",\"{cumulus.AirQualityUnitText}\"],"); - json.Append($"[\"{cumulus.CO2_pm10_24hrCaption}\",\"{CO2_pm10_24h:F1}\",\"{cumulus.AirQualityUnitText}\"]"); - json.Append("]}"); - return json.ToString(); - } - - public string GetLightning() - { - var json = new StringBuilder("{\"data\":[", 256); - - json.Append($"[\"Distance to last strike\",\"{LightningDistance.ToString(cumulus.WindRunFormat)}\",\"{cumulus.Units.WindRunText}\"],"); - json.Append($"[\"Time of last strike\",\"{LightningTime}\",\"\"],"); - json.Append($"[\"Number of strikes today\",\"{LightningStrikesToday}\",\"\"]"); - json.Append("]}"); - return json.ToString(); - } - - public string GetLeaf() - { - var json = new StringBuilder("{\"data\":[", 256); - - json.Append($"[\"{cumulus.LeafTempCaptions[1]}\",\"{LeafTemp1.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); - json.Append($"[\"{cumulus.LeafTempCaptions[2]}\",\"{LeafTemp2.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); - json.Append($"[\"{cumulus.LeafWetnessCaptions[1]}\",\"{LeafWetness1}\",\" \"],"); - json.Append($"[\"{cumulus.LeafWetnessCaptions[2]}\",\"{LeafWetness2}\",\" \"]"); - json.Append("]}"); - return json.ToString(); - } - - public string GetLeaf4() - { - var json = new StringBuilder("{\"data\":[", 256); - - json.Append($"[\"{cumulus.LeafTempCaptions[1]}\",\"{LeafTemp1.ToString(cumulus.TempFormat)} °{cumulus.Units.TempText[1]}\",\"{LeafWetness1}\"],"); - json.Append($"[\"{cumulus.LeafTempCaptions[2]}\",\"{LeafTemp2.ToString(cumulus.TempFormat)} °{cumulus.Units.TempText[1]}\",\"{LeafWetness2}\"],"); - json.Append($"[\"{cumulus.LeafWetnessCaptions[1]}\",\"{LeafWetness1}\",\" \"],"); - json.Append($"[\"{cumulus.LeafWetnessCaptions[2]}\",\"{LeafWetness2}\",\" \"],"); - json.Append($"[\"{cumulus.LeafWetnessCaptions[3]}\",\"{LeafWetness3}\",\" \"],"); - json.Append($"[\"{cumulus.LeafWetnessCaptions[4]}\",\"{LeafWetness4}\",\" \"]"); - json.Append("]}"); - return json.ToString(); - } - - public string GetLeaf8() - { - var json = new StringBuilder("{\"data\":[", 256); - - json.Append($"[\"{cumulus.LeafWetnessCaptions[1]}\",\"{LeafWetness1}\",\" \"],"); - json.Append($"[\"{cumulus.LeafWetnessCaptions[2]}\",\"{LeafWetness2}\",\" \"],"); - json.Append($"[\"{cumulus.LeafWetnessCaptions[3]}\",\"{LeafWetness3}\",\" \"],"); - json.Append($"[\"{cumulus.LeafWetnessCaptions[4]}\",\"{LeafWetness4}\",\" \"],"); - json.Append($"[\"{cumulus.LeafWetnessCaptions[5]}\",\"{LeafWetness5}\",\" \"],"); - json.Append($"[\"{cumulus.LeafWetnessCaptions[6]}\",\"{LeafWetness6}\",\" \"],"); - json.Append($"[\"{cumulus.LeafWetnessCaptions[7]}\",\"{LeafWetness7}\",\" \"],"); - json.Append($"[\"{cumulus.LeafWetnessCaptions[8]}\",\"{LeafWetness8}\",\" \"]"); - json.Append("]}"); - return json.ToString(); - } - - - public string GetAirLinkCountsOut() - { - var json = new StringBuilder("{\"data\":[", 256); - if (cumulus.airLinkOut != null) - { - json.Append($"[\"1 μm\",\"{cumulus.airLinkDataOut.pm1:F1}\",\"--\",\"--\",\"--\",\"--\"],"); - json.Append($"[\"2.5 μm\",\"{cumulus.airLinkDataOut.pm2p5:F1}\",\"{cumulus.airLinkDataOut.pm2p5_1hr:F1}\",\"{cumulus.airLinkDataOut.pm2p5_3hr:F1}\",\"{cumulus.airLinkDataOut.pm2p5_24hr:F1}\",\"{cumulus.airLinkDataOut.pm2p5_nowcast:F1}\"],"); - json.Append($"[\"10 μm\",\"{cumulus.airLinkDataOut.pm10:F1}\",\"{cumulus.airLinkDataOut.pm10_1hr:F1}\",\"{cumulus.airLinkDataOut.pm10_3hr:F1}\",\"{cumulus.airLinkDataOut.pm10_24hr:F1}\",\"{cumulus.airLinkDataOut.pm10_nowcast:F1}\"]"); - } - else - { - json.Append("[\"1 μm\",\"--\",\"--\",\"--\",\"--\",\"--\"],"); - json.Append("[\"2.5 μm\",\"--\",\"--\",\"--\",\"--\",\"--\"],"); - json.Append("[\"10 μm\",\"--\",\"--\",\"--\",\"--\",\"--\"]"); - } - json.Append("]}"); - return json.ToString(); - } - - public string GetAirLinkAqiOut() - { - var json = new StringBuilder("{\"data\":[", 256); - if (cumulus.airLinkOut != null) - { - json.Append($"[\"2.5 μm\",\"{cumulus.airLinkDataOut.aqiPm2p5:F1}\",\"{cumulus.airLinkDataOut.aqiPm2p5_1hr:F1}\",\"{cumulus.airLinkDataOut.aqiPm2p5_3hr:F1}\",\"{cumulus.airLinkDataOut.aqiPm2p5_24hr:F1}\",\"{cumulus.airLinkDataOut.aqiPm2p5_nowcast:F1}\"],"); - json.Append($"[\"10 μm\",\"{cumulus.airLinkDataOut.aqiPm10:F1}\",\"{cumulus.airLinkDataOut.aqiPm10_1hr:F1}\",\"{cumulus.airLinkDataOut.aqiPm10_3hr:F1}\",\"{cumulus.airLinkDataOut.aqiPm10_24hr:F1}\",\"{cumulus.airLinkDataOut.aqiPm10_nowcast:F1}\"]"); - } - else - { - json.Append("[\"2.5 μm\",\"--\",\"--\",\"--\",\"--\",\"--\"],"); - json.Append("[\"10 μm\",\"--\",\"--\",\"--\",\"--\",\"--\"]"); - } - json.Append("]}"); - return json.ToString(); - } - - public string GetAirLinkPctOut() - { - var json = new StringBuilder("{\"data\":[", 256); - if (cumulus.airLinkOut != null) - { - json.Append($"[\"All sizes\",\"--\",\"{cumulus.airLinkDataOut.pct_1hr}%\",\"{cumulus.airLinkDataOut.pct_3hr}%\",\"{cumulus.airLinkDataOut.pct_24hr}%\",\"{cumulus.airLinkDataOut.pct_nowcast}%\"]"); - } - else - { - json.Append("[\"All sizes\",\"--\",\"--\",\"--\",\"--\",\"--\"]"); - } - json.Append("]}"); - return json.ToString(); - } - - public string GetAirLinkCountsIn() - { - var json = new StringBuilder("{\"data\":[", 256); - if (cumulus.airLinkIn != null) - { - json.Append($"[\"1 μm\",\"{cumulus.airLinkDataIn.pm1:F1}\",\"--\",\"--\",\"--\",\"--\"],"); - json.Append($"[\"2.5 μm\",\"{cumulus.airLinkDataIn.pm2p5:F1}\",\"{cumulus.airLinkDataIn.pm2p5_1hr:F1}\",\"{cumulus.airLinkDataIn.pm2p5_3hr:F1}\",\"{cumulus.airLinkDataIn.pm2p5_24hr:F1}\",\"{cumulus.airLinkDataIn.pm2p5_nowcast:F1}\"],"); - json.Append($"[\"10 μm\",\"{cumulus.airLinkDataIn.pm10:F1}\",\"{cumulus.airLinkDataIn.pm10_1hr:F1}\",\"{cumulus.airLinkDataIn.pm10_3hr:F1}\",\"{cumulus.airLinkDataIn.pm10_24hr:F1}\",\"{cumulus.airLinkDataIn.pm10_nowcast:F1}\"]"); - } - else - { - json.Append("[\"1 μm\",\"--\",\"--\",\"--\",\"--\",\"--\"],"); - json.Append("[\"2.5 μm\",\"--\",\"--\",\"--\",\"--\",\"--\"],"); - json.Append("[\"10 μm\",\"--\",\"--\",\"--\",\"--\",\"--\"]"); - } - json.Append("]}"); - return json.ToString(); - } - - public string GetAirLinkAqiIn() - { - var json = new StringBuilder("{\"data\":[", 256); - if (cumulus.airLinkIn != null) - { - json.Append($"[\"2.5 μm\",\"{cumulus.airLinkDataIn.aqiPm2p5:F1}\",\"{cumulus.airLinkDataIn.aqiPm2p5_1hr:F1}\",\"{cumulus.airLinkDataIn.aqiPm2p5_3hr:F1}\",\"{cumulus.airLinkDataIn.aqiPm2p5_24hr:F1}\",\"{cumulus.airLinkDataIn.aqiPm2p5_nowcast:F1}\"],"); - json.Append($"[\"10 μm\",\"{cumulus.airLinkDataIn.aqiPm10:F1}\",\"{cumulus.airLinkDataIn.aqiPm10_1hr:F1}\",\"{cumulus.airLinkDataIn.aqiPm10_3hr:F1}\",\"{cumulus.airLinkDataIn.aqiPm10_24hr:F1}\",\"{cumulus.airLinkDataIn.aqiPm10_nowcast:F1}\"]"); - } - else - { - json.Append("[\"2.5 μm\",\"--\",\"--\",\"--\",\"--\",\"--\"],"); - json.Append("[\"10 μm\",\"--\",\"--\",\"--\",\"--\",\"--\"]"); - } - json.Append("]}"); - return json.ToString(); - } - - public string GetAirLinkPctIn() - { - var json = new StringBuilder("{\"data\":[", 256); - if (cumulus.airLinkIn != null) - { - json.Append($"[\"All sizes\",\"--\",\"{cumulus.airLinkDataIn.pct_1hr}%\",\"{cumulus.airLinkDataIn.pct_3hr}%\",\"{cumulus.airLinkDataIn.pct_24hr}%\",\"{cumulus.airLinkDataIn.pct_nowcast}%\"]"); - } - else - { - json.Append("[\"All sizes\",\"--\",\"--\",\"--\",\"--\",\"--\"]"); - } - json.Append("]}"); - return json.ToString(); - } - - /* - private string extrajsonformat(int item, double value, DateTime timestamp, string unit, string valueformat, string dateformat) - { - return "[\"" + alltimedescs[item] + "\",\"" + value.ToString(valueformat) + " " + unit + "\",\"" + timestamp.ToString(dateformat) + "\"]"; - } - */ - - // The Today/Yesterday data is in the form: - // Name, today value + units, today time, yesterday value + units, yesterday time - // It's used to automatically populate a DataTables table in the browser interface - public string GetTodayYestTemp() - { - var json = new StringBuilder("{\"data\":[", 2048); - var sepStr = "\",\""; - var closeStr = "\"],"; - var tempUnitStr = " °" + cumulus.Units.TempText[1].ToString() + sepStr; - - json.Append("[\"High Temperature\",\""); - json.Append(HiLoToday.HighTemp.ToString(cumulus.TempFormat)); - json.Append(tempUnitStr); - json.Append(HiLoToday.HighTempTime.ToShortTimeString()); - json.Append(sepStr); - json.Append(HiLoYest.HighTemp.ToString(cumulus.TempFormat)); - json.Append(tempUnitStr); - json.Append(HiLoYest.HighTempTime.ToShortTimeString()); - json.Append(closeStr); - - json.Append("[\"Low Temperature\",\""); - json.Append(HiLoToday.LowTemp.ToString(cumulus.TempFormat)); - json.Append(tempUnitStr); - json.Append(HiLoToday.LowTempTime.ToShortTimeString()); - json.Append(sepStr); - json.Append(HiLoYest.LowTemp.ToString(cumulus.TempFormat)); - json.Append(tempUnitStr); - json.Append(HiLoYest.LowTempTime.ToShortTimeString()); - json.Append(closeStr); - - json.Append("[\"Temperature Range\",\""); - json.Append((HiLoToday.HighTemp - HiLoToday.LowTemp).ToString(cumulus.TempFormat)); - json.Append(tempUnitStr); - json.Append(" \",\""); - json.Append((HiLoYest.HighTemp - HiLoYest.LowTemp).ToString(cumulus.TempFormat)); - json.Append(tempUnitStr); - json.Append(" \"],"); - - json.Append("[\"High Apparent Temperature\",\""); - json.Append(HiLoToday.HighAppTemp.ToString(cumulus.TempFormat)); - json.Append(tempUnitStr); - json.Append(HiLoToday.HighAppTempTime.ToShortTimeString()); - json.Append(sepStr); - json.Append(HiLoYest.HighAppTemp.ToString(cumulus.TempFormat)); - json.Append(tempUnitStr); - json.Append(HiLoYest.HighAppTempTime.ToShortTimeString()); - json.Append(closeStr); - - json.Append("[\"Low Apparent Temperature\",\""); - json.Append(HiLoToday.LowAppTemp.ToString(cumulus.TempFormat)); - json.Append(tempUnitStr); - json.Append(HiLoToday.LowAppTempTime.ToShortTimeString()); - json.Append(sepStr); - json.Append(HiLoYest.LowAppTemp.ToString(cumulus.TempFormat)); - json.Append(tempUnitStr); - json.Append(HiLoYest.LowAppTempTime.ToShortTimeString()); - json.Append(closeStr); - - json.Append("[\"High Feels Like\",\""); - json.Append(HiLoToday.HighFeelsLike.ToString(cumulus.TempFormat)); - json.Append(tempUnitStr); - json.Append(HiLoToday.HighFeelsLikeTime.ToShortTimeString()); - json.Append(sepStr); - json.Append(HiLoYest.HighFeelsLike.ToString(cumulus.TempFormat)); - json.Append(tempUnitStr); - json.Append(HiLoYest.HighFeelsLikeTime.ToShortTimeString()); - json.Append(closeStr); - - json.Append("[\"Low Feels Like\",\""); - json.Append(HiLoToday.LowFeelsLike.ToString(cumulus.TempFormat)); - json.Append(tempUnitStr); - json.Append(HiLoToday.LowFeelsLikeTime.ToShortTimeString()); - json.Append(sepStr); - json.Append(HiLoYest.LowFeelsLike.ToString(cumulus.TempFormat)); - json.Append(tempUnitStr); - json.Append(HiLoYest.LowFeelsLikeTime.ToShortTimeString()); - json.Append(closeStr); - - json.Append("[\"High Humidex\",\""); - json.Append(HiLoToday.HighHumidex.ToString(cumulus.TempFormat)); - json.Append("\",\""); - json.Append(HiLoToday.HighHumidexTime.ToShortTimeString()); - json.Append(sepStr); - json.Append(HiLoYest.HighHumidex.ToString(cumulus.TempFormat)); - json.Append("\",\""); - json.Append(HiLoYest.HighHumidexTime.ToShortTimeString()); - json.Append(closeStr); - json.Append("[\"High Dew Point\",\""); - json.Append(HiLoToday.HighDewPoint.ToString(cumulus.TempFormat)); - json.Append(tempUnitStr); - json.Append(HiLoToday.HighDewPointTime.ToShortTimeString()); - json.Append(sepStr); - json.Append(HiLoYest.HighDewPoint.ToString(cumulus.TempFormat)); - json.Append(tempUnitStr); - json.Append(HiLoYest.HighDewPointTime.ToShortTimeString()); - json.Append(closeStr); - - json.Append("[\"Low Dew Point\",\""); - json.Append(HiLoToday.LowDewPoint.ToString(cumulus.TempFormat)); - json.Append(tempUnitStr); - json.Append(HiLoToday.LowDewPointTime.ToShortTimeString()); - json.Append(sepStr); - json.Append(HiLoYest.LowDewPoint.ToString(cumulus.TempFormat)); - json.Append(tempUnitStr); - json.Append(HiLoYest.LowDewPointTime.ToShortTimeString()); - json.Append(closeStr); - - json.Append("[\"Low Wind Chill\",\""); - json.Append(HiLoToday.LowWindChill.ToString(cumulus.TempFormat)); - json.Append(tempUnitStr); - json.Append(HiLoToday.LowWindChillTime.ToShortTimeString()); - json.Append(sepStr); - json.Append(HiLoYest.LowWindChill.ToString(cumulus.TempFormat)); - json.Append(tempUnitStr); - json.Append(HiLoYest.LowWindChillTime.ToShortTimeString()); - json.Append(closeStr); - - json.Append("[\"High Heat Index\",\""); - json.Append(HiLoToday.HighHeatIndex.ToString(cumulus.TempFormat)); - json.Append(tempUnitStr); - json.Append(HiLoToday.HighHeatIndexTime.ToShortTimeString()); - json.Append(sepStr); - json.Append(HiLoYest.HighHeatIndex.ToString(cumulus.TempFormat)); - json.Append(tempUnitStr); - json.Append(HiLoYest.HighHeatIndexTime.ToShortTimeString()); - json.Append("\"]"); - - json.Append("]}"); - return json.ToString(); - } - - public string GetTodayYestHum() - { - var json = new StringBuilder("{\"data\":[", 512); - var sepStr = "\",\""; - var unitStr = " %" + sepStr; - - json.Append("[\"High Humidity\",\""); - json.Append(HiLoToday.HighHumidity.ToString(cumulus.HumFormat)); - json.Append(unitStr); - json.Append(HiLoToday.HighHumidityTime.ToShortTimeString()); - json.Append(sepStr); - json.Append(HiLoYest.HighHumidity.ToString(cumulus.HumFormat)); - json.Append(unitStr); - json.Append(HiLoYest.HighHumidityTime.ToShortTimeString()); - json.Append("\"],"); - - json.Append("[\"Low Humidity\",\""); - json.Append(HiLoToday.LowHumidity.ToString(cumulus.HumFormat)); - json.Append(unitStr); - json.Append(HiLoToday.LowHumidityTime.ToShortTimeString()); - json.Append(sepStr); - json.Append(HiLoYest.LowHumidity.ToString(cumulus.HumFormat)); - json.Append(unitStr); - json.Append(HiLoYest.LowHumidityTime.ToShortTimeString()); - json.Append("\"]"); - - json.Append("]}"); - return json.ToString(); - } - - public string GetTodayYestRain() - { - var json = new StringBuilder("{\"data\":[", 512); - var sepStr = "\",\""; - var unitStr = " " + cumulus.Units.RainText; - - json.Append("[\"Total Rain\",\""); - json.Append(RainToday.ToString(cumulus.RainFormat)); - json.Append(unitStr); - json.Append(sepStr); - json.Append(" "); - json.Append(sepStr); - json.Append(RainYesterday.ToString(cumulus.RainFormat)); - json.Append(unitStr); - json.Append(sepStr); - json.Append(" "); - json.Append("\"],"); - - json.Append("[\"High Rain Rate\",\""); - json.Append(HiLoToday.HighRainRate.ToString(cumulus.RainFormat)); - json.Append(unitStr + "/hr"); - json.Append(sepStr); - json.Append(HiLoToday.HighRainRateTime.ToShortTimeString()); - json.Append(sepStr); - json.Append(HiLoYest.HighRainRate.ToString(cumulus.RainFormat)); - json.Append(unitStr + "/hr"); - json.Append(sepStr); - json.Append(HiLoYest.HighRainRateTime.ToShortTimeString()); - json.Append("\"],"); - - json.Append("[\"High Hourly Rain\",\""); - json.Append(HiLoToday.HighHourlyRain.ToString(cumulus.RainFormat)); - json.Append(unitStr); - json.Append(sepStr); - json.Append(HiLoToday.HighHourlyRainTime.ToShortTimeString()); - json.Append(sepStr); - json.Append(HiLoYest.HighHourlyRain.ToString(cumulus.RainFormat)); - json.Append(unitStr); - json.Append(sepStr); - json.Append(HiLoYest.HighHourlyRainTime.ToShortTimeString()); - json.Append("\"]"); - - json.Append("]}"); - return json.ToString(); - } - - public string GetTodayYestWind() - { - var json = new StringBuilder("{\"data\":[", 512); - var sepStr = "\",\""; - - json.Append("[\"Highest Gust\",\""); - json.Append(HiLoToday.HighGust.ToString(cumulus.WindFormat)); - json.Append(" " + cumulus.Units.WindText); - json.Append(sepStr); - json.Append(HiLoToday.HighGustTime.ToShortTimeString()); - json.Append(sepStr); - json.Append(HiLoYest.HighGust.ToString(cumulus.WindFormat)); - json.Append(" " + cumulus.Units.WindText); - json.Append(sepStr); - json.Append(HiLoYest.HighGustTime.ToShortTimeString()); - json.Append("\"],"); - - json.Append("[\"Highest Speed\",\""); - json.Append(HiLoToday.HighWind.ToString(cumulus.WindAvgFormat)); - json.Append(" " + cumulus.Units.WindText); - json.Append(sepStr); - json.Append(HiLoToday.HighWindTime.ToShortTimeString()); - json.Append(sepStr); - json.Append(HiLoYest.HighWind.ToString(cumulus.WindAvgFormat)); - json.Append(" " + cumulus.Units.WindText); - json.Append(sepStr); - json.Append(HiLoYest.HighWindTime.ToShortTimeString()); - json.Append("\"],"); - - json.Append("[\"Wind Run\",\""); - json.Append(WindRunToday.ToString(cumulus.WindRunFormat)); - json.Append(" " + cumulus.Units.WindRunText); - json.Append(sepStr); - json.Append(" "); - json.Append(sepStr); - json.Append(YesterdayWindRun.ToString(cumulus.WindRunFormat)); - json.Append(" " + cumulus.Units.WindRunText); - json.Append(sepStr); - json.Append(" "); - json.Append("\"],"); - - json.Append("[\"Dominant Direction\",\""); - json.Append(DominantWindBearing.ToString("F0")); - json.Append(" ° " + CompassPoint(DominantWindBearing)); - json.Append(sepStr); - json.Append(" "); - json.Append(sepStr); - json.Append(YestDominantWindBearing.ToString("F0")); - json.Append(" ° " + CompassPoint(YestDominantWindBearing)); - json.Append(sepStr); - json.Append(" "); - json.Append("\"]"); - - json.Append("]}"); - return json.ToString(); - } - - public string GetTodayYestPressure() - { - var json = new StringBuilder("{\"data\":[", 512); - var sepStr = "\",\""; - var unitStr = " " + cumulus.Units.PressText; - - json.Append("[\"High Pressure\",\""); - json.Append(HiLoToday.HighPress.ToString(cumulus.PressFormat)); - json.Append(unitStr); - json.Append(sepStr); - json.Append(HiLoToday.HighPressTime.ToShortTimeString()); - json.Append(sepStr); - json.Append(HiLoYest.HighPress.ToString(cumulus.PressFormat)); - json.Append(unitStr); - json.Append(sepStr); - json.Append(HiLoYest.HighPressTime.ToShortTimeString()); - json.Append("\"],"); - - json.Append("[\"Low Pressure\",\""); - json.Append(HiLoToday.LowPress.ToString(cumulus.PressFormat)); - json.Append(unitStr); - json.Append(sepStr); - json.Append(HiLoToday.LowPressTime.ToShortTimeString()); - json.Append(sepStr); - json.Append(HiLoYest.LowPress.ToString(cumulus.PressFormat)); - json.Append(unitStr); - json.Append(sepStr); - json.Append(HiLoYest.LowPressTime.ToShortTimeString()); - json.Append("\"]"); - - json.Append("]}"); - return json.ToString(); - } - - public string GetTodayYestSolar() - { - var json = new StringBuilder("{\"data\":[", 512); - var sepStr = "\",\""; - - json.Append("[\"High Solar Radiation\",\""); - json.Append(HiLoToday.HighSolar.ToString("F0")); - json.Append(" W/m2"); - json.Append(sepStr); - json.Append(HiLoToday.HighSolarTime.ToShortTimeString()); - json.Append(sepStr); - json.Append(HiLoYest.HighSolar.ToString("F0")); - json.Append(" W/m2"); - json.Append(sepStr); - json.Append(HiLoYest.HighSolarTime.ToShortTimeString()); - json.Append("\"],"); - - json.Append("[\"Hours of Sunshine\",\""); - json.Append(SunshineHours.ToString(cumulus.SunFormat)); - json.Append(" hrs"); - json.Append(sepStr); - json.Append(" "); - json.Append(sepStr); - json.Append(YestSunshineHours.ToString(cumulus.SunFormat)); - json.Append(" hrs"); - json.Append(sepStr); - json.Append(" "); - json.Append("\"]"); - - json.Append("]}"); - return json.ToString(); - } - - /// - /// Return lines from dayfile.txt in json format - /// - /// - /// - /// - /// JSON encoded section of the dayfile - public string GetDayfile(string draw, int start, int length) - { - try - { - //var total = File.ReadLines(cumulus.DayFile).Count(); - var allLines = File.ReadAllLines(cumulus.DayFileName); - var total = allLines.Length; - var lines = allLines.Skip(start).Take(length); - - var json = new StringBuilder(350 * lines.Count()); - - json.Append("{\"draw\":" + draw); - json.Append(",\"recordsTotal\":" + total); - json.Append(",\"recordsFiltered\":" + total); - json.Append(",\"data\":["); - - //var lines = File.ReadLines(cumulus.DayFile).Skip(start).Take(length); - - var lineNum = start + 1; // Start is zero relative - - foreach (var line in lines) - { - var fields = line.Split(Convert.ToChar(cumulus.ListSeparator)); - var numFields = fields.Length; - json.Append($"[{lineNum++},"); - for (var i = 0; i < numFields; i++) - { - json.Append($"\"{fields[i]}\""); - if (i < fields.Length - 1) - { - json.Append(","); - } - } - - if (numFields < Cumulus.DayfileFields) - { - // insufficient fields, pad with empty fields - for (var i = numFields; i < Cumulus.DayfileFields; i++) - { - json.Append(",\"\""); - } - } - json.Append("],"); - } - - // trim last "," - json.Length--; - json.Append("]}"); - - return json.ToString(); - } - catch (Exception ex) - { - cumulus.LogMessage(ex.ToString()); - } - - return ""; - } - - internal string GetDiaryData(string date) - { - - StringBuilder json = new StringBuilder("{\"entry\":\"", 1024); - - var result = cumulus.DiaryDB.Query("select * from DiaryData where date(Timestamp,'utc') = ? order by Timestamp limit 1", date); - - if (result.Count > 0) - { - json.Append(result[0].entry + "\","); - json.Append("\"snowFalling\":"); - json.Append(result[0].snowFalling + ","); - json.Append("\"snowLying\":"); - json.Append(result[0].snowLying + ","); - json.Append("\"snowDepth\":\""); - json.Append(result[0].snowDepth); - json.Append("\"}"); - } - else - { - json.Append("\",\"snowFalling\":0,\"snowLying\":0,\"snowDepth\":\"\"}"); - } - - return json.ToString(); - } - - // Fetchs all days in the required month that have a diary entry - //internal string GetDiarySummary(string year, string month) - internal string GetDiarySummary() - { - var json = new StringBuilder(512); - //var result = cumulus.DiaryDB.Query("select Timestamp from DiaryData where strftime('%Y', Timestamp) = ? and strftime('%m', Timestamp) = ? order by Timestamp", year, month); - var result = cumulus.DiaryDB.Query("select Timestamp from DiaryData order by Timestamp"); - - if (result.Count > 0) - { - json.Append("{\"dates\":["); - for (int i = 0; i < result.Count; i++) - { - json.Append("\""); - json.Append(result[i].Timestamp.ToUniversalTime().ToString("yyy-MM-dd")); - json.Append("\","); - } - json.Length--; - json.Append("]}"); - } - else - { - json.Append("{\"dates\":[]}"); - } - - return json.ToString(); - } - - /// - /// Return lines from log file in json format - /// - /// - /// - /// - /// - /// - /// - public string GetLogfile(string date, string draw, int start, int length, bool extra) - { - try - { - // date will (hopefully) be in format "m-yyyy" or "mm-yyyy" - int month = Convert.ToInt32(date.Split('-')[0]); - int year = Convert.ToInt32(date.Split('-')[1]); - - // Get a timestamp, use 15th day to avoid wrap issues - var ts = new DateTime(year, month, 15); - - var logfile = extra ? cumulus.GetExtraLogFileName(ts) : cumulus.GetLogFileName(ts); - var numFields = extra ? Cumulus.NumExtraLogFileFields : Cumulus.NumLogFileFields; - - if (!File.Exists(logfile)) - { - cumulus.LogMessage($"GetLogFile: Error, file does not exist: {logfile}"); - return ""; - } - - var allLines = File.ReadAllLines(logfile); - var total = allLines.Length; - var lines = allLines.Skip(start).Take(length); - - //var total = File.ReadLines(logfile).Count(); - var json = new StringBuilder(220 * lines.Count()); - - json.Append("{\"draw\":"); - json.Append(draw); - json.Append(",\"recordsTotal\":"); - json.Append(total); - json.Append(",\"recordsFiltered\":"); - json.Append(total); - json.Append(",\"data\":["); - - //var lines = File.ReadLines(logfile).Skip(start).Take(length); - - var lineNum = start + 1; // Start is zero relative - - foreach (var line in lines) - { - var fields = line.Split(Convert.ToChar(cumulus.ListSeparator)); - json.Append($"[{lineNum++},"); - for (var i = 0; i < numFields; i++) - { - if (i < fields.Length) - { - // field exists - json.Append("\""); - json.Append(fields[i]); - json.Append("\""); - } - else - { - // add padding - json.Append("\" \""); - } - - if (i < numFields - 1) - { - json.Append(","); - } - } - json.Append("],"); - } - - // trim trailing "," - json.Length--; - json.Append("]}"); - - return json.ToString(); - } - catch (Exception ex) - { - cumulus.LogMessage(ex.ToString()); - } - - return ""; - } - - public string GetUnits() - { - return $"{{\"temp\":\"{cumulus.Units.TempText[1]}\",\"wind\":\"{cumulus.Units.WindText}\",\"rain\":\"{cumulus.Units.RainText}\",\"press\":\"{cumulus.Units.PressText}\"}}"; - } - - public string GetGraphConfig() - { - var json = new StringBuilder(200); - json.Append("{"); - json.Append($"\"temp\":{{\"units\":\"{cumulus.Units.TempText[1]}\",\"decimals\":{cumulus.TempDPlaces}}},"); - json.Append($"\"wind\":{{\"units\":\"{cumulus.Units.WindText}\",\"decimals\":{cumulus.WindAvgDPlaces},\"rununits\":\"{cumulus.Units.WindRunText}\"}},"); - json.Append($"\"rain\":{{\"units\":\"{cumulus.Units.RainText}\",\"decimals\":{cumulus.RainDPlaces}}},"); - json.Append($"\"press\":{{\"units\":\"{cumulus.Units.PressText}\",\"decimals\":{cumulus.PressDPlaces}}},"); - json.Append($"\"hum\":{{\"decimals\":{cumulus.HumDPlaces}}},"); - json.Append($"\"uv\":{{\"decimals\":{cumulus.UVDPlaces}}}"); - json.Append("}"); - return json.ToString(); - } - - public string GetAvailGraphData() - { - var json = new StringBuilder(200); - - // Temp values - json.Append("{\"Temperature\":["); - - if (cumulus.GraphOptions.TempVisible) - json.Append("\"Temperature\","); - - if (cumulus.GraphOptions.InTempVisible) - json.Append("\"Indoor Temp\","); - - if (cumulus.GraphOptions.HIVisible) - json.Append("\"Heat Index\","); - - if (cumulus.GraphOptions.DPVisible) - json.Append("\"Dew Point\","); - - if (cumulus.GraphOptions.WCVisible) - json.Append("\"Wind Chill\","); - - if (cumulus.GraphOptions.AppTempVisible) - json.Append("\"Apparent Temp\","); - - if (cumulus.GraphOptions.FeelsLikeVisible) - json.Append("\"Feels Like\","); - - //if (cumulus.GraphOptions.HumidexVisible) - // json.Append("\"Humidex\","); - - if (json[json.Length - 1] == ',') - json.Length--; - - // humidity values - json.Append("],\"Humidity\":["); - - if (cumulus.GraphOptions.OutHumVisible) - json.Append("\"Humidity\","); - - if (cumulus.GraphOptions.InHumVisible) - json.Append("\"Indoor Hum\","); - - if (json[json.Length - 1] == ',') - json.Length--; - - // fixed values - // pressure - json.Append("],\"Pressure\":[\"Pressure\"],"); - - // wind - json.Append("\"Wind\":[\"Wind Speed\",\"Wind Gust\",\"Wind Bearing\"],"); - - // rain - json.Append("\"Rain\":[\"Rainfall\",\"Rainfall Rate\"]"); - - if (cumulus.GraphOptions.DailyAvgTempVisible || cumulus.GraphOptions.DailyMaxTempVisible || cumulus.GraphOptions.DailyMinTempVisible) - { - json.Append(",\"DailyTemps\":["); - - if (cumulus.GraphOptions.DailyAvgTempVisible) - json.Append("\"AvgTemp\","); - if (cumulus.GraphOptions.DailyMaxTempVisible) - json.Append("\"MaxTemp\","); - if (cumulus.GraphOptions.DailyMinTempVisible) - json.Append("\"MinTemp\","); - - if (json[json.Length - 1] == ',') - json.Length--; - - json.Append("]"); - } - - - // solar values - if (cumulus.GraphOptions.SolarVisible || cumulus.GraphOptions.UVVisible) - { - json.Append(",\"Solar\":["); - - if (cumulus.GraphOptions.SolarVisible) - json.Append("\"Solar Rad\","); - - if (cumulus.GraphOptions.UVVisible) - json.Append("\"UV Index\","); - - if (json[json.Length - 1] == ',') - json.Length--; - - json.Append("]"); - } - - // Sunshine - if (cumulus.GraphOptions.SunshineVisible) - { - json.Append(",\"Sunshine\":[\"sunhours\"]"); - } - - // air quality - // Check if we are to generate AQ data at all. Only if a primary sensor is defined and it isn't the Indoor AirLink - if (cumulus.StationOptions.PrimaryAqSensor > (int)Cumulus.PrimaryAqSensor.Undefined - && cumulus.StationOptions.PrimaryAqSensor != (int)Cumulus.PrimaryAqSensor.AirLinkIndoor) - { - json.Append(",\"AirQuality\":["); - json.Append("\"PM 2.5\""); - - // Only the AirLink and Ecowitt CO2 servers provide PM10 values at the moment - if (cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.AirLinkOutdoor || - cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.AirLinkIndoor || - cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.EcowittCO2) - { - json.Append(",\"PM 10\""); - } - json.Append("]"); - } - - // Degree Days - if (cumulus.GraphOptions.GrowingDegreeDaysVisible1 || cumulus.GraphOptions.GrowingDegreeDaysVisible2) - { - json.Append(",\"DegreeDays\":["); - if (cumulus.GraphOptions.GrowingDegreeDaysVisible1) - json.Append("\"GDD1\","); - - if (cumulus.GraphOptions.GrowingDegreeDaysVisible2) - json.Append("\"GDD2\""); - - if (json[json.Length - 1] == ',') - json.Length--; - - json.Append("]"); - } - - // Temp Sum - if (cumulus.GraphOptions.TempSumVisible0 || cumulus.GraphOptions.TempSumVisible1 || cumulus.GraphOptions.TempSumVisible2) - { - json.Append(",\"TempSum\":["); - if (cumulus.GraphOptions.TempSumVisible0) - json.Append("\"Sum0\","); - if (cumulus.GraphOptions.TempSumVisible1) - json.Append("\"Sum1\","); - if (cumulus.GraphOptions.TempSumVisible2) - json.Append("\"Sum2\""); - - if (json[json.Length - 1] == ',') - json.Length--; - - json.Append("]"); - } - - json.Append("}"); - return json.ToString(); - } - - - public string GetSelectaChartOptions() - { - return JsonSerializer.SerializeToString(cumulus.SelectaChartOptions); - } - - - public string GetDailyRainGraphData() - { - var datefrom = DateTime.Now.AddDays(-cumulus.GraphDays - 1); - - var InvC = new CultureInfo(""); - StringBuilder sb = new StringBuilder("{\"dailyrain\":[", 10000); - - var data = DayFile.Where(rec => rec.Date >= datefrom).ToList(); - for (var i = 0; i < data.Count; i++) - { - sb.Append($"[{DateTimeToUnix(data[i].Date) * 1000},{data[i].TotalRain.ToString(cumulus.RainFormat, InvC)}]"); - - if (i < data.Count - 1) - sb.Append(","); - } - sb.Append("]}"); - return sb.ToString(); - } - - public string GetSunHoursGraphData() - { - var InvC = new CultureInfo(""); - StringBuilder sb = new StringBuilder("{", 10000); - if (cumulus.GraphOptions.SunshineVisible) - { - var datefrom = DateTime.Now.AddDays(-cumulus.GraphDays - 1); - var data = DayFile.Where(rec => rec.Date >= datefrom).ToList(); - - sb.Append("\"sunhours\":["); - for (var i = 0; i < data.Count; i++) - { - var sunhrs = data[i].SunShineHours >= 0 ? data[i].SunShineHours : 0; - sb.Append($"[{DateTimeToUnix(data[i].Date) * 1000},{sunhrs.ToString(cumulus.SunFormat, InvC)}]"); - - if (i < data.Count - 1) - sb.Append(","); - } - sb.Append("]"); - } - sb.Append("}"); - return sb.ToString(); - } - - public string GetDailyTempGraphData() - { - var InvC = new CultureInfo(""); - var datefrom = DateTime.Now.AddDays(-cumulus.GraphDays - 1); - var data = DayFile.Where(rec => rec.Date >= datefrom).ToList(); - var append = false; - StringBuilder sb = new StringBuilder("{"); - - if (cumulus.GraphOptions.DailyMinTempVisible) - { - sb.Append("\"mintemp\":["); - - for (var i = 0; i < data.Count; i++) - { - sb.Append($"[{DateTimeToUnix(data[i].Date) * 1000},{data[i].LowTemp.ToString(cumulus.TempFormat, InvC)}]"); - if (i < data.Count - 1) - sb.Append(","); - } - - sb.Append("]"); - append = true; - } - - if (cumulus.GraphOptions.DailyMaxTempVisible) - { - if (append) - sb.Append(","); - - sb.Append("\"maxtemp\":["); - - for (var i = 0; i < data.Count; i++) - { - sb.Append($"[{DateTimeToUnix(data[i].Date) * 1000},{data[i].HighTemp.ToString(cumulus.TempFormat, InvC)}]"); - if (i < data.Count - 1) - sb.Append(","); - } - - sb.Append("]"); - append = true; - } - - if (cumulus.GraphOptions.DailyAvgTempVisible) - { - if (append) - sb.Append(","); - - sb.Append("\"avgtemp\":["); - for (var i = 0; i < data.Count; i++) - { - sb.Append($"[{DateTimeToUnix(data[i].Date) * 1000},{data[i].AvgTemp.ToString(cumulus.TempFormat, InvC)}]"); - if (i < data.Count - 1) - sb.Append(","); - } - - sb.Append("]"); - } - - sb.Append("}"); - return sb.ToString(); - } - - public string GetAllDailyTempGraphData() - { - var InvC = new CultureInfo(""); - /* returns: - * { - * highgust:[[date1,val1],[date2,val2]...], - * mintemp:[[date1,val1],[date2,val2]...], - * etc - * } - */ - - StringBuilder sb = new StringBuilder("{"); - StringBuilder minTemp = new StringBuilder("["); - StringBuilder maxTemp = new StringBuilder("["); - StringBuilder avgTemp = new StringBuilder("["); - StringBuilder heatIdx = new StringBuilder("["); - StringBuilder maxApp = new StringBuilder("["); - StringBuilder minApp = new StringBuilder("["); - StringBuilder windChill = new StringBuilder("["); - StringBuilder maxDew = new StringBuilder("["); - StringBuilder minDew = new StringBuilder("["); - StringBuilder maxFeels = new StringBuilder("["); - StringBuilder minFeels = new StringBuilder("["); - StringBuilder humidex = new StringBuilder("["); - - // Read the day file list and extract the data from there - if (DayFile.Count() > 0) - { - var len = DayFile.Count() - 1; - - for (var i = 0; i < DayFile.Count(); i++) - { - var recDate = DateTimeToUnix(DayFile[i].Date) * 1000; - // lo temp - if (cumulus.GraphOptions.DailyMinTempVisible) - minTemp.Append($"[{recDate},{DayFile[i].LowTemp.ToString(cumulus.TempFormat, InvC)}]"); - // hi temp - if (cumulus.GraphOptions.DailyMaxTempVisible) - maxTemp.Append($"[{recDate},{DayFile[i].HighTemp.ToString(cumulus.TempFormat, InvC)}]"); - // avg temp - if (cumulus.GraphOptions.DailyAvgTempVisible) - avgTemp.Append($"[{recDate},{DayFile[i].AvgTemp.ToString(cumulus.TempFormat, InvC)}]"); - - if (i < len) - { - minTemp.Append(","); - maxTemp.Append(","); - avgTemp.Append(","); - } - - if (cumulus.GraphOptions.HIVisible) - { - // hi heat index - if (DayFile[i].HighHeatIndex > -999) - heatIdx.Append($"[{recDate},{DayFile[i].HighHeatIndex.ToString(cumulus.TempFormat, InvC)}]"); - else - heatIdx.Append($"[{recDate},null]"); - - if (i < len) - heatIdx.Append(","); - } - if (cumulus.GraphOptions.AppTempVisible) - { - // hi app temp - if (DayFile[i].HighAppTemp > -999) - maxApp.Append($"[{recDate},{DayFile[i].HighAppTemp.ToString(cumulus.TempFormat, InvC)}]"); - else - maxApp.Append($"[{recDate},null]"); - - // lo app temp - if (DayFile[i].LowAppTemp < 999) - minApp.Append($"[{recDate},{DayFile[i].LowAppTemp.ToString(cumulus.TempFormat, InvC)}]"); - else - minApp.Append($"[{recDate},null]"); - - if (i < len) - { - maxApp.Append(","); - minApp.Append(","); - } - } - // lo wind chill - if (cumulus.GraphOptions.WCVisible) - { - if (DayFile[i].LowWindChill < 999) - windChill.Append($"[{recDate},{DayFile[i].LowWindChill.ToString(cumulus.TempFormat, InvC)}]"); - else - windChill.Append($"[{recDate},null]"); - - if (i < len) - windChill.Append(","); - } - - if (cumulus.GraphOptions.DPVisible) - { - // hi dewpt - if (DayFile[i].HighDewPoint > -999) - maxDew.Append($"[{recDate},{DayFile[i].HighDewPoint.ToString(cumulus.TempFormat, InvC)}]"); - else - maxDew.Append($"[{recDate},null]"); - - // lo dewpt - if (DayFile[i].LowDewPoint < 999) - minDew.Append($"[{recDate},{DayFile[i].LowDewPoint.ToString(cumulus.TempFormat, InvC)}]"); - else - minDew.Append($"[{recDate},null]"); - - if (i < len) - { - maxDew.Append(","); - minDew.Append(","); - } - } - - if (cumulus.GraphOptions.FeelsLikeVisible) - { - // hi feels like - if (DayFile[i].HighFeelsLike > -999) - maxFeels.Append($"[{recDate},{DayFile[i].HighFeelsLike.ToString(cumulus.TempFormat, InvC)}]"); - else - maxFeels.Append($"[{recDate},null]"); - - // lo feels like - if (DayFile[i].LowFeelsLike < 999) - minFeels.Append($"[{recDate},{DayFile[i].LowFeelsLike.ToString(cumulus.TempFormat, InvC)}]"); - else - minFeels.Append($"[{recDate},null]"); - - if (i < len) - { - maxFeels.Append(","); - minFeels.Append(","); - } - } - - if (cumulus.GraphOptions.HumidexVisible) - { - // hi humidex - if (DayFile[i].HighHumidex > -999) - humidex.Append($"[{recDate},{DayFile[i].HighHumidex.ToString(cumulus.TempFormat, InvC)}]"); - else - humidex.Append($"[{recDate},null]"); - - if (i < len) - humidex.Append(","); - } - } - } - if (cumulus.GraphOptions.DailyMinTempVisible) - sb.Append("\"minTemp\":" + minTemp.ToString() + "],"); - if (cumulus.GraphOptions.DailyMaxTempVisible) - sb.Append("\"maxTemp\":" + maxTemp.ToString() + "],"); - if (cumulus.GraphOptions.DailyAvgTempVisible) - sb.Append("\"avgTemp\":" + avgTemp.ToString() + "],"); - if (cumulus.GraphOptions.HIVisible) - sb.Append("\"heatIndex\":" + heatIdx.ToString() + "],"); - if (cumulus.GraphOptions.AppTempVisible) - { - sb.Append("\"maxApp\":" + maxApp.ToString() + "],"); - sb.Append("\"minApp\":" + minApp.ToString() + "],"); - } - if (cumulus.GraphOptions.WCVisible) - sb.Append("\"windChill\":" + windChill.ToString() + "],"); - if (cumulus.GraphOptions.DPVisible) - { - sb.Append("\"maxDew\":" + maxDew.ToString() + "],"); - sb.Append("\"minDew\":" + minDew.ToString() + "],"); - } - if (cumulus.GraphOptions.FeelsLikeVisible) - { - sb.Append("\"maxFeels\":" + maxFeels.ToString() + "],"); - sb.Append("\"minFeels\":" + minFeels.ToString() + "],"); - } - if (cumulus.GraphOptions.HumidexVisible) - sb.Append("\"humidex\":" + humidex.ToString() + "],"); - - sb.Length--; - sb.Append("}"); - - return sb.ToString(); - } - - public string GetAllDailyWindGraphData() - { - var InvC = new CultureInfo(""); - - /* returns: - * { - * highgust:[[date1,val1],[date2,val2]...], - * mintemp:[[date1,val1],[date2,val2]...], - * etc - * } - */ - - StringBuilder sb = new StringBuilder("{"); - StringBuilder maxGust = new StringBuilder("["); - StringBuilder windRun = new StringBuilder("["); - StringBuilder maxWind = new StringBuilder("["); - - // Read the day file list and extract the data from there - if (DayFile.Count() > 0) - { - var len = DayFile.Count() - 1; - - for (var i = 0; i < DayFile.Count(); i++) - { - var recDate = DateTimeToUnix(DayFile[i].Date) * 1000; - - // hi gust - maxGust.Append($"[{recDate},{DayFile[i].HighGust.ToString(cumulus.WindFormat, InvC)}]"); - // hi wind run - windRun.Append($"[{recDate},{DayFile[i].WindRun.ToString(cumulus.WindRunFormat, InvC)}]"); - // hi wind - maxWind.Append($"[{recDate},{DayFile[i].HighAvgWind.ToString(cumulus.WindAvgFormat, InvC)}]"); - - if (i < len) - { - maxGust.Append(","); - windRun.Append(","); - maxWind.Append(","); - } - } - } - sb.Append("\"maxGust\":" + maxGust.ToString() + "],"); - sb.Append("\"windRun\":" + windRun.ToString() + "],"); - sb.Append("\"maxWind\":" + maxWind.ToString() + "]"); - sb.Append("}"); - - return sb.ToString(); - } - - public string GetAllDailyRainGraphData() - { - var InvC = new CultureInfo(""); - - /* returns: - * { - * highgust:[[date1,val1],[date2,val2]...], - * mintemp:[[date1,val1],[date2,val2]...], - * etc - * } - */ - - StringBuilder sb = new StringBuilder("{"); - StringBuilder maxRRate = new StringBuilder("["); - StringBuilder rain = new StringBuilder("["); - - // Read the day file list and extract the data from there - if (DayFile.Count() > 0) - { - var len = DayFile.Count() - 1; - - for (var i = 0; i < DayFile.Count(); i++) - { - - long recDate = DateTimeToUnix(DayFile[i].Date) * 1000; - - // hi rain rate - maxRRate.Append($"[{recDate},{DayFile[i].HighRainRate.ToString(cumulus.RainFormat, InvC)}]"); - // total rain - rain.Append($"[{recDate},{DayFile[i].TotalRain.ToString(cumulus.RainFormat, InvC)}]"); - - if (i < len) - { - maxRRate.Append(","); - rain.Append(","); - } - } - } - sb.Append("\"maxRainRate\":" + maxRRate.ToString() + "],"); - sb.Append("\"rain\":" + rain.ToString() + "]"); - sb.Append("}"); - - return sb.ToString(); - } - - public string GetAllDailyPressGraphData() - { - var InvC = new CultureInfo(""); - - /* returns: - * { - * highgust:[[date1,val1],[date2,val2]...], - * mintemp:[[date1,val1],[date2,val2]...], - * etc - * } - */ - - StringBuilder sb = new StringBuilder("{"); - StringBuilder minBaro = new StringBuilder("["); - StringBuilder maxBaro = new StringBuilder("["); - - - // Read the day file list and extract the data from there - if (DayFile.Count() > 0) - { - var len = DayFile.Count() - 1; - - for (var i = 0; i < DayFile.Count(); i++) - { - - long recDate = DateTimeToUnix(DayFile[i].Date) * 1000; - - // lo baro - minBaro.Append($"[{recDate},{DayFile[i].LowPress.ToString(cumulus.PressFormat, InvC)}]"); - // hi baro - maxBaro.Append($"[{recDate},{DayFile[i].HighPress.ToString(cumulus.PressFormat, InvC)}]"); - - if (i < len) - { - maxBaro.Append(","); - minBaro.Append(","); - } - } - } - sb.Append("\"minBaro\":" + minBaro.ToString() + "],"); - sb.Append("\"maxBaro\":" + maxBaro.ToString() + "]"); - sb.Append("}"); - - return sb.ToString(); - } - - //public string GetAllDailyWindDirGraphData() - //{ - // int linenum = 0; - // int valInt; - - // /* returns: - // * { - // * highgust:[[date1,val1],[date2,val2]...], - // * mintemp:[[date1,val1],[date2,val2]...], - // * etc - // * } - // */ - - // StringBuilder sb = new StringBuilder("{"); - // StringBuilder windDir = new StringBuilder("["); - - // var watch = 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++; - // List st = new List(Regex.Split(line, CultureInfo.CurrentCulture.TextInfo.ListSeparator)); - - // if (st.Count <= 0) continue; - - // // dominant wind direction - // if (st.Count > 39) - // { - // long recDate = DateTimeToUnix(ddmmyyStrToDate(st[0])) * 1000; - - // if (int.TryParse(st[39], out valInt)) - // windDir.Append($"[{recDate},{valInt}]"); - // else - // windDir.Append($"[{recDate},null]"); - // if (linenum < dayfile.Length) - // windDir.Append(","); - // } - // } - // } - // catch (Exception e) - // { - // cumulus.LogMessage("GetAllDailyWindDirGraphData: Error on line " + linenum + " of " + cumulus.DayFile + ": " + e.Message); - // } - // } - // sb.Append("\"windDir\":" + windDir.ToString() + "]"); - // sb.Append("}"); - - // watch.Stop(); - // cumulus.LogDebugMessage($"GetAllDailyWindDirGraphData: Dayfile parse = {watch.ElapsedMilliseconds} ms"); - - // return sb.ToString(); - //} - - public string GetAllDailyHumGraphData() - { - /* returns: - * { - * highgust:[[date1,val1],[date2,val2]...], - * mintemp:[[date1,val1],[date2,val2]...], - * etc - * } - */ - - StringBuilder sb = new StringBuilder("{"); - StringBuilder minHum = new StringBuilder("["); - StringBuilder maxHum = new StringBuilder("["); - - // Read the day file list and extract the data from there - if (DayFile.Count() > 0) - { - var len = DayFile.Count() - 1; - - for (var i = 0; i < DayFile.Count(); i++) - { - - long recDate = DateTimeToUnix(DayFile[i].Date) * 1000; - - // lo humidity - minHum.Append($"[{recDate},{DayFile[i].LowHumidity}]"); - // hi humidity - maxHum.Append($"[{recDate},{DayFile[i].HighHumidity}]"); - - if (i < len) - { - minHum.Append(","); - maxHum.Append(","); - } - } - } - sb.Append("\"minHum\":" + minHum.ToString() + "],"); - sb.Append("\"maxHum\":" + maxHum.ToString() + "]"); - sb.Append("}"); - - return sb.ToString(); - } - - public string GetAllDailySolarGraphData() - { - var InvC = new CultureInfo(""); - - /* returns: - * { - * highgust:[[date1,val1],[date2,val2]...], - * mintemp:[[date1,val1],[date2,val2]...], - * etc - * } - */ - - StringBuilder sb = new StringBuilder("{"); - StringBuilder sunHours = new StringBuilder("["); - StringBuilder solarRad = new StringBuilder("["); - StringBuilder uvi = new StringBuilder("["); - - // Read the day file list and extract the data from there - if (DayFile.Count() > 0) - { - var len = DayFile.Count() - 1; - - for (var i = 0; i < DayFile.Count(); i++) - { - long recDate = DateTimeToUnix(DayFile[i].Date) * 1000; - - if (cumulus.GraphOptions.SunshineVisible) - { - // sunshine hours - sunHours.Append($"[{recDate},{DayFile[i].SunShineHours.ToString(InvC)}]"); - if (i < len) - sunHours.Append(","); - } - - if (cumulus.GraphOptions.SolarVisible) - { - // hi solar rad - solarRad.Append($"[{recDate},{DayFile[i].HighSolar}]"); - if (i < len) - solarRad.Append(","); - } - - if (cumulus.GraphOptions.UVVisible) - { - // hi UV-I - uvi.Append($"[{recDate},{DayFile[i].HighUv.ToString(cumulus.UVFormat, InvC)}]"); - if (i < len) - uvi.Append(","); - } - } - } - if (cumulus.GraphOptions.SunshineVisible) - sb.Append("\"sunHours\":" + sunHours.ToString() + "]"); - - if (cumulus.GraphOptions.SolarVisible) - { - if (cumulus.GraphOptions.SunshineVisible) - sb.Append(","); - - sb.Append("\"solarRad\":" + solarRad.ToString() + "]"); - } - - if (cumulus.GraphOptions.UVVisible) - { - if (cumulus.GraphOptions.SunshineVisible || cumulus.GraphOptions.SolarVisible) - sb.Append(","); - - sb.Append("\"uvi\":" + uvi.ToString() + "]"); - } - sb.Append("}"); - - return sb.ToString(); - } - - public string GetAllDegreeDaysGraphData() - { - var InvC = new CultureInfo(""); - - StringBuilder sb = new StringBuilder("{"); - StringBuilder growdegdaysYears1 = new StringBuilder("{", 32768); - StringBuilder growdegdaysYears2 = new StringBuilder("{", 32768); - - StringBuilder growYear1 = new StringBuilder("[", 8600); - StringBuilder growYear2 = new StringBuilder("[", 8600); - - var options = $"\"options\":{{\"gddBase1\":{cumulus.GrowingBase1},\"gddBase2\":{cumulus.GrowingBase2},\"startMon\":{cumulus.GrowingYearStarts}}}"; - - DateTime nextYear; - - // 2000 was a leap year, so make sure February falls in 2000 - // for Southern hemisphere this means the start year must be 1999 - var plotYear = cumulus.GrowingYearStarts < 3 ? 2000 : 1999; - - int startYear; - - var annualGrowingDegDays1 = 0.0; - var annualGrowingDegDays2 = 0.0; - - // Read the day file list and extract the data from there - if (DayFile.Count() > 0 && (cumulus.GraphOptions.GrowingDegreeDaysVisible1 || cumulus.GraphOptions.GrowingDegreeDaysVisible2)) - { - // we have to detect a new growing deg day year is starting - nextYear = new DateTime(DayFile[0].Date.Year, cumulus.GrowingYearStarts, 1); - - if (DayFile[0].Date >= nextYear) - { - nextYear = nextYear.AddYears(1); - } - - // are we starting part way through a year that does not start in January? - if (DayFile[0].Date.Year == nextYear.Year) - { - startYear = DayFile[0].Date.Year - 1; - } - else - { - startYear = DayFile[0].Date.Year; - } - - if (cumulus.GraphOptions.GrowingDegreeDaysVisible1) - { - growdegdaysYears1.Append($"\"{startYear}\":"); - } - if (cumulus.GraphOptions.GrowingDegreeDaysVisible2) - { - growdegdaysYears2.Append($"\"{startYear}\":"); - } - - - for (var i = 0; i < DayFile.Count(); i++) - { - // we have rolled over into a new GDD year, write out what we have and reset - if (DayFile[i].Date >= nextYear) - { - if (cumulus.GraphOptions.GrowingDegreeDaysVisible1 && growYear1.Length > 10) - { - // remove last comma - growYear1.Length--; - // close the year data - growYear1.Append("],"); - // apend to years array - growdegdaysYears1.Append(growYear1); - - growYear1.Clear().Append($"\"{DayFile[i].Date.Year}\":["); - } - if (cumulus.GraphOptions.GrowingDegreeDaysVisible2 && growYear2.Length > 10) - { - // remove last comma - growYear2.Length--; - // close the year data - growYear2.Append("],"); - // apend to years array - growdegdaysYears2.Append(growYear2); - - growYear2.Clear().Append($"\"{DayFile[i].Date.Year}\":["); - } - - // reset the plot year for Southern hemi - plotYear = cumulus.GrowingYearStarts < 3 ? 2000 : 1999; - - annualGrowingDegDays1 = 0; - annualGrowingDegDays2 = 0; - do - { - nextYear = nextYear.AddYears(1); - } - while (DayFile[i].Date >= nextYear); - } - - // make all series the same year so they plot together - // 2000 was a leap year, so make sure February falls in 2000 - // for Southern hemisphere this means the start year must be 1999 - if (cumulus.GrowingYearStarts > 2 && plotYear == 1999 && DayFile[i].Date.Month == 1) - { - plotYear++; - } - - // make all series the same year so they plot together - long recDate = DateTimeToUnix(new DateTime(plotYear, DayFile[i].Date.Month, DayFile[i].Date.Day)) * 1000; - - if (cumulus.GraphOptions.GrowingDegreeDaysVisible1) - { - // growing degree days - var gdd = MeteoLib.GrowingDegreeDays(ConvertUserTempToC(DayFile[i].HighTemp), ConvertUserTempToC(DayFile[i].LowTemp), ConvertUserTempToC(cumulus.GrowingBase1), cumulus.GrowingCap30C); - - // annual accumulation - annualGrowingDegDays1 += gdd; - - growYear1.Append($"[{recDate},{annualGrowingDegDays1.ToString("F1", InvC)}],"); - } - - if (cumulus.GraphOptions.GrowingDegreeDaysVisible2) - { - // growing degree days - var gdd = MeteoLib.GrowingDegreeDays(ConvertUserTempToC(DayFile[i].HighTemp), ConvertUserTempToC(DayFile[i].LowTemp), ConvertUserTempToC(cumulus.GrowingBase2), cumulus.GrowingCap30C); - - // annual accumulation - annualGrowingDegDays2 += gdd; - - growYear2.Append($"[{recDate},{annualGrowingDegDays2.ToString("F1", InvC)}],"); - } - } - } - - // remove last commas from the years arrays and close them off - if (cumulus.GraphOptions.GrowingDegreeDaysVisible1) - { - if (growYear1[growYear1.Length - 1] == ',') - { - growYear1.Length--; - } - - // have previous years been appended? - if (growdegdaysYears1[growdegdaysYears1.Length - 1] == ']') - { - growdegdaysYears1.Append(","); - } - - growdegdaysYears1.Append(growYear1 + "]"); - - // add to main json - sb.Append("\"GDD1\":" + growdegdaysYears1 + "},"); - } - if (cumulus.GraphOptions.GrowingDegreeDaysVisible2) - { - if (growYear2[growYear2.Length - 1] == ',') - { - growYear2.Length--; - } - - // have previous years been appended? - if (growdegdaysYears2[growdegdaysYears2.Length - 1] == ']') - { - growdegdaysYears2.Append(","); - } - growdegdaysYears2.Append(growYear2 + "]"); - - // add to main json - sb.Append("\"GDD2\":" + growdegdaysYears2 + "},"); - } - - sb.Append(options); - - sb.Append("}"); - - return sb.ToString(); - } - - public string GetAllTempSumGraphData() - { - var InvC = new CultureInfo(""); - - StringBuilder sb = new StringBuilder("{"); - StringBuilder tempSumYears0 = new StringBuilder("{", 32768); - StringBuilder tempSumYears1 = new StringBuilder("{", 32768); - StringBuilder tempSumYears2 = new StringBuilder("{", 32768); - - StringBuilder tempSum0 = new StringBuilder("[", 8600); - StringBuilder tempSum1 = new StringBuilder("[", 8600); - StringBuilder tempSum2 = new StringBuilder("[", 8600); - - DateTime nextYear; - - // 2000 was a leap year, so make sure February falls in 2000 - // for Southern hemisphere this means the start year must be 1999 - var plotYear = cumulus.TempSumYearStarts < 3 ? 2000 : 1999; - - int startYear; - var annualTempSum0 = 0.0; - var annualTempSum1 = 0.0; - var annualTempSum2 = 0.0; - - var options = $"\"options\":{{\"sumBase1\":{cumulus.TempSumBase1},\"sumBase2\":{cumulus.TempSumBase2},\"startMon\":{cumulus.TempSumYearStarts}}}"; - - // Read the day file list and extract the data from there - if (DayFile.Count() > 0 && (cumulus.GraphOptions.TempSumVisible0 || cumulus.GraphOptions.TempSumVisible1 || cumulus.GraphOptions.TempSumVisible2)) - { - // we have to detect a new year is starting - nextYear = new DateTime(DayFile[0].Date.Year, cumulus.TempSumYearStarts, 1); - - if (DayFile[0].Date >= nextYear) - { - nextYear = nextYear.AddYears(1); - } - - // are we starting part way through a year that does not start in January? - if (DayFile[0].Date.Year == nextYear.Year) - { - startYear = DayFile[0].Date.Year - 1; - } - else - { - startYear = DayFile[0].Date.Year; - } - - if (cumulus.GraphOptions.TempSumVisible0) - { - tempSumYears0.Append($"\"{startYear}\":"); - } - if (cumulus.GraphOptions.TempSumVisible1) - { - tempSumYears1.Append($"\"{startYear}\":"); - } - if (cumulus.GraphOptions.TempSumVisible2) - { - tempSumYears2.Append($"\"{startYear}\":"); - } - - for (var i = 0; i < DayFile.Count(); i++) - { - // we have rolled over into a new GDD year, write out what we have and reset - if (DayFile[i].Date >= nextYear) - { - if (cumulus.GraphOptions.TempSumVisible0 && tempSum0.Length > 10) - { - // remove last comma - tempSum0.Length--; - // close the year data - tempSum0.Append("],"); - // apend to years array - tempSumYears0.Append(tempSum0); - - tempSum0.Clear().Append($"\"{DayFile[i].Date.Year}\":["); - } - if (cumulus.GraphOptions.TempSumVisible1 && tempSum1.Length > 10) - { - // remove last comma - tempSum1.Length--; - // close the year data - tempSum1.Append("],"); - // apend to years array - tempSumYears1.Append(tempSum1); - - tempSum1.Clear().Append($"\"{DayFile[i].Date.Year}\":["); - } - if (cumulus.GraphOptions.TempSumVisible2 && tempSum2.Length > 10) - { - // remove last comma - tempSum2.Length--; - // close the year data - tempSum2.Append("],"); - // apend to years array - tempSumYears2.Append(tempSum2); - - tempSum2.Clear().Append($"\"{DayFile[i].Date.Year}\":["); - } - - // reset the plot year for Southern hemi - plotYear = cumulus.TempSumYearStarts < 3 ? 2000 : 1999; - - annualTempSum0 = 0; - annualTempSum1 = 0; - annualTempSum2 = 0; - - do - { - nextYear = nextYear.AddYears(1); - } - while (DayFile[i].Date >= nextYear); - } - // make all series the same year so they plot together - // 2000 was a leap year, so make sure February falls in 2000 - // for Southern hemisphere this means the start year must be 1999 - if (cumulus.TempSumYearStarts > 2 && plotYear == 1999 && DayFile[i].Date.Month == 1) - { - plotYear++; - } - - long recDate = DateTimeToUnix(new DateTime(plotYear, DayFile[i].Date.Month, DayFile[i].Date.Day)) * 1000; - - if (cumulus.GraphOptions.TempSumVisible0) - { - // annual accumulation - annualTempSum0 += DayFile[i].AvgTemp; - tempSum0.Append($"[{recDate},{annualTempSum0.ToString("F0", InvC)}],"); - } - if (cumulus.GraphOptions.TempSumVisible1) - { - // annual accumulation - annualTempSum1 += DayFile[i].AvgTemp - cumulus.TempSumBase1; - tempSum1.Append($"[{recDate},{annualTempSum1.ToString("F0", InvC)}],"); - } - if (cumulus.GraphOptions.TempSumVisible2) - { - // annual accumulation - annualTempSum2 += DayFile[i].AvgTemp - cumulus.TempSumBase2; - tempSum2.Append($"[{recDate},{annualTempSum2.ToString("F0", InvC)}],"); - } - } - } - - // remove last commas from the years arrays and close them off - if (cumulus.GraphOptions.TempSumVisible0) - { - if (tempSum0[tempSum0.Length - 1] == ',') - { - tempSum0.Length--; - } - - // have previous years been appended? - if (tempSumYears0[tempSumYears0.Length - 1] == ']') - { - tempSumYears0.Append(","); - } - - tempSumYears0.Append(tempSum0 + "]"); - - // add to main json - sb.Append("\"Sum0\":" + tempSumYears0 + "}"); - - if (cumulus.GraphOptions.TempSumVisible1 || cumulus.GraphOptions.TempSumVisible2) - sb.Append(","); - } - if (cumulus.GraphOptions.TempSumVisible1) - { - if (tempSum1[tempSum1.Length - 1] == ',') - { - tempSum1.Length--; - } - - // have previous years been appended? - if (tempSumYears1[tempSumYears1.Length - 1] == ']') - { - tempSumYears1.Append(","); - } - - tempSumYears1.Append(tempSum1 + "]"); - - // add to main json - sb.Append("\"Sum1\":" + tempSumYears1 + "},"); - } - if (cumulus.GraphOptions.TempSumVisible2) - { - if (tempSum2[tempSum2.Length - 1] == ',') - { - tempSum2.Length--; - } - - // have previous years been appended? - if (tempSumYears2[tempSumYears2.Length - 1] == ']') - { - tempSumYears2.Append(","); - } - - tempSumYears2.Append(tempSum2 + "]"); - - // add to main json - sb.Append("\"Sum2\":" + tempSumYears2 + "},"); - } - - sb.Append(options); - - sb.Append("}"); - - return sb.ToString(); - } - - - - internal string GetCurrentData() - { - StringBuilder windRoseData = new StringBuilder((windcounts[0] * cumulus.Calib.WindGust.Mult).ToString(cumulus.WindFormat, CultureInfo.InvariantCulture), 4096); - lock (windRoseData) - { - for (var i = 1; i < cumulus.NumWindRosePoints; i++) - { - windRoseData.Append(","); - windRoseData.Append((windcounts[i] * cumulus.Calib.WindGust.Mult).ToString(cumulus.WindFormat, CultureInfo.InvariantCulture)); - } - } - string stormRainStart = StartOfStorm == DateTime.MinValue ? "-----" : StartOfStorm.ToString("d"); - - var data = new DataStruct(cumulus, OutdoorTemperature, OutdoorHumidity, TempTotalToday / tempsamplestoday, IndoorTemperature, OutdoorDewpoint, WindChill, IndoorHumidity, - Pressure, WindLatest, WindAverage, RecentMaxGust, WindRunToday, Bearing, AvgBearing, RainToday, RainYesterday, RainMonth, RainYear, RainRate, - RainLastHour, HeatIndex, Humidex, ApparentTemperature, temptrendval, presstrendval, HiLoToday.HighGust, HiLoToday.HighGustTime.ToString("HH:mm"), HiLoToday.HighWind, - HiLoToday.HighGustBearing, cumulus.Units.WindText, BearingRangeFrom10, BearingRangeTo10, windRoseData.ToString(), HiLoToday.HighTemp, HiLoToday.LowTemp, - HiLoToday.HighTempTime.ToString("HH:mm"), HiLoToday.LowTempTime.ToString("HH:mm"), HiLoToday.HighPress, HiLoToday.LowPress, HiLoToday.HighPressTime.ToString("HH:mm"), - HiLoToday.LowPressTime.ToString("HH:mm"), HiLoToday.HighRainRate, HiLoToday.HighRainRateTime.ToString("HH:mm"), HiLoToday.HighHumidity, HiLoToday.LowHumidity, - HiLoToday.HighHumidityTime.ToString("HH:mm"), HiLoToday.LowHumidityTime.ToString("HH:mm"), cumulus.Units.PressText, cumulus.Units.TempText, cumulus.Units.RainText, - HiLoToday.HighDewPoint, HiLoToday.LowDewPoint, HiLoToday.HighDewPointTime.ToString("HH:mm"), HiLoToday.LowDewPointTime.ToString("HH:mm"), HiLoToday.LowWindChill, - HiLoToday.LowWindChillTime.ToString("HH:mm"), (int)SolarRad, (int)HiLoToday.HighSolar, HiLoToday.HighSolarTime.ToString("HH:mm"), UV, HiLoToday.HighUv, - HiLoToday.HighUvTime.ToString("HH:mm"), forecaststr, getTimeString(cumulus.SunRiseTime), getTimeString(cumulus.SunSetTime), - getTimeString(cumulus.MoonRiseTime), getTimeString(cumulus.MoonSetTime), HiLoToday.HighHeatIndex, HiLoToday.HighHeatIndexTime.ToString("HH:mm"), HiLoToday.HighAppTemp, - HiLoToday.LowAppTemp, HiLoToday.HighAppTempTime.ToString("HH:mm"), HiLoToday.LowAppTempTime.ToString("HH:mm"), (int)Math.Round(CurrentSolarMax), - AllTime.HighPress.Val, AllTime.LowPress.Val, SunshineHours, CompassPoint(DominantWindBearing), LastRainTip, - HiLoToday.HighHourlyRain, HiLoToday.HighHourlyRainTime.ToString("HH:mm"), "F" + cumulus.Beaufort(HiLoToday.HighWind), "F" + cumulus.Beaufort(WindAverage), - cumulus.BeaufortDesc(WindAverage), LastDataReadTimestamp.ToString("HH:mm:ss"), DataStopped, StormRain, stormRainStart, CloudBase, cumulus.CloudBaseInFeet ? "ft" : "m", RainLast24Hour, - cumulus.LowTempAlarm.Triggered, cumulus.HighTempAlarm.Triggered, cumulus.TempChangeAlarm.UpTriggered, cumulus.TempChangeAlarm.DownTriggered, cumulus.HighRainTodayAlarm.Triggered, cumulus.HighRainRateAlarm.Triggered, - cumulus.LowPressAlarm.Triggered, cumulus.HighPressAlarm.Triggered, cumulus.PressChangeAlarm.UpTriggered, cumulus.PressChangeAlarm.DownTriggered, cumulus.HighGustAlarm.Triggered, cumulus.HighWindAlarm.Triggered, - cumulus.SensorAlarm.Triggered, cumulus.BatteryLowAlarm.Triggered, cumulus.SpikeAlarm.Triggered, cumulus.UpgradeAlarm.Triggered, cumulus.HttpUploadAlarm.Triggered, cumulus.MySqlUploadAlarm.Triggered, - FeelsLike, HiLoToday.HighFeelsLike, HiLoToday.HighFeelsLikeTime.ToString("HH:mm:ss"), HiLoToday.LowFeelsLike, HiLoToday.LowFeelsLikeTime.ToString("HH:mm:ss"), - HiLoToday.HighHumidex, HiLoToday.HighHumidexTime.ToString("HH:mm:ss")); - - try - { - using (MemoryStream stream = new MemoryStream()) - { - DataContractJsonSerializer ds = new DataContractJsonSerializer(typeof(DataStruct)); - DataContractJsonSerializerSettings s = new DataContractJsonSerializerSettings(); - ds.WriteObject(stream, data); - string jsonString = Encoding.UTF8.GetString(stream.ToArray()); - stream.Close(); - return jsonString; - } - } - catch (Exception ex) - { - cumulus.LogConsoleMessage(ex.Message); - return ""; - } - - } - - // Returns true if the gust value exceeds current RecentMaxGust, false if it fails - public bool CheckHighGust(double gust, int gustdir, DateTime timestamp) - { - // Spike check is in m/s - var windGustMS = ConvertUserWindToMS(gust); - if (((previousGust != 999) && (Math.Abs(windGustMS - previousGust) > cumulus.Spike.GustDiff)) || windGustMS >= cumulus.Limit.WindHigh) - { - cumulus.LogSpikeRemoval("Wind Gust difference greater than specified; reading ignored"); - cumulus.LogSpikeRemoval($"Gust: NewVal={windGustMS:F1} OldVal={previousGust:F1} SpikeGustDiff={cumulus.Spike.GustDiff:F1} HighLimit={cumulus.Limit.WindHigh:F1}"); - return false; - } - - if (gust > RecentMaxGust) - { - if (gust > HiLoToday.HighGust) - { - HiLoToday.HighGust = gust; - HiLoToday.HighGustTime = timestamp; - HiLoToday.HighGustBearing = gustdir; - WriteTodayFile(timestamp, false); - } - if (gust > ThisMonth.HighGust.Val) - { - ThisMonth.HighGust.Val = gust; - ThisMonth.HighGust.Ts = timestamp; - WriteMonthIniFile(); - } - if (gust > ThisYear.HighGust.Val) - { - ThisYear.HighGust.Val = gust; - ThisYear.HighGust.Ts = timestamp; - WriteYearIniFile(); - } - // All time high gust? - if (gust > AllTime.HighGust.Val) - { - SetAlltime(AllTime.HighGust, gust, timestamp); - } - - // check for monthly all time records (and set) - CheckMonthlyAlltime("HighGust", gust, true, timestamp); - } - return true; - } - - /// - /// Calculates the Davis version (almost) of Evapotranspiration - /// - /// ET for past hour in mm - /* - public double CalulateEvapotranspiration(DateTime ts) - { - var onehourago = ts.AddHours(-1); - - // Mean temperature in Fahrenheit - var result = RecentDataDb.Query("select avg(OutsideTemp) temp, avg(WindSpeed) wind, avg(SolarRad) solar, avg(Humidity) hum from RecentData where Timestamp >= ?", onehourago); - - var meanTempC = ConvertUserTempToC(result[0].temp); - var meanTempK = meanTempC + 273.16; - var meanWind = ConvertUserWindToMS(result[0].wind); - var meanHum = result[0].hum; - var meanSolar = result[0].solar; - var pressure = ConvertUserPressToMB(AltimeterPressure) / 100; // need kPa - - var satVapPress = MeteoLib.SaturationVapourPressure2008(meanTempC); - var waterVapour = satVapPress * meanHum / 100; - - var delta = satVapPress / meanTempK * ((6790.4985 / meanTempK) - 5.02808); - - var gamma = 0.000646 * (1 + 0.000946 * meanTempC) * pressure; - - var weighting = delta / (delta + gamma); - - double windFunc; - if (meanSolar > 0) - windFunc = 0.030 + 0.0576 * meanWind; - else - windFunc = 0.125 + 0.0439 * meanWind; - - var lambda = 69.5 * (1 - 0.000946 * meanTempC); - - //TODO: Need to calculate the net radiation rather than use meanSolar - need mean theoretical solar value for this - var meanSolarMax = 0.0; //TODO - - // clear sky - meanSolar/meanSolarMax >= 1, c <= 1, solar elevation > 10 deg - var clearSky = (1.333 - 1.333 * meanSolar / meanSolarMax); - - var netSolar = 0.0; //TODO - - var ET = weighting * netSolar / lambda + (1 - weighting) * (satVapPress - waterVapour) * windFunc; - - return ET; - } - */ - - - public void UpdateAPRS() - { - if (DataStopped) - { - // No data coming in, do nothing - return; - } - - cumulus.LogMessage("Updating CWOP"); - using (var client = new TcpClient(cumulus.APRS.Server, cumulus.APRS.Port)) - using (var ns = client.GetStream()) - { - try - { - using (StreamWriter writer = new StreamWriter(ns)) - { - StringBuilder message = new StringBuilder(256); - message.Append($"user {cumulus.APRS.ID} pass {cumulus.APRS.PW} vers Cumulus {cumulus.Version}"); - - //Byte[] data = Encoding.ASCII.GetBytes(message.ToString()); - - cumulus.LogDebugMessage("Sending user and pass to CWOP"); - - writer.WriteLine(message.ToString()); - writer.Flush(); - - Thread.Sleep(3000); - - string timeUTC = DateTime.Now.ToUniversalTime().ToString("ddHHmm"); - - message.Clear(); - message.Append($"{cumulus.APRS.ID}>APRS,TCPIP*:@{timeUTC}z{APRSLat(cumulus)}/{APRSLon(cumulus)}"); - // bearing _nnn - message.Append($"_{AvgBearing:D3}"); - // wind speed mph /nnn - message.Append($"/{APRSwind(WindAverage)}"); - // wind gust last 5 mins mph gnnn - message.Append($"g{APRSwind(RecentMaxGust)}"); - // temp F tnnn - message.Append($"t{APRStemp(OutdoorTemperature)}"); - // rain last hour 0.01 inches rnnn - message.Append($"r{APRSrain(RainLastHour)}"); - // rain last 24 hours 0.01 inches pnnn - message.Append($"p{APRSrain(RainLast24Hour)}"); - message.Append("P"); - if (cumulus.RolloverHour == 0) - { - // use today"s rain for safety - message.Append(APRSrain(RainToday)); - } - else - { - // 0900 day, use midnight calculation - message.Append(APRSrain(RainSinceMidnight)); - } - if ((!cumulus.APRS.HumidityCutoff) || (ConvertUserTempToC(OutdoorTemperature) >= -10)) - { - // humidity Hnn - message.Append($"h{APRShum(OutdoorHumidity)}"); - } - // bar 0.1mb Bnnnnn - message.Append($"b{APRSpress(AltimeterPressure)}"); - if (cumulus.APRS.SendSolar) - { - message.Append(APRSsolarradStr(Convert.ToInt32(SolarRad))); - } - - // station type e - message.Append($"eCumulus{cumulus.APRSstationtype[cumulus.StationType]}"); - - cumulus.LogDebugMessage($"Sending: {message}"); - - //data = Encoding.ASCII.GetBytes(message.ToString()); - - writer.WriteLine(message.ToString()); - writer.Flush(); - - Thread.Sleep(3000); - writer.Close(); - } - cumulus.LogDebugMessage("End of CWOP update"); - } - catch (Exception e) - { - cumulus.LogMessage("CWOP error: " + e.Message); - } - } - } - - /// - /// Takes latitude in degrees and converts it to APRS format ddmm.hhX: - /// (hh = hundredths of a minute) - /// e.g. 5914.55N - /// - /// - private string APRSLat(Cumulus cumulus) - { - string dir; - double lat; - int d, m, s; - if (cumulus.Latitude < 0) - { - lat = -cumulus.Latitude; - dir = "S"; - } - else - { - lat = cumulus.Latitude; - dir = "N"; - } - - cumulus.DegToDMS(lat, out d, out m, out s); - int hh = (int) Math.Round(s*100/60.0); - - return String.Format("{0:D2}{1:D2}.{2:D2}{3}", d, m, hh, dir); - } - - /// - /// Takes longitude in degrees and converts it to APRS format dddmm.hhX: - /// (hh = hundredths of a minute) - /// e.g. 15914.55W - /// - /// - private string APRSLon(Cumulus cumulus) - { - string dir; - double lon; - int d, m, s; - if (cumulus.Longitude < 0) - { - lon = -cumulus.Longitude; - dir = "W"; - } - else - { - lon = cumulus.Longitude; - dir = "E"; - } - - cumulus.DegToDMS(lon, out d, out m, out s); - int hh = (int) Math.Round(s*100/60.0); - - return String.Format("{0:D3}{1:D2}.{2:D2}{3}", d, m, hh, dir); - } - - /// - /// input is in Units.Wind units, convert to mph for APRS - /// and return 3 digits - /// - /// - /// - private string APRSwind(double wind) - { - var windMPH = Convert.ToInt32(ConvertUserWindToMPH(wind)); - return windMPH.ToString("D3"); - } - - /// - /// input is in Units.Press units, convert to tenths of mb for APRS - /// return 5 digit string - /// - /// - /// - public string APRSpress(double press) - { - var press10mb = Convert.ToInt32(ConvertUserPressToMB(press) * 10); - return press10mb.ToString("D5"); - } - - /// - /// return humidity as 2-digit string - /// represent 100 by 00 - /// send 1 instead of zero - /// - /// - /// - public string APRShum(int hum) - { - if (hum == 100) - { - return "00"; - } - - if (hum == 0) - { - return "01"; - } - - return hum.ToString("D2"); - } - - /// - /// input is in RainUnit units, convert to hundredths of inches for APRS - /// and return 3 digits - /// - /// - /// - public string APRSrain(double rain) - { - var rain100IN = Convert.ToInt32(ConvertUserRainToIN(rain) * 100); - return rain100IN.ToString("D3"); - } - - public class CommTimer : IDisposable - { - public Timer tmrComm = new Timer(); - public bool timedout = false; - public CommTimer() - { - timedout = false; - tmrComm.AutoReset = false; - tmrComm.Enabled = false; - tmrComm.Interval = 1000; //default to 1 second - tmrComm.Elapsed += new ElapsedEventHandler(OnTimedCommEvent); - } - - public void OnTimedCommEvent(object source, ElapsedEventArgs e) - { - timedout = true; - tmrComm.Stop(); - } - - public void Start(double timeoutperiod) - { - tmrComm.Interval = timeoutperiod; //time to time out in milliseconds - tmrComm.Stop(); - timedout = false; - tmrComm.Start(); - } - - public void Stop() - { - tmrComm.Stop(); - timedout = true; - } - - public void Dispose() - { - tmrComm.Close(); - tmrComm.Dispose(); - } - } - - } - - //public partial class CumulusData : DataContext - //{ - // public Table Datas; - // //public Table ExtraData; - // public CumulusData(string connection) : base(connection) { } - //} - - public class Last3HourData - { - public DateTime timestamp; - public double pressure; - public double temperature; - - public Last3HourData(DateTime ts, double press, double temp) - { - timestamp = ts; - pressure = press; - temperature = temp; - } - } - - public class LastHourData - { - public DateTime timestamp; - public double raincounter; - public double temperature; - - public LastHourData(DateTime ts, double rain, double temp) - { - timestamp = ts; - raincounter = rain; - temperature = temp; - } - } - - public class GraphData - { - public DateTime timestamp; - public double raincounter; - public double RainToday; - public double rainrate; - public double temperature; - public double dewpoint; - public double apptemp; - public double feelslike; - public double humidex; - public double windchill; - public double heatindex; - public double insidetemp; - public double pressure; - public double windspeed; - public double windgust; - public int avgwinddir; - public int winddir; - public int humidity; - public int inhumidity; - public double solarrad; - public double solarmax; - public double uvindex; - public double pm2p5; - public double pm10; - - public GraphData(DateTime ts, double rain, double raintoday, double rrate, double temp, double dp, double appt, double chill, double heat, double intemp, double press, - double speed, double gust, int avgdir, int wdir, int hum, int inhum, double solar, double smax, double uv, double feels, double humidx, double pm2p5, double pm10) - { - timestamp = ts; - raincounter = rain; - RainToday = raintoday; - rainrate = rrate; - temperature = temp; - dewpoint = dp; - apptemp = appt; - windchill = chill; - heatindex = heat; - insidetemp = intemp; - pressure = press; - windspeed = speed; - windgust = gust; - avgwinddir = avgdir; - winddir = wdir; - humidity = hum; - inhumidity = inhum; - solarrad = solar; - solarmax = smax; - uvindex = uv; - feelslike = feels; - humidex = humidx; - this.pm2p5 = pm2p5; - this.pm10 = pm10; - } - } - - public class Last10MinWind - { - public DateTime timestamp; - public double gust; - public double speed; - public double gustX; - public double gustY; - - public Last10MinWind(DateTime ts, double windgust, double windspeed, double Xgust, double Ygust) - { - timestamp = ts; - gust = windgust; - speed = windspeed; - gustX = Xgust; - gustY = Ygust; - } - } - - public class RecentDailyData - { - public DateTime timestamp; - public double rain; - public double sunhours; - public double mintemp; - public double maxtemp; - public double avgtemp; - - public RecentDailyData(DateTime ts, double dailyrain, double sunhrs, double mint, double maxt, double avgt) - { - timestamp = ts; - rain = dailyrain; - sunhours = sunhrs; - mintemp = mint; - maxtemp = maxt; - avgtemp = avgt; - } - } - - public class RecentData - { - [PrimaryKey] - public DateTime Timestamp { get; set; } - - public double WindSpeed { get; set; } - public double WindGust { get; set; } - public double WindLatest { get; set; } - public int WindDir { get; set; } - public int WindAvgDir { get; set; } - public double OutsideTemp { get; set; } - public double WindChill { get; set; } - public double DewPoint { get; set; } - public double HeatIndex { get; set; } - public double Humidity { get; set; } - public double Pressure { get; set; } - public double RainToday { get; set; } - public int SolarRad { get; set; } - public double UV { get; set; } - public double raincounter { get; set; } - public double FeelsLike { get; set; } - public double Humidex { get; set; } - } - - public class AvgData - { - public double temp { get; set; } - public double wind { get; set; } - public double solar { get; set; } - public double hum { get; set; } - } - - - /* - public class StandardData - { - [PrimaryKey] - public DateTime Timestamp { get; set; } - - public int Interval { get; set; } - - public double OutTemp { get; set; } - public double LoOutTemp { get; set; } - public double HiOutTemp { get; set; } - - public double DewPoint { get; set; } - public double LoDewPoint { get; set; } - public double HiDewPoint { get; set; } - - public double WindChill { get; set; } - public double LoWindChill { get; set; } - public double HiWindChill { get; set; } - - public double InTemp { get; set; } - public double LoInTemp { get; set; } - public double HiInTemp { get; set; } - - public double Pressure { get; set; } - public double LoPressure { get; set; } - public double HiPressure { get; set; } - } - */ - - public class AllTimeRecords - { - // Add an indexer so we can reference properties with a string - public AllTimeRec this[string propertyName] - { - get - { - // probably faster without reflection: - // like: return Properties.Settings.Default.PropertyValues[propertyName] - // instead of the following - Type myType = typeof(AllTimeRecords); - PropertyInfo myPropInfo = myType.GetProperty(propertyName); - return (AllTimeRec) myPropInfo.GetValue(this, null); - } - set - { - Type myType = typeof(AllTimeRecords); - PropertyInfo myPropInfo = myType.GetProperty(propertyName); - myPropInfo.SetValue(this, value, null); - } - } - - public AllTimeRec HighTemp { get; set; } = new AllTimeRec(0); - public AllTimeRec LowTemp { get; set; } = new AllTimeRec(1); - public AllTimeRec HighGust { get; set; } = new AllTimeRec(2); - public AllTimeRec HighWind { get; set; } = new AllTimeRec(3); - public AllTimeRec LowChill { get; set; } = new AllTimeRec(4); - public AllTimeRec HighRainRate { get; set; } = new AllTimeRec(5); - public AllTimeRec DailyRain { get; set; } = new AllTimeRec(6); - public AllTimeRec HourlyRain { get; set; } = new AllTimeRec(7); - public AllTimeRec LowPress { get; set; } = new AllTimeRec(8); - public AllTimeRec HighPress { get; set; } = new AllTimeRec(9); - public AllTimeRec MonthlyRain { get; set; } = new AllTimeRec(10); - public AllTimeRec HighMinTemp { get; set; } = new AllTimeRec(11); - public AllTimeRec LowMaxTemp { get; set; } = new AllTimeRec(12); - public AllTimeRec HighHumidity { get; set; } = new AllTimeRec(13); - public AllTimeRec LowHumidity { get; set; } = new AllTimeRec(14); - public AllTimeRec HighAppTemp { get; set; } = new AllTimeRec(15); - public AllTimeRec LowAppTemp { get; set; } = new AllTimeRec(16); - public AllTimeRec HighHeatIndex { get; set; } = new AllTimeRec(17); - public AllTimeRec HighDewPoint { get; set; } = new AllTimeRec(18); - public AllTimeRec LowDewPoint{ get; set; } = new AllTimeRec(19); - public AllTimeRec HighWindRun { get; set; } = new AllTimeRec(20); - public AllTimeRec LongestDryPeriod { get; set; } = new AllTimeRec(21); - public AllTimeRec LongestWetPeriod { get; set; } = new AllTimeRec(22); - public AllTimeRec HighDailyTempRange { get; set; } = new AllTimeRec(23); - public AllTimeRec LowDailyTempRange { get; set; } = new AllTimeRec(24); - public AllTimeRec HighFeelsLike { get; set; } = new AllTimeRec(25); - public AllTimeRec LowFeelsLike { get; set; } = new AllTimeRec(26); - public AllTimeRec HighHumidex { get; set; } = new AllTimeRec(27); - } - - public class AllTimeRec - { - private static string[] alltimedescs = new[] - { - "High temperature", "Low temperature", "High gust", "High wind speed", "Low wind chill", "High rain rate", "High daily rain", - "High hourly rain", "Low pressure", "High pressure", "Highest monthly rainfall", "Highest minimum temp", "Lowest maximum temp", - "High humidity", "Low humidity", "High apparent temp", "Low apparent temp", "High heat index", "High dew point", "Low dew point", - "High daily windrun", "Longest dry period", "Longest wet period", "High daily temp range", "Low daily temp range", - "High feels like", "Low feels like", "High Humidex" - }; - private int idx; - - public AllTimeRec(int index) - { - idx = index; - } - public double Val { get; set; } - public DateTime Ts { get; set; } - public string Desc - { - get - { - return alltimedescs[idx]; - } - } - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.IO.Ports; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Reflection; +using System.Runtime.Serialization.Json; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Timers; +using MySqlConnector; +using SQLite; +using Timer = System.Timers.Timer; +using ServiceStack.Text; +using System.Web; + +namespace CumulusMX +{ + internal abstract class WeatherStation + { + public struct TWindRecent + { + public double Gust; // uncalibrated "gust" as read from station + public double Speed; // uncalibrated "speed" as read from station + public DateTime Timestamp; + } + + public struct TWindVec + { + public double X; + public double Y; + public int Bearing; + public DateTime Timestamp; + } + + private readonly Object monthIniThreadLock = new Object(); + public readonly Object yearIniThreadLock = new Object(); + public readonly Object alltimeIniThreadLock = new Object(); + public readonly Object monthlyalltimeIniThreadLock = new Object(); + + // holds all time highs and lows + public AllTimeRecords AllTime = new AllTimeRecords(); + + // holds monthly all time highs and lows + private AllTimeRecords[] monthlyRecs = new AllTimeRecords[13]; + public AllTimeRecords[] MonthlyRecs + { + get + { + if (monthlyRecs == null) + { + monthlyRecs = new AllTimeRecords[13]; + } + + return monthlyRecs; + } + } + + public struct dayfilerec + { + public DateTime Date; + public double HighGust; + public int HighGustBearing; + public DateTime HighGustTime; + public double LowTemp; + public DateTime LowTempTime; + public double HighTemp; + public DateTime HighTempTime; + public double LowPress; + public DateTime LowPressTime; + public double HighPress; + public DateTime HighPressTime; + public double HighRainRate; + public DateTime HighRainRateTime; + public double TotalRain; + public double AvgTemp; + public double WindRun; + public double HighAvgWind; + public DateTime HighAvgWindTime; + public int LowHumidity; + public DateTime LowHumidityTime; + public int HighHumidity; + public DateTime HighHumidityTime; + public double ET; + public double SunShineHours; + public double HighHeatIndex; + public DateTime HighHeatIndexTime; + public double HighAppTemp; + public DateTime HighAppTempTime; + public double LowAppTemp; + public DateTime LowAppTempTime; + public double HighHourlyRain; + public DateTime HighHourlyRainTime; + public double LowWindChill; + public DateTime LowWindChillTime; + public double HighDewPoint; + public DateTime HighDewPointTime; + public double LowDewPoint; + public DateTime LowDewPointTime; + public int DominantWindBearing; + public double HeatingDegreeDays; + public double CoolingDegreeDays; + public int HighSolar; + public DateTime HighSolarTime; + public double HighUv; + public DateTime HighUvTime; + public double HighFeelsLike; + public DateTime HighFeelsLikeTime; + public double LowFeelsLike; + public DateTime LowFeelsLikeTime; + public double HighHumidex; + public DateTime HighHumidexTime; + } + + public List DayFile = new List(); + + + // this month highs and lows + public AllTimeRecords ThisMonth = new AllTimeRecords(); + + public AllTimeRecords ThisYear = new AllTimeRecords(); + + + //public DateTime lastArchiveTimeUTC; + + public string LatestFOReading { get; set; } + + //public int LastDailySummaryOADate; + + public Cumulus cumulus; + + private int lastMinute; + private int lastHour; + + public bool[] WMR928ChannelPresent = new[] { false, false, false, false }; + public bool[] WMR928ExtraTempValueOnly = new[] { false, false, false, false }; + public double[] WMR928ExtraTempValues = new[] { 0.0, 0.0, 0.0, 0.0 }; + public double[] WMR928ExtraDPValues = new[] { 0.0, 0.0, 0.0, 0.0 }; + public int[] WMR928ExtraHumValues = new[] { 0, 0, 0, 0 }; + + + public DateTime AlltimeRecordTimestamp { get; set; } + + //private ProgressWindow progressWindow; + + //public historyProgressWindow histprog; + public BackgroundWorker bw; + + //public bool importingData = false; + + public bool calculaterainrate = false; + + protected List buffer = new List(); + + private readonly List Last3HourDataList = new List(); + private readonly List LastHourDataList = new List(); + private readonly List GraphDataList = new List(); + private readonly List Last10MinWindList = new List(); +// private readonly List RecentDailyDataList = new List(); + + public WeatherDataCollection weatherDataCollection = new WeatherDataCollection(); + + // Current values + + public double THWIndex = 0; + public double THSWIndex = 0; + + public double raindaystart = 0.0; + public double Raincounter = 0.0; + public bool gotraindaystart = false; + protected double prevraincounter = 0.0; + + public struct DailyHighLow + { + public double HighGust; + public int HighGustBearing; + public DateTime HighGustTime; + public double HighWind; + public DateTime HighWindTime; + public double HighTemp; + public DateTime HighTempTime; + public double LowTemp; + public DateTime LowTempTime; + public double TempRange; + public double HighAppTemp; + public DateTime HighAppTempTime; + public double LowAppTemp; + public DateTime LowAppTempTime; + public double HighFeelsLike; + public DateTime HighFeelsLikeTime; + public double LowFeelsLike; + public DateTime LowFeelsLikeTime; + public double HighHumidex; + public DateTime HighHumidexTime; + public double HighPress; + public DateTime HighPressTime; + public double LowPress; + public DateTime LowPressTime; + public double HighRainRate; + public DateTime HighRainRateTime; + public double HighHourlyRain; + public DateTime HighHourlyRainTime; + public int HighHumidity; + public DateTime HighHumidityTime; + public int LowHumidity; + public DateTime LowHumidityTime; + public double HighHeatIndex; + public DateTime HighHeatIndexTime; + public double LowWindChill; + public DateTime LowWindChillTime; + public double HighDewPoint; + public DateTime HighDewPointTime; + public double LowDewPoint; + public DateTime LowDewPointTime; + public double HighSolar; + public DateTime HighSolarTime; + public double HighUv; + public DateTime HighUvTime; + + }; + + // today highs and lows + public DailyHighLow HiLoToday = new DailyHighLow() + { + HighTemp = -500, + HighAppTemp = -500, + HighFeelsLike = -500, + HighHumidex = -500, + HighHeatIndex = -500, + HighDewPoint = -500, + LowTemp = 999, + LowAppTemp = 999, + LowFeelsLike = 999, + LowWindChill = 999, + LowDewPoint = 999, + LowPress = 9999, + LowHumidity = 100 + }; + + // yesterdays highs and lows + public DailyHighLow HiLoYest = new DailyHighLow() + { + HighTemp = -500, + HighAppTemp = -500, + HighFeelsLike = -500, + HighHumidex = -500, + HighHeatIndex = -500, + HighDewPoint = -500, + LowTemp = 999, + LowAppTemp = 999, + LowFeelsLike = 999, + LowWindChill = 999, + LowDewPoint = 999, + LowPress = 9999, + LowHumidity = 100 + }; + + + public int IndoorBattStatus; + public int WindBattStatus; + public int RainBattStatus; + public int TempBattStatus; + public int UVBattStatus; + + public double[] WMR200ExtraDPValues { get; set; } + + public bool[] WMR200ChannelPresent { get; set; } + + public double[] WMR200ExtraHumValues { get; set; } + + public double[] WMR200ExtraTempValues { get; set; } + + public DateTime lastDataReadTime; + public bool haveReadData = false; + + public bool ExtraSensorsDetected = false; + + // Should Cumulus find the peak gust? + // This gets set to false for Davis stations after logger download + // if 10-minute gust period is in use, so we use the Davis value instead. + public bool CalcRecentMaxGust = true; + + public SerialPort comport; + + //private TextWriterTraceListener myTextListener; + + private Thread t; + + public Timer secondTimer; + public double presstrendval; + public double temptrendval; + + public int multicastsGood, multicastsBad; + + public bool timerStartNeeded = false; + + private readonly DateTime versionCheckTime; + + public SQLiteConnection RecentDataDb; + // Extra sensors + + public double SolarElevation; + + public double SolarFactor = -1; // used to adjust solar transmission factor (range 0-1), disabled = -1 + + public bool WindReadyToPlot = false; + public bool TempReadyToPlot = false; + private bool first_temp = true; + public double RG11RainYesterday { get; set; } + + public abstract void Start(); + + public WeatherStation(Cumulus cumulus) + { + // save the reference to the owner + this.cumulus = cumulus; + + // initialise the monthly array of records - element zero is not used + for (var i = 1; i <= 12; i++) + { + MonthlyRecs[i] = new AllTimeRecords(); + } + + CumulusForecast = cumulus.ForecastNotAvailable; + wsforecast = cumulus.ForecastNotAvailable; + + ExtraTemp = new double[11]; + ExtraHum = new double[11]; + ExtraDewPoint = new double[11]; + UserTemp = new double[9]; + + windcounts = new double[16]; + WindRecent = new TWindRecent[MaxWindRecent]; + WindVec = new TWindVec[MaxWindRecent]; + + ReadTodayFile(); + ReadYesterdayFile(); + ReadAlltimeIniFile(); + ReadMonthlyAlltimeIniFile(); + ReadMonthIniFile(); + ReadYearIniFile(); + + GetRainCounter(); + GetRainFallTotals(); + + RecentDataDb = new SQLiteConnection(":memory:", true); + RecentDataDb.CreateTable(); + + var rnd = new Random(); + versionCheckTime = new DateTime(1, 1, 1, rnd.Next(0, 23), rnd.Next(0, 59), 0); + } + + private void GetRainCounter() + { + // Find today's rain so far from last record in log file + bool midnightrainfound = false; + //string LogFile = cumulus.Datapath + cumulus.LastUpdateTime.ToString("MMMyy") + "log.txt"; + string LogFile = cumulus.GetLogFileName(cumulus.LastUpdateTime); + double raincount = 0; + string logdate = "00/00/00"; + string prevlogdate = "00/00/00"; + string listSep = CultureInfo.CurrentCulture.TextInfo.ListSeparator; + string todaydatestring = cumulus.LastUpdateTime.ToString("dd/MM/yy"); + + cumulus.LogMessage("Finding raintoday from logfile " + LogFile); + cumulus.LogMessage("Expecting listsep=" + listSep + " decimal=" + CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator); + + if (File.Exists(LogFile)) + { + int linenum = 0; + try + { + using (var sr = new StreamReader(LogFile)) + { + // now process each record to get the last "raintoday" figure + do + { + string Line = sr.ReadLine(); + linenum++; + var st = new List(Regex.Split(Line, listSep)); + if (st.Count > 0) + { + RainToday = Double.Parse(st[9]); + // get date of this entry + logdate = st[0]; + if (!midnightrainfound) + { + if (logdate != prevlogdate) + { + if (todaydatestring == logdate) + { + // this is the first entry of a new day AND the new day is today + midnightrainfound = true; + cumulus.LogMessage("Midnight rain found in the following entry:"); + cumulus.LogMessage(Line); + raincount = Double.Parse(st[11]); + } + } + } + prevlogdate = logdate; + } + } while (!sr.EndOfStream); + } + } + catch (Exception E) + { + cumulus.LogMessage("Error on line " + linenum + " of " + LogFile + ": " + E.Message); + } + } + + if (midnightrainfound) + { + if ((logdate.Substring(0, 2) == "01") && (logdate.Substring(3, 2) == cumulus.RainSeasonStart.ToString("D2")) && (cumulus.Manufacturer == cumulus.DAVIS)) + { + // special case: rain counter is about to be reset + //TODO: MC: Hmm are there issues here, what if the console clock is wrong and it does not reset for another hour, or it already reset and we have had rain since? + var month = CultureInfo.InvariantCulture.DateTimeFormat.GetMonthName(cumulus.RainSeasonStart); + cumulus.LogMessage($"Special case, Davis station on 1st of {month}. Set midnight rain count to zero"); + midnightraincount = 0; + } + else + { + cumulus.LogMessage("Midnight rain found, setting midnight rain count = " + raincount); + midnightraincount = raincount; + } + } + else + { + cumulus.LogMessage("Midnight rain not found, setting midnight count to raindaystart = " + raindaystart); + midnightraincount = raindaystart; + } + + // If we do not have a rain counter value for start of day from Today.ini, then use the midnight counter + if (initialiseRainCounterOnFirstData) + { + Raincounter = midnightraincount + (RainToday / cumulus.Calib.Rain.Mult); + } + else + { + // Otherwise use the counter value from today.ini plus total so far today to infer the counter value + Raincounter = raindaystart + (RainToday / cumulus.Calib.Rain.Mult); + } + + cumulus.LogMessage("Checking rain counter = " + Raincounter); + if (Raincounter < 0) + { + cumulus.LogMessage("Rain counter negative, setting to zero"); + Raincounter = 0; + } + else + { + cumulus.LogMessage("Rain counter set to = " + Raincounter); + } + } + + public DateTime ddmmyyStrToDate(string d) + { + // Converts a date string in UK order to a DateTime + // Horrible hack, but we have localised separators, but UK sequence, so localised parsing may fail + string[] date = d.Split(new string[] { CultureInfo.CurrentCulture.DateTimeFormat.DateSeparator }, StringSplitOptions.None); + + int D = Convert.ToInt32(date[0]); + int M = Convert.ToInt32(date[1]); + int Y = Convert.ToInt32(date[2]); + if (Y > 70) + { + Y += 1900; + } + else + { + Y += 2000; + } + + return new DateTime(Y, M, D); + } + + public DateTime ddmmyyhhmmStrToDate(string d, string t) + { + // Converts a date string in UK order to a DateTime + // Horrible hack, but we have localised separators, but UK sequence, so localised parsing may fail + string[] date = d.Split(new string[] { CultureInfo.CurrentCulture.DateTimeFormat.DateSeparator }, StringSplitOptions.None); + string[] time = t.Split(new string[] { CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator }, StringSplitOptions.None); + + int D = Convert.ToInt32(date[0]); + int M = Convert.ToInt32(date[1]); + int Y = Convert.ToInt32(date[2]); + + // Double check - just in case we get a four digit year! + if (Y < 1900) + { + Y += Y > 70 ? 1900 : 2000; + } + int h = Convert.ToInt32(time[0]); + int m = Convert.ToInt32(time[1]); + + return new DateTime(Y, M, D, h, m, 0); + } + + public void GetRainFallTotals() + { + cumulus.LogMessage("Getting rain totals, rain season start = " + cumulus.RainSeasonStart); + rainthismonth = 0; + rainthisyear = 0; + int linenum = 0; + // get today"s date for month check; allow for 0900 rollover + var hourInc = cumulus.GetHourInc(); + var ModifiedNow = DateTime.Now.AddHours(hourInc); + // avoid any funny locale peculiarities on date formats + string Today = ModifiedNow.ToString("dd/MM/yy", CultureInfo.InvariantCulture); + cumulus.LogMessage("Today = " + Today); + // get today's date offset by rain season start for year check + int offsetYearToday = ModifiedNow.AddMonths(-(cumulus.RainSeasonStart - 1)).Year; + + if (File.Exists(cumulus.DayFileName)) + { + try + { + using (var sr = new StreamReader(cumulus.DayFileName)) + { + do + { + string Line = sr.ReadLine(); + linenum++; + var st = new List(Regex.Split(Line, CultureInfo.CurrentCulture.TextInfo.ListSeparator)); + + if (st.Count > 0) + { + string datestr = st[0]; + DateTime loggedDate = ddmmyyStrToDate(datestr); + int offsetLoggedYear = loggedDate.AddMonths(-(cumulus.RainSeasonStart - 1)).Year; + // This year? + if (offsetLoggedYear == offsetYearToday) + { + rainthisyear += Double.Parse(st[14]); + } + // This month? + if ((loggedDate.Month == ModifiedNow.Month) && (loggedDate.Year == ModifiedNow.Year)) + { + rainthismonth += Double.Parse(st[14]); + } + } + } while (!sr.EndOfStream); + } + } + catch (Exception ex) + { + cumulus.LogMessage("GetRainfallTotals: Error on line " + linenum + " of dayfile.txt: " + ex.Message); + } + + cumulus.LogMessage("Rainthismonth from dayfile.txt: " + rainthismonth); + cumulus.LogMessage("Rainthisyear from dayfile.txt: " + rainthisyear); + } + + // Add in year-to-date rain (if necessary) + if (cumulus.YTDrainyear == Convert.ToInt32(Today.Substring(6, 2)) + 2000) + { + cumulus.LogMessage("Adding YTD rain: " + cumulus.YTDrain); + rainthisyear += cumulus.YTDrain; + cumulus.LogMessage("Rainthisyear: " + rainthisyear); + } + } + + public void ReadTodayFile() + { + if (!File.Exists(cumulus.TodayIniFile)) + { + FirstRun = true; + } + + IniFile ini = new IniFile(cumulus.TodayIniFile); + + cumulus.LogConsoleMessage("Today.ini = " + cumulus.TodayIniFile); + + var todayfiledate = ini.GetValue("General", "Date", "00/00/00"); + var timestampstr = ini.GetValue("General", "Timestamp", DateTime.Now.ToString("s")); + + cumulus.LogConsoleMessage("Last update=" + timestampstr); + + cumulus.LastUpdateTime = DateTime.Parse(timestampstr); + cumulus.LogMessage("Last update time from today.ini: " + cumulus.LastUpdateTime); + + DateTime currentMonthTS = cumulus.LastUpdateTime.AddHours(cumulus.GetHourInc()); + + int defaultyear = currentMonthTS.Year; + int defaultmonth = currentMonthTS.Month; + int defaultday = currentMonthTS.Day; + + CurrentYear = ini.GetValue("General", "CurrentYear", defaultyear); + CurrentMonth = ini.GetValue("General", "CurrentMonth", defaultmonth); + CurrentDay = ini.GetValue("General", "CurrentDay", defaultday); + + cumulus.LogMessage("Read today file: Date = " + todayfiledate + ", LastUpdateTime = " + cumulus.LastUpdateTime + ", Month = " + CurrentMonth); + + LastRainTip = ini.GetValue("Rain", "LastTip", "0000-00-00 00:00"); + + FOSensorClockTime = ini.GetValue("FineOffset", "FOSensorClockTime", DateTime.MinValue); + FOStationClockTime = ini.GetValue("FineOffset", "FOStationClockTime", DateTime.MinValue); + if (cumulus.FineOffsetOptions.SyncReads) + { + cumulus.LogMessage("Sensor clock " + FOSensorClockTime.ToLongTimeString()); + cumulus.LogMessage("Station clock " + FOStationClockTime.ToLongTimeString()); + } + ConsecutiveRainDays = ini.GetValue("Rain", "ConsecutiveRainDays", 0); + ConsecutiveDryDays = ini.GetValue("Rain", "ConsecutiveDryDays", 0); + + AnnualETTotal = ini.GetValue("ET", "Annual", 0.0); + StartofdayET = ini.GetValue("ET", "Startofday", -1.0); + if (StartofdayET < 0) + { + cumulus.LogMessage("ET not initialised"); + noET = true; + } + ChillHours = ini.GetValue("Temp", "ChillHours", 0.0); + + // NOAA report names + cumulus.NOAALatestMonthlyReport = ini.GetValue("NOAA", "LatestMonthlyReport", ""); + cumulus.NOAALatestYearlyReport = ini.GetValue("NOAA", "LatestYearlyReport", ""); + + // Solar + HiLoToday.HighSolar = ini.GetValue("Solar", "HighSolarRad", 0.0); + HiLoToday.HighSolarTime = ini.GetValue("Solar", "HighSolarRadTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); + HiLoToday.HighUv = ini.GetValue("Solar", "HighUV", 0.0); + HiLoToday.HighUvTime = ini.GetValue("Solar", "HighUVTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); + StartOfDaySunHourCounter = ini.GetValue("Solar", "SunStart", -9999.0); + RG11RainToday = ini.GetValue("Rain", "RG11Today", 0.0); + + // Wind + HiLoToday.HighWind = ini.GetValue("Wind", "Speed", 0.0); + HiLoToday.HighWindTime = ini.GetValue("Wind", "SpTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); + HiLoToday.HighGust = ini.GetValue("Wind", "Gust", 0.0); + HiLoToday.HighGustTime = ini.GetValue("Wind", "Time", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); + HiLoToday.HighGustBearing = ini.GetValue("Wind", "Bearing", 0); + WindRunToday = ini.GetValue("Wind", "Windrun", 0.0); + DominantWindBearing = ini.GetValue("Wind", "DominantWindBearing", 0); + DominantWindBearingMinutes = ini.GetValue("Wind", "DominantWindBearingMinutes", 0); + DominantWindBearingX = ini.GetValue("Wind", "DominantWindBearingX", 0.0); + DominantWindBearingY = ini.GetValue("Wind", "DominantWindBearingY", 0.0); + // Temperature + HiLoToday.LowTemp = ini.GetValue("Temp", "Low", 999.0); + HiLoToday.LowTempTime = ini.GetValue("Temp", "LTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); + HiLoToday.HighTemp = ini.GetValue("Temp", "High", -999.0); + HiLoToday.HighTempTime = ini.GetValue("Temp", "HTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); + if ((HiLoToday.HighTemp > -400) && (HiLoToday.LowTemp < 400)) + HiLoToday.TempRange = HiLoToday.HighTemp - HiLoToday.LowTemp; + else + HiLoToday.TempRange = 0; + TempTotalToday = ini.GetValue("Temp", "Total", 0.0); + tempsamplestoday = ini.GetValue("Temp", "Samples", 1); + HeatingDegreeDays = ini.GetValue("Temp", "HeatingDegreeDays", 0.0); + CoolingDegreeDays = ini.GetValue("Temp", "CoolingDegreeDays", 0.0); + GrowingDegreeDaysThisYear1 = ini.GetValue("Temp", "GrowingDegreeDaysThisYear1", 0.0); + GrowingDegreeDaysThisYear2 = ini.GetValue("Temp", "GrowingDegreeDaysThisYear2", 0.0); + // PressureHighDewpoint + HiLoToday.LowPress = ini.GetValue("Pressure", "Low", 9999.0); + HiLoToday.LowPressTime = ini.GetValue("Pressure", "LTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); + HiLoToday.HighPress = ini.GetValue("Pressure", "High", 0.0); + HiLoToday.HighPressTime = ini.GetValue("Pressure", "HTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); + // rain + HiLoToday.HighRainRate = ini.GetValue("Rain", "High", 0.0); + HiLoToday.HighRainRateTime = ini.GetValue("Rain", "HTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); + HiLoToday.HighHourlyRain = ini.GetValue("Rain", "HourlyHigh", 0.0); + HiLoToday.HighHourlyRainTime = ini.GetValue("Rain", "HHourlyTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); + raindaystart = ini.GetValue("Rain", "Start", -1.0); + cumulus.LogMessage($"ReadTodayfile: Rain day start = {raindaystart}"); + RainYesterday = ini.GetValue("Rain", "Yesterday", 0.0); + if (raindaystart >= 0) + { + cumulus.LogMessage("ReadTodayfile: set initialiseRainCounterOnFirstData false"); + initialiseRainCounterOnFirstData = false; + } + // humidity + HiLoToday.LowHumidity = ini.GetValue("Humidity", "Low", 100); + HiLoToday.HighHumidity = ini.GetValue("Humidity", "High", 0); + HiLoToday.LowHumidityTime = ini.GetValue("Humidity", "LTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); + HiLoToday.HighHumidityTime = ini.GetValue("Humidity", "HTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); + // Solar + SunshineHours = ini.GetValue("Solar", "SunshineHours", 0.0); + SunshineToMidnight = ini.GetValue("Solar", "SunshineHoursToMidnight", 0.0); + // heat index + HiLoToday.HighHeatIndex = ini.GetValue("HeatIndex", "High", -999.0); + HiLoToday.HighHeatIndexTime = ini.GetValue("HeatIndex", "HTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); + // Apparent temp + HiLoToday.HighAppTemp = ini.GetValue("AppTemp", "High", -999.0); + HiLoToday.HighAppTempTime = ini.GetValue("AppTemp", "HTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); + HiLoToday.LowAppTemp = ini.GetValue("AppTemp", "Low", 999.0); + HiLoToday.LowAppTempTime = ini.GetValue("AppTemp", "LTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); + // wind chill + HiLoToday.LowWindChill = ini.GetValue("WindChill", "Low", 999.0); + HiLoToday.LowWindChillTime = ini.GetValue("WindChill", "LTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); + // Dew point + HiLoToday.HighDewPoint = ini.GetValue("Dewpoint", "High", -999.0); + HiLoToday.HighDewPointTime = ini.GetValue("Dewpoint", "HTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); + HiLoToday.LowDewPoint = ini.GetValue("Dewpoint", "Low", 999.0); + HiLoToday.LowDewPointTime = ini.GetValue("Dewpoint", "LTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); + // Feels like + HiLoToday.HighFeelsLike = ini.GetValue("FeelsLike", "High", -999.0); + HiLoToday.HighFeelsLikeTime = ini.GetValue("FeelsLike", "HTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); + HiLoToday.LowFeelsLike = ini.GetValue("FeelsLike", "Low", 999.0); + HiLoToday.LowFeelsLikeTime = ini.GetValue("FeelsLike", "LTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); + // Humidex + HiLoToday.HighHumidex = ini.GetValue("Humidex", "High", -999.0); + HiLoToday.HighHumidexTime = ini.GetValue("Humidex", "HTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); + + // Records + AlltimeRecordTimestamp = ini.GetValue("Records", "Alltime", DateTime.MinValue); + } + + public void WriteTodayFile(DateTime timestamp, bool Log) + { + try + { + var hourInc = cumulus.GetHourInc(); + + IniFile ini = new IniFile(cumulus.TodayIniFile); + + // Date + ini.SetValue("General", "Date", timestamp.AddHours(hourInc).ToShortDateString()); + // Timestamp + ini.SetValue("General", "Timestamp", cumulus.LastUpdateTime.ToString("s")); + ini.SetValue("General", "CurrentYear", CurrentYear); + ini.SetValue("General", "CurrentMonth", CurrentMonth); + ini.SetValue("General", "CurrentDay", CurrentDay); + // Wind + ini.SetValue("Wind", "Speed", HiLoToday.HighWind); + ini.SetValue("Wind", "SpTime", HiLoToday.HighWindTime.ToString("HH:mm")); + ini.SetValue("Wind", "Gust", HiLoToday.HighGust); + ini.SetValue("Wind", "Time", HiLoToday.HighGustTime.ToString("HH:mm")); + ini.SetValue("Wind", "Bearing", HiLoToday.HighGustBearing); + ini.SetValue("Wind", "Direction", CompassPoint(HiLoToday.HighGustBearing)); + ini.SetValue("Wind", "Windrun", WindRunToday); + ini.SetValue("Wind", "DominantWindBearing", DominantWindBearing); + ini.SetValue("Wind", "DominantWindBearingMinutes", DominantWindBearingMinutes); + ini.SetValue("Wind", "DominantWindBearingX", DominantWindBearingX); + ini.SetValue("Wind", "DominantWindBearingY", DominantWindBearingY); + // Temperature + ini.SetValue("Temp", "Low", HiLoToday.LowTemp); + ini.SetValue("Temp", "LTime", HiLoToday.LowTempTime.ToString("HH:mm")); + ini.SetValue("Temp", "High", HiLoToday.HighTemp); + ini.SetValue("Temp", "HTime", HiLoToday.HighTempTime.ToString("HH:mm")); + ini.SetValue("Temp", "Total", TempTotalToday); + ini.SetValue("Temp", "Samples", tempsamplestoday); + ini.SetValue("Temp", "ChillHours", ChillHours); + ini.SetValue("Temp", "HeatingDegreeDays", HeatingDegreeDays); + ini.SetValue("Temp", "CoolingDegreeDays", CoolingDegreeDays); + ini.SetValue("Temp", "GrowingDegreeDaysThisYear1", GrowingDegreeDaysThisYear1); + ini.SetValue("Temp", "GrowingDegreeDaysThisYear2", GrowingDegreeDaysThisYear2); + // Pressure + ini.SetValue("Pressure", "Low", HiLoToday.LowPress); + ini.SetValue("Pressure", "LTime", HiLoToday.LowPressTime.ToString("HH:mm")); + ini.SetValue("Pressure", "High", HiLoToday.HighPress); + ini.SetValue("Pressure", "HTime", HiLoToday.HighPressTime.ToString("HH:mm")); + // rain + ini.SetValue("Rain", "High", HiLoToday.HighRainRate); + ini.SetValue("Rain", "HTime", HiLoToday.HighRainRateTime.ToString("HH:mm")); + ini.SetValue("Rain", "HourlyHigh", HiLoToday.HighHourlyRain); + ini.SetValue("Rain", "HHourlyTime", HiLoToday.HighHourlyRainTime.ToString("HH:mm")); + ini.SetValue("Rain", "Start", raindaystart); + ini.SetValue("Rain", "Yesterday", RainYesterday); + ini.SetValue("Rain", "LastTip", LastRainTip); + ini.SetValue("Rain", "ConsecutiveRainDays", ConsecutiveRainDays); + ini.SetValue("Rain", "ConsecutiveDryDays", ConsecutiveDryDays); + ini.SetValue("Rain", "RG11Today", RG11RainToday); + // ET + ini.SetValue("ET", "Annual", AnnualETTotal); + ini.SetValue("ET", "Startofday", StartofdayET); + // humidity + ini.SetValue("Humidity", "Low", HiLoToday.LowHumidity); + ini.SetValue("Humidity", "High", HiLoToday.HighHumidity); + ini.SetValue("Humidity", "LTime", HiLoToday.LowHumidityTime.ToString("HH:mm")); + ini.SetValue("Humidity", "HTime", HiLoToday.HighHumidityTime.ToString("HH:mm")); + // Solar + ini.SetValue("Solar", "SunshineHours", SunshineHours); + ini.SetValue("Solar", "SunshineHoursToMidnight", SunshineToMidnight); + // heat index + ini.SetValue("HeatIndex", "High", HiLoToday.HighHeatIndex); + ini.SetValue("HeatIndex", "HTime", HiLoToday.HighHeatIndexTime.ToString("HH:mm")); + // App temp + ini.SetValue("AppTemp", "Low", HiLoToday.LowAppTemp); + ini.SetValue("AppTemp", "LTime", HiLoToday.LowAppTempTime.ToString("HH:mm")); + ini.SetValue("AppTemp", "High", HiLoToday.HighAppTemp); + ini.SetValue("AppTemp", "HTime", HiLoToday.HighAppTempTime.ToString("HH:mm")); + // Feels like + ini.SetValue("FeelsLike", "Low", HiLoToday.LowFeelsLike); + ini.SetValue("FeelsLike", "LTime", HiLoToday.LowFeelsLikeTime.ToString("HH:mm")); + ini.SetValue("FeelsLike", "High", HiLoToday.HighFeelsLike); + ini.SetValue("FeelsLike", "HTime", HiLoToday.HighFeelsLikeTime.ToString("HH:mm")); + // Humidex + ini.SetValue("Humidex", "High", HiLoToday.HighHumidex); + ini.SetValue("Humidex", "HTime", HiLoToday.HighHumidexTime.ToString("HH:mm")); + // wind chill + ini.SetValue("WindChill", "Low", HiLoToday.LowWindChill); + ini.SetValue("WindChill", "LTime", HiLoToday.LowWindChillTime.ToString("HH:mm")); + // Dewpoint + ini.SetValue("Dewpoint", "Low", HiLoToday.LowDewPoint); + ini.SetValue("Dewpoint", "LTime", HiLoToday.LowDewPointTime.ToString("HH:mm")); + ini.SetValue("Dewpoint", "High", HiLoToday.HighDewPoint); + ini.SetValue("Dewpoint", "HTime", HiLoToday.HighDewPointTime.ToString("HH:mm")); + + // NOAA report names + ini.SetValue("NOAA", "LatestMonthlyReport", cumulus.NOAALatestMonthlyReport); + ini.SetValue("NOAA", "LatestYearlyReport", cumulus.NOAALatestYearlyReport); + + // Solar + ini.SetValue("Solar", "HighSolarRad", HiLoToday.HighSolar); + ini.SetValue("Solar", "HighSolarRadTime", HiLoToday.HighSolarTime.ToString("HH:mm")); + ini.SetValue("Solar", "HighUV", HiLoToday.HighUv); + ini.SetValue("Solar", "HighUVTime", HiLoToday.HighUvTime.ToString("HH:mm")); + ini.SetValue("Solar", "SunStart", StartOfDaySunHourCounter); + + // Special Fine Offset data + ini.SetValue("FineOffset", "FOSensorClockTime", FOSensorClockTime); + ini.SetValue("FineOffset", "FOStationClockTime", FOStationClockTime); + + // Records + ini.SetValue("Records", "Alltime", AlltimeRecordTimestamp); + + if (Log) + { + cumulus.LogMessage("Writing today.ini, LastUpdateTime = " + cumulus.LastUpdateTime + " raindaystart = " + raindaystart.ToString() + " rain counter = " + + Raincounter.ToString()); + + if (cumulus.FineOffsetStation) + { + cumulus.LogMessage("Latest reading: " + LatestFOReading); + } + else if (cumulus.StationType == StationTypes.Instromet) + { + cumulus.LogMessage("Latest reading: " + cumulus.LatestImetReading); + } + } + + ini.Flush(); + } + catch (Exception ex) + { + cumulus.LogDebugMessage("Error writing today.ini: " + ex.Message); + } + } + + /// + /// calculate the start of today in UTC + /// + /// timestamp of start of today UTC + /* + private DateTime StartOfTodayUTC() + { + DateTime now = DateTime.Now; + + int y = now.Year; + int m = now.Month; + int d = now.Day; + + return new DateTime(y, m, d, 0, 0, 0).ToUniversalTime(); + } + */ + + /// + /// calculate the start of yesterday in UTC + /// + /// timestamp of start of yesterday UTC + /* + private DateTime StartOfYesterdayUTC() + { + DateTime yesterday = DateTime.Now.AddDays(-1); + + int y = yesterday.Year; + int m = yesterday.Month; + int d = yesterday.Day; + + return new DateTime(y, m, d, 0, 0, 0).ToUniversalTime(); + } + */ + + /// + /// calculate the start of this year in UTC + /// + /// timestamp of start of year in UTC + /* + private DateTime StartOfYearUTC() + { + DateTime now = DateTime.Now; + int y = now.Year; + + return new DateTime(y, 1, 1, 0, 0, 0).ToUniversalTime(); + } + */ + + /// + /// calculate the start of this month in UTC + /// + /// timestamp of start of month in UTC + /* + private DateTime StartOfMonthUTC() + { + DateTime now = DateTime.Now; + int y = now.Year; + int m = now.Month; + + return new DateTime(y, m, 1, 0, 0, 0).ToUniversalTime(); + } + */ + + /// + /// calculate the start of this year in OAdate + /// + /// timestamp of start of year in OAdate + /* + private int StartOfYearOADate() + { + DateTime now = DateTime.Now; + int y = now.Year; + + return (int) new DateTime(y, 1, 1, 0, 0, 0).ToOADate(); + } + */ + + /// + /// calculate the start of this month in OADate + /// + /// timestamp of start of month in OADate + /* + private int StartOfMonthOADate() + { + DateTime now = DateTime.Now; + int y = now.Year; + int m = now.Month; + + return (int) new DateTime(y, m, 1, 0, 0, 0).ToOADate(); + } + */ + + /// + /// Indoor temperature in C + /// + public double IndoorTemperature { get; set; } = 0; + + /// + /// Solar Radiation in W/m2 + /// + public double SolarRad { get; set; } = 0; + + /// + /// UV index + /// + public double UV { get; set; } = 0; + + public void UpdatePressureTrendString() + { + double threeHourlyPressureChangeMb = 0; + + switch (cumulus.Units.Press) + { + case 0: + case 1: + threeHourlyPressureChangeMb = presstrendval * 3; + break; + case 2: + threeHourlyPressureChangeMb = presstrendval * 3 / 0.0295333727; + break; + } + + if (threeHourlyPressureChangeMb > 6) Presstrendstr = cumulus.Risingveryrapidly; + else if (threeHourlyPressureChangeMb > 3.5) Presstrendstr = cumulus.Risingquickly; + else if (threeHourlyPressureChangeMb > 1.5) Presstrendstr = cumulus.Rising; + else if (threeHourlyPressureChangeMb > 0.1) Presstrendstr = cumulus.Risingslowly; + else if (threeHourlyPressureChangeMb > -0.1) Presstrendstr = cumulus.Steady; + else if (threeHourlyPressureChangeMb > -1.5) Presstrendstr = cumulus.Fallingslowly; + else if (threeHourlyPressureChangeMb > -3.5) Presstrendstr = cumulus.Falling; + else if (threeHourlyPressureChangeMb > -6) Presstrendstr = cumulus.Fallingquickly; + else + Presstrendstr = cumulus.Fallingveryrapidly; + } + + public string Presstrendstr { get; set; } + + public void CheckMonthlyAlltime(string index, double value, bool higher, DateTime timestamp) + { + lock (monthlyalltimeIniThreadLock) + { + bool recordbroken; + + // Make the delta relate to the precision for dervied values such as feels like + string[] derivedVals = { "HighHeatIndex", "HighAppTemp", "LowAppTemp", "LowChill", "HighHumidex", "HighDewPoint", "LowDewPoint", "HighFeelsLike", "LowFeelsLike" }; + + double epsilon = derivedVals.Contains(index) ? Math.Pow(10, -cumulus.TempDPlaces) : 0.001; // required difference for new record + + int month; + int day; + int year; + + + // Determine month day and year + if (cumulus.RolloverHour == 0) + { + month = timestamp.Month; + day = timestamp.Day; + year = timestamp.Year; + } + else + { + TimeZone tz = TimeZone.CurrentTimeZone; + DateTime adjustedTS; + + if (cumulus.Use10amInSummer && tz.IsDaylightSavingTime(timestamp)) + { + // Locale is currently on Daylight (summer) time + adjustedTS = timestamp.AddHours(-10); + } + else + { + // Locale is currently on Standard time or unknown + adjustedTS = timestamp.AddHours(-9); + } + + month = adjustedTS.Month; + day = adjustedTS.Day; + year = adjustedTS.Year; + } + + AllTimeRec rec = MonthlyRecs[month][index]; + + double oldvalue = rec.Val; + //DateTime oldts = monthlyrecarray[index, month].timestamp; + + if (higher) + { + // check new value is higher than existing record + recordbroken = (value - oldvalue >= epsilon); + } + else + { + // check new value is lower than existing record + recordbroken = (oldvalue - value >= epsilon); + } + + if (recordbroken) + { + // records which apply to whole days or months need their timestamps adjusting + if ((index == "MonthlyRain") || (index == "DailyRain")) + { + DateTime CurrentMonthTS = new DateTime(year, month, day); + SetMonthlyAlltime(rec, value, CurrentMonthTS); + } + else + { + SetMonthlyAlltime(rec, value, timestamp); + } + } + } + } + + private string FormatDateTime(string fmt, DateTime timestamp) + { + return timestamp.ToString(fmt); + } + + + public int CurrentDay { get; set; } + + public int CurrentMonth { get; set; } + + public int CurrentYear { get; set; } + + /// + /// Indoor relative humidity in % + /// + public int IndoorHumidity { get; set; } = 0; + + /// + /// Sea-level pressure + /// + public double Pressure { get; set; } = 0; + + public double StationPressure { get; set; } + + public string Forecast { get; set; } = "Forecast: "; + + /// + /// Outdoor temp + /// + public double OutdoorTemperature { get; set; } = 0; + + /// + /// Outdoor dew point + /// + public double OutdoorDewpoint { get; set; } = 0; + + /// + /// Wind chill + /// + public double WindChill { get; set; } = 0; + + /// + /// Outdoor relative humidity in % + /// + public int OutdoorHumidity { get; set; } = 0; + + /// + /// Apparent temperature + /// + public double ApparentTemperature { get; set; } + + /// + /// Heat index + /// + public double HeatIndex { get; set; } = 0; + + /// + /// Humidex + /// + public double Humidex { get; set; } = 0; + + /// + /// Feels like (JAG/TI) + /// + public double FeelsLike { get; set; } = 0; + + + /// + /// Latest wind speed/gust + /// + public double WindLatest { get; set; } = 0; + + /// + /// Average wind speed + /// + public double WindAverage { get; set; } = 0; + + /// + /// Peak wind gust in last 10 minutes + /// + public double RecentMaxGust { get; set; } = 0; + + /// + /// Wind direction in degrees + /// + public int Bearing { get; set; } = 0; + + /// + /// Wind direction as compass points + /// + public string BearingText { get; set; } = "---"; + + /// + /// Wind direction in degrees + /// + public int AvgBearing { get; set; } = 0; + + /// + /// Wind direction as compass points + /// + public string AvgBearingText { get; set; } = "---"; + + /// + /// Rainfall today + /// + public double RainToday { get; set; } = 0; + + /// + /// Rain this month + /// + public double RainMonth { get; set; } = 0; + + /// + /// Rain this year + /// + public double RainYear { get; set; } = 0; + + /// + /// Current rain rate + /// + public double RainRate { get; set; } = 0; + + public double ET { get; set; } + + public double LightValue { get; set; } + + public double HeatingDegreeDays { get; set; } + + public double CoolingDegreeDays { get; set; } + + public double GrowingDegreeDaysThisYear1 { get; set; } + public double GrowingDegreeDaysThisYear2 { get; set; } + + public int tempsamplestoday { get; set; } + + public double TempTotalToday { get; set; } + + public double ChillHours { get; set; } + + public double midnightraincount { get; set; } + + public int MidnightRainResetDay { get; set; } + + + public DateTime lastSpikeRemoval = DateTime.MinValue; + private double previousPress = 9999; + public double previousGust = 999; + private double previousWind = 999; + private int previousHum = 999; + private double previousTemp = 999; + + + public void UpdateDegreeDays(int interval) + { + if (OutdoorTemperature < cumulus.NOAAheatingthreshold) + { + HeatingDegreeDays += (((cumulus.NOAAheatingthreshold - OutdoorTemperature) * interval) / 1440); + } + if (OutdoorTemperature > cumulus.NOAAcoolingthreshold) + { + CoolingDegreeDays += (((OutdoorTemperature - cumulus.NOAAcoolingthreshold) * interval) / 1440); + } + } + + /// + /// Wind run for today + /// + public double WindRunToday { get; set; } = 0; + + /// + /// Extra Temps + /// + public double[] ExtraTemp { get; set; } + + /// + /// User allocated Temps + /// + public double[] UserTemp { get; set; } + + /// + /// Extra Humidity + /// + public double[] ExtraHum { get; set; } + + /// + /// Extra dewpoint + /// + public double[] ExtraDewPoint { get; set; } + + /// + /// Soil Temp 1 in C + /// + public double SoilTemp1 { get; set; } + + /// + /// Soil Temp 2 in C + /// + public double SoilTemp2 { get; set; } + + /// + /// Soil Temp 3 in C + /// + public double SoilTemp3 { get; set; } + + /// + /// Soil Temp 4 in C + /// + public double SoilTemp4 { get; set; } + public double SoilTemp5 { get; set; } + public double SoilTemp6 { get; set; } + public double SoilTemp7 { get; set; } + public double SoilTemp8 { get; set; } + public double SoilTemp9 { get; set; } + public double SoilTemp10 { get; set; } + public double SoilTemp11 { get; set; } + public double SoilTemp12 { get; set; } + public double SoilTemp13 { get; set; } + public double SoilTemp14 { get; set; } + public double SoilTemp15 { get; set; } + public double SoilTemp16 { get; set; } + + public double RainYesterday { get; set; } + + public double RainLastHour { get; set; } + + public int SoilMoisture1 { get; set; } + + public int SoilMoisture2 { get; set; } + + public int SoilMoisture3 { get; set; } + + public int SoilMoisture4 { get; set; } + + public int SoilMoisture5 { get; set; } + + public int SoilMoisture6 { get; set; } + + public int SoilMoisture7 { get; set; } + + public int SoilMoisture8 { get; set; } + + public int SoilMoisture9 { get; set; } + + public int SoilMoisture10 { get; set; } + + public int SoilMoisture11 { get; set; } + + public int SoilMoisture12 { get; set; } + + public int SoilMoisture13 { get; set; } + + public int SoilMoisture14 { get; set; } + + public int SoilMoisture15 { get; set; } + + public int SoilMoisture16 { get; set; } + + public double AirQuality1 { get; set; } + public double AirQuality2 { get; set; } + public double AirQuality3 { get; set; } + public double AirQuality4 { get; set; } + public double AirQualityAvg1 { get; set; } + public double AirQualityAvg2 { get; set; } + public double AirQualityAvg3 { get; set; } + public double AirQualityAvg4 { get; set; } + + public int CO2 { get; set; } + public int CO2_24h { get; set; } + public double CO2_pm2p5 { get; set; } + public double CO2_pm2p5_24h { get; set; } + public double CO2_pm10 { get; set; } + public double CO2_pm10_24h { get; set; } + public double CO2_temperature { get; set; } + public double CO2_humidity { get; set; } + + public int LeakSensor1 { get; set; } + public int LeakSensor2 { get; set; } + public int LeakSensor3 { get; set; } + public int LeakSensor4 { get; set; } + + public double LightningDistance { get; set; } + public DateTime LightningTime { get; set; } + public int LightningStrikesToday { get; set; } + + public double LeafTemp1 { get; set; } + public double LeafTemp2 { get; set; } + public double LeafTemp3 { get; set; } + public double LeafTemp4 { get; set; } + public double LeafTemp5 { get; set; } + public double LeafTemp6 { get; set; } + public double LeafTemp7 { get; set; } + public double LeafTemp8 { get; set; } + + public int LeafWetness1 { get; set; } + public int LeafWetness2 { get; set; } + public int LeafWetness3 { get; set; } + public int LeafWetness4 { get; set; } + public int LeafWetness5 { get; set; } + public int LeafWetness6 { get; set; } + public int LeafWetness7 { get; set; } + public int LeafWetness8 { get; set; } + + public double SunshineHours { get; set; } = 0; + + public double YestSunshineHours { get; set; } = 0; + + public double SunshineToMidnight { get; set; } + + public double SunHourCounter { get; set; } + + public double StartOfDaySunHourCounter { get; set; } + + public double CurrentSolarMax { get; set; } + + public double RG11RainToday { get; set; } + + public double RainSinceMidnight { get; set; } + + /// + /// Checks whether a new day has started and does a rollover if necessary + /// + /// + /* + public void CheckForRollover(int oadate) + { + if (oadate != LastDailySummaryOADate) + { + DoRollover(); + } + } + */ + + /* + private void DoRollover() + { + //throw new NotImplementedException(); + } + */ + + /// + /// + /// + /// + /// + /// Difference in minutes + /* + private int TimeDiff(DateTime later, DateTime earlier) + { + TimeSpan diff = later - earlier; + + return (int) Math.Round(diff.TotalMinutes); + } + */ + + public void StartMinuteTimer() + { + lastMinute = DateTime.Now.Minute; + lastHour = DateTime.Now.Hour; + secondTimer = new Timer(500); + secondTimer.Elapsed += SecondTimer; + secondTimer.Start(); + } + + public void StopMinuteTimer() + { + if (secondTimer != null) secondTimer.Stop(); + } + + public void SecondTimer(object sender, ElapsedEventArgs e) + { + var timeNow = DateTime.Now; // b3085 change to using a single fixed point in time to make it independent of how long the process takes + + if (timeNow.Minute != lastMinute) + { + lastMinute = timeNow.Minute; + + if ((timeNow.Minute % 10) == 0) + { + TenMinuteChanged(); + } + + if (timeNow.Hour != lastHour) + { + lastHour = timeNow.Hour; + HourChanged(timeNow); + } + + MinuteChanged(timeNow); + + if (DataStopped) + { + // No data coming in, do not do anything else + return; + } + } + + if ((int)timeNow.TimeOfDay.TotalMilliseconds % 2500 <= 500) + { + // send current data to websocket every 3 seconds + try + { + StringBuilder windRoseData = new StringBuilder(80); + + lock (windcounts) + { + windRoseData.Append((windcounts[0] * cumulus.Calib.WindGust.Mult).ToString(cumulus.WindFormat, CultureInfo.InvariantCulture)); + + for (var i = 1; i < cumulus.NumWindRosePoints; i++) + { + windRoseData.Append(","); + windRoseData.Append((windcounts[i] * cumulus.Calib.WindGust.Mult).ToString(cumulus.WindFormat, CultureInfo.InvariantCulture)); + } + } + + string stormRainStart = StartOfStorm == DateTime.MinValue ? "-----" : StartOfStorm.ToString("d"); + + var data = new DataStruct(cumulus, OutdoorTemperature, OutdoorHumidity, TempTotalToday / tempsamplestoday, IndoorTemperature, OutdoorDewpoint, WindChill, IndoorHumidity, + Pressure, WindLatest, WindAverage, RecentMaxGust, WindRunToday, Bearing, AvgBearing, RainToday, RainYesterday, RainMonth, RainYear, RainRate, + RainLastHour, HeatIndex, Humidex, ApparentTemperature, temptrendval, presstrendval, HiLoToday.HighGust, HiLoToday.HighGustTime.ToString("HH:mm"), HiLoToday.HighWind, + HiLoToday.HighGustBearing, cumulus.Units.WindText, BearingRangeFrom10, BearingRangeTo10, windRoseData.ToString(), HiLoToday.HighTemp, HiLoToday.LowTemp, + HiLoToday.HighTempTime.ToString("HH:mm"), HiLoToday.LowTempTime.ToString("HH:mm"), HiLoToday.HighPress, HiLoToday.LowPress, HiLoToday.HighPressTime.ToString("HH:mm"), + HiLoToday.LowPressTime.ToString("HH:mm"), HiLoToday.HighRainRate, HiLoToday.HighRainRateTime.ToString("HH:mm"), HiLoToday.HighHumidity, HiLoToday.LowHumidity, + HiLoToday.HighHumidityTime.ToString("HH:mm"), HiLoToday.LowHumidityTime.ToString("HH:mm"), cumulus.Units.PressText, cumulus.Units.TempText, cumulus.Units.RainText, + HiLoToday.HighDewPoint, HiLoToday.LowDewPoint, HiLoToday.HighDewPointTime.ToString("HH:mm"), HiLoToday.LowDewPointTime.ToString("HH:mm"), HiLoToday.LowWindChill, + HiLoToday.LowWindChillTime.ToString("HH:mm"), (int)SolarRad, (int)HiLoToday.HighSolar, HiLoToday.HighSolarTime.ToString("HH:mm"), UV, HiLoToday.HighUv, + HiLoToday.HighUvTime.ToString("HH:mm"), forecaststr, getTimeString(cumulus.SunRiseTime), getTimeString(cumulus.SunSetTime), + getTimeString(cumulus.MoonRiseTime), getTimeString(cumulus.MoonSetTime), HiLoToday.HighHeatIndex, HiLoToday.HighHeatIndexTime.ToString("HH:mm"), HiLoToday.HighAppTemp, + HiLoToday.LowAppTemp, HiLoToday.HighAppTempTime.ToString("HH:mm"), HiLoToday.LowAppTempTime.ToString("HH:mm"), (int)CurrentSolarMax, + AllTime.HighPress.Val, AllTime.LowPress.Val, SunshineHours, CompassPoint(DominantWindBearing), LastRainTip, + HiLoToday.HighHourlyRain, HiLoToday.HighHourlyRainTime.ToString("HH:mm"), "F" + cumulus.Beaufort(HiLoToday.HighWind), "F" + cumulus.Beaufort(WindAverage), cumulus.BeaufortDesc(WindAverage), + LastDataReadTimestamp.ToString("HH:mm:ss"), DataStopped, StormRain, stormRainStart, CloudBase, cumulus.CloudBaseInFeet ? "ft" : "m", RainLast24Hour, + cumulus.LowTempAlarm.Triggered, cumulus.HighTempAlarm.Triggered, cumulus.TempChangeAlarm.UpTriggered, cumulus.TempChangeAlarm.DownTriggered, cumulus.HighRainTodayAlarm.Triggered, cumulus.HighRainRateAlarm.Triggered, + cumulus.LowPressAlarm.Triggered, cumulus.HighPressAlarm.Triggered, cumulus.PressChangeAlarm.UpTriggered, cumulus.PressChangeAlarm.DownTriggered, cumulus.HighGustAlarm.Triggered, cumulus.HighWindAlarm.Triggered, + cumulus.SensorAlarm.Triggered, cumulus.BatteryLowAlarm.Triggered, cumulus.SpikeAlarm.Triggered, cumulus.UpgradeAlarm.Triggered, + cumulus.HttpUploadAlarm.Triggered, cumulus.MySqlUploadAlarm.Triggered, + FeelsLike, HiLoToday.HighFeelsLike, HiLoToday.HighFeelsLikeTime.ToString("HH:mm"), HiLoToday.LowFeelsLike, HiLoToday.LowFeelsLikeTime.ToString("HH:mm"), + HiLoToday.HighHumidex, HiLoToday.HighHumidexTime.ToString("HH:mm")); + + //var json = jss.Serialize(data); + + var ser = new DataContractJsonSerializer(typeof(DataStruct)); + + var stream = new MemoryStream(); + + ser.WriteObject(stream, data); + + stream.Position = 0; + + WebSocket.SendMessage(new StreamReader(stream).ReadToEnd()); + } + catch (Exception ex) + { + cumulus.LogMessage(ex.Message); + } + } + } + + private string getTimeString(DateTime time) + { + if (time <= DateTime.MinValue) + { + return "-----"; + } + else + { + return time.ToString("HH:mm"); + } + } + + private string getTimeString(TimeSpan timespan) + { + try + { + if (timespan.TotalSeconds < 0) + { + return "-----"; + } + + DateTime dt = DateTime.MinValue.Add(timespan); + + return getTimeString(dt); + } + catch (Exception e) + { + cumulus.LogMessage($"getTimeString: Exception caught - {e.Message}"); + return "-----"; + } + } + + private void HourChanged(DateTime now) + { + cumulus.LogMessage("Hour changed: " + now.Hour); + cumulus.DoSunriseAndSunset(); + cumulus.DoMoonImage(); + + if (cumulus.HourlyForecast) + { + DoForecast("", true); + } + + if (DataStopped) + { + // No data coming in, do not do anything else + return; + } + + + if (now.Hour == 0) + { + ResetMidnightRain(now); + //RecalcSolarFactor(now); + } + + int rollHour = Math.Abs(cumulus.GetHourInc()); + + if (now.Hour == rollHour) + { + DayReset(now); + cumulus.BackupData(true, now); + } + + if (now.Hour == 0) + { + ResetSunshineHours(); + } + + RemoveOldRecentData(now); + } + + private void RemoveOldRecentData(DateTime ts) + { + var deleteTime = ts.AddDays(-7); + + RecentDataDb.Execute("delete from RecentData where Timestamp < ?", deleteTime); + } + + private void ClearAlarms() + { + if (cumulus.DataStoppedAlarm.Latch && cumulus.DataStoppedAlarm.Triggered && DateTime.Now > cumulus.DataStoppedAlarm.TriggeredTime.AddHours(cumulus.DataStoppedAlarm.LatchHours)) + cumulus.DataStoppedAlarm.Triggered = false; + + if (cumulus.BatteryLowAlarm.Latch && cumulus.BatteryLowAlarm.Triggered && DateTime.Now > cumulus.BatteryLowAlarm.TriggeredTime.AddHours(cumulus.BatteryLowAlarm.LatchHours)) + cumulus.BatteryLowAlarm.Triggered = false; + + if (cumulus.SensorAlarm.Latch && cumulus.SensorAlarm.Triggered && DateTime.Now > cumulus.SensorAlarm.TriggeredTime.AddHours(cumulus.SensorAlarm.LatchHours)) + cumulus.SensorAlarm.Triggered = false; + + if (cumulus.SpikeAlarm.Latch && cumulus.SpikeAlarm.Triggered && DateTime.Now > cumulus.SpikeAlarm.TriggeredTime.AddHours(cumulus.SpikeAlarm.LatchHours)) + cumulus.SpikeAlarm.Triggered = false; + + if (cumulus.UpgradeAlarm.Latch && cumulus.UpgradeAlarm.Triggered && DateTime.Now > cumulus.UpgradeAlarm.TriggeredTime.AddHours(cumulus.UpgradeAlarm.LatchHours)) + cumulus.UpgradeAlarm.Triggered = false; + + if (cumulus.HttpUploadAlarm.Latch && cumulus.HttpUploadAlarm.Triggered && DateTime.Now > cumulus.HttpUploadAlarm.TriggeredTime.AddHours(cumulus.HttpUploadAlarm.LatchHours)) + cumulus.HttpUploadAlarm.Triggered = false; + + if (cumulus.MySqlUploadAlarm.Latch && cumulus.MySqlUploadAlarm.Triggered && DateTime.Now > cumulus.MySqlUploadAlarm.TriggeredTime.AddHours(cumulus.MySqlUploadAlarm.LatchHours)) + cumulus.MySqlUploadAlarm.Triggered = false; + + if (cumulus.HighWindAlarm.Latch && cumulus.HighWindAlarm.Triggered && DateTime.Now > cumulus.HighWindAlarm.TriggeredTime.AddHours(cumulus.HighWindAlarm.LatchHours)) + cumulus.HighWindAlarm.Triggered = false; + + if (cumulus.HighGustAlarm.Latch && cumulus.HighGustAlarm.Triggered && DateTime.Now > cumulus.HighGustAlarm.TriggeredTime.AddHours(cumulus.HighGustAlarm.LatchHours)) + cumulus.HighGustAlarm.Triggered = false; + + if (cumulus.HighRainRateAlarm.Latch && cumulus.HighRainRateAlarm.Triggered && DateTime.Now > cumulus.HighRainRateAlarm.TriggeredTime.AddHours(cumulus.HighRainRateAlarm.LatchHours)) + cumulus.HighRainRateAlarm.Triggered = false; + + if (cumulus.HighRainTodayAlarm.Latch && cumulus.HighRainTodayAlarm.Triggered && DateTime.Now > cumulus.HighRainTodayAlarm.TriggeredTime.AddHours(cumulus.HighRainTodayAlarm.LatchHours)) + cumulus.HighRainTodayAlarm.Triggered = false; + + if (cumulus.HighPressAlarm.Latch && cumulus.HighPressAlarm.Triggered && DateTime.Now > cumulus.HighPressAlarm.TriggeredTime.AddHours(cumulus.HighPressAlarm.LatchHours)) + cumulus.HighPressAlarm.Triggered = false; + + if (cumulus.LowPressAlarm.Latch && cumulus.LowPressAlarm.Triggered && DateTime.Now > cumulus.LowPressAlarm.TriggeredTime.AddHours(cumulus.LowPressAlarm.LatchHours)) + cumulus.LowPressAlarm.Triggered = false; + + if (cumulus.HighTempAlarm.Latch && cumulus.HighTempAlarm.Triggered && DateTime.Now > cumulus.HighTempAlarm.TriggeredTime.AddHours(cumulus.HighTempAlarm.LatchHours)) + cumulus.HighTempAlarm.Triggered = false; + + if (cumulus.LowTempAlarm.Latch && cumulus.LowTempAlarm.Triggered && DateTime.Now > cumulus.LowTempAlarm.TriggeredTime.AddHours(cumulus.LowTempAlarm.LatchHours)) + cumulus.LowTempAlarm.Triggered = false; + + if (cumulus.TempChangeAlarm.Latch && cumulus.TempChangeAlarm.UpTriggered && DateTime.Now > cumulus.TempChangeAlarm.UpTriggeredTime.AddHours(cumulus.TempChangeAlarm.LatchHours)) + cumulus.TempChangeAlarm.UpTriggered = false; + + if (cumulus.TempChangeAlarm.Latch && cumulus.TempChangeAlarm.DownTriggered && DateTime.Now > cumulus.TempChangeAlarm.DownTriggeredTime.AddHours(cumulus.TempChangeAlarm.LatchHours)) + cumulus.TempChangeAlarm.DownTriggered = false; + + if (cumulus.PressChangeAlarm.Latch && cumulus.PressChangeAlarm.UpTriggered && DateTime.Now > cumulus.PressChangeAlarm.UpTriggeredTime.AddHours(cumulus.PressChangeAlarm.LatchHours)) + cumulus.PressChangeAlarm.UpTriggered = false; + + if (cumulus.PressChangeAlarm.Latch && cumulus.PressChangeAlarm.DownTriggered && DateTime.Now > cumulus.PressChangeAlarm.DownTriggeredTime.AddHours(cumulus.PressChangeAlarm.LatchHours)) + cumulus.PressChangeAlarm.DownTriggered = false; + } + + private void MinuteChanged(DateTime now) + { + CheckForDataStopped(); + + if (!DataStopped) + { + CurrentSolarMax = AstroLib.SolarMax(now, cumulus.Longitude, cumulus.Latitude, AltitudeM(cumulus.Altitude), out SolarElevation, cumulus.RStransfactor, cumulus.BrasTurbidity, cumulus.SolarCalc); + if (((Pressure > 0) && TempReadyToPlot && WindReadyToPlot) || cumulus.StationOptions.NoSensorCheck) + { + // increment wind run by one minute's worth of average speed + + WindRunToday += WindAverage * WindRunHourMult[cumulus.Units.Wind] / 60.0; + + CheckForWindrunHighLow(now); + + CalculateDominantWindBearing(AvgBearing, WindAverage, 1); + + if (OutdoorTemperature < cumulus.ChillHourThreshold) + { + // add 1 minute to chill hours + ChillHours += 1.0 / 60.0; + } + + // update sunshine hours + if (cumulus.UseBlakeLarsen) + { + ReadBlakeLarsenData(); + } + else if ((SolarRad > (CurrentSolarMax * cumulus.SunThreshold / 100.0)) && (SolarRad >= cumulus.SolarMinimum)) + { + SunshineHours += 1.0 / 60.0; + } + + // update heating/cooling degree days + UpdateDegreeDays(1); + + weatherDataCollection.Add(new WeatherData + { + //station = this, + DT = System.DateTime.Now, + WindSpeed = WindLatest, + WindAverage = WindAverage, + OutdoorTemp = OutdoorTemperature, + Pressure = Pressure, + Raintotal = RainToday + }); + + while (weatherDataCollection[0].DT < now.AddHours(-1)) + { + weatherDataCollection.RemoveAt(0); + } + + if (!first_temp) + { + // update temperature average items + tempsamplestoday++; + TempTotalToday += OutdoorTemperature; + } + + AddLastHourDataEntry(now, Raincounter, OutdoorTemperature); + RemoveOldLHData(now); + AddLast3HourDataEntry(now, Pressure, OutdoorTemperature); + RemoveOldL3HData(now); + AddGraphDataEntry(now, Raincounter, RainToday, RainRate, OutdoorTemperature, OutdoorDewpoint, ApparentTemperature, WindChill, HeatIndex, + IndoorTemperature, Pressure, WindAverage, RecentMaxGust, AvgBearing, Bearing, OutdoorHumidity, IndoorHumidity, SolarRad, CurrentSolarMax, UV, FeelsLike, Humidex); + RemoveOldGraphData(now); + DoTrendValues(now); + AddRecentDataEntry(now, WindAverage, RecentMaxGust, WindLatest, Bearing, AvgBearing, OutdoorTemperature, WindChill, OutdoorDewpoint, HeatIndex, OutdoorHumidity, + Pressure, RainToday, SolarRad, UV, Raincounter, FeelsLike, Humidex); + + if (now.Minute % cumulus.logints[cumulus.DataLogInterval] == 0) + { + cumulus.DoLogFile(now, true); + + if (cumulus.StationOptions.LogExtraSensors) + { + cumulus.DoExtraLogFile(now); + } + + if (cumulus.AirLinkInEnabled || cumulus.AirLinkOutEnabled) + { + cumulus.DoAirLinkLogFile(now); + } + } + + // Custom MySQL update - minutes interval + if (cumulus.CustomMySqlMinutesEnabled && now.Minute % cumulus.CustomMySqlMinutesInterval == 0) + { + _ = cumulus.CustomMysqlMinutesTimerTick(); + } + + // Custom HTTP update - minutes interval + if (cumulus.CustomHttpMinutesEnabled && now.Minute % cumulus.CustomHttpMinutesInterval == 0) + { + cumulus.CustomHttpMinutesUpdate(); + } + + if (cumulus.WebIntervalEnabled && cumulus.SynchronisedWebUpdate && (now.Minute % cumulus.UpdateInterval == 0)) + { + if (cumulus.WebUpdating == 1) + { + // Skip this update interval + cumulus.LogMessage("Warning, previous web update is still in progress, first chance, skipping this interval"); + cumulus.WebUpdating++; + } + else if (cumulus.WebUpdating >= 2) + { + cumulus.LogMessage("Warning, previous web update is still in progress,second chance, aborting connection"); + if (cumulus.ftpThread.ThreadState == System.Threading.ThreadState.Running) + cumulus.ftpThread.Abort(); + cumulus.LogMessage("Trying new web update"); + cumulus.WebUpdating = 1; + cumulus.ftpThread = new Thread(cumulus.DoHTMLFiles); + cumulus.ftpThread.IsBackground = true; + cumulus.ftpThread.Start(); + } + else + { + cumulus.WebUpdating = 1; + cumulus.ftpThread = new Thread(cumulus.DoHTMLFiles); + cumulus.ftpThread.IsBackground = true; + cumulus.ftpThread.Start(); + } + } + + if (cumulus.Wund.Enabled && (now.Minute % cumulus.Wund.Interval == 0) && cumulus.Wund.SynchronisedUpdate && !String.IsNullOrWhiteSpace(cumulus.Wund.ID)) + { + cumulus.UpdateWunderground(now); + } + + if (cumulus.Windy.Enabled && (now.Minute % cumulus.Windy.Interval == 0) && !String.IsNullOrWhiteSpace(cumulus.Windy.ApiKey)) + { + cumulus.UpdateWindy(now); + } + + if (cumulus.WindGuru.Enabled && (now.Minute % cumulus.WindGuru.Interval == 0) && !String.IsNullOrWhiteSpace(cumulus.WindGuru.ID)) + { + cumulus.UpdateWindGuru(now); + } + + if (cumulus.AWEKAS.Enabled && (now.Minute % ((double)cumulus.AWEKAS.Interval / 60) == 0) && cumulus.AWEKAS.SynchronisedUpdate && !String.IsNullOrWhiteSpace(cumulus.AWEKAS.ID)) + { + cumulus.UpdateAwekas(now); + } + + if (cumulus.WCloud.Enabled && (now.Minute % cumulus.WCloud.Interval == 0) && !String.IsNullOrWhiteSpace(cumulus.WCloud.ID)) + { + cumulus.UpdateWCloud(now); + } + + if (cumulus.OpenWeatherMap.Enabled && (now.Minute % cumulus.OpenWeatherMap.Interval == 0) && !string.IsNullOrWhiteSpace(cumulus.OpenWeatherMap.ID)) + { + cumulus.UpdateOpenWeatherMap(now); + } + + if (cumulus.PWS.Enabled && (now.Minute % cumulus.PWS.Interval == 0) && !String.IsNullOrWhiteSpace(cumulus.PWS.ID) && !String.IsNullOrWhiteSpace(cumulus.PWS.PW)) + { + cumulus.UpdatePWSweather(now); + } + + if (cumulus.WOW.Enabled && (now.Minute % cumulus.WOW.Interval == 0) && !String.IsNullOrWhiteSpace(cumulus.WOW.ID) && !String.IsNullOrWhiteSpace(cumulus.WOW.PW)) + { + cumulus.UpdateWOW(now); + } + + if (cumulus.APRS.Enabled && (now.Minute % cumulus.APRS.Interval == 0) && !String.IsNullOrWhiteSpace(cumulus.APRS.ID)) + { + UpdateAPRS(); + } + + if (cumulus.Twitter.Enabled && (now.Minute % cumulus.Twitter.Interval == 0) && !String.IsNullOrWhiteSpace(cumulus.Twitter.ID) && !String.IsNullOrWhiteSpace(cumulus.Twitter.PW)) + { + cumulus.UpdateTwitter(); + } + + if (cumulus.xapEnabled) + { + using (Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) + { + IPEndPoint iep1 = new IPEndPoint(IPAddress.Broadcast, cumulus.xapPort); + + byte[] data = Encoding.ASCII.GetBytes(cumulus.xapHeartbeat); + + sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1); + + sock.SendTo(data, iep1); + + var timeUTC = now.ToUniversalTime().ToString("HH:mm"); + var dateISO = now.ToUniversalTime().ToString("yyyyMMdd"); + + var xapReport = new StringBuilder("", 1024); + xapReport.Append("xap-header\n{\nv=12\nhop=1\n"); + xapReport.Append($"uid=FF{cumulus.xapUID}00\n"); + xapReport.Append("class=weather.report\n"); + xapReport.Append($"source={cumulus.xapsource}\n"); + xapReport.Append("}\n"); + xapReport.Append("weather.report\n{\n"); + xapReport.Append($"UTC={timeUTC}\nDATE={dateISO}\n"); + xapReport.Append($"WindM={ConvertUserWindToMPH(WindAverage):F1}\n"); + xapReport.Append($"WindK={ConvertUserWindToKPH(WindAverage):F1}\n"); + xapReport.Append($"WindGustsM={ConvertUserWindToMPH(RecentMaxGust):F1}\n"); + xapReport.Append($"WindGustsK={ConvertUserWindToKPH(RecentMaxGust):F1}\n"); + xapReport.Append($"WindDirD={Bearing}\n"); + xapReport.Append($"WindDirC={AvgBearing}\n"); + xapReport.Append($"TempC={ConvertUserTempToC(OutdoorTemperature):F1}\n"); + xapReport.Append($"TempF={ConvertUserTempToF(OutdoorTemperature):F1}\n"); + xapReport.Append($"DewC={ConvertUserTempToC(OutdoorDewpoint):F1}\n"); + xapReport.Append($"DewF={ConvertUserTempToF(OutdoorDewpoint):F1}\n"); + xapReport.Append($"AirPressure={ConvertUserPressToMB(Pressure):F1}\n"); + xapReport.Append($"Rain={ConvertUserRainToMM(RainToday):F1}\n"); + xapReport.Append("}"); + + data = Encoding.ASCII.GetBytes(xapReport.ToString()); + + sock.SendTo(data, iep1); + + sock.Close(); + } + } + + var wxfile = cumulus.StdWebFiles.SingleOrDefault(item => item.LocalFileName == "wxnow.txt"); + if (wxfile.Create) + { + CreateWxnowFile(); + } + } + else + { + cumulus.LogMessage("Minimum data set of pressure, temperature, and wind is not available and NoSensorCheck is not enabled. Skip processing"); + } + } + + // Check for a new version of Cumulus once a day + if (now.Minute == versionCheckTime.Minute && now.Hour == versionCheckTime.Hour) + { + cumulus.LogMessage("Checking for latest Cumulus MX version..."); + cumulus.GetLatestVersion(); + } + + // If not on windows, check for CPU temp + if (Type.GetType("Mono.Runtime") != null && File.Exists("/sys/class/thermal/thermal_zone0/temp")) + { + try + { + var raw = File.ReadAllText(@"/sys/class/thermal/thermal_zone0/temp"); + cumulus.CPUtemp = ConvertTempCToUser(double.Parse(raw) / 1000); + cumulus.LogDebugMessage($"Current CPU temp = {cumulus.CPUtemp.ToString(cumulus.TempFormat)}{cumulus.Units.TempText}"); + } + catch (Exception ex) + { + cumulus.LogDebugMessage($"Error reading CPU temperature - {ex.Message}"); + } + } + } + + private void TenMinuteChanged() + { + cumulus.DoMoonPhase(); + cumulus.MoonAge = MoonriseMoonset.MoonAge(); + + cumulus.RotateLogFiles(); + + ClearAlarms(); + } + + private void CheckForDataStopped() + { + // Check whether we have read data since the last clock minute. + if ((LastDataReadTimestamp != DateTime.MinValue) && (LastDataReadTimestamp == SavedLastDataReadTimestamp) && (LastDataReadTimestamp < DateTime.Now)) + { + // Data input appears to have has stopped + DataStopped = true; + cumulus.DataStoppedAlarm.Triggered = true; + /*if (RestartIfDataStops) + { + cumulus.LogMessage("*** Data input appears to have stopped, restarting"); + ApplicationExec(ParamStr(0), '', SW_SHOW); + TerminateProcess(GetCurrentProcess, 0); + }*/ + if (cumulus.ReportDataStoppedErrors) + { + cumulus.LogMessage("*** Data input appears to have stopped"); + } + } + else + { + DataStopped = false; + cumulus.DataStoppedAlarm.Triggered = false; + } + + // save the time that data was last read so we can check in a minute's time that it's changed + SavedLastDataReadTimestamp = LastDataReadTimestamp; + } + + private void ReadBlakeLarsenData() + { + var blFile = cumulus.AppDir + "SRsunshine.dat"; + + if (File.Exists(blFile)) + { + using (var sr = new StreamReader(blFile)) + { + try + { + string line = sr.ReadLine(); + SunshineHours = double.Parse(line, CultureInfo.InvariantCulture.NumberFormat); + sr.ReadLine(); + sr.ReadLine(); + line = sr.ReadLine(); + IsSunny = (line == "True"); + } + catch (Exception ex) + { + cumulus.LogMessage("Error reading SRsunshine.dat: " + ex.Message); + } + } + } + } + + /* + internal void UpdateDatabase(DateTime timestamp, int interval, bool updateHighsAndLows) + // Add an entry to the database + { + double raininterval; + + if (prevraincounter == 0) + { + raininterval = 0; + } + else + { + raininterval = Raincounter - prevraincounter; + } + + //using (cumulusEntities dataContext = new cumulusEntities()) + //{ + // dataContext.AddToStandardData(newdata); + + // // Submit the change to the database. + // try + // { + // dataContext.SaveChanges(); + // } + // catch (Exception ex) + // { + // Trace.WriteLine(ex.ToString()); + // Trace.Flush(); + // } + + // reset highs and lows since last update + loOutdoorTemperature = OutdoorTemperature; + hiOutdoorTemperature = OutdoorTemperature; + loIndoorTemperature = IndoorTemperature; + hiIndoorTemperature = IndoorTemperature; + loIndoorHumidity = IndoorHumidity; + hiIndoorHumidity = IndoorHumidity; + loOutdoorHumidity = OutdoorHumidity; + hiOutdoorHumidity = OutdoorHumidity; + loPressure = Pressure; + hiPressure = Pressure; + hiWind = WindAverage; + hiGust = WindLatest; + hiWindBearing = Bearing; + hiGustBearing = Bearing; + prevraincounter = Raincounter; + hiRainRate = RainRate; + hiDewPoint = OutdoorDewpoint; + loDewPoint = OutdoorDewpoint; + hiHeatIndex = HeatIndex; + hiHumidex = Humidex; + loWindChill = WindChill; + hiApparentTemperature = ApparentTemperature; + loApparentTemperature = ApparentTemperature; + } + */ + + private long DateTimeToUnix(DateTime timestamp) + { + var timeSpan = (timestamp - new DateTime(1970, 1, 1, 0, 0, 0)); + return (long)timeSpan.TotalSeconds; + } + + /* + private long DateTimeToJS(DateTime timestamp) + { + return DateTimeToUnix(timestamp) * 1000; + } + */ + + public void CreateGraphDataFiles() + { + // Chart data for Highcharts graphs + string json = ""; + for (var i = 0; i < cumulus.GraphDataFiles.Length; i++) + { + // We double up the meaning of .FtpRequired to creation as well. + // The FtpRequired flag is only cleared for the config files that are pretty static so it is pointless + // recreating them every update too. + if (cumulus.GraphDataFiles[i].Create && cumulus.GraphDataFiles[i].CreateRequired) + { + switch (cumulus.GraphDataFiles[i].LocalFileName) + { + case "graphconfig.json": + json = GetGraphConfig(); + break; + case "availabledata.json": + json = GetAvailGraphData(); + break; + case "tempdata.json": + json = GetTempGraphData(); + break; + case "pressdata.json": + json = GetPressGraphData(); + break; + case "winddata.json": + json = GetWindGraphData(); + break; + case "wdirdata.json": + json = GetWindDirGraphData(); + break; + case "humdata.json": + json = GetHumGraphData(); + break; + case "raindata.json": + json = GetRainGraphData(); + break; + case "dailyrain.json": + json = GetDailyRainGraphData(); + break; + case "dailytemp.json": + json = GetDailyTempGraphData(); + break; + case "solardata.json": + json = GetSolarGraphData(); + break; + case "sunhours.json": + json = GetSunHoursGraphData(); + break; + case "airquality.json": + json = GetAqGraphData(); + break; + } + + try + { + var dest = cumulus.GraphDataFiles[i].LocalPath + cumulus.GraphDataFiles[i].LocalFileName; + using (var file = new StreamWriter(dest, false)) + { + file.WriteLine(json); + file.Close(); + } + + // The config files only need creating once per change + if (cumulus.GraphDataFiles[i].LocalFileName == "availabledata.json" || cumulus.GraphDataFiles[i].LocalFileName == "graphconfig.json") + { + cumulus.GraphDataFiles[i].CreateRequired = false; + } + } + catch (Exception ex) + { + cumulus.LogMessage($"Error writing {cumulus.GraphDataFiles[i].LocalFileName}: {ex}"); + } + } + } + } + + public void CreateEodGraphDataFiles() + { + string json = ""; + for (var i = 0; i < cumulus.GraphDataEodFiles.Length; i++) + { + if (cumulus.GraphDataEodFiles[i].Create) + { + switch (cumulus.GraphDataEodFiles[i].LocalFileName) + { + case "alldailytempdata.json": + json = GetAllDailyTempGraphData(); + break; + case "alldailypressdata.json": + json = GetAllDailyPressGraphData(); + break; + case "alldailywinddata.json": + json = GetAllDailyWindGraphData(); + break; + case "alldailyhumdata.json": + json = GetAllDailyHumGraphData(); + break; + case "alldailyraindata.json": + json = GetAllDailyRainGraphData(); + break; + case "alldailysolardata.json": + json = GetAllDailySolarGraphData(); + break; + case "alldailydegdaydata.json": + json = GetAllDegreeDaysGraphData(); + break; + case "alltempsumdata.json": + json = GetAllTempSumGraphData(); + break; + } + + try + { + var dest = cumulus.GraphDataEodFiles[i].LocalPath + cumulus.GraphDataEodFiles[i].LocalFileName; + using (var file = new StreamWriter(dest, false)) + { + file.WriteLine(json); + file.Close(); + } + // Now set the flag that upload is required (if enabled) + cumulus.GraphDataEodFiles[i].FtpRequired = true; + } + catch (Exception ex) + { + cumulus.LogMessage($"Error writing {cumulus.GraphDataEodFiles[i].LocalFileName}: {ex}"); + } + } + } + } + + public string GetSolarGraphData() + { + var InvC = new CultureInfo(""); + var sb = new StringBuilder("{"); + + lock (GraphDataList) + { + if (cumulus.GraphOptions.UVVisible) + { + sb.Append("\"UV\":["); + for (var i = 0; i < GraphDataList.Count; i++) + { + sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].uvindex.ToString(cumulus.UVFormat, InvC)}]"); + if (i < GraphDataList.Count - 1) + sb.Append(","); + } + sb.Append("]"); + } + + if (cumulus.GraphOptions.SolarVisible) + { + if (cumulus.GraphOptions.UVVisible) + { + sb.Append(","); + } + + sb.Append("\"SolarRad\":["); + for (var i = 0; i < GraphDataList.Count; i++) + { + sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{(int)GraphDataList[i].solarrad}]"); + if (i < GraphDataList.Count - 1) + sb.Append(","); + } + + + sb.Append("],\"CurrentSolarMax\":["); + for (var i = 0; i < GraphDataList.Count; i++) + { + sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{(int)GraphDataList[i].solarmax}]"); + if (i < GraphDataList.Count - 1) + sb.Append(","); + } + sb.Append("]"); + } + } + sb.Append("}"); + return sb.ToString(); + } + + public string GetRainGraphData() + { + var InvC = new CultureInfo(""); + var sb = new StringBuilder("{\"rfall\":["); + lock (GraphDataList) + { + for (var i = 0; i < GraphDataList.Count; i++) + { + sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].RainToday.ToString(cumulus.RainFormat, InvC)}]"); + if (i < GraphDataList.Count - 1) + sb.Append(","); + } + + sb.Append("],\"rrate\":["); + for (var i = 0; i < GraphDataList.Count; i++) + { + sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].rainrate.ToString(cumulus.RainFormat, InvC)}]"); + if (i < GraphDataList.Count - 1) + sb.Append(","); + } + } + sb.Append("]}"); + return sb.ToString(); + } + + public string GetHumGraphData() + { + var sb = new StringBuilder("{", 10240); + var append = false; + lock (GraphDataList) + { + if (cumulus.GraphOptions.OutHumVisible) + { + sb.Append("\"hum\":["); + for (var i = 0; i < GraphDataList.Count; i++) + { + sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].humidity}]"); + if (i < GraphDataList.Count - 1) + sb.Append(","); + } + sb.Append("]"); + append = true; + } + + if (cumulus.GraphOptions.InHumVisible) + { + if (append) + { + sb.Append(","); + } + sb.Append("\"inhum\":["); + for (var i = 0; i < GraphDataList.Count; i++) + { + sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].inhumidity}]"); + if (i < GraphDataList.Count - 1) + sb.Append(","); + } + sb.Append("]"); + } + } + sb.Append("}"); + return sb.ToString(); + } + + public string GetWindDirGraphData() + { + var sb = new StringBuilder("{\"bearing\":["); + lock (GraphDataList) + { + for (var i = 0; i < GraphDataList.Count; i++) + { + sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].winddir}]"); + if (i < GraphDataList.Count - 1) + sb.Append(","); + } + + sb.Append("],\"avgbearing\":["); + for (var i = 0; i < GraphDataList.Count; i++) + { + sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].avgwinddir}]"); + if (i < GraphDataList.Count - 1) + sb.Append(","); + } + } + sb.Append("]}"); + return sb.ToString(); + } + + public string GetWindGraphData() + { + var InvC = new CultureInfo(""); + var sb = new StringBuilder("{\"wgust\":["); + lock (GraphDataList) + { + for (var i = 0; i < GraphDataList.Count; i++) + { + sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].windgust.ToString(cumulus.WindFormat, InvC)}]"); + if (i < GraphDataList.Count - 1) + sb.Append(","); + } + + sb.Append("],\"wspeed\":["); + for (var i = 0; i < GraphDataList.Count; i++) + { + sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].windspeed.ToString(cumulus.WindAvgFormat, InvC)}]"); + if (i < GraphDataList.Count - 1) + sb.Append(","); + } + } + sb.Append("]}"); + return sb.ToString(); + } + + public string GetPressGraphData() + { + var InvC = new CultureInfo(""); + StringBuilder sb = new StringBuilder("{\"press\":["); + lock (GraphDataList) + { + for (var i = 0; i < GraphDataList.Count; i++) + { + sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].pressure.ToString(cumulus.PressFormat, InvC)}]"); + + if (i < GraphDataList.Count - 1) + sb.Append(","); + } + } + sb.Append("]}"); + return sb.ToString(); + } + + public string GetTempGraphData() + { + var InvC = new CultureInfo(""); + var append = false; + StringBuilder sb = new StringBuilder("{", 10240); + lock (GraphDataList) + { + if (cumulus.GraphOptions.InTempVisible) + { + if (append) + { + sb.Append(","); + } + sb.Append("\"intemp\":["); + for (var i = 0; i < GraphDataList.Count; i++) + { + sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].insidetemp.ToString(cumulus.TempFormat, InvC)}]"); + if (i < GraphDataList.Count - 1) + sb.Append(","); + } + sb.Append("]"); + append = true; + } + + if (cumulus.GraphOptions.DPVisible) + { + if (append) + { + sb.Append(","); + } + sb.Append("\"dew\":["); + for (var i = 0; i < GraphDataList.Count; i++) + { + sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].dewpoint.ToString(cumulus.TempFormat, InvC)}]"); + if (i < GraphDataList.Count - 1) + sb.Append(","); + } + sb.Append("]"); + append = true; + } + + if (cumulus.GraphOptions.AppTempVisible) + { + if (append) + { + sb.Append(","); + } + sb.Append("\"apptemp\":["); + for (var i = 0; i < GraphDataList.Count; i++) + { + sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].apptemp.ToString(cumulus.TempFormat, InvC)}]"); + if (i < GraphDataList.Count - 1) + sb.Append(","); + } + sb.Append("]"); + append = true; + } + + if (cumulus.GraphOptions.FeelsLikeVisible) + { + if (append) + { + sb.Append(","); + } + sb.Append("\"feelslike\":["); + for (var i = 0; i < GraphDataList.Count; i++) + { + sb.Append("[" + DateTimeToUnix(GraphDataList[i].timestamp) * 1000 + "," + GraphDataList[i].feelslike.ToString(cumulus.TempFormat, InvC) + "]"); + if (i < GraphDataList.Count - 1) + sb.Append(","); + } + sb.Append("]"); + append = true; + } + + if (cumulus.GraphOptions.WCVisible) + { + if (append) + { + sb.Append(","); + } + sb.Append("\"wchill\":["); + for (var i = 0; i < GraphDataList.Count; i++) + { + sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].windchill.ToString(cumulus.TempFormat, InvC)}]"); + if (i < GraphDataList.Count - 1) + sb.Append(","); + } + sb.Append("]"); + append = true; + } + + if (cumulus.GraphOptions.HIVisible) + { + if (append) + { + sb.Append(","); + } + sb.Append("\"heatindex\":["); + for (var i = 0; i < GraphDataList.Count; i++) + { + sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].heatindex.ToString(cumulus.TempFormat, InvC)}]"); + if (i < GraphDataList.Count - 1) + sb.Append(","); + } + sb.Append("]"); + append = true; + } + + if (cumulus.GraphOptions.TempVisible) + { + if (append) + { + sb.Append(","); + } + sb.Append("\"temp\":["); + for (var i = 0; i < GraphDataList.Count; i++) + { + sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].temperature.ToString(cumulus.TempFormat, InvC)}]"); + if (i < GraphDataList.Count - 1) + sb.Append(","); + } + sb.Append("]"); + append = true; + } + + if (cumulus.GraphOptions.HumidexVisible) + { + if (append) + { + sb.Append(","); + } + sb.Append("\"humidex\":["); + for (var i = 0; i < GraphDataList.Count; i++) + { + sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{GraphDataList[i].humidex.ToString(cumulus.TempFormat, InvC)}]"); + if (i < GraphDataList.Count - 1) + sb.Append(","); + } + sb.Append("]"); + append = true; + } + } + sb.Append("}"); + return sb.ToString(); + } + + public string GetAqGraphData() + { + var InvC = new CultureInfo(""); + var sb = new StringBuilder("{"); + // Check if we are to generate AQ data at all. Only if a primary sensor is defined and it isn't the Indoor AirLink + if (cumulus.StationOptions.PrimaryAqSensor > (int)Cumulus.PrimaryAqSensor.Undefined + && cumulus.StationOptions.PrimaryAqSensor != (int)Cumulus.PrimaryAqSensor.AirLinkIndoor) + { + sb.Append("\"pm2p5\":["); + lock (GraphDataList) + { + for (var i = 0; i < GraphDataList.Count; i++) + { + var val = GraphDataList[i].pm2p5 == -1 ? "null" : GraphDataList[i].pm2p5.ToString("F1", InvC); + sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{val}]"); + if (i < GraphDataList.Count - 1) + sb.Append(","); + } + sb.Append("]"); + // Only the AirLink and Ecowitt CO2 servers provide PM10 values at the moment + if (cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.AirLinkOutdoor || + cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.AirLinkIndoor || + cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.EcowittCO2) + { + sb.Append(",\"pm10\":["); + for (var i = 0; i < GraphDataList.Count; i++) + { + var val = GraphDataList[i].pm10 == -1 ? "null" : GraphDataList[i].pm10.ToString("F1", InvC); + sb.Append($"[{DateTimeToUnix(GraphDataList[i].timestamp) * 1000},{val}]"); + if (i < GraphDataList.Count - 1) + sb.Append(","); + } + sb.Append("]"); + } + } + } + + sb.Append("}"); + return sb.ToString(); + } + + public void AddRecentDataEntry(DateTime timestamp, double windAverage, double recentMaxGust, double windLatest, int bearing, int avgBearing, double outsidetemp, + double windChill, double dewpoint, double heatIndex, int humidity, double pressure, double rainToday, double solarRad, double uv, double rainCounter, double feelslike, double humidex) + { + try + { + RecentDataDb.InsertOrReplace(new RecentData() + { + Timestamp = timestamp, + DewPoint = dewpoint, + HeatIndex = heatIndex, + Humidity = humidity, + OutsideTemp = outsidetemp, + Pressure = pressure, + RainToday = rainToday, + SolarRad = (int)solarRad, + UV = uv, + WindAvgDir = avgBearing, + WindGust = recentMaxGust, + WindLatest = windLatest, + WindChill = windChill, + WindDir = bearing, + WindSpeed = windAverage, + raincounter = rainCounter, + FeelsLike = feelslike, + Humidex = humidex + }); + } + catch (Exception ex) + { + cumulus.LogDebugMessage("AddRecentDataEntry: " + ex.Message); + } + } + + private void CreateWxnowFile() + { + // Jun 01 2003 08:07 + // 272/000g006t069r010p030P020h61b10150 + + // 272 - wind direction - 272 degrees + // 010 - wind speed - 10 mph + + // g015 - wind gust - 15 mph + // t069 - temperature - 69 degrees F + // r010 - rain in last hour in hundredths of an inch - 0.1 inches + // p030 - rain in last 24 hours in hundredths of an inch - 0.3 inches + // P020 - rain since midnight in hundredths of an inch - 0.2 inches + // h61 - humidity 61% (00 = 100%) + // b10153 - barometric pressure in tenths of a millibar - 1015.3 millibars + + var filename = cumulus.AppDir + cumulus.WxnowFile; + var timestamp = DateTime.Now.ToString(@"MMM dd yyyy HH\:mm"); + + int mphwind = Convert.ToInt32(ConvertUserWindToMPH(WindAverage)); + int mphgust = Convert.ToInt32(ConvertUserWindToMPH(RecentMaxGust)); + // ftemp = trunc(TempF(OutsideTemp)); + string ftempstr = APRStemp(OutdoorTemperature); + int in100rainlasthour = Convert.ToInt32(ConvertUserRainToIn(RainLastHour) * 100); + int in100rainlast24hours = Convert.ToInt32(ConvertUserRainToIn(RainLast24Hour) * 100); + int in100raintoday; + if (cumulus.RolloverHour == 0) + // use today's rain for safety + in100raintoday = Convert.ToInt32(ConvertUserRainToIn(RainToday) * 100); + else + // 0900 day, use midnight calculation + in100raintoday = Convert.ToInt32(ConvertUserRainToIn(RainSinceMidnight) * 100); + int mb10press = Convert.ToInt32(ConvertUserPressToMB(AltimeterPressure) * 10); + // For 100% humidity, send zero. For zero humidity, send 1 + int hum; + if (OutdoorHumidity == 0) + hum = 1; + else if (OutdoorHumidity == 100) + hum = 0; + else + hum = OutdoorHumidity; + + string data = String.Format("{0:000}/{1:000}g{2:000}t{3}r{4:000}p{5:000}P{6:000}h{7:00}b{8:00000}", AvgBearing, mphwind, mphgust, ftempstr, in100rainlasthour, + in100rainlast24hours, in100raintoday, hum, mb10press); + + if (cumulus.APRS.SendSolar) + { + data += APRSsolarradStr(SolarRad); + } + + using (StreamWriter file = new StreamWriter(filename, false)) + { + file.WriteLine(timestamp); + file.WriteLine(data); + file.Close(); + } + } + + private string APRSsolarradStr(double solarRad) + { + if (solarRad < 1000) + { + return 'L' + (Convert.ToInt32(solarRad)).ToString("D3"); + } + else + { + return 'l' + (Convert.ToInt32(solarRad - 1000)).ToString("D3"); + } + } + + private string APRStemp(double temp) + { + // input is in TempUnit units, convert to F for APRS + // and return three digits + int num; + + if (cumulus.Units.Temp == 0) + { + num = Convert.ToInt32(((temp * 1.8) + 32)); + } + + else + { + num = Convert.ToInt32(temp); + } + + if (num < 0) + { + num = -num; + return '-' + num.ToString("00"); + } + else + { + return num.ToString("000"); + } + } + + private double ConvertUserRainToIn(double value) + { + if (cumulus.Units.Rain == 1) + { + return value; + } + else + { + return value / 25.4; + } + } + + public double ConvertUserWindToMPH(double value) + { + switch (cumulus.Units.Wind) + { + case 0: + return value * 2.23693629; + case 1: + return value; + case 2: + return value * 0.621371; + case 3: + return value * 1.15077945; + default: + return 0; + } + } + + public double ConvertUserWindToKnots(double value) + { + switch (cumulus.Units.Wind) + { + case 0: + return value * 1.943844; + case 1: + return value * 0.8689758; + case 2: + return value * 0.5399565; + case 3: + return value; + default: + return 0; + } + } + + + public void ResetSunshineHours() // called at midnight irrespective of rollover time + { + YestSunshineHours = SunshineHours; + + cumulus.LogMessage("Reset sunshine hours, yesterday = " + YestSunshineHours); + + SunshineToMidnight = SunshineHours; + SunshineHours = 0; + StartOfDaySunHourCounter = SunHourCounter; + WriteYesterdayFile(); + } + + /* + private void RecalcSolarFactor(DateTime now) // called at midnight irrespective of rollover time + { + if (cumulus.SolarFactorSummer > 0 && cumulus.SolarFactorWinter > 0) + { + // Calculate the solar factor from the day of the year + // Use a cosine of the difference between summer and winter values + int doy = now.DayOfYear; + // take summer solistice as June 21 or December 21 (N & S hemispheres) - ignore leap years + // sol = day 172 (North) + // sol = day 355 (South) + int sol = cumulus.Latitude >= 0 ? 172 : 355; + int daysSinceSol = (doy - sol) % 365; + double multiplier = Math.Cos((daysSinceSol / 365) * 2 * Math.PI); // range +1/-1 + SolarFactor = (multiplier + 1) / 2; // bring it into the range 0-1 + } + else + { + SolarFactor = -1; + } + } + */ + + public void SwitchToNormalRunning() + { + cumulus.CurrentActivity = "Normal running"; + + DoDayResetIfNeeded(); + DoTrendValues(DateTime.Now); + cumulus.StartTimersAndSensors(); + } + + public void ResetMidnightRain(DateTime timestamp) + { + int mrrday = timestamp.Day; + + int mrrmonth = timestamp.Month; + + if (mrrday != MidnightRainResetDay) + { + midnightraincount = Raincounter; + RainSinceMidnight = 0; + MidnightRainResetDay = mrrday; + cumulus.LogMessage("Midnight rain reset, count = " + Raincounter + " time = " + timestamp.ToShortTimeString()); + if ((mrrday == 1) && (mrrmonth == 1) && (cumulus.StationType == StationTypes.VantagePro)) + { + // special case: rain counter is about to be reset + cumulus.LogMessage("Special case, Davis station on 1st Jan. Set midnight rain count to zero"); + midnightraincount = 0; + } + } + } + + public void DoIndoorHumidity(int hum) + { + IndoorHumidity = hum; + HaveReadData = true; + } + + public void DoIndoorTemp(double temp) + { + IndoorTemperature = temp + cumulus.Calib.InTemp.Offset; + HaveReadData = true; + } + + public void DoOutdoorHumidity(int humpar, DateTime timestamp) + { + // Spike check + if ((previousHum != 999) && (Math.Abs(humpar - previousHum) > cumulus.Spike.HumidityDiff)) + { + cumulus.LogSpikeRemoval("Humidity difference greater than specified; reading ignored"); + cumulus.LogSpikeRemoval($"NewVal={humpar} OldVal={previousHum} SpikeHumidityDiff={cumulus.Spike.HumidityDiff:F1}"); + lastSpikeRemoval = DateTime.Now; + cumulus.SpikeAlarm.LastError = $"Humidity difference greater than spike value - NewVal={humpar} OldVal={previousHum}"; + cumulus.SpikeAlarm.Triggered = true; + return; + } + previousHum = humpar; + + if ((humpar >= 98) && cumulus.StationOptions.Humidity98Fix) + { + OutdoorHumidity = 100; + } + else + { + OutdoorHumidity = humpar; + } + + // apply offset and multipliers and round. This is different to C1, which truncates. I'm not sure why C1 does that + OutdoorHumidity = (int)Math.Round((OutdoorHumidity * OutdoorHumidity * cumulus.Calib.Hum.Mult2) + (OutdoorHumidity * cumulus.Calib.Hum.Mult) + cumulus.Calib.Hum.Offset); + + if (OutdoorHumidity < 0) + { + OutdoorHumidity = 0; + } + if (OutdoorHumidity > 100) + { + OutdoorHumidity = 100; + } + + if (OutdoorHumidity > HiLoToday.HighHumidity) + { + HiLoToday.HighHumidity = OutdoorHumidity; + HiLoToday.HighHumidityTime = timestamp; + WriteTodayFile(timestamp, false); + } + if (OutdoorHumidity < HiLoToday.LowHumidity) + { + HiLoToday.LowHumidity = OutdoorHumidity; + HiLoToday.LowHumidityTime = timestamp; + WriteTodayFile(timestamp, false); + } + if (OutdoorHumidity > ThisMonth.HighHumidity.Val) + { + ThisMonth.HighHumidity.Val = OutdoorHumidity; + ThisMonth.HighHumidity.Ts = timestamp; + WriteMonthIniFile(); + } + if (OutdoorHumidity < ThisMonth.LowHumidity.Val) + { + ThisMonth.LowHumidity.Val = OutdoorHumidity; + ThisMonth.LowHumidity.Ts = timestamp; + WriteMonthIniFile(); + } + if (OutdoorHumidity > ThisYear.HighHumidity.Val) + { + ThisYear.HighHumidity.Val = OutdoorHumidity; + ThisYear.HighHumidity.Ts = timestamp; + WriteYearIniFile(); + } + if (OutdoorHumidity < ThisYear.LowHumidity.Val) + { + ThisYear.LowHumidity.Val = OutdoorHumidity; + ThisYear.LowHumidity.Ts = timestamp; + WriteYearIniFile(); + } + if (OutdoorHumidity > AllTime.HighHumidity.Val) + { + SetAlltime(AllTime.HighHumidity, OutdoorHumidity, timestamp); + } + CheckMonthlyAlltime("HighHumidity", OutdoorHumidity, true, timestamp); + if (OutdoorHumidity < AllTime.LowHumidity.Val) + { + SetAlltime(AllTime.LowHumidity, OutdoorHumidity, timestamp); + } + CheckMonthlyAlltime("LowHumidity", OutdoorHumidity, false, timestamp); + HaveReadData = true; + } + + public double CalibrateTemp(double temp) + { + return (temp * temp * cumulus.Calib.Temp.Mult2) + (temp * cumulus.Calib.Temp.Mult) + cumulus.Calib.Temp.Offset; + } + + public void DoOutdoorTemp(double temp, DateTime timestamp) + { + // Spike removal is in Celsius + var tempC = ConvertUserTempToC(temp); + if (((Math.Abs(tempC - previousTemp) > cumulus.Spike.TempDiff) && (previousTemp != 999)) || + tempC >= cumulus.Limit.TempHigh || tempC <= cumulus.Limit.TempLow) + { + lastSpikeRemoval = DateTime.Now; + cumulus.SpikeAlarm.LastError = $"Temp difference greater than spike value - NewVal={tempC.ToString(cumulus.TempFormat)} OldVal={previousTemp.ToString(cumulus.TempFormat)}"; + cumulus.SpikeAlarm.Triggered = true; + cumulus.LogSpikeRemoval("Temp difference greater than specified; reading ignored"); + cumulus.LogSpikeRemoval($"NewVal={tempC.ToString(cumulus.TempFormat)} OldVal={previousTemp.ToString(cumulus.TempFormat)} SpikeTempDiff={cumulus.Spike.TempDiff.ToString(cumulus.TempFormat)} HighLimit={cumulus.Limit.TempHigh.ToString(cumulus.TempFormat)} LowLimit={cumulus.Limit.TempLow.ToString(cumulus.TempFormat)}"); + return; + } + previousTemp = tempC; + + // UpdateStatusPanel; + // update global temp + OutdoorTemperature = CalibrateTemp(temp); + + double tempinF = ConvertUserTempToF(OutdoorTemperature); + double tempinC = ConvertUserTempToC(OutdoorTemperature); + + first_temp = false; + + // Does this reading set any records or trigger any alarms? + if (OutdoorTemperature > AllTime.HighTemp.Val) + SetAlltime(AllTime.HighTemp, OutdoorTemperature, timestamp); + + cumulus.HighTempAlarm.Triggered = DoAlarm(OutdoorTemperature, cumulus.HighTempAlarm.Value, cumulus.HighTempAlarm.Enabled, true); + + if (OutdoorTemperature < AllTime.LowTemp.Val) + SetAlltime(AllTime.LowTemp, OutdoorTemperature, timestamp); + + cumulus.LowTempAlarm.Triggered = DoAlarm(OutdoorTemperature, cumulus.LowTempAlarm.Value, cumulus.LowTempAlarm.Enabled, false); + + CheckMonthlyAlltime("HighTemp", OutdoorTemperature, true, timestamp); + CheckMonthlyAlltime("LowTemp", OutdoorTemperature, false, timestamp); + + if (OutdoorTemperature > HiLoToday.HighTemp) + { + HiLoToday.HighTemp = OutdoorTemperature; + HiLoToday.HighTempTime = timestamp; + WriteTodayFile(timestamp, false); + } + + if (OutdoorTemperature < HiLoToday.LowTemp) + { + HiLoToday.LowTemp = OutdoorTemperature; + HiLoToday.LowTempTime = timestamp; + WriteTodayFile(timestamp, false); + } + + if (OutdoorTemperature > ThisMonth.HighTemp.Val) + { + ThisMonth.HighTemp.Val = OutdoorTemperature; + ThisMonth.HighTemp.Ts = timestamp; + WriteMonthIniFile(); + } + + if (OutdoorTemperature < ThisMonth.LowTemp.Val) + { + ThisMonth.LowTemp.Val = OutdoorTemperature; + ThisMonth.LowTemp.Ts = timestamp; + WriteMonthIniFile(); + } + + if (OutdoorTemperature > ThisYear.HighTemp.Val) + { + ThisYear.HighTemp.Val = OutdoorTemperature; + ThisYear.HighTemp.Ts = timestamp; + WriteYearIniFile(); + } + + if (OutdoorTemperature < ThisYear.LowTemp.Val) + { + ThisYear.LowTemp.Val = OutdoorTemperature; + ThisYear.LowTemp.Ts = timestamp; + WriteYearIniFile(); + } + + // Calculate temperature range + HiLoToday.TempRange = HiLoToday.HighTemp - HiLoToday.LowTemp; + + if ((cumulus.StationOptions.CalculatedDP || cumulus.DavisStation) && (OutdoorHumidity != 0) && (!cumulus.FineOffsetStation)) + { + // Calculate DewPoint. + // dewpoint = TempinC + ((0.13 * TempinC) + 13.6) * Ln(humidity / 100); + OutdoorDewpoint = ConvertTempCToUser(MeteoLib.DewPoint(tempinC, OutdoorHumidity)); + + CheckForDewpointHighLow(timestamp); + } + + // Calculate cloudbase + if (cumulus.CloudBaseInFeet) + { + CloudBase = (int)Math.Floor(((tempinF - ConvertUserTempToF(OutdoorDewpoint)) / 4.4) * 1000); + if (CloudBase < 0) + CloudBase = 0; + } + else + { + CloudBase = (int)Math.Floor((((tempinF - ConvertUserTempToF(OutdoorDewpoint)) / 4.4) * 1000) / 3.2808399); + if (CloudBase < 0) + CloudBase = 0; + } + + HeatIndex = ConvertTempCToUser(MeteoLib.HeatIndex(tempinC, OutdoorHumidity)); + + if (HeatIndex > HiLoToday.HighHeatIndex) + { + HiLoToday.HighHeatIndex = HeatIndex; + HiLoToday.HighHeatIndexTime = timestamp; + WriteTodayFile(timestamp, false); + } + + if (HeatIndex > ThisMonth.HighHeatIndex.Val) + { + ThisMonth.HighHeatIndex.Val = HeatIndex; + ThisMonth.HighHeatIndex.Ts = timestamp; + WriteMonthIniFile(); + } + + if (HeatIndex > ThisYear.HighHeatIndex.Val) + { + ThisYear.HighHeatIndex.Val = HeatIndex; + ThisYear.HighHeatIndex.Ts = timestamp; + WriteYearIniFile(); + } + + if (HeatIndex > AllTime.HighHeatIndex.Val) + SetAlltime(AllTime.HighHeatIndex, HeatIndex, timestamp); + + CheckMonthlyAlltime("HighHeatIndex", HeatIndex, true, timestamp); + + //DoApparentTemp(timestamp); + + // Find estimated wet bulb temp. First time this is called, required variables may not have been set up yet + try + { + WetBulb = ConvertTempCToUser(MeteoLib.CalculateWetBulbC(tempinC, ConvertUserTempToC(OutdoorDewpoint), ConvertUserPressToMB(Pressure))); + } + catch + { + WetBulb = OutdoorTemperature; + } + + TempReadyToPlot = true; + HaveReadData = true; + } + + public void DoApparentTemp(DateTime timestamp) + { + // Calculates Apparent Temperature + // See http://www.bom.gov.au/info/thermal_stress/#atapproximation + + // don't try to calculate apparent if we haven't yet had wind and temp readings + //if (TempReadyToPlot && WindReadyToPlot) + //{ + //ApparentTemperature = + //ConvertTempCToUser(ConvertUserTempToC(OutdoorTemperature) + (0.33 * MeteoLib.ActualVapourPressure(ConvertUserTempToC(OutdoorTemperature), OutdoorHumidity)) - + // (0.7 * ConvertUserWindToMS(WindAverage)) - 4); + ApparentTemperature = ConvertTempCToUser(MeteoLib.ApparentTemperature(ConvertUserTempToC(OutdoorTemperature), ConvertUserWindToMS(WindAverage), OutdoorHumidity)); + + + // we will tag on the THW Index here + THWIndex = ConvertTempCToUser(MeteoLib.THWIndex(ConvertUserTempToC(OutdoorTemperature), OutdoorHumidity, ConvertUserWindToKPH(WindAverage))); + + if (ApparentTemperature > HiLoToday.HighAppTemp) + { + HiLoToday.HighAppTemp = ApparentTemperature; + HiLoToday.HighAppTempTime = timestamp; + WriteTodayFile(timestamp, false); + } + + if (ApparentTemperature < HiLoToday.LowAppTemp) + { + HiLoToday.LowAppTemp = ApparentTemperature; + HiLoToday.LowAppTempTime = timestamp; + WriteTodayFile(timestamp, false); + } + + if (ApparentTemperature > ThisMonth.HighAppTemp.Val) + { + ThisMonth.HighAppTemp.Val = ApparentTemperature; + ThisMonth.HighAppTemp.Ts = timestamp; + WriteMonthIniFile(); + } + + if (ApparentTemperature < ThisMonth.LowAppTemp.Val) + { + ThisMonth.LowAppTemp.Val = ApparentTemperature; + ThisMonth.LowAppTemp.Ts = timestamp; + WriteMonthIniFile(); + } + + if (ApparentTemperature > ThisYear.HighAppTemp.Val) + { + ThisYear.HighAppTemp.Val = ApparentTemperature; + ThisYear.HighAppTemp.Ts = timestamp; + WriteYearIniFile(); + } + + if (ApparentTemperature < ThisYear.LowAppTemp.Val) + { + ThisYear.LowAppTemp.Val = ApparentTemperature; + ThisYear.LowAppTemp.Ts = timestamp; + WriteYearIniFile(); + } + + if (ApparentTemperature > AllTime.HighAppTemp.Val) + SetAlltime(AllTime.HighAppTemp, ApparentTemperature, timestamp); + + if (ApparentTemperature < AllTime.LowAppTemp.Val) + SetAlltime(AllTime.LowAppTemp, ApparentTemperature, timestamp); + + CheckMonthlyAlltime("HighAppTemp", ApparentTemperature, true, timestamp); + CheckMonthlyAlltime("LowAppTemp", ApparentTemperature, false, timestamp); + //} + } + + public void DoWindChill(double chillpar, DateTime timestamp) + { + bool chillvalid = true; + + if (cumulus.StationOptions.CalculatedWC) + { + // don"t try to calculate windchill if we haven"t yet had wind and temp readings + if (TempReadyToPlot && WindReadyToPlot) + { + double TempinC = ConvertUserTempToC(OutdoorTemperature); + double windinKPH = ConvertUserWindToKPH(WindAverage); + WindChill = ConvertTempCToUser(MeteoLib.WindChill(TempinC, windinKPH)); + } + else + { + chillvalid = false; + } + } + else + { + WindChill = chillpar; + } + + if (chillvalid) + { + //WindChillReadyToPlot = true; + + if (WindChill < HiLoToday.LowWindChill) + { + HiLoToday.LowWindChill = WindChill; + HiLoToday.LowWindChillTime = timestamp; + WriteTodayFile(timestamp, false); + } + + if (WindChill < ThisMonth.LowChill.Val) + { + ThisMonth.LowChill.Val = WindChill; + ThisMonth.LowChill.Ts = timestamp; + WriteMonthIniFile(); + } + + if (WindChill < ThisYear.LowChill.Val) + { + ThisYear.LowChill.Val = WindChill; + ThisYear.LowChill.Ts = timestamp; + WriteYearIniFile(); + } + + // All time wind chill + if (WindChill < AllTime.LowChill.Val) + { + SetAlltime(AllTime.LowChill, WindChill, timestamp); + } + + CheckMonthlyAlltime("LowChill", WindChill, false, timestamp); + } + } + + public void DoFeelsLike(DateTime timestamp) + { + FeelsLike = ConvertTempCToUser(MeteoLib.FeelsLike(ConvertUserTempToC(OutdoorTemperature), ConvertUserWindToKPH(WindAverage), OutdoorHumidity)); + + if (FeelsLike > HiLoToday.HighFeelsLike) + { + HiLoToday.HighFeelsLike = FeelsLike; + HiLoToday.HighFeelsLikeTime = timestamp; + WriteTodayFile(timestamp, false); + } + + if (FeelsLike < HiLoToday.LowFeelsLike) + { + HiLoToday.LowFeelsLike = FeelsLike; + HiLoToday.LowFeelsLikeTime = timestamp; + WriteTodayFile(timestamp, false); + } + + if (FeelsLike > ThisMonth.HighFeelsLike.Val) + { + ThisMonth.HighFeelsLike.Val = FeelsLike; + ThisMonth.HighFeelsLike.Ts = timestamp; + WriteMonthIniFile(); + } + + if (FeelsLike < ThisMonth.LowFeelsLike.Val) + { + ThisMonth.LowFeelsLike.Val = FeelsLike; + ThisMonth.LowFeelsLike.Ts = timestamp; + WriteMonthIniFile(); + } + + if (FeelsLike > ThisYear.HighFeelsLike.Val) + { + ThisYear.HighFeelsLike.Val = FeelsLike; + ThisYear.HighFeelsLike.Ts = timestamp; + WriteYearIniFile(); + } + + if (FeelsLike < ThisYear.LowFeelsLike.Val) + { + ThisYear.LowFeelsLike.Val = FeelsLike; + ThisYear.LowFeelsLike.Ts = timestamp; + WriteYearIniFile(); + } + + if (FeelsLike > AllTime.HighFeelsLike.Val) + SetAlltime(AllTime.HighFeelsLike, FeelsLike, timestamp); + + if (FeelsLike < AllTime.LowFeelsLike.Val) + SetAlltime(AllTime.LowFeelsLike, FeelsLike, timestamp); + + CheckMonthlyAlltime("HighFeelsLike", FeelsLike, true, timestamp); + CheckMonthlyAlltime("LowFeelsLike", FeelsLike, false, timestamp); + } + + public void DoHumidex(DateTime timestamp) + { + Humidex = MeteoLib.Humidex(ConvertUserTempToC(OutdoorTemperature), OutdoorHumidity); + + if (Humidex > HiLoToday.HighHumidex) + { + HiLoToday.HighHumidex = Humidex; + HiLoToday.HighHumidexTime = timestamp; + WriteTodayFile(timestamp, false); + } + + if (Humidex > ThisMonth.HighHumidex.Val) + { + ThisMonth.HighHumidex.Val = Humidex; + ThisMonth.HighHumidex.Ts = timestamp; + WriteMonthIniFile(); + } + + if (Humidex > ThisYear.HighHumidex.Val) + { + ThisYear.HighHumidex.Val = Humidex; + ThisYear.HighHumidex.Ts = timestamp; + WriteYearIniFile(); + } + + if (Humidex > AllTime.HighHumidex.Val) + SetAlltime(AllTime.HighHumidex, Humidex, timestamp); + + CheckMonthlyAlltime("HighHumidex", Humidex, true, timestamp); + } + + public void CheckForWindrunHighLow(DateTime timestamp) + { + DateTime adjustedtimestamp = timestamp.AddHours(cumulus.GetHourInc()); + + if (WindRunToday > ThisMonth.HighWindRun.Val) + { + ThisMonth.HighWindRun.Val = WindRunToday; + ThisMonth.HighWindRun.Ts = adjustedtimestamp; + WriteMonthIniFile(); + } + + if (WindRunToday > ThisYear.HighWindRun.Val) + { + ThisYear.HighWindRun.Val = WindRunToday; + ThisYear.HighWindRun.Ts = adjustedtimestamp; + WriteYearIniFile(); + } + + if (WindRunToday > AllTime.HighWindRun.Val) + { + SetAlltime(AllTime.HighWindRun, WindRunToday, adjustedtimestamp); + } + + CheckMonthlyAlltime("HighWindRun", WindRunToday, true, adjustedtimestamp); + } + + public void CheckForDewpointHighLow(DateTime timestamp) + { + if (OutdoorDewpoint > HiLoToday.HighDewPoint) + { + HiLoToday.HighDewPoint = OutdoorDewpoint; + HiLoToday.HighDewPointTime = timestamp; + WriteTodayFile(timestamp, false); + } + if (OutdoorDewpoint < HiLoToday.LowDewPoint) + { + HiLoToday.LowDewPoint = OutdoorDewpoint; + HiLoToday.LowDewPointTime = timestamp; + WriteTodayFile(timestamp, false); + } + if (OutdoorDewpoint > ThisMonth.HighDewPoint.Val) + { + ThisMonth.HighDewPoint.Val = OutdoorDewpoint; + ThisMonth.HighDewPoint.Ts = timestamp; + WriteMonthIniFile(); + } + if (OutdoorDewpoint < ThisMonth.LowDewPoint.Val) + { + ThisMonth.LowDewPoint.Val = OutdoorDewpoint; + ThisMonth.LowDewPoint.Ts = timestamp; + WriteMonthIniFile(); + } + if (OutdoorDewpoint > ThisYear.HighDewPoint.Val) + { + ThisYear.HighDewPoint.Val = OutdoorDewpoint; + ThisYear.HighDewPoint.Ts = timestamp; + WriteYearIniFile(); + } + if (OutdoorDewpoint < ThisYear.LowDewPoint.Val) + { + ThisYear.LowDewPoint.Val = OutdoorDewpoint; + ThisYear.LowDewPoint.Ts = timestamp; + WriteYearIniFile(); + } + ; + if (OutdoorDewpoint > AllTime.HighDewPoint.Val) + { + SetAlltime(AllTime.HighDewPoint, OutdoorDewpoint, timestamp); + } + if (OutdoorDewpoint < AllTime.LowDewPoint.Val) + SetAlltime(AllTime.LowDewPoint, OutdoorDewpoint, timestamp); + + CheckMonthlyAlltime("HighDewPoint", OutdoorDewpoint, true, timestamp); + CheckMonthlyAlltime("LowDewPoint", OutdoorDewpoint, false, timestamp); + } + + public void DoPressure(double sl, DateTime timestamp) + { + // Spike removal is in mb/hPa + var pressMB = ConvertUserPressToMB(sl); + if (((Math.Abs(pressMB - previousPress) > cumulus.Spike.PressDiff) && (previousPress != 9999)) || + pressMB >= cumulus.Limit.PressHigh || pressMB <= cumulus.Limit.PressLow) + { + cumulus.LogSpikeRemoval("Pressure difference greater than specified; reading ignored"); + cumulus.LogSpikeRemoval($"NewVal={pressMB:F1} OldVal={previousPress:F1} SpikePressDiff={cumulus.Spike.PressDiff:F1} HighLimit={cumulus.Limit.PressHigh:F1} LowLimit={cumulus.Limit.PressLow:F1}"); + lastSpikeRemoval = DateTime.Now; + cumulus.SpikeAlarm.LastError = $"Pressure difference greater than spike value - NewVal={pressMB:F1} OldVal={previousPress:F1}"; + cumulus.SpikeAlarm.Triggered = true; + return; + } + + previousPress = pressMB; + + Pressure = sl * cumulus.Calib.Press.Mult + cumulus.Calib.Press.Offset; + if (cumulus.Manufacturer == cumulus.DAVIS) + { + if (!cumulus.DavisOptions.UseLoop2) + { + // Loop2 data not available, just use sea level (for now, anyway) + AltimeterPressure = Pressure; + } + } + else + { + if (cumulus.Manufacturer == cumulus.OREGONUSB) + { + AltimeterPressure = ConvertPressMBToUser(StationToAltimeter(ConvertUserPressureToHPa(StationPressure), AltitudeM(cumulus.Altitude))); + } + else + { + // For all other stations, altimeter is same as sea-level + AltimeterPressure = Pressure; + } + } + + first_press = false; + + if (Pressure > AllTime.HighPress.Val) + { + SetAlltime(AllTime.HighPress, Pressure, timestamp); + } + + cumulus.HighPressAlarm.Triggered = DoAlarm(Pressure, cumulus.HighPressAlarm.Value, cumulus.HighPressAlarm.Enabled, true); + + if (Pressure < AllTime.LowPress.Val) + { + SetAlltime(AllTime.LowPress, Pressure, timestamp); + } + + cumulus.LowPressAlarm.Triggered = DoAlarm(Pressure, cumulus.LowPressAlarm.Value, cumulus.LowPressAlarm.Enabled, false); + CheckMonthlyAlltime("LowPress", Pressure, false, timestamp); + CheckMonthlyAlltime("HighPress", Pressure, true, timestamp); + + if (Pressure > HiLoToday.HighPress) + { + HiLoToday.HighPress = Pressure; + HiLoToday.HighPressTime = timestamp; + WriteTodayFile(timestamp, false); + } + + if (Pressure < HiLoToday.LowPress) + { + HiLoToday.LowPress = Pressure; + HiLoToday.LowPressTime = timestamp; + WriteTodayFile(timestamp, false); + } + + if (Pressure > ThisMonth.HighPress.Val) + { + ThisMonth.HighPress.Val = Pressure; + ThisMonth.HighPress.Ts = timestamp; + WriteMonthIniFile(); + } + + if (Pressure < ThisMonth.LowPress.Val) + { + ThisMonth.LowPress.Val = Pressure; + ThisMonth.LowPress.Ts = timestamp; + WriteMonthIniFile(); + } + + if (Pressure > ThisYear.HighPress.Val) + { + ThisYear.HighPress.Val = Pressure; + ThisYear.HighPress.Ts = timestamp; + WriteYearIniFile(); + } + + if (Pressure < ThisYear.LowPress.Val) + { + ThisYear.LowPress.Val = Pressure; + ThisYear.LowPress.Ts = timestamp; + WriteYearIniFile(); + } + + PressReadyToPlot = true; + HaveReadData = true; + } + + protected void DoPressTrend(string trend) + { + if (cumulus.StationOptions.UseCumulusPresstrendstr || cumulus.Manufacturer == cumulus.DAVIS) + { + UpdatePressureTrendString(); + } + else + { + Presstrendstr = trend; + } + } + + public void DoRain(double total, double rate, DateTime timestamp) + { + DateTime readingTS = timestamp.AddHours(cumulus.GetHourInc()); + + //cumulus.LogDebugMessage($"DoRain: counter={total}, rate={rate}; RainToday={RainToday}, StartOfDay={raindaystart}"); + + // Spike removal is in mm + var rainRateMM = ConvertUserRainToMM(rate); + if (rainRateMM > cumulus.Spike.MaxRainRate) + { + cumulus.LogSpikeRemoval("Rain rate greater than specified; reading ignored"); + cumulus.LogSpikeRemoval($"Rate value = {rainRateMM:F2} SpikeMaxRainRate = {cumulus.Spike.MaxRainRate:F2}"); + lastSpikeRemoval = DateTime.Now; + cumulus.SpikeAlarm.LastError = $"Rain rate greater than spike value - value = {rainRateMM:F2}mm/hr"; + cumulus.SpikeAlarm.Triggered = true; + return; + } + + if ((CurrentDay != readingTS.Day) || (CurrentMonth != readingTS.Month) || (CurrentYear != readingTS.Year)) + { + // A reading has apparently arrived at the start of a new day, but before we have done the rollover + // Ignore it, as otherwise it may cause a new monthly record to be logged using last month's total + return; + } + + var previoustotal = Raincounter; + + double raintipthreshold = 0; ; + if (cumulus.Manufacturer == cumulus.DAVIS) // Davis can have either 0.2mm or 0.01in buckets, and the user could select to measure in mm or inches! + { + // If the bucket size is set, use that, otherwise infer from rain units + var bucketSize = cumulus.DavisOptions.RainGaugeType == -1 ? cumulus.Units.Rain : cumulus.DavisOptions.RainGaugeType; + + switch (bucketSize) + { + case 0: // 0.2 mm tips + // mm/mm (0.2) or mm/in (0.00787) + raintipthreshold = cumulus.Units.Rain == 0 ? 0.19 : 0.006; + break; + case 1: // 0.01 inch tips + // in/mm (0.254) or in/in (0.01) + raintipthreshold = cumulus.Units.Rain == 0 ? 0.2 : 0.009; + break; + case 2: // 0.01 mm tips + // mm/mm (0.1) or mm/in (0.0394) + raintipthreshold = cumulus.Units.Rain == 0 ? 0.09 : 0.003; + break; + case 3: // 0.001 inch tips + // in/mm (0.0254) or in/in (0.001) + raintipthreshold = cumulus.Units.Rain == 0 ? 0.02 : 0.0009; + break; + } + } + else + { + if (cumulus.Units.Rain == 0) + { + // mm + raintipthreshold = cumulus.Manufacturer == cumulus.INSTROMET ? 0.009 : 0.09; + } + else + { + // in + raintipthreshold = cumulus.Manufacturer == cumulus.INSTROMET ? 0.0003 : 0.009; + } + } + + Raincounter = total; + + //first_rain = false; + if (initialiseRainCounterOnFirstData) + { + raindaystart = Raincounter; + midnightraincount = Raincounter; + cumulus.LogMessage(" First rain data, raindaystart = " + raindaystart); + + initialiseRainCounterOnFirstData = false; + WriteTodayFile(timestamp, false); + HaveReadData = true; + return; + } + + // Has the rain total in the station been reset? + // raindaystart greater than current total, allow for rounding + if (raindaystart - Raincounter > 0.1) + { + if (FirstChanceRainReset) + // second consecutive reading with reset value + { + cumulus.LogMessage(" ****Rain counter reset confirmed: raindaystart = " + raindaystart + ", Raincounter = " + Raincounter); + + // set the start of day figure so it reflects the rain + // so far today + raindaystart = Raincounter - (RainToday / cumulus.Calib.Rain.Mult); + cumulus.LogMessage("Setting raindaystart to " + raindaystart); + + midnightraincount = Raincounter; + + FirstChanceRainReset = false; + } + else + { + cumulus.LogMessage(" ****Rain reset? First chance: raindaystart = " + raindaystart + ", Raincounter = " + Raincounter); + + // reset the counter to ignore this reading + Raincounter = previoustotal; + cumulus.LogMessage("Leaving counter at " + Raincounter); + + FirstChanceRainReset = true; + } + } + else + { + FirstChanceRainReset = false; + } + + if (rate > -1) + // Do rain rate + { + // scale rainfall rate + RainRate = rate * cumulus.Calib.Rain.Mult; + + if (RainRate > AllTime.HighRainRate.Val) + SetAlltime(AllTime.HighRainRate, RainRate, timestamp); + + CheckMonthlyAlltime("HighRainRate", RainRate, true, timestamp); + + cumulus.HighRainRateAlarm.Triggered = DoAlarm(RainRate, cumulus.HighRainRateAlarm.Value, cumulus.HighRainRateAlarm.Enabled, true); + + if (RainRate > HiLoToday.HighRainRate) + { + HiLoToday.HighRainRate = RainRate; + HiLoToday.HighRainRateTime = timestamp; + WriteTodayFile(timestamp, false); + } + + if (RainRate > ThisMonth.HighRainRate.Val) + { + ThisMonth.HighRainRate.Val = RainRate; + ThisMonth.HighRainRate.Ts = timestamp; + WriteMonthIniFile(); + } + + if (RainRate > ThisYear.HighRainRate.Val) + { + ThisYear.HighRainRate.Val = RainRate; + ThisYear.HighRainRate.Ts = timestamp; + WriteYearIniFile(); + } + } + + if (!FirstChanceRainReset) + { + // Has a tip occured? + if (total - previoustotal > raintipthreshold) + { + // rain has occurred + LastRainTip = timestamp.ToString("yyyy-MM-dd HH:mm"); + } + + // Calculate today"s rainfall + RainToday = Raincounter - raindaystart; + //cumulus.LogDebugMessage("Uncalibrated RainToday = " + RainToday); + + // scale for calibration + RainToday *= cumulus.Calib.Rain.Mult; + + // Calculate rain since midnight for Wunderground etc + double trendval = Raincounter - midnightraincount; + + // Round value as some values may have been read from log file and already rounded + trendval = Math.Round(trendval, cumulus.RainDPlaces); + + if (trendval < 0) + { + RainSinceMidnight = 0; + } + else + { + RainSinceMidnight = trendval * cumulus.Calib.Rain.Mult; + } + + // rain this month so far + RainMonth = rainthismonth + RainToday; + + // get correct date for rain records + var offsetdate = timestamp.AddHours(cumulus.GetHourInc()); + + // rain this year so far + RainYear = rainthisyear + RainToday; + + if (RainToday > AllTime.DailyRain.Val) + SetAlltime(AllTime.DailyRain, RainToday, offsetdate); + + CheckMonthlyAlltime("DailyRain", RainToday, true, timestamp); + + if (RainToday > ThisMonth.DailyRain.Val) + { + ThisMonth.DailyRain.Val = RainToday; + ThisMonth.DailyRain.Ts = offsetdate; + WriteMonthIniFile(); + } + + if (RainToday > ThisYear.DailyRain.Val) + { + ThisYear.DailyRain.Val = RainToday; + ThisYear.DailyRain.Ts = offsetdate; + WriteYearIniFile(); + } + + if (RainMonth > ThisYear.MonthlyRain.Val) + { + ThisYear.MonthlyRain.Val = RainMonth; + ThisYear.MonthlyRain.Ts = offsetdate; + WriteYearIniFile(); + } + + if (RainMonth > AllTime.MonthlyRain.Val) + SetAlltime(AllTime.MonthlyRain, RainMonth, offsetdate); + + CheckMonthlyAlltime("MonthlyRain", RainMonth, true, timestamp); + + cumulus.HighRainTodayAlarm.Triggered = DoAlarm(RainToday, cumulus.HighRainTodayAlarm.Value, cumulus.HighRainTodayAlarm.Enabled, true); + + // Yesterday"s rain - Scale for units + // rainyest = rainyesterday * RainMult; + + //RainReadyToPlot = true; + } + HaveReadData = true; + } + + public void DoOutdoorDewpoint(double dp, DateTime timestamp) + { + if (!cumulus.StationOptions.CalculatedDP) + { + if (ConvertUserTempToC(dp) <= cumulus.Limit.DewHigh) + { + OutdoorDewpoint = dp; + CheckForDewpointHighLow(timestamp); + } + else + { + var msg = $"Dew point greater than limit ({cumulus.Limit.DewHigh.ToString(cumulus.TempFormat)}); reading ignored: {dp.ToString(cumulus.TempFormat)}"; + lastSpikeRemoval = DateTime.Now; + cumulus.SpikeAlarm.LastError = msg; + cumulus.SpikeAlarm.Triggered = true; + cumulus.LogSpikeRemoval(msg); + } + } + } + + public string LastRainTip { get; set; } + + public void DoExtraHum(double hum, int channel) + { + if ((channel > 0) && (channel < 11)) + { + ExtraHum[channel] = (int)hum; + } + } + + public void DoExtraTemp(double temp, int channel) + { + if ((channel > 0) && (channel < 11)) + { + ExtraTemp[channel] = temp; + } + } + + public void DoUserTemp(double temp, int channel) + { + if ((channel > 0) && (channel < 11)) + { + UserTemp[channel] = temp; + } + } + + + public void DoExtraDP(double dp, int channel) + { + if ((channel > 0) && (channel < 11)) + { + ExtraDewPoint[channel] = dp; + } + } + + public void DoForecast(string forecast, bool hourly) + { + // store weather station forecast if available + + if (forecast != "") + { + wsforecast = forecast; + } + + if (!cumulus.UseCumulusForecast) + { + // user wants to display station forecast + forecaststr = wsforecast; + } + // determine whether we need to update the Cumulus forecast; user may have chosen to only update once an hour, but + // we still need to do that once to get an initial forecast + if ((!FirstForecastDone) || (!cumulus.HourlyForecast) || (hourly && cumulus.HourlyForecast)) + { + int bartrend; + if ((presstrendval >= -cumulus.FCPressureThreshold) && (presstrendval <= cumulus.FCPressureThreshold)) + bartrend = 0; + else if (presstrendval < 0) + bartrend = 2; + else + bartrend = 1; + + string windDir; + if (WindAverage < 0.1) + { + windDir = "calm"; + } + else + { + windDir = AvgBearingText; + } + + double lp; + double hp; + if (cumulus.FCpressinMB) + { + lp = cumulus.FClowpress; + hp = cumulus.FChighpress; + } + else + { + lp = cumulus.FClowpress / 0.0295333727; + hp = cumulus.FChighpress / 0.0295333727; + } + + CumulusForecast = BetelCast(ConvertUserPressureToHPa(Pressure), DateTime.Now.Month, windDir, bartrend, cumulus.Latitude > 0, hp, lp); + + if (cumulus.UseCumulusForecast) + { + // user wants to display Cumulus forecast + forecaststr = CumulusForecast; + } + } + + FirstForecastDone = true; + HaveReadData = true; + } + + public string forecaststr { get; set; } + + public string CumulusForecast { get; set; } + + public string wsforecast { get; set; } + + public bool FirstForecastDone = false; + + /// + /// Convert altitude from user units to metres + /// + /// + /// + public double AltitudeM(double altitude) + { + if (cumulus.AltitudeInFeet) + { + return altitude * 0.3048; + } + else + { + return altitude; + } + } + + /// + /// Convert pressure from user units to hPa + /// + /// + /// + public double ConvertUserPressureToHPa(double value) + { + if (cumulus.Units.Press == 2) + return value / 0.0295333727; + else + return value; + } + + public double StationToAltimeter(double pressureHPa, double elevationM) + { + // from MADIS API by NOAA Forecast Systems Lab, see http://madis.noaa.gov/madis_api.html + + double k1 = 0.190284; // discrepency with calculated k1 probably because Smithsonian used less precise gas constant and gravity values + double k2 = 8.4184960528E-5; // (standardLapseRate / standardTempK) * (Power(standardSLP, k1) + return Math.Pow(Math.Pow(pressureHPa - 0.3, k1) + (k2 * elevationM), 1 / k1); + } + + public bool PressReadyToPlot { get; set; } + + public bool first_press { get; set; } + + /* + private string TimeToStrHHMM(DateTime timestamp) + { + return timestamp.ToString("HHmm"); + } + */ + + public bool DoAlarm(double value, double threshold, bool enabled, bool testAbove) + { + if (enabled) + { + if (testAbove) + { + return value > threshold; + } + else + { + return value < threshold; + } + } + return false; + } + + public void DoWind(double gustpar, int bearingpar, double speedpar, DateTime timestamp) + { + // Spike removal is in m/s + var windGustMS = ConvertUserWindToMS(gustpar); + var windAvgMS = ConvertUserWindToMS(speedpar); + + if (((Math.Abs(windGustMS - previousGust) > cumulus.Spike.GustDiff) && (previousGust != 999)) || + ((Math.Abs(windAvgMS - previousWind) > cumulus.Spike.WindDiff) && (previousWind != 999)) || + windGustMS >= cumulus.Limit.WindHigh + ) + { + cumulus.LogSpikeRemoval("Wind or gust difference greater than specified; reading ignored"); + cumulus.LogSpikeRemoval($"Gust: NewVal={windGustMS:F1} OldVal={previousGust:F1} SpikeGustDiff={cumulus.Spike.GustDiff:F1} HighLimit={cumulus.Limit.WindHigh:F1}"); + cumulus.LogSpikeRemoval($"Wind: NewVal={windAvgMS:F1} OldVal={previousWind:F1} SpikeWindDiff={cumulus.Spike.WindDiff:F1}"); + lastSpikeRemoval = DateTime.Now; + cumulus.SpikeAlarm.LastError = $"Wind or gust difference greater than spike value - Gust: NewVal={windGustMS:F1}m/s OldVal={previousGust:F1}m/s - Wind: NewVal={windAvgMS:F1}m/s OldVal={previousWind:F1}m/s"; + cumulus.SpikeAlarm.Triggered = true; + return; + } + + previousGust = windGustMS; + previousWind = windAvgMS; + + // use bearing of zero when calm + if ((Math.Abs(gustpar) < 0.001) && cumulus.StationOptions.UseZeroBearing) + { + Bearing = 0; + } + else + { + Bearing = (bearingpar + (int)cumulus.Calib.WindDir.Offset) % 360; + if (Bearing < 0) + { + Bearing = 360 + Bearing; + } + + if (Bearing == 0) + { + Bearing = 360; + } + } + var uncalibratedgust = gustpar; + calibratedgust = uncalibratedgust * cumulus.Calib.WindGust.Mult; + WindLatest = calibratedgust; + windspeeds[nextwindvalue] = uncalibratedgust; + windbears[nextwindvalue] = Bearing; + + // Recalculate wind rose data + lock (windcounts) + { + for (int i = 0; i < cumulus.NumWindRosePoints; i++) + { + windcounts[i] = 0; + } + + for (int i = 0; i < numwindvalues; i++) + { + int j = (((windbears[i] * 100) + 1125) % 36000) / (int)Math.Floor(cumulus.WindRoseAngle * 100); + windcounts[j] += windspeeds[i]; + } + } + + if (numwindvalues < maxwindvalues) + { + numwindvalues++; + } + + nextwindvalue = (nextwindvalue + 1) % maxwindvalues; + if (calibratedgust > HiLoToday.HighGust) + { + HiLoToday.HighGust = calibratedgust; + HiLoToday.HighGustTime = timestamp; + HiLoToday.HighGustBearing = Bearing; + WriteTodayFile(timestamp, false); + } + if (calibratedgust > ThisMonth.HighGust.Val) + { + ThisMonth.HighGust.Val = calibratedgust; + ThisMonth.HighGust.Ts = timestamp; + WriteMonthIniFile(); + } + if (calibratedgust > ThisYear.HighGust.Val) + { + ThisYear.HighGust.Val = calibratedgust; + ThisYear.HighGust.Ts = timestamp; + WriteYearIniFile(); + } + // All time high gust? + if (calibratedgust > AllTime.HighGust.Val) + { + SetAlltime(AllTime.HighGust, calibratedgust, timestamp); + } + + // check for monthly all time records (and set) + CheckMonthlyAlltime("HighGust", calibratedgust, true, timestamp); + + WindRecent[nextwind].Gust = uncalibratedgust; + WindRecent[nextwind].Speed = speedpar; + WindRecent[nextwind].Timestamp = timestamp; + nextwind = (nextwind + 1) % MaxWindRecent; + if (cumulus.StationOptions.UseWind10MinAve) + { + int numvalues = 0; + double totalwind = 0; + for (int i = 0; i < MaxWindRecent; i++) + { + if (timestamp - WindRecent[i].Timestamp <= cumulus.AvgSpeedTime) + { + numvalues++; + if (cumulus.StationOptions.UseSpeedForAvgCalc) + { + totalwind += WindRecent[i].Speed; + } + else + { + totalwind += WindRecent[i].Gust; + } + } + } + // average the values + WindAverage = totalwind / numvalues; + //cumulus.LogDebugMessage("next=" + nextwind + " wind=" + uncalibratedgust + " tot=" + totalwind + " numv=" + numvalues + " avg=" + WindAverage); + } + else + { + WindAverage = speedpar; + } + + WindAverage *= cumulus.Calib.WindSpeed.Mult; + + cumulus.HighWindAlarm.Triggered = DoAlarm(WindAverage, cumulus.HighWindAlarm.Value, cumulus.HighWindAlarm.Enabled, true); + + + if (CalcRecentMaxGust) + { + // Find recent max gust + double maxgust = 0; + for (int i = 0; i <= MaxWindRecent - 1; i++) + { + if (timestamp - WindRecent[i].Timestamp <= cumulus.PeakGustTime) + { + if (WindRecent[i].Gust > maxgust) + { + maxgust = WindRecent[i].Gust; + } + } + } + RecentMaxGust = maxgust * cumulus.Calib.WindGust.Mult; + } + + cumulus.HighGustAlarm.Triggered = DoAlarm(RecentMaxGust, cumulus.HighGustAlarm.Value, cumulus.HighGustAlarm.Enabled, true); + + if (WindAverage > HiLoToday.HighWind) + { + HiLoToday.HighWind = WindAverage; + HiLoToday.HighWindTime = timestamp; + WriteTodayFile(timestamp, false); + } + if (WindAverage > ThisMonth.HighWind.Val) + { + ThisMonth.HighWind.Val = WindAverage; + ThisMonth.HighWind.Ts = timestamp; + WriteMonthIniFile(); + } + if (WindAverage > ThisYear.HighWind.Val) + { + ThisYear.HighWind.Val = WindAverage; + ThisYear.HighWind.Ts = timestamp; + WriteYearIniFile(); + } + + WindVec[nextwindvec].X = calibratedgust * Math.Sin(DegToRad(Bearing)); + WindVec[nextwindvec].Y = calibratedgust * Math.Cos(DegToRad(Bearing)); + // save timestamp of this reading + WindVec[nextwindvec].Timestamp = timestamp; + // save bearing + WindVec[nextwindvec].Bearing = Bearing; // savedBearing; + // increment index for next reading + nextwindvec = (nextwindvec + 1) % MaxWindRecent; + + // Now add up all the values within the required period + double totalwindX = 0; + double totalwindY = 0; + for (int i = 0; i < MaxWindRecent; i++) + { + if (timestamp - WindVec[i].Timestamp < cumulus.AvgBearingTime) + { + totalwindX += WindVec[i].X; + totalwindY += WindVec[i].Y; + } + } + if (totalwindX == 0) + { + AvgBearing = 0; + } + else + { + AvgBearing = (int)Math.Round(RadToDeg(Math.Atan(totalwindY / totalwindX))); + + if (totalwindX < 0) + { + AvgBearing = 270 - AvgBearing; + } + else + { + AvgBearing = 90 - AvgBearing; + } + + if (AvgBearing == 0) + { + AvgBearing = 360; + } + } + + if ((Math.Abs(WindAverage) < 0.01) && cumulus.StationOptions.UseZeroBearing) + { + AvgBearing = 0; + } + + AvgBearingText = CompassPoint(AvgBearing); + + int diffFrom = 0; + int diffTo = 0; + BearingRangeFrom = AvgBearing; + BearingRangeTo = AvgBearing; + if (AvgBearing != 0) + { + for (int i = 0; i <= MaxWindRecent - 1; i++) + { + if ((timestamp - WindVec[i].Timestamp < cumulus.AvgBearingTime) && (WindVec[i].Bearing != 0)) + { + // this reading was within the last N minutes + int difference = getShortestAngle(AvgBearing, WindVec[i].Bearing); + if ((difference > diffTo)) + { + diffTo = difference; + BearingRangeTo = WindVec[i].Bearing; + // Calculate rounded up value + BearingRangeTo10 = (int)(Math.Ceiling(WindVec[i].Bearing / 10.0) * 10); + } + if ((difference < diffFrom)) + { + diffFrom = difference; + BearingRangeFrom = WindVec[i].Bearing; + BearingRangeFrom10 = (int)(Math.Floor(WindVec[i].Bearing / 10.0) * 10); + } + } + } + } + else + { + BearingRangeFrom10 = 0; + BearingRangeTo10 = 0; + } + + // All time high wind speed? + if (WindAverage > AllTime.HighWind.Val) + { + SetAlltime(AllTime.HighWind, WindAverage, timestamp); + } + + // check for monthly all time records (and set) + CheckMonthlyAlltime("HighWind", WindAverage, true, timestamp); + + WindReadyToPlot = true; + HaveReadData = true; + } + + public void DoUV(double value, DateTime timestamp) + { + UV = (value * cumulus.Calib.UV.Mult) + cumulus.Calib.UV.Offset; + if (UV < 0) + UV = 0; + if (UV > 16) + UV = 16; + + if (UV > HiLoToday.HighUv) + { + HiLoToday.HighUv = UV; + HiLoToday.HighUvTime = timestamp; + } + + HaveReadData = true; + } + + protected void DoSolarRad(int value, DateTime timestamp) + { + SolarRad = (value * cumulus.Calib.Solar.Mult) + cumulus.Calib.Solar.Offset; + // Update display + + if (SolarRad > HiLoToday.HighSolar) + { + HiLoToday.HighSolar = SolarRad; + HiLoToday.HighSolarTime = timestamp; + } + CurrentSolarMax = AstroLib.SolarMax(timestamp, cumulus.Longitude, cumulus.Latitude, AltitudeM(cumulus.Altitude), out SolarElevation, cumulus.RStransfactor, cumulus.BrasTurbidity, cumulus.SolarCalc); + + if (!cumulus.UseBlakeLarsen) + { + IsSunny = (SolarRad > (CurrentSolarMax * cumulus.SunThreshold / 100)) && (SolarRad >= cumulus.SolarMinimum); + } + HaveReadData = true; + } + + protected void DoSunHours(double hrs) + { + if (StartOfDaySunHourCounter < -9998) + { + cumulus.LogMessage("No start of day sun counter. Start counting from now"); + StartOfDaySunHourCounter = hrs; + } + + // Has the counter reset to a value less than we were expecting. Or has it changed by some infeasibly large value? + if (hrs < SunHourCounter || Math.Abs(hrs - SunHourCounter) > 20) + { + // counter reset + cumulus.LogMessage("Sun hour counter reset. Old value = " + SunHourCounter + "New value = " + hrs); + StartOfDaySunHourCounter = hrs - SunshineHours; + } + SunHourCounter = hrs; + SunshineHours = hrs - StartOfDaySunHourCounter; + } + + protected void DoWetBulb(double temp, DateTime timestamp) // Supplied in CELSIUS + + { + WetBulb = ConvertTempCToUser(temp); + WetBulb = (WetBulb * cumulus.Calib.WetBulb.Mult) + cumulus.Calib.WetBulb.Offset; + + // calculate RH + double TempDry = ConvertUserTempToC(OutdoorTemperature); + double Es = MeteoLib.SaturationVapourPressure1980(TempDry); + double Ew = MeteoLib.SaturationVapourPressure1980(temp); + double E = Ew - (0.00066 * (1 + 0.00115 * temp) * (TempDry - temp) * 1013); + int hum = (int)(100 * (E / Es)); + DoOutdoorHumidity(hum, timestamp); + // calculate DP + // Calculate DewPoint + + // dewpoint = TempinC + ((0.13 * TempinC) + 13.6) * Ln(humidity / 100); + OutdoorDewpoint = ConvertTempCToUser(MeteoLib.DewPoint(TempDry, hum)); + + CheckForDewpointHighLow(timestamp); + } + + public bool IsSunny { get; set; } + + public bool HaveReadData { get; set; } = false; + + public void SetAlltime(AllTimeRec rec, double value, DateTime timestamp) + { + lock (alltimeIniThreadLock) + { + double oldvalue = rec.Val; + DateTime oldts = rec.Ts; + + rec.Val = value; + + rec.Ts = timestamp; + + WriteAlltimeIniFile(); + + AlltimeRecordTimestamp = timestamp; + + // add an entry to the log. date/time/value/item/old date/old time/old value + // dates in ISO format, times always have a colon. Example: + // 2010-02-24 05:19 -7.6 "Lowest temperature" 2009-02-09 04:50 -6.5 + var sb = new StringBuilder("New all-time record: New time = ", 100); + sb.Append(FormatDateTime("yyyy-MM-dd HH:mm", rec.Ts)); + sb.Append(", new value = "); + sb.Append(string.Format("{0,7:0.000}", value)); + sb.Append(" \""); + sb.Append(rec.Desc); + sb.Append("\" prev time = "); + sb.Append(FormatDateTime("yyyy-MM-dd HH:mm", oldts)); + sb.Append(", prev value = "); + sb.Append(string.Format("{0,7:0.000}", oldvalue)); + + cumulus.LogMessage(sb.ToString()); + + sb.Append(Environment.NewLine); + File.AppendAllText(cumulus.Alltimelogfile, sb.ToString()); + } + } + + public void SetMonthlyAlltime(AllTimeRec rec, double value, DateTime timestamp) + { + double oldvalue = rec.Val; + DateTime oldts = rec.Ts; + + rec.Val = value; + rec.Ts = timestamp; + + WriteMonthlyAlltimeIniFile(); + + var sb = new StringBuilder("New monthly record: month = ", 200); + sb.Append(timestamp.Month.ToString("D2")); + sb.Append(": New time = "); + sb.Append(FormatDateTime("yyy-MM-dd HH:mm", timestamp)); + sb.Append(", new value = "); + sb.Append(value.ToString("F3")); + sb.Append(" \""); + sb.Append(rec.Desc); + sb.Append("\" prev time = "); + sb.Append(FormatDateTime("yyyy-MM-dd HH:mm", oldts)); + sb.Append(", prev value = "); + sb.Append(oldvalue.ToString("F3")); + + cumulus.LogMessage(sb.ToString()); + + sb.Append(Environment.NewLine); + File.AppendAllText(cumulus.MonthlyAlltimeLogFile, sb.ToString()); + } + + /// + /// Returns the angle from bearing2 to bearing1, in the range -180 to +180 degrees + /// + /// + /// + /// the required angle + private int getShortestAngle(int bearing1, int bearing2) + { + int diff = bearing2 - bearing1; + + if (diff >= 180) + { + // result is obtuse and positive, subtract 360 to go the other way + diff -= 360; + } + else + { + if (diff <= -180) + { + // result is obtuse and negative, add 360 to go the other way + diff += 360; + } + } + return diff; + } + + public int BearingRangeTo10 { get; set; } + + public int BearingRangeFrom10 { get; set; } + + public int BearingRangeTo { get; set; } + + public int BearingRangeFrom { get; set; } + + public const int maxwindvalues = 3600; + + public int[] windbears = new int[maxwindvalues]; + + public int numwindvalues { get; set; } + + public double[] windspeeds = new double[maxwindvalues]; + + public double[] windcounts { get; set; } + + public int nextwindvalue { get; set; } + + public double calibratedgust { get; set; } + + public int nextwind { get; set; } = 0; + + public int nextwindvec { get; set; } = 0; + + public TWindRecent[] WindRecent { get; set; } + + public TWindVec[] WindVec { get; set; } + + + //private bool first_rain = true; + private bool FirstChanceRainReset = false; + public bool initialiseRainCounterOnFirstData = true; + //private bool RainReadyToPlot = false; + private double rainthismonth = 0; + private double rainthisyear = 0; + //private bool WindChillReadyToPlot = false; + public bool noET = false; + private int DayResetDay = 0; + protected bool FirstRun = false; + public const int MaxWindRecent = 720; + protected readonly double[] WindRunHourMult = { 3.6, 1.0, 1.0, 1.0 }; + public DateTime LastDataReadTimestamp = DateTime.MinValue; + public DateTime SavedLastDataReadTimestamp = DateTime.MinValue; + // Create arrays with 9 entries, 0 = VP2, 1-8 = WLL TxIds + public int DavisTotalPacketsReceived = 0; + public int[] DavisTotalPacketsMissed = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + public int[] DavisNumberOfResynchs = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + public int[] DavisMaxInARow = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + public int[] DavisNumCRCerrors = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + public int[] DavisReceptionPct = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + public int[] DavisTxRssi = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + public string DavisFirmwareVersion = "???"; + public string GW1000FirmwareVersion = "???"; + + //private bool manualftp; + + public void WriteYesterdayFile() + { + cumulus.LogMessage("Writing yesterday.ini"); + var hourInc = cumulus.GetHourInc(); + + IniFile ini = new IniFile(cumulus.YesterdayFile); + + ini.SetValue("General", "Date", DateTime.Now.AddHours(hourInc)); + // Wind + ini.SetValue("Wind", "Speed", HiLoYest.HighWind); + ini.SetValue("Wind", "SpTime", HiLoYest.HighWindTime.ToString("HH:mm")); + ini.SetValue("Wind", "Gust", HiLoYest.HighGust); + ini.SetValue("Wind", "Time", HiLoYest.HighGustTime.ToString("HH:mm")); + ini.SetValue("Wind", "Bearing", HiLoYest.HighGustBearing); + ini.SetValue("Wind", "Direction", CompassPoint(HiLoYest.HighGustBearing)); + ini.SetValue("Wind", "Windrun", YesterdayWindRun); + ini.SetValue("Wind", "DominantWindBearing", YestDominantWindBearing); + // Temperature + ini.SetValue("Temp", "Low", HiLoYest.LowTemp); + ini.SetValue("Temp", "LTime", HiLoYest.LowTempTime.ToString("HH:mm")); + ini.SetValue("Temp", "High", HiLoYest.HighTemp); + ini.SetValue("Temp", "HTime", HiLoYest.HighTempTime.ToString("HH:mm")); + ini.SetValue("Temp", "HeatingDegreeDays", YestHeatingDegreeDays); + ini.SetValue("Temp", "CoolingDegreeDays", YestCoolingDegreeDays); + ini.SetValue("Temp", "AvgTemp", YestAvgTemp); + // Pressure + ini.SetValue("Pressure", "Low", HiLoYest.LowPress); + ini.SetValue("Pressure", "LTime", HiLoYest.LowPressTime.ToString("HH:mm")); + ini.SetValue("Pressure", "High", HiLoYest.HighPress); + ini.SetValue("Pressure", "HTime", HiLoYest.HighPressTime.ToString("HH:mm")); + // rain rate + ini.SetValue("Rain", "High", HiLoYest.HighRainRate); + ini.SetValue("Rain", "HTime", HiLoYest.HighRainRateTime.ToString("HH:mm")); + ini.SetValue("Rain", "HourlyHigh", HiLoYest.HighHourlyRain); + ini.SetValue("Rain", "HHourlyTime", HiLoYest.HighHourlyRainTime.ToString("HH:mm")); + ini.SetValue("Rain", "RG11Yesterday", RG11RainYesterday); + // humidity + ini.SetValue("Humidity", "Low", HiLoYest.LowHumidity); + ini.SetValue("Humidity", "High", HiLoYest.HighHumidity); + ini.SetValue("Humidity", "LTime", HiLoYest.LowHumidityTime.ToString("HH:mm")); + ini.SetValue("Humidity", "HTime", HiLoYest.HighHumidityTime.ToString("HH:mm")); + // Solar + ini.SetValue("Solar", "SunshineHours", YestSunshineHours); + // heat index + ini.SetValue("HeatIndex", "High", HiLoYest.HighHeatIndex); + ini.SetValue("HeatIndex", "HTime", HiLoYest.HighHeatIndexTime.ToString("HH:mm")); + // App temp + ini.SetValue("AppTemp", "Low", HiLoYest.LowAppTemp); + ini.SetValue("AppTemp", "LTime", HiLoYest.LowAppTempTime.ToString("HH:mm")); + ini.SetValue("AppTemp", "High", HiLoYest.HighAppTemp); + ini.SetValue("AppTemp", "HTime", HiLoYest.HighAppTempTime.ToString("HH:mm")); + // wind chill + ini.SetValue("WindChill", "Low", HiLoYest.LowWindChill); + ini.SetValue("WindChill", "LTime", HiLoYest.LowWindChillTime.ToString("HH:mm")); + // Dewpoint + ini.SetValue("Dewpoint", "Low", HiLoYest.LowDewPoint); + ini.SetValue("Dewpoint", "LTime", HiLoYest.LowDewPointTime.ToString("HH:mm")); + ini.SetValue("Dewpoint", "High", HiLoYest.HighDewPoint); + ini.SetValue("Dewpoint", "HTime", HiLoYest.HighDewPointTime.ToString("HH:mm")); + // Solar + ini.SetValue("Solar", "HighSolarRad", HiLoYest.HighSolar); + ini.SetValue("Solar", "HighSolarRadTime", HiLoYest.HighSolarTime.ToString("HH:mm")); + ini.SetValue("Solar", "HighUV", HiLoYest.HighUv); + ini.SetValue("Solar", "HighUVTime", HiLoYest.HighUvTime.ToString("HH:mm")); + // Feels like + ini.SetValue("FeelsLike", "Low", HiLoYest.LowFeelsLike); + ini.SetValue("FeelsLike", "LTime", HiLoYest.LowFeelsLikeTime.ToString("HH:mm")); + ini.SetValue("FeelsLike", "High", HiLoYest.HighFeelsLike); + ini.SetValue("FeelsLike", "HTime", HiLoYest.HighFeelsLikeTime.ToString("HH:mm")); + // Humidex + ini.SetValue("Humidex", "High", HiLoYest.HighHumidex); + ini.SetValue("Humidex", "HTime", HiLoYest.HighHumidexTime.ToString("HH:mm")); + + ini.Flush(); + + cumulus.LogMessage("Written yesterday.ini"); + } + + public void ReadYesterdayFile() + { + //var hourInc = cumulus.GetHourInc(); + + IniFile ini = new IniFile(cumulus.YesterdayFile); + + // Wind + HiLoYest.HighWind = ini.GetValue("Wind", "Speed", 0.0); + HiLoYest.HighWindTime = ini.GetValue("Wind", "SpTime", DateTime.MinValue); + HiLoYest.HighGust = ini.GetValue("Wind", "Gust", 0.0); + HiLoYest.HighGustTime = ini.GetValue("Wind", "Time", DateTime.MinValue); + HiLoYest.HighGustBearing = ini.GetValue("Wind", "Bearing", 0); + + YesterdayWindRun = ini.GetValue("Wind", "Windrun", 0.0); + YestDominantWindBearing = ini.GetValue("Wind", "DominantWindBearing", 0); + // Temperature + HiLoYest.LowTemp = ini.GetValue("Temp", "Low", 0.0); + HiLoYest.LowTempTime = ini.GetValue("Temp", "LTime", DateTime.MinValue); + HiLoYest.HighTemp = ini.GetValue("Temp", "High", 0.0); + HiLoYest.HighTempTime = ini.GetValue("Temp", "HTime", DateTime.MinValue); + YestHeatingDegreeDays = ini.GetValue("Temp", "HeatingDegreeDays", 0.0); + YestCoolingDegreeDays = ini.GetValue("Temp", "CoolingDegreeDays", 0.0); + YestAvgTemp = ini.GetValue("Temp", "AvgTemp", 0.0); + HiLoYest.TempRange = HiLoYest.HighTemp - HiLoYest.LowTemp; + // Pressure + HiLoYest.LowPress = ini.GetValue("Pressure", "Low", 0.0); + HiLoYest.LowPressTime = ini.GetValue("Pressure", "LTime", DateTime.MinValue); + HiLoYest.HighPress = ini.GetValue("Pressure", "High", 0.0); + HiLoYest.HighPressTime = ini.GetValue("Pressure", "HTime", DateTime.MinValue); + // rain rate + HiLoYest.HighRainRate = ini.GetValue("Rain", "High", 0.0); + HiLoYest.HighRainRateTime = ini.GetValue("Rain", "HTime", DateTime.MinValue); + HiLoYest.HighHourlyRain = ini.GetValue("Rain", "HourlyHigh", 0.0); + HiLoYest.HighHourlyRainTime = ini.GetValue("Rain", "HHourlyTime", DateTime.MinValue); + RG11RainYesterday = ini.GetValue("Rain", "RG11Yesterday", 0.0); + // humidity + HiLoYest.LowHumidity = ini.GetValue("Humidity", "Low", 0); + HiLoYest.HighHumidity = ini.GetValue("Humidity", "High", 0); + HiLoYest.LowHumidityTime = ini.GetValue("Humidity", "LTime", DateTime.MinValue); + HiLoYest.HighHumidityTime = ini.GetValue("Humidity", "HTime", DateTime.MinValue); + // Solar + YestSunshineHours = ini.GetValue("Solar", "SunshineHours", 0.0); + // heat index + HiLoYest.HighHeatIndex = ini.GetValue("HeatIndex", "High", 0.0); + HiLoYest.HighHeatIndexTime = ini.GetValue("HeatIndex", "HTime", DateTime.MinValue); + // App temp + HiLoYest.LowAppTemp = ini.GetValue("AppTemp", "Low", 0.0); + HiLoYest.LowAppTempTime = ini.GetValue("AppTemp", "LTime", DateTime.MinValue); + HiLoYest.HighAppTemp = ini.GetValue("AppTemp", "High", 0.0); + HiLoYest.HighAppTempTime = ini.GetValue("AppTemp", "HTime", DateTime.MinValue); + // wind chill + HiLoYest.LowWindChill = ini.GetValue("WindChill", "Low", 0.0); + HiLoYest.LowWindChillTime = ini.GetValue("WindChill", "LTime", DateTime.MinValue); + // Dewpoint + HiLoYest.LowDewPoint = ini.GetValue("Dewpoint", "Low", 0.0); + HiLoYest.LowDewPointTime = ini.GetValue("Dewpoint", "LTime", DateTime.MinValue); + HiLoYest.HighDewPoint = ini.GetValue("Dewpoint", "High", 0.0); + HiLoYest.HighDewPointTime = ini.GetValue("Dewpoint", "HTime", DateTime.MinValue); + // Solar + HiLoYest.HighSolar = ini.GetValue("Solar", "HighSolarRad", 0.0); + HiLoYest.HighSolarTime = ini.GetValue("Solar", "HighSolarRadTime", DateTime.MinValue); + HiLoYest.HighUv = ini.GetValue("Solar", "HighUV", 0.0); + HiLoYest.HighUvTime = ini.GetValue("Solar", "HighUVTime", DateTime.MinValue); + // Feels like + HiLoYest.LowFeelsLike = ini.GetValue("FeelsLike", "Low", 0.0); + HiLoYest.LowFeelsLikeTime = ini.GetValue("FeelsLike", "LTime", DateTime.MinValue); + HiLoYest.HighFeelsLike = ini.GetValue("FeelsLike", "High", 0.0); + HiLoYest.HighFeelsLikeTime = ini.GetValue("FeelsLike", "HTime", DateTime.MinValue); + // Humidex + HiLoYest.HighHumidex = ini.GetValue("Humidex", "High", 0.0); + HiLoYest.HighHumidexTime = ini.GetValue("Humidex", "HTime", DateTime.MinValue); + } + + public void DayReset(DateTime timestamp) + { + int drday = timestamp.Day; + DateTime yesterday = timestamp.AddDays(-1); + cumulus.LogMessage("=== Day reset, today = " + drday); + if (drday != DayResetDay) + { + cumulus.LogMessage("=== Day reset for " + yesterday.Date); + + int day = timestamp.Day; + int month = timestamp.Month; + DayResetDay = drday; + + if (cumulus.CustomMySqlRolloverEnabled) + { + _ = cumulus.CustomMysqlRolloverTimerTick(); + } + + if (cumulus.CustomHttpRolloverEnabled) + { + cumulus.CustomHttpRolloverUpdate(); + } + + // First save today"s extremes + DoDayfile(timestamp); + cumulus.LogMessage("Raincounter = " + Raincounter + " Raindaystart = " + raindaystart); + + // Calculate yesterday"s rain, allowing for the multiplier - + // raintotal && raindaystart are not calibrated + RainYesterday = (Raincounter - raindaystart) * cumulus.Calib.Rain.Mult; + cumulus.LogMessage("Rainyesterday (calibrated) set to " + RainYesterday); + + //AddRecentDailyData(timestamp.AddDays(-1), RainYesterday, (cumulus.RolloverHour == 0 ? SunshineHours : SunshineToMidnight), HiLoToday.LowTemp, HiLoToday.HighTemp, YestAvgTemp); + //RemoveOldRecentDailyData(); + + int rdthresh1000; + if (cumulus.RainDayThreshold < 0) + // default + { + if (cumulus.Units.Rain == 0) + { + rdthresh1000 = 200; // 0.2mm *1000 + } + else + { + rdthresh1000 = 10; // 0.01in *1000 + } + } + else + { + rdthresh1000 = Convert.ToInt32(cumulus.RainDayThreshold * 1000.0); + } + + // set up rain yesterday * 1000 for comparison + int ryest1000 = Convert.ToInt32(RainYesterday * 1000.0); + + cumulus.LogMessage("RainDayThreshold = " + cumulus.RainDayThreshold); + cumulus.LogMessage("rdt1000=" + rdthresh1000 + " ry1000=" + ryest1000); + + if (ryest1000 >= rdthresh1000) + { + // It rained yesterday + cumulus.LogMessage("Yesterday was a rain day"); + ConsecutiveRainDays++; + ConsecutiveDryDays = 0; + cumulus.LogMessage("Consecutive rain days = " + ConsecutiveRainDays); + // check for highs + if (ConsecutiveRainDays > ThisMonth.LongestWetPeriod.Val) + { + ThisMonth.LongestWetPeriod.Val = ConsecutiveRainDays; + ThisMonth.LongestWetPeriod.Ts = yesterday; + WriteMonthIniFile(); + } + + if (ConsecutiveRainDays > ThisYear.LongestWetPeriod.Val) + { + ThisYear.LongestWetPeriod.Val = ConsecutiveRainDays; + ThisYear.LongestWetPeriod.Ts = yesterday; + WriteYearIniFile(); + } + + if (ConsecutiveRainDays > AllTime.LongestWetPeriod.Val) + SetAlltime(AllTime.LongestWetPeriod, ConsecutiveRainDays, yesterday); + + CheckMonthlyAlltime("LongestWetPeriod", ConsecutiveRainDays, true, yesterday); + } + else + { + // It didn't rain yesterday + cumulus.LogMessage("Yesterday was a dry day"); + ConsecutiveDryDays++; + ConsecutiveRainDays = 0; + cumulus.LogMessage("Consecutive dry days = " + ConsecutiveDryDays); + + // check for highs + if (ConsecutiveDryDays > ThisMonth.LongestDryPeriod.Val) + { + ThisMonth.LongestDryPeriod.Val = ConsecutiveDryDays; + ThisMonth.LongestDryPeriod.Ts = yesterday; + WriteMonthIniFile(); + } + + if (ConsecutiveDryDays > ThisYear.LongestDryPeriod.Val) + { + ThisYear.LongestDryPeriod.Val = ConsecutiveDryDays; + ThisYear.LongestDryPeriod.Ts = yesterday; + WriteYearIniFile(); + } + + if (ConsecutiveDryDays > AllTime.LongestDryPeriod.Val) + SetAlltime(AllTime.LongestDryPeriod, ConsecutiveDryDays, yesterday); + + CheckMonthlyAlltime("LongestDryPeriod", ConsecutiveDryDays, true, yesterday); + } + + // offset high temp today timestamp to allow for 0900 rollover + int hr; + int mn; + DateTime ts; + try + { + hr = HiLoToday.HighTempTime.Hour; + mn = HiLoToday.HighTempTime.Minute; + ts = timestamp.Date + new TimeSpan(hr, mn, 0); + + if (hr >= cumulus.RolloverHour) + // time is between rollover hour && midnight + // so subtract a day + ts = ts.AddDays(-1); + } + catch + { + ts = timestamp.AddDays(-1); + } + + if (HiLoToday.HighTemp < AllTime.LowMaxTemp.Val) + { + SetAlltime(AllTime.LowMaxTemp, HiLoToday.HighTemp, ts); + } + + CheckMonthlyAlltime("LowMaxTemp", HiLoToday.HighTemp, false, ts); + + if (HiLoToday.HighTemp < ThisMonth.LowMaxTemp.Val) + { + ThisMonth.LowMaxTemp.Val = HiLoToday.HighTemp; + try + { + hr = HiLoToday.HighTempTime.Hour; + mn = HiLoToday.HighTempTime.Minute; + ThisMonth.LowMaxTemp.Ts = timestamp.Date + new TimeSpan(hr, mn, 0); + + if (hr >= cumulus.RolloverHour) + // time is between rollover hour && midnight + // so subtract a day + ThisMonth.LowMaxTemp.Ts = ThisMonth.LowMaxTemp.Ts.AddDays(-1); + } + catch + { + ThisMonth.LowMaxTemp.Ts = timestamp.AddDays(-1); + } + + WriteMonthIniFile(); + } + + if (HiLoToday.HighTemp < ThisYear.LowMaxTemp.Val) + { + ThisYear.LowMaxTemp.Val = HiLoToday.HighTemp; + try + { + hr = HiLoToday.HighTempTime.Hour; + mn = HiLoToday.HighTempTime.Minute; + ThisYear.LowMaxTemp.Ts = timestamp.Date + new TimeSpan(hr, mn, 0); + + if (hr >= cumulus.RolloverHour) + // time is between rollover hour && midnight + // so subtract a day + ThisYear.LowMaxTemp.Ts = ThisYear.LowMaxTemp.Ts.AddDays(-1); + } + catch + { + ThisYear.LowMaxTemp.Ts = timestamp.AddDays(-1); + } + + WriteYearIniFile(); + } + + // offset low temp today timestamp to allow for 0900 rollover + try + { + hr = HiLoToday.LowTempTime.Hour; + mn = HiLoToday.LowTempTime.Minute; + ts = timestamp.Date + new TimeSpan(hr, mn, 0); + + if (hr >= cumulus.RolloverHour) + // time is between rollover hour && midnight + // so subtract a day + ts = ts.AddDays(-1); + } + catch + { + ts = timestamp.AddDays(-1); + } + + if (HiLoToday.LowTemp > AllTime.HighMinTemp.Val) + { + SetAlltime(AllTime.HighMinTemp, HiLoToday.LowTemp, ts); + } + + CheckMonthlyAlltime("HighMinTemp", HiLoToday.LowTemp, true, ts); + + if (HiLoToday.LowTemp > ThisMonth.HighMinTemp.Val) + { + ThisMonth.HighMinTemp.Val = HiLoToday.LowTemp; + try + { + hr = HiLoToday.LowTempTime.Hour; + mn = HiLoToday.LowTempTime.Minute; + ThisMonth.HighMinTemp.Ts = timestamp.Date + new TimeSpan(hr, mn, 0); + + if (hr >= cumulus.RolloverHour) + // time is between rollover hour && midnight + // so subtract a day + ThisMonth.HighMinTemp.Ts = ThisMonth.HighMinTemp.Ts.AddDays(-1); + } + catch + { + ThisMonth.HighMinTemp.Ts = timestamp.AddDays(-1); + } + WriteMonthIniFile(); + } + + if (HiLoToday.LowTemp > ThisYear.HighMinTemp.Val) + { + ThisYear.HighMinTemp.Val = HiLoToday.LowTemp; + try + { + hr = HiLoToday.LowTempTime.Hour; + mn = HiLoToday.LowTempTime.Minute; + ThisYear.HighMinTemp.Ts = timestamp.Date + new TimeSpan(hr, mn, 0); + + if (hr >= cumulus.RolloverHour) + // time is between rollover hour && midnight + // so subtract a day + ThisYear.HighMinTemp.Ts = ThisYear.HighMinTemp.Ts.AddDays(-1); + } + catch + { + ThisYear.HighMinTemp.Ts = timestamp.AddDays(-1); + } + WriteYearIniFile(); + } + + // check temp range for highs && lows + if (HiLoToday.TempRange > AllTime.HighDailyTempRange.Val) + SetAlltime(AllTime.HighDailyTempRange, HiLoToday.TempRange, yesterday); + + if (HiLoToday.TempRange < AllTime.LowDailyTempRange.Val) + SetAlltime(AllTime.LowDailyTempRange, HiLoToday.TempRange, yesterday); + + CheckMonthlyAlltime("HighDailyTempRange", HiLoToday.TempRange, true, yesterday); + CheckMonthlyAlltime("LowDailyTempRange", HiLoToday.TempRange, false, yesterday); + + if (HiLoToday.TempRange > ThisMonth.HighDailyTempRange.Val) + { + ThisMonth.HighDailyTempRange.Val = HiLoToday.TempRange; + ThisMonth.HighDailyTempRange.Ts = yesterday; + WriteMonthIniFile(); + } + + if (HiLoToday.TempRange < ThisMonth.LowDailyTempRange.Val) + { + ThisMonth.LowDailyTempRange.Val = HiLoToday.TempRange; + ThisMonth.LowDailyTempRange.Ts = yesterday; + WriteMonthIniFile(); + } + + if (HiLoToday.TempRange > ThisYear.HighDailyTempRange.Val) + { + ThisYear.HighDailyTempRange.Val = HiLoToday.TempRange; + ThisYear.HighDailyTempRange.Ts = yesterday; + WriteYearIniFile(); + } + + if (HiLoToday.TempRange < ThisYear.LowDailyTempRange.Val) + { + ThisYear.LowDailyTempRange.Val = HiLoToday.TempRange; + ThisYear.LowDailyTempRange.Ts = yesterday; + WriteYearIniFile(); + } + + RG11RainYesterday = RG11RainToday; + RG11RainToday = 0; + + if (day == 1) + { + // new month starting + cumulus.LogMessage(" New month starting - " + month); + + CopyMonthIniFile(timestamp.AddDays(-1)); + + rainthismonth = 0; + + ThisMonth.HighGust.Val = calibratedgust; + ThisMonth.HighWind.Val = WindAverage; + ThisMonth.HighTemp.Val = OutdoorTemperature; + ThisMonth.LowTemp.Val = OutdoorTemperature; + ThisMonth.HighAppTemp.Val = ApparentTemperature; + ThisMonth.LowAppTemp.Val = ApparentTemperature; + ThisMonth.HighFeelsLike.Val = FeelsLike; + ThisMonth.LowFeelsLike.Val = FeelsLike; + ThisMonth.HighHumidex.Val = Humidex; + ThisMonth.HighPress.Val = Pressure; + ThisMonth.LowPress.Val = Pressure; + ThisMonth.HighRainRate.Val = RainRate; + ThisMonth.HourlyRain.Val = RainLastHour; + ThisMonth.DailyRain.Val = 0; + ThisMonth.HighHumidity.Val = OutdoorHumidity; + ThisMonth.LowHumidity.Val = OutdoorHumidity; + ThisMonth.HighHeatIndex.Val = HeatIndex; + ThisMonth.LowChill.Val = WindChill; + ThisMonth.HighMinTemp.Val = -999; + ThisMonth.LowMaxTemp.Val = 999; + ThisMonth.HighDewPoint.Val = OutdoorDewpoint; + ThisMonth.LowDewPoint.Val = OutdoorDewpoint; + ThisMonth.HighWindRun.Val = 0; + ThisMonth.LongestDryPeriod.Val = 0; + ThisMonth.LongestWetPeriod.Val = 0; + ThisMonth.HighDailyTempRange.Val = -999; + ThisMonth.LowDailyTempRange.Val = 999; + + // this month highs && lows - timestamps + ThisMonth.HighGust.Ts = timestamp; + ThisMonth.HighWind.Ts = timestamp; + ThisMonth.HighTemp.Ts = timestamp; + ThisMonth.LowTemp.Ts = timestamp; + ThisMonth.HighAppTemp.Ts = timestamp; + ThisMonth.LowAppTemp.Ts = timestamp; + ThisMonth.HighFeelsLike.Ts = timestamp; + ThisMonth.LowFeelsLike.Ts = timestamp; + ThisMonth.HighHumidex.Ts = timestamp; + ThisMonth.HighPress.Ts = timestamp; + ThisMonth.LowPress.Ts = timestamp; + ThisMonth.HighRainRate.Ts = timestamp; + ThisMonth.HourlyRain.Ts = timestamp; + ThisMonth.DailyRain.Ts = timestamp; + ThisMonth.HighHumidity.Ts = timestamp; + ThisMonth.LowHumidity.Ts = timestamp; + ThisMonth.HighHeatIndex.Ts = timestamp; + ThisMonth.LowChill.Ts = timestamp; + ThisMonth.HighMinTemp.Ts = timestamp; + ThisMonth.LowMaxTemp.Ts = timestamp; + ThisMonth.HighDewPoint.Ts = timestamp; + ThisMonth.LowDewPoint.Ts = timestamp; + ThisMonth.HighWindRun.Ts = timestamp; + ThisMonth.LongestDryPeriod.Ts = timestamp; + ThisMonth.LongestWetPeriod.Ts = timestamp; + ThisMonth.LowDailyTempRange.Ts = timestamp; + ThisMonth.HighDailyTempRange.Ts = timestamp; + } + else + rainthismonth += RainYesterday; + + if ((day == 1) && (month == 1)) + { + // new year starting + cumulus.LogMessage(" New year starting"); + + CopyYearIniFile(timestamp.AddDays(-1)); + + ThisYear.HighGust.Val = calibratedgust; + ThisYear.HighWind.Val = WindAverage; + ThisYear.HighTemp.Val = OutdoorTemperature; + ThisYear.LowTemp.Val = OutdoorTemperature; + ThisYear.HighAppTemp.Val = ApparentTemperature; + ThisYear.LowAppTemp.Val = ApparentTemperature; + ThisYear.HighFeelsLike.Val = FeelsLike; + ThisYear.LowFeelsLike.Val = FeelsLike; + ThisYear.HighHumidex.Val = Humidex; + ThisYear.HighPress.Val = Pressure; + ThisYear.LowPress.Val = Pressure; + ThisYear.HighRainRate.Val = RainRate; + ThisYear.HourlyRain.Val = RainLastHour; + ThisYear.DailyRain.Val = 0; + ThisYear.MonthlyRain.Val = 0; + ThisYear.HighHumidity.Val = OutdoorHumidity; + ThisYear.LowHumidity.Val = OutdoorHumidity; + ThisYear.HighHeatIndex.Val = HeatIndex; + ThisYear.LowChill.Val = WindChill; + ThisYear.HighMinTemp.Val = -999; + ThisYear.LowMaxTemp.Val = 999; + ThisYear.HighDewPoint.Val = OutdoorDewpoint; + ThisYear.LowDewPoint.Val = OutdoorDewpoint; + ThisYear.HighWindRun.Val = 0; + ThisYear.LongestDryPeriod.Val = 0; + ThisYear.LongestWetPeriod.Val = 0; + ThisYear.HighDailyTempRange.Val = -999; + ThisYear.LowDailyTempRange.Val = 999; + + // this Year highs && lows - timestamps + ThisYear.HighGust.Ts = timestamp; + ThisYear.HighWind.Ts = timestamp; + ThisYear.HighTemp.Ts = timestamp; + ThisYear.LowTemp.Ts = timestamp; + ThisYear.HighAppTemp.Ts = timestamp; + ThisYear.LowAppTemp.Ts = timestamp; + ThisYear.HighFeelsLike.Ts = timestamp; + ThisYear.LowFeelsLike.Ts = timestamp; + ThisYear.HighHumidex.Ts = timestamp; + ThisYear.HighPress.Ts = timestamp; + ThisYear.LowPress.Ts = timestamp; + ThisYear.HighRainRate.Ts = timestamp; + ThisYear.HourlyRain.Ts = timestamp; + ThisYear.DailyRain.Ts = timestamp; + ThisYear.MonthlyRain.Ts = timestamp; + ThisYear.HighHumidity.Ts = timestamp; + ThisYear.LowHumidity.Ts = timestamp; + ThisYear.HighHeatIndex.Ts = timestamp; + ThisYear.LowChill.Ts = timestamp; + ThisYear.HighMinTemp.Ts = timestamp; + ThisYear.LowMaxTemp.Ts = timestamp; + ThisYear.HighDewPoint.Ts = timestamp; + ThisYear.LowDewPoint.Ts = timestamp; + ThisYear.HighWindRun.Ts = timestamp; + ThisYear.LongestDryPeriod.Ts = timestamp; + ThisYear.LongestWetPeriod.Ts = timestamp; + ThisYear.HighDailyTempRange.Ts = timestamp; + ThisYear.LowDailyTempRange.Ts = timestamp; + } + + if ((day == 1) && (month == cumulus.RainSeasonStart)) + { + // new year starting + cumulus.LogMessage(" New rain season starting"); + rainthisyear = 0; + } + else + { + rainthisyear += RainYesterday; + } + + if ((day == 1) && (month == cumulus.ChillHourSeasonStart)) + { + // new year starting + cumulus.LogMessage(" Chill hour season starting"); + ChillHours = 0; + } + + if ((day == 1) && (month == cumulus.GrowingYearStarts)) + { + cumulus.LogMessage(" New growing degree day season starting"); + GrowingDegreeDaysThisYear1 = 0; + GrowingDegreeDaysThisYear2 = 0; + } + + GrowingDegreeDaysThisYear1 += MeteoLib.GrowingDegreeDays(ConvertUserTempToC(HiLoToday.HighTemp), ConvertUserTempToC(HiLoToday.LowTemp), ConvertUserTempToC(cumulus.GrowingBase1), cumulus.GrowingCap30C); + GrowingDegreeDaysThisYear2 += MeteoLib.GrowingDegreeDays(ConvertUserTempToC(HiLoToday.HighTemp), ConvertUserTempToC(HiLoToday.LowTemp), ConvertUserTempToC(cumulus.GrowingBase2), cumulus.GrowingCap30C); + + // Now reset all values to the current or default ones + // We may be doing a rollover from the first logger entry, + // && as we do the rollover before processing the entry, the + // current items may not be set up. + + raindaystart = Raincounter; + cumulus.LogMessage("Raindaystart set to " + raindaystart); + + RainToday = 0; + + TempTotalToday = OutdoorTemperature; + tempsamplestoday = 1; + + // Copy today"s high wind settings to yesterday + HiLoYest.HighWind = HiLoToday.HighWind; + HiLoYest.HighWindTime = HiLoToday.HighWindTime; + HiLoYest.HighGust = HiLoToday.HighGust; + HiLoYest.HighGustTime = HiLoToday.HighGustTime; + HiLoYest.HighGustBearing = HiLoToday.HighGustBearing; + + // Reset today"s high wind settings + HiLoToday.HighGust = calibratedgust; + HiLoToday.HighGustBearing = Bearing; + HiLoToday.HighWind = WindAverage; + + HiLoToday.HighWindTime = timestamp; + HiLoToday.HighGustTime = timestamp; + + // Copy today"s high temp settings to yesterday + HiLoYest.HighTemp = HiLoToday.HighTemp; + HiLoYest.HighTempTime = HiLoToday.HighTempTime; + // Reset today"s high temp settings + HiLoToday.HighTemp = OutdoorTemperature; + HiLoToday.HighTempTime = timestamp; + + // Copy today"s low temp settings to yesterday + HiLoYest.LowTemp = HiLoToday.LowTemp; + HiLoYest.LowTempTime = HiLoToday.LowTempTime; + // Reset today"s low temp settings + HiLoToday.LowTemp = OutdoorTemperature; + HiLoToday.LowTempTime = timestamp; + + HiLoYest.TempRange = HiLoToday.TempRange; + HiLoToday.TempRange = 0; + + // Copy today"s low pressure settings to yesterday + HiLoYest.LowPress = HiLoToday.LowPress; + HiLoYest.LowPressTime = HiLoToday.LowPressTime; + // Reset today"s low pressure settings + HiLoToday.LowPress = Pressure; + HiLoToday.LowPressTime = timestamp; + + // Copy today"s high pressure settings to yesterday + HiLoYest.HighPress = HiLoToday.HighPress; + HiLoYest.HighPressTime = HiLoToday.HighPressTime; + // Reset today"s high pressure settings + HiLoToday.HighPress = Pressure; + HiLoToday.HighPressTime = timestamp; + + // Copy today"s high rain rate settings to yesterday + HiLoYest.HighRainRate = HiLoToday.HighRainRate; + HiLoYest.HighRainRateTime = HiLoToday.HighRainRateTime; + // Reset today"s high rain rate settings + HiLoToday.HighRainRate = RainRate; + HiLoToday.HighRainRateTime = timestamp; + + HiLoYest.HighHourlyRain = HiLoToday.HighHourlyRain; + HiLoYest.HighHourlyRainTime = HiLoToday.HighHourlyRainTime; + HiLoToday.HighHourlyRain = RainLastHour; + HiLoToday.HighHourlyRainTime = timestamp; + + YesterdayWindRun = WindRunToday; + WindRunToday = 0; + + YestDominantWindBearing = DominantWindBearing; + + DominantWindBearing = 0; + DominantWindBearingX = 0; + DominantWindBearingY = 0; + DominantWindBearingMinutes = 0; + + YestHeatingDegreeDays = HeatingDegreeDays; + YestCoolingDegreeDays = CoolingDegreeDays; + HeatingDegreeDays = 0; + CoolingDegreeDays = 0; + + // reset startofdayET value + StartofdayET = AnnualETTotal; + cumulus.LogMessage("StartofdayET set to " + StartofdayET); + ET = 0; + + // Humidity + HiLoYest.LowHumidity = HiLoToday.LowHumidity; + HiLoYest.LowHumidityTime = HiLoToday.LowHumidityTime; + HiLoToday.LowHumidity = OutdoorHumidity; + HiLoToday.LowHumidityTime = timestamp; + + HiLoYest.HighHumidity = HiLoToday.HighHumidity; + HiLoYest.HighHumidityTime = HiLoToday.HighHumidityTime; + HiLoToday.HighHumidity = OutdoorHumidity; + HiLoToday.HighHumidityTime = timestamp; + + // heat index + HiLoYest.HighHeatIndex = HiLoToday.HighHeatIndex; + HiLoYest.HighHeatIndexTime = HiLoToday.HighHeatIndexTime; + HiLoToday.HighHeatIndex = HeatIndex; + HiLoToday.HighHeatIndexTime = timestamp; + + // App temp + HiLoYest.HighAppTemp = HiLoToday.HighAppTemp; + HiLoYest.HighAppTempTime = HiLoToday.HighAppTempTime; + HiLoToday.HighAppTemp = ApparentTemperature; + HiLoToday.HighAppTempTime = timestamp; + + HiLoYest.LowAppTemp = HiLoToday.LowAppTemp; + HiLoYest.LowAppTempTime = HiLoToday.LowAppTempTime; + HiLoToday.LowAppTemp = ApparentTemperature; + HiLoToday.LowAppTempTime = timestamp; + + // wind chill + HiLoYest.LowWindChill = HiLoToday.LowWindChill; + HiLoYest.LowWindChillTime = HiLoToday.LowWindChillTime; + HiLoToday.LowWindChill = WindChill; + HiLoToday.LowWindChillTime = timestamp; + + // dew point + HiLoYest.HighDewPoint = HiLoToday.HighDewPoint; + HiLoYest.HighDewPointTime = HiLoToday.HighDewPointTime; + HiLoToday.HighDewPoint = OutdoorDewpoint; + HiLoToday.HighDewPointTime = timestamp; + + HiLoYest.LowDewPoint = HiLoToday.LowDewPoint; + HiLoYest.LowDewPointTime = HiLoToday.LowDewPointTime; + HiLoToday.LowDewPoint = OutdoorDewpoint; + HiLoToday.LowDewPointTime = timestamp; + + // solar + HiLoYest.HighSolar = HiLoToday.HighSolar; + HiLoYest.HighSolarTime = HiLoToday.HighSolarTime; + HiLoToday.HighSolar = SolarRad; + HiLoToday.HighSolarTime = timestamp; + + HiLoYest.HighUv = HiLoToday.HighUv; + HiLoYest.HighUvTime = HiLoToday.HighUvTime; + HiLoToday.HighUv = UV; + HiLoToday.HighUvTime = timestamp; + + // Feels like + HiLoYest.HighFeelsLike = HiLoToday.HighFeelsLike; + HiLoYest.HighFeelsLikeTime = HiLoToday.HighFeelsLikeTime; + HiLoToday.HighFeelsLike = FeelsLike; + HiLoToday.HighFeelsLikeTime = timestamp; + + HiLoYest.LowFeelsLike = HiLoToday.LowFeelsLike; + HiLoYest.LowFeelsLikeTime = HiLoToday.LowFeelsLikeTime; + HiLoToday.LowFeelsLike = FeelsLike; + HiLoToday.LowFeelsLikeTime = timestamp; + + // Humidex + HiLoYest.HighHumidex = HiLoToday.HighHumidex; + HiLoYest.HighHumidexTime = HiLoToday.HighHumidexTime; + HiLoToday.HighHumidex = Humidex; + HiLoToday.HighHumidexTime = timestamp; + + // Save the current values in case of program restart + WriteTodayFile(timestamp, true); + WriteYesterdayFile(); + + if (cumulus.NOAAAutoSave) + { + try + { + NOAA noaa = new NOAA(cumulus); + var utf8WithoutBom = new System.Text.UTF8Encoding(false); + var encoding = cumulus.NOAAUseUTF8 ? utf8WithoutBom : System.Text.Encoding.GetEncoding("iso-8859-1"); + + List report; + + DateTime noaats = timestamp.AddDays(-1); + + // do monthly NOAA report + cumulus.LogMessage("Creating NOAA monthly report for " + noaats.ToLongDateString()); + report = noaa.CreateMonthlyReport(noaats); + cumulus.NOAALatestMonthlyReport = FormatDateTime(cumulus.NOAAMonthFileFormat, noaats); + string noaafile = cumulus.ReportPath + cumulus.NOAALatestMonthlyReport; + cumulus.LogMessage("Saving monthly report as " + noaafile); + File.WriteAllLines(noaafile, report, encoding); + + // do yearly NOAA report + cumulus.LogMessage("Creating NOAA yearly report"); + report = noaa.CreateYearlyReport(noaats); + cumulus.NOAALatestYearlyReport = FormatDateTime(cumulus.NOAAYearFileFormat, noaats); + noaafile = cumulus.ReportPath + cumulus.NOAALatestYearlyReport; + cumulus.LogMessage("Saving yearly report as " + noaafile); + File.WriteAllLines(noaafile, report, encoding); + } + catch (Exception ex) + { + cumulus.LogMessage("Error creating NOAA reports: " + ex.Message); + } + } + + // Do we need to upload NOAA reports on next FTP? + cumulus.NOAANeedFTP = cumulus.NOAAAutoFTP; + + if (cumulus.NOAANeedFTP) + { + cumulus.LogMessage("NOAA reports will be uploaded at next web update"); + } + + // Do the End of day Extra files + // This will set a flag to transfer on next FTP if required + cumulus.DoExtraEndOfDayFiles(); + if (cumulus.EODfilesNeedFTP) + { + cumulus.LogMessage("Extra files will be uploaded at next web update"); + } + + // Do the Daily graph data files + CreateEodGraphDataFiles(); + cumulus.LogMessage("If required the daily graph data files will be uploaded at next web update"); + + + if (!string.IsNullOrEmpty(cumulus.DailyProgram)) + { + cumulus.LogMessage("Executing daily program: " + cumulus.DailyProgram + " params: " + cumulus.DailyParams); + try + { + // Prepare the process to run + ProcessStartInfo start = new ProcessStartInfo(); + // Enter in the command line arguments + start.Arguments = cumulus.DailyParams; + // Enter the executable to run, including the complete path + start.FileName = cumulus.DailyProgram; + // Don"t show a console window + start.CreateNoWindow = true; + // Run the external process + Process.Start(start); + } + catch (Exception ex) + { + cumulus.LogMessage("Error executing external program: " + ex.Message); + } + } + + CurrentDay = timestamp.Day; + CurrentMonth = timestamp.Month; + CurrentYear = timestamp.Year; + cumulus.LogMessage("=== Day reset complete"); + cumulus.LogMessage("Now recording data for day=" + CurrentDay + " month=" + CurrentMonth + " year=" + CurrentYear); + } + else + { + cumulus.LogMessage("=== Day reset already done on day " + drday); + } + } + + private void CopyMonthIniFile(DateTime ts) + { + string year = ts.Year.ToString(); + string month = ts.Month.ToString("D2"); + string savedFile = cumulus.Datapath + "month" + year + month + ".ini"; + cumulus.LogMessage("Saving month.ini file as " + savedFile); + try + { + File.Copy(cumulus.MonthIniFile, savedFile); + } + catch (Exception) + { + // ignore - probably just that it has already been copied + } + } + + private void CopyYearIniFile(DateTime ts) + { + string year = ts.Year.ToString(); + string savedFile = cumulus.Datapath + "year" + year + ".ini"; + cumulus.LogMessage("Saving year.ini file as " + savedFile); + try + { + File.Copy(cumulus.YearIniFile, savedFile); + } + catch (Exception) + { + // ignore - probably just that it has already been copied + } + } + + private void DoDayfile(DateTime timestamp) + { + // Writes an entry to the daily extreme log file. Fields are comma-separated. + // 0 Date in the form dd/mm/yy (the slash may be replaced by a dash in some cases) + // 1 Highest wind gust + // 2 Bearing of highest wind gust + // 3 Time of highest wind gust + // 4 Minimum temperature + // 5 Time of minimum temperature + // 6 Maximum temperature + // 7 Time of maximum temperature + // 8 Minimum sea level pressure + // 9 Time of minimum pressure + // 10 Maximum sea level pressure + // 11 Time of maximum pressure + // 12 Maximum rainfall rate + // 13 Time of maximum rainfall rate + // 14 Total rainfall for the day + // 15 Average temperature for the day + // 16 Total wind run + // 17 Highest average wind speed + // 18 Time of highest average wind speed + // 19 Lowest humidity + // 20 Time of lowest humidity + // 21 Highest humidity + // 22 Time of highest humidity + // 23 Total evapotranspiration + // 24 Total hours of sunshine + // 25 High heat index + // 26 Time of high heat index + // 27 High apparent temperature + // 28 Time of high apparent temperature + // 29 Low apparent temperature + // 30 Time of low apparent temperature + // 31 High hourly rain + // 32 Time of high hourly rain + // 33 Low wind chill + // 34 Time of low wind chill + // 35 High dew point + // 36 Time of high dew point + // 37 Low dew point + // 38 Time of low dew point + // 39 Dominant wind bearing + // 40 Heating degree days + // 41 Cooling degree days + // 42 High solar radiation + // 43 Time of high solar radiation + // 44 High UV Index + // 45 Time of high UV Index + // 46 High Feels like + // 47 Time of high feels like + // 48 Low feels like + // 49 Time of low feels like + // 50 High Humidex + // 51 Time of high Humidex + + double AvgTemp; + if (tempsamplestoday > 0) + AvgTemp = TempTotalToday / tempsamplestoday; + else + AvgTemp = 0; + + // save the value for yesterday + YestAvgTemp = AvgTemp; + + string datestring = timestamp.AddDays(-1).ToString("dd/MM/yy"); ; + // NB this string is just for logging, the dayfile update code is further down + var strb = new StringBuilder(300); + strb.Append(datestring + cumulus.ListSeparator); + strb.Append(HiLoToday.HighGust.ToString(cumulus.WindFormat) + cumulus.ListSeparator); + strb.Append(HiLoToday.HighGustBearing + cumulus.ListSeparator); + strb.Append(HiLoToday.HighGustTime.ToString("HH:mm") + cumulus.ListSeparator); + strb.Append(HiLoToday.LowTemp.ToString(cumulus.TempFormat) + cumulus.ListSeparator); + strb.Append(HiLoToday.LowTempTime.ToString("HH:mm") + cumulus.ListSeparator); + strb.Append(HiLoToday.HighTemp.ToString(cumulus.TempFormat) + cumulus.ListSeparator); + strb.Append(HiLoToday.HighTempTime.ToString("HH:mm") + cumulus.ListSeparator); + strb.Append(HiLoToday.LowPress.ToString(cumulus.PressFormat) + cumulus.ListSeparator); + strb.Append(HiLoToday.LowPressTime.ToString("HH:mm") + cumulus.ListSeparator); + strb.Append(HiLoToday.HighPress.ToString(cumulus.PressFormat) + cumulus.ListSeparator); + strb.Append(HiLoToday.HighPressTime.ToString("HH:mm") + cumulus.ListSeparator); + strb.Append(HiLoToday.HighRainRate.ToString(cumulus.RainFormat) + cumulus.ListSeparator); + strb.Append(HiLoToday.HighRainRateTime.ToString("HH:mm") + cumulus.ListSeparator); + strb.Append(RainToday.ToString(cumulus.RainFormat) + cumulus.ListSeparator); + strb.Append(AvgTemp.ToString(cumulus.TempFormat) + cumulus.ListSeparator); + strb.Append(WindRunToday.ToString("F1") + cumulus.ListSeparator); + strb.Append(HiLoToday.HighWind.ToString(cumulus.WindAvgFormat) + cumulus.ListSeparator); + strb.Append(HiLoToday.HighWindTime.ToString("HH:mm") + cumulus.ListSeparator); + strb.Append(HiLoToday.LowHumidity + cumulus.ListSeparator); + strb.Append(HiLoToday.LowHumidityTime.ToString("HH:mm") + cumulus.ListSeparator); + strb.Append(HiLoToday.HighHumidity + cumulus.ListSeparator); + strb.Append(HiLoToday.HighHumidityTime.ToString("HH:mm") + cumulus.ListSeparator); + strb.Append(ET.ToString(cumulus.ETFormat) + cumulus.ListSeparator); + if (cumulus.RolloverHour == 0) + { + // use existing current sunshinehour count + strb.Append(SunshineHours.ToString(cumulus.SunFormat) + cumulus.ListSeparator); + } + else + { + // for non-midnight rollover, use new item + strb.Append(SunshineToMidnight.ToString(cumulus.SunFormat) + cumulus.ListSeparator); + } + strb.Append(HiLoToday.HighHeatIndex.ToString(cumulus.TempFormat) + cumulus.ListSeparator); + strb.Append(HiLoToday.HighHeatIndexTime.ToString("HH:mm") + cumulus.ListSeparator); + strb.Append(HiLoToday.HighAppTemp.ToString(cumulus.TempFormat) + cumulus.ListSeparator); + strb.Append(HiLoToday.HighAppTempTime.ToString("HH:mm") + cumulus.ListSeparator); + strb.Append(HiLoToday.LowAppTemp.ToString(cumulus.TempFormat) + cumulus.ListSeparator); + strb.Append(HiLoToday.LowAppTempTime.ToString("HH:mm") + cumulus.ListSeparator); + strb.Append(HiLoToday.HighHourlyRain.ToString(cumulus.RainFormat) + cumulus.ListSeparator); + strb.Append(HiLoToday.HighHourlyRainTime.ToString("HH:mm") + cumulus.ListSeparator); + strb.Append(HiLoToday.LowWindChill.ToString(cumulus.TempFormat) + cumulus.ListSeparator); + strb.Append(HiLoToday.LowWindChillTime.ToString("HH:mm") + cumulus.ListSeparator); + strb.Append(HiLoToday.HighDewPoint.ToString(cumulus.TempFormat) + cumulus.ListSeparator); + strb.Append(HiLoToday.HighDewPointTime.ToString("HH:mm") + cumulus.ListSeparator); + strb.Append(HiLoToday.LowDewPoint.ToString(cumulus.TempFormat) + cumulus.ListSeparator); + strb.Append(HiLoToday.LowDewPointTime.ToString("HH:mm") + cumulus.ListSeparator); + strb.Append(DominantWindBearing + cumulus.ListSeparator); + strb.Append(HeatingDegreeDays.ToString("F1") + cumulus.ListSeparator); + strb.Append(CoolingDegreeDays.ToString("F1") + cumulus.ListSeparator); + strb.Append((int)HiLoToday.HighSolar + cumulus.ListSeparator); + strb.Append(HiLoToday.HighSolarTime.ToString("HH:mm") + cumulus.ListSeparator); + strb.Append(HiLoToday.HighUv.ToString(cumulus.UVFormat) + cumulus.ListSeparator); + strb.Append(HiLoToday.HighUvTime.ToString("HH:mm") + cumulus.ListSeparator); + strb.Append(HiLoToday.HighFeelsLike.ToString(cumulus.TempFormat) + cumulus.ListSeparator); + strb.Append(HiLoToday.HighFeelsLikeTime.ToString("HH:mm") + cumulus.ListSeparator); + strb.Append(HiLoToday.LowFeelsLike.ToString(cumulus.TempFormat) + cumulus.ListSeparator); + strb.Append(HiLoToday.LowFeelsLikeTime.ToString("HH:mm") + cumulus.ListSeparator); + strb.Append(HiLoToday.HighHumidex.ToString(cumulus.TempFormat) + cumulus.ListSeparator); + strb.Append(HiLoToday.HighHumidexTime.ToString("HH:mm")); + + cumulus.LogMessage("Dayfile.txt entry:"); + cumulus.LogMessage(strb.ToString()); + + try + { + using (FileStream fs = new FileStream(cumulus.DayFileName, FileMode.Append, FileAccess.Write, FileShare.Read)) + using (StreamWriter file = new StreamWriter(fs)) + { + cumulus.LogMessage("Dayfile.txt opened for writing"); + + if ((HiLoToday.HighTemp < -400) || (HiLoToday.LowTemp > 900)) + { + cumulus.LogMessage("***Error: Daily values are still at default at end of day"); + cumulus.LogMessage("Data not logged to dayfile.txt"); + } + else + { + cumulus.LogMessage("Writing entry to dayfile.txt"); + + file.WriteLine(strb.ToString()); + file.Close(); + } + } + } + catch (Exception ex) + { + cumulus.LogMessage("Error writing to dayfile.txt: " + ex.Message); + } + + // Add a new record to the in memory dayfile data + var newRec = new dayfilerec(); + var tim = timestamp.AddDays(-1); + newRec.Date = new DateTime(tim.Year, tim.Month, tim.Day); + newRec.HighGust = HiLoToday.HighGust; + newRec.HighGustBearing = HiLoToday.HighGustBearing; + newRec.HighGustTime = HiLoToday.HighGustTime; + newRec.LowTemp = HiLoToday.LowTemp; + newRec.LowTempTime = HiLoToday.LowTempTime; + newRec.HighTemp = HiLoToday.HighTemp; + newRec.HighTempTime = HiLoToday.HighTempTime; + newRec.LowPress = HiLoToday.LowPress; + newRec.LowPressTime = HiLoToday.LowPressTime; + newRec.HighPress = HiLoToday.HighPress; + newRec.HighPressTime = HiLoToday.HighPressTime; + newRec.HighRainRate = HiLoToday.HighRainRate; + newRec.HighRainRateTime = HiLoToday.HighRainRateTime; + newRec.TotalRain = RainToday; + newRec.AvgTemp = AvgTemp; + newRec.WindRun = WindRunToday; + newRec.HighAvgWind = HiLoToday.HighWind; + newRec.HighAvgWindTime = HiLoToday.HighWindTime; + newRec.LowHumidity = HiLoToday.LowHumidity; + newRec.LowHumidityTime = HiLoToday.LowHumidityTime; + newRec.HighHumidity = HiLoToday.HighHumidity; + newRec.HighHumidityTime = HiLoToday.HighHumidityTime; + newRec.ET = ET; + newRec.SunShineHours = cumulus.RolloverHour == 0 ? SunshineHours : SunshineToMidnight; + newRec.HighHeatIndex = HiLoToday.HighHeatIndex; + newRec.HighHeatIndexTime = HiLoToday.HighHeatIndexTime; + newRec.HighAppTemp = HiLoToday.HighAppTemp; + newRec.HighAppTempTime = HiLoToday.HighAppTempTime; + newRec.LowAppTemp = HiLoToday.LowAppTemp; + newRec.LowAppTempTime = HiLoToday.LowAppTempTime; + newRec.HighHourlyRain = HiLoToday.HighHourlyRain; + newRec.HighHourlyRainTime = HiLoToday.HighHourlyRainTime; + newRec.LowWindChill = HiLoToday.LowWindChill; + newRec.LowWindChillTime = HiLoToday.LowWindChillTime; + newRec.HighDewPoint = HiLoToday.HighDewPoint; + newRec.HighDewPointTime = HiLoToday.HighDewPointTime; + newRec.LowDewPoint = HiLoToday.LowDewPoint; + newRec.LowDewPointTime = HiLoToday.LowDewPointTime; + newRec.DominantWindBearing = DominantWindBearing; + newRec.HeatingDegreeDays = HeatingDegreeDays; + newRec.CoolingDegreeDays = CoolingDegreeDays; + newRec.HighSolar = (int)HiLoToday.HighSolar; + newRec.HighSolarTime = HiLoToday.HighSolarTime; + newRec.HighUv = HiLoToday.HighUv; + newRec.HighUvTime = HiLoToday.HighUvTime; + newRec.HighFeelsLike = HiLoToday.HighFeelsLike; + newRec.HighFeelsLikeTime = HiLoToday.HighFeelsLikeTime; + newRec.LowFeelsLike = HiLoToday.LowFeelsLike; + newRec.LowFeelsLikeTime = HiLoToday.LowFeelsLikeTime; + newRec.HighHumidex = HiLoToday.HighHumidex; + newRec.HighHumidexTime = HiLoToday.HighHumidexTime; + + DayFile.Add(newRec); + + + + if (cumulus.DayfileMySqlEnabled) + { + var mySqlConn = new MySqlConnection(cumulus.MySqlConnSettings.ToString()); + + var InvC = new CultureInfo(""); + + StringBuilder queryString = new StringBuilder(cumulus.StartOfDayfileInsertSQL, 1024); + queryString.Append(" Values('"); + queryString.Append(timestamp.AddDays(-1).ToString("yy-MM-dd") + "',"); + queryString.Append(HiLoToday.HighGust.ToString(cumulus.WindFormat, InvC) + ","); + queryString.Append(HiLoToday.HighGustBearing + ","); + queryString.Append(HiLoToday.HighGustTime.ToString("\\'HH:mm\\'") + ","); + queryString.Append(HiLoToday.LowTemp.ToString(cumulus.TempFormat, InvC) + ","); + queryString.Append(HiLoToday.LowTempTime.ToString("\\'HH:mm\\'") + ","); + queryString.Append(HiLoToday.HighTemp.ToString(cumulus.TempFormat, InvC) + ","); + queryString.Append(HiLoToday.HighTempTime.ToString("\\'HH:mm\\'") + ","); + queryString.Append(HiLoToday.LowPress.ToString(cumulus.PressFormat, InvC) + ","); + queryString.Append(HiLoToday.LowPressTime.ToString("\\'HH:mm\\'") + ","); + queryString.Append(HiLoToday.HighPress.ToString(cumulus.PressFormat, InvC) + ","); + queryString.Append(HiLoToday.HighPressTime.ToString("\\'HH:mm\\'") + ","); + queryString.Append(HiLoToday.HighRainRate.ToString(cumulus.RainFormat, InvC) + ","); + queryString.Append(HiLoToday.HighRainRateTime.ToString("\\'HH:mm\\'") + ","); + queryString.Append(RainToday.ToString(cumulus.RainFormat, InvC) + ","); + queryString.Append(AvgTemp.ToString(cumulus.TempFormat, InvC) + ","); + queryString.Append(WindRunToday.ToString("F1", InvC) + ","); + queryString.Append(HiLoToday.HighWind.ToString(cumulus.WindAvgFormat, InvC) + ","); + queryString.Append(HiLoToday.HighWindTime.ToString("\\'HH:mm\\'") + ","); + queryString.Append(HiLoToday.LowHumidity + ","); + queryString.Append(HiLoToday.LowHumidityTime.ToString("\\'HH:mm\\'") + ","); + queryString.Append(HiLoToday.HighHumidity + ","); + queryString.Append(HiLoToday.HighHumidityTime.ToString("\\'HH:mm\\'") + ","); + queryString.Append(ET.ToString(cumulus.ETFormat, InvC) + ","); + queryString.Append((cumulus.RolloverHour == 0 ? SunshineHours.ToString(cumulus.SunFormat, InvC) : SunshineToMidnight.ToString(cumulus.SunFormat, InvC)) + ","); + queryString.Append(HiLoToday.HighHeatIndex.ToString(cumulus.TempFormat, InvC) + ","); + queryString.Append(HiLoToday.HighHeatIndexTime.ToString("\\'HH:mm\\'") + ","); + queryString.Append(HiLoToday.HighAppTemp.ToString(cumulus.TempFormat, InvC) + ","); + queryString.Append(HiLoToday.HighAppTempTime.ToString("\\'HH:mm\\'") + ","); + queryString.Append(HiLoToday.LowAppTemp.ToString(cumulus.TempFormat, InvC) + ","); + queryString.Append(HiLoToday.LowAppTempTime.ToString("\\'HH:mm\\'") + ","); + queryString.Append(HiLoToday.HighHourlyRain.ToString(cumulus.RainFormat, InvC) + ","); + queryString.Append(HiLoToday.HighHourlyRainTime.ToString("\\'HH:mm\\'") + ","); + queryString.Append(HiLoToday.LowWindChill.ToString(cumulus.TempFormat, InvC) + ","); + queryString.Append(HiLoToday.LowWindChillTime.ToString("\\'HH:mm\\'") + ","); + queryString.Append(HiLoToday.HighDewPoint.ToString(cumulus.TempFormat, InvC) + ","); + queryString.Append(HiLoToday.HighDewPointTime.ToString("\\'HH:mm\\'") + ","); + queryString.Append(HiLoToday.LowDewPoint.ToString(cumulus.TempFormat, InvC) + ","); + queryString.Append(HiLoToday.LowDewPointTime.ToString("\\'HH:mm\\'") + ","); + queryString.Append(DominantWindBearing + ","); + queryString.Append(HeatingDegreeDays.ToString("F1", InvC) + ","); + queryString.Append(CoolingDegreeDays.ToString("F1", InvC) + ","); + queryString.Append((int)HiLoToday.HighSolar + ","); + queryString.Append(HiLoToday.HighSolarTime.ToString("\\'HH:mm\\'") + ","); + queryString.Append(HiLoToday.HighUv.ToString(cumulus.UVFormat, InvC) + ","); + queryString.Append(HiLoToday.HighUvTime.ToString("\\'HH:mm\\'") + ",'"); + queryString.Append(CompassPoint(HiLoToday.HighGustBearing) + "','"); + queryString.Append(CompassPoint(DominantWindBearing) + "',"); + queryString.Append(HiLoToday.HighFeelsLike.ToString(cumulus.TempFormat, InvC) + ","); + queryString.Append(HiLoToday.HighFeelsLikeTime.ToString("\\'HH:mm\\'") + ","); + queryString.Append(HiLoToday.LowFeelsLike.ToString(cumulus.TempFormat, InvC) + ","); + queryString.Append(HiLoToday.LowFeelsLikeTime.ToString("\\'HH:mm\\'") + ","); + queryString.Append(HiLoToday.HighHumidex.ToString(cumulus.TempFormat, InvC) + ","); + queryString.Append(HiLoToday.HighFeelsLikeTime.ToString("\\'HH:mm\\'")); + + queryString.Append(")"); + + // run the query async so we do not block the main EOD processing + _ = cumulus.MySqlCommandAsync(queryString.ToString(), mySqlConn, "MySQL Dayfile", true, true); + } + } + + /// + /// Calculate checksum of data received from serial port + /// + /// + /// + protected int checksum(List data) + { + int sum = 0; + + for (int i = 0; i < data.Count - 1; i++) + { + sum += data[i]; + } + + return sum % 256; + } + + protected int BCDchartoint(int c) + { + return ((c / 16) * 10) + (c % 16); + } + + /// + /// Convert temp supplied in C to units in use + /// + /// Temp in C + /// Temp in configured units + public double ConvertTempCToUser(double value) + { + if (cumulus.Units.Temp == 1) + { + return MeteoLib.CToF(value); + } + else + { + // C + return value; + } + } + + /// + /// Convert temp supplied in F to units in use + /// + /// Temp in F + /// Temp in configured units + public double ConvertTempFToUser(double value) + { + if (cumulus.Units.Temp == 0) + { + return MeteoLib.FtoC(value); + } + else + { + // F + return value; + } + } + + /// + /// Convert temp supplied in user units to C + /// + /// Temp in configured units + /// Temp in C + public double ConvertUserTempToC(double value) + { + if (cumulus.Units.Temp == 1) + { + return MeteoLib.FtoC(value); + } + else + { + // C + return value; + } + } + + /// + /// Convert temp supplied in user units to F + /// + /// Temp in configured units + /// Temp in F + public double ConvertUserTempToF(double value) + { + if (cumulus.Units.Temp == 1) + { + return value; + } + else + { + // C + return MeteoLib.CToF(value); + } + } + + /// + /// Converts wind supplied in m/s to user units + /// + /// Wind in m/s + /// Wind in configured units + public double ConvertWindMSToUser(double value) + { + switch (cumulus.Units.Wind) + { + case 0: + return value; + case 1: + return value * 2.23693629; + case 2: + return value * 3.6; + case 3: + return value * 1.94384449; + default: + return 0; + } + } + + /// + /// Converts wind supplied in mph to user units + /// + /// Wind in m/s + /// Wind in configured units + public double ConvertWindMPHToUser(double value) + { + switch (cumulus.Units.Wind) + { + case 0: + return value * 0.44704; + case 1: + return value; + case 2: + return value * 1.60934; + case 3: + return value * 0.868976; + default: + return 0; + } + } + + /// + /// Converts wind in user units to m/s + /// + /// + /// + public virtual double ConvertUserWindToMS(double value) + { + switch (cumulus.Units.Wind) + { + case 0: + return value; + case 1: + return value / 2.23693629; + case 2: + return value / 3.6F; + case 3: + return value / 1.94384449; + default: + return 0; + } + } + + /// + /// Converts value in kilometres to distance unit based on users configured wind units + /// + /// + /// Wind in configured units + public double ConvertKmtoUserUnits(double val) + { + switch (cumulus.Units.Wind) + { + case 0: // m/s + case 2: // km/h + return val; + case 1: // mph + return val * 0.621371; + case 3: // knots + return val * 0.539957; + } + return val; + } + + /// + /// Converts windrun supplied in user units to km + /// + /// Windrun in configured units + /// Wind in km + public virtual double ConvertWindRunToKm(double value) + { + switch (cumulus.Units.Wind) + { + case 0: // m/s + case 2: // km/h + return value; + case 1: // mph + return value / 0.621371192; + case 3: // knots + return value / 0.539956803; + default: + return 0; + } + } + + public double ConvertUserWindToKPH(double wind) // input is in Units.Wind units, convert to km/h + { + switch (cumulus.Units.Wind) + { + case 0: // m/s + return wind * 3.6; + case 1: // mph + return wind * 1.609344; + case 2: // kph + return wind; + case 3: // knots + return wind * 1.852; + default: + return wind; + } + } + + /// + /// Converts rain in mm to units in use + /// + /// Rain in mm + /// Rain in configured units + public virtual double ConvertRainMMToUser(double value) + { + return cumulus.Units.Rain == 1 ? value * 0.0393700787 : value; + } + + /// + /// Converts rain in inches to units in use + /// + /// Rain in mm + /// Rain in configured units + public virtual double ConvertRainINToUser(double value) + { + return cumulus.Units.Rain == 1 ? value : value * 25.4; + } + + /// + /// Converts rain in units in use to mm + /// + /// Rain in configured units + /// Rain in mm + public virtual double ConvertUserRainToMM(double value) + { + return cumulus.Units.Rain == 1 ? value / 0.0393700787 : value; + } + + /// + /// Convert pressure in mb to units in use + /// + /// pressure in mb + /// pressure in configured units + public double ConvertPressMBToUser(double value) + { + return cumulus.Units.Press == 2 ? value * 0.0295333727 : value; + } + + /// + /// Convert pressure in inHg to units in use + /// + /// pressure in mb + /// pressure in configured units + public double ConvertPressINHGToUser(double value) + { + return cumulus.Units.Press == 2 ? value : value * 33.8638866667; + } + + /// + /// Convert pressure in units in use to mb + /// + /// pressure in configured units + /// pressure in mb + public double ConvertUserPressToMB(double value) + { + return cumulus.Units.Press == 2 ? value / 0.0295333727 : value; + } + + /// + /// Convert pressure in units in use to inHg + /// + /// pressure in configured units + /// pressure in mb + public double ConvertUserPressToIN(double value) + { + return cumulus.Units.Press == 2 ? value : value * 0.0295333727; + } + + public string CompassPoint(int bearing) + { + return bearing == 0 ? "-" : cumulus.compassp[(((bearing * 100) + 1125) % 36000) / 2250]; + } + + public void StartLoop() + { + t = new Thread(Start) { IsBackground = true }; + t.Start(); + } + + public virtual void getAndProcessHistoryData() + { + } + + public virtual void startReadingHistoryData() + { + } + + /// + /// Calculates average bearing for last 10 minutes + /// + /// + public int CalcAverageBearing() + { + double totalwindX = Last10MinWindList.Sum(o => o.gustX); + double totalwindY = Last10MinWindList.Sum(o => o.gustY); + + if (totalwindX == 0) + { + return 0; + } + + int avgbear = calcavgbear(totalwindX, totalwindY); + + if (avgbear == 0) + { + avgbear = 360; + } + + return avgbear; + } + + private int calcavgbear(double x, double y) + { + var avg = 90 - (int)(RadToDeg(Math.Atan2(y, x))); + if (avg < 0) + { + avg = 360 + avg; + } + + return avg; + } + + /// + /// Adds a new entry to the list of data readings from the last 3 hours + /// + /// + /// + /// + public void AddLast3HourDataEntry(DateTime ts, double press, double temp) + { + Last3HourData last3hourdata = new Last3HourData(ts, press, temp); + + Last3HourDataList.Add(last3hourdata); + } + + /// + /// Adds a new entry to the list of data readings from the last hour + /// + /// + /// + /// + public void AddLastHourDataEntry(DateTime ts, double rain, double temp) + { + LastHourData lasthourdata = new LastHourData(ts, rain, temp); + + LastHourDataList.Add(lasthourdata); + } + + /// + /// Adds a new entry to the list of data readings for the graphs + /// + /// + /// + /// + /// + public void AddGraphDataEntry(DateTime ts, double rain, double raintoday, double rrate, double temp, double dp, double appt, double chill, double heat, double intemp, + double press, double speed, double gust, int avgdir, int wdir, int hum, int inhum, double solar, double smax, double uv, double feels, double humidx) + { + double pm2p5 = -1; + double pm10 = -1; + // Check for Air Quality readings + switch (cumulus.StationOptions.PrimaryAqSensor) + { + case (int)Cumulus.PrimaryAqSensor.AirLinkOutdoor: + if (cumulus.airLinkDataOut != null) + { + pm2p5 = cumulus.airLinkDataOut.pm2p5; + pm10 = cumulus.airLinkDataOut.pm10; + } + break; + case (int)Cumulus.PrimaryAqSensor.AirLinkIndoor: + if (cumulus.airLinkDataIn != null) + { + pm2p5 = cumulus.airLinkDataIn.pm2p5; + pm10 = cumulus.airLinkDataIn.pm10; + } + break; + case (int)Cumulus.PrimaryAqSensor.Ecowitt1: + pm2p5 = AirQuality1; + break; + case (int)Cumulus.PrimaryAqSensor.Ecowitt2: + pm2p5 = AirQuality2; + break; + case (int)Cumulus.PrimaryAqSensor.Ecowitt3: + pm2p5 = AirQuality3; + break; + case (int)Cumulus.PrimaryAqSensor.Ecowitt4: + pm2p5 = AirQuality3; + break; + case (int)Cumulus.PrimaryAqSensor.EcowittCO2: + pm2p5 = CO2_pm2p5; + pm10 = CO2_pm10; + break; + + default: // Not enabled, use invalid values + break; + } + var graphdata = new GraphData(ts, rain, raintoday, rrate, temp, dp, appt, chill, heat, intemp, press, speed, gust, avgdir, wdir, hum, inhum, solar, smax, uv, feels, humidx, pm2p5, pm10); + lock (GraphDataList) + { + GraphDataList.Add(graphdata); + } + } + + public void UpdateGraphDataAqEntry(DateTime ts, double pm2p5, double pm10) + { + try + { + var toUpdate = GraphDataList.Single(x => x.timestamp == ts); + if (toUpdate != null) + { + toUpdate.pm2p5 = pm2p5; + toUpdate.pm10 = pm10; + } + } + catch (InvalidOperationException e) + { + cumulus.LogDebugMessage($"UpdateGraphDataAqEntry: Failed to find a record matching ts: {ts}. Exception: {e.Message}"); + } + catch (Exception e) + { + cumulus.LogMessage($"UpdateGraphDataAqEntry: Exception caught: {e.Message}"); + } + } + + /// + /// Adds a new entry to the list of wind readings from the last 10 minutes + /// + /// + public void AddLast10MinWindEntry(DateTime ts, double windgust, double windspeed, double Xvec, double Yvec) + { + Last10MinWind last10minwind = new Last10MinWind(ts, windgust, windspeed, Xvec, Yvec); + Last10MinWindList.Add(last10minwind); + } + + public double DegToRad(int deg) + { + return deg * Math.PI / 180; + } + + public double RadToDeg(double rad) + { + return rad * 180 / Math.PI; + } + + /* + public double getStartOfDayRainCounter(DateTime timestamp) + { + // TODO: + return -1; + } + */ + + /// + /// Removes entries from Last3HourDataList older than ts - 3 hours + /// + /// + /// + public void RemoveOldL3HData(DateTime ts) + { + DateTime threehoursago = ts.AddHours(-3); + + if (Last3HourDataList.Count > 0) + { + // there are entries to consider + while ((Last3HourDataList.Count > 0) && (Last3HourDataList.First().timestamp < threehoursago)) + { + // the oldest entry is older than 3 hours ago, delete it + Last3HourDataList.RemoveAt(0); + } + } + } + + /// + /// Removes entries from GraphDataList older than ts - 24 hours + /// + /// + /// + public void RemoveOldGraphData(DateTime ts) + { + DateTime graphperiod = ts.AddHours(-cumulus.GraphHours); + lock (GraphDataList) + { + if (GraphDataList.Count > 0) + { + // there are entries to consider + while ((GraphDataList.Count > 0) && (GraphDataList.First().timestamp < graphperiod)) + { + // the oldest entry is older than required, delete it + GraphDataList.RemoveAt(0); + } + } + } + } + + + /// + /// Removes entries from LastHourDataList older than ts - 1 hours + /// + /// + /// + public void RemoveOldLHData(DateTime ts) + { + DateTime onehourago = ts.AddHours(-1); + + if (LastHourDataList.Count > 0) + { + // there are entries to consider + while ((LastHourDataList.Count > 0) && (LastHourDataList.First().timestamp < onehourago)) + { + // the oldest entry is older than 1 hour ago, delete it + LastHourDataList.RemoveAt(0); + } + } + } + + /// + /// Removes entries from Last10MinWindList older than ts - 10 minutes + /// + /// + /// + public void RemoveOld10MinWindData(DateTime ts) + { + DateTime tenminutesago = ts.AddMinutes(-10); + + if (Last10MinWindList.Count > 0) + { + // there are entries to consider + while ((Last10MinWindList.Count > 0) && (Last10MinWindList.First().timestamp < tenminutesago)) + { + // the oldest entry is older than 10 mins ago, delete it + Last10MinWindList.RemoveAt(0); + } + } + } + + public void DoTrendValues(DateTime ts) + { + if (Last3HourDataList.Count > 0) + { + // calculate and display the temp trend + + double firstval = Last3HourDataList.First().temperature; + double lastval = Last3HourDataList.Last().temperature; + + double trendval = (lastval - firstval) / 3.0F; + + temptrendval = trendval; + + cumulus.TempChangeAlarm.UpTriggered = DoAlarm(temptrendval, cumulus.TempChangeAlarm.Value, cumulus.TempChangeAlarm.Enabled, true); + cumulus.TempChangeAlarm.DownTriggered = DoAlarm(temptrendval, cumulus.TempChangeAlarm.Value * -1, cumulus.TempChangeAlarm.Enabled, false); + + if (LastHourDataList.Count > 0) + { + firstval = LastHourDataList.First().temperature; + lastval = LastHourDataList.Last().temperature; + + TempChangeLastHour = lastval - firstval; + + // calculate and display rainfall in last hour + firstval = LastHourDataList.First().raincounter; + lastval = LastHourDataList.Last().raincounter; + + if (lastval < firstval) + { + // rain total has gone down, assume it was reset to zero, just use zero + trendval = 0; + } + else + { + // normal case + trendval = lastval - firstval; + } + + // Round value as some values may have been read from log file and already rounded + trendval = Math.Round(trendval, cumulus.RainDPlaces); + + var tempRainLastHour = trendval * cumulus.Calib.Rain.Mult; + + if (tempRainLastHour > cumulus.Spike.MaxHourlyRain) + { + // ignore + } + else + { + RainLastHour = tempRainLastHour; + + if (RainLastHour > AllTime.HourlyRain.Val) + SetAlltime(AllTime.HourlyRain, RainLastHour, ts); + + CheckMonthlyAlltime("HourlyRain", RainLastHour, true, ts); + + if (RainLastHour > HiLoToday.HighHourlyRain) + { + HiLoToday.HighHourlyRain = RainLastHour; + HiLoToday.HighHourlyRainTime = ts; + WriteTodayFile(ts, false); + } + + if (RainLastHour > ThisMonth.HourlyRain.Val) + { + ThisMonth.HourlyRain.Val = RainLastHour; + ThisMonth.HourlyRain.Ts = ts; + WriteMonthIniFile(); + } + + if (RainLastHour > ThisYear.HourlyRain.Val) + { + ThisYear.HourlyRain.Val = RainLastHour; + ThisYear.HourlyRain.Ts = ts; + WriteYearIniFile(); + } + } + } + + // calculate and display the pressure trend + + firstval = Last3HourDataList.First().pressure; + lastval = Last3HourDataList.Last().pressure; + + // save pressure trend in internal units + presstrendval = (lastval - firstval) / 3.0; + + cumulus.PressChangeAlarm.UpTriggered = DoAlarm(presstrendval, cumulus.PressChangeAlarm.Value, cumulus.PressChangeAlarm.Enabled, true); + cumulus.PressChangeAlarm.DownTriggered = DoAlarm(presstrendval, cumulus.PressChangeAlarm.Value * -1, cumulus.PressChangeAlarm.Enabled, false); + + // Convert for display + trendval = ConvertPressMBToUser(presstrendval); + + if (calculaterainrate) + { + // Station doesn't supply rain rate, calculate one based on rain in last 5 minutes + + DateTime fiveminutesago = ts.AddSeconds(-330); + + var requiredData = from p in LastHourDataList where p.timestamp > fiveminutesago select p; + + var fiveminutedata = requiredData as IList ?? requiredData.ToList(); + if (fiveminutedata.Count() > 1) + { + // we have at least two values to compare + + TimeSpan span = fiveminutedata.Last().timestamp.Subtract(fiveminutedata.First().timestamp); + + double timediffhours = span.TotalHours; + + //cumulus.LogMessage("first time = " + fiveminutedata.First().timestamp + " last time = " + fiveminutedata.Last().timestamp); + //cumulus.LogMessage("timediffhours = " + timediffhours); + + // if less than 5 minutes, use 5 minutes + if (timediffhours < 1.0 / 12.0) + { + timediffhours = 1.0 / 12.0; + } + + double raindiff = Math.Round(fiveminutedata.Last().raincounter, cumulus.RainDPlaces) - Math.Round(fiveminutedata.First().raincounter, cumulus.RainDPlaces); + //cumulus.LogMessage("first value = " + fiveminutedata.First().raincounter + " last value = " + fiveminutedata.Last().raincounter); + //cumulus.LogMessage("raindiff = " + raindiff); + + // Scale the counter values + var tempRainRate = (double)(raindiff / timediffhours) * cumulus.Calib.Rain.Mult; + + if (tempRainRate < 0) + { + tempRainRate = 0; + } + + if (tempRainRate > cumulus.Spike.MaxRainRate) + { + // ignore + } + else + { + RainRate = tempRainRate; + + if (RainRate > AllTime.HighRainRate.Val) + SetAlltime(AllTime.HighRainRate, RainRate, ts); + + CheckMonthlyAlltime("HighRainRate", RainRate, true, ts); + + cumulus.HighRainRateAlarm.Triggered = DoAlarm(RainRate, cumulus.HighRainRateAlarm.Value, cumulus.HighRainRateAlarm.Enabled, true); + + if (RainRate > HiLoToday.HighRainRate) + { + HiLoToday.HighRainRate = RainRate; + HiLoToday.HighRainRateTime = ts; + WriteTodayFile(ts, false); + } + + if (RainRate > ThisMonth.HighRainRate.Val) + { + ThisMonth.HighRainRate.Val = RainRate; + ThisMonth.HighRainRate.Ts = ts; + WriteMonthIniFile(); + } + + if (RainRate > ThisYear.HighRainRate.Val) + { + ThisYear.HighRainRate.Val = RainRate; + ThisYear.HighRainRate.Ts = ts; + WriteYearIniFile(); + } + } + } + } + + // calculate and display rainfall in last 24 hour + var onedayago = ts.AddDays(-1); + var result = RecentDataDb.Query("select * from RecentData where Timestamp >= ? order by Timestamp limit 1", onedayago); + + if (result.Count == 0) + { + // Unable to retrieve rain counter from 24 hours ago + trendval = 0; + } + else + { + firstval = result[0].raincounter; + lastval = Raincounter; + + trendval = lastval - firstval; + // Round value as some values may have been read from log file and already rounded + trendval = Math.Round(trendval, cumulus.RainDPlaces); + + if (trendval < 0) + { + trendval = 0; + } + } + + RainLast24Hour = trendval * cumulus.Calib.Rain.Mult; + } + } + + /* + private double ConvertTempTrendToDisplay(double trendval) + { + double num; + + if (cumulus.TempUnit == 1) + { + num = (trendval*1.8F); + } + else + { + // C + num = trendval; + } + + return num; + } + */ + + public void CalculateDominantWindBearing(int averageBearing, double averageSpeed, int minutes) + { + DominantWindBearingX += (minutes * averageSpeed * Math.Sin(DegToRad(averageBearing))); + DominantWindBearingY += (minutes * averageSpeed * Math.Cos(DegToRad(averageBearing))); + DominantWindBearingMinutes += minutes; + + if (DominantWindBearingX == 0) + { + DominantWindBearing = 0; + } + else + { + try + { + DominantWindBearing = calcavgbear(DominantWindBearingX, DominantWindBearingY); + if (DominantWindBearing == 0) + { + DominantWindBearing = 360; + } + } + catch + { + cumulus.LogMessage("Error in dominant wind direction calculation"); + } + } + + /*if (DominantWindBearingX < 0) + { + DominantWindBearing = 270 - DominantWindBearing; + } + else + { + DominantWindBearing = 90 - DominantWindBearing; + }*/ + } + + public void DoDayResetIfNeeded() + { + int hourInc = cumulus.GetHourInc(); + + if (cumulus.LastUpdateTime.AddHours(hourInc).Date != DateTime.Now.AddHours(hourInc).Date) + { + cumulus.LogMessage("Day reset required"); + DayReset(DateTime.Now); + } + + if (cumulus.LastUpdateTime.Date != DateTime.Now.Date) + { + ResetMidnightRain(DateTime.Now); + ResetSunshineHours(); + //RecalcSolarFactor(DateTime.Now); + } + } + + public int DominantWindBearing { get; set; } + + public int DominantWindBearingMinutes { get; set; } + + public double DominantWindBearingY { get; set; } + + public double DominantWindBearingX { get; set; } + + public double YesterdayWindRun { get; set; } + public double AnnualETTotal { get; set; } + public double StartofdayET { get; set; } + + public int ConsecutiveRainDays { get; set; } + public int ConsecutiveDryDays { get; set; } + public DateTime FOSensorClockTime { get; set; } + public DateTime FOStationClockTime { get; set; } + public double YestAvgTemp { get; set; } + public double AltimeterPressure { get; set; } + public int YestDominantWindBearing { get; set; } + public double RainLast24Hour { get; set; } + public string ConBatText { get; set; } + public string ConSupplyVoltageText { get; set; } + public string TxBatText { get; set; } + + public double YestHeatingDegreeDays { get; set; } + public double YestCoolingDegreeDays { get; set; } + public double TempChangeLastHour { get; set; } + public double WetBulb { get; set; } + public int CloudBase { get; set; } + public double StormRain { get; set; } + public DateTime StartOfStorm { get; set; } + public bool SensorContactLost { get; set; } + public bool DataStopped { get; set; } + public bool IsRaining { get; set; } + + public void LoadLastHoursFromDataLogs(DateTime ts) + { + cumulus.LogMessage("Loading last N hour data from data logs: " + ts); + LoadLastHourFromDataLogs(ts); + LoadLast3HourFromDataLogs(ts); + LoadGraphDataFromDataLogs(ts); + LoadAqGraphDataFromDataLogs(ts); + LoadRecentFromDataLogs(ts); + LoadDayFile(); + //LoadRecentDailyDataFromDayfile(); + LoadRecentWindRose(); + } + + private void LoadRecentFromDataLogs(DateTime ts) + { + // Recent data goes back a week + var datefrom = ts.AddDays(-7); + var dateto = ts; + var entrydate = datefrom; + var filedate = datefrom; + string logFile = cumulus.GetLogFileName(filedate); + bool finished = false; + int numadded = 0; + + cumulus.LogMessage($"LoadRecent: Attempting to load 7 days of entries to recent data list"); + + while (!finished) + { + if (File.Exists(logFile)) + { + int linenum = 0; + int errorCount = 0; + + try + { + using (var sr = new StreamReader(logFile)) + { + do + { + try + { + // process each record in the file + linenum++; + string Line = sr.ReadLine(); + var st = new List(Regex.Split(Line, CultureInfo.CurrentCulture.TextInfo.ListSeparator)); + entrydate = ddmmyyhhmmStrToDate(st[0], st[1]); + + if (entrydate >= datefrom && entrydate <= dateto) + { + // entry is from required period + var raintoday = Convert.ToDouble(st[9]); + var gust = Convert.ToDouble(st[6]); + var speed = Convert.ToDouble(st[5]); + var wlatest = Convert.ToDouble(st[14]); + var bearing = Convert.ToInt32(st[24]); + var avgbearing = Convert.ToInt32(st[7]); + var outsidetemp = Convert.ToDouble(st[2]); + var dewpoint = Convert.ToDouble(st[4]); + var chill = Convert.ToDouble(st[15]); + var heat = Convert.ToDouble(st[16]); + var pressure = Convert.ToDouble(st[10]); + var hum = Convert.ToInt32(st[3]); + var solar = Convert.ToDouble(st[18]); + var uv = Convert.ToDouble(st[17]); + var raincounter = Convert.ToDouble(st[11]); + var feelslike = st.Count > 27 ? Convert.ToDouble(st[27]) : 0; + var humidex = st.Count > 28 ? Convert.ToDouble(st[28]) : 0; + + AddRecentDataEntry(entrydate, speed, gust, wlatest, bearing, avgbearing, outsidetemp, chill, dewpoint, heat, hum, pressure, raintoday, solar, uv, raincounter, feelslike, humidex); + ++numadded; + } + } + catch (Exception e) + { + cumulus.LogMessage($"LoadRecent: Error at line {linenum} of {logFile} : {e.Message}"); + cumulus.LogMessage("Please edit the file to correct the error"); + errorCount++; + if (errorCount >= 10) + { + cumulus.LogMessage($"LoadRecent: Too many errors reading {logFile} - aborting load of graph data"); + } + } + } while (!(sr.EndOfStream || entrydate >= dateto || errorCount >= 10)); + } + } + catch (Exception e) + { + cumulus.LogMessage($"LoadRecent: Error at line {linenum} of {logFile} : {e.Message}"); + cumulus.LogMessage("Please edit the file to correct the error"); + } + } + + if (entrydate >= dateto || filedate > dateto.AddMonths(1)) + { + finished = true; + } + else + { + filedate = filedate.AddMonths(1); + logFile = cumulus.GetLogFileName(filedate); + } + } + cumulus.LogMessage($"LoadRecent: Loaded {numadded} entries to recent data list"); + } + + private void LoadGraphDataFromDataLogs(DateTime ts) + { + var datefrom = ts.AddHours(-cumulus.GraphHours); + var dateto = ts; + var entrydate = datefrom; + var filedate = datefrom; + string logFile = cumulus.GetLogFileName(filedate); + bool finished = false; + + cumulus.LogMessage($"LoadGraphData: Attempting to load {cumulus.GraphHours} hours of entries to graph data list"); + + while (!finished) + { + if (File.Exists(logFile)) + { + int linenum = 0; + int errorCount = 0; + + try + { + using (var sr = new StreamReader(logFile)) + { + do + { + try + { + // process each record in the file + linenum++; + string Line = sr.ReadLine(); + var st = new List(Regex.Split(Line, CultureInfo.CurrentCulture.TextInfo.ListSeparator)); + entrydate = ddmmyyhhmmStrToDate(st[0], st[1]); + + if (entrydate >= datefrom && entrydate <= dateto) + { + // entry is from required period + var raintotal = Convert.ToDouble(st[11]); + var raintoday = Convert.ToDouble(st[9]); + var rainrate = Convert.ToDouble(st[8]); + var gust = Convert.ToDouble(st[6]); + var speed = Convert.ToDouble(st[5]); + var avgbearing = Convert.ToInt32(st[7]); + var bearing = Convert.ToInt32(st[24]); + var outsidetemp = Convert.ToDouble(st[2]); + var dewpoint = Convert.ToDouble(st[4]); + var appt = Convert.ToDouble(st[21]); + var chill = Convert.ToDouble(st[15]); + var heat = Convert.ToDouble(st[16]); + var insidetemp = Convert.ToDouble(st[12]); + var pressure = Convert.ToDouble(st[10]); + var hum = Convert.ToInt32(st[3]); + var inhum = Convert.ToInt32(st[13]); + var solar = Convert.ToDouble(st[18]); + var solarmax = Convert.ToDouble(st[22]); + var uv = Convert.ToDouble(st[17]); + var feels = st.Count > 27 ? Convert.ToDouble(st[27]) : 0.0; + var humidex = st.Count > 28 ? Convert.ToDouble(st[28]) : 0.0; + + AddGraphDataEntry(entrydate, raintotal, raintoday, rainrate, outsidetemp, dewpoint, appt, chill, heat, insidetemp, pressure, speed, gust, + avgbearing, bearing, hum, inhum, solar, solarmax, uv, feels, humidex); + } + } + catch (Exception e) + { + cumulus.LogMessage($"LoadGraphData: Error at line {linenum} of {logFile} : {e.Message}"); + cumulus.LogMessage("LoadGraphData: Please edit the file to correct the error"); + errorCount++; + if (errorCount >= 10) + { + cumulus.LogMessage($"LoadGraphData: Too many errors reading {logFile} - aborting load of graph data"); + } + } + } while (!(sr.EndOfStream || entrydate >= dateto || errorCount >= 10)); + } + } + catch (Exception e) + { + cumulus.LogMessage($"LoadGraphData: Error at line {linenum} of {logFile} : {e.Message}"); + cumulus.LogMessage("Please edit the file to correct the error"); + } + } + + if (entrydate >= dateto || filedate > dateto.AddMonths(1)) + { + finished = true; + } + else + { + filedate = filedate.AddMonths(1); + logFile = cumulus.GetLogFileName(filedate); + } + } + cumulus.LogMessage($"LoadGraphData: Loaded {GraphDataList.Count} entries to graph data list"); + } + + private void LoadAqGraphDataFromDataLogs(DateTime ts) + { + var datefrom = ts.AddHours(-cumulus.GraphHours); + var dateto = ts; + var entrydate = datefrom; + var filedate = datefrom; + string logFile; + bool finished = false; + int updatedCount = 0; + + if (cumulus.StationOptions.PrimaryAqSensor < 0) return; + + cumulus.LogMessage($"LoadAqGraphData: Attempting to load {cumulus.GraphHours} hours of entries to Air Quality graph data"); + + if (cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.AirLinkOutdoor + || cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.AirLinkIndoor) + { + logFile = cumulus.GetAirLinkLogFileName(filedate); + } + else if ((cumulus.StationOptions.PrimaryAqSensor >= (int)Cumulus.PrimaryAqSensor.Ecowitt1 && cumulus.StationOptions.PrimaryAqSensor <= (int)Cumulus.PrimaryAqSensor.Ecowitt4) || + cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.EcowittCO2) // Ecowitt + { + logFile = cumulus.GetExtraLogFileName(filedate); + } + else + { + cumulus.LogMessage($"LoadAqGraphData: Error - The primary AQ sensor is not set to a valid value, currently={cumulus.StationOptions.PrimaryAqSensor}"); + return; + } + + while (!finished) + { + if (File.Exists(logFile)) + { + int linenum = 0; + int errorCount = 0; + + try + { + using (var sr = new StreamReader(logFile)) + { + do + { + try + { + // process each record in the file + linenum++; + string Line = sr.ReadLine(); + var st = new List(Regex.Split(Line, CultureInfo.CurrentCulture.TextInfo.ListSeparator)); + entrydate = ddmmyyhhmmStrToDate(st[0], st[1]); + + if (entrydate >= datefrom && entrydate <= dateto) + { + // entry is from required period + double pm2p5, pm10; + if (cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.AirLinkIndoor) + { + // AirLink Indoor + pm2p5 = Convert.ToDouble(st[5]); + pm10 = Convert.ToDouble(st[10]); + } + else if (cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.AirLinkOutdoor) + { + // AirLink Outdoor + pm2p5 = Convert.ToDouble(st[32]); + pm10 = Convert.ToDouble(st[37]); + } + else if (cumulus.StationOptions.PrimaryAqSensor >= (int)Cumulus.PrimaryAqSensor.Ecowitt1 && cumulus.StationOptions.PrimaryAqSensor <= (int)Cumulus.PrimaryAqSensor.Ecowitt4) + { + // Ecowitt sensor 1-4 - fields 68 -> 71 + pm2p5 = Convert.ToDouble(st[67 + cumulus.StationOptions.PrimaryAqSensor]); + pm10 = -1; + } + else + { + // Ecowitt CO2 sensor + pm2p5 = Convert.ToDouble(st[86]); + pm10 = Convert.ToDouble(st[88]); + } + + UpdateGraphDataAqEntry(entrydate, pm2p5, pm10); + updatedCount++; + } + } + catch (Exception e) + { + cumulus.LogMessage($"LoadAqGraphData: Error at line {linenum} of {logFile} : {e.Message}"); + cumulus.LogMessage("Please edit the file to correct the error"); + errorCount++; + if (errorCount >= 20) + { + cumulus.LogMessage($"LoadAqGraphData: Too many errors reading {logFile} - aborting load of graph data"); + } + } + } while (!(sr.EndOfStream || entrydate >= dateto || errorCount >= 20)); + } + } + catch (Exception e) + { + cumulus.LogMessage($"LoadAqGraphData: Error at line {linenum} of {logFile} : {e.Message}"); + cumulus.LogMessage("Please edit the file to correct the error"); + } + } + + if (entrydate >= dateto || filedate > dateto.AddMonths(1)) + { + finished = true; + } + else + { + filedate = filedate.AddMonths(1); + if (cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.AirLinkOutdoor + || cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.AirLinkIndoor) // AirLink + { + logFile = cumulus.GetAirLinkLogFileName(filedate); + } + else if ((cumulus.StationOptions.PrimaryAqSensor >= (int)Cumulus.PrimaryAqSensor.Ecowitt1 + && cumulus.StationOptions.PrimaryAqSensor <= (int)Cumulus.PrimaryAqSensor.Ecowitt4) + || cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.EcowittCO2) // Ecowitt + { + logFile = cumulus.GetExtraLogFileName(filedate); + } + } + } + cumulus.LogMessage($"LoadAqGraphData: Loaded {updatedCount} entries to graph data list"); + } + + + private void LoadRecentWindRose() + { + // We can now just query the recent data DB as it has been populated from the loags + var datefrom = DateTime.Now.AddHours(-24); + + var result = RecentDataDb.Query("select WindGust, WindDir from RecentData where Timestamp >= ? order by Timestamp", datefrom); + + foreach (var rec in result) + { + windspeeds[nextwindvalue] = rec.WindGust; + windbears[nextwindvalue] = rec.WindDir; + nextwindvalue = (nextwindvalue + 1) % MaxWindRecent; + if (numwindvalues < maxwindvalues) + { + numwindvalues++; + } + } + } + + private void LoadLast3HourFromDataLogs(DateTime ts) + { + var datefrom = ts.AddHours(-3); + var dateto = ts; + var entrydate = datefrom; + var filedate = datefrom; + string logFile = cumulus.GetLogFileName(filedate); + bool finished = false; + + cumulus.LogMessage($"LoadLast3Hour: Attempting to load 3 hour data list"); + + while (!finished) + { + if (File.Exists(logFile)) + { + int linenum = 0; + int errorCount = 0; + try + { + using (var sr = new StreamReader(logFile)) + { + do + { + try + { + // process each record in the file + linenum++; + string Line = sr.ReadLine(); + var st = new List(Regex.Split(Line, CultureInfo.CurrentCulture.TextInfo.ListSeparator)); + entrydate = ddmmyyhhmmStrToDate(st[0], st[1]); + + if (entrydate >= datefrom && entrydate <= dateto) + { + // entry is from required period + var outsidetemp = Convert.ToDouble(st[2]); + var pressure = Convert.ToDouble(st[10]); + + AddLast3HourDataEntry(entrydate, pressure, outsidetemp); + + var gust = Convert.ToDouble(st[14]); + var speed = Convert.ToDouble(st[5]); + var bearing = Convert.ToInt32(st[7]); + + WindRecent[nextwind].Gust = gust; + WindRecent[nextwind].Speed = speed; + WindRecent[nextwind].Timestamp = entrydate; + nextwind = (nextwind + 1) % MaxWindRecent; + + WindVec[nextwindvec].X = gust * Math.Sin(DegToRad(bearing)); + WindVec[nextwindvec].Y = gust * Math.Cos(DegToRad(bearing)); + WindVec[nextwindvec].Timestamp = entrydate; + WindVec[nextwindvec].Bearing = Bearing; // savedBearing; + nextwindvec = (nextwindvec + 1) % MaxWindRecent; + } + } + catch (Exception e) + { + cumulus.LogMessage($"LoadLast3Hour: Error at line {linenum} of {logFile} : {e.Message}"); + cumulus.LogMessage("Please edit the file to correct the error"); + errorCount++; + if (errorCount >= 10) + { + cumulus.LogMessage($"LoadLast3Hour: Too many errors reading {logFile} - aborting load of last hour data"); + } + } + } while (!(sr.EndOfStream || entrydate >= dateto || errorCount >= 10)); + } + } + catch (Exception e) + { + cumulus.LogMessage($"LoadLast3Hour: Error at line {linenum} of {logFile} : {e.Message}"); + cumulus.LogMessage("Please edit the file to correct the error"); + } + } + + if (entrydate >= dateto || filedate > dateto.AddMonths(1)) + { + finished = true; + } + else + { + filedate = filedate.AddMonths(1); + logFile = cumulus.GetLogFileName(filedate); + } + } + cumulus.LogMessage($"LoadLast3Hour: Loaded {Last3HourDataList.Count} entries to last 3 hour data list"); + } + + private void LoadLastHourFromDataLogs(DateTime ts) + { + var datefrom = ts.AddHours(-1); + var dateto = ts; + var entrydate = datefrom; + var filedate = datefrom; + string logFile = cumulus.GetLogFileName(filedate); + bool finished = false; + + cumulus.LogMessage("LoadLastHour: Attempting to load last hour entries"); + + while (!finished) + { + if (File.Exists(logFile)) + { + int linenum = 0; + int errorCount = 0; + + try + { + using (var sr = new StreamReader(logFile)) + { + do + { + try + { + // process each record in the file + linenum++; + string Line = sr.ReadLine(); + var st = new List(Regex.Split(Line, CultureInfo.CurrentCulture.TextInfo.ListSeparator)); + entrydate = ddmmyyhhmmStrToDate(st[0], st[1]); + + if (entrydate >= datefrom && entrydate <= dateto) + { + // entry is from required period + var outsidetemp = Convert.ToDouble(st[2]); + var raintotal = Convert.ToDouble(st[11]); + + AddLastHourDataEntry(entrydate, raintotal, outsidetemp); + } + } + catch (Exception e) + { + cumulus.LogMessage($"LoadLastHour: Error at line {linenum} of {logFile} : {e.Message}"); + cumulus.LogMessage("Please edit the file to correct the error"); + errorCount++; + if (errorCount >= 10) + { + cumulus.LogMessage($"LoadLastHour: Too many errors reading {logFile} - aborting load of last hour data"); + } + } + } while (!(sr.EndOfStream || entrydate >= dateto || errorCount >= 10)); + } + } + catch (Exception e) + { + cumulus.LogMessage($"LoadLastHour: Error at line {linenum} of {logFile} : {e.Message}"); + cumulus.LogMessage("Please edit the file to correct the error"); + } + } + + if (entrydate >= dateto || filedate > dateto.AddMonths(1)) + { + finished = true; + } + else + { + filedate = filedate.AddMonths(1); + logFile = cumulus.GetLogFileName(filedate); + } + } + cumulus.LogMessage($"LoadLastHour: Loaded {LastHourDataList.Count} entries to last hour data list"); + } + + private static DateTime GetDateTime(DateTime date, string time) + { + 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); + } + + public void LoadDayFile() + { + int addedEntries = 0; + + cumulus.LogMessage($"LoadDayFile: Attempting to load the day file"); + if (File.Exists(cumulus.DayFileName)) + { + int linenum = 0; + int errorCount = 0; + + var watch = Stopwatch.StartNew(); + + // Clear the existing list + DayFile.Clear(); + + try + { + using (var sr = new StreamReader(cumulus.DayFileName)) + { + do + { + try + { + // process each record in the file + + linenum++; + string Line = sr.ReadLine(); + DayFile.Add(ParseDayFileRec(Line)); + + addedEntries++; + } + catch (Exception e) + { + cumulus.LogMessage($"LoadDayFile: Error at line {linenum} of {cumulus.DayFileName} : {e.Message}"); + cumulus.LogMessage("Please edit the file to correct the error"); + errorCount++; + if (errorCount >= 20) + { + cumulus.LogMessage($"LoadDayFile: Too many errors reading {cumulus.DayFileName} - aborting load of daily data"); + } + } + } while (!(sr.EndOfStream || errorCount >= 20)); + } + + watch.Stop(); + cumulus.LogDebugMessage($"LoadDayFile: Dayfile parse = {watch.ElapsedMilliseconds} ms"); + + } + catch (Exception e) + { + cumulus.LogMessage($"LoadDayFile: Error at line {linenum} of {cumulus.DayFileName} : {e.Message}"); + cumulus.LogMessage("Please edit the file to correct the error"); + } + cumulus.LogMessage($"LoadDayFile: Loaded {addedEntries} entries to recent daily data list"); + } + else + { + cumulus.LogMessage("LoadDayFile: No Dayfile found - No entries added to recent daily data list"); + } + } + + // errors are caught by the caller + public dayfilerec ParseDayFileRec(string data) + { + var st = new List(Regex.Split(data, CultureInfo.CurrentCulture.TextInfo.ListSeparator)); + double varDbl; + int varInt; + int idx = 0; + + var rec = new dayfilerec(); + try + { + rec.Date = ddmmyyStrToDate(st[idx++]); + rec.HighGust = Convert.ToDouble(st[idx++]); + rec.HighGustBearing = Convert.ToInt32(st[idx++]); + rec.HighGustTime = GetDateTime(rec.Date, st[idx++]); + rec.LowTemp = Convert.ToDouble(st[idx++]); + rec.LowTempTime = GetDateTime(rec.Date, st[idx++]); + rec.HighTemp = Convert.ToDouble(st[idx++]); + rec.HighTempTime = GetDateTime(rec.Date, st[idx++]); + rec.LowPress = Convert.ToDouble(st[idx++]); + rec.LowPressTime = GetDateTime(rec.Date, st[idx++]); + rec.HighPress = Convert.ToDouble(st[idx++]); + rec.HighPressTime = GetDateTime(rec.Date, st[idx++]); + rec.HighRainRate = Convert.ToDouble(st[idx++]); + rec.HighRainRateTime = GetDateTime(rec.Date, st[idx++]); + rec.TotalRain = Convert.ToDouble(st[idx++]); + rec.AvgTemp = Convert.ToDouble(st[idx++]); + + if (st.Count > idx++ && double.TryParse(st[16], out varDbl)) + rec.WindRun = varDbl; + + if (st.Count > idx++ && double.TryParse(st[17], out varDbl)) + rec.HighAvgWind = varDbl; + + if (st.Count > idx++ && st[18].Length == 5) + rec.HighAvgWindTime = GetDateTime(rec.Date, st[18]); + + if (st.Count > idx++ && int.TryParse(st[19], out varInt)) + rec.LowHumidity = varInt; + else + rec.LowHumidity = 9999; + + if (st.Count > idx++ && st[20].Length == 5) + rec.LowHumidityTime = GetDateTime(rec.Date, st[20]); + + if (st.Count > idx++ && int.TryParse(st[21], out varInt)) + rec.HighHumidity = varInt; + else + rec.HighHumidity = -9999; + + if (st.Count > idx++ && st[22].Length == 5) + rec.HighHumidityTime = GetDateTime(rec.Date, st[22]); + + if (st.Count > idx++ && double.TryParse(st[23], out varDbl)) + rec.ET = varDbl; + + if (st.Count > idx++ && double.TryParse(st[24], out varDbl)) + rec.SunShineHours = varDbl; + + if (st.Count > idx++ && double.TryParse(st[25], out varDbl)) + rec.HighHeatIndex = varDbl; + else + rec.HighHeatIndex = -9999; + + if (st.Count > idx++ && st[26].Length == 5) + rec.HighHeatIndexTime = GetDateTime(rec.Date, st[26]); + + if (st.Count > idx++ && double.TryParse(st[27], out varDbl)) + rec.HighAppTemp = varDbl; + else + rec.HighAppTemp = -9999; + + if (st.Count > idx++ && st[28].Length == 5) + rec.HighAppTempTime = GetDateTime(rec.Date, st[28]); + + if (st.Count > idx++ && double.TryParse(st[29], out varDbl)) + rec.LowAppTemp = varDbl; + else + rec.LowAppTemp = 9999; + + if (st.Count > idx++ && st[30].Length == 5) + rec.LowAppTempTime = GetDateTime(rec.Date, st[30]); + + if (st.Count > idx++ && double.TryParse(st[31], out varDbl)) + rec.HighHourlyRain = varDbl; + + if (st.Count > idx++ && st[32].Length == 5) + rec.HighHourlyRainTime = GetDateTime(rec.Date, st[32]); + + if (st.Count > idx++ && double.TryParse(st[33], out varDbl)) + rec.LowWindChill = varDbl; + else + rec.LowWindChill = 9999; + + if (st.Count > idx++ && st[34].Length == 5) + rec.LowWindChillTime = GetDateTime(rec.Date, st[34]); + + if (st.Count > idx++ && double.TryParse(st[35], out varDbl)) + rec.HighDewPoint = varDbl; + else + rec.HighDewPoint = -9999; + + if (st.Count > idx++ && st[36].Length == 5) + rec.HighDewPointTime = GetDateTime(rec.Date, st[36]); + + if (st.Count > idx++ && double.TryParse(st[37], out varDbl)) + rec.LowDewPoint = varDbl; + else + rec.LowDewPoint = 9999; + + if (st.Count > idx++ && st[38].Length == 5) + rec.LowDewPointTime = GetDateTime(rec.Date, st[38]); + + if (st.Count > idx++ && int.TryParse(st[39], out varInt)) + rec.DominantWindBearing = varInt; + + if (st.Count > idx++ && double.TryParse(st[40], out varDbl)) + rec.HeatingDegreeDays = varDbl; + + if (st.Count > idx++ && double.TryParse(st[41], out varDbl)) + rec.CoolingDegreeDays = varDbl; + + if (st.Count > idx++ && int.TryParse(st[42], out varInt)) + rec.HighSolar = varInt; + + if (st.Count > idx++ && st[43].Length == 5) + rec.HighSolarTime = GetDateTime(rec.Date, st[43]); + + if (st.Count > idx++ && double.TryParse(st[44], out varDbl)) + rec.HighUv = varDbl; + + if (st.Count > idx++ && st[45].Length == 5) + rec.HighUvTime = GetDateTime(rec.Date, st[45]); + + if (st.Count > idx++ && double.TryParse(st[46], out varDbl)) + rec.HighFeelsLike = varDbl; + else + rec.HighFeelsLike = -9999; + + if (st.Count > idx++ && st[47].Length == 5) + rec.HighFeelsLikeTime = GetDateTime(rec.Date, st[47]); + + if (st.Count > idx++ && double.TryParse(st[48], out varDbl)) + rec.LowFeelsLike = varDbl; + else + rec.LowFeelsLike = 9999; + + if (st.Count > idx++ && st[49].Length == 5) + rec.LowFeelsLikeTime = GetDateTime(rec.Date, st[49]); + + if (st.Count > idx++ && double.TryParse(st[50], out varDbl)) + rec.HighHumidex = varDbl; + else + rec.HighHumidex = -9999; + + if (st.Count > idx++ && st[51].Length == 5) + rec.HighHumidexTime = GetDateTime(rec.Date, st[51]); + } + catch (Exception ex) + { + cumulus.LogDebugMessage($"ParseDayFileRec: Error at record {idx} - {ex.Message}"); + var e = new Exception($"Error at record {idx} = \"{st[idx-1]}\" - {ex.Message}"); + throw e; + } + return rec; + } + + + protected void UpdateStatusPanel(DateTime timestamp) + { + LastDataReadTimestamp = timestamp; + } + + + protected void UpdateMQTT() + { + if (cumulus.MQTT.EnableDataUpdate) + { + MqttPublisher.UpdateMQTTfeed("DataUpdate"); + } + } + + /// + /// Returns a plus sign if the supplied number is greater than zero, otherwise empty string + /// + /// The number to be tested + /// Plus sign or empty + /* + private string PlusSign(double num) + { + return num > 0 ? "+" : ""; + } + */ + + public void DoET(double value, DateTime timestamp) + { + // Value is annual total + + if (noET) + { + // Start of day ET value not yet set + cumulus.LogMessage("*** First ET reading. Set startofdayET to total: " + value); + StartofdayET = value; + noET = false; + } + + //if ((value == 0) && (StartofdayET > 0)) + if (Math.Round(value, 3) < Math.Round(StartofdayET, 3)) // change b3046 + { + // ET reset + cumulus.LogMessage(String.Format("*** ET Reset *** AnnualET: {0:0.000}, StartofdayET: {1:0.000}, StationET: {2:0.000}, CurrentET: {3:0.000}", AnnualETTotal, StartofdayET, value, ET)); + AnnualETTotal = value; // add b3046 + // set the start of day figure so it reflects the ET + // so far today + StartofdayET = AnnualETTotal - ET; + WriteTodayFile(timestamp, false); + cumulus.LogMessage(String.Format("New ET values. AnnualET: {0:0.000}, StartofdayET: {1:0.000}, StationET: {2:0.000}, CurrentET: {3:0.000}", AnnualETTotal, StartofdayET, value, ET)); + } + else + { + AnnualETTotal = value; + } + + ET = AnnualETTotal - StartofdayET; + + HaveReadData = true; + } + + public void DoSoilMoisture(double value, int index) + { + switch (index) + { + case 1: + SoilMoisture1 = (int)value; + break; + case 2: + SoilMoisture2 = (int)value; + break; + case 3: + SoilMoisture3 = (int)value; + break; + case 4: + SoilMoisture4 = (int)value; + break; + case 5: + SoilMoisture5 = (int)value; + break; + case 6: + SoilMoisture6 = (int)value; + break; + case 7: + SoilMoisture7 = (int)value; + break; + case 8: + SoilMoisture8 = (int)value; + break; + case 9: + SoilMoisture9 = (int)value; + break; + case 10: + SoilMoisture10 = (int)value; + break; + case 11: + SoilMoisture11 = (int)value; + break; + case 12: + SoilMoisture12 = (int)value; + break; + case 13: + SoilMoisture13 = (int)value; + break; + case 14: + SoilMoisture14 = (int)value; + break; + case 15: + SoilMoisture15 = (int)value; + break; + case 16: + SoilMoisture16 = (int)value; + break; + } + } + + public void DoSoilTemp(double value, int index) + { + switch (index) + { + case 1: + SoilTemp1 = value; + break; + case 2: + SoilTemp2 = value; + break; + case 3: + SoilTemp3 = value; + break; + case 4: + SoilTemp4 = value; + break; + case 5: + SoilTemp5 = value; + break; + case 6: + SoilTemp6 = value; + break; + case 7: + SoilTemp7 = value; + break; + case 8: + SoilTemp8 = value; + break; + case 9: + SoilTemp9 = value; + break; + case 10: + SoilTemp10 = value; + break; + case 11: + SoilTemp11 = value; + break; + case 12: + SoilTemp12 = value; + break; + case 13: + SoilTemp13 = value; + break; + case 14: + SoilTemp14 = value; + break; + case 15: + SoilTemp15 = value; + break; + case 16: + SoilTemp16 = value; + break; + } + } + + public void DoAirQuality(double value, int index) + { + switch (index) + { + case 1: + AirQuality1 = value; + break; + case 2: + AirQuality2 = value; + break; + case 3: + AirQuality3 = value; + break; + case 4: + AirQuality4 = value; + break; + } + } + + public void DoAirQualityAvg(double value, int index) + { + switch (index) + { + case 1: + AirQualityAvg1 = value; + break; + case 2: + AirQualityAvg2 = value; + break; + case 3: + AirQualityAvg3 = value; + break; + case 4: + AirQualityAvg4 = value; + break; + } + } + + public void DoLeakSensor(int value, int index) + { + switch (index) + { + case 1: + LeakSensor1 = value; + break; + case 2: + LeakSensor2 = value; + break; + case 3: + LeakSensor3 = value; + break; + case 4: + LeakSensor4 = value; + break; + } + } + + public void DoLeafWetness(double value, int index) + { + switch (index) + { + case 1: + LeafWetness1 = (int)value; + break; + case 2: + LeafWetness2 = (int)value; + break; + case 3: + LeafWetness3 = (int)value; + break; + case 4: + LeafWetness4 = (int)value; + break; + case 5: + LeafWetness5 = (int)value; + break; + case 6: + LeafWetness6 = (int)value; + break; + case 7: + LeafWetness7 = (int)value; + break; + case 8: + LeafWetness8 = (int)value; + break; + } + } + + public void DoLeafTemp(double value, int index) + { + switch (index) + { + case 1: + LeafTemp1 = value; + break; + case 2: + LeafTemp2 = value; + break; + case 3: + LeafTemp3 = value; + break; + case 4: + LeafTemp4 = value; + break; + } + } + + public string BetelCast(double z_hpa, int z_month, string z_wind, int z_trend, bool z_north, double z_baro_top, double z_baro_bottom) + { + double z_range = z_baro_top - z_baro_bottom; + double z_constant = (z_range / 22.0F); + + bool z_summer = (z_month >= 4 && z_month <= 9); // true if "Summer" + + if (z_north) + { + // North hemisphere + if (z_wind == cumulus.compassp[0]) // N + { + z_hpa += 6F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[1]) // NNE + { + z_hpa += 5F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[2]) // NE + { + // z_hpa += 4 ; + z_hpa += 5F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[3]) // ENE + { + z_hpa += 2F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[4]) // E + { + z_hpa -= 0.5F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[5]) // ESE + { + // z_hpa -= 3 ; + z_hpa -= 2F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[6]) // SE + { + z_hpa -= 5F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[7]) // SSE + { + z_hpa -= 8.5F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[8]) // S + { + // z_hpa -= 11 ; + z_hpa -= 12F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[9]) // SSW + { + z_hpa -= 10F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[10]) // SW + { + z_hpa -= 6F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[11]) // WSW + { + z_hpa -= 4.5F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[12]) // W + { + z_hpa -= 3F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[13]) // WNW + { + z_hpa -= 0.5F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[14]) // NW + { + z_hpa += 1.5F / 100 * z_range; + } + else if (z_wind == cumulus.compassp[15]) // NNW + { + z_hpa += 3F / 100F * z_range; + } + if (z_summer) + { + // if Summer + if (z_trend == 1) + { + // rising + z_hpa += 7F / 100F * z_range; + } + else if (z_trend == 2) + { + // falling + z_hpa -= 7F / 100F * z_range; + } + } + } + else + { + // must be South hemisphere + if (z_wind == cumulus.compassp[8]) // S + { + z_hpa += 6F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[9]) // SSW + { + z_hpa += 5F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[10]) // SW + { + // z_hpa += 4 ; + z_hpa += 5F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[11]) // WSW + { + z_hpa += 2F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[12]) // W + { + z_hpa -= 0.5F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[13]) // WNW + { + // z_hpa -= 3 ; + z_hpa -= 2F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[14]) // NW + { + z_hpa -= 5F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[15]) // NNW + { + z_hpa -= 8.5F / 100 * z_range; + } + else if (z_wind == cumulus.compassp[0]) // N + { + // z_hpa -= 11 ; + z_hpa -= 12F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[1]) // NNE + { + z_hpa -= 10F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[2]) // NE + { + z_hpa -= 6F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[3]) // ENE + { + z_hpa -= 4.5F / 100 * z_range; // + } + else if (z_wind == cumulus.compassp[4]) // E + { + z_hpa -= 3F / 100F * z_range; + } + else if (z_wind == cumulus.compassp[5]) // ESE + { + z_hpa -= 0.5F / 100 * z_range; + } + else if (z_wind == cumulus.compassp[6]) // SE + { + z_hpa += 1.5F / 100 * z_range; + } + else if (z_wind == cumulus.compassp[7]) // SSE + { + z_hpa += 3F / 100F * z_range; + } + if (!z_summer) + { + // if Winter + if (z_trend == 1) + { + // rising + z_hpa += 7F / 100F * z_range; + } + else if (z_trend == 2) + { + // falling + z_hpa -= 7F / 100F * z_range; + } + } + } // END North / South + + if (z_hpa == z_baro_top) + { + z_hpa = z_baro_top - 1; + } + + int z_option = (int)Math.Floor((z_hpa - z_baro_bottom) / z_constant); + + StringBuilder z_output = new StringBuilder(100); + if (z_option < 0) + { + z_option = 0; + z_output.Append($"{cumulus.exceptional}, "); + } + if (z_option > 21) + { + z_option = 21; + z_output.Append($"{cumulus.exceptional}, "); + } + + if (z_trend == 1) + { + // rising + Forecastnumber = cumulus.riseOptions[z_option] + 1; + z_output.Append(cumulus.zForecast[cumulus.riseOptions[z_option]]); + } + else if (z_trend == 2) + { + // falling + Forecastnumber = cumulus.fallOptions[z_option] + 1; + z_output.Append(cumulus.zForecast[cumulus.fallOptions[z_option]]); + } + else + { + // must be "steady" + Forecastnumber = cumulus.steadyOptions[z_option] + 1; + z_output.Append(cumulus.zForecast[cumulus.steadyOptions[z_option]]); + } + return z_output.ToString(); + } + + public int Forecastnumber { get; set; } + + /// + /// Takes speed in user units, returns Bft number + /// + /// + /// + public int Beaufort(double speed) + { + double windspeedMS = ConvertUserWindToMS(speed); + if (windspeedMS < 0.3) + return 0; + else if (windspeedMS < 1.6) + return 1; + else if (windspeedMS < 3.4) + return 2; + else if (windspeedMS < 5.5) + return 3; + else if (windspeedMS < 8.0) + return 4; + else if (windspeedMS < 10.8) + return 5; + else if (windspeedMS < 13.9) + return 6; + else if (windspeedMS < 17.2) + return 7; + else if (windspeedMS < 20.8) + return 8; + else if (windspeedMS < 24.5) + return 9; + else if (windspeedMS < 28.5) + return 10; + else if (windspeedMS < 32.7) + return 11; + else return 12; + } + + // This overridden in each station implementation + public abstract void Stop(); + + public void ReadAlltimeIniFile() + { + cumulus.LogMessage(Path.GetFullPath(cumulus.AlltimeIniFile)); + IniFile ini = new IniFile(cumulus.AlltimeIniFile); + + AllTime.HighTemp.Val = ini.GetValue("Temperature", "hightempvalue", -999.0); + AllTime.HighTemp.Ts = ini.GetValue("Temperature", "hightemptime", cumulus.defaultRecordTS); + + AllTime.LowTemp.Val = ini.GetValue("Temperature", "lowtempvalue", 999.0); + AllTime.LowTemp.Ts = ini.GetValue("Temperature", "lowtemptime", cumulus.defaultRecordTS); + + AllTime.LowChill.Val = ini.GetValue("Temperature", "lowchillvalue", 999.0); + AllTime.LowChill.Ts = ini.GetValue("Temperature", "lowchilltime", cumulus.defaultRecordTS); + + AllTime.HighMinTemp.Val = ini.GetValue("Temperature", "highmintempvalue", -999.0); + AllTime.HighMinTemp.Ts = ini.GetValue("Temperature", "highmintemptime", cumulus.defaultRecordTS); + + AllTime.LowMaxTemp.Val = ini.GetValue("Temperature", "lowmaxtempvalue", 999.0); + AllTime.LowMaxTemp.Ts = ini.GetValue("Temperature", "lowmaxtemptime", cumulus.defaultRecordTS); + + AllTime.HighAppTemp.Val = ini.GetValue("Temperature", "highapptempvalue", -999.0); + AllTime.HighAppTemp.Ts = ini.GetValue("Temperature", "highapptemptime", cumulus.defaultRecordTS); + + AllTime.LowAppTemp.Val = ini.GetValue("Temperature", "lowapptempvalue", 999.0); + AllTime.LowAppTemp.Ts = ini.GetValue("Temperature", "lowapptemptime", cumulus.defaultRecordTS); + + AllTime.HighFeelsLike.Val = ini.GetValue("Temperature", "highfeelslikevalue", -999.0); + AllTime.HighFeelsLike.Ts = ini.GetValue("Temperature", "highfeelsliketime", cumulus.defaultRecordTS); + + AllTime.LowFeelsLike.Val = ini.GetValue("Temperature", "lowfeelslikevalue", 999.0); + AllTime.LowFeelsLike.Ts = ini.GetValue("Temperature", "lowfeelsliketime", cumulus.defaultRecordTS); + + AllTime.HighHumidex.Val = ini.GetValue("Temperature", "highhumidexvalue", -999.0); + AllTime.HighHumidex.Ts = ini.GetValue("Temperature", "highhumidextime", cumulus.defaultRecordTS); + + AllTime.HighHeatIndex.Val = ini.GetValue("Temperature", "highheatindexvalue", -999.0); + AllTime.HighHeatIndex.Ts = ini.GetValue("Temperature", "highheatindextime", cumulus.defaultRecordTS); + + AllTime.HighDewPoint.Val = ini.GetValue("Temperature", "highdewpointvalue", -999.0); + AllTime.HighDewPoint.Ts = ini.GetValue("Temperature", "highdewpointtime", cumulus.defaultRecordTS); + + AllTime.LowDewPoint.Val = ini.GetValue("Temperature", "lowdewpointvalue", 999.0); + AllTime.LowDewPoint.Ts = ini.GetValue("Temperature", "lowdewpointtime", cumulus.defaultRecordTS); + + AllTime.HighDailyTempRange.Val = ini.GetValue("Temperature", "hightemprangevalue", 0.0); + AllTime.HighDailyTempRange.Ts = ini.GetValue("Temperature", "hightemprangetime", cumulus.defaultRecordTS); + + AllTime.LowDailyTempRange.Val = ini.GetValue("Temperature", "lowtemprangevalue", 999.0); + AllTime.LowDailyTempRange.Ts = ini.GetValue("Temperature", "lowtemprangetime", cumulus.defaultRecordTS); + + AllTime.HighWind.Val = ini.GetValue("Wind", "highwindvalue", 0.0); + AllTime.HighWind.Ts = ini.GetValue("Wind", "highwindtime", cumulus.defaultRecordTS); + + AllTime.HighGust.Val = ini.GetValue("Wind", "highgustvalue", 0.0); + AllTime.HighGust.Ts = ini.GetValue("Wind", "highgusttime", cumulus.defaultRecordTS); + + AllTime.HighWindRun.Val = ini.GetValue("Wind", "highdailywindrunvalue", 0.0); + AllTime.HighWindRun.Ts = ini.GetValue("Wind", "highdailywindruntime", cumulus.defaultRecordTS); + + AllTime.HighRainRate.Val = ini.GetValue("Rain", "highrainratevalue", 0.0); + AllTime.HighRainRate.Ts = ini.GetValue("Rain", "highrainratetime", cumulus.defaultRecordTS); + + AllTime.DailyRain.Val = ini.GetValue("Rain", "highdailyrainvalue", 0.0); + AllTime.DailyRain.Ts = ini.GetValue("Rain", "highdailyraintime", cumulus.defaultRecordTS); + + AllTime.HourlyRain.Val = ini.GetValue("Rain", "highhourlyrainvalue", 0.0); + AllTime.HourlyRain.Ts = ini.GetValue("Rain", "highhourlyraintime", cumulus.defaultRecordTS); + + AllTime.MonthlyRain.Val = ini.GetValue("Rain", "highmonthlyrainvalue", 0.0); + AllTime.MonthlyRain.Ts = ini.GetValue("Rain", "highmonthlyraintime", cumulus.defaultRecordTS); + + AllTime.LongestDryPeriod.Val = ini.GetValue("Rain", "longestdryperiodvalue", 0); + AllTime.LongestDryPeriod.Ts = ini.GetValue("Rain", "longestdryperiodtime", cumulus.defaultRecordTS); + + AllTime.LongestWetPeriod.Val = ini.GetValue("Rain", "longestwetperiodvalue", 0); + AllTime.LongestWetPeriod.Ts = ini.GetValue("Rain", "longestwetperiodtime", cumulus.defaultRecordTS); + + AllTime.HighPress.Val = ini.GetValue("Pressure", "highpressurevalue", 0.0); + AllTime.HighPress.Ts = ini.GetValue("Pressure", "highpressuretime", cumulus.defaultRecordTS); + + AllTime.LowPress.Val = ini.GetValue("Pressure", "lowpressurevalue", 9999.0); + AllTime.LowPress.Ts = ini.GetValue("Pressure", "lowpressuretime", cumulus.defaultRecordTS); + + AllTime.HighHumidity.Val = ini.GetValue("Humidity", "highhumidityvalue", 0); + AllTime.HighHumidity.Ts = ini.GetValue("Humidity", "highhumiditytime", cumulus.defaultRecordTS); + + AllTime.LowHumidity.Val = ini.GetValue("Humidity", "lowhumidityvalue", 999); + AllTime.LowHumidity.Ts = ini.GetValue("Humidity", "lowhumiditytime", cumulus.defaultRecordTS); + + cumulus.LogMessage("Alltime.ini file read"); + } + + public void WriteAlltimeIniFile() + { + try + { + IniFile ini = new IniFile(cumulus.AlltimeIniFile); + + ini.SetValue("Temperature", "hightempvalue", AllTime.HighTemp.Val); + ini.SetValue("Temperature", "hightemptime", AllTime.HighTemp.Ts); + ini.SetValue("Temperature", "lowtempvalue", AllTime.LowTemp.Val); + ini.SetValue("Temperature", "lowtemptime", AllTime.LowTemp.Ts); + ini.SetValue("Temperature", "lowchillvalue", AllTime.LowChill.Val); + ini.SetValue("Temperature", "lowchilltime", AllTime.LowChill.Ts); + ini.SetValue("Temperature", "highmintempvalue", AllTime.HighMinTemp.Val); + ini.SetValue("Temperature", "highmintemptime", AllTime.HighMinTemp.Ts); + ini.SetValue("Temperature", "lowmaxtempvalue", AllTime.LowMaxTemp.Val); + ini.SetValue("Temperature", "lowmaxtemptime", AllTime.LowMaxTemp.Ts); + ini.SetValue("Temperature", "highapptempvalue", AllTime.HighAppTemp.Val); + ini.SetValue("Temperature", "highapptemptime", AllTime.HighAppTemp.Ts); + ini.SetValue("Temperature", "lowapptempvalue", AllTime.LowAppTemp.Val); + ini.SetValue("Temperature", "lowapptemptime", AllTime.LowAppTemp.Ts); + ini.SetValue("Temperature", "highfeelslikevalue", AllTime.HighFeelsLike.Val); + ini.SetValue("Temperature", "highfeelsliketime", AllTime.HighFeelsLike.Ts); + ini.SetValue("Temperature", "lowfeelslikevalue", AllTime.LowFeelsLike.Val); + ini.SetValue("Temperature", "lowfeelsliketime", AllTime.LowFeelsLike.Ts); + ini.SetValue("Temperature", "highhumidexvalue", AllTime.HighHumidex.Val); + ini.SetValue("Temperature", "highhumidextime", AllTime.HighHumidex.Ts); + ini.SetValue("Temperature", "highheatindexvalue", AllTime.HighHeatIndex.Val); + ini.SetValue("Temperature", "highheatindextime", AllTime.HighHeatIndex.Ts); + ini.SetValue("Temperature", "highdewpointvalue", AllTime.HighDewPoint.Val); + ini.SetValue("Temperature", "highdewpointtime", AllTime.HighDewPoint.Ts); + ini.SetValue("Temperature", "lowdewpointvalue", AllTime.LowDewPoint.Val); + ini.SetValue("Temperature", "lowdewpointtime", AllTime.LowDewPoint.Ts); + ini.SetValue("Temperature", "hightemprangevalue", AllTime.HighDailyTempRange.Val); + ini.SetValue("Temperature", "hightemprangetime", AllTime.HighDailyTempRange.Ts); + ini.SetValue("Temperature", "lowtemprangevalue", AllTime.LowDailyTempRange.Val); + ini.SetValue("Temperature", "lowtemprangetime", AllTime.LowDailyTempRange.Ts); + ini.SetValue("Wind", "highwindvalue", AllTime.HighWind.Val); + ini.SetValue("Wind", "highwindtime", AllTime.HighWind.Ts); + ini.SetValue("Wind", "highgustvalue", AllTime.HighGust.Val); + ini.SetValue("Wind", "highgusttime", AllTime.HighGust.Ts); + ini.SetValue("Wind", "highdailywindrunvalue", AllTime.HighWindRun.Val); + ini.SetValue("Wind", "highdailywindruntime", AllTime.HighWindRun.Ts); + ini.SetValue("Rain", "highrainratevalue", AllTime.HighRainRate.Val); + ini.SetValue("Rain", "highrainratetime", AllTime.HighRainRate.Ts); + ini.SetValue("Rain", "highdailyrainvalue", AllTime.DailyRain.Val); + ini.SetValue("Rain", "highdailyraintime", AllTime.DailyRain.Ts); + ini.SetValue("Rain", "highhourlyrainvalue", AllTime.HourlyRain.Val); + ini.SetValue("Rain", "highhourlyraintime", AllTime.HourlyRain.Ts); + ini.SetValue("Rain", "highmonthlyrainvalue", AllTime.MonthlyRain.Val); + ini.SetValue("Rain", "highmonthlyraintime", AllTime.MonthlyRain.Ts); + ini.SetValue("Rain", "longestdryperiodvalue", AllTime.LongestDryPeriod.Val); + ini.SetValue("Rain", "longestdryperiodtime", AllTime.LongestDryPeriod.Ts); + ini.SetValue("Rain", "longestwetperiodvalue", AllTime.LongestWetPeriod.Val); + ini.SetValue("Rain", "longestwetperiodtime", AllTime.LongestWetPeriod.Ts); + ini.SetValue("Pressure", "highpressurevalue", AllTime.HighPress.Val); + ini.SetValue("Pressure", "highpressuretime", AllTime.HighPress.Ts); + ini.SetValue("Pressure", "lowpressurevalue", AllTime.LowPress.Val); + ini.SetValue("Pressure", "lowpressuretime", AllTime.LowPress.Ts); + ini.SetValue("Humidity", "highhumidityvalue", AllTime.HighHumidity.Val); + ini.SetValue("Humidity", "highhumiditytime", AllTime.HighHumidity.Ts); + ini.SetValue("Humidity", "lowhumidityvalue", AllTime.LowHumidity.Val); + ini.SetValue("Humidity", "lowhumiditytime", AllTime.LowHumidity.Ts); + + ini.Flush(); + } + catch (Exception ex) + { + cumulus.LogMessage("Error writing alltime.ini file: " + ex.Message); + } + } + + public void ReadMonthlyAlltimeIniFile() + { + IniFile ini = new IniFile(cumulus.MonthlyAlltimeIniFile); + for (int month = 1; month <= 12; month++) + { + string monthstr = month.ToString("D2"); + + MonthlyRecs[month].HighTemp.Val = ini.GetValue("Temperature" + monthstr, "hightempvalue", -999.0); + MonthlyRecs[month].HighTemp.Ts = ini.GetValue("Temperature" + monthstr, "hightemptime", cumulus.defaultRecordTS); + + MonthlyRecs[month].LowTemp.Val = ini.GetValue("Temperature" + monthstr, "lowtempvalue", 999.0); + MonthlyRecs[month].LowTemp.Ts = ini.GetValue("Temperature" + monthstr, "lowtemptime", cumulus.defaultRecordTS); + + MonthlyRecs[month].LowChill.Val = ini.GetValue("Temperature" + monthstr, "lowchillvalue", 999.0); + MonthlyRecs[month].LowChill.Ts = ini.GetValue("Temperature" + monthstr, "lowchilltime", cumulus.defaultRecordTS); + + MonthlyRecs[month].HighMinTemp.Val = ini.GetValue("Temperature" + monthstr, "highmintempvalue", -999.0); + MonthlyRecs[month].HighMinTemp.Ts = ini.GetValue("Temperature" + monthstr, "highmintemptime", cumulus.defaultRecordTS); + + MonthlyRecs[month].LowMaxTemp.Val = ini.GetValue("Temperature" + monthstr, "lowmaxtempvalue", 999.0); + MonthlyRecs[month].LowMaxTemp.Ts = ini.GetValue("Temperature" + monthstr, "lowmaxtemptime", cumulus.defaultRecordTS); + + MonthlyRecs[month].HighAppTemp.Val = ini.GetValue("Temperature" + monthstr, "highapptempvalue", -999.0); + MonthlyRecs[month].HighAppTemp.Ts = ini.GetValue("Temperature" + monthstr, "highapptemptime", cumulus.defaultRecordTS); + + MonthlyRecs[month].LowAppTemp.Val = ini.GetValue("Temperature" + monthstr, "lowapptempvalue", 999.0); + MonthlyRecs[month].LowAppTemp.Ts = ini.GetValue("Temperature" + monthstr, "lowapptemptime", cumulus.defaultRecordTS); + + MonthlyRecs[month].HighFeelsLike.Val = ini.GetValue("Temperature" + monthstr, "highfeelslikevalue", -999.0); + MonthlyRecs[month].HighFeelsLike.Ts = ini.GetValue("Temperature" + monthstr, "highfeelsliketime", cumulus.defaultRecordTS); + + MonthlyRecs[month].LowFeelsLike.Val = ini.GetValue("Temperature" + monthstr, "lowfeelslikevalue", 999.0); + MonthlyRecs[month].LowFeelsLike.Ts = ini.GetValue("Temperature" + monthstr, "lowfeelsliketime", cumulus.defaultRecordTS); + + MonthlyRecs[month].HighHumidex.Val = ini.GetValue("Temperature" + monthstr, "highhumidexvalue", -999.0); + MonthlyRecs[month].HighHumidex.Ts = ini.GetValue("Temperature" + monthstr, "highhumidextime", cumulus.defaultRecordTS); + + MonthlyRecs[month].HighHeatIndex.Val = ini.GetValue("Temperature" + monthstr, "highheatindexvalue", -999.0); + MonthlyRecs[month].HighHeatIndex.Ts = ini.GetValue("Temperature" + monthstr, "highheatindextime", cumulus.defaultRecordTS); + + MonthlyRecs[month].HighDewPoint.Val = ini.GetValue("Temperature" + monthstr, "highdewpointvalue", -999.0); + MonthlyRecs[month].HighDewPoint.Ts = ini.GetValue("Temperature" + monthstr, "highdewpointtime", cumulus.defaultRecordTS); + + MonthlyRecs[month].LowDewPoint.Val = ini.GetValue("Temperature" + monthstr, "lowdewpointvalue", 999.0); + MonthlyRecs[month].LowDewPoint.Ts = ini.GetValue("Temperature" + monthstr, "lowdewpointtime", cumulus.defaultRecordTS); + + MonthlyRecs[month].HighDailyTempRange.Val = ini.GetValue("Temperature" + monthstr, "hightemprangevalue", 0.0); + MonthlyRecs[month].HighDailyTempRange.Ts = ini.GetValue("Temperature" + monthstr, "hightemprangetime", cumulus.defaultRecordTS); + + MonthlyRecs[month].LowDailyTempRange.Val = ini.GetValue("Temperature" + monthstr, "lowtemprangevalue", 999.0); + MonthlyRecs[month].LowDailyTempRange.Ts = ini.GetValue("Temperature" + monthstr, "lowtemprangetime", cumulus.defaultRecordTS); + + MonthlyRecs[month].HighWind.Val = ini.GetValue("Wind" + monthstr, "highwindvalue", 0.0); + MonthlyRecs[month].HighWind.Ts = ini.GetValue("Wind" + monthstr, "highwindtime", cumulus.defaultRecordTS); + + MonthlyRecs[month].HighGust.Val = ini.GetValue("Wind" + monthstr, "highgustvalue", 0.0); + MonthlyRecs[month].HighGust.Ts = ini.GetValue("Wind" + monthstr, "highgusttime", cumulus.defaultRecordTS); + + MonthlyRecs[month].HighWindRun.Val = ini.GetValue("Wind" + monthstr, "highdailywindrunvalue", 0.0); + MonthlyRecs[month].HighWindRun.Ts = ini.GetValue("Wind" + monthstr, "highdailywindruntime", cumulus.defaultRecordTS); + + MonthlyRecs[month].HighRainRate.Val = ini.GetValue("Rain" + monthstr, "highrainratevalue", 0.0); + MonthlyRecs[month].HighRainRate.Ts = ini.GetValue("Rain" + monthstr, "highrainratetime", cumulus.defaultRecordTS); + + MonthlyRecs[month].DailyRain.Val = ini.GetValue("Rain" + monthstr, "highdailyrainvalue", 0.0); + MonthlyRecs[month].DailyRain.Ts = ini.GetValue("Rain" + monthstr, "highdailyraintime", cumulus.defaultRecordTS); + + MonthlyRecs[month].HourlyRain.Val = ini.GetValue("Rain" + monthstr, "highhourlyrainvalue", 0.0); + MonthlyRecs[month].HourlyRain.Ts = ini.GetValue("Rain" + monthstr, "highhourlyraintime", cumulus.defaultRecordTS); + + MonthlyRecs[month].MonthlyRain.Val = ini.GetValue("Rain" + monthstr, "highmonthlyrainvalue", 0.0); + MonthlyRecs[month].MonthlyRain.Ts = ini.GetValue("Rain" + monthstr, "highmonthlyraintime", cumulus.defaultRecordTS); + + MonthlyRecs[month].LongestDryPeriod.Val = ini.GetValue("Rain" + monthstr, "longestdryperiodvalue", 0); + MonthlyRecs[month].LongestDryPeriod.Ts = ini.GetValue("Rain" + monthstr, "longestdryperiodtime", cumulus.defaultRecordTS); + + MonthlyRecs[month].LongestWetPeriod.Val = ini.GetValue("Rain" + monthstr, "longestwetperiodvalue", 0); + MonthlyRecs[month].LongestWetPeriod.Ts = ini.GetValue("Rain" + monthstr, "longestwetperiodtime", cumulus.defaultRecordTS); + + MonthlyRecs[month].HighPress.Val = ini.GetValue("Pressure" + monthstr, "highpressurevalue", 0.0); + MonthlyRecs[month].HighPress.Ts = ini.GetValue("Pressure" + monthstr, "highpressuretime", cumulus.defaultRecordTS); + + MonthlyRecs[month].LowPress.Val = ini.GetValue("Pressure" + monthstr, "lowpressurevalue", 9999.0); + MonthlyRecs[month].LowPress.Ts = ini.GetValue("Pressure" + monthstr, "lowpressuretime", cumulus.defaultRecordTS); + + MonthlyRecs[month].HighHumidity.Val = ini.GetValue("Humidity" + monthstr, "highhumidityvalue", 0.0); + MonthlyRecs[month].HighHumidity.Ts = ini.GetValue("Humidity" + monthstr, "highhumiditytime", cumulus.defaultRecordTS); + + MonthlyRecs[month].LowHumidity.Val = ini.GetValue("Humidity" + monthstr, "lowhumidityvalue", 999.0); + MonthlyRecs[month].LowHumidity.Ts = ini.GetValue("Humidity" + monthstr, "lowhumiditytime", cumulus.defaultRecordTS); + } + + cumulus.LogMessage("MonthlyAlltime.ini file read"); + } + + public void WriteMonthlyAlltimeIniFile() + { + try + { + IniFile ini = new IniFile(cumulus.MonthlyAlltimeIniFile); + for (int month = 1; month <= 12; month++) + { + string monthstr = month.ToString("D2"); + + ini.SetValue("Temperature" + monthstr, "hightempvalue", MonthlyRecs[month].HighTemp.Val); + ini.SetValue("Temperature" + monthstr, "hightemptime", MonthlyRecs[month].HighTemp.Ts); + ini.SetValue("Temperature" + monthstr, "lowtempvalue", MonthlyRecs[month].LowTemp.Val); + ini.SetValue("Temperature" + monthstr, "lowtemptime", MonthlyRecs[month].LowTemp.Ts); + ini.SetValue("Temperature" + monthstr, "lowchillvalue", MonthlyRecs[month].LowChill.Val); + ini.SetValue("Temperature" + monthstr, "lowchilltime", MonthlyRecs[month].LowChill.Ts); + ini.SetValue("Temperature" + monthstr, "highmintempvalue", MonthlyRecs[month].HighMinTemp.Val); + ini.SetValue("Temperature" + monthstr, "highmintemptime", MonthlyRecs[month].HighMinTemp.Ts); + ini.SetValue("Temperature" + monthstr, "lowmaxtempvalue", MonthlyRecs[month].LowMaxTemp.Val); + ini.SetValue("Temperature" + monthstr, "lowmaxtemptime", MonthlyRecs[month].LowMaxTemp.Ts); + ini.SetValue("Temperature" + monthstr, "highapptempvalue", MonthlyRecs[month].HighAppTemp.Val); + ini.SetValue("Temperature" + monthstr, "highapptemptime", MonthlyRecs[month].HighAppTemp.Ts); + ini.SetValue("Temperature" + monthstr, "lowapptempvalue", MonthlyRecs[month].LowAppTemp.Val); + ini.SetValue("Temperature" + monthstr, "lowapptemptime", MonthlyRecs[month].LowAppTemp.Ts); + ini.SetValue("Temperature" + monthstr, "highfeelslikevalue", MonthlyRecs[month].HighFeelsLike.Val); + ini.SetValue("Temperature" + monthstr, "highfeelsliketime", MonthlyRecs[month].HighFeelsLike.Ts); + ini.SetValue("Temperature" + monthstr, "lowfeelslikevalue", MonthlyRecs[month].LowFeelsLike.Val); + ini.SetValue("Temperature" + monthstr, "lowfeelsliketime", MonthlyRecs[month].LowFeelsLike.Ts); + ini.SetValue("Temperature" + monthstr, "highhumidexvalue", MonthlyRecs[month].HighHumidex.Val); + ini.SetValue("Temperature" + monthstr, "highhumidextime", MonthlyRecs[month].HighHumidex.Ts); + ini.SetValue("Temperature" + monthstr, "highheatindexvalue", MonthlyRecs[month].HighHeatIndex.Val); + ini.SetValue("Temperature" + monthstr, "highheatindextime", MonthlyRecs[month].HighHeatIndex.Ts); + ini.SetValue("Temperature" + monthstr, "highdewpointvalue", MonthlyRecs[month].HighDewPoint.Val); + ini.SetValue("Temperature" + monthstr, "highdewpointtime", MonthlyRecs[month].HighDewPoint.Ts); + ini.SetValue("Temperature" + monthstr, "lowdewpointvalue", MonthlyRecs[month].LowDewPoint.Val); + ini.SetValue("Temperature" + monthstr, "lowdewpointtime", MonthlyRecs[month].LowDewPoint.Ts); + ini.SetValue("Temperature" + monthstr, "hightemprangevalue", MonthlyRecs[month].HighDailyTempRange.Val); + ini.SetValue("Temperature" + monthstr, "hightemprangetime", MonthlyRecs[month].HighDailyTempRange.Ts); + ini.SetValue("Temperature" + monthstr, "lowtemprangevalue", MonthlyRecs[month].LowDailyTempRange.Val); + ini.SetValue("Temperature" + monthstr, "lowtemprangetime", MonthlyRecs[month].LowDailyTempRange.Ts); + ini.SetValue("Wind" + monthstr, "highwindvalue", MonthlyRecs[month].HighWind.Val); + ini.SetValue("Wind" + monthstr, "highwindtime", MonthlyRecs[month].HighWind.Ts); + ini.SetValue("Wind" + monthstr, "highgustvalue", MonthlyRecs[month].HighGust.Val); + ini.SetValue("Wind" + monthstr, "highgusttime", MonthlyRecs[month].HighGust.Ts); + ini.SetValue("Wind" + monthstr, "highdailywindrunvalue", MonthlyRecs[month].HighWindRun.Val); + ini.SetValue("Wind" + monthstr, "highdailywindruntime", MonthlyRecs[month].HighWindRun.Ts); + ini.SetValue("Rain" + monthstr, "highrainratevalue", MonthlyRecs[month].HighRainRate.Val); + ini.SetValue("Rain" + monthstr, "highrainratetime", MonthlyRecs[month].HighRainRate.Ts); + ini.SetValue("Rain" + monthstr, "highdailyrainvalue", MonthlyRecs[month].DailyRain.Val); + ini.SetValue("Rain" + monthstr, "highdailyraintime", MonthlyRecs[month].DailyRain.Ts); + ini.SetValue("Rain" + monthstr, "highhourlyrainvalue", MonthlyRecs[month].HourlyRain.Val); + ini.SetValue("Rain" + monthstr, "highhourlyraintime", MonthlyRecs[month].HourlyRain.Ts); + ini.SetValue("Rain" + monthstr, "highmonthlyrainvalue", MonthlyRecs[month].MonthlyRain.Val); + ini.SetValue("Rain" + monthstr, "highmonthlyraintime", MonthlyRecs[month].MonthlyRain.Ts); + ini.SetValue("Rain" + monthstr, "longestdryperiodvalue", MonthlyRecs[month].LongestDryPeriod.Val); + ini.SetValue("Rain" + monthstr, "longestdryperiodtime", MonthlyRecs[month].LongestDryPeriod.Ts); + ini.SetValue("Rain" + monthstr, "longestwetperiodvalue", MonthlyRecs[month].LongestWetPeriod.Val); + ini.SetValue("Rain" + monthstr, "longestwetperiodtime", MonthlyRecs[month].LongestWetPeriod.Ts); + ini.SetValue("Pressure" + monthstr, "highpressurevalue", MonthlyRecs[month].HighPress.Val); + ini.SetValue("Pressure" + monthstr, "highpressuretime", MonthlyRecs[month].HighPress.Ts); + ini.SetValue("Pressure" + monthstr, "lowpressurevalue", MonthlyRecs[month].LowPress.Val); + ini.SetValue("Pressure" + monthstr, "lowpressuretime", MonthlyRecs[month].LowPress.Ts); + ini.SetValue("Humidity" + monthstr, "highhumidityvalue", MonthlyRecs[month].HighHumidity.Val); + ini.SetValue("Humidity" + monthstr, "highhumiditytime", MonthlyRecs[month].HighHumidity.Ts); + ini.SetValue("Humidity" + monthstr, "lowhumidityvalue", MonthlyRecs[month].LowHumidity.Val); + ini.SetValue("Humidity" + monthstr, "lowhumiditytime", MonthlyRecs[month].LowHumidity.Ts); + } + ini.Flush(); + } + catch (Exception ex) + { + cumulus.LogMessage("Error writing MonthlyAlltime.ini file: " + ex.Message); + } + } + + public void SetDefaultMonthlyHighsAndLows() + { + // this Month highs and lows + ThisMonth.HighGust.Val = 0; + ThisMonth.HighWind.Val = 0; + ThisMonth.HighTemp.Val = -999; + ThisMonth.LowTemp.Val = 999; + ThisMonth.HighAppTemp.Val = -999; + ThisMonth.LowAppTemp.Val = 999; + ThisMonth.HighFeelsLike.Val = -999; + ThisMonth.LowFeelsLike.Val = 999; + ThisMonth.HighHumidex.Val = -999; + ThisMonth.HighDewPoint.Val = -999; + ThisMonth.LowDewPoint.Val = 999; + ThisMonth.HighPress.Val = 0; + ThisMonth.LowPress.Val = 9999; + ThisMonth.HighRainRate.Val = 0; + ThisMonth.HourlyRain.Val = 0; + ThisMonth.DailyRain.Val = 0; + ThisMonth.HighHumidity.Val = 0; + ThisMonth.LowHumidity.Val = 999; + ThisMonth.HighHeatIndex.Val = -999; + ThisMonth.LowChill.Val = 999; + ThisMonth.HighMinTemp.Val = -999; + ThisMonth.LowMaxTemp.Val = 999; + ThisMonth.HighWindRun.Val = 0; + ThisMonth.LowDailyTempRange.Val = 999; + ThisMonth.HighDailyTempRange.Val = -999; + + // this Month highs and lows - timestamps + ThisMonth.HighGust.Ts = cumulus.defaultRecordTS; + ThisMonth.HighWind.Ts = cumulus.defaultRecordTS; + ThisMonth.HighTemp.Ts = cumulus.defaultRecordTS; + ThisMonth.LowTemp.Ts = cumulus.defaultRecordTS; + ThisMonth.HighAppTemp.Ts = cumulus.defaultRecordTS; + ThisMonth.LowAppTemp.Ts = cumulus.defaultRecordTS; + ThisMonth.HighFeelsLike.Ts = cumulus.defaultRecordTS; + ThisMonth.LowFeelsLike.Ts = cumulus.defaultRecordTS; + ThisMonth.HighHumidex.Ts = cumulus.defaultRecordTS; + ThisMonth.HighDewPoint.Ts = cumulus.defaultRecordTS; + ThisMonth.LowDewPoint.Ts = cumulus.defaultRecordTS; + ThisMonth.HighPress.Ts = cumulus.defaultRecordTS; + ThisMonth.LowPress.Ts = cumulus.defaultRecordTS; + ThisMonth.HighRainRate.Ts = cumulus.defaultRecordTS; + ThisMonth.HourlyRain.Ts = cumulus.defaultRecordTS; + ThisMonth.DailyRain.Ts = cumulus.defaultRecordTS; + ThisMonth.HighHumidity.Ts = cumulus.defaultRecordTS; + ThisMonth.LowHumidity.Ts = cumulus.defaultRecordTS; + ThisMonth.HighHeatIndex.Ts = cumulus.defaultRecordTS; + ThisMonth.LowChill.Ts = cumulus.defaultRecordTS; + ThisMonth.HighMinTemp.Ts = cumulus.defaultRecordTS; + ThisMonth.LowMaxTemp.Ts = cumulus.defaultRecordTS; + ThisMonth.HighWindRun.Ts = cumulus.defaultRecordTS; + ThisMonth.LowDailyTempRange.Ts = cumulus.defaultRecordTS; + ThisMonth.HighDailyTempRange.Ts = cumulus.defaultRecordTS; + } + + public void ReadMonthIniFile() + { + //DateTime timestamp; + + SetDefaultMonthlyHighsAndLows(); + + if (File.Exists(cumulus.MonthIniFile)) + { + //int hourInc = cumulus.GetHourInc(); + + IniFile ini = new IniFile(cumulus.MonthIniFile); + + // Date + //timestamp = ini.GetValue("General", "Date", cumulus.defaultRecordTS); + + ThisMonth.HighWind.Val = ini.GetValue("Wind", "Speed", 0.0); + ThisMonth.HighWind.Ts = ini.GetValue("Wind", "SpTime", cumulus.defaultRecordTS); + ThisMonth.HighGust.Val = ini.GetValue("Wind", "Gust", 0.0); + ThisMonth.HighGust.Ts = ini.GetValue("Wind", "Time", cumulus.defaultRecordTS); + ThisMonth.HighWindRun.Val = ini.GetValue("Wind", "Windrun", 0.0); + ThisMonth.HighWindRun.Ts = ini.GetValue("Wind", "WindrunTime", cumulus.defaultRecordTS); + // Temperature + ThisMonth.LowTemp.Val = ini.GetValue("Temp", "Low", 999.0); + ThisMonth.LowTemp.Ts = ini.GetValue("Temp", "LTime", cumulus.defaultRecordTS); + ThisMonth.HighTemp.Val = ini.GetValue("Temp", "High", -999.0); + ThisMonth.HighTemp.Ts = ini.GetValue("Temp", "HTime", cumulus.defaultRecordTS); + ThisMonth.LowMaxTemp.Val = ini.GetValue("Temp", "LowMax", 999.0); + ThisMonth.LowMaxTemp.Ts = ini.GetValue("Temp", "LMTime", cumulus.defaultRecordTS); + ThisMonth.HighMinTemp.Val = ini.GetValue("Temp", "HighMin", -999.0); + ThisMonth.HighMinTemp.Ts = ini.GetValue("Temp", "HMTime", cumulus.defaultRecordTS); + ThisMonth.LowDailyTempRange.Val = ini.GetValue("Temp", "LowRange", 999.0); + ThisMonth.LowDailyTempRange.Ts = ini.GetValue("Temp", "LowRangeTime", cumulus.defaultRecordTS); + ThisMonth.HighDailyTempRange.Val = ini.GetValue("Temp", "HighRange", -999.0); + ThisMonth.HighDailyTempRange.Ts = ini.GetValue("Temp", "HighRangeTime", cumulus.defaultRecordTS); + // Pressure + ThisMonth.LowPress.Val = ini.GetValue("Pressure", "Low", 9999.0); + ThisMonth.LowPress.Ts = ini.GetValue("Pressure", "LTime", cumulus.defaultRecordTS); + ThisMonth.HighPress.Val = ini.GetValue("Pressure", "High", -9999.0); + ThisMonth.HighPress.Ts = ini.GetValue("Pressure", "HTime", cumulus.defaultRecordTS); + // rain rate + ThisMonth.HighRainRate.Val = ini.GetValue("Rain", "High", 0.0); + ThisMonth.HighRainRate.Ts = ini.GetValue("Rain", "HTime", cumulus.defaultRecordTS); + ThisMonth.HourlyRain.Val = ini.GetValue("Rain", "HourlyHigh", 0.0); + ThisMonth.HourlyRain.Ts = ini.GetValue("Rain", "HHourlyTime", cumulus.defaultRecordTS); + ThisMonth.DailyRain.Val = ini.GetValue("Rain", "DailyHigh", 0.0); + ThisMonth.DailyRain.Ts = ini.GetValue("Rain", "HDailyTime", cumulus.defaultRecordTS); + ThisMonth.LongestDryPeriod.Val = ini.GetValue("Rain", "LongestDryPeriod", 0); + ThisMonth.LongestDryPeriod.Ts = ini.GetValue("Rain", "LongestDryPeriodTime", cumulus.defaultRecordTS); + ThisMonth.LongestWetPeriod.Val = ini.GetValue("Rain", "LongestWetPeriod", 0); + ThisMonth.LongestWetPeriod.Ts = ini.GetValue("Rain", "LongestWetPeriodTime", cumulus.defaultRecordTS); + // humidity + ThisMonth.LowHumidity.Val = ini.GetValue("Humidity", "Low", 999); + ThisMonth.LowHumidity.Ts = ini.GetValue("Humidity", "LTime", cumulus.defaultRecordTS); + ThisMonth.HighHumidity.Val = ini.GetValue("Humidity", "High", -999); + ThisMonth.HighHumidity.Ts = ini.GetValue("Humidity", "HTime", cumulus.defaultRecordTS); + // heat index + ThisMonth.HighHeatIndex.Val = ini.GetValue("HeatIndex", "High", -999.0); + ThisMonth.HighHeatIndex.Ts = ini.GetValue("HeatIndex", "HTime", cumulus.defaultRecordTS); + // App temp + ThisMonth.LowAppTemp.Val = ini.GetValue("AppTemp", "Low", 999.0); + ThisMonth.LowAppTemp.Ts = ini.GetValue("AppTemp", "LTime", cumulus.defaultRecordTS); + ThisMonth.HighAppTemp.Val = ini.GetValue("AppTemp", "High", -999.0); + ThisMonth.HighAppTemp.Ts = ini.GetValue("AppTemp", "HTime", cumulus.defaultRecordTS); + // Dewpoint + ThisMonth.LowDewPoint.Val = ini.GetValue("Dewpoint", "Low", 999.0); + ThisMonth.LowDewPoint.Ts = ini.GetValue("Dewpoint", "LTime", cumulus.defaultRecordTS); + ThisMonth.HighDewPoint.Val = ini.GetValue("Dewpoint", "High", -999.0); + ThisMonth.HighDewPoint.Ts = ini.GetValue("Dewpoint", "HTime", cumulus.defaultRecordTS); + // wind chill + ThisMonth.LowChill.Val = ini.GetValue("WindChill", "Low", 999.0); + ThisMonth.LowChill.Ts = ini.GetValue("WindChill", "LTime", cumulus.defaultRecordTS); + // Feels like temp + ThisMonth.LowFeelsLike.Val = ini.GetValue("FeelsLike", "Low", 999.0); + ThisMonth.LowFeelsLike.Ts = ini.GetValue("FeelsLike", "LTime", cumulus.defaultRecordTS); + ThisMonth.HighFeelsLike.Val = ini.GetValue("FeelsLike", "High", -999.0); + ThisMonth.HighFeelsLike.Ts = ini.GetValue("FeelsLike", "HTime", cumulus.defaultRecordTS); + // Humidex + ThisMonth.HighHumidex.Val = ini.GetValue("Humidex", "High", -999.0); + ThisMonth.HighHumidex.Ts = ini.GetValue("Humidex", "HTime", cumulus.defaultRecordTS); + + cumulus.LogMessage("Month.ini file read"); + } + } + + public void WriteMonthIniFile() + { + cumulus.LogDebugMessage("Writing to Month.ini file"); + lock (monthIniThreadLock) + { + try + { + int hourInc = cumulus.GetHourInc(); + + IniFile ini = new IniFile(cumulus.MonthIniFile); + // Date + ini.SetValue("General", "Date", DateTime.Now.AddHours(hourInc)); + // Wind + ini.SetValue("Wind", "Speed", ThisMonth.HighWind.Val); + ini.SetValue("Wind", "SpTime", ThisMonth.HighWind.Ts); + ini.SetValue("Wind", "Gust", ThisMonth.HighGust.Val); + ini.SetValue("Wind", "Time", ThisMonth.HighGust.Ts); + ini.SetValue("Wind", "Windrun", ThisMonth.HighWindRun.Val); + ini.SetValue("Wind", "WindrunTime", ThisMonth.HighWindRun.Ts); + // Temperature + ini.SetValue("Temp", "Low", ThisMonth.LowTemp.Val); + ini.SetValue("Temp", "LTime", ThisMonth.LowTemp.Ts); + ini.SetValue("Temp", "High", ThisMonth.HighTemp.Val); + ini.SetValue("Temp", "HTime", ThisMonth.HighTemp.Ts); + ini.SetValue("Temp", "LowMax", ThisMonth.LowMaxTemp.Val); + ini.SetValue("Temp", "LMTime", ThisMonth.LowMaxTemp.Ts); + ini.SetValue("Temp", "HighMin", ThisMonth.HighMinTemp.Val); + ini.SetValue("Temp", "HMTime", ThisMonth.HighMinTemp.Ts); + ini.SetValue("Temp", "LowRange", ThisMonth.LowDailyTempRange.Val); + ini.SetValue("Temp", "LowRangeTime", ThisMonth.LowDailyTempRange.Ts); + ini.SetValue("Temp", "HighRange", ThisMonth.HighDailyTempRange.Val); + ini.SetValue("Temp", "HighRangeTime", ThisMonth.HighDailyTempRange.Ts); + // Pressure + ini.SetValue("Pressure", "Low", ThisMonth.LowPress.Val); + ini.SetValue("Pressure", "LTime", ThisMonth.LowPress.Ts); + ini.SetValue("Pressure", "High", ThisMonth.HighPress.Val); + ini.SetValue("Pressure", "HTime", ThisMonth.HighPress.Ts); + // rain + ini.SetValue("Rain", "High", ThisMonth.HighRainRate.Val); + ini.SetValue("Rain", "HTime", ThisMonth.HighRainRate.Ts); + ini.SetValue("Rain", "HourlyHigh", ThisMonth.HourlyRain.Val); + ini.SetValue("Rain", "HHourlyTime", ThisMonth.HourlyRain.Ts); + ini.SetValue("Rain", "DailyHigh", ThisMonth.DailyRain.Val); + ini.SetValue("Rain", "HDailyTime", ThisMonth.DailyRain.Ts); + ini.SetValue("Rain", "LongestDryPeriod", ThisMonth.LongestDryPeriod.Val); + ini.SetValue("Rain", "LongestDryPeriodTime", ThisMonth.LongestDryPeriod.Ts); + ini.SetValue("Rain", "LongestWetPeriod", ThisMonth.LongestWetPeriod.Val); + ini.SetValue("Rain", "LongestWetPeriodTime", ThisMonth.LongestWetPeriod.Ts); + // humidity + ini.SetValue("Humidity", "Low", ThisMonth.LowHumidity.Val); + ini.SetValue("Humidity", "LTime", ThisMonth.LowHumidity.Ts); + ini.SetValue("Humidity", "High", ThisMonth.HighHumidity.Val); + ini.SetValue("Humidity", "HTime", ThisMonth.HighHumidity.Ts); + // heat index + ini.SetValue("HeatIndex", "High", ThisMonth.HighHeatIndex.Val); + ini.SetValue("HeatIndex", "HTime", ThisMonth.HighHeatIndex.Ts); + // App temp + ini.SetValue("AppTemp", "Low", ThisMonth.LowAppTemp.Val); + ini.SetValue("AppTemp", "LTime", ThisMonth.LowAppTemp.Ts); + ini.SetValue("AppTemp", "High", ThisMonth.HighAppTemp.Val); + ini.SetValue("AppTemp", "HTime", ThisMonth.HighAppTemp.Ts); + // Dewpoint + ini.SetValue("Dewpoint", "Low", ThisMonth.LowDewPoint.Val); + ini.SetValue("Dewpoint", "LTime", ThisMonth.LowDewPoint.Ts); + ini.SetValue("Dewpoint", "High", ThisMonth.HighDewPoint.Val); + ini.SetValue("Dewpoint", "HTime", ThisMonth.HighDewPoint.Ts); + // wind chill + ini.SetValue("WindChill", "Low", ThisMonth.LowChill.Val); + ini.SetValue("WindChill", "LTime", ThisMonth.LowChill.Ts); + // feels like + ini.SetValue("FeelsLike", "Low", ThisMonth.LowFeelsLike.Val); + ini.SetValue("FeelsLike", "LTime", ThisMonth.LowFeelsLike.Ts); + ini.SetValue("FeelsLike", "High", ThisMonth.HighFeelsLike.Val); + ini.SetValue("FeelsLike", "HTime", ThisMonth.HighFeelsLike.Ts); + // Humidex + ini.SetValue("Humidex", "High", ThisMonth.HighHumidex.Val); + ini.SetValue("Humidex", "HTime", ThisMonth.HighHumidex.Ts); + + ini.Flush(); + } + catch (Exception ex) + { + cumulus.LogMessage("Error writing month.ini file: " + ex.Message); + } + } + cumulus.LogDebugMessage("End writing to Month.ini file"); + } + + public void ReadYearIniFile() + { + //DateTime timestamp; + + SetDefaultYearlyHighsAndLows(); + + if (File.Exists(cumulus.YearIniFile)) + { + //int hourInc = cumulus.GetHourInc(); + + IniFile ini = new IniFile(cumulus.YearIniFile); + + // Date + //timestamp = ini.GetValue("General", "Date", cumulus.defaultRecordTS); + + ThisYear.HighWind.Val = ini.GetValue("Wind", "Speed", 0.0); + ThisYear.HighWind.Ts = ini.GetValue("Wind", "SpTime", cumulus.defaultRecordTS); + ThisYear.HighGust.Val = ini.GetValue("Wind", "Gust", 0.0); + ThisYear.HighGust.Ts = ini.GetValue("Wind", "Time", cumulus.defaultRecordTS); + ThisYear.HighWindRun.Val = ini.GetValue("Wind", "Windrun", 0.0); + ThisYear.HighWindRun.Ts = ini.GetValue("Wind", "WindrunTime", cumulus.defaultRecordTS); + // Temperature + ThisYear.LowTemp.Val = ini.GetValue("Temp", "Low", 999.0); + ThisYear.LowTemp.Ts = ini.GetValue("Temp", "LTime", cumulus.defaultRecordTS); + ThisYear.HighTemp.Val = ini.GetValue("Temp", "High", -999.0); + ThisYear.HighTemp.Ts = ini.GetValue("Temp", "HTime", cumulus.defaultRecordTS); + ThisYear.LowMaxTemp.Val = ini.GetValue("Temp", "LowMax", 999.0); + ThisYear.LowMaxTemp.Ts = ini.GetValue("Temp", "LMTime", cumulus.defaultRecordTS); + ThisYear.HighMinTemp.Val = ini.GetValue("Temp", "HighMin", -999.0); + ThisYear.HighMinTemp.Ts = ini.GetValue("Temp", "HMTime", cumulus.defaultRecordTS); + ThisYear.LowDailyTempRange.Val = ini.GetValue("Temp", "LowRange", 999.0); + ThisYear.LowDailyTempRange.Ts = ini.GetValue("Temp", "LowRangeTime", cumulus.defaultRecordTS); + ThisYear.HighDailyTempRange.Val = ini.GetValue("Temp", "HighRange", -999.0); + ThisYear.HighDailyTempRange.Ts = ini.GetValue("Temp", "HighRangeTime", cumulus.defaultRecordTS); + // Pressure + ThisYear.LowPress.Val = ini.GetValue("Pressure", "Low", 9999.0); + ThisYear.LowPress.Ts = ini.GetValue("Pressure", "LTime", cumulus.defaultRecordTS); + ThisYear.HighPress.Val = ini.GetValue("Pressure", "High", -9999.0); + ThisYear.HighPress.Ts = ini.GetValue("Pressure", "HTime", cumulus.defaultRecordTS); + // rain rate + ThisYear.HighRainRate.Val = ini.GetValue("Rain", "High", 0.0); + ThisYear.HighRainRate.Ts = ini.GetValue("Rain", "HTime", cumulus.defaultRecordTS); + ThisYear.HourlyRain.Val = ini.GetValue("Rain", "HourlyHigh", 0.0); + ThisYear.HourlyRain.Ts = ini.GetValue("Rain", "HHourlyTime", cumulus.defaultRecordTS); + ThisYear.DailyRain.Val = ini.GetValue("Rain", "DailyHigh", 0.0); + ThisYear.DailyRain.Ts = ini.GetValue("Rain", "HDailyTime", cumulus.defaultRecordTS); + ThisYear.MonthlyRain.Val = ini.GetValue("Rain", "MonthlyHigh", 0.0); + ThisYear.MonthlyRain.Ts = ini.GetValue("Rain", "HMonthlyTime", cumulus.defaultRecordTS); + ThisYear.LongestDryPeriod.Val = ini.GetValue("Rain", "LongestDryPeriod", 0); + ThisYear.LongestDryPeriod.Ts = ini.GetValue("Rain", "LongestDryPeriodTime", cumulus.defaultRecordTS); + ThisYear.LongestWetPeriod.Val = ini.GetValue("Rain", "LongestWetPeriod", 0); + ThisYear.LongestWetPeriod.Ts = ini.GetValue("Rain", "LongestWetPeriodTime", cumulus.defaultRecordTS); + // humidity + ThisYear.LowHumidity.Val = ini.GetValue("Humidity", "Low", 999); + ThisYear.LowHumidity.Ts = ini.GetValue("Humidity", "LTime", cumulus.defaultRecordTS); + ThisYear.HighHumidity.Val = ini.GetValue("Humidity", "High", -999); + ThisYear.HighHumidity.Ts = ini.GetValue("Humidity", "HTime", cumulus.defaultRecordTS); + // heat index + ThisYear.HighHeatIndex.Val = ini.GetValue("HeatIndex", "High", -999.0); + ThisYear.HighHeatIndex.Ts = ini.GetValue("HeatIndex", "HTime", cumulus.defaultRecordTS); + // App temp + ThisYear.LowAppTemp.Val = ini.GetValue("AppTemp", "Low", 999.0); + ThisYear.LowAppTemp.Ts = ini.GetValue("AppTemp", "LTime", cumulus.defaultRecordTS); + ThisYear.HighAppTemp.Val = ini.GetValue("AppTemp", "High", -999.0); + ThisYear.HighAppTemp.Ts = ini.GetValue("AppTemp", "HTime", cumulus.defaultRecordTS); + // Dewpoint + ThisYear.LowDewPoint.Val = ini.GetValue("Dewpoint", "Low", 999.0); + ThisYear.LowDewPoint.Ts = ini.GetValue("Dewpoint", "LTime", cumulus.defaultRecordTS); + ThisYear.HighDewPoint.Val = ini.GetValue("Dewpoint", "High", -999.0); + ThisYear.HighDewPoint.Ts = ini.GetValue("Dewpoint", "HTime", cumulus.defaultRecordTS); + // wind chill + ThisYear.LowChill.Val = ini.GetValue("WindChill", "Low", 999.0); + ThisYear.LowChill.Ts = ini.GetValue("WindChill", "LTime", cumulus.defaultRecordTS); + // Feels like + ThisYear.LowFeelsLike.Val = ini.GetValue("FeelsLike", "Low", 999.0); + ThisYear.LowFeelsLike.Ts = ini.GetValue("FeelsLike", "LTime", cumulus.defaultRecordTS); + ThisYear.HighFeelsLike.Val = ini.GetValue("FeelsLike", "High", -999.0); + ThisYear.HighFeelsLike.Ts = ini.GetValue("FeelsLike", "HTime", cumulus.defaultRecordTS); + // Humidex + ThisYear.HighHumidex.Val = ini.GetValue("Humidex", "High", -999.0); + ThisYear.HighHumidex.Ts = ini.GetValue("Humidex", "HTime", cumulus.defaultRecordTS); + + cumulus.LogMessage("Year.ini file read"); + } + } + + public void WriteYearIniFile() + { + lock (yearIniThreadLock) + { + try + { + int hourInc = cumulus.GetHourInc(); + + IniFile ini = new IniFile(cumulus.YearIniFile); + // Date + ini.SetValue("General", "Date", DateTime.Now.AddHours(hourInc)); + // Wind + ini.SetValue("Wind", "Speed", ThisYear.HighWind.Val); + ini.SetValue("Wind", "SpTime", ThisYear.HighWind.Ts); + ini.SetValue("Wind", "Gust", ThisYear.HighGust.Val); + ini.SetValue("Wind", "Time", ThisYear.HighGust.Ts); + ini.SetValue("Wind", "Windrun", ThisYear.HighWindRun.Val); + ini.SetValue("Wind", "WindrunTime", ThisYear.HighWindRun.Ts); + // Temperature + ini.SetValue("Temp", "Low", ThisYear.LowTemp.Val); + ini.SetValue("Temp", "LTime", ThisYear.LowTemp.Ts); + ini.SetValue("Temp", "High", ThisYear.HighTemp.Val); + ini.SetValue("Temp", "HTime", ThisYear.HighTemp.Ts); + ini.SetValue("Temp", "LowMax", ThisYear.LowMaxTemp.Val); + ini.SetValue("Temp", "LMTime", ThisYear.LowMaxTemp.Ts); + ini.SetValue("Temp", "HighMin", ThisYear.HighMinTemp.Val); + ini.SetValue("Temp", "HMTime", ThisYear.HighMinTemp.Ts); + ini.SetValue("Temp", "LowRange", ThisYear.LowDailyTempRange.Val); + ini.SetValue("Temp", "LowRangeTime", ThisYear.LowDailyTempRange.Ts); + ini.SetValue("Temp", "HighRange", ThisYear.HighDailyTempRange.Val); + ini.SetValue("Temp", "HighRangeTime", ThisYear.HighDailyTempRange.Ts); + // Pressure + ini.SetValue("Pressure", "Low", ThisYear.LowPress.Val); + ini.SetValue("Pressure", "LTime", ThisYear.LowPress.Ts); + ini.SetValue("Pressure", "High", ThisYear.HighPress.Val); + ini.SetValue("Pressure", "HTime", ThisYear.HighPress.Ts); + // rain + ini.SetValue("Rain", "High", ThisYear.HighRainRate.Val); + ini.SetValue("Rain", "HTime", ThisYear.HighRainRate.Ts); + ini.SetValue("Rain", "HourlyHigh", ThisYear.HourlyRain.Val); + ini.SetValue("Rain", "HHourlyTime", ThisYear.HourlyRain.Ts); + ini.SetValue("Rain", "DailyHigh", ThisYear.DailyRain.Val); + ini.SetValue("Rain", "HDailyTime", ThisYear.DailyRain.Ts); + ini.SetValue("Rain", "MonthlyHigh", ThisYear.MonthlyRain.Val); + ini.SetValue("Rain", "HMonthlyTime", ThisYear.MonthlyRain.Ts); + ini.SetValue("Rain", "LongestDryPeriod", ThisYear.LongestDryPeriod.Val); + ini.SetValue("Rain", "LongestDryPeriodTime", ThisYear.LongestDryPeriod.Ts); + ini.SetValue("Rain", "LongestWetPeriod", ThisYear.LongestWetPeriod.Val); + ini.SetValue("Rain", "LongestWetPeriodTime", ThisYear.LongestWetPeriod.Ts); + // humidity + ini.SetValue("Humidity", "Low", ThisYear.LowHumidity.Val); + ini.SetValue("Humidity", "LTime", ThisYear.LowHumidity.Ts); + ini.SetValue("Humidity", "High", ThisYear.HighHumidity.Val); + ini.SetValue("Humidity", "HTime", ThisYear.HighHumidity.Ts); + // heat index + ini.SetValue("HeatIndex", "High", ThisYear.HighHeatIndex.Val); + ini.SetValue("HeatIndex", "HTime", ThisYear.HighHeatIndex.Ts); + // App temp + ini.SetValue("AppTemp", "Low", ThisYear.LowAppTemp.Val); + ini.SetValue("AppTemp", "LTime", ThisYear.LowAppTemp.Ts); + ini.SetValue("AppTemp", "High", ThisYear.HighAppTemp.Val); + ini.SetValue("AppTemp", "HTime", ThisYear.HighAppTemp.Ts); + // Dewpoint + ini.SetValue("Dewpoint", "Low", ThisYear.LowDewPoint.Val); + ini.SetValue("Dewpoint", "LTime", ThisYear.LowDewPoint.Ts); + ini.SetValue("Dewpoint", "High", ThisYear.HighDewPoint.Val); + ini.SetValue("Dewpoint", "HTime", ThisYear.HighDewPoint.Ts); + // wind chill + ini.SetValue("WindChill", "Low", ThisYear.LowChill.Val); + ini.SetValue("WindChill", "LTime", ThisYear.LowChill.Ts); + // Feels like + ini.SetValue("FeelsLike", "Low", ThisYear.LowFeelsLike.Val); + ini.SetValue("FeelsLike", "LTime", ThisYear.LowFeelsLike.Ts); + ini.SetValue("FeelsLike", "High", ThisYear.HighFeelsLike.Val); + ini.SetValue("FeelsLike", "HTime", ThisYear.HighFeelsLike.Ts); + // Humidex + ini.SetValue("Humidex", "High", ThisYear.HighHumidex.Val); + ini.SetValue("Humidex", "HTime", ThisYear.HighHumidex.Ts); + + ini.Flush(); + } + catch (Exception ex) + { + cumulus.LogMessage("Error writing year.ini file: " + ex.Message); + } + } + } + + public void SetDefaultYearlyHighsAndLows() + { + // this Year highs and lows + ThisYear.HighGust.Val = 0; + ThisYear.HighWind.Val = 0; + ThisYear.HighTemp.Val = -999; + ThisYear.LowTemp.Val = 999; + ThisYear.HighAppTemp.Val = -999; + ThisYear.LowAppTemp.Val = 999; + ThisYear.HighFeelsLike.Val = -999; + ThisYear.LowFeelsLike.Val = 999; + ThisYear.HighHumidex.Val = -999; + ThisYear.HighDewPoint.Val = -999; + ThisYear.LowDewPoint.Val = 999; + ThisYear.HighPress.Val = 0; + ThisYear.LowPress.Val = 9999; + ThisYear.HighRainRate.Val = 0; + ThisYear.HourlyRain.Val = 0; + ThisYear.DailyRain.Val = 0; + ThisYear.MonthlyRain.Val = 0; + ThisYear.HighHumidity.Val = 0; + ThisYear.LowHumidity.Val = 999; + ThisYear.HighHeatIndex.Val = -999; + ThisYear.LowChill.Val = 999; + ThisYear.HighMinTemp.Val = -999; + ThisYear.LowMaxTemp.Val = 999; + ThisYear.HighWindRun.Val = 0; + ThisYear.LowDailyTempRange.Val = 999; + ThisYear.HighDailyTempRange.Val = -999; + + // this Year highs and lows - timestamps + ThisYear.HighGust.Ts = cumulus.defaultRecordTS; + ThisYear.HighWind.Ts = cumulus.defaultRecordTS; + ThisYear.HighTemp.Ts = cumulus.defaultRecordTS; + ThisYear.LowTemp.Ts = cumulus.defaultRecordTS; + ThisYear.HighAppTemp.Ts = cumulus.defaultRecordTS; + ThisYear.LowAppTemp.Ts = cumulus.defaultRecordTS; + ThisYear.HighFeelsLike.Ts = cumulus.defaultRecordTS; + ThisYear.LowFeelsLike.Ts = cumulus.defaultRecordTS; + ThisYear.HighHumidex.Ts = cumulus.defaultRecordTS; + ThisYear.HighDewPoint.Ts = cumulus.defaultRecordTS; + ThisYear.LowDewPoint.Ts = cumulus.defaultRecordTS; + ThisYear.HighPress.Ts = cumulus.defaultRecordTS; + ThisYear.LowPress.Ts = cumulus.defaultRecordTS; + ThisYear.HighRainRate.Ts = cumulus.defaultRecordTS; + ThisYear.HourlyRain.Ts = cumulus.defaultRecordTS; + ThisYear.DailyRain.Ts = cumulus.defaultRecordTS; + ThisYear.MonthlyRain.Ts = cumulus.defaultRecordTS; + ThisYear.HighHumidity.Ts = cumulus.defaultRecordTS; + ThisYear.LowHumidity.Ts = cumulus.defaultRecordTS; + ThisYear.HighHeatIndex.Ts = cumulus.defaultRecordTS; + ThisYear.LowChill.Ts = cumulus.defaultRecordTS; + ThisYear.HighMinTemp.Ts = cumulus.defaultRecordTS; + ThisYear.LowMaxTemp.Ts = cumulus.defaultRecordTS; + ThisYear.DailyRain.Ts = cumulus.defaultRecordTS; + ThisYear.LowDailyTempRange.Ts = cumulus.defaultRecordTS; + ThisYear.HighDailyTempRange.Ts = cumulus.defaultRecordTS; + } + + public string GetWCloudURL(out string pwstring, DateTime timestamp) + { + pwstring = cumulus.WCloud.PW; + StringBuilder sb = new StringBuilder($"https://api.weathercloud.net/v01/set?wid={cumulus.WCloud.ID}&key={pwstring}"); + + //Temperature + sb.Append("&tempin=" + (int)Math.Round(ConvertUserTempToC(IndoorTemperature) * 10)); + sb.Append("&temp=" + (int)Math.Round(ConvertUserTempToC(OutdoorTemperature) * 10)); + sb.Append("&chill=" + (int)Math.Round(ConvertUserTempToC(WindChill) * 10)); + sb.Append("&dew=" + (int)Math.Round(ConvertUserTempToC(OutdoorDewpoint) * 10)); + sb.Append("&heat=" + (int)Math.Round(ConvertUserTempToC(HeatIndex) * 10)); + + // Humidity + sb.Append("&humin=" + IndoorHumidity); + sb.Append("&hum=" + OutdoorHumidity); + + // Wind + sb.Append("&wspd=" + (int)Math.Round(ConvertUserWindToMS(WindLatest) * 10)); + sb.Append("&wspdhi=" + (int)Math.Round(ConvertUserWindToMS(RecentMaxGust) * 10)); + sb.Append("&wspdavg=" + (int)Math.Round(ConvertUserWindToMS(WindAverage) * 10)); + + // Wind Direction + sb.Append("&wdir=" + Bearing); + sb.Append("&wdiravg=" + AvgBearing); + + // Pressure + sb.Append("&bar=" + (int)Math.Round(ConvertUserPressToMB(Pressure) * 10)); + + // rain + sb.Append("&rain=" + (int)Math.Round(ConvertUserRainToMM(RainToday) * 10)); + sb.Append("&rainrate=" + (int)Math.Round(ConvertUserRainToMM(RainRate) * 10)); + + // ET + if (cumulus.WCloud.SendSolar && cumulus.Manufacturer == cumulus.DAVIS) + { + sb.Append("&et=" + (int)Math.Round(ConvertUserRainToMM(ET) * 10)); + } + + // solar + if (cumulus.WCloud.SendSolar) + { + sb.Append("&solarrad=" + (int)Math.Round(SolarRad * 10)); + } + + // uv + if (cumulus.WCloud.SendUV) + { + sb.Append("&uvi=" + (int)Math.Round(UV * 10)); + } + + // aq + if (cumulus.WCloud.SendAirQuality) + { + switch (cumulus.StationOptions.PrimaryAqSensor) + { + case (int)Cumulus.PrimaryAqSensor.AirLinkOutdoor: + if (cumulus.airLinkDataOut != null) + { + sb.Append($"&pm25={cumulus.airLinkDataOut.pm2p5:F0}"); + sb.Append($"&pm10={cumulus.airLinkDataOut.pm10:F0}"); + sb.Append($"&aqi={AirQualityIndices.US_EPApm2p5(cumulus.airLinkDataOut.pm2p5_24hr)}"); + } + break; + case (int)Cumulus.PrimaryAqSensor.Ecowitt1: + sb.Append($"&pm25={AirQuality1:F0}"); + sb.Append($"&aqi={AirQualityIndices.US_EPApm2p5(AirQualityAvg1)}"); + break; + case (int)Cumulus.PrimaryAqSensor.Ecowitt2: + sb.Append($"&pm25={AirQuality2:F0}"); + sb.Append($"&aqi={AirQualityIndices.US_EPApm2p5(AirQualityAvg2)}"); + break; + case (int)Cumulus.PrimaryAqSensor.Ecowitt3: + sb.Append($"&pm25={AirQuality3:F0}"); + sb.Append($"&aqi={AirQualityIndices.US_EPApm2p5(AirQualityAvg3)}"); + break; + case (int)Cumulus.PrimaryAqSensor.Ecowitt4: + sb.Append($"&pm25={AirQuality4:F0}"); + sb.Append($"&aqi={AirQualityIndices.US_EPApm2p5(AirQualityAvg4)}"); + break; + case (int)Cumulus.PrimaryAqSensor.EcowittCO2: + sb.Append($"&pm25={CO2_pm2p5:F0}"); + sb.Append($"&pm10={CO2_pm10:F0}"); + sb.Append($"&aqi={AirQualityIndices.US_EPApm2p5(CO2_pm2p5_24h)}"); + break; + } + } + + // soil moisture + if (cumulus.WCloud.SendSoilMoisture) + { + // Weathercloud wants soil moisture in centibar. Davis supplies this, but Ecowitt provide a percentage + int moist = 0; + + switch (cumulus.WCloud.SoilMoistureSensor) + { + case 1: + moist = SoilMoisture1; + break; + case 2: + moist = SoilMoisture2; + break; + case 3: + moist = SoilMoisture3; + break; + case 4: + moist = SoilMoisture4; + break; + case 5: + moist = SoilMoisture5; + break; + case 6: + moist = SoilMoisture6; + break; + case 7: + moist = SoilMoisture7; + break; + case 8: + moist = SoilMoisture8; + break; + case 9: + moist = SoilMoisture9; + break; + case 10: + moist = SoilMoisture10; + break; + case 11: + moist = SoilMoisture11; + break; + case 12: + moist = SoilMoisture12; + break; + case 13: + moist = SoilMoisture13; + break; + case 14: + moist = SoilMoisture14; + break; + case 15: + moist = SoilMoisture15; + break; + case 16: + moist = SoilMoisture16; + break; + } + + if (cumulus.Manufacturer == cumulus.EW) + { + // very! approximate conversion from percentage to cb + moist = (100 - SoilMoisture1) * 2; + } + + sb.Append($"&soilmoist={moist}"); + } + + // leaf wetness + if (cumulus.WCloud.SendLeafWetness) + { + // Weathercloud wants soil moisture in centibar. Davis supplies this, but Ecowitt provide a percentage + int wet = 0; + + switch (cumulus.WCloud.LeafWetnessSensor) + { + case 1: + wet = LeafWetness1; + break; + case 2: + wet = LeafWetness2; + break; + case 3: + wet = LeafWetness3; + break; + case 4: + wet = LeafWetness4; + break; + case 5: + wet = LeafWetness5; + break; + case 6: + wet = LeafWetness6; + break; + case 7: + wet = LeafWetness7; + break; + case 8: + wet = LeafWetness8; + break; + } + + sb.Append($"&leafwet={wet}"); + } + + // time - UTC + sb.Append("&time=" + timestamp.ToUniversalTime().ToString("HHmm")); + + // date - UTC + sb.Append("&date=" + timestamp.ToUniversalTime().ToString("yyyyMMdd")); + + // software identification + //sb.Append("&type=291&ver=" + cumulus.Version); + sb.Append($"&software=Cumulus_MX_v{cumulus.Version}&softwareid=142787ebe716"); + + return sb.ToString(); + } + + public string GetAwekasURLv4(out string pwstring, DateTime timestamp) + { + var InvC = new CultureInfo(""); + string sep = ";"; + + int presstrend; + + // password is passed as a MD5 hash - not very secure, but better than plain text I guess + pwstring = Utils.GetMd5String(cumulus.AWEKAS.PW); + + double threeHourlyPressureChangeMb = 0; + + switch (cumulus.Units.Press) + { + case 0: + case 1: + threeHourlyPressureChangeMb = presstrendval * 3; + break; + case 2: + threeHourlyPressureChangeMb = presstrendval * 3 / 0.0295333727; + break; + } + + if (threeHourlyPressureChangeMb > 6) presstrend = 2; + else if (threeHourlyPressureChangeMb > 3.5) presstrend = 2; + else if (threeHourlyPressureChangeMb > 1.5) presstrend = 1; + else if (threeHourlyPressureChangeMb > 0.1) presstrend = 1; + else if (threeHourlyPressureChangeMb > -0.1) presstrend = 0; + else if (threeHourlyPressureChangeMb > -1.5) presstrend = -1; + else if (threeHourlyPressureChangeMb > -3.5) presstrend = -1; + else if (threeHourlyPressureChangeMb > -6) presstrend = -2; + else + presstrend = -2; + + double AvgTemp; + if (tempsamplestoday > 0) + AvgTemp = TempTotalToday / tempsamplestoday; + else + AvgTemp = 0; + + StringBuilder sb = new StringBuilder("http://data.awekas.at/eingabe_pruefung.php?"); + + var started = false; + + // indoor temp/humidity + if (cumulus.AWEKAS.SendIndoor) + { + sb.Append("indoortemp=" + ConvertUserTempToC(IndoorTemperature).ToString("F1", InvC)); + sb.Append("&indoorhumidity=" + IndoorHumidity); + started = true; + } + + if (cumulus.AWEKAS.SendSoilTemp) + { + if (started) sb.Append("&"); else started = true; + sb.Append("soiltemp1=" + ConvertUserTempToC(SoilTemp1).ToString("F1", InvC)); + sb.Append("&soiltemp2=" + ConvertUserTempToC(SoilTemp2).ToString("F1", InvC)); + sb.Append("&soiltemp3=" + ConvertUserTempToC(SoilTemp3).ToString("F1", InvC)); + sb.Append("&soiltemp4=" + ConvertUserTempToC(SoilTemp4).ToString("F1", InvC)); + } + + if (cumulus.AWEKAS.SendSoilMoisture) + { + if (started) sb.Append("&"); else started = true; + sb.Append("soilmoisture1=" + SoilMoisture1); + sb.Append("&soilmoisture2=" + SoilMoisture2); + sb.Append("&soilmoisture3=" + SoilMoisture3); + sb.Append("&soilmoisture4=" + SoilMoisture4); + } + + if (cumulus.AWEKAS.SendLeafWetness) + { + if (started) sb.Append("&"); else started = true; + sb.Append("leafwetness1=" + LeafWetness1); + sb.Append("&leafwetness2=" + LeafWetness2); + sb.Append("&leafwetness3=" + LeafWetness3); + sb.Append("&leafwetness4=" + LeafWetness4); + } + + if (cumulus.AWEKAS.SendAirQuality) + { + if (started) sb.Append("&"); else started = true; + + switch (cumulus.StationOptions.PrimaryAqSensor) + { + case (int)Cumulus.PrimaryAqSensor.AirLinkOutdoor: + if (cumulus.airLinkDataOut != null) + { + sb.Append($"AqPM1={cumulus.airLinkDataOut.pm1.ToString("F1", InvC)}"); + sb.Append($"&AqPM2.5={cumulus.airLinkDataOut.pm2p5.ToString("F1", InvC)}"); + sb.Append($"&AqPM10={cumulus.airLinkDataOut.pm10.ToString("F1", InvC)}"); + sb.Append($"&AqPM2.5_avg_24h={cumulus.airLinkDataOut.pm2p5_24hr.ToString("F1", InvC)}"); + sb.Append($"&AqPM10_avg_24h={cumulus.airLinkDataOut.pm10_24hr.ToString("F1", InvC)}"); + } + break; + case (int)Cumulus.PrimaryAqSensor.Ecowitt1: + sb.Append($"AqPM2.5={AirQuality1.ToString("F1", InvC)}"); + sb.Append($"&AqPM2.5_avg_24h={AirQualityAvg1.ToString("F1", InvC)}"); + break; + case (int)Cumulus.PrimaryAqSensor.Ecowitt2: + sb.Append($"AqPM2.5={AirQuality2.ToString("F1", InvC)}"); + sb.Append($"&AqPM2.5_avg_24h={AirQualityAvg2.ToString("F1", InvC)}"); + break; + case (int)Cumulus.PrimaryAqSensor.Ecowitt3: + sb.Append($"AqPM2.5={AirQuality3.ToString("F1", InvC)}"); + sb.Append($"&AqPM2.5_avg_24h={AirQualityAvg3.ToString("F1", InvC)}"); + break; + case (int)Cumulus.PrimaryAqSensor.Ecowitt4: + sb.Append($"AqPM2.5={AirQuality4.ToString("F1", InvC)}"); + sb.Append($"&AqPM2.5_avg_24h={AirQualityAvg4.ToString("F1", InvC)}"); + break; + case (int)Cumulus.PrimaryAqSensor.EcowittCO2: + sb.Append($"AqPM2.5={CO2_pm2p5.ToString("F1", InvC)}"); + sb.Append($"&AqPM2.5_avg_24h={CO2_pm2p5_24h.ToString("F1", InvC)}"); + sb.Append($"&AqPM10={CO2_pm10.ToString("F1", InvC)}"); + sb.Append($"&AqPM10_avg_24h={CO2_pm10_24h.ToString("F1", InvC)}"); + break; + } + } + + if (started) sb.Append("&"); + sb.Append("output=json&val="); + + // + // Start of val + // + sb.Append(cumulus.AWEKAS.ID + sep); // 1 + sb.Append(pwstring + sep); // 2 + sb.Append(timestamp.ToString("dd'.'MM'.'yyyy';'HH':'mm") + sep); // 3 + 4 + sb.Append(ConvertUserTempToC(OutdoorTemperature).ToString("F1", InvC) + sep); // 5 + sb.Append(OutdoorHumidity + sep); // 6 + sb.Append(ConvertUserPressToMB(Pressure).ToString("F1", InvC) + sep); // 7 + sb.Append(ConvertUserRainToMM(RainSinceMidnight).ToString("F1", InvC) + sep); // 8 - was RainToday in v2 + sb.Append(ConvertUserWindToKPH(WindAverage).ToString("F1", InvC) + sep); // 9 + sb.Append(AvgBearing + sep); // 10 + sb.Append(sep + sep + sep); // 11/12/13 - condition and warning, snow height + sb.Append(cumulus.AWEKAS.Lang + sep); // 14 + sb.Append(presstrend + sep); // 15 + sb.Append(ConvertUserWindToKPH(RecentMaxGust).ToString("F1", InvC) + sep); // 16 + + if (cumulus.AWEKAS.SendSolar) + sb.Append(SolarRad.ToString("F1", InvC) + sep); // 17 + else + sb.Append(sep); + + if (cumulus.AWEKAS.SendUV) + sb.Append(UV.ToString("F1", InvC) + sep); // 18 + else + sb.Append(sep); + + if (cumulus.AWEKAS.SendSolar) + { + if (cumulus.StationType == StationTypes.FineOffsetSolar) + sb.Append(LightValue.ToString("F0", InvC) + sep); // 19 + else + sb.Append(sep); + + sb.Append(SunshineHours.ToString("F2", InvC) + sep); // 20 + } + else + { + sb.Append(sep + sep); + } + + if (cumulus.AWEKAS.SendSoilTemp) + sb.Append(ConvertUserTempToC(SoilTemp1).ToString("F1", InvC) + sep); // 21 + else + sb.Append(sep); + + sb.Append(ConvertUserRainToMM(RainRate).ToString("F1", InvC) + sep); // 22 + sb.Append("Cum_" + cumulus.Version + sep); // 23 + sb.Append(sep + sep); // 24/25 location for mobile + sb.Append(ConvertUserTempToC(HiLoToday.LowTemp).ToString("F1", InvC) + sep); // 26 + sb.Append(ConvertUserTempToC(AvgTemp).ToString("F1", InvC) + sep); // 27 + sb.Append(ConvertUserTempToC(HiLoToday.HighTemp).ToString("F1", InvC) + sep); // 28 + sb.Append(ConvertUserTempToC(ThisMonth.LowTemp.Val).ToString("F1", InvC) + sep);// 29 + sb.Append(sep); // 30 avg temp this month + sb.Append(ConvertUserTempToC(ThisMonth.HighTemp.Val).ToString("F1", InvC) + sep);// 31 + sb.Append(ConvertUserTempToC(ThisYear.LowTemp.Val).ToString("F1", InvC) + sep); // 32 + sb.Append(sep); // 33 avg temp this year + sb.Append(ConvertUserTempToC(ThisYear.HighTemp.Val).ToString("F1", InvC) + sep);// 34 + sb.Append(HiLoToday.LowHumidity + sep); // 35 + sb.Append(sep); // 36 avg hum today + sb.Append(HiLoToday.HighHumidity + sep); // 37 + sb.Append(ThisMonth.LowHumidity.Val + sep); // 38 + sb.Append(sep); // 39 avg hum this month + sb.Append(ThisMonth.HighHumidity.Val + sep); // 40 + sb.Append(ThisYear.LowHumidity.Val + sep); // 41 + sb.Append(sep); // 42 avg hum this year + sb.Append(ThisYear.HighHumidity.Val + sep); // 43 + sb.Append(ConvertUserPressToMB(HiLoToday.LowPress).ToString("F1", InvC) + sep); // 44 + sb.Append(sep); // 45 avg press today + sb.Append(ConvertUserPressToMB(HiLoToday.HighPress).ToString("F1", InvC) + sep);// 46 + sb.Append(ConvertUserPressToMB(ThisMonth.LowPress.Val).ToString("F1", InvC) + sep); // 47 + sb.Append(sep); // 48 avg press this month + sb.Append(ConvertUserPressToMB(ThisMonth.HighPress.Val).ToString("F1", InvC) + sep); // 49 + sb.Append(ConvertUserPressToMB(ThisYear.LowPress.Val).ToString("F1", InvC) + sep); // 50 + sb.Append(sep); // 51 avg press this year + sb.Append(ConvertUserPressToMB(ThisYear.HighPress.Val).ToString("F1", InvC) + sep); // 52 + sb.Append(sep + sep); // 53/54 min/avg wind today + sb.Append(ConvertUserWindToKPH(HiLoToday.HighWind).ToString("F1", InvC) + sep); // 55 + sb.Append(sep + sep); // 56/57 min/avg wind this month + sb.Append(ConvertUserWindToKPH(ThisMonth.HighWind.Val).ToString("F1", InvC) + sep); // 58 + sb.Append(sep + sep); // 59/60 min/avg wind this year + sb.Append(ConvertUserWindToKPH(ThisYear.HighWind.Val).ToString("F1", InvC) + sep); // 61 + sb.Append(sep + sep); // 62/63 min/avg gust today + sb.Append(ConvertUserWindToKPH(HiLoToday.HighGust).ToString("F1", InvC) + sep); // 64 + sb.Append(sep + sep); // 65/66 min/avg gust this month + sb.Append(ConvertUserWindToKPH(ThisMonth.HighGust.Val).ToString("F1", InvC) + sep); // 67 + sb.Append(sep + sep); // 68/69 min/avg gust this year + sb.Append(ConvertUserWindToKPH(ThisYear.HighGust.Val).ToString("F1", InvC) + sep); // 70 + sb.Append(sep + sep + sep); // 71/72/73 avg wind bearing today/month/year + sb.Append(ConvertUserRainToMM(RainLast24Hour).ToString("F1", InvC) + sep); // 74 + sb.Append(ConvertUserRainToMM(RainMonth).ToString("F1", InvC) + sep); // 75 + sb.Append(ConvertUserRainToMM(RainYear).ToString("F1", InvC) + sep); // 76 + sb.Append(sep); // 77 avg rain rate today + sb.Append(ConvertUserRainToMM(HiLoToday.HighRainRate).ToString("F1", InvC) + sep); // 78 + sb.Append(sep); // 79 avg rain rate this month + sb.Append(ConvertUserRainToMM(ThisMonth.HighRainRate.Val).ToString("F1", InvC) + sep); // 80 + sb.Append(sep); // 81 avg rain rate this year + sb.Append(ConvertUserRainToMM(ThisYear.HighRainRate.Val).ToString("F1", InvC) + sep); // 82 + sb.Append(sep); // 83 avg solar today + if (cumulus.AWEKAS.SendSolar) + sb.Append(HiLoToday.HighSolar.ToString("F1", InvC)); // 84 + else + sb.Append(sep); + + sb.Append(sep + sep); // 85/86 avg/high solar this month + sb.Append(sep + sep); // 87/88 avg/high solar this year + sb.Append(sep); // 89 avg uv today + + if (cumulus.AWEKAS.SendUV) + sb.Append(HiLoToday.HighUv.ToString("F1", InvC)); // 90 + else + sb.Append(sep); + + sb.Append(sep + sep); // 91/92 avg/high uv this month + sb.Append(sep + sep); // 93/94 avg/high uv this year + sb.Append(sep + sep + sep + sep + sep + sep); // 95/96/97/98/99/100 avg/max lux today/month/year + sb.Append(sep + sep); // 101/102 sun hours this month/year + sb.Append(sep + sep + sep + sep + sep + sep + sep + sep + sep); // 103-111 min/avg/max Soil temp today/month/year + // + // End of val fixed structure + // + + return sb.ToString(); + } + + + public string GetWundergroundURL(out string pwstring, DateTime timestamp, bool catchup) + { + // API documentation: https://support.weather.com/s/article/PWS-Upload-Protocol?language=en_US + + var invC = new CultureInfo(""); + + string dateUTC = timestamp.ToUniversalTime().ToString("yyyy'-'MM'-'dd'+'HH'%3A'mm'%3A'ss"); + StringBuilder URL = new StringBuilder(1024); + if (cumulus.Wund.RapidFireEnabled && !catchup) + { + URL.Append("http://rtupdate.wunderground.com/weatherstation/updateweatherstation.php?ID="); + } + else + { + URL.Append("http://weatherstation.wunderground.com/weatherstation/updateweatherstation.php?ID="); + } + + pwstring = $"&PASSWORD={cumulus.Wund.PW}"; + URL.Append(cumulus.Wund.ID); + URL.Append(pwstring); + URL.Append($"&dateutc={dateUTC}"); + StringBuilder Data = new StringBuilder(1024); + if (cumulus.Wund.SendAverage) + { + // send average speed and bearing + Data.Append($"&winddir={AvgBearing}&windspeedmph={WindMPHStr(WindAverage).Replace(',', '.')}"); + } + else + { + // send "instantaneous" speed (i.e. latest) and bearing + Data.Append($"&winddir={Bearing}&windspeedmph={WindMPHStr(WindLatest).Replace(',', '.')}"); + } + Data.Append($"&windgustmph={WindMPHStr(RecentMaxGust).Replace(',', '.')}"); + // may not strictly be a 2 min average! + Data.Append($"&windspdmph_avg2m={WindMPHStr(WindAverage).Replace(',', '.')}"); + Data.Append($"&winddir_avg2m={AvgBearing}"); + Data.Append($"&humidity={OutdoorHumidity}"); + Data.Append($"&tempf={TempFstr(OutdoorTemperature).Replace(',', '.')}"); + Data.Append($"&rainin={RainINstr(RainLastHour).Replace(',', '.')}"); + Data.Append("&dailyrainin="); + if (cumulus.RolloverHour == 0) + { + // use today"s rain + Data.Append(RainINstr(RainToday).Replace(',', '.')); + } + else + { + Data.Append(RainINstr(RainSinceMidnight).Replace(',', '.')); + } + Data.Append($"&baromin={PressINstr(Pressure).Replace(',', '.')}"); + Data.Append($"&dewptf={TempFstr(OutdoorDewpoint).Replace(',', '.')}"); + if (cumulus.Wund.SendUV) + Data.Append($"&UV={UV.ToString(cumulus.UVFormat).Replace(',', '.')}"); + if (cumulus.Wund.SendSolar) + Data.Append($"&solarradiation={SolarRad:F0}"); + if (cumulus.Wund.SendIndoor) + { + Data.Append($"&indoortempf={TempFstr(IndoorTemperature).Replace(',', '.')}"); + Data.Append($"&indoorhumidity={IndoorHumidity}"); + } + // Davis soil and leaf sensors + if (cumulus.Wund.SendSoilTemp1) + Data.Append($"&soiltempf={TempFstr(SoilTemp1).Replace(',', '.')}"); + if (cumulus.Wund.SendSoilTemp2) + Data.Append($"&soiltempf2={TempFstr(SoilTemp2).Replace(',', '.')}"); + if (cumulus.Wund.SendSoilTemp3) + Data.Append($"&soiltempf3={TempFstr(SoilTemp3).Replace(',', '.')}"); + if (cumulus.Wund.SendSoilTemp4) + Data.Append($"&soiltempf4={TempFstr(SoilTemp4).Replace(',', '.')}"); + + if (cumulus.Wund.SendSoilMoisture1) + Data.Append($"&soilmoisture={SoilMoisture1}"); + if (cumulus.Wund.SendSoilMoisture2) + Data.Append($"&soilmoisture2={SoilMoisture2}"); + if (cumulus.Wund.SendSoilMoisture3) + Data.Append($"&soilmoisture3={SoilMoisture3}"); + if (cumulus.Wund.SendSoilMoisture4) + Data.Append($"&soilmoisture4={SoilMoisture4}"); + + if (cumulus.Wund.SendLeafWetness1) + Data.Append($"&leafwetness={LeafWetness1}"); + if (cumulus.Wund.SendLeafWetness2) + Data.Append($"&leafwetness2={LeafWetness2}"); + + if (cumulus.Wund.SendAirQuality && cumulus.StationOptions.PrimaryAqSensor > (int)Cumulus.PrimaryAqSensor.Undefined) + { + switch (cumulus.StationOptions.PrimaryAqSensor) + { + case (int)Cumulus.PrimaryAqSensor.AirLinkOutdoor: + if (cumulus.airLinkDataOut != null) + { + Data.Append($"&AqPM2.5={cumulus.airLinkDataOut.pm2p5:F1}&AqPM10={cumulus.airLinkDataOut.pm10.ToString("F1", invC)}"); + } + break; + case (int)Cumulus.PrimaryAqSensor.Ecowitt1: + Data.Append($"&AqPM2.5={AirQuality1.ToString("F1", invC)}"); + break; + case (int)Cumulus.PrimaryAqSensor.Ecowitt2: + Data.Append($"&AqPM2.5={AirQuality2.ToString("F1", invC)}"); + break; + case (int)Cumulus.PrimaryAqSensor.Ecowitt3: + Data.Append($"&AqPM2.5={AirQuality3.ToString("F1", invC)}"); + break; + case (int)Cumulus.PrimaryAqSensor.Ecowitt4: + Data.Append($"&AqPM2.5={AirQuality4.ToString("F1", invC)}"); + break; + } + } + + Data.Append($"&softwaretype=Cumulus%20v{cumulus.Version}"); + Data.Append("&action=updateraw"); + if (cumulus.Wund.RapidFireEnabled && !catchup) + Data.Append("&realtime=1&rtfreq=5"); + + Data.Replace(",", "."); + URL.Append(Data); + + return URL.ToString(); + } + + public string GetWindyURL(out string apistring, DateTime timestamp) + { + string dateUTC = timestamp.ToUniversalTime().ToString("yyyy'-'MM'-'dd'+'HH':'mm':'ss"); + StringBuilder URL = new StringBuilder("https://stations.windy.com/pws/update/", 1024); + + apistring = cumulus.Windy.ApiKey; + + URL.Append(cumulus.Windy.ApiKey); + URL.Append("?station=" + cumulus.Windy.StationIdx); + URL.Append("&dateutc=" + dateUTC); + StringBuilder Data = new StringBuilder(1024); + Data.Append("&winddir=" + AvgBearing); + Data.Append("&windspeedmph=" + WindMPHStr(WindAverage).Replace(',', '.')); + Data.Append("&windgustmph=" + WindMPHStr(RecentMaxGust).Replace(',', '.')); + Data.Append("&tempf=" + TempFstr(OutdoorTemperature).Replace(',', '.')); + Data.Append("&rainin=" + RainINstr(RainLastHour).Replace(',', '.')); + Data.Append("&baromin=" + PressINstr(Pressure).Replace(',', '.')); + Data.Append("&dewptf=" + TempFstr(OutdoorDewpoint).Replace(',', '.')); + Data.Append("&humidity=" + OutdoorHumidity); + + if (cumulus.Windy.SendUV) + Data.Append("&uv=" + UV.ToString(cumulus.UVFormat).Replace(',', '.')); + if (cumulus.Windy.SendSolar) + Data.Append("&solarradiation=" + SolarRad.ToString("F0")); + + Data.Replace(",", "."); + URL.Append(Data); + + return URL.ToString(); + } + + + // Documentation on the API can be found here... + // https://stations.windguru.cz/upload_api.php + // + public string GetWindGuruURL(out string uidstring, DateTime timestamp) + { + var InvC = new CultureInfo(""); + + string salt = timestamp.ToUnixTime().ToString(); + string hash = Utils.GetMd5String(salt + cumulus.WindGuru.ID + cumulus.WindGuru.PW); + + uidstring = cumulus.WindGuru.ID; + + int numvalues = 0; + double totalwind = 0; + double maxwind = 0; + double minwind = 999; + for (int i = 0; i < MaxWindRecent; i++) + { + if (WindRecent[i].Timestamp >= DateTime.Now.AddMinutes(-cumulus.WindGuru.Interval)) + { + numvalues++; + totalwind += WindRecent[i].Gust; + + if (WindRecent[i].Gust > maxwind) + { + maxwind = WindRecent[i].Gust; + } + + if (WindRecent[i].Gust < minwind) + { + minwind = WindRecent[i].Gust; + } + } + } + // average the values + double avgwind = totalwind / numvalues * cumulus.Calib.WindSpeed.Mult; + + maxwind *= cumulus.Calib.WindGust.Mult; + minwind *= cumulus.Calib.WindGust.Mult; + + + StringBuilder URL = new StringBuilder("http://www.windguru.cz/upload/api.php?", 1024); + + URL.Append("uid=" + HttpUtility.UrlEncode(cumulus.WindGuru.ID)); + URL.Append("&salt=" + salt); + URL.Append("&hash=" + hash); + URL.Append("&interval=" + cumulus.WindGuru.Interval * 60); + URL.Append("&wind_avg=" + ConvertUserWindToKnots(avgwind).ToString("F1", InvC)); + URL.Append("&wind_max=" + ConvertUserWindToKnots(maxwind).ToString("F1", InvC)); + URL.Append("&wind_min=" + ConvertUserWindToKnots(minwind).ToString("F1", InvC)); + URL.Append("&wind_direction=" + AvgBearing); + URL.Append("&temperature=" + ConvertUserTempToC(OutdoorTemperature).ToString("F1", InvC)); + URL.Append("&rh=" + OutdoorHumidity); + URL.Append("&mslp=" + ConvertUserPressureToHPa(Pressure).ToString("F1", InvC)); + if (cumulus.WindGuru.SendRain) + { + URL.Append("&precip=" + ConvertUserRainToMM(RainLastHour).ToString("F1", InvC)); + URL.Append("&precip_interval=3600"); + } + + return URL.ToString(); + } + + public string GetOpenWeatherMapData(DateTime timestamp) + { + StringBuilder sb = new StringBuilder($"[{{\"station_id\":\"{cumulus.OpenWeatherMap.ID}\","); + var invC = new CultureInfo(""); + + sb.Append($"\"dt\":{Utils.ToUnixTime(timestamp)},"); + sb.Append($"\"temperature\":{Math.Round(ConvertUserTempToC(OutdoorTemperature), 1).ToString(invC)},"); + sb.Append($"\"wind_deg\":{AvgBearing},"); + sb.Append($"\"wind_speed\":{Math.Round(ConvertUserWindToMS(WindAverage), 1).ToString(invC)},"); + sb.Append($"\"wind_gust\":{Math.Round(ConvertUserWindToMS(RecentMaxGust), 1).ToString(invC)},"); + sb.Append($"\"pressure\":{Math.Round(ConvertUserPressureToHPa(Pressure), 1).ToString(invC)},"); + sb.Append($"\"humidity\":{OutdoorHumidity},"); + sb.Append($"\"rain_1h\":{Math.Round(ConvertUserRainToMM(RainLastHour), 1).ToString(invC)},"); + sb.Append($"\"rain_24h\":{Math.Round(ConvertUserRainToMM(RainLast24Hour), 1).ToString(invC)}"); + sb.Append("}]"); + + return sb.ToString(); + } + + private string PressINstr(double pressure) + { + var pressIN = ConvertUserPressToIN(pressure); + + return pressIN.ToString("F3"); + } + + private string WindMPHStr(double wind) + { + var windMPH = ConvertUserWindToMPH(wind); + if (cumulus.StationOptions.RoundWindSpeed) + windMPH = Math.Round(windMPH); + + return windMPH.ToString("F1"); + } + + /// + /// Convert rain in user units to inches for WU etc + /// + /// + /// + private string RainINstr(double rain) + { + var rainIN = ConvertUserRainToIn(rain); + + return rainIN.ToString("F2"); + } + + /// + /// Convert temp in user units to F for WU etc + /// + /// + /// + private string TempFstr(double temp) + { + double tempf = ConvertUserTempToF(temp); + + return tempf.ToString("F1"); + } + + public string GetPWSURL(out string pwstring, DateTime timestamp) + { + string dateUTC = timestamp.ToUniversalTime().ToString("yyyy'-'MM'-'dd'+'HH'%3A'mm'%3A'ss"); + StringBuilder URL = new StringBuilder("http://www.pwsweather.com/pwsupdate/pwsupdate.php?ID=", 1024); + + pwstring = "&PASSWORD=" + cumulus.PWS.PW; + URL.Append(cumulus.PWS.ID + pwstring); + URL.Append("&dateutc=" + dateUTC); + + StringBuilder Data = new StringBuilder(1024); + + // send average speed and bearing + Data.Append("&winddir=" + AvgBearing); + Data.Append("&windspeedmph=" + WindMPHStr(WindAverage).Replace(',', '.')); + Data.Append("&windgustmph=" + WindMPHStr(RecentMaxGust).Replace(',', '.')); + Data.Append("&humidity=" + OutdoorHumidity); + Data.Append("&tempf=" + TempFstr(OutdoorTemperature).Replace(',', '.')); + Data.Append("&rainin=" + RainINstr(RainLastHour).Replace(',', '.')); + Data.Append("&dailyrainin="); + if (cumulus.RolloverHour == 0) + { + // use today"s rain + Data.Append(RainINstr(RainToday).Replace(',', '.')); + } + else + { + Data.Append(RainINstr(RainSinceMidnight).Replace(',', '.')); + } + Data.Append("&baromin=" + PressINstr(Pressure).Replace(',', '.')); + Data.Append("&dewptf=" + TempFstr(OutdoorDewpoint).Replace(',', '.')); + if (cumulus.PWS.SendUV) + { + Data.Append("&UV=" + UV.ToString(cumulus.UVFormat).Replace(',', '.')); + } + + if (cumulus.PWS.SendSolar) + { + Data.Append("&solarradiation=" + SolarRad.ToString("F0")); + } + + Data.Append("&softwaretype=Cumulus%20v" + cumulus.Version); + Data.Append("&action=updateraw"); + + Data.Replace(",", "."); + URL.Append(Data); + + return URL.ToString(); + } + + public string GetWOWURL(out string pwstring, DateTime timestamp) + { + string dateUTC = timestamp.ToUniversalTime().ToString("yyyy'-'MM'-'dd'+'HH'%3A'mm'%3A'ss"); + StringBuilder URL = new StringBuilder("http://wow.metoffice.gov.uk/automaticreading?siteid=", 1024); + + pwstring = "&siteAuthenticationKey=" + cumulus.WOW.PW; + URL.Append(cumulus.WOW.ID); + URL.Append(pwstring); + URL.Append("&dateutc=" + dateUTC); + + StringBuilder Data = new StringBuilder(1024); + + // send average speed and bearing + Data.Append("&winddir=" + AvgBearing); + Data.Append("&windspeedmph=" + WindMPHStr(WindAverage).Replace(',', '.')); + Data.Append("&windgustmph=" + WindMPHStr(RecentMaxGust).Replace(',', '.')); + Data.Append("&humidity=" + OutdoorHumidity); + Data.Append("&tempf=" + TempFstr(OutdoorTemperature).Replace(',', '.')); + Data.Append("&rainin=" + RainINstr(RainLastHour).Replace(',', '.')); + Data.Append("&dailyrainin="); + if (cumulus.RolloverHour == 0) + { + // use today"s rain + Data.Append(RainINstr(RainToday).Replace(',', '.')); + } + else + { + Data.Append(RainINstr(RainSinceMidnight).Replace(',', '.')); + } + Data.Append("&baromin=" + PressINstr(Pressure).Replace(',', '.')); + Data.Append("&dewptf=" + TempFstr(OutdoorDewpoint).Replace(',', '.')); + if (cumulus.WOW.SendUV) + { + Data.Append("&UV=" + UV.ToString(cumulus.UVFormat).Replace(',', '.')); + } + if (cumulus.WOW.SendSolar) + { + Data.Append("&solarradiation=" + SolarRad.ToString("F0")); + } + + Data.Append("&softwaretype=Cumulus%20v" + cumulus.Version); + Data.Append("&action=updateraw"); + + Data.Replace(",", "."); + URL.Append(Data); + + return URL.ToString(); + } + + public double ConvertUserRainToIN(double rain) + { + if (cumulus.Units.Rain == 0) + { + return rain * 0.0393700787; + } + else + { + return rain; + } + } + + private string alltimejsonformat(AllTimeRec item, string unit, string valueformat, string dateformat) + { + return $"[\"{item.Desc}\",\"{item.Val.ToString(valueformat)} {unit}\",\"{item.Ts.ToString(dateformat)}\"]"; + } + + public string GetTempRecords() + { + var json = new StringBuilder("{\"data\":[", 2048); + + json.Append(alltimejsonformat(AllTime.HighTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); + json.Append(alltimejsonformat(AllTime.LowTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); + json.Append(alltimejsonformat(AllTime.HighDewPoint, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); + json.Append(alltimejsonformat(AllTime.LowDewPoint, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); + json.Append(alltimejsonformat(AllTime.HighAppTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); + json.Append(alltimejsonformat(AllTime.LowAppTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); + json.Append(alltimejsonformat(AllTime.HighFeelsLike, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); + json.Append(alltimejsonformat(AllTime.LowFeelsLike, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); + json.Append(alltimejsonformat(AllTime.HighHumidex, " ", cumulus.TempFormat, "f") + ","); + json.Append(alltimejsonformat(AllTime.LowChill, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); + json.Append(alltimejsonformat(AllTime.HighHeatIndex, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); + json.Append(alltimejsonformat(AllTime.HighMinTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); + json.Append(alltimejsonformat(AllTime.LowMaxTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f") + ","); + json.Append(alltimejsonformat(AllTime.HighDailyTempRange, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "D") + ","); + json.Append(alltimejsonformat(AllTime.LowDailyTempRange, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "D")); + json.Append("]}"); + return json.ToString(); + } + + public string GetHumRecords() + { + var json = new StringBuilder("{\"data\":[", 512); + + json.Append(alltimejsonformat(AllTime.HighHumidity, "%", cumulus.HumFormat, "f")); + json.Append(","); + json.Append(alltimejsonformat(AllTime.LowHumidity, "%", cumulus.HumFormat, "f")); + json.Append("]}"); + return json.ToString(); + } + + public string GetPressRecords() + { + var json = new StringBuilder("{\"data\":[", 512); + + json.Append(alltimejsonformat(AllTime.HighPress, cumulus.Units.PressText, cumulus.PressFormat, "f")); + json.Append(","); + json.Append(alltimejsonformat(AllTime.LowPress, cumulus.Units.PressText, cumulus.PressFormat, "f")); + json.Append("]}"); + return json.ToString(); + } + + public string GetWindRecords() + { + var json = new StringBuilder("{\"data\":[", 512); + + json.Append(alltimejsonformat(AllTime.HighGust, cumulus.Units.WindText, cumulus.WindFormat, "f")); + json.Append(","); + json.Append(alltimejsonformat(AllTime.HighWind, cumulus.Units.WindText, cumulus.WindAvgFormat, "f")); + json.Append(","); + json.Append(alltimejsonformat(AllTime.HighWindRun, cumulus.Units.WindRunText, cumulus.WindRunFormat, "D")); + json.Append("]}"); + return json.ToString(); + } + + public string GetRainRecords() + { + var json = new StringBuilder("{\"data\":[", 512); + + json.Append(alltimejsonformat(AllTime.HighRainRate, cumulus.Units.RainText + "/hr", cumulus.RainFormat, "f")); + json.Append(","); + json.Append(alltimejsonformat(AllTime.HourlyRain, cumulus.Units.RainText, cumulus.RainFormat, "f")); + json.Append(","); + json.Append(alltimejsonformat(AllTime.DailyRain, cumulus.Units.RainText, cumulus.RainFormat, "D")); + json.Append(","); + json.Append(alltimejsonformat(AllTime.MonthlyRain, cumulus.Units.RainText, cumulus.RainFormat, "Y")); + json.Append(","); + json.Append(alltimejsonformat(AllTime.LongestDryPeriod, "days", "f0", "D")); + json.Append(","); + json.Append(alltimejsonformat(AllTime.LongestWetPeriod, "days", "f0", "D")); + json.Append("]}"); + return json.ToString(); + } + + private string monthlyjsonformat(AllTimeRec item, string unit, string valueformat, string dateformat) + { + return $"[\"{item.Desc}\",\"{item.Val.ToString(valueformat)} {unit}\",\"{item.Ts.ToString(dateformat)}\"]"; + } + + public string GetMonthlyTempRecords(int month) + { + var json = new StringBuilder("{\"data\":[", 1024); + + json.Append(monthlyjsonformat(MonthlyRecs[month].HighTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthlyjsonformat(MonthlyRecs[month].LowTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthlyjsonformat(MonthlyRecs[month].HighDewPoint, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthlyjsonformat(MonthlyRecs[month].LowDewPoint, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthlyjsonformat(MonthlyRecs[month].HighAppTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthlyjsonformat(MonthlyRecs[month].LowAppTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthlyjsonformat(MonthlyRecs[month].HighFeelsLike, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthlyjsonformat(MonthlyRecs[month].LowFeelsLike, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthlyjsonformat(MonthlyRecs[month].HighHumidex, " ", cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthlyjsonformat(MonthlyRecs[month].LowChill, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthlyjsonformat(MonthlyRecs[month].HighHeatIndex, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthlyjsonformat(MonthlyRecs[month].HighMinTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthlyjsonformat(MonthlyRecs[month].LowMaxTemp, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthlyjsonformat(MonthlyRecs[month].HighDailyTempRange, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "D")); + json.Append(","); + json.Append(monthlyjsonformat(MonthlyRecs[month].LowDailyTempRange, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "D")); + json.Append("]}"); + return json.ToString(); + } + + public string GetMonthlyHumRecords(int month) + { + var json = new StringBuilder("{\"data\":[", 256); + + json.Append(monthlyjsonformat(MonthlyRecs[month].HighHumidity, "%", cumulus.HumFormat, "f")); + json.Append(","); + json.Append(monthlyjsonformat(MonthlyRecs[month].LowHumidity, "%", cumulus.HumFormat, "f")); + json.Append("]}"); + return json.ToString(); + } + + public string GetMonthlyPressRecords(int month) + { + var json = new StringBuilder("{\"data\":[", 256); + + json.Append(monthlyjsonformat(MonthlyRecs[month].HighPress, cumulus.Units.PressText, cumulus.PressFormat, "f")); + json.Append(","); + json.Append(monthlyjsonformat(MonthlyRecs[month].LowPress, cumulus.Units.PressText, cumulus.PressFormat, "f")); + json.Append("]}"); + return json.ToString(); + } + + public string GetMonthlyWindRecords(int month) + { + var json = new StringBuilder("{\"data\":[", 256); + + json.Append(monthlyjsonformat(MonthlyRecs[month].HighGust, cumulus.Units.WindText, cumulus.WindFormat, "f")); + json.Append(","); + json.Append(monthlyjsonformat(MonthlyRecs[month].HighWind, cumulus.Units.WindText, cumulus.WindAvgFormat, "f")); + json.Append(","); + json.Append(monthlyjsonformat(MonthlyRecs[month].HighWindRun, cumulus.Units.WindRunText, cumulus.WindRunFormat, "D")); + json.Append("]}"); + return json.ToString(); + } + + public string GetMonthlyRainRecords(int month) + { + var json = new StringBuilder("{\"data\":[", 512); + + json.Append(monthlyjsonformat(MonthlyRecs[month].HighRainRate, cumulus.Units.RainText + "/hr", cumulus.RainFormat, "f")); + json.Append(","); + json.Append(monthlyjsonformat(MonthlyRecs[month].HourlyRain, cumulus.Units.RainText, cumulus.RainFormat, "f")); + json.Append(","); + json.Append(monthlyjsonformat(MonthlyRecs[month].DailyRain, cumulus.Units.RainText, cumulus.RainFormat, "D")); + json.Append(","); + json.Append(monthlyjsonformat(MonthlyRecs[month].MonthlyRain, cumulus.Units.RainText, cumulus.RainFormat, "Y")); + json.Append(","); + json.Append(monthlyjsonformat(MonthlyRecs[month].LongestDryPeriod, "days", "f0", "D")); + json.Append(","); + json.Append(monthlyjsonformat(MonthlyRecs[month].LongestWetPeriod, "days", "f0", "D")); + json.Append("]}"); + return json.ToString(); + } + + private string monthyearjsonformat(string description, double value, DateTime timestamp, string unit, string valueformat, string dateformat) + { + return $"[\"{description}\",\"{value.ToString(valueformat)} {unit}\",\"{timestamp.ToString(dateformat)}\"]"; + } + + public string GetThisMonthTempRecords() + { + var json = new StringBuilder("{\"data\":[", 1024); + + json.Append(monthyearjsonformat(ThisMonth.HighTemp.Desc, ThisMonth.HighTemp.Val, ThisMonth.HighTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisMonth.LowTemp.Desc, ThisMonth.LowTemp.Val, ThisMonth.LowTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisMonth.HighDewPoint.Desc, ThisMonth.HighDewPoint.Val, ThisMonth.HighDewPoint.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisMonth.LowDewPoint.Desc, ThisMonth.LowDewPoint.Val, ThisMonth.LowDewPoint.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisMonth.HighAppTemp.Desc, ThisMonth.HighAppTemp.Val, ThisMonth.HighAppTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisMonth.LowAppTemp.Desc, ThisMonth.LowAppTemp.Val, ThisMonth.LowAppTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisMonth.HighFeelsLike.Desc, ThisMonth.HighFeelsLike.Val, ThisMonth.HighFeelsLike.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisMonth.LowFeelsLike.Desc, ThisMonth.LowFeelsLike.Val, ThisMonth.LowFeelsLike.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisMonth.HighHumidex.Desc, ThisMonth.HighHumidex.Val, ThisMonth.HighHumidex.Ts, " ", cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisMonth.LowChill.Desc, ThisMonth.LowChill.Val, ThisMonth.LowChill.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisMonth.HighHeatIndex.Desc, ThisMonth.HighHeatIndex.Val, ThisMonth.HighHeatIndex.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisMonth.HighMinTemp.Desc, ThisMonth.HighMinTemp.Val, ThisMonth.HighMinTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisMonth.LowMaxTemp.Desc, ThisMonth.LowMaxTemp.Val, ThisMonth.LowMaxTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisMonth.HighDailyTempRange.Desc, ThisMonth.HighDailyTempRange.Val, ThisMonth.HighDailyTempRange.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "D")); + json.Append(","); + json.Append(monthyearjsonformat(ThisMonth.LowDailyTempRange.Desc, ThisMonth.LowDailyTempRange.Val, ThisMonth.LowDailyTempRange.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "D")); + json.Append("]}"); + return json.ToString(); + } + + public string GetThisMonthHumRecords() + { + var json = new StringBuilder("{\"data\":[", 256); + + json.Append(monthyearjsonformat(ThisMonth.HighHumidity.Desc, ThisMonth.HighHumidity.Val, ThisMonth.HighHumidity.Ts, "%", cumulus.HumFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisMonth.LowHumidity.Desc, ThisMonth.LowHumidity.Val, ThisMonth.LowHumidity.Ts, "%", cumulus.HumFormat, "f")); + json.Append("]}"); + return json.ToString(); + } + + public string GetThisMonthPressRecords() + { + var json = new StringBuilder("{\"data\":[", 256); + + json.Append(monthyearjsonformat(ThisMonth.HighPress.Desc, ThisMonth.HighPress.Val, ThisMonth.HighPress.Ts, cumulus.Units.PressText, cumulus.PressFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisMonth.LowPress.Desc, ThisMonth.LowPress.Val, ThisMonth.LowPress.Ts, cumulus.Units.PressText, cumulus.PressFormat, "f")); + json.Append("]}"); + return json.ToString(); + } + + public string GetThisMonthWindRecords() + { + var json = new StringBuilder("{\"data\":[", 256); + + json.Append(monthyearjsonformat(ThisMonth.HighGust.Desc, ThisMonth.HighGust.Val, ThisMonth.HighGust.Ts, cumulus.Units.WindText, cumulus.WindFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisMonth.HighWind.Desc, ThisMonth.HighWind.Val, ThisMonth.HighWind.Ts, cumulus.Units.WindText, cumulus.WindAvgFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisMonth.HighWindRun.Desc, ThisMonth.HighWindRun.Val, ThisMonth.HighWindRun.Ts, cumulus.Units.WindRunText, cumulus.WindRunFormat, "D")); + json.Append("]}"); + return json.ToString(); + } + + public string GetThisMonthRainRecords() + { + var json = new StringBuilder("{\"data\":[", 512); + + json.Append(monthyearjsonformat(ThisMonth.HighRainRate.Desc, ThisMonth.HighRainRate.Val, ThisMonth.HighRainRate.Ts, cumulus.Units.RainText + "/hr", cumulus.RainFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisMonth.HourlyRain.Desc, ThisMonth.HourlyRain.Val, ThisMonth.HourlyRain.Ts, cumulus.Units.RainText, cumulus.RainFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisMonth.DailyRain.Desc, ThisMonth.DailyRain.Val, ThisMonth.DailyRain.Ts, cumulus.Units.RainText, cumulus.RainFormat, "D")); + json.Append(","); + //json.Append(monthyearjsonformat(ThisMonth.WetMonth.Desc, month, cumulus.Units.RainText, cumulus.RainFormat, "Y")); + //json.Append(","); + json.Append(monthyearjsonformat(ThisMonth.LongestDryPeriod.Desc, ThisMonth.LongestDryPeriod.Val, ThisMonth.LongestDryPeriod.Ts, "days", "f0", "D")); + json.Append(","); + json.Append(monthyearjsonformat(ThisMonth.LongestWetPeriod.Desc, ThisMonth.LongestWetPeriod.Val, ThisMonth.LongestWetPeriod.Ts, "days", "f0", "D")); + json.Append("]}"); + return json.ToString(); + } + + public string GetThisYearTempRecords() + { + var json = new StringBuilder("{\"data\":[", 1024); + + json.Append(monthyearjsonformat(ThisYear.HighTemp.Desc, ThisYear.HighTemp.Val, ThisYear.HighTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisYear.LowTemp.Desc, ThisYear.LowTemp.Val, ThisYear.LowTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisYear.HighDewPoint.Desc, ThisYear.HighDewPoint.Val, ThisYear.HighDewPoint.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisYear.LowDewPoint.Desc, ThisYear.LowDewPoint.Val, ThisYear.LowDewPoint.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisYear.HighAppTemp.Desc, ThisYear.HighAppTemp.Val, ThisYear.HighAppTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisYear.LowAppTemp.Desc, ThisYear.LowAppTemp.Val, ThisYear.LowAppTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisYear.HighFeelsLike.Desc, ThisYear.HighFeelsLike.Val, ThisYear.HighFeelsLike.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisYear.LowFeelsLike.Desc, ThisYear.LowFeelsLike.Val, ThisYear.LowFeelsLike.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisYear.HighHumidex.Desc, ThisYear.HighHumidex.Val, ThisYear.HighHumidex.Ts, " ", cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisYear.LowChill.Desc, ThisYear.LowChill.Val, ThisYear.LowChill.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisYear.HighHeatIndex.Desc, ThisYear.HighHeatIndex.Val, ThisYear.HighHeatIndex.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisYear.HighMinTemp.Desc, ThisYear.HighMinTemp.Val, ThisYear.HighMinTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisYear.LowMaxTemp.Desc, ThisYear.LowMaxTemp.Val, ThisYear.LowMaxTemp.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisYear.HighDailyTempRange.Desc, ThisYear.HighDailyTempRange.Val, ThisYear.HighDailyTempRange.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "D")); + json.Append(","); + json.Append(monthyearjsonformat(ThisYear.LowDailyTempRange.Desc, ThisYear.LowDailyTempRange.Val, ThisYear.LowDailyTempRange.Ts, "°" + cumulus.Units.TempText[1].ToString(), cumulus.TempFormat, "D")); + json.Append("]}"); + return json.ToString(); + } + + public string GetThisYearHumRecords() + { + var json = new StringBuilder("{\"data\":[", 256); + + json.Append(monthyearjsonformat(ThisYear.HighHumidity.Desc, ThisYear.HighHumidity.Val, ThisYear.HighHumidity.Ts, "%", cumulus.HumFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisYear.LowHumidity.Desc, ThisYear.LowHumidity.Val, ThisYear.LowHumidity.Ts, "%", cumulus.HumFormat, "f")); + json.Append("]}"); + return json.ToString(); + } + + public string GetThisYearPressRecords() + { + var json = new StringBuilder("{\"data\":[", 256); + + json.Append(monthyearjsonformat(ThisYear.HighPress.Desc, ThisYear.HighPress.Val, ThisYear.HighPress.Ts, cumulus.Units.PressText, cumulus.PressFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisYear.LowPress.Desc, ThisYear.LowPress.Val, ThisYear.LowPress.Ts, cumulus.Units.PressText, cumulus.PressFormat, "f")); + json.Append("]}"); + return json.ToString(); + } + + public string GetThisYearWindRecords() + { + var json = new StringBuilder("{\"data\":[", 256); + + json.Append(monthyearjsonformat(ThisYear.HighGust.Desc, ThisYear.HighGust.Val, ThisYear.HighGust.Ts, cumulus.Units.WindText, cumulus.WindFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisYear.HighWind.Desc, ThisYear.HighWind.Val, ThisYear.HighWind.Ts, cumulus.Units.WindText, cumulus.WindAvgFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisYear.HighWindRun.Desc, ThisYear.HighWindRun.Val, ThisYear.HighWindRun.Ts, cumulus.Units.WindRunText, cumulus.WindRunFormat, "D")); + json.Append("]}"); + return json.ToString(); + } + + public string GetThisYearRainRecords() + { + var json = new StringBuilder("{\"data\":[", 512); + + json.Append(monthyearjsonformat(ThisYear.HighRainRate.Desc, ThisYear.HighRainRate.Val, ThisYear.HighRainRate.Ts, cumulus.Units.RainText + "/hr", cumulus.RainFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisYear.HourlyRain.Desc, ThisYear.HourlyRain.Val, ThisYear.HourlyRain.Ts, cumulus.Units.RainText, cumulus.RainFormat, "f")); + json.Append(","); + json.Append(monthyearjsonformat(ThisYear.DailyRain.Desc, ThisYear.DailyRain.Val, ThisYear.DailyRain.Ts, cumulus.Units.RainText, cumulus.RainFormat, "D")); + json.Append(","); + json.Append(monthyearjsonformat(ThisYear.MonthlyRain.Desc, ThisYear.MonthlyRain.Val, ThisYear.MonthlyRain.Ts, cumulus.Units.RainText, cumulus.RainFormat, "Y")); + json.Append(","); + json.Append(monthyearjsonformat(ThisYear.LongestDryPeriod.Desc, ThisYear.LongestDryPeriod.Val, ThisYear.LongestDryPeriod.Ts, "days", "f0", "D")); + json.Append(","); + json.Append(monthyearjsonformat(ThisYear.LongestWetPeriod.Desc, ThisYear.LongestWetPeriod.Val, ThisYear.LongestWetPeriod.Ts, "days", "f0", "D")); + json.Append("]}"); + return json.ToString(); + } + + public string GetExtraTemp() + { + var json = new StringBuilder("{\"data\":[", 1024); + + for (int sensor = 1; sensor < 11; sensor++) + { + json.Append("[\""); + json.Append(cumulus.ExtraTempCaptions[sensor]); + json.Append("\",\""); + json.Append(ExtraTemp[sensor].ToString(cumulus.TempFormat)); + json.Append("\",\"°"); + json.Append(cumulus.Units.TempText[1].ToString()); + json.Append("\"]"); + + if (sensor < 10) + { + json.Append(","); + } + } + + json.Append("]}"); + return json.ToString(); + } + + public string GetUserTemp() + { + var json = new StringBuilder("{\"data\":[", 1024); + + for (int sensor = 1; sensor < 9; sensor++) + { + json.Append("[\""); + json.Append(cumulus.UserTempCaptions[sensor]); + json.Append("\",\""); + json.Append(UserTemp[sensor].ToString(cumulus.TempFormat)); + json.Append("\",\"°"); + json.Append(cumulus.Units.TempText[1].ToString()); + json.Append("\"]"); + + if (sensor < 8) + { + json.Append(","); + } + } + + json.Append("]}"); + return json.ToString(); + } + + public string GetExtraHum() + { + var json = new StringBuilder("{\"data\":[", 1024); + + for (int sensor = 1; sensor < 11; sensor++) + { + json.Append("[\""); + json.Append(cumulus.ExtraHumCaptions[sensor]); + json.Append("\",\""); + json.Append(ExtraHum[sensor].ToString(cumulus.HumFormat)); + json.Append("\",\"%\"]"); + + if (sensor < 10) + { + json.Append(","); + } + } + + json.Append("]}"); + return json.ToString(); + } + + public string GetExtraDew() + { + var json = new StringBuilder("{\"data\":[", 1024); + + for (int sensor = 1; sensor < 11; sensor++) + { + json.Append("[\""); + json.Append(cumulus.ExtraDPCaptions[sensor]); + json.Append("\",\""); + json.Append(ExtraDewPoint[sensor].ToString(cumulus.TempFormat)); + json.Append("\",\"°"); + json.Append(cumulus.Units.TempText[1].ToString()); + json.Append("\"]"); + + if (sensor < 10) + { + json.Append(","); + } + } + + json.Append("]}"); + return json.ToString(); + } + + public string GetSoilTemp() + { + var json = new StringBuilder("{\"data\":[", 2048); + + json.Append($"[\"{cumulus.SoilTempCaptions[1]}\",\"{SoilTemp1.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); + json.Append($"[\"{cumulus.SoilTempCaptions[2]}\",\"{SoilTemp2.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); + json.Append($"[\"{cumulus.SoilTempCaptions[3]}\",\"{SoilTemp3.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); + json.Append($"[\"{cumulus.SoilTempCaptions[4]}\",\"{SoilTemp4.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); + json.Append($"[\"{cumulus.SoilTempCaptions[5]}\",\"{SoilTemp5.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); + json.Append($"[\"{cumulus.SoilTempCaptions[6]}\",\"{SoilTemp6.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); + json.Append($"[\"{cumulus.SoilTempCaptions[7]}\",\"{SoilTemp7.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); + json.Append($"[\"{cumulus.SoilTempCaptions[8]}\",\"{SoilTemp8.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); + json.Append($"[\"{cumulus.SoilTempCaptions[9]}\",\"{SoilTemp9.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); + json.Append($"[\"{cumulus.SoilTempCaptions[10]}\",\"{SoilTemp10.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); + json.Append($"[\"{cumulus.SoilTempCaptions[11]}\",\"{SoilTemp11.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); + json.Append($"[\"{cumulus.SoilTempCaptions[12]}\",\"{SoilTemp12.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); + json.Append($"[\"{cumulus.SoilTempCaptions[13]}\",\"{SoilTemp13.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); + json.Append($"[\"{cumulus.SoilTempCaptions[14]}\",\"{SoilTemp14.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); + json.Append($"[\"{cumulus.SoilTempCaptions[15]}\",\"{SoilTemp15.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); + json.Append($"[\"{cumulus.SoilTempCaptions[16]}\",\"{SoilTemp16.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"]"); + json.Append("]}"); + return json.ToString(); + } + + public string GetSoilMoisture() + { + var json = new StringBuilder("{\"data\":[", 1024); + + json.Append($"[\"{cumulus.SoilMoistureCaptions[1]}\",\"{SoilMoisture1:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); + json.Append($"[\"{cumulus.SoilMoistureCaptions[2]}\",\"{SoilMoisture2:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); + json.Append($"[\"{cumulus.SoilMoistureCaptions[3]}\",\"{SoilMoisture3:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); + json.Append($"[\"{cumulus.SoilMoistureCaptions[4]}\",\"{SoilMoisture4:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); + json.Append($"[\"{cumulus.SoilMoistureCaptions[5]}\",\"{SoilMoisture5:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); + json.Append($"[\"{cumulus.SoilMoistureCaptions[6]}\",\"{SoilMoisture6:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); + json.Append($"[\"{cumulus.SoilMoistureCaptions[7]}\",\"{SoilMoisture7:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); + json.Append($"[\"{cumulus.SoilMoistureCaptions[8]}\",\"{SoilMoisture8:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); + json.Append($"[\"{cumulus.SoilMoistureCaptions[9]}\",\"{SoilMoisture9:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); + json.Append($"[\"{cumulus.SoilMoistureCaptions[10]}\",\"{SoilMoisture10:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); + json.Append($"[\"{cumulus.SoilMoistureCaptions[11]}\",\"{SoilMoisture11:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); + json.Append($"[\"{cumulus.SoilMoistureCaptions[12]}\",\"{SoilMoisture12:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); + json.Append($"[\"{cumulus.SoilMoistureCaptions[13]}\",\"{SoilMoisture13:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); + json.Append($"[\"{cumulus.SoilMoistureCaptions[14]}\",\"{SoilMoisture14:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); + json.Append($"[\"{cumulus.SoilMoistureCaptions[15]}\",\"{SoilMoisture15:F0}\",\"{cumulus.SoilMoistureUnitText}\"],"); + json.Append($"[\"{cumulus.SoilMoistureCaptions[16]}\",\"{SoilMoisture16:F0}\",\"{cumulus.SoilMoistureUnitText}\"]"); + json.Append("]}"); + return json.ToString(); + } + + public string GetAirQuality() + { + var json = new StringBuilder("{\"data\":[", 1024); + + json.Append($"[\"{cumulus.AirQualityCaptions[1]}\",\"{AirQuality1:F1}\",\"{cumulus.AirQualityUnitText}\"],"); + json.Append($"[\"{cumulus.AirQualityCaptions[2]}\",\"{AirQuality2:F1}\",\"{cumulus.AirQualityUnitText}\"],"); + json.Append($"[\"{cumulus.AirQualityCaptions[3]}\",\"{AirQuality3:F1}\",\"{cumulus.AirQualityUnitText}\"],"); + json.Append($"[\"{cumulus.AirQualityCaptions[4]}\",\"{AirQuality4:F1}\",\"{cumulus.AirQualityUnitText}\"],"); + json.Append($"[\"{cumulus.AirQualityAvgCaptions[1]}\",\"{AirQualityAvg1:F1}\",\"{cumulus.AirQualityUnitText}\"],"); + json.Append($"[\"{cumulus.AirQualityAvgCaptions[2]}\",\"{AirQualityAvg2:F1}\",\"{cumulus.AirQualityUnitText}\"],"); + json.Append($"[\"{cumulus.AirQualityAvgCaptions[3]}\",\"{AirQualityAvg3:F1}\",\"{cumulus.AirQualityUnitText}\"],"); + json.Append($"[\"{cumulus.AirQualityAvgCaptions[4]}\",\"{AirQualityAvg4:F1}\",\"{cumulus.AirQualityUnitText}\"]"); + json.Append("]}"); + return json.ToString(); + } + + public string GetCO2sensor() + { + var json = new StringBuilder("{\"data\":[", 1024); + + json.Append($"[\"{cumulus.CO2_CurrentCaption}\",\"{CO2}\",\"{cumulus.CO2UnitText}\"],"); + json.Append($"[\"{cumulus.CO2_24HourCaption}\",\"{CO2_24h}\",\"{cumulus.CO2UnitText}\"],"); + json.Append($"[\"{cumulus.CO2_pm2p5Caption}\",\"{CO2_pm2p5:F1}\",\"{cumulus.AirQualityUnitText}\"],"); + json.Append($"[\"{cumulus.CO2_pm2p5_24hrCaption}\",\"{CO2_pm2p5_24h:F1}\",\"{cumulus.AirQualityUnitText}\"],"); + json.Append($"[\"{cumulus.CO2_pm10Caption}\",\"{CO2_pm10:F1}\",\"{cumulus.AirQualityUnitText}\"],"); + json.Append($"[\"{cumulus.CO2_pm10_24hrCaption}\",\"{CO2_pm10_24h:F1}\",\"{cumulus.AirQualityUnitText}\"]"); + json.Append("]}"); + return json.ToString(); + } + + public string GetLightning() + { + var json = new StringBuilder("{\"data\":[", 256); + + json.Append($"[\"Distance to last strike\",\"{LightningDistance.ToString(cumulus.WindRunFormat)}\",\"{cumulus.Units.WindRunText}\"],"); + json.Append($"[\"Time of last strike\",\"{LightningTime}\",\"\"],"); + json.Append($"[\"Number of strikes today\",\"{LightningStrikesToday}\",\"\"]"); + json.Append("]}"); + return json.ToString(); + } + + public string GetLeaf() + { + var json = new StringBuilder("{\"data\":[", 256); + + json.Append($"[\"{cumulus.LeafTempCaptions[1]}\",\"{LeafTemp1.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); + json.Append($"[\"{cumulus.LeafTempCaptions[2]}\",\"{LeafTemp2.ToString(cumulus.TempFormat)}\",\"°{cumulus.Units.TempText[1]}\"],"); + json.Append($"[\"{cumulus.LeafWetnessCaptions[1]}\",\"{LeafWetness1}\",\" \"],"); + json.Append($"[\"{cumulus.LeafWetnessCaptions[2]}\",\"{LeafWetness2}\",\" \"]"); + json.Append("]}"); + return json.ToString(); + } + + public string GetLeaf4() + { + var json = new StringBuilder("{\"data\":[", 256); + + json.Append($"[\"{cumulus.LeafTempCaptions[1]}\",\"{LeafTemp1.ToString(cumulus.TempFormat)} °{cumulus.Units.TempText[1]}\",\"{LeafWetness1}\"],"); + json.Append($"[\"{cumulus.LeafTempCaptions[2]}\",\"{LeafTemp2.ToString(cumulus.TempFormat)} °{cumulus.Units.TempText[1]}\",\"{LeafWetness2}\"],"); + json.Append($"[\"{cumulus.LeafWetnessCaptions[1]}\",\"{LeafWetness1}\",\" \"],"); + json.Append($"[\"{cumulus.LeafWetnessCaptions[2]}\",\"{LeafWetness2}\",\" \"],"); + json.Append($"[\"{cumulus.LeafWetnessCaptions[3]}\",\"{LeafWetness3}\",\" \"],"); + json.Append($"[\"{cumulus.LeafWetnessCaptions[4]}\",\"{LeafWetness4}\",\" \"]"); + json.Append("]}"); + return json.ToString(); + } + + public string GetLeaf8() + { + var json = new StringBuilder("{\"data\":[", 256); + + json.Append($"[\"{cumulus.LeafWetnessCaptions[1]}\",\"{LeafWetness1}\",\" \"],"); + json.Append($"[\"{cumulus.LeafWetnessCaptions[2]}\",\"{LeafWetness2}\",\" \"],"); + json.Append($"[\"{cumulus.LeafWetnessCaptions[3]}\",\"{LeafWetness3}\",\" \"],"); + json.Append($"[\"{cumulus.LeafWetnessCaptions[4]}\",\"{LeafWetness4}\",\" \"],"); + json.Append($"[\"{cumulus.LeafWetnessCaptions[5]}\",\"{LeafWetness5}\",\" \"],"); + json.Append($"[\"{cumulus.LeafWetnessCaptions[6]}\",\"{LeafWetness6}\",\" \"],"); + json.Append($"[\"{cumulus.LeafWetnessCaptions[7]}\",\"{LeafWetness7}\",\" \"],"); + json.Append($"[\"{cumulus.LeafWetnessCaptions[8]}\",\"{LeafWetness8}\",\" \"]"); + json.Append("]}"); + return json.ToString(); + } + + + public string GetAirLinkCountsOut() + { + var json = new StringBuilder("{\"data\":[", 256); + if (cumulus.airLinkOut != null) + { + json.Append($"[\"1 μm\",\"{cumulus.airLinkDataOut.pm1:F1}\",\"--\",\"--\",\"--\",\"--\"],"); + json.Append($"[\"2.5 μm\",\"{cumulus.airLinkDataOut.pm2p5:F1}\",\"{cumulus.airLinkDataOut.pm2p5_1hr:F1}\",\"{cumulus.airLinkDataOut.pm2p5_3hr:F1}\",\"{cumulus.airLinkDataOut.pm2p5_24hr:F1}\",\"{cumulus.airLinkDataOut.pm2p5_nowcast:F1}\"],"); + json.Append($"[\"10 μm\",\"{cumulus.airLinkDataOut.pm10:F1}\",\"{cumulus.airLinkDataOut.pm10_1hr:F1}\",\"{cumulus.airLinkDataOut.pm10_3hr:F1}\",\"{cumulus.airLinkDataOut.pm10_24hr:F1}\",\"{cumulus.airLinkDataOut.pm10_nowcast:F1}\"]"); + } + else + { + json.Append("[\"1 μm\",\"--\",\"--\",\"--\",\"--\",\"--\"],"); + json.Append("[\"2.5 μm\",\"--\",\"--\",\"--\",\"--\",\"--\"],"); + json.Append("[\"10 μm\",\"--\",\"--\",\"--\",\"--\",\"--\"]"); + } + json.Append("]}"); + return json.ToString(); + } + + public string GetAirLinkAqiOut() + { + var json = new StringBuilder("{\"data\":[", 256); + if (cumulus.airLinkOut != null) + { + json.Append($"[\"2.5 μm\",\"{cumulus.airLinkDataOut.aqiPm2p5:F1}\",\"{cumulus.airLinkDataOut.aqiPm2p5_1hr:F1}\",\"{cumulus.airLinkDataOut.aqiPm2p5_3hr:F1}\",\"{cumulus.airLinkDataOut.aqiPm2p5_24hr:F1}\",\"{cumulus.airLinkDataOut.aqiPm2p5_nowcast:F1}\"],"); + json.Append($"[\"10 μm\",\"{cumulus.airLinkDataOut.aqiPm10:F1}\",\"{cumulus.airLinkDataOut.aqiPm10_1hr:F1}\",\"{cumulus.airLinkDataOut.aqiPm10_3hr:F1}\",\"{cumulus.airLinkDataOut.aqiPm10_24hr:F1}\",\"{cumulus.airLinkDataOut.aqiPm10_nowcast:F1}\"]"); + } + else + { + json.Append("[\"2.5 μm\",\"--\",\"--\",\"--\",\"--\",\"--\"],"); + json.Append("[\"10 μm\",\"--\",\"--\",\"--\",\"--\",\"--\"]"); + } + json.Append("]}"); + return json.ToString(); + } + + public string GetAirLinkPctOut() + { + var json = new StringBuilder("{\"data\":[", 256); + if (cumulus.airLinkOut != null) + { + json.Append($"[\"All sizes\",\"--\",\"{cumulus.airLinkDataOut.pct_1hr}%\",\"{cumulus.airLinkDataOut.pct_3hr}%\",\"{cumulus.airLinkDataOut.pct_24hr}%\",\"{cumulus.airLinkDataOut.pct_nowcast}%\"]"); + } + else + { + json.Append("[\"All sizes\",\"--\",\"--\",\"--\",\"--\",\"--\"]"); + } + json.Append("]}"); + return json.ToString(); + } + + public string GetAirLinkCountsIn() + { + var json = new StringBuilder("{\"data\":[", 256); + if (cumulus.airLinkIn != null) + { + json.Append($"[\"1 μm\",\"{cumulus.airLinkDataIn.pm1:F1}\",\"--\",\"--\",\"--\",\"--\"],"); + json.Append($"[\"2.5 μm\",\"{cumulus.airLinkDataIn.pm2p5:F1}\",\"{cumulus.airLinkDataIn.pm2p5_1hr:F1}\",\"{cumulus.airLinkDataIn.pm2p5_3hr:F1}\",\"{cumulus.airLinkDataIn.pm2p5_24hr:F1}\",\"{cumulus.airLinkDataIn.pm2p5_nowcast:F1}\"],"); + json.Append($"[\"10 μm\",\"{cumulus.airLinkDataIn.pm10:F1}\",\"{cumulus.airLinkDataIn.pm10_1hr:F1}\",\"{cumulus.airLinkDataIn.pm10_3hr:F1}\",\"{cumulus.airLinkDataIn.pm10_24hr:F1}\",\"{cumulus.airLinkDataIn.pm10_nowcast:F1}\"]"); + } + else + { + json.Append("[\"1 μm\",\"--\",\"--\",\"--\",\"--\",\"--\"],"); + json.Append("[\"2.5 μm\",\"--\",\"--\",\"--\",\"--\",\"--\"],"); + json.Append("[\"10 μm\",\"--\",\"--\",\"--\",\"--\",\"--\"]"); + } + json.Append("]}"); + return json.ToString(); + } + + public string GetAirLinkAqiIn() + { + var json = new StringBuilder("{\"data\":[", 256); + if (cumulus.airLinkIn != null) + { + json.Append($"[\"2.5 μm\",\"{cumulus.airLinkDataIn.aqiPm2p5:F1}\",\"{cumulus.airLinkDataIn.aqiPm2p5_1hr:F1}\",\"{cumulus.airLinkDataIn.aqiPm2p5_3hr:F1}\",\"{cumulus.airLinkDataIn.aqiPm2p5_24hr:F1}\",\"{cumulus.airLinkDataIn.aqiPm2p5_nowcast:F1}\"],"); + json.Append($"[\"10 μm\",\"{cumulus.airLinkDataIn.aqiPm10:F1}\",\"{cumulus.airLinkDataIn.aqiPm10_1hr:F1}\",\"{cumulus.airLinkDataIn.aqiPm10_3hr:F1}\",\"{cumulus.airLinkDataIn.aqiPm10_24hr:F1}\",\"{cumulus.airLinkDataIn.aqiPm10_nowcast:F1}\"]"); + } + else + { + json.Append("[\"2.5 μm\",\"--\",\"--\",\"--\",\"--\",\"--\"],"); + json.Append("[\"10 μm\",\"--\",\"--\",\"--\",\"--\",\"--\"]"); + } + json.Append("]}"); + return json.ToString(); + } + + public string GetAirLinkPctIn() + { + var json = new StringBuilder("{\"data\":[", 256); + if (cumulus.airLinkIn != null) + { + json.Append($"[\"All sizes\",\"--\",\"{cumulus.airLinkDataIn.pct_1hr}%\",\"{cumulus.airLinkDataIn.pct_3hr}%\",\"{cumulus.airLinkDataIn.pct_24hr}%\",\"{cumulus.airLinkDataIn.pct_nowcast}%\"]"); + } + else + { + json.Append("[\"All sizes\",\"--\",\"--\",\"--\",\"--\",\"--\"]"); + } + json.Append("]}"); + return json.ToString(); + } + + /* + private string extrajsonformat(int item, double value, DateTime timestamp, string unit, string valueformat, string dateformat) + { + return "[\"" + alltimedescs[item] + "\",\"" + value.ToString(valueformat) + " " + unit + "\",\"" + timestamp.ToString(dateformat) + "\"]"; + } + */ + + // The Today/Yesterday data is in the form: + // Name, today value + units, today time, yesterday value + units, yesterday time + // It's used to automatically populate a DataTables table in the browser interface + public string GetTodayYestTemp() + { + var json = new StringBuilder("{\"data\":[", 2048); + var sepStr = "\",\""; + var closeStr = "\"],"; + var tempUnitStr = " °" + cumulus.Units.TempText[1].ToString() + sepStr; + + json.Append("[\"High Temperature\",\""); + json.Append(HiLoToday.HighTemp.ToString(cumulus.TempFormat)); + json.Append(tempUnitStr); + json.Append(HiLoToday.HighTempTime.ToShortTimeString()); + json.Append(sepStr); + json.Append(HiLoYest.HighTemp.ToString(cumulus.TempFormat)); + json.Append(tempUnitStr); + json.Append(HiLoYest.HighTempTime.ToShortTimeString()); + json.Append(closeStr); + + json.Append("[\"Low Temperature\",\""); + json.Append(HiLoToday.LowTemp.ToString(cumulus.TempFormat)); + json.Append(tempUnitStr); + json.Append(HiLoToday.LowTempTime.ToShortTimeString()); + json.Append(sepStr); + json.Append(HiLoYest.LowTemp.ToString(cumulus.TempFormat)); + json.Append(tempUnitStr); + json.Append(HiLoYest.LowTempTime.ToShortTimeString()); + json.Append(closeStr); + + json.Append("[\"Temperature Range\",\""); + json.Append((HiLoToday.HighTemp - HiLoToday.LowTemp).ToString(cumulus.TempFormat)); + json.Append(tempUnitStr); + json.Append(" \",\""); + json.Append((HiLoYest.HighTemp - HiLoYest.LowTemp).ToString(cumulus.TempFormat)); + json.Append(tempUnitStr); + json.Append(" \"],"); + + json.Append("[\"High Apparent Temperature\",\""); + json.Append(HiLoToday.HighAppTemp.ToString(cumulus.TempFormat)); + json.Append(tempUnitStr); + json.Append(HiLoToday.HighAppTempTime.ToShortTimeString()); + json.Append(sepStr); + json.Append(HiLoYest.HighAppTemp.ToString(cumulus.TempFormat)); + json.Append(tempUnitStr); + json.Append(HiLoYest.HighAppTempTime.ToShortTimeString()); + json.Append(closeStr); + + json.Append("[\"Low Apparent Temperature\",\""); + json.Append(HiLoToday.LowAppTemp.ToString(cumulus.TempFormat)); + json.Append(tempUnitStr); + json.Append(HiLoToday.LowAppTempTime.ToShortTimeString()); + json.Append(sepStr); + json.Append(HiLoYest.LowAppTemp.ToString(cumulus.TempFormat)); + json.Append(tempUnitStr); + json.Append(HiLoYest.LowAppTempTime.ToShortTimeString()); + json.Append(closeStr); + + json.Append("[\"High Feels Like\",\""); + json.Append(HiLoToday.HighFeelsLike.ToString(cumulus.TempFormat)); + json.Append(tempUnitStr); + json.Append(HiLoToday.HighFeelsLikeTime.ToShortTimeString()); + json.Append(sepStr); + json.Append(HiLoYest.HighFeelsLike.ToString(cumulus.TempFormat)); + json.Append(tempUnitStr); + json.Append(HiLoYest.HighFeelsLikeTime.ToShortTimeString()); + json.Append(closeStr); + + json.Append("[\"Low Feels Like\",\""); + json.Append(HiLoToday.LowFeelsLike.ToString(cumulus.TempFormat)); + json.Append(tempUnitStr); + json.Append(HiLoToday.LowFeelsLikeTime.ToShortTimeString()); + json.Append(sepStr); + json.Append(HiLoYest.LowFeelsLike.ToString(cumulus.TempFormat)); + json.Append(tempUnitStr); + json.Append(HiLoYest.LowFeelsLikeTime.ToShortTimeString()); + json.Append(closeStr); + + json.Append("[\"High Humidex\",\""); + json.Append(HiLoToday.HighHumidex.ToString(cumulus.TempFormat)); + json.Append("\",\""); + json.Append(HiLoToday.HighHumidexTime.ToShortTimeString()); + json.Append(sepStr); + json.Append(HiLoYest.HighHumidex.ToString(cumulus.TempFormat)); + json.Append("\",\""); + json.Append(HiLoYest.HighHumidexTime.ToShortTimeString()); + json.Append(closeStr); + json.Append("[\"High Dew Point\",\""); + json.Append(HiLoToday.HighDewPoint.ToString(cumulus.TempFormat)); + json.Append(tempUnitStr); + json.Append(HiLoToday.HighDewPointTime.ToShortTimeString()); + json.Append(sepStr); + json.Append(HiLoYest.HighDewPoint.ToString(cumulus.TempFormat)); + json.Append(tempUnitStr); + json.Append(HiLoYest.HighDewPointTime.ToShortTimeString()); + json.Append(closeStr); + + json.Append("[\"Low Dew Point\",\""); + json.Append(HiLoToday.LowDewPoint.ToString(cumulus.TempFormat)); + json.Append(tempUnitStr); + json.Append(HiLoToday.LowDewPointTime.ToShortTimeString()); + json.Append(sepStr); + json.Append(HiLoYest.LowDewPoint.ToString(cumulus.TempFormat)); + json.Append(tempUnitStr); + json.Append(HiLoYest.LowDewPointTime.ToShortTimeString()); + json.Append(closeStr); + + json.Append("[\"Low Wind Chill\",\""); + json.Append(HiLoToday.LowWindChill.ToString(cumulus.TempFormat)); + json.Append(tempUnitStr); + json.Append(HiLoToday.LowWindChillTime.ToShortTimeString()); + json.Append(sepStr); + json.Append(HiLoYest.LowWindChill.ToString(cumulus.TempFormat)); + json.Append(tempUnitStr); + json.Append(HiLoYest.LowWindChillTime.ToShortTimeString()); + json.Append(closeStr); + + json.Append("[\"High Heat Index\",\""); + json.Append(HiLoToday.HighHeatIndex.ToString(cumulus.TempFormat)); + json.Append(tempUnitStr); + json.Append(HiLoToday.HighHeatIndexTime.ToShortTimeString()); + json.Append(sepStr); + json.Append(HiLoYest.HighHeatIndex.ToString(cumulus.TempFormat)); + json.Append(tempUnitStr); + json.Append(HiLoYest.HighHeatIndexTime.ToShortTimeString()); + json.Append("\"]"); + + json.Append("]}"); + return json.ToString(); + } + + public string GetTodayYestHum() + { + var json = new StringBuilder("{\"data\":[", 512); + var sepStr = "\",\""; + var unitStr = " %" + sepStr; + + json.Append("[\"High Humidity\",\""); + json.Append(HiLoToday.HighHumidity.ToString(cumulus.HumFormat)); + json.Append(unitStr); + json.Append(HiLoToday.HighHumidityTime.ToShortTimeString()); + json.Append(sepStr); + json.Append(HiLoYest.HighHumidity.ToString(cumulus.HumFormat)); + json.Append(unitStr); + json.Append(HiLoYest.HighHumidityTime.ToShortTimeString()); + json.Append("\"],"); + + json.Append("[\"Low Humidity\",\""); + json.Append(HiLoToday.LowHumidity.ToString(cumulus.HumFormat)); + json.Append(unitStr); + json.Append(HiLoToday.LowHumidityTime.ToShortTimeString()); + json.Append(sepStr); + json.Append(HiLoYest.LowHumidity.ToString(cumulus.HumFormat)); + json.Append(unitStr); + json.Append(HiLoYest.LowHumidityTime.ToShortTimeString()); + json.Append("\"]"); + + json.Append("]}"); + return json.ToString(); + } + + public string GetTodayYestRain() + { + var json = new StringBuilder("{\"data\":[", 512); + var sepStr = "\",\""; + var unitStr = " " + cumulus.Units.RainText; + + json.Append("[\"Total Rain\",\""); + json.Append(RainToday.ToString(cumulus.RainFormat)); + json.Append(unitStr); + json.Append(sepStr); + json.Append(" "); + json.Append(sepStr); + json.Append(RainYesterday.ToString(cumulus.RainFormat)); + json.Append(unitStr); + json.Append(sepStr); + json.Append(" "); + json.Append("\"],"); + + json.Append("[\"High Rain Rate\",\""); + json.Append(HiLoToday.HighRainRate.ToString(cumulus.RainFormat)); + json.Append(unitStr + "/hr"); + json.Append(sepStr); + json.Append(HiLoToday.HighRainRateTime.ToShortTimeString()); + json.Append(sepStr); + json.Append(HiLoYest.HighRainRate.ToString(cumulus.RainFormat)); + json.Append(unitStr + "/hr"); + json.Append(sepStr); + json.Append(HiLoYest.HighRainRateTime.ToShortTimeString()); + json.Append("\"],"); + + json.Append("[\"High Hourly Rain\",\""); + json.Append(HiLoToday.HighHourlyRain.ToString(cumulus.RainFormat)); + json.Append(unitStr); + json.Append(sepStr); + json.Append(HiLoToday.HighHourlyRainTime.ToShortTimeString()); + json.Append(sepStr); + json.Append(HiLoYest.HighHourlyRain.ToString(cumulus.RainFormat)); + json.Append(unitStr); + json.Append(sepStr); + json.Append(HiLoYest.HighHourlyRainTime.ToShortTimeString()); + json.Append("\"]"); + + json.Append("]}"); + return json.ToString(); + } + + public string GetTodayYestWind() + { + var json = new StringBuilder("{\"data\":[", 512); + var sepStr = "\",\""; + + json.Append("[\"Highest Gust\",\""); + json.Append(HiLoToday.HighGust.ToString(cumulus.WindFormat)); + json.Append(" " + cumulus.Units.WindText); + json.Append(sepStr); + json.Append(HiLoToday.HighGustTime.ToShortTimeString()); + json.Append(sepStr); + json.Append(HiLoYest.HighGust.ToString(cumulus.WindFormat)); + json.Append(" " + cumulus.Units.WindText); + json.Append(sepStr); + json.Append(HiLoYest.HighGustTime.ToShortTimeString()); + json.Append("\"],"); + + json.Append("[\"Highest Speed\",\""); + json.Append(HiLoToday.HighWind.ToString(cumulus.WindAvgFormat)); + json.Append(" " + cumulus.Units.WindText); + json.Append(sepStr); + json.Append(HiLoToday.HighWindTime.ToShortTimeString()); + json.Append(sepStr); + json.Append(HiLoYest.HighWind.ToString(cumulus.WindAvgFormat)); + json.Append(" " + cumulus.Units.WindText); + json.Append(sepStr); + json.Append(HiLoYest.HighWindTime.ToShortTimeString()); + json.Append("\"],"); + + json.Append("[\"Wind Run\",\""); + json.Append(WindRunToday.ToString(cumulus.WindRunFormat)); + json.Append(" " + cumulus.Units.WindRunText); + json.Append(sepStr); + json.Append(" "); + json.Append(sepStr); + json.Append(YesterdayWindRun.ToString(cumulus.WindRunFormat)); + json.Append(" " + cumulus.Units.WindRunText); + json.Append(sepStr); + json.Append(" "); + json.Append("\"],"); + + json.Append("[\"Dominant Direction\",\""); + json.Append(DominantWindBearing.ToString("F0")); + json.Append(" ° " + CompassPoint(DominantWindBearing)); + json.Append(sepStr); + json.Append(" "); + json.Append(sepStr); + json.Append(YestDominantWindBearing.ToString("F0")); + json.Append(" ° " + CompassPoint(YestDominantWindBearing)); + json.Append(sepStr); + json.Append(" "); + json.Append("\"]"); + + json.Append("]}"); + return json.ToString(); + } + + public string GetTodayYestPressure() + { + var json = new StringBuilder("{\"data\":[", 512); + var sepStr = "\",\""; + var unitStr = " " + cumulus.Units.PressText; + + json.Append("[\"High Pressure\",\""); + json.Append(HiLoToday.HighPress.ToString(cumulus.PressFormat)); + json.Append(unitStr); + json.Append(sepStr); + json.Append(HiLoToday.HighPressTime.ToShortTimeString()); + json.Append(sepStr); + json.Append(HiLoYest.HighPress.ToString(cumulus.PressFormat)); + json.Append(unitStr); + json.Append(sepStr); + json.Append(HiLoYest.HighPressTime.ToShortTimeString()); + json.Append("\"],"); + + json.Append("[\"Low Pressure\",\""); + json.Append(HiLoToday.LowPress.ToString(cumulus.PressFormat)); + json.Append(unitStr); + json.Append(sepStr); + json.Append(HiLoToday.LowPressTime.ToShortTimeString()); + json.Append(sepStr); + json.Append(HiLoYest.LowPress.ToString(cumulus.PressFormat)); + json.Append(unitStr); + json.Append(sepStr); + json.Append(HiLoYest.LowPressTime.ToShortTimeString()); + json.Append("\"]"); + + json.Append("]}"); + return json.ToString(); + } + + public string GetTodayYestSolar() + { + var json = new StringBuilder("{\"data\":[", 512); + var sepStr = "\",\""; + + json.Append("[\"High Solar Radiation\",\""); + json.Append(HiLoToday.HighSolar.ToString("F0")); + json.Append(" W/m2"); + json.Append(sepStr); + json.Append(HiLoToday.HighSolarTime.ToShortTimeString()); + json.Append(sepStr); + json.Append(HiLoYest.HighSolar.ToString("F0")); + json.Append(" W/m2"); + json.Append(sepStr); + json.Append(HiLoYest.HighSolarTime.ToShortTimeString()); + json.Append("\"],"); + + json.Append("[\"Hours of Sunshine\",\""); + json.Append(SunshineHours.ToString(cumulus.SunFormat)); + json.Append(" hrs"); + json.Append(sepStr); + json.Append(" "); + json.Append(sepStr); + json.Append(YestSunshineHours.ToString(cumulus.SunFormat)); + json.Append(" hrs"); + json.Append(sepStr); + json.Append(" "); + json.Append("\"]"); + + json.Append("]}"); + return json.ToString(); + } + + /// + /// Return lines from dayfile.txt in json format + /// + /// + /// + /// + /// JSON encoded section of the dayfile + public string GetDayfile(string draw, int start, int length) + { + try + { + //var total = File.ReadLines(cumulus.DayFile).Count(); + var allLines = File.ReadAllLines(cumulus.DayFileName); + var total = allLines.Length; + var lines = allLines.Skip(start).Take(length); + + var json = new StringBuilder(350 * lines.Count()); + + json.Append("{\"draw\":" + draw); + json.Append(",\"recordsTotal\":" + total); + json.Append(",\"recordsFiltered\":" + total); + json.Append(",\"data\":["); + + //var lines = File.ReadLines(cumulus.DayFile).Skip(start).Take(length); + + var lineNum = start + 1; // Start is zero relative + + foreach (var line in lines) + { + var fields = line.Split(Convert.ToChar(cumulus.ListSeparator)); + var numFields = fields.Length; + json.Append($"[{lineNum++},"); + for (var i = 0; i < numFields; i++) + { + json.Append($"\"{fields[i]}\""); + if (i < fields.Length - 1) + { + json.Append(","); + } + } + + if (numFields < Cumulus.DayfileFields) + { + // insufficient fields, pad with empty fields + for (var i = numFields; i < Cumulus.DayfileFields; i++) + { + json.Append(",\"\""); + } + } + json.Append("],"); + } + + // trim last "," + json.Length--; + json.Append("]}"); + + return json.ToString(); + } + catch (Exception ex) + { + cumulus.LogMessage(ex.ToString()); + } + + return ""; + } + + internal string GetDiaryData(string date) + { + + StringBuilder json = new StringBuilder("{\"entry\":\"", 1024); + + var result = cumulus.DiaryDB.Query("select * from DiaryData where date(Timestamp,'utc') = ? order by Timestamp limit 1", date); + + if (result.Count > 0) + { + json.Append(result[0].entry + "\","); + json.Append("\"snowFalling\":"); + json.Append(result[0].snowFalling + ","); + json.Append("\"snowLying\":"); + json.Append(result[0].snowLying + ","); + json.Append("\"snowDepth\":\""); + json.Append(result[0].snowDepth); + json.Append("\"}"); + } + else + { + json.Append("\",\"snowFalling\":0,\"snowLying\":0,\"snowDepth\":\"\"}"); + } + + return json.ToString(); + } + + // Fetchs all days in the required month that have a diary entry + //internal string GetDiarySummary(string year, string month) + internal string GetDiarySummary() + { + var json = new StringBuilder(512); + //var result = cumulus.DiaryDB.Query("select Timestamp from DiaryData where strftime('%Y', Timestamp) = ? and strftime('%m', Timestamp) = ? order by Timestamp", year, month); + var result = cumulus.DiaryDB.Query("select Timestamp from DiaryData order by Timestamp"); + + if (result.Count > 0) + { + json.Append("{\"dates\":["); + for (int i = 0; i < result.Count; i++) + { + json.Append("\""); + json.Append(result[i].Timestamp.ToUniversalTime().ToString("yyy-MM-dd")); + json.Append("\","); + } + json.Length--; + json.Append("]}"); + } + else + { + json.Append("{\"dates\":[]}"); + } + + return json.ToString(); + } + + /// + /// Return lines from log file in json format + /// + /// + /// + /// + /// + /// + /// + public string GetLogfile(string date, string draw, int start, int length, bool extra) + { + try + { + // date will (hopefully) be in format "m-yyyy" or "mm-yyyy" + int month = Convert.ToInt32(date.Split('-')[0]); + int year = Convert.ToInt32(date.Split('-')[1]); + + // Get a timestamp, use 15th day to avoid wrap issues + var ts = new DateTime(year, month, 15); + + var logfile = extra ? cumulus.GetExtraLogFileName(ts) : cumulus.GetLogFileName(ts); + var numFields = extra ? Cumulus.NumExtraLogFileFields : Cumulus.NumLogFileFields; + + if (!File.Exists(logfile)) + { + cumulus.LogMessage($"GetLogFile: Error, file does not exist: {logfile}"); + return ""; + } + + var allLines = File.ReadAllLines(logfile); + var total = allLines.Length; + var lines = allLines.Skip(start).Take(length); + + //var total = File.ReadLines(logfile).Count(); + var json = new StringBuilder(220 * lines.Count()); + + json.Append("{\"draw\":"); + json.Append(draw); + json.Append(",\"recordsTotal\":"); + json.Append(total); + json.Append(",\"recordsFiltered\":"); + json.Append(total); + json.Append(",\"data\":["); + + //var lines = File.ReadLines(logfile).Skip(start).Take(length); + + var lineNum = start + 1; // Start is zero relative + + foreach (var line in lines) + { + var fields = line.Split(Convert.ToChar(cumulus.ListSeparator)); + json.Append($"[{lineNum++},"); + for (var i = 0; i < numFields; i++) + { + if (i < fields.Length) + { + // field exists + json.Append("\""); + json.Append(fields[i]); + json.Append("\""); + } + else + { + // add padding + json.Append("\" \""); + } + + if (i < numFields - 1) + { + json.Append(","); + } + } + json.Append("],"); + } + + // trim trailing "," + json.Length--; + json.Append("]}"); + + return json.ToString(); + } + catch (Exception ex) + { + cumulus.LogMessage(ex.ToString()); + } + + return ""; + } + + public string GetUnits() + { + return $"{{\"temp\":\"{cumulus.Units.TempText[1]}\",\"wind\":\"{cumulus.Units.WindText}\",\"rain\":\"{cumulus.Units.RainText}\",\"press\":\"{cumulus.Units.PressText}\"}}"; + } + + public string GetGraphConfig() + { + var json = new StringBuilder(200); + json.Append("{"); + json.Append($"\"temp\":{{\"units\":\"{cumulus.Units.TempText[1]}\",\"decimals\":{cumulus.TempDPlaces}}},"); + json.Append($"\"wind\":{{\"units\":\"{cumulus.Units.WindText}\",\"decimals\":{cumulus.WindAvgDPlaces},\"rununits\":\"{cumulus.Units.WindRunText}\"}},"); + json.Append($"\"rain\":{{\"units\":\"{cumulus.Units.RainText}\",\"decimals\":{cumulus.RainDPlaces}}},"); + json.Append($"\"press\":{{\"units\":\"{cumulus.Units.PressText}\",\"decimals\":{cumulus.PressDPlaces}}},"); + json.Append($"\"hum\":{{\"decimals\":{cumulus.HumDPlaces}}},"); + json.Append($"\"uv\":{{\"decimals\":{cumulus.UVDPlaces}}}"); + json.Append("}"); + return json.ToString(); + } + + public string GetAvailGraphData() + { + var json = new StringBuilder(200); + + // Temp values + json.Append("{\"Temperature\":["); + + if (cumulus.GraphOptions.TempVisible) + json.Append("\"Temperature\","); + + if (cumulus.GraphOptions.InTempVisible) + json.Append("\"Indoor Temp\","); + + if (cumulus.GraphOptions.HIVisible) + json.Append("\"Heat Index\","); + + if (cumulus.GraphOptions.DPVisible) + json.Append("\"Dew Point\","); + + if (cumulus.GraphOptions.WCVisible) + json.Append("\"Wind Chill\","); + + if (cumulus.GraphOptions.AppTempVisible) + json.Append("\"Apparent Temp\","); + + if (cumulus.GraphOptions.FeelsLikeVisible) + json.Append("\"Feels Like\","); + + //if (cumulus.GraphOptions.HumidexVisible) + // json.Append("\"Humidex\","); + + if (json[json.Length - 1] == ',') + json.Length--; + + // humidity values + json.Append("],\"Humidity\":["); + + if (cumulus.GraphOptions.OutHumVisible) + json.Append("\"Humidity\","); + + if (cumulus.GraphOptions.InHumVisible) + json.Append("\"Indoor Hum\","); + + if (json[json.Length - 1] == ',') + json.Length--; + + // fixed values + // pressure + json.Append("],\"Pressure\":[\"Pressure\"],"); + + // wind + json.Append("\"Wind\":[\"Wind Speed\",\"Wind Gust\",\"Wind Bearing\"],"); + + // rain + json.Append("\"Rain\":[\"Rainfall\",\"Rainfall Rate\"]"); + + if (cumulus.GraphOptions.DailyAvgTempVisible || cumulus.GraphOptions.DailyMaxTempVisible || cumulus.GraphOptions.DailyMinTempVisible) + { + json.Append(",\"DailyTemps\":["); + + if (cumulus.GraphOptions.DailyAvgTempVisible) + json.Append("\"AvgTemp\","); + if (cumulus.GraphOptions.DailyMaxTempVisible) + json.Append("\"MaxTemp\","); + if (cumulus.GraphOptions.DailyMinTempVisible) + json.Append("\"MinTemp\","); + + if (json[json.Length - 1] == ',') + json.Length--; + + json.Append("]"); + } + + + // solar values + if (cumulus.GraphOptions.SolarVisible || cumulus.GraphOptions.UVVisible) + { + json.Append(",\"Solar\":["); + + if (cumulus.GraphOptions.SolarVisible) + json.Append("\"Solar Rad\","); + + if (cumulus.GraphOptions.UVVisible) + json.Append("\"UV Index\","); + + if (json[json.Length - 1] == ',') + json.Length--; + + json.Append("]"); + } + + // Sunshine + if (cumulus.GraphOptions.SunshineVisible) + { + json.Append(",\"Sunshine\":[\"sunhours\"]"); + } + + // air quality + // Check if we are to generate AQ data at all. Only if a primary sensor is defined and it isn't the Indoor AirLink + if (cumulus.StationOptions.PrimaryAqSensor > (int)Cumulus.PrimaryAqSensor.Undefined + && cumulus.StationOptions.PrimaryAqSensor != (int)Cumulus.PrimaryAqSensor.AirLinkIndoor) + { + json.Append(",\"AirQuality\":["); + json.Append("\"PM 2.5\""); + + // Only the AirLink and Ecowitt CO2 servers provide PM10 values at the moment + if (cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.AirLinkOutdoor || + cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.AirLinkIndoor || + cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.EcowittCO2) + { + json.Append(",\"PM 10\""); + } + json.Append("]"); + } + + // Degree Days + if (cumulus.GraphOptions.GrowingDegreeDaysVisible1 || cumulus.GraphOptions.GrowingDegreeDaysVisible2) + { + json.Append(",\"DegreeDays\":["); + if (cumulus.GraphOptions.GrowingDegreeDaysVisible1) + json.Append("\"GDD1\","); + + if (cumulus.GraphOptions.GrowingDegreeDaysVisible2) + json.Append("\"GDD2\""); + + if (json[json.Length - 1] == ',') + json.Length--; + + json.Append("]"); + } + + // Temp Sum + if (cumulus.GraphOptions.TempSumVisible0 || cumulus.GraphOptions.TempSumVisible1 || cumulus.GraphOptions.TempSumVisible2) + { + json.Append(",\"TempSum\":["); + if (cumulus.GraphOptions.TempSumVisible0) + json.Append("\"Sum0\","); + if (cumulus.GraphOptions.TempSumVisible1) + json.Append("\"Sum1\","); + if (cumulus.GraphOptions.TempSumVisible2) + json.Append("\"Sum2\""); + + if (json[json.Length - 1] == ',') + json.Length--; + + json.Append("]"); + } + + json.Append("}"); + return json.ToString(); + } + + + public string GetSelectaChartOptions() + { + return JsonSerializer.SerializeToString(cumulus.SelectaChartOptions); + } + + + public string GetDailyRainGraphData() + { + var datefrom = DateTime.Now.AddDays(-cumulus.GraphDays - 1); + + var InvC = new CultureInfo(""); + StringBuilder sb = new StringBuilder("{\"dailyrain\":[", 10000); + + var data = DayFile.Where(rec => rec.Date >= datefrom).ToList(); + for (var i = 0; i < data.Count; i++) + { + sb.Append($"[{DateTimeToUnix(data[i].Date) * 1000},{data[i].TotalRain.ToString(cumulus.RainFormat, InvC)}]"); + + if (i < data.Count - 1) + sb.Append(","); + } + sb.Append("]}"); + return sb.ToString(); + } + + public string GetSunHoursGraphData() + { + var InvC = new CultureInfo(""); + StringBuilder sb = new StringBuilder("{", 10000); + if (cumulus.GraphOptions.SunshineVisible) + { + var datefrom = DateTime.Now.AddDays(-cumulus.GraphDays - 1); + var data = DayFile.Where(rec => rec.Date >= datefrom).ToList(); + + sb.Append("\"sunhours\":["); + for (var i = 0; i < data.Count; i++) + { + var sunhrs = data[i].SunShineHours >= 0 ? data[i].SunShineHours : 0; + sb.Append($"[{DateTimeToUnix(data[i].Date) * 1000},{sunhrs.ToString(cumulus.SunFormat, InvC)}]"); + + if (i < data.Count - 1) + sb.Append(","); + } + sb.Append("]"); + } + sb.Append("}"); + return sb.ToString(); + } + + public string GetDailyTempGraphData() + { + var InvC = new CultureInfo(""); + var datefrom = DateTime.Now.AddDays(-cumulus.GraphDays - 1); + var data = DayFile.Where(rec => rec.Date >= datefrom).ToList(); + var append = false; + StringBuilder sb = new StringBuilder("{"); + + if (cumulus.GraphOptions.DailyMinTempVisible) + { + sb.Append("\"mintemp\":["); + + for (var i = 0; i < data.Count; i++) + { + sb.Append($"[{DateTimeToUnix(data[i].Date) * 1000},{data[i].LowTemp.ToString(cumulus.TempFormat, InvC)}]"); + if (i < data.Count - 1) + sb.Append(","); + } + + sb.Append("]"); + append = true; + } + + if (cumulus.GraphOptions.DailyMaxTempVisible) + { + if (append) + sb.Append(","); + + sb.Append("\"maxtemp\":["); + + for (var i = 0; i < data.Count; i++) + { + sb.Append($"[{DateTimeToUnix(data[i].Date) * 1000},{data[i].HighTemp.ToString(cumulus.TempFormat, InvC)}]"); + if (i < data.Count - 1) + sb.Append(","); + } + + sb.Append("]"); + append = true; + } + + if (cumulus.GraphOptions.DailyAvgTempVisible) + { + if (append) + sb.Append(","); + + sb.Append("\"avgtemp\":["); + for (var i = 0; i < data.Count; i++) + { + sb.Append($"[{DateTimeToUnix(data[i].Date) * 1000},{data[i].AvgTemp.ToString(cumulus.TempFormat, InvC)}]"); + if (i < data.Count - 1) + sb.Append(","); + } + + sb.Append("]"); + } + + sb.Append("}"); + return sb.ToString(); + } + + public string GetAllDailyTempGraphData() + { + var InvC = new CultureInfo(""); + /* returns: + * { + * highgust:[[date1,val1],[date2,val2]...], + * mintemp:[[date1,val1],[date2,val2]...], + * etc + * } + */ + + StringBuilder sb = new StringBuilder("{"); + StringBuilder minTemp = new StringBuilder("["); + StringBuilder maxTemp = new StringBuilder("["); + StringBuilder avgTemp = new StringBuilder("["); + StringBuilder heatIdx = new StringBuilder("["); + StringBuilder maxApp = new StringBuilder("["); + StringBuilder minApp = new StringBuilder("["); + StringBuilder windChill = new StringBuilder("["); + StringBuilder maxDew = new StringBuilder("["); + StringBuilder minDew = new StringBuilder("["); + StringBuilder maxFeels = new StringBuilder("["); + StringBuilder minFeels = new StringBuilder("["); + StringBuilder humidex = new StringBuilder("["); + + // Read the day file list and extract the data from there + if (DayFile.Count() > 0) + { + var len = DayFile.Count() - 1; + + for (var i = 0; i < DayFile.Count(); i++) + { + var recDate = DateTimeToUnix(DayFile[i].Date) * 1000; + // lo temp + if (cumulus.GraphOptions.DailyMinTempVisible) + minTemp.Append($"[{recDate},{DayFile[i].LowTemp.ToString(cumulus.TempFormat, InvC)}]"); + // hi temp + if (cumulus.GraphOptions.DailyMaxTempVisible) + maxTemp.Append($"[{recDate},{DayFile[i].HighTemp.ToString(cumulus.TempFormat, InvC)}]"); + // avg temp + if (cumulus.GraphOptions.DailyAvgTempVisible) + avgTemp.Append($"[{recDate},{DayFile[i].AvgTemp.ToString(cumulus.TempFormat, InvC)}]"); + + if (i < len) + { + minTemp.Append(","); + maxTemp.Append(","); + avgTemp.Append(","); + } + + if (cumulus.GraphOptions.HIVisible) + { + // hi heat index + if (DayFile[i].HighHeatIndex > -999) + heatIdx.Append($"[{recDate},{DayFile[i].HighHeatIndex.ToString(cumulus.TempFormat, InvC)}]"); + else + heatIdx.Append($"[{recDate},null]"); + + if (i < len) + heatIdx.Append(","); + } + if (cumulus.GraphOptions.AppTempVisible) + { + // hi app temp + if (DayFile[i].HighAppTemp > -999) + maxApp.Append($"[{recDate},{DayFile[i].HighAppTemp.ToString(cumulus.TempFormat, InvC)}]"); + else + maxApp.Append($"[{recDate},null]"); + + // lo app temp + if (DayFile[i].LowAppTemp < 999) + minApp.Append($"[{recDate},{DayFile[i].LowAppTemp.ToString(cumulus.TempFormat, InvC)}]"); + else + minApp.Append($"[{recDate},null]"); + + if (i < len) + { + maxApp.Append(","); + minApp.Append(","); + } + } + // lo wind chill + if (cumulus.GraphOptions.WCVisible) + { + if (DayFile[i].LowWindChill < 999) + windChill.Append($"[{recDate},{DayFile[i].LowWindChill.ToString(cumulus.TempFormat, InvC)}]"); + else + windChill.Append($"[{recDate},null]"); + + if (i < len) + windChill.Append(","); + } + + if (cumulus.GraphOptions.DPVisible) + { + // hi dewpt + if (DayFile[i].HighDewPoint > -999) + maxDew.Append($"[{recDate},{DayFile[i].HighDewPoint.ToString(cumulus.TempFormat, InvC)}]"); + else + maxDew.Append($"[{recDate},null]"); + + // lo dewpt + if (DayFile[i].LowDewPoint < 999) + minDew.Append($"[{recDate},{DayFile[i].LowDewPoint.ToString(cumulus.TempFormat, InvC)}]"); + else + minDew.Append($"[{recDate},null]"); + + if (i < len) + { + maxDew.Append(","); + minDew.Append(","); + } + } + + if (cumulus.GraphOptions.FeelsLikeVisible) + { + // hi feels like + if (DayFile[i].HighFeelsLike > -999) + maxFeels.Append($"[{recDate},{DayFile[i].HighFeelsLike.ToString(cumulus.TempFormat, InvC)}]"); + else + maxFeels.Append($"[{recDate},null]"); + + // lo feels like + if (DayFile[i].LowFeelsLike < 999) + minFeels.Append($"[{recDate},{DayFile[i].LowFeelsLike.ToString(cumulus.TempFormat, InvC)}]"); + else + minFeels.Append($"[{recDate},null]"); + + if (i < len) + { + maxFeels.Append(","); + minFeels.Append(","); + } + } + + if (cumulus.GraphOptions.HumidexVisible) + { + // hi humidex + if (DayFile[i].HighHumidex > -999) + humidex.Append($"[{recDate},{DayFile[i].HighHumidex.ToString(cumulus.TempFormat, InvC)}]"); + else + humidex.Append($"[{recDate},null]"); + + if (i < len) + humidex.Append(","); + } + } + } + if (cumulus.GraphOptions.DailyMinTempVisible) + sb.Append("\"minTemp\":" + minTemp.ToString() + "],"); + if (cumulus.GraphOptions.DailyMaxTempVisible) + sb.Append("\"maxTemp\":" + maxTemp.ToString() + "],"); + if (cumulus.GraphOptions.DailyAvgTempVisible) + sb.Append("\"avgTemp\":" + avgTemp.ToString() + "],"); + if (cumulus.GraphOptions.HIVisible) + sb.Append("\"heatIndex\":" + heatIdx.ToString() + "],"); + if (cumulus.GraphOptions.AppTempVisible) + { + sb.Append("\"maxApp\":" + maxApp.ToString() + "],"); + sb.Append("\"minApp\":" + minApp.ToString() + "],"); + } + if (cumulus.GraphOptions.WCVisible) + sb.Append("\"windChill\":" + windChill.ToString() + "],"); + if (cumulus.GraphOptions.DPVisible) + { + sb.Append("\"maxDew\":" + maxDew.ToString() + "],"); + sb.Append("\"minDew\":" + minDew.ToString() + "],"); + } + if (cumulus.GraphOptions.FeelsLikeVisible) + { + sb.Append("\"maxFeels\":" + maxFeels.ToString() + "],"); + sb.Append("\"minFeels\":" + minFeels.ToString() + "],"); + } + if (cumulus.GraphOptions.HumidexVisible) + sb.Append("\"humidex\":" + humidex.ToString() + "],"); + + sb.Length--; + sb.Append("}"); + + return sb.ToString(); + } + + public string GetAllDailyWindGraphData() + { + var InvC = new CultureInfo(""); + + /* returns: + * { + * highgust:[[date1,val1],[date2,val2]...], + * mintemp:[[date1,val1],[date2,val2]...], + * etc + * } + */ + + StringBuilder sb = new StringBuilder("{"); + StringBuilder maxGust = new StringBuilder("["); + StringBuilder windRun = new StringBuilder("["); + StringBuilder maxWind = new StringBuilder("["); + + // Read the day file list and extract the data from there + if (DayFile.Count() > 0) + { + var len = DayFile.Count() - 1; + + for (var i = 0; i < DayFile.Count(); i++) + { + var recDate = DateTimeToUnix(DayFile[i].Date) * 1000; + + // hi gust + maxGust.Append($"[{recDate},{DayFile[i].HighGust.ToString(cumulus.WindFormat, InvC)}]"); + // hi wind run + windRun.Append($"[{recDate},{DayFile[i].WindRun.ToString(cumulus.WindRunFormat, InvC)}]"); + // hi wind + maxWind.Append($"[{recDate},{DayFile[i].HighAvgWind.ToString(cumulus.WindAvgFormat, InvC)}]"); + + if (i < len) + { + maxGust.Append(","); + windRun.Append(","); + maxWind.Append(","); + } + } + } + sb.Append("\"maxGust\":" + maxGust.ToString() + "],"); + sb.Append("\"windRun\":" + windRun.ToString() + "],"); + sb.Append("\"maxWind\":" + maxWind.ToString() + "]"); + sb.Append("}"); + + return sb.ToString(); + } + + public string GetAllDailyRainGraphData() + { + var InvC = new CultureInfo(""); + + /* returns: + * { + * highgust:[[date1,val1],[date2,val2]...], + * mintemp:[[date1,val1],[date2,val2]...], + * etc + * } + */ + + StringBuilder sb = new StringBuilder("{"); + StringBuilder maxRRate = new StringBuilder("["); + StringBuilder rain = new StringBuilder("["); + + // Read the day file list and extract the data from there + if (DayFile.Count() > 0) + { + var len = DayFile.Count() - 1; + + for (var i = 0; i < DayFile.Count(); i++) + { + + long recDate = DateTimeToUnix(DayFile[i].Date) * 1000; + + // hi rain rate + maxRRate.Append($"[{recDate},{DayFile[i].HighRainRate.ToString(cumulus.RainFormat, InvC)}]"); + // total rain + rain.Append($"[{recDate},{DayFile[i].TotalRain.ToString(cumulus.RainFormat, InvC)}]"); + + if (i < len) + { + maxRRate.Append(","); + rain.Append(","); + } + } + } + sb.Append("\"maxRainRate\":" + maxRRate.ToString() + "],"); + sb.Append("\"rain\":" + rain.ToString() + "]"); + sb.Append("}"); + + return sb.ToString(); + } + + public string GetAllDailyPressGraphData() + { + var InvC = new CultureInfo(""); + + /* returns: + * { + * highgust:[[date1,val1],[date2,val2]...], + * mintemp:[[date1,val1],[date2,val2]...], + * etc + * } + */ + + StringBuilder sb = new StringBuilder("{"); + StringBuilder minBaro = new StringBuilder("["); + StringBuilder maxBaro = new StringBuilder("["); + + + // Read the day file list and extract the data from there + if (DayFile.Count() > 0) + { + var len = DayFile.Count() - 1; + + for (var i = 0; i < DayFile.Count(); i++) + { + + long recDate = DateTimeToUnix(DayFile[i].Date) * 1000; + + // lo baro + minBaro.Append($"[{recDate},{DayFile[i].LowPress.ToString(cumulus.PressFormat, InvC)}]"); + // hi baro + maxBaro.Append($"[{recDate},{DayFile[i].HighPress.ToString(cumulus.PressFormat, InvC)}]"); + + if (i < len) + { + maxBaro.Append(","); + minBaro.Append(","); + } + } + } + sb.Append("\"minBaro\":" + minBaro.ToString() + "],"); + sb.Append("\"maxBaro\":" + maxBaro.ToString() + "]"); + sb.Append("}"); + + return sb.ToString(); + } + + //public string GetAllDailyWindDirGraphData() + //{ + // int linenum = 0; + // int valInt; + + // /* returns: + // * { + // * highgust:[[date1,val1],[date2,val2]...], + // * mintemp:[[date1,val1],[date2,val2]...], + // * etc + // * } + // */ + + // StringBuilder sb = new StringBuilder("{"); + // StringBuilder windDir = new StringBuilder("["); + + // var watch = 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++; + // List st = new List(Regex.Split(line, CultureInfo.CurrentCulture.TextInfo.ListSeparator)); + + // if (st.Count <= 0) continue; + + // // dominant wind direction + // if (st.Count > 39) + // { + // long recDate = DateTimeToUnix(ddmmyyStrToDate(st[0])) * 1000; + + // if (int.TryParse(st[39], out valInt)) + // windDir.Append($"[{recDate},{valInt}]"); + // else + // windDir.Append($"[{recDate},null]"); + // if (linenum < dayfile.Length) + // windDir.Append(","); + // } + // } + // } + // catch (Exception e) + // { + // cumulus.LogMessage("GetAllDailyWindDirGraphData: Error on line " + linenum + " of " + cumulus.DayFile + ": " + e.Message); + // } + // } + // sb.Append("\"windDir\":" + windDir.ToString() + "]"); + // sb.Append("}"); + + // watch.Stop(); + // cumulus.LogDebugMessage($"GetAllDailyWindDirGraphData: Dayfile parse = {watch.ElapsedMilliseconds} ms"); + + // return sb.ToString(); + //} + + public string GetAllDailyHumGraphData() + { + /* returns: + * { + * highgust:[[date1,val1],[date2,val2]...], + * mintemp:[[date1,val1],[date2,val2]...], + * etc + * } + */ + + StringBuilder sb = new StringBuilder("{"); + StringBuilder minHum = new StringBuilder("["); + StringBuilder maxHum = new StringBuilder("["); + + // Read the day file list and extract the data from there + if (DayFile.Count() > 0) + { + var len = DayFile.Count() - 1; + + for (var i = 0; i < DayFile.Count(); i++) + { + + long recDate = DateTimeToUnix(DayFile[i].Date) * 1000; + + // lo humidity + minHum.Append($"[{recDate},{DayFile[i].LowHumidity}]"); + // hi humidity + maxHum.Append($"[{recDate},{DayFile[i].HighHumidity}]"); + + if (i < len) + { + minHum.Append(","); + maxHum.Append(","); + } + } + } + sb.Append("\"minHum\":" + minHum.ToString() + "],"); + sb.Append("\"maxHum\":" + maxHum.ToString() + "]"); + sb.Append("}"); + + return sb.ToString(); + } + + public string GetAllDailySolarGraphData() + { + var InvC = new CultureInfo(""); + + /* returns: + * { + * highgust:[[date1,val1],[date2,val2]...], + * mintemp:[[date1,val1],[date2,val2]...], + * etc + * } + */ + + StringBuilder sb = new StringBuilder("{"); + StringBuilder sunHours = new StringBuilder("["); + StringBuilder solarRad = new StringBuilder("["); + StringBuilder uvi = new StringBuilder("["); + + // Read the day file list and extract the data from there + if (DayFile.Count() > 0) + { + var len = DayFile.Count() - 1; + + for (var i = 0; i < DayFile.Count(); i++) + { + long recDate = DateTimeToUnix(DayFile[i].Date) * 1000; + + if (cumulus.GraphOptions.SunshineVisible) + { + // sunshine hours + sunHours.Append($"[{recDate},{DayFile[i].SunShineHours.ToString(InvC)}]"); + if (i < len) + sunHours.Append(","); + } + + if (cumulus.GraphOptions.SolarVisible) + { + // hi solar rad + solarRad.Append($"[{recDate},{DayFile[i].HighSolar}]"); + if (i < len) + solarRad.Append(","); + } + + if (cumulus.GraphOptions.UVVisible) + { + // hi UV-I + uvi.Append($"[{recDate},{DayFile[i].HighUv.ToString(cumulus.UVFormat, InvC)}]"); + if (i < len) + uvi.Append(","); + } + } + } + if (cumulus.GraphOptions.SunshineVisible) + sb.Append("\"sunHours\":" + sunHours.ToString() + "]"); + + if (cumulus.GraphOptions.SolarVisible) + { + if (cumulus.GraphOptions.SunshineVisible) + sb.Append(","); + + sb.Append("\"solarRad\":" + solarRad.ToString() + "]"); + } + + if (cumulus.GraphOptions.UVVisible) + { + if (cumulus.GraphOptions.SunshineVisible || cumulus.GraphOptions.SolarVisible) + sb.Append(","); + + sb.Append("\"uvi\":" + uvi.ToString() + "]"); + } + sb.Append("}"); + + return sb.ToString(); + } + + public string GetAllDegreeDaysGraphData() + { + var InvC = new CultureInfo(""); + + StringBuilder sb = new StringBuilder("{"); + StringBuilder growdegdaysYears1 = new StringBuilder("{", 32768); + StringBuilder growdegdaysYears2 = new StringBuilder("{", 32768); + + StringBuilder growYear1 = new StringBuilder("[", 8600); + StringBuilder growYear2 = new StringBuilder("[", 8600); + + var options = $"\"options\":{{\"gddBase1\":{cumulus.GrowingBase1},\"gddBase2\":{cumulus.GrowingBase2},\"startMon\":{cumulus.GrowingYearStarts}}}"; + + DateTime nextYear; + + // 2000 was a leap year, so make sure February falls in 2000 + // for Southern hemisphere this means the start year must be 1999 + var plotYear = cumulus.GrowingYearStarts < 3 ? 2000 : 1999; + + int startYear; + + var annualGrowingDegDays1 = 0.0; + var annualGrowingDegDays2 = 0.0; + + // Read the day file list and extract the data from there + if (DayFile.Count() > 0 && (cumulus.GraphOptions.GrowingDegreeDaysVisible1 || cumulus.GraphOptions.GrowingDegreeDaysVisible2)) + { + // we have to detect a new growing deg day year is starting + nextYear = new DateTime(DayFile[0].Date.Year, cumulus.GrowingYearStarts, 1); + + if (DayFile[0].Date >= nextYear) + { + nextYear = nextYear.AddYears(1); + } + + // are we starting part way through a year that does not start in January? + if (DayFile[0].Date.Year == nextYear.Year) + { + startYear = DayFile[0].Date.Year - 1; + } + else + { + startYear = DayFile[0].Date.Year; + } + + if (cumulus.GraphOptions.GrowingDegreeDaysVisible1) + { + growdegdaysYears1.Append($"\"{startYear}\":"); + } + if (cumulus.GraphOptions.GrowingDegreeDaysVisible2) + { + growdegdaysYears2.Append($"\"{startYear}\":"); + } + + + for (var i = 0; i < DayFile.Count(); i++) + { + // we have rolled over into a new GDD year, write out what we have and reset + if (DayFile[i].Date >= nextYear) + { + if (cumulus.GraphOptions.GrowingDegreeDaysVisible1 && growYear1.Length > 10) + { + // remove last comma + growYear1.Length--; + // close the year data + growYear1.Append("],"); + // apend to years array + growdegdaysYears1.Append(growYear1); + + growYear1.Clear().Append($"\"{DayFile[i].Date.Year}\":["); + } + if (cumulus.GraphOptions.GrowingDegreeDaysVisible2 && growYear2.Length > 10) + { + // remove last comma + growYear2.Length--; + // close the year data + growYear2.Append("],"); + // apend to years array + growdegdaysYears2.Append(growYear2); + + growYear2.Clear().Append($"\"{DayFile[i].Date.Year}\":["); + } + + // reset the plot year for Southern hemi + plotYear = cumulus.GrowingYearStarts < 3 ? 2000 : 1999; + + annualGrowingDegDays1 = 0; + annualGrowingDegDays2 = 0; + do + { + nextYear = nextYear.AddYears(1); + } + while (DayFile[i].Date >= nextYear); + } + + // make all series the same year so they plot together + // 2000 was a leap year, so make sure February falls in 2000 + // for Southern hemisphere this means the start year must be 1999 + if (cumulus.GrowingYearStarts > 2 && plotYear == 1999 && DayFile[i].Date.Month == 1) + { + plotYear++; + } + + // make all series the same year so they plot together + long recDate = DateTimeToUnix(new DateTime(plotYear, DayFile[i].Date.Month, DayFile[i].Date.Day)) * 1000; + + if (cumulus.GraphOptions.GrowingDegreeDaysVisible1) + { + // growing degree days + var gdd = MeteoLib.GrowingDegreeDays(ConvertUserTempToC(DayFile[i].HighTemp), ConvertUserTempToC(DayFile[i].LowTemp), ConvertUserTempToC(cumulus.GrowingBase1), cumulus.GrowingCap30C); + + // annual accumulation + annualGrowingDegDays1 += gdd; + + growYear1.Append($"[{recDate},{annualGrowingDegDays1.ToString("F1", InvC)}],"); + } + + if (cumulus.GraphOptions.GrowingDegreeDaysVisible2) + { + // growing degree days + var gdd = MeteoLib.GrowingDegreeDays(ConvertUserTempToC(DayFile[i].HighTemp), ConvertUserTempToC(DayFile[i].LowTemp), ConvertUserTempToC(cumulus.GrowingBase2), cumulus.GrowingCap30C); + + // annual accumulation + annualGrowingDegDays2 += gdd; + + growYear2.Append($"[{recDate},{annualGrowingDegDays2.ToString("F1", InvC)}],"); + } + } + } + + // remove last commas from the years arrays and close them off + if (cumulus.GraphOptions.GrowingDegreeDaysVisible1) + { + if (growYear1[growYear1.Length - 1] == ',') + { + growYear1.Length--; + } + + // have previous years been appended? + if (growdegdaysYears1[growdegdaysYears1.Length - 1] == ']') + { + growdegdaysYears1.Append(","); + } + + growdegdaysYears1.Append(growYear1 + "]"); + + // add to main json + sb.Append("\"GDD1\":" + growdegdaysYears1 + "},"); + } + if (cumulus.GraphOptions.GrowingDegreeDaysVisible2) + { + if (growYear2[growYear2.Length - 1] == ',') + { + growYear2.Length--; + } + + // have previous years been appended? + if (growdegdaysYears2[growdegdaysYears2.Length - 1] == ']') + { + growdegdaysYears2.Append(","); + } + growdegdaysYears2.Append(growYear2 + "]"); + + // add to main json + sb.Append("\"GDD2\":" + growdegdaysYears2 + "},"); + } + + sb.Append(options); + + sb.Append("}"); + + return sb.ToString(); + } + + public string GetAllTempSumGraphData() + { + var InvC = new CultureInfo(""); + + StringBuilder sb = new StringBuilder("{"); + StringBuilder tempSumYears0 = new StringBuilder("{", 32768); + StringBuilder tempSumYears1 = new StringBuilder("{", 32768); + StringBuilder tempSumYears2 = new StringBuilder("{", 32768); + + StringBuilder tempSum0 = new StringBuilder("[", 8600); + StringBuilder tempSum1 = new StringBuilder("[", 8600); + StringBuilder tempSum2 = new StringBuilder("[", 8600); + + DateTime nextYear; + + // 2000 was a leap year, so make sure February falls in 2000 + // for Southern hemisphere this means the start year must be 1999 + var plotYear = cumulus.TempSumYearStarts < 3 ? 2000 : 1999; + + int startYear; + var annualTempSum0 = 0.0; + var annualTempSum1 = 0.0; + var annualTempSum2 = 0.0; + + var options = $"\"options\":{{\"sumBase1\":{cumulus.TempSumBase1},\"sumBase2\":{cumulus.TempSumBase2},\"startMon\":{cumulus.TempSumYearStarts}}}"; + + // Read the day file list and extract the data from there + if (DayFile.Count() > 0 && (cumulus.GraphOptions.TempSumVisible0 || cumulus.GraphOptions.TempSumVisible1 || cumulus.GraphOptions.TempSumVisible2)) + { + // we have to detect a new year is starting + nextYear = new DateTime(DayFile[0].Date.Year, cumulus.TempSumYearStarts, 1); + + if (DayFile[0].Date >= nextYear) + { + nextYear = nextYear.AddYears(1); + } + + // are we starting part way through a year that does not start in January? + if (DayFile[0].Date.Year == nextYear.Year) + { + startYear = DayFile[0].Date.Year - 1; + } + else + { + startYear = DayFile[0].Date.Year; + } + + if (cumulus.GraphOptions.TempSumVisible0) + { + tempSumYears0.Append($"\"{startYear}\":"); + } + if (cumulus.GraphOptions.TempSumVisible1) + { + tempSumYears1.Append($"\"{startYear}\":"); + } + if (cumulus.GraphOptions.TempSumVisible2) + { + tempSumYears2.Append($"\"{startYear}\":"); + } + + for (var i = 0; i < DayFile.Count(); i++) + { + // we have rolled over into a new GDD year, write out what we have and reset + if (DayFile[i].Date >= nextYear) + { + if (cumulus.GraphOptions.TempSumVisible0 && tempSum0.Length > 10) + { + // remove last comma + tempSum0.Length--; + // close the year data + tempSum0.Append("],"); + // apend to years array + tempSumYears0.Append(tempSum0); + + tempSum0.Clear().Append($"\"{DayFile[i].Date.Year}\":["); + } + if (cumulus.GraphOptions.TempSumVisible1 && tempSum1.Length > 10) + { + // remove last comma + tempSum1.Length--; + // close the year data + tempSum1.Append("],"); + // apend to years array + tempSumYears1.Append(tempSum1); + + tempSum1.Clear().Append($"\"{DayFile[i].Date.Year}\":["); + } + if (cumulus.GraphOptions.TempSumVisible2 && tempSum2.Length > 10) + { + // remove last comma + tempSum2.Length--; + // close the year data + tempSum2.Append("],"); + // apend to years array + tempSumYears2.Append(tempSum2); + + tempSum2.Clear().Append($"\"{DayFile[i].Date.Year}\":["); + } + + // reset the plot year for Southern hemi + plotYear = cumulus.TempSumYearStarts < 3 ? 2000 : 1999; + + annualTempSum0 = 0; + annualTempSum1 = 0; + annualTempSum2 = 0; + + do + { + nextYear = nextYear.AddYears(1); + } + while (DayFile[i].Date >= nextYear); + } + // make all series the same year so they plot together + // 2000 was a leap year, so make sure February falls in 2000 + // for Southern hemisphere this means the start year must be 1999 + if (cumulus.TempSumYearStarts > 2 && plotYear == 1999 && DayFile[i].Date.Month == 1) + { + plotYear++; + } + + long recDate = DateTimeToUnix(new DateTime(plotYear, DayFile[i].Date.Month, DayFile[i].Date.Day)) * 1000; + + if (cumulus.GraphOptions.TempSumVisible0) + { + // annual accumulation + annualTempSum0 += DayFile[i].AvgTemp; + tempSum0.Append($"[{recDate},{annualTempSum0.ToString("F0", InvC)}],"); + } + if (cumulus.GraphOptions.TempSumVisible1) + { + // annual accumulation + annualTempSum1 += DayFile[i].AvgTemp - cumulus.TempSumBase1; + tempSum1.Append($"[{recDate},{annualTempSum1.ToString("F0", InvC)}],"); + } + if (cumulus.GraphOptions.TempSumVisible2) + { + // annual accumulation + annualTempSum2 += DayFile[i].AvgTemp - cumulus.TempSumBase2; + tempSum2.Append($"[{recDate},{annualTempSum2.ToString("F0", InvC)}],"); + } + } + } + + // remove last commas from the years arrays and close them off + if (cumulus.GraphOptions.TempSumVisible0) + { + if (tempSum0[tempSum0.Length - 1] == ',') + { + tempSum0.Length--; + } + + // have previous years been appended? + if (tempSumYears0[tempSumYears0.Length - 1] == ']') + { + tempSumYears0.Append(","); + } + + tempSumYears0.Append(tempSum0 + "]"); + + // add to main json + sb.Append("\"Sum0\":" + tempSumYears0 + "}"); + + if (cumulus.GraphOptions.TempSumVisible1 || cumulus.GraphOptions.TempSumVisible2) + sb.Append(","); + } + if (cumulus.GraphOptions.TempSumVisible1) + { + if (tempSum1[tempSum1.Length - 1] == ',') + { + tempSum1.Length--; + } + + // have previous years been appended? + if (tempSumYears1[tempSumYears1.Length - 1] == ']') + { + tempSumYears1.Append(","); + } + + tempSumYears1.Append(tempSum1 + "]"); + + // add to main json + sb.Append("\"Sum1\":" + tempSumYears1 + "},"); + } + if (cumulus.GraphOptions.TempSumVisible2) + { + if (tempSum2[tempSum2.Length - 1] == ',') + { + tempSum2.Length--; + } + + // have previous years been appended? + if (tempSumYears2[tempSumYears2.Length - 1] == ']') + { + tempSumYears2.Append(","); + } + + tempSumYears2.Append(tempSum2 + "]"); + + // add to main json + sb.Append("\"Sum2\":" + tempSumYears2 + "},"); + } + + sb.Append(options); + + sb.Append("}"); + + return sb.ToString(); + } + + + + internal string GetCurrentData() + { + StringBuilder windRoseData = new StringBuilder((windcounts[0] * cumulus.Calib.WindGust.Mult).ToString(cumulus.WindFormat, CultureInfo.InvariantCulture), 4096); + lock (windRoseData) + { + for (var i = 1; i < cumulus.NumWindRosePoints; i++) + { + windRoseData.Append(","); + windRoseData.Append((windcounts[i] * cumulus.Calib.WindGust.Mult).ToString(cumulus.WindFormat, CultureInfo.InvariantCulture)); + } + } + string stormRainStart = StartOfStorm == DateTime.MinValue ? "-----" : StartOfStorm.ToString("d"); + + var data = new DataStruct(cumulus, OutdoorTemperature, OutdoorHumidity, TempTotalToday / tempsamplestoday, IndoorTemperature, OutdoorDewpoint, WindChill, IndoorHumidity, + Pressure, WindLatest, WindAverage, RecentMaxGust, WindRunToday, Bearing, AvgBearing, RainToday, RainYesterday, RainMonth, RainYear, RainRate, + RainLastHour, HeatIndex, Humidex, ApparentTemperature, temptrendval, presstrendval, HiLoToday.HighGust, HiLoToday.HighGustTime.ToString("HH:mm"), HiLoToday.HighWind, + HiLoToday.HighGustBearing, cumulus.Units.WindText, BearingRangeFrom10, BearingRangeTo10, windRoseData.ToString(), HiLoToday.HighTemp, HiLoToday.LowTemp, + HiLoToday.HighTempTime.ToString("HH:mm"), HiLoToday.LowTempTime.ToString("HH:mm"), HiLoToday.HighPress, HiLoToday.LowPress, HiLoToday.HighPressTime.ToString("HH:mm"), + HiLoToday.LowPressTime.ToString("HH:mm"), HiLoToday.HighRainRate, HiLoToday.HighRainRateTime.ToString("HH:mm"), HiLoToday.HighHumidity, HiLoToday.LowHumidity, + HiLoToday.HighHumidityTime.ToString("HH:mm"), HiLoToday.LowHumidityTime.ToString("HH:mm"), cumulus.Units.PressText, cumulus.Units.TempText, cumulus.Units.RainText, + HiLoToday.HighDewPoint, HiLoToday.LowDewPoint, HiLoToday.HighDewPointTime.ToString("HH:mm"), HiLoToday.LowDewPointTime.ToString("HH:mm"), HiLoToday.LowWindChill, + HiLoToday.LowWindChillTime.ToString("HH:mm"), (int)SolarRad, (int)HiLoToday.HighSolar, HiLoToday.HighSolarTime.ToString("HH:mm"), UV, HiLoToday.HighUv, + HiLoToday.HighUvTime.ToString("HH:mm"), forecaststr, getTimeString(cumulus.SunRiseTime), getTimeString(cumulus.SunSetTime), + getTimeString(cumulus.MoonRiseTime), getTimeString(cumulus.MoonSetTime), HiLoToday.HighHeatIndex, HiLoToday.HighHeatIndexTime.ToString("HH:mm"), HiLoToday.HighAppTemp, + HiLoToday.LowAppTemp, HiLoToday.HighAppTempTime.ToString("HH:mm"), HiLoToday.LowAppTempTime.ToString("HH:mm"), (int)Math.Round(CurrentSolarMax), + AllTime.HighPress.Val, AllTime.LowPress.Val, SunshineHours, CompassPoint(DominantWindBearing), LastRainTip, + HiLoToday.HighHourlyRain, HiLoToday.HighHourlyRainTime.ToString("HH:mm"), "F" + cumulus.Beaufort(HiLoToday.HighWind), "F" + cumulus.Beaufort(WindAverage), + cumulus.BeaufortDesc(WindAverage), LastDataReadTimestamp.ToString("HH:mm:ss"), DataStopped, StormRain, stormRainStart, CloudBase, cumulus.CloudBaseInFeet ? "ft" : "m", RainLast24Hour, + cumulus.LowTempAlarm.Triggered, cumulus.HighTempAlarm.Triggered, cumulus.TempChangeAlarm.UpTriggered, cumulus.TempChangeAlarm.DownTriggered, cumulus.HighRainTodayAlarm.Triggered, cumulus.HighRainRateAlarm.Triggered, + cumulus.LowPressAlarm.Triggered, cumulus.HighPressAlarm.Triggered, cumulus.PressChangeAlarm.UpTriggered, cumulus.PressChangeAlarm.DownTriggered, cumulus.HighGustAlarm.Triggered, cumulus.HighWindAlarm.Triggered, + cumulus.SensorAlarm.Triggered, cumulus.BatteryLowAlarm.Triggered, cumulus.SpikeAlarm.Triggered, cumulus.UpgradeAlarm.Triggered, cumulus.HttpUploadAlarm.Triggered, cumulus.MySqlUploadAlarm.Triggered, + FeelsLike, HiLoToday.HighFeelsLike, HiLoToday.HighFeelsLikeTime.ToString("HH:mm:ss"), HiLoToday.LowFeelsLike, HiLoToday.LowFeelsLikeTime.ToString("HH:mm:ss"), + HiLoToday.HighHumidex, HiLoToday.HighHumidexTime.ToString("HH:mm:ss")); + + try + { + using (MemoryStream stream = new MemoryStream()) + { + DataContractJsonSerializer ds = new DataContractJsonSerializer(typeof(DataStruct)); + DataContractJsonSerializerSettings s = new DataContractJsonSerializerSettings(); + ds.WriteObject(stream, data); + string jsonString = Encoding.UTF8.GetString(stream.ToArray()); + stream.Close(); + return jsonString; + } + } + catch (Exception ex) + { + cumulus.LogConsoleMessage(ex.Message); + return ""; + } + + } + + // Returns true if the gust value exceeds current RecentMaxGust, false if it fails + public bool CheckHighGust(double gust, int gustdir, DateTime timestamp) + { + // Spike check is in m/s + var windGustMS = ConvertUserWindToMS(gust); + if (((previousGust != 999) && (Math.Abs(windGustMS - previousGust) > cumulus.Spike.GustDiff)) || windGustMS >= cumulus.Limit.WindHigh) + { + cumulus.LogSpikeRemoval("Wind Gust difference greater than specified; reading ignored"); + cumulus.LogSpikeRemoval($"Gust: NewVal={windGustMS:F1} OldVal={previousGust:F1} SpikeGustDiff={cumulus.Spike.GustDiff:F1} HighLimit={cumulus.Limit.WindHigh:F1}"); + return false; + } + + if (gust > RecentMaxGust) + { + if (gust > HiLoToday.HighGust) + { + HiLoToday.HighGust = gust; + HiLoToday.HighGustTime = timestamp; + HiLoToday.HighGustBearing = gustdir; + WriteTodayFile(timestamp, false); + } + if (gust > ThisMonth.HighGust.Val) + { + ThisMonth.HighGust.Val = gust; + ThisMonth.HighGust.Ts = timestamp; + WriteMonthIniFile(); + } + if (gust > ThisYear.HighGust.Val) + { + ThisYear.HighGust.Val = gust; + ThisYear.HighGust.Ts = timestamp; + WriteYearIniFile(); + } + // All time high gust? + if (gust > AllTime.HighGust.Val) + { + SetAlltime(AllTime.HighGust, gust, timestamp); + } + + // check for monthly all time records (and set) + CheckMonthlyAlltime("HighGust", gust, true, timestamp); + } + return true; + } + + /// + /// Calculates the Davis version (almost) of Evapotranspiration + /// + /// ET for past hour in mm + /* + public double CalulateEvapotranspiration(DateTime ts) + { + var onehourago = ts.AddHours(-1); + + // Mean temperature in Fahrenheit + var result = RecentDataDb.Query("select avg(OutsideTemp) temp, avg(WindSpeed) wind, avg(SolarRad) solar, avg(Humidity) hum from RecentData where Timestamp >= ?", onehourago); + + var meanTempC = ConvertUserTempToC(result[0].temp); + var meanTempK = meanTempC + 273.16; + var meanWind = ConvertUserWindToMS(result[0].wind); + var meanHum = result[0].hum; + var meanSolar = result[0].solar; + var pressure = ConvertUserPressToMB(AltimeterPressure) / 100; // need kPa + + var satVapPress = MeteoLib.SaturationVapourPressure2008(meanTempC); + var waterVapour = satVapPress * meanHum / 100; + + var delta = satVapPress / meanTempK * ((6790.4985 / meanTempK) - 5.02808); + + var gamma = 0.000646 * (1 + 0.000946 * meanTempC) * pressure; + + var weighting = delta / (delta + gamma); + + double windFunc; + if (meanSolar > 0) + windFunc = 0.030 + 0.0576 * meanWind; + else + windFunc = 0.125 + 0.0439 * meanWind; + + var lambda = 69.5 * (1 - 0.000946 * meanTempC); + + //TODO: Need to calculate the net radiation rather than use meanSolar - need mean theoretical solar value for this + var meanSolarMax = 0.0; //TODO + + // clear sky - meanSolar/meanSolarMax >= 1, c <= 1, solar elevation > 10 deg + var clearSky = (1.333 - 1.333 * meanSolar / meanSolarMax); + + var netSolar = 0.0; //TODO + + var ET = weighting * netSolar / lambda + (1 - weighting) * (satVapPress - waterVapour) * windFunc; + + return ET; + } + */ + + + public void UpdateAPRS() + { + if (DataStopped) + { + // No data coming in, do nothing + return; + } + + cumulus.LogMessage("Updating CWOP"); + using (var client = new TcpClient(cumulus.APRS.Server, cumulus.APRS.Port)) + using (var ns = client.GetStream()) + { + try + { + using (StreamWriter writer = new StreamWriter(ns)) + { + StringBuilder message = new StringBuilder(256); + message.Append($"user {cumulus.APRS.ID} pass {cumulus.APRS.PW} vers Cumulus {cumulus.Version}"); + + //Byte[] data = Encoding.ASCII.GetBytes(message.ToString()); + + cumulus.LogDebugMessage("Sending user and pass to CWOP"); + + writer.WriteLine(message.ToString()); + writer.Flush(); + + Thread.Sleep(3000); + + string timeUTC = DateTime.Now.ToUniversalTime().ToString("ddHHmm"); + + message.Clear(); + message.Append($"{cumulus.APRS.ID}>APRS,TCPIP*:@{timeUTC}z{APRSLat(cumulus)}/{APRSLon(cumulus)}"); + // bearing _nnn + message.Append($"_{AvgBearing:D3}"); + // wind speed mph /nnn + message.Append($"/{APRSwind(WindAverage)}"); + // wind gust last 5 mins mph gnnn + message.Append($"g{APRSwind(RecentMaxGust)}"); + // temp F tnnn + message.Append($"t{APRStemp(OutdoorTemperature)}"); + // rain last hour 0.01 inches rnnn + message.Append($"r{APRSrain(RainLastHour)}"); + // rain last 24 hours 0.01 inches pnnn + message.Append($"p{APRSrain(RainLast24Hour)}"); + message.Append("P"); + if (cumulus.RolloverHour == 0) + { + // use today"s rain for safety + message.Append(APRSrain(RainToday)); + } + else + { + // 0900 day, use midnight calculation + message.Append(APRSrain(RainSinceMidnight)); + } + if ((!cumulus.APRS.HumidityCutoff) || (ConvertUserTempToC(OutdoorTemperature) >= -10)) + { + // humidity Hnn + message.Append($"h{APRShum(OutdoorHumidity)}"); + } + // bar 0.1mb Bnnnnn + message.Append($"b{APRSpress(AltimeterPressure)}"); + if (cumulus.APRS.SendSolar) + { + message.Append(APRSsolarradStr(Convert.ToInt32(SolarRad))); + } + + // station type e + message.Append($"eCumulus{cumulus.APRSstationtype[cumulus.StationType]}"); + + cumulus.LogDebugMessage($"Sending: {message}"); + + //data = Encoding.ASCII.GetBytes(message.ToString()); + + writer.WriteLine(message.ToString()); + writer.Flush(); + + Thread.Sleep(3000); + writer.Close(); + } + cumulus.LogDebugMessage("End of CWOP update"); + } + catch (Exception e) + { + cumulus.LogMessage("CWOP error: " + e.Message); + } + } + } + + /// + /// Takes latitude in degrees and converts it to APRS format ddmm.hhX: + /// (hh = hundredths of a minute) + /// e.g. 5914.55N + /// + /// + private string APRSLat(Cumulus cumulus) + { + string dir; + double lat; + int d, m, s; + if (cumulus.Latitude < 0) + { + lat = -cumulus.Latitude; + dir = "S"; + } + else + { + lat = cumulus.Latitude; + dir = "N"; + } + + cumulus.DegToDMS(lat, out d, out m, out s); + int hh = (int) Math.Round(s*100/60.0); + + return String.Format("{0:D2}{1:D2}.{2:D2}{3}", d, m, hh, dir); + } + + /// + /// Takes longitude in degrees and converts it to APRS format dddmm.hhX: + /// (hh = hundredths of a minute) + /// e.g. 15914.55W + /// + /// + private string APRSLon(Cumulus cumulus) + { + string dir; + double lon; + int d, m, s; + if (cumulus.Longitude < 0) + { + lon = -cumulus.Longitude; + dir = "W"; + } + else + { + lon = cumulus.Longitude; + dir = "E"; + } + + cumulus.DegToDMS(lon, out d, out m, out s); + int hh = (int) Math.Round(s*100/60.0); + + return String.Format("{0:D3}{1:D2}.{2:D2}{3}", d, m, hh, dir); + } + + /// + /// input is in Units.Wind units, convert to mph for APRS + /// and return 3 digits + /// + /// + /// + private string APRSwind(double wind) + { + var windMPH = Convert.ToInt32(ConvertUserWindToMPH(wind)); + return windMPH.ToString("D3"); + } + + /// + /// input is in Units.Press units, convert to tenths of mb for APRS + /// return 5 digit string + /// + /// + /// + public string APRSpress(double press) + { + var press10mb = Convert.ToInt32(ConvertUserPressToMB(press) * 10); + return press10mb.ToString("D5"); + } + + /// + /// return humidity as 2-digit string + /// represent 100 by 00 + /// send 1 instead of zero + /// + /// + /// + public string APRShum(int hum) + { + if (hum == 100) + { + return "00"; + } + + if (hum == 0) + { + return "01"; + } + + return hum.ToString("D2"); + } + + /// + /// input is in RainUnit units, convert to hundredths of inches for APRS + /// and return 3 digits + /// + /// + /// + public string APRSrain(double rain) + { + var rain100IN = Convert.ToInt32(ConvertUserRainToIN(rain) * 100); + return rain100IN.ToString("D3"); + } + + public class CommTimer : IDisposable + { + public Timer tmrComm = new Timer(); + public bool timedout = false; + public CommTimer() + { + timedout = false; + tmrComm.AutoReset = false; + tmrComm.Enabled = false; + tmrComm.Interval = 1000; //default to 1 second + tmrComm.Elapsed += new ElapsedEventHandler(OnTimedCommEvent); + } + + public void OnTimedCommEvent(object source, ElapsedEventArgs e) + { + timedout = true; + tmrComm.Stop(); + } + + public void Start(double timeoutperiod) + { + tmrComm.Interval = timeoutperiod; //time to time out in milliseconds + tmrComm.Stop(); + timedout = false; + tmrComm.Start(); + } + + public void Stop() + { + tmrComm.Stop(); + timedout = true; + } + + public void Dispose() + { + tmrComm.Close(); + tmrComm.Dispose(); + } + } + + } + + //public partial class CumulusData : DataContext + //{ + // public Table Datas; + // //public Table ExtraData; + // public CumulusData(string connection) : base(connection) { } + //} + + public class Last3HourData + { + public DateTime timestamp; + public double pressure; + public double temperature; + + public Last3HourData(DateTime ts, double press, double temp) + { + timestamp = ts; + pressure = press; + temperature = temp; + } + } + + public class LastHourData + { + public DateTime timestamp; + public double raincounter; + public double temperature; + + public LastHourData(DateTime ts, double rain, double temp) + { + timestamp = ts; + raincounter = rain; + temperature = temp; + } + } + + public class GraphData + { + public DateTime timestamp; + public double raincounter; + public double RainToday; + public double rainrate; + public double temperature; + public double dewpoint; + public double apptemp; + public double feelslike; + public double humidex; + public double windchill; + public double heatindex; + public double insidetemp; + public double pressure; + public double windspeed; + public double windgust; + public int avgwinddir; + public int winddir; + public int humidity; + public int inhumidity; + public double solarrad; + public double solarmax; + public double uvindex; + public double pm2p5; + public double pm10; + + public GraphData(DateTime ts, double rain, double raintoday, double rrate, double temp, double dp, double appt, double chill, double heat, double intemp, double press, + double speed, double gust, int avgdir, int wdir, int hum, int inhum, double solar, double smax, double uv, double feels, double humidx, double pm2p5, double pm10) + { + timestamp = ts; + raincounter = rain; + RainToday = raintoday; + rainrate = rrate; + temperature = temp; + dewpoint = dp; + apptemp = appt; + windchill = chill; + heatindex = heat; + insidetemp = intemp; + pressure = press; + windspeed = speed; + windgust = gust; + avgwinddir = avgdir; + winddir = wdir; + humidity = hum; + inhumidity = inhum; + solarrad = solar; + solarmax = smax; + uvindex = uv; + feelslike = feels; + humidex = humidx; + this.pm2p5 = pm2p5; + this.pm10 = pm10; + } + } + + public class Last10MinWind + { + public DateTime timestamp; + public double gust; + public double speed; + public double gustX; + public double gustY; + + public Last10MinWind(DateTime ts, double windgust, double windspeed, double Xgust, double Ygust) + { + timestamp = ts; + gust = windgust; + speed = windspeed; + gustX = Xgust; + gustY = Ygust; + } + } + + public class RecentDailyData + { + public DateTime timestamp; + public double rain; + public double sunhours; + public double mintemp; + public double maxtemp; + public double avgtemp; + + public RecentDailyData(DateTime ts, double dailyrain, double sunhrs, double mint, double maxt, double avgt) + { + timestamp = ts; + rain = dailyrain; + sunhours = sunhrs; + mintemp = mint; + maxtemp = maxt; + avgtemp = avgt; + } + } + + public class RecentData + { + [PrimaryKey] + public DateTime Timestamp { get; set; } + + public double WindSpeed { get; set; } + public double WindGust { get; set; } + public double WindLatest { get; set; } + public int WindDir { get; set; } + public int WindAvgDir { get; set; } + public double OutsideTemp { get; set; } + public double WindChill { get; set; } + public double DewPoint { get; set; } + public double HeatIndex { get; set; } + public double Humidity { get; set; } + public double Pressure { get; set; } + public double RainToday { get; set; } + public int SolarRad { get; set; } + public double UV { get; set; } + public double raincounter { get; set; } + public double FeelsLike { get; set; } + public double Humidex { get; set; } + } + + public class AvgData + { + public double temp { get; set; } + public double wind { get; set; } + public double solar { get; set; } + public double hum { get; set; } + } + + + /* + public class StandardData + { + [PrimaryKey] + public DateTime Timestamp { get; set; } + + public int Interval { get; set; } + + public double OutTemp { get; set; } + public double LoOutTemp { get; set; } + public double HiOutTemp { get; set; } + + public double DewPoint { get; set; } + public double LoDewPoint { get; set; } + public double HiDewPoint { get; set; } + + public double WindChill { get; set; } + public double LoWindChill { get; set; } + public double HiWindChill { get; set; } + + public double InTemp { get; set; } + public double LoInTemp { get; set; } + public double HiInTemp { get; set; } + + public double Pressure { get; set; } + public double LoPressure { get; set; } + public double HiPressure { get; set; } + } + */ + + public class AllTimeRecords + { + // Add an indexer so we can reference properties with a string + public AllTimeRec this[string propertyName] + { + get + { + // probably faster without reflection: + // like: return Properties.Settings.Default.PropertyValues[propertyName] + // instead of the following + Type myType = typeof(AllTimeRecords); + PropertyInfo myPropInfo = myType.GetProperty(propertyName); + return (AllTimeRec) myPropInfo.GetValue(this, null); + } + set + { + Type myType = typeof(AllTimeRecords); + PropertyInfo myPropInfo = myType.GetProperty(propertyName); + myPropInfo.SetValue(this, value, null); + } + } + + public AllTimeRec HighTemp { get; set; } = new AllTimeRec(0); + public AllTimeRec LowTemp { get; set; } = new AllTimeRec(1); + public AllTimeRec HighGust { get; set; } = new AllTimeRec(2); + public AllTimeRec HighWind { get; set; } = new AllTimeRec(3); + public AllTimeRec LowChill { get; set; } = new AllTimeRec(4); + public AllTimeRec HighRainRate { get; set; } = new AllTimeRec(5); + public AllTimeRec DailyRain { get; set; } = new AllTimeRec(6); + public AllTimeRec HourlyRain { get; set; } = new AllTimeRec(7); + public AllTimeRec LowPress { get; set; } = new AllTimeRec(8); + public AllTimeRec HighPress { get; set; } = new AllTimeRec(9); + public AllTimeRec MonthlyRain { get; set; } = new AllTimeRec(10); + public AllTimeRec HighMinTemp { get; set; } = new AllTimeRec(11); + public AllTimeRec LowMaxTemp { get; set; } = new AllTimeRec(12); + public AllTimeRec HighHumidity { get; set; } = new AllTimeRec(13); + public AllTimeRec LowHumidity { get; set; } = new AllTimeRec(14); + public AllTimeRec HighAppTemp { get; set; } = new AllTimeRec(15); + public AllTimeRec LowAppTemp { get; set; } = new AllTimeRec(16); + public AllTimeRec HighHeatIndex { get; set; } = new AllTimeRec(17); + public AllTimeRec HighDewPoint { get; set; } = new AllTimeRec(18); + public AllTimeRec LowDewPoint{ get; set; } = new AllTimeRec(19); + public AllTimeRec HighWindRun { get; set; } = new AllTimeRec(20); + public AllTimeRec LongestDryPeriod { get; set; } = new AllTimeRec(21); + public AllTimeRec LongestWetPeriod { get; set; } = new AllTimeRec(22); + public AllTimeRec HighDailyTempRange { get; set; } = new AllTimeRec(23); + public AllTimeRec LowDailyTempRange { get; set; } = new AllTimeRec(24); + public AllTimeRec HighFeelsLike { get; set; } = new AllTimeRec(25); + public AllTimeRec LowFeelsLike { get; set; } = new AllTimeRec(26); + public AllTimeRec HighHumidex { get; set; } = new AllTimeRec(27); + } + + public class AllTimeRec + { + private static string[] alltimedescs = new[] + { + "High temperature", "Low temperature", "High gust", "High wind speed", "Low wind chill", "High rain rate", "High daily rain", + "High hourly rain", "Low pressure", "High pressure", "Highest monthly rainfall", "Highest minimum temp", "Lowest maximum temp", + "High humidity", "Low humidity", "High apparent temp", "Low apparent temp", "High heat index", "High dew point", "Low dew point", + "High daily windrun", "Longest dry period", "Longest wet period", "High daily temp range", "Low daily temp range", + "High feels like", "Low feels like", "High Humidex" + }; + private int idx; + + public AllTimeRec(int index) + { + idx = index; + } + public double Val { get; set; } + public DateTime Ts { get; set; } + public string Desc + { + get + { + return alltimedescs[idx]; + } + } + } +} diff --git a/Updates.txt b/Updates.txt index 7c30a5ca..66b2ec55 100644 --- a/Updates.txt +++ b/Updates.txt @@ -1,1677 +1,1695 @@ -3.11.2 - b3131 -—————————————— -- Fix: Send email failing if email logging is not enabled -- Fix: Solar Rad colour now displays as selected on first load in the Selectachart on the dashboard and default website -- Fix: Handlebars JS script updated due to severe security risk on previous version -- Fix: GW1000 for 433, 868, 915 Mhz variants - -- Change: Setting of ALL logging options via the GUI is now "sticky" - i.e. preserved across runs of Cumulus MX -- Change: Many of the main settings screens now alert to to the first invalid settings in a message, and the tree containing - the error is highlighted in red: - - Program Settings - - Station Settings - - Internet Settings - - Third Party Settings - - Extra Sensor Settings - - Calibration Settings - - NOAA Settings - - MySQL Settings -- Change: If any of the base units (wind, temp, rain, pressure) are changed via Station Settings (for instance during the initial configuration), then all the - thresholds and base values associated with that unit are reset to the defaults for the new unit -- Change: The main dashboard page now only shows alarm indicators for enabled alarms - -- New: Implements two new alarms for HTTP uploads failing, and MySQL uploads failing -- New: Web tags - <#HttpUploadAlarm>, <#MySqlUploadAlarm> - - -3.11.1 - b3130 -—————————————— -- Fix: Fix Test email logging "success" on failure -- Fix: Rollback FluentFTP package to previous 32.3.1 version due to crashes in Mono with newer versions - -- New: Pressure change in last 3 hours web tag - <#PressChangeLast3Hours> -- New: Enable additional Accessibility feature - configured via either Station Settings or Program Settings - The following pages now have some enhanced accessibility features: - - Program Settings - - Station Settings - - Internet Settings - - Third Party Settings - - Extra Sensor Settings - - Calibration Settings - - NOAA Settings - - MySQL Settings - Adds a new entry in Cumulus.ini - - - -3.11.0 - b3129 -—————————————— -- Fix: Remove THW Index in /web/websitedataT.json, and default web site index.htm -- Fix: OpenWeatherMap - create new station was not forcing the use of dot decimals for lat/long values -- Fix: WeatherCloud - date/time of reading should be UTC -- Fix: End of day backup now always runs at rollover -- Fix: FineOffset stations were not ignoring invalid wind speeds on historic catch up -- Fix: FineOffset stations historic catch up was failing when it reached a logger memory wrap around -- Fix: FineOffset stations historic catch up is now limited to the number of logger entries the console says are recorded -- Fix: FineOffset synchronise reads process improvements (attempt) -- Fix: Catch some errors that could occur in the start-up PING process -- Fix: MySQL connections that use encryption should now be supported (MySQL 8.0+ default) -- Fix: GW1000 System Information now correctly decodes 915 and 920MHz devices -- Fix: GW1000 occasional crash when an unexpected response was received -- Fix: All settings screens error handling messages now no longer display "[object object]" - -- New: Adds support for a THW Index calculation. This value is now available for all station types via the web tag <#THWindex> -- New: Adds support for Windguru uploads -- New: Adds Email support for Alarms - - Configured via Internet Settings (email server config), and Alarm Settings (from/to addresses for alarms, and a Test Email function) - - Creates a new section in Cumulus.ini: - [SMTP] - ServerName= - Port=587 - SSLOption=1 - RequiresAuthentication=0 - User= - Password= - Logging=0 - - Adds to the Alarms section in Cumulus.ini - [Alarms] - xxxxxAlarmEmail=0 - FromEmail= - DestEmail= - - Creates a new section in strings.ini, with email text - [AlarmEmails] -- New: WeatherCloud now supports uploading of Air Quality, Soil Moisture, and Leaf Wetness -- New: Adds support for two sets of Growing Degree Days data - - Configured via Station Settings|Growing Degree Days, visibility via Station Settings|Graphs|Data Series Visibility|Degree Days -- New: Adds support for Temperature Sum - annual running total of daily average temperatures - - Configured via Station Settings|Temperature Sum, visibility via Station Settings|Graphs|Data Series Visibility|Temperature Data -- New: The graphs pages - both dashboard and default web site - now set a page hash value depending on the graph being shown. - Benefit = you can now link directly to a particular graph to show on page load -- New: FineOffset stations now report a warning if the console logger interval does match the Cumulus MX interval -- New: FineOffset experimental feature to set the console logger interval to match Cumulus logging interval -- New: JavaScript encoded web tags. These tags were previously only available as HTML entity encoded strings: - <#latitudeJsEnc>, <#longitudeJsEnc> - <#locationJsEnc>, <#longlocationJsEnc> - <#forecastJsEnc>, <#cumulusforecastJsEnc>, <#wsforecastJsEnc> - <#currcondJsEnc>, -- New: Web tags with no HTML entity encoding: - <#tempunitnoenc>, <#altitudenoenc> - -- Changed: Moved FTP Logging option from Internet Settings to Program Settings to collect all the logging options in one place -- Changed: A Data Stopped state now stops all logging, MySQL and web activity -- Changed: Updates to various library components... - - Updated to latest versions: - - FTP: FluentFTP - - JSON: ServiceStack.Text - - Pointers: System.Runtime.CompilerServices.Unsafe - - MQTT: MQTTnet - - Removed/Added - - MySQL: Removed Devart, replaced with MySqlConnector - - MailKit: Added along with supporting packages MimeKit, and Portable.BouncyCastle -- Changed: FineOffset stations now attempt to reconnect the USB after a data stopped event. These stations now also keep retrying to connect to the station at start-up -- Changed: The MySQL real time table data retention settings reworked. -- Changed: Moved all third party web uploads from Internet Settings page to their own Third Party Settings page -- Changed: Internal optimisations -- Changed: JQuery library updated to v3.6.0 for both the Dashboard interface, and the default web site - - - -3.10.5 - b3122 -—————————————— -- Fix: Comma decimal issues with OpenWeatherMap and other third party uploads -- Fix: Occasional GW1000 crash on GetSensorIdsNew response timeout -- Fix: GW1000, WH40 rain gauge, removed battery status check as it does not send this information -- Fix: Adds missing WeatherCloud interval setting to Internet Settings -- Fix: Default web site Monthly NOAA reports before the year 2010 - -- New: Now determines the Ecowitt GW1000 device main sensor type (WH65/WH24) and prints system information to the log file -- New: Adds the free text station model to the Station settings page -- New: Adds a cache buster to default web site webpagedata.json downloads -- New: Adds optional UV index to Now, Today, Yesterday pages of the default web site -- New: All decimal web tag values now accept a dp=N and tc=y overrides for the number of decimal places -- New: Adds option to upload AQ data to WeatherCloud - - -3.10.4 - b3121 -—————————————— -- Fix: Issue introduced in v3.10.3 decoding Ecowitt live data -- Fix: Catch real time FTP updates that have hung for more than 5 minutes and restart them. - - - -3.10.3 - b3120 -—————————————— -- Fix: Problem with setpagedata.js in "/legacy-webfiles" -- Fix: Ecowitt GW1000 and clone auto-discovery extended to WH2650 and Ambient clones -- Fix: Ecowitt auto-discovery broken after a recent Ecowitt firmware update -- Fix: Ecowitt WH45 battery status on firmware levels below 1.6.5 -- Fix: Blake-Larsen sunshine recorder, the SRsunshine.dat file was using user locale decode, it should always use dot decimal -- Fix: MQTT only read the Update template file at start-up or if the filename was changed. It now reads the file at every update -- Fix: Davis WLL was not calculating chill hours and heating/cooling degree days on catch-up -- Fix: Add missing COM port setting for OS WM918/WMR928 stations - -- New: Adds Records Began Date to - Station Settings|General|Advanced -- New: Adds support for GW1000 firmware 1.6.6 -- New: Default web site - You can now use the "data-cmxdata" attribute on any HTML element, not just spans. BUT note, ALL the innerHTML will get replaced with the JSON data. -- New: Two new web tags that HTML encode the station description strings - <#locationenc>, <#longlocationenc> -- New: Adds a NOAA Reports page to the default web site -- New: Adds support for the EcoWitt WH35 8 channel leaf wetness sensor - - Note that only channels 1-4 will be displayed on the dashboard - - Extends the leaf wetness web tags with <#LeafWetness5> to <#LeafWetness8> added - - samplestrings.ini adds leaf wetness captions 5-8 -- New: Experimental - Enables Battery Low alarm for WMR100/WMR928 stations - -- Changed: The Default web site menu system has been rewritten. It is now all defined in the file menu.js - - -3.10.2 - b3117 -—————————————— -- Fix: Improve the AWEKAS fall back for upload interval to go right back to 5 minutes in two stages -- Fix: Occasional corrupt files output that have processed web tags in them -- Fix: Error creating the NOAA Year report for some people -- Fix: Missing station location on gauges.htm web site page - -- New: Default website now removes the "Current Conditions" element if the value is blank -- New: Default web site now auto updates the index.htm and today.htm pages every minute - -- Change: The current conditions is now HTML encoded in case it contains illegal characters - -- Updated default web site files - \web\websitedataT.json - \webfiles\index.htm - \webfiles\gauges.htm - \webfiles\js\setpagedata.js - - - -3.10.1 - b3116 -—————————————— -- Fix: Bug in temperature conversions introduced in v3.10.0 - - -3.10.0 - b3115 -—————————————— -- Fix: Catch error creating System Uptime counter on Windows -- Fix: The Local web server is now brought up before initialising the station. This allows you to correct a misconfigured station without resorting to editing the Cumulus.ini file. -- Fix: Diary Editor creating entries on the wrong day, and revamp the interface a bit -- Fix: WLL day average temp stats on historic catch-up -- Fix: GW1000 auto-discovery was triggering an erroneous IP address change -- Fix: Cumulus MX shutdown when running as a system service is now orderly -- Fix: The start-up ping now refreshes the DNS cache before every re-try to avoid using null entries cached before the internet comes up - -- New: Brand new default web site template courtesy of Neil Thomas. The original "legacy" web site is still included, but it has been moved to the /webfiles-legacy folder. - - The new web site is now data file driven as opposed to all pages being processed and uploaded. The legacy web site has also been updated to use this method. -- New: The previous Console log file is now copied to an "-old" file on start-up -- New: For Davis WLL stations using weatherlink.com, Cumulus now checks and reports the operational status of weatherlink.com on start-up and if an error occurs accessing the service -- New: Two web tags <#forumurl> and <#webcamurl>, which just return the respective URLs rather than the pre-canned HTML of <#forum> and <#webcam> -- New: The start of a Display Options section under Station, which controls what data is displayed on the default web site, implementing some of the Cumulus 1 options - - New web tags for this: <#Option_useApparent>, <#Option_showSolar>, <#Option_showUV> - -- Change: All the settings screens revamped, reorganised and extended. - - Many of the settings are now context sensitive, only showing items relevant to your station and configuration. - - Most of the previously config file "read-only" settings are now available in an Advanced section relevant to the configuration item. These settings are now read/write. - - Many new Cumulus.ini configuration entries, and some now depreciated. - - Virtually all the standard files that can be generated can now be controlled for enabling/disabling generation and FTP transfer independently. - - Added more graph data series controls. -- Change: The two graph config files availabledata.json and graphconfig.json are now only uploaded on program start-up and when the station config is changed. -- Change: Dayfile, Monthly Log, and Extra log file editors now have a selectable page length, and a goto-page feature -- Change: The default web site is now driven by a single data file (plus realtimegauges.txt), rather than every page being updated and uploaded each interval. -- Change: The various charting pages now hide buttons for graphs that do not contain any data - both on the dashboard and default web site. -- Change: Creation of the wxnow.txt file is now disabled by default for new installs -- Change: Clock sync (Davis VP2 & Instromet) now occurs at 2 minutes past the hour selected -- Change: Davis VP/VP2/Vue ReadReceptionStats now defaults to enabled for new installs -- Change: The default output file format is now UTF-8 for new installs - - -3.9.7 - b3107 -————————————— -- Fix: Unhandled exception in ProcessTemplateFile if cumulus cannot write to the output file -- Fix: Davis IP logger prevents Cumulus exiting if no station is found on the network -- Fix: Ecowitt AQ sensors not loading graph data from the log files on start-up -- Fix: Bug in dayfile parser that was using the time of highest wind speed as time of lowest humidity. - - The dayfile parser now outputs the first field number in a line that has failed to parse along with the field contents -- Fix: The GW1000 retains the previous lightning time/distance if the station resets the values to defaults -- Fix: Now opens the Davis WLL multi-cast listening port in shared mode to allow multiple copies of Cumulus to run on the same computer. - Only tested on Windows, may not work on macOS. - -- New: Adds a Select-a-Chart to the dashboard for recent data so you can plot different data on the same chart -- New: Adds a Select-a-Chart to the default web site for recent data - - Creates a new json data file - availabledata.json - that is uploaded to the remote site -- New: Adds a hot link to the Upgrade alarm on the dashboard -- New: Adds a start-up host ping escape time. Allows Cumulus to continue loading even if no ping response is received for the specified time - -- Change: The default web site gets a CSS change that dynamically alters the "content" width depending on the screen size. On smaller screens the white space either side will reduce in size. -- Change: The graph JSON data files are now always created locally, regardless of the Include Graph Data Files setting. That now setting only controls FTP like the standard files. - - -3.9.6 - b3101 -————————————— -- Fix: NOAA monthly report min temp/rain not formatting in dot decimal correctly -- Fix: Windows console log no longer being created -- Fix: Fix for exit handlers being called before cumulus instance is initialised causing an unhandled exception -- Fix: Airlink auto-discovery/update when you have more than one Airlink device -- Fix: Upgrade & Spike alarm latch hours not being saved/read correctly. You will need to check the values after installing this release. -- Fix: Rework both the interface graphs and default web site graphs JavaScript files -- Fix: SampleStrings.ini changes/fixes. The following entries should not longer be quoted - Davis Forecast strings and CO₂ captions - -- Change: Recent graph hours now defaults for new installs to 72 hours instead of 24 -- Change: All alarm latch hours now default to 24 hours - - -3.9.5 - b3100 -————————————— -- Fix: Alarms being cleared after their defined latch time even if latching was disabled -- Fix: Adds missing dew point calculation for GW1000 extra T/H sensors 1-8 -- Fix: Potential Object reference not set to an instance of an object fault on initial configuration [Goran Devic] -- Fix: Failed connections to some SFTP servers - Renchi.SshNet library reverted to older version -- Fix: Improved accessibility of the "Extra web files" page -- Fix: Tweak to Sunshine hours counter reset logic (primarily for Imet stations during a power cycle) -- Fix: Davis Console battery low threshold changed from 4.0 V to 3.5 V -- Fix: Rework station rain counter reset logic. Cope with Davis station start-up on the first day of new rain season which is not January - -- New: More comprehensive support for the Ecowitt WH45 CO₂ sensor... - - Added to Extra Sensors page - - Added to Strings.ini - - Can be selected as the primary AQ sensor - - New web tags: - <#CO2-pm2p5>, <#CO2-pm2p5-24h>, <#CO2-pm10>, <#CO2-pm10-24h>, <#CO2-temp>, <#CO2-hum> - - Added to ExtraLog file, columns for: CO2, CO2Avg, pm2.5, pm2.5Avg, pm10, pm10Avg, Temp, Hum -- New: Improved GW-1000 device auto-discovery, after initial discovery or user input of the IP address it locks onto the MAC address for further changes in IP - If multiple potential devices are found they are now listed and the user must add the appropriate IP into the config and restart Cumulus. - Discovery is now run whenever a Data Stopped condition is detected (and periodically thereafter until the data resumes) - New Cumulus.ini setting: - [GW1000] - MACAddress= -- New: Adds a browser cache time limit of 5 minutes to the Admin interface static files -- New: NOAA reports settings now have an option to force "dot" decimal points, this overrides your locale setting - New Cumulus.ini setting: - [NOAA] - UseDotDecimal=0 -- Tweaks the charts for both the admin interface and the default web site -- Rework of Airlink sensor implementation - no functional changes - - -3.9.4 - b3099 -————————————— -- Fix: WMR100 stations generated empty daily graph data files on start-up -- Fix: System uptime web tag for Linux systems - maybe! -- Fix: Historic charts wind run tooltip suffix -- Fix: High Min Temp and Low Max Temp records editors were not displaying and allowing input of the time -- Fix: This Month/This Year records editors were showing records for the entire day file rather than the specific period -- Fix: GW1000 station dewpoint calculation incorrect on first packet read at start-up - -- New: Settings page: Program Settings. Moved Debug/Data logging options and Prevent Second Instance to here from Station Settings. - Note: These settings still remain in the [Station] section of the Cumulus.ini file for backwards compatibility -- New: Two settings to delay the start-up of Cumulus MX. Both added to the new Program Settings page. - Start-up Host PING: Specify a remote hostname or IP address, Cumulus will wait for a successful PING from this address before continuing - Start-up Delay: Specify a fixed time in seconds to delay the start-up. The delay will be executed after the PING check is one is specified - Start-up Delay Max Uptime: Specify a maximum system uptime in seconds after which the delay will no longer be applied. Set to zero to always delay - New section in Cumulus.ini: - [Program] - StartupPingHost= // Default is no remote host - enter a host name or IP address - StartupDelaySecs=0 // Default is no delay = 0 - StartupDelayMaxUptime=300 // Default is 300 seconds (5 minutes) -- New: FTP Now - Adds a new option to regenerate the daily graph data files (if enabled), and include them in the manually invoked FTP session -- New: Adds support for OpenWeatherMap uploads (and catch-up). Using an existing station if the id is supplied, or your API key only has one station defined. - If no stations are defined CMX automatically creates a new one using your station details and starts uploading to it. - - - -3.9.3 - b3098 -————————————— -- Fix: Records check was triggering for very small value changes in derived values such as feels like temps -- Fix: Catch uncaught exception on SFTP disconnect -- Fix: FTP was inconsistent in empty remote server folder handling for real-time and interval files -- Fix: AirLink was not using the local vs WLL API data correctly -- Fix: EU CAQI AQI index calculations (based on the City/Urban scale) -- Fix: US EPA AQI calculation error of values between 51 and 100 -- Fix: Removes redundant alldailywdirdata.json file from daily uploads -- Fix: Corrects incorrect/invalid JSON being created in the alldailytempdata.json file when the dayfile contains blank entry's for dew point -- Fix: Attempted FTP uploads of files that do not exist caused the FTP session to become non-functional -- Fix: The monthly data log file editor erroring with 404 - -- New: You can now use "" in the extra files local filename to copy/upload the latest AirLink data log file -- New: You can now define your Indoor AirLink as the primary AQ sensor. Note this only applies to the admin interface, its data will not be - uploaded to the default web site -- New: Adds Cumulus MX update available alarm to the dashboard - enabled by default. This also has an associated web tag <#UpgradeAlarm> -- New: If enabled, the daily graph data files are now created at start-up and uploaded at first interval FTP. - Previously you had to wait for the first EOD rollover. -- New: If a FineOffset station is not found, MX now dumps a list of all the USB devices it can see to the MXdiags log - - - -3.9.2 - b3097 -————————————— -- Fix: Change log messages for number of SQL rows affected to Debug only messages -- Fix: AirLink web tags no longer error if the sensor is not enabled - all now return "--" -- Fix: Australia NEPM AQIs to allow values greater than 101 -- Fix: Canada AQHI - still based on PM2.5 3 hour values only -- Fix: Dutch and Belgian AQI calculations -- Fix: Default web site temperature Trends graph yAxis issue -- Fix: Broken logfile, extralogfile, and dayfile editors in b3096 -- Fix: Improve Instromet stations error handling of out of sequence responses -- Fix: Extra files without the EOD flag were not being transferred during the EOD upload period (first after rollover) - - -- Adds Notifications to Alarms - Note: Cumulus notifications only work by default if you run the browser on the same machine as Cumulus and connect using the - url: http://localhost:8998 - In order to get them working on a remote machine you will have to change an advanced flag in your browser. - This is at your own risk as advanced flags are unsupported features in Chrome and Edge. - In Chrome or Edge, open the url - "about:flags" - Find the entry "Insecure origins treated as secure" and enable it - The add the url you use to remotely access the Cumulus dashboard - eg. http://192.168.1.23:8998 - Then click the button to restart your browser. Notifications should now work. -- Adds Latch times to Alarms. You can now specify a period in hours that an alarm will remain active after the event that tripped it has reset. -- The default value for "DavisIncrementPressureDP" is changed from true to false - ** The effect of this is that for Davis VP/VP2 stations, the default number of decimal places used for pressure values changes - from 2 dp (hPa/mb) and 3 dp (kPa, inHg) to 1 dp (hPa/mb) and 2 dp (kPa, inHg) - ** If you wish to retain the previous behaviour, then you MUST add the setting "DavisIncrementPressureDP=1" to your Cumulus.ini file -- Wind Rose is now populated from data files at start-up (last 24 hours) -- New graphs option to show/hide sunshine hours data -- New admin interface historic graphs (and associated API) -- New default web site page to show historic graphs -- Adds Air Quality to Recent Graphs in Admin Interface and default web site - configure a primary AQ sensor in Station.Options -- Adds Air Quality upload to WeatherUnderground. Enable in WeatherUnderGround settings, and configure a primary AQ sensor in Station.Options -- Adds Air Quality upload to AWEKAS. Enable in AWEKAS settings, and configure a primary AQ sensor in Station.Options -- Adds ability to define the number of decimal places used for various weather units via read-only settings in Cumulus.ini. - The full list of existing and new values is: - [Station] - WindSpeedDecimals=1 // Existing entry - WindSpeedAvgDecimals=1 // Existing entry - WindRunDecimals=1 // NEW - SunshineHrsDecimals=1 // Existing entry - PressDecimals= // NEW [hPa/mb=1, inHg=2] - RainDecimals= // NEW [mm=1, in=2] - TempDecimals=1 // NEW - UVDecimals=1 // NEW - AirQualityDecimals=1 // NEW - affects AQI values only, not PM values -- Lots of internal code fettling -- New Cumulus.ini read-only entry to control the data read rate for Instromet stations. The value is in milliseconds, default is 500. - [Station] - ImetReadDelay=500 -- Admin interface tweaks -- Change WUnderground Password field name to Station Key in settings - - -3.9.1 - b3096 -————————————— -- Fix: AirLink - Limit the number of decimal places in the AirLink log file -- Fix: AirLink - The web tags for 1hr average AQI values were picking up the latest reading value in error -- Fix: Send rounded wind speed to Weather Underground/Windy/PWS/WoW if Cumulus "Round Wind Speeds" is set -- Fix: Replaces a bad copy of trendsT.htm that crept into b3095 -- Fix: Bug in wind spike removal that prevented it working -- Fix: Airlink caused premature day rollover processing during catch-up -- Workaround for the Chromium browser CSS bug that adds scroll bars to the settings screens -- Adds a new AirLink Sensors page to the admin interface -- Adds Netherlands LKI, and Belgian BelAQI AQI scales [6=Netherlands=LKI, 7=Belgium-BelAQI] to AirLink devices -- All AirLink AQI web tags now support the tc=y parameter, to properly truncate the AQI to an integer value -- Adds the ability to "latch" all alarms for a user configured number of hours -- Adds a new web tag <#CPUTemp> - only works on Linux, updates once a minute -- Change of JSON library from Newtonsoft to ServiceStack.Text -- Dashboard now updates every 2.5 seconds to match Davis stations (was 3 seconds) - -- Updated files - \AirLinkFileHeader.txt - NEW - \CumulusMX.exe - \MQTTnet.dll - \Newtonsoft.Json.dll - REMOVED - \ServiceStack.Text.dll - NEW - \System.Buffers.dll - NEW - \System.Memory.dll - NEW - \System.Runtime.CompilerServices.Unsafe.dll - NEW - \interface\*.html - ALL html files - \interface\css\cumulus.css - \interface\json\ExtraSensorOptions.json - NEW - \interface\json\ExtraSensorSchema.json - NEW - \interface\json\InternetSchema.json - \interface\json\InternetOptions.json - \interface\js\airlink.js - \interface\js\extrasensorsettings.js - NEW - \interface\alarmsettings.js - \interface\lib\alpaca\alpaca.css - \web\trendsT.htm - - - - -3.9.0 - b3095 -————————————— -- Fix: Running as a Windows service, now correctly reads command line parameters from the registry "ImagePath" field -- Fix: Cumulus forecast would sometimes start with a comma -- Fix: trendsT.htm location formatting -- Adds support for the Davis AirLink air quality monitor. - - Like the WLL it supports auto-discovery of the AirLink IP address, this can be disabled via config - - A new Settings page has been created for "Extra sensors", this is where you can configure the AirLink. - - Logs AirLink data to a new CSV data file "AirLinklog.txt" - see AirLinkFileHeader.txt for field information - - Historic catch-up is supported if the main station is a Davis WLL and the AirLink has been added as node to this station - - Historic catch-up for other stations will be a later release - - Creates a new Cumulus.ini file section - [AirLink] - WLv2ApiKey= - WLv2ApiSecret= - AutoUpdateIpAddress=1 - In-Enabled=0 - In-IPAddress=0.0.0.0 - In-IsNode=0 - In-WLStationId= - Out-Enabled=0 - Out-IPAddress=0.0.0.0 - Out-IsNode=0 - Out-WLStationId= - AQIformula=0 [0=US-EPA, 1=UK-COMEAP, 2=EU-AQI, 3=EU-CAQI, 4=Canada-AQHI, 5=Australia-NEMP] - - Adds new web tags for Davis AirLink - <#AirLinkFirmwareVersionIn>,<#AirLinkWifiRssiIn> - Requires Davis WeatherLink Live Pro subscription - <#AirLinkFirmwareVersionOut>,<#AirLinkWifiRssiOut> - Requires Davis WeatherLink Live Pro subscription - <#AirLinkTempIn>,<#AirLinkHumIn> - <#AirLinkTempOut>,<#AirLinkHumOut> - <#AirLinkPm1In>,<#AirLinkPm1Out> - <#AirLinkPm2p5In>,<#AirLinkPm2p5_1hrIn>,<#AirLinkPm2p5_3hrIn>,<#AirLinkPm2p5_24hrIn>,<#AirLinkPm2p5_NowcastIn> - <#AirLinkPm2p5Out>,<#AirLinkPm2p5_1hrOut>,<#AirLinkPm2p5_3hrOut>,<#AirLinkPm2p5_24hrOut>,<#AirLinkPm2p5_NowcastOut> - <#AirLinkPm10In>,<#AirLinkPm10_1hrIn>,<#AirLinkPm10_3hrIn>,<#AirLinkPm10_24hrIn>,<#AirLinkPm10_NowcastIn> - <#AirLinkPm10Out>,<#AirLinkPm10_1hrOut>,<#AirLinkPm10_3hrOut>,<#AirLinkPm10_24hrOut>,<#AirLinkPm10_NowcastOut> - <#AirLinkAqiPm2p5In>,<#AirLinkAqiPm2p5_1hrIn>,<#AirLinkAqiPm2p5_3hrIn>,<#AirLinkAqiPm2p5_24hrIn>,<#AirLinkAqiPm2p5_NowcastIn> - <#AirLinkAqiPm2p5Out>,<#AirLinkAqiPm2p5_1hrOut>,<#AirLinkAqiPm2p5_3hrOut>,<#AirLinkAqiPm2p5_24hrOut>,<#AirLinkAqiPm2p5_NowcastOut> - <#AirLinkAqiPm10In>,<#AirLinkAqiPm10_1hrIn>,<#AirLinkAqiPm10_3hrIn>,<#AirLinkAqiPm10_24hrIn>,<#AirLinkAqiPm10_NowcastIn> - <#AirLinkAqiPm10Out>,<#AirLinkAqiPm10_1hrOut>,<#AirLinkAqiPm10_3hrOut>,<#AirLinkAqiPm10_24hrOut>,<#AirLinkAqiPm10_NowcastOut> - <#AirLinkPct_1hrIn>,<#AirLinkPct_3hrIn>,<#AirLinkPct_24hrIn><#AirLinkPct_NowcastIn> - <#AirLinkPct_1hrOut>,<#AirLinkPct_3hrOut>,<#AirLinkPct_24hrOut><#AirLinkPct_NowcastOut> -- The Blake-Larsen sunshine recorder, and Hydreon RG-11 rain device configuration options can now be set on the new Extra Sensors page. - There are two new settings for RG-11 devices, if you use these devices you must set these via the configuration page as they will default to disabled. - [Station] - RG11Enabled=0 - RG11Enabled2=0 -- Adds new graph option - Solar Visible. Setting both Solar and UV to hidden means the solar graph data files will be empty. -- CumulusMX no longer re-writes the Cumulus.ini file on shutdown -- Adds the option to set the retained flag for the MQTT feeds - - New Cumulus.ini file settings - [MQTT] - UpdateRetained=0 - IntervalRetained=0 - -- Updated files - \CumulusMX.exe - \AirLinkFileHeader.txt - \interface - lots of changes, all *.html files, /scripts, and /json - \web\trendsT.htm - - -3.8.4 - b3094 -————————————— -- Fix: Unhandled exception in MySQL during the start-up catch-up process -- Fix: Graph buttons not wrapping on default web site trends page -- Fix: Calculating the trend rain rate when a rainfall multiplier is used -- Fix: Program Uptime when running under Mono and uptime > 24 days -- Fix: When running as a Windows service, the service was stopped unintentionally on non-critical system power events -- Fix: Davis VP receptions stats - ReceptionStats, FirmwareVersion and BaroRead functions rewritten -- Adds Ecowitt GW1000 decoding of WH34 battery status -- Adds Ecowitt GW1000 support for CO₂ sensor, no logging yet, but the decoding is there for when these sensors are released. - - New web tags <#CO2>, <#CO2-24h> -- Adds new Linux cumulusmx.service configuration file (thanks to @freddie), this allows you run Cumulus MX as a service under Linux using the - newer systemd control rather than the original init.d script supplied with v3.8.0 - - Edit the cumulusmx.service configuration file and change the path to match your Cumulus MX installation folder - - Copy cumulusmx.service to your /etc/systemd/system/ folder - - Cumulus can then be started and stopped with the following commands: - > systemctl start cumulusmx - > systemctl stop cumulusmx - - Status and restart are available too: - > systemctl restart cumulusmx - > systemctl status cumulusmx - - If you make a change to the unit file, you need to run the following command so that systemd re-reads your file: - > systemctl daemon-reload - - To have Cumulus start on reboot, issue the following command: - > systemctl enable cumulusmx - - Finally, to stop the service running on reboot, use the following: - > systemctl disable cumulusmx -- Adds a new web tag <#ProgramUpTimeMs> that returns the Cumulus run time in milliseconds - -- Updated files - \CumulusMX.exe - \web\trendsT.htm - -- New files - \MXutils\linux\cumulusmx.service - - -3.8.3 - b3093 -————————————— -- Fix: The dew point threshold limit (default value 40C) was incorrectly being applied to installations configured to use Fahrenheit - as if the value were in Fahrenheit. -- Fix: Davis VP/VP2 force 1 minute barometer updates no longer working correctly after v3.8.2 protocol changes. - Note: this setting should only be used for VP stations, or VP2 stations with very old firmware. -- Fix: Last rain date not updating for Davis stations (fixes the mixed unit/bucket size update in 3.8.2) -- Adds two new Cumulus.ini file settings to control the number of decimal places used for wind speeds. These override the built-in defaults. - These settings are read-only, they can to be manually added ONLY if you need to use them. Add either or both settings as required. - [Station] - WindSpeedDecimals=[0|1|2] - WindSpeedAvgDecimals=[0|1|2] - -- Updated files - \CumulusMX.exe - - -3.8.2 - b3092 -————————————— -- Fix: Davis VP2 stations - LastRainTip not being updated correctly with mixed metric/imperial gauges/units -- Fix: Davis VP2 stations - Bug that prevented the seconds being read from the console clock -- Fix: Dashboard temp/press trend arrows not working in locales with comma decimals -- MXdiags log files are now rotated if they exceed 20MB in size, 15 log files are now retained -- For Davis VP2 stations, setting the console clock now checks the console time and only sets it if it - differs from the current time by greater than 60 seconds -- Davis VP2 stations protocols have been extensively rewritten to make them more efficient and less timing sensitive - Third party IP loggers such as meteobridge and WiFi logger should now work more reliably -- Enables PSK authentication when using SFTP. Options are now Password, PSK, PSK with Password fall-back - New Cumulus.ini entries... - [FTP site] - SshFtpAuthentication=password [password (default), psk, password_psk] - SshFtpPskFile= [PSK filename, must include the full path is the file is not in the CumulusMX folder] -- CMX calculated dew point is automatically enabled for GW1000 stations - -- Updated files - \CumulusMX.exe - \interface\js\dashboard.js - \interface\json\InternetOptions.json - \interface\json\InternetSchema.json - - -3.8.1 - b3091 -————————————— -- Fix: Davis TCP loggers failing to connect correctly -- Fix: NOAA reports not viewable in Admin interface when Cumulus started as a service -- Fix: Prevents an Exception on shutdown when running as a service - -- Updated files - \CumulusMX.exe - - -3.8.0 - b3090 -————————————— -- Fix: Malformed API JSON returned by api/tags/process.json if no data output is generated -- Fix: Crash on start-up on the 1st of the month between 00:00 and rollover hour when using 9am rollover -- Fix: AWEKAS Sunshine hours were being truncated to integer hours -- New feature: Cumulus MX now supports running as a system service - - When running as a service the normal console output is logged to a file: \MXdiags\ServiceConsoleLog.txt - - On Windows - - To set Cumulus up as a service run it as an Administrator using the command line parameter -install - - To remove the Cumulus system service run it as an Administrator using the command line parameter -uninstall - - The service will stop itself if it detects that the computer is going into standby - - To add parameters to the service when it starts automatically you will have to edit the registry - HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\CumulusMX\ImagePath - Add the parameters after the quoted executable string. Eg. - "C:\CumulusMX\CumulusMX.exe" -port 9000 - - The service can be stopped/started using the usual utilities: - The Services applet:- services.msc (You can pass parameters using this tool, but they are not saved for future use) - The service command line tool:- sc start|stop|query CumulusMX [optional parameters for start-up here] - - On Linux - - Run Cumulus using the mono-service command instead of mono. You also need to add the command line parameter -service - > mono-service -l:/var/run/cmx.pid CumulusMX.exe -service (if pathed to CumulusMX folder) - or - > mono-service -l:/var/run/cmx.pid -d:<> CumulusMX.exe -service (from any location) - - To stop the service you use kill, which will perform an orderly shutdown of MX - > kill `cat /var/run/cmx.pid` -- New feature: - - When running on Linux there is now an init.d script to start/stop the service. - - The script can be found in the \MXutils\linux folder - - Copy it to your /etc/init.d folder - - Change the permissions to 0755 - > chmod 0755 /etc/init.d/cumulusmx.sh - - The script supports the following commands: start, stop, restart, status, removelock - - start: Starts MX as a service and creates a lock file to prevent it running again - - stop: Stops MX as a service and removes the lock file - - restart: Stops, then starts MX - - status: Reports if MX is running (only by checking the lock file at present), and dumps the latest console output to screen - - removelock: Removes a stray lock file if one gets left behind - - The command line would be: /etc/init.d/cumulusmx.sh start|stop|restart|status|removelock - - Script name: \MXutils\linux\cumulusmx.sh -- New feature: - - When running on Windows as a service, the service will stop if the computer goes to standby/hibernate mode. A script to set up - a Scheduled Task that will restart the service on resume from standby/hibernate is provided in the \MXutils\windows folder. - The script MUST be run with Administrator privileges. - - Script name: \MXutils\windows\CreateCmxResumeFromStandbyTask.ps1 -- Adds a new web tags for the Davis WLL device. - <#ConsoleSupplyV> - The WLL external supply voltage - <#MulticastBadCnt> - Count of multicast packets missed or have bad a CRC - <#MulticastGoodCnt> - Count of good multicast packets received - <#MulticastGoodPct> - Percentage of good multicast packets received -- On new installs of Cumulus MX, changes the default value of "WarnMultiple" from false to true -- Adds a download history limit of 50 bad packets for WMR200 station types. Previously it could get stuck in a loop downloading bad data. -- Adds Show UV-I to the graph hide/show options -- Updates ExportMySQL to include the new Humidex values from b3089 -- AWEKAS uploads updated to API v4 - now can include indoor data, soil temp/moisture 1-4, and leaf wetness 1-2. And it supports rapid - updates - minimum 15 seconds if you have a Stationsweb subscription, min 60 secs for Pro accounts, and 300 seconds for normal accounts - - IMPORTANT NOTE: THe AWEKAS interval has changed from being set in minutes, to being set in seconds. - You MUST update the AWEKAS interval in the Internet settings/Cumulus.ini file to reflect this change. - - New Cumulus.ini entries - [Awekas] - Language=en - SendIndoor=0 - SendSoilMoisture=0 - SendLeafWetness=0 -- Enabling/disabling and changing intervals for Twitter, WUnderground, Windy, PWS Weather, WOW, APRS, AWEKAS, Weather Cloud, MQTT from - Internet Settings now takes immediate effect and does not require a Cumulus restart. -- Adds a new Cumulus.ini file only, read-only setting - [Station] - SunshineHrsDecimals=1 -- Adds a new folder in Dist zip file: /MXutils - In here I will add the various scripts etc that are provided with the releases to update MySQL or aid moving from one release to the next. - See above for new Windows and Linux scripts when running as a service - - -- Updated files - \CumulusMX.exe - \ExportMySQL.exe - \interface\js\charts.js - \interface\json\InternetOptions.json - \interface\json\InternetSchema.json - \interface\json\StationOptions.json - \interface\json\StationSchema.json - \webfiles\js\cumuluscharts.js - --New files - \MXutils - - -3.7.0 - b3089 -————————————— -- Fix: Humidex was treated by MX as a temperature and incorrectly converted using the Celsius to Fahrenheit - conversion (if that was the user selected temperature unit). - Humidex is a dimensionless scale that should not be converted to retain meaning -- Fix: Thread unsafe graph data access -- Fix: Real time SFTP not recovering from some reconnection situations -- Fix: Add missing "rc" function from <#battery> web tag -- Fix: Remove units from Humidex value on the chart tooltip -- Fix: Station Settings page menu -- Fix: Davis WLL devices were flagging sensor contact lost for lost packets, now it only flags contact lost - when resynchronising with a transmitter -- Fix: Monthly records editor not functioning correctly on Humidex in beta1 -- Adds a logger interval check for Davis VP2 stations - prints warning to console and log file if there is a mismatch -- Now supports some of the Cumulus 1 Graph plotting options, configured via Station Settings - - Cumulus.ini file settings - [Graphs] - TempVisible=1 - InTempVisible=1 *new? - HIVisible=1 - DPVisible=1 - WCVisible=1 - AppTempVisible=1 - FeelsLikeVisible=1 - HumidexVisible=1 *new - InHumVisible=1 - OutHumVisible=1 -- Remove defunct WeatherBug upload from Cumulus MX -- Humidex is now a fully fledged variable... - - Daily, monthly, yearly highs - - Recent data - - Graph data - added to MX interface - - Records - - New Humidex web tags: - <#HighHumidexRecordSet> - <#humidexTH>, <#ThumidexTH> - <#humidexYH>, <#ThumidexYH> - <#MonthHumidexH>, <#MonthHumidexHT>, <#MonthHumidexHD> - <#YearHumidexH>, <#YearHumidexHT>, <#YearHumidexHD> - <#humidexH>, <#ThumidexH> - <#ByMonthHumidexH>, <#ByMonthHumidexHT> - <#RecentHumidex> -- Add Humidex to default web site trend graphs -- Now only calculates Humidex above 10C -- Day file updated with four additional fields or Humidex high, high time -- Monthly log file has an additional field for Humidex -- Daily SQL table has four additional columns for Humidex high, high time -- Monthly SQL table has an additional column for Humidex -- Add new web tag <#timeUnix> = provides current date/time as a Unix timestamp (like <#timeJavaScript>) -- The web tags <#SunshineHours> and <#YSunshineHours> now accept the decimal place parameter "dp=n" -- Adds a new web token processor API - details available separately -- Spike removal settings are now active for all station types - TAKE CARE! Use with caution -- Spike/limit logging is now enabled by default for new installs -- Adds a new alarm - Spike removal triggered - it stays active for 12 hours, or it is cleared if you save the calibration settings - - New web tag <#DataSpikeAlarm> - - New Cumulus.ini file settings... - [Alarms] - DataSpikeAlarmSet=0 - DataSpikeAlarmSound=0 - DataSpikeAlarmSoundFile=alarm.mp3 -- New "Limits" implemented. Now MX ignores temperature, dew point, pressure, and wind values outside sensible value ranges from any station type (these also trigger a spike alarm) - - Set via the Calibration settings screen - - New Cumulus.ini file settings... - [Limits] - TempHighC=60 - TempLowC=-60 - DewHighC=40 - PressHighMB=1090 - PressLowMB=870 - WindHighMS=90 -- New web tags for latest build notification - these require internet access to be meaningful - <#NewBuildAvailable> - returns "1" or "0" - <#NewBuildNumber> - returns the latest available Cumulus MX build number - - -- Updated files - \CumulusMX.exe - \dayfileheader.txt - \monthlyfileheader.txt - \interface\alarmsettings.html - \interface\alltimerecseditor.html - \interface\index.html - \interface\monthlyrecseditor.html - \interface\stationsettings.html - \interface\thismonthrecseditor.html - \interface\thisyearrecseditor.html - \interface\js\alarmsettings.js - \interface\js\alltimerecseditor.js - \interface\js\charts.js - \interface\js\dashboard.js - \interface\js\datalogs.js - \interface\js\dayfileeditor.js - \interface\js\monthlyrecseditor.js - \interface\js\thismonthrecseditor.js - \interface\js\thisyearrecseditor.js - \interface\json\CalibrationOptions.json - \interface\json\CalibrationSchema.json - \interface\json\InternetOptions.json - \interface\json\InternetSchema.json - \webfiles\js\cumuluscharts.js - - -3.6.12 - b3088 -—————————————— -- Fix Davis stations not downloading historic logger data (in b3087) - -- Updated files - \CumulusMX.exe - - -3.6.11 - b3087 -—————————————— -- Fix Davis TCP logger connections not timing out occasionally -- Fix heading on interface Now page, remove units from Humidex -- Fix FTP log file handling in Extra Files, with EOD option on the first of the month -- Add Feels Like to the default web site trends temperature graph -- Add Extra Sensors log file to the backup routine -- Add previous months log files (monthly and extra) to the backup on the first of the month -- Add "" tag to Extra Web Files to specify the variable extra log file name -- Improve web tag token parser performance -- Cumulus (Zambretti) forecast now works with localised compass points -- Internal optimisations (watch out for new issues!) -- Uplift the SFTP component from a 2016 version to new beta version - supports more encryption methods and key file formats -- Further additions to shutdown code for all stations -- Adds new web tag <#RecentFeelsLike> - -- Updated files - \CumulusMX.exe - \Renchi.SshNet.dll - \interface\now.html - \webfiles\js\cumuluscharts.js - - -3.6.10 - b3086 -—————————————— -- Fix for Feels Like calculation broken in previous release -- Fix for Davis WLL wind values when using units other than mph -- Fix for poor performance of wind direction charts on the MX interface and base web site -- Make end of day SQL inserts asynchronous -- Use a fixed timestamp for all EOD operations - -- Updated files - \CumulusMX.exe - \interface\charts.html - \interface\js\charts.js - \web\trendsT.htm - \webfiles\js\cumuluscharts.js - -3.6.9 - b3085 -————————————— -- RELEASE WITHDRAWN - - -3.6.8 - b3084 -————————————— -- Simplify realtime SFTP error detection and recovery -- Change the default web site Gauges page to not show pop-up graphs by default -- Fix for Ecowitt GW1000 stations when sensors go offline/online (wind and rain values) -- Fix for GW1000 stations wind gust values when using units other than "mph" -- Fix for GW1000 stations with WH34 type sensors and firmware 1.6.0 or later. You *must* now use firmware 1.6.0+ with WH34 devices -- Fix crash when creating the graph JSON files when file in use by FTP -- Fix for rc=y parameter not working with the <#intemp> web tag -- Fix low contrast menus on admin interface -- Fix HighCharts theme on admin interface Charts page, and default web site Trends page -- Fix for web tags <#daylength> and <#daylightlength> to display "24:00" if they last all day (they still allow custom formats) - -- Updated files - \CumulusmX.exe - \interface\charts.html - \interface\css\cumulus.css - \web\trendsT.htm - \webfiles\lib\steelseries\scripts\gauges.js - - -3.6.7 - b3083 -————————————— -- Add catches for real time MySQL updates and all real time file failures -- Adds Station (Absolute) and Altimeter pressure values for Davis WLL stations - -- Updated files - \CumulusMX.exe - - -3.6.6 - b3082 -————————————— -- Change ini files to use 17 significant figures for decimal values (up from 15) -- Fix for Davis WLL health data decoding when the WLL is LAN attached -- Fix for real time SFTP not reconnecting after failure - -- Updated files - \CumulusMX.exe - - -3.6.5 - b3081 -————————————— -- Fix for sun rise/set and dawn/dusk calculations when there is one event but not the other in a single day -- Fix for realtime FTP timeout/recovery issues - -- Updated files - \CumulusMX.exe - - -3.6.4 - b3080 -————————————— -- Fix for Ctrl-C not being handled when running under Linux/mono. Now handles SIGTERM and console Ctrl-C -- Fix for realtime FTP getting stuck on "already in progress" -- Adds support for Ecowitt GW1000 WH34 8 channel "User" (soil and water) temperature sensors - New web tags <#UserTemp1> - <#UserTemp8> - ExtraLog file has eight new fields appended - UserTemp1-8 - -- Updated files - \CumulusMX.exe - \Extrafileheader.txt - \SampleStrings.ini - \interface\extra.html - \interface\js\extradatalogs.js - \interface\js\extrasensors.js - - -3.6.3 - b3079 -————————————— -- Reverts b3077 FluentFTP update -- Fix for the long standing random Cumulus.ini/today.ini corruption when shutting down on Windows -- Another fix for Oregon WMR928 extra temperature only sensors - - -- Updated files - \CumulusMX.exe - \FluentFTP.dll - - -3.6.2 - b3078 -————————————— -- Fix for badly formed realtime.txt in b3077 - - -- Updated files - \CumulusMX.exe - - - -3.6.1 - b3077 -————————————— -- Fix for Oregon WMR928 extra temperature only sensors -- Fix for yesterdays Feels Like values in Admin interface Today/Yesterday screen -- Adds Feels Like to realtime.txt file as field 59 -- Changes GW1000 default Lightning distance to 999 (all user units), and time to 1900-01-01 00:00:00 - The corresponding Webtags will output "--" and "---" respectively -- Adds a new web tag <#LastRainTip>, which unlike <#LastRainTipISO> will accept a date/time format string - - -- Updated files - \CumulusMX.exe - \FluentFTP.dll - - - -3.6.0 - b3076 -————————————— -- Fix for records editors failing to read log files from Cumulus 1 versions -- Fix for Ecowitt GW-1000 devices to bypass the auto-discovery mechanism if it is disabled in the config -- NOAA reports now include degree, minutes, seconds symbols -- Slightly enhanced program termination logging -- Implements highs/low/records for Feels like - - Changes to ini files to add Feels Like - Adds [FeelsLike] section to - today.ini, yesterday.ini, month.ini, year.ini, alltime.ini, monthlyalltime.ini - - Changes to log files - monthlog.txt - adds field 27, feels like - dayfile.txt - adds fields 47-50, high feels like, high time, low, low time - - Changes to the MYSQL database tables are required - Adds column FeelsLike to the Monthly table - Adds columns MaxFeelsLike, TMaxFeelsLike, MinFeelsLike, TMinFeelsLike to the Dayfile table - - New web tags - <#HighFeelsLikeRecordSet> - <#LowFeelsLikeRecordSet> - <#ByMonthFeelsLikeHT> - <#ByMonthFeelsLikeLT> - <#ByMonthFeelsLikeL> - <#ByMonthFeelsLikeH> - <#YearFeelsLikeHD> - <#YearFeelsLikeLD> - <#YearFeelsLikeHT> - <#YearFeelsLikeLT> - <#YearFeelsLikeH> - <#YearFeelsLikeL> - <#MonthFeelsLikeHD> - <#MonthFeelsLikeLD> - <#MonthFeelsLikeHT> - <#MonthFeelsLikeLT> - <#MonthFeelsLikeH> - <#MonthFeelsLikeL> - <#feelslikeH> - <#TfeelslikeH> - <#feelslikeL> - <#TfeelslikeL> - <#feelslikeYH> - <#TfeelslikeYH> - <#feelslikeYL> - <#TfeelslikeYL> - <#feelslikeTH> - <#TfeelslikeTH> - <#feelslikeTL> - <#TfeelslikeTL> - - Updated record editors - - Updated log file viewers/editors -- Adds battery and reception data for Davis WLL. It now logs battery and input voltages to the MXdiags. - These are updated every 15 minutes and require you to have a WeatherLink Pro subscription. - The WLL unlike the VP2 console provides individual data for each transmitter - - The following web tags have been updated to accept a "tx=n" parameter, where n=1-8 and equals the desired transmitter id. - Omitting the tx= parameter or using tx=0 makes the tag function as before for Davis VP2 systems - <#DavisTotalPacketsMissed tx=n> - <#DavisNumberOfResynchs tx=n> - <#DavisMaxInARow tx=n> - <#DavisNumCRCerrors tx=n> - - New web tags for WLL transmitter reception percentage and RSSI figure, these must be used with the tx=n parameter - <#DavisReceptionPercent tx=n> - defaults to tx=1, tx=0 is unused - <#DavisTxRssi tx=n> - defaults to tx=1, use tx=0 to get the WLL WiFi RSSI -- Updated ExportMySQL.exe to version 1.1.0 - - Incorporates the new Feels Like data - - Uses compass point "-" for Calm - - Reads customised compass points from strings.ini if set - - -- Updated files - \CumulusMX.exe - \dayfileheader.txt - \ExportMySQL.exe - \Extrafileheader.txt - \monthlyfileheader.txt - \interface\alltimerecseditor.html - \interface\extrawebfiles.html - \interface\index.html - \interface\monthlyrecseditor.html - \interface\now.html - \interface\thismonthrecseditor.html - \interface\thisyearrecseditor.html - \interface\todayest.html - \interface\js\alltimerecseditor.js - \interface\js\charts.js - \interface\js\datalogs.js - \interface\js\dayfileeditor.js - \interface\js\monthlyrecseditor.js - \interface\js\thismonthrecseditor.js - \interface\js\thisyearrecseditor.js - \interface\json\StationOptions.json - - - -3.5.4 - b3075 -————————————— -- Fix for bearing zero on the interface "Now" page -- Fix for admin interface charts popup -- Fix for "normal" Extra files not being FTP'd at rollover interval, only those flagged as EOD were being transferred -- Another attempt to rationalise the Moon Phase messages - each quarter (new, 1st, full, 3rd) should now show for approximately 12 hours either side of the event -- All web tags that produce decimal number output now support the "rc=y" option -- Additional Davis WLL health info dumped in the MXdiags log on start-up and every 10 minutes if debug logging is on (voltages, uptimes, WiFi RSSI etc) - Logs warnings to the command line console and log file if voltages are too low -- Adds BatteryLow Alarm for WLL (Console [if you have an API key] & Tx), VP2 (Console & Tx), GW1000 (Tx), and adds a new web tag <#BatteryLowAlarm> -- Adds new web tag <#feelslike> - calculated using the JAG/TI formula used in the UK, USA, Canada etc. Currently there are no stats for this value - -- Updated files - \CumulusMX.exe - \interface\alarmsettings.html - \interface\alltimerecseditor.html - \interface\index.html - \interface\now.html - \interface\todayest.html - \interface\js\alarmsettings.js - \interface\js\charts.js - \interface\js\dashboard.js - \interface\js\now.js - \mqtt\DataUpdateTemplate.txt - \mqtt\IntervalUpdate.txt - \webfiles\js\cumuluscharts.js - - - -3.5.3 - b3074 -————————————— -- Fix: Backs out changes that created bad file paths in b3073 - - -- Updated files - \CumulusMX.exe - \web\webfiles\lib\steelseries\scripts\gauges.js - - - -3.5.2 - b3073 -————————————— -- Fixes and improvements to MQTT processing -- Adds new Cumulus.ini setting for MQTT to force IPv4/IPv6 connectivity (default is System decides which to use) - [MQTT] - IPversion=0 (0=default, 4=IPv4, 6=IPv6) -- Adds Sensor Contact lost flag/alarm for Davis WLL devices -- GW1000 raw Lux value is now available via the <#Light> web tag like other Fine Offset stations -- Adds three new web tags... - <#timeJavaScript> - returns the current date/time in JavaScript milliseconds. Example use = "var dt = Date(<#timeJavaScript>)" - <#directionTM> - returns todays max wind gust direction as a compass point - <#directionYM> - returns yesterdays max wind gust direction as a compass point - - -- Updated files - \CumulusMX.exe - \MQTTnet.dll - - - -3.5.1 - b3072 -————————————— -- Fix for the "Stop second instance" option now working a bit too well, you could not disable it! -- Implements the "record set" web tags. These will be set from the time of the record until a timeout value (default 24 hours). - You can change the default timeout by adding a entry to Cumulus.ini - [Station] - RecordSetTimeoutHrs=24 - - The web tags enabled are: TempRecordSet, WindRecordSet, RainRecordSet, HumidityRecordSet, PressureRecordSet, HighTempRecordSet, - LowTempRecordSet, HighAppTempRecordSet, LowAppTempRecordSet, HighHeatIndexRecordSet, LowWindChillRecordSet, HighMinTempRecordSet, - LowMaxTempRecordSet, HighDewPointRecordSet, LowDewPointRecordSet, HighWindGustRecordSet, HighWindSpeedRecordSet, HighRainRateRecordSet, - HighHourlyRainRecordSet, HighDailyRainRecordSet, HighMonthlyRainRecordSet, HighHumidityRecordSet, HighWindrunRecordSet, LowHumidityRecordSet, - HighPressureRecordSet, LowPressureRecordSet, LongestDryPeriodRecordSet, LongestWetPeriodRecordSet, HighTempRangeRecordSet, LowTempRangeRecordSet - - -- Updated files - \CumulusMX.exe - - - -3.5.0 - b3071 -————————————— -- Fix to "Stop second instance" of Cumulus running -- Fix for hung update interval (S)FTP sessions getting hung -- Fix to <#moonage> web tag to improve accuracy -- Adds support for MQTT output - Two options, send an MQTT message whenever new data is received, or send a message at a fixed interval - The message format is defined in template files, using web tags, located in the \mqtt folder -- Tidy up of /interface folder to remove unused files -- Removal of Highcharts scripts from the distribution -- Add support for generating a Moon Phase image. This is disabled by default. The output image will be generated and - optionally FTP'd once an hour. The generated local image file is always \web\moon.png - [FTP site] - IncludeMoonImage=1 - - [Graphs] - MoonImageEnabled=1 - MoonImageSize=100 - MoonImageFtpDest=images/moon.png - - -- New files - \Licences-Additional.txt - \MQTTnet.dll - \mqtt\DataUpdateTemplate.txt - \mqtt\IntervalTemplate.txt - \web\MoonBaseImage.png - -- Updated files - \CumulusMX.exe - \FluentFTP.dll - \Newtonsoft.Json.dll - \interface\ - [Many changes, delete and replace] - \web\indexT.htm - \webfiles\js\cumuluscharts.js - - - -3.4.6 - b3070 -————————————— -- Fix for station wind chill on Davis WLL devices -- Fix for auto-discovered Davis WLL Station-Ids not being saved to the config file -- More robust failure handling for realtime FTP connections -- Additional diagnostics output in the console and log file for badly formed web tags - -- Updated files - \CumulusMX.exe - - - -3.4.5 - b3069 -————————————— -- Adds Editors for: Dayfile, Monthly Logs, Extra Logs -- Adds line numbers to the log file viewer/editors -- Widens the time windows for the Moons phase names -- Fix for <#MoonPercent> and <#MoonPercentAbs> always showing integer values even with the dp=n option - - -- Updated files - \CumulusMX.exe - \interface\ - - - -3.4.4 - b3068 -————————————— -- Fix for incorrect NOAA yearly report, annual averages for temperature and wind were calculated incorrectly -- Now detects invalid CumulusMX.exe command line parameters -- Adds a new command line parameter -debug. This switches on debug and data logging from the start-up of Cumulus MX. You no - longer have to edit Cumulus.ini to gather these diagnostics. - -- Updated files - \CumulusMX.exe - - - -3.4.3 - b3067 -————————————— -- Adds a new option to Davis WLL and Ecowitt GW1000 station settings to disable IP address auto-discovery. Use this option - if you have more than one of these devices on your network, then enter the IP address manually. - -- Updated files - \CumulusMX.exe - \interface\json\StationOptions.json - \interface\json\StationSchema.json - - - -3.4.2 - b3066 -————————————— -- Improved error handing for invalid Davis WLL Station Ids -- Improved error handling when the network connection to a Davis WLL is lost (and restored) -- Adds missing Data Stopped alarm to the Dashboard and Alarm Settings screens -- Adds auto-discovery for Ecowitt GW1000 devices IP addresses -- Adds DataStopped handling to Ecowitt GW1000 devices - -- Updated files - \CumulusMX.exe - \interface\alarmsettings.html - \interface\index.html - \interface\js\alarmsettings.js - \interface\js\dashboard.js - - - -3.4.1 - b3065 -————————————— -- Fix for WLL if you change the WL.com logging interval around a catch-up period -- Fix for gust values from WLL devices -- Add WLL broadcast data watchdog and warning, implements DataStopped flag -- Adds WLL Cumulus.ini readonly setting, AutoUpdateIpAddress, use this to switch off the WLL autodetection of IP - address. This is a workaround for a WLL firmware bug that does not update the IP address when it changes using DHCP. - AutoUpdateIpAddress=0 #default = 1 - -- Updated files - \CumulusMX.exe - - - -3.4.0 - b3064 -————————————— -- Adds the option for Davis WLL users who have a WL.com Pro subscription to use WL.com as a "logger" to catch up - missing data on Cumulus MX start-up. -- Adds to option to truncate the <#MoonAge> tag value to an integer value instead of rounding it. - Use <#MoonAge tc=y> - ideal if you use the tag for image selection -- Updates FTP Now so that it does a full file process and FTP cycle, previously it just ran the FTP process -- Adds a Cumulus MX version check at startup - if online - and logs a message in the console and diags when a newer - build is available -- Fixes the Monthly Records editor for dry/wet periods that end on the last day of a month being incorrectly recorded - against the following month. -- Fixes the Beaufort calculations - there were some rounding errors in edge cases. -- Fixes Davis VP2 and WLL that were not using peak speeds from LOOP2 (VP2) and live/historic data (WLL) when the Cumulus logging - interval is set to 10 minutes or more. -- Fix for FTPS on realtime FTP updates -- Adds new ini file only option to disable Explicit FTPS - ie use Implicit mode - DisableFtpsExplicit=1 - -- Updated files - \CumulusMX.exe - \interface\json\StationOptions.json - \interface\json\StationSchema.json - - -3.3.0 - b3063 -————————————— -- Adds support for SFTP (SSH FTP) - - Moves the FTP SSL option to the web server settings - - Adds SFTP to existing FTP, FTPS options -- Fixes Ecowitt Soil Temperature/Moisture/Leak detector channel numbering -- Fix for an occasional error in station logger data handling of today's rainfall during CMX start-up -- Fix for Ecowitt GW1000 Lightning data decode -- Fix for incorrect date on <#Snow*> web tags -- Now automatically fixes two Cumulus.ini changes from Cumulus 1 generated files - - Changes the [FTP Site] section name to [FTP site] - - Changes NOAA default monthly name (if still set) from "NOAAMO'mmyy'.txt" to "NOAAMO'MMyy'.txt" -- Additional diagnostic logging info for Lacrosse WS2300 stations - -- Updated files - \CumulusMX.exe - \FluentFTP.dll - \interface\json\InternetOptions.json - \interface\json\InternetSchema.json - -- New files - \Renci.SshNet.dll - - -3.2.6 - b3062 -————————————— -- Fixes monthly records editor for stations with a met day starting at 9am -- Adds range checks for latitude and longitude values - -- Updated files - \CumulusMX.exe - - -3.2.5 - b3061 -————————————— -- Adds This Month and This Year records editors -- Adds FTP Now function -- Fix to MonthlyAlltimeLog.txt to add line feeds -- Fix missing WLL station description from APRS data - -- Updated files - \CumulusMX.exe - \interface\.html - -- New files - \interface\ftpnow.html - \interface\thismonthrecseditor.html - \interface\thisyearrecseditor.html - \interface\js\thismonthrecseditor.js - \interface\js\thisyearrecseditor.js - - -3.2.4 - b3060 -————————————— -- Fix uncaught Web exceptions in Davis WLL -- Fix Monthly Records editor not saving updated date/time stamps -- Adds a log file for MonthlyAlltime.ini file changes - -- Updated files - \CumulusMX.exe - - -3.2.3 - b3059 -————————————— -- Adds the Cumulus.ini file to the files automatically backed up each day/program start -- Fixes to the Monthly Records editor monthly rainfall figures - -- Updated files - \CumulusMX.exe - \interface\alltimerecseditor.html - \interface\monthlyrecseditor.html - \interface\js\alltimerecseditor.js - \interface\js\monthlyrecseditor.js - - -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 -- Fix the dashboard for Internet Explorer -- Fix default website index page header not wrapping on small screens -- Fix for Davis stations connected via TCP/IP to detect failures and reopen the connection more quickly during loop data processing -- Adds Solar calibration settings offset and multiplier -- Updates the SampleStrings.ini file with the extra captions added in b3056 - -- Updated files - \CumulusMX.exe - \SampleStrings.ini - \HidSharp.dll - \interface\js\dashboard.js - \interface\json\CalibrationSchema.json - \web\indexT.htm - - -3.2.0 - b3056 -————————————— -- Adds support for Ecowitt GW1000 WiFi gateway -- New web tag <#GW1000FirmwareVersion> -- Added extra soil temp sensors 5-16 for GW1000 stations - - New web tags <#SoilTemp5-16> - - Value are logged to extra log file - - Added custom captions available in Cumulus.ini [SoilTempCaptions] : Sensor5-16 -- Added extra soil moisture sensors 5-16 for GW1000 stations - - New web tags <#SoilMoisture5-16> - - Values are logged to extra log file - - Added custom captions available in Cumulus.ini [SoilMoistureCaptions] : Sensors5-16 - - Units now change between cb for Davis and % for GW1000 sensors -- Added extra air quality sensors 1-4 for GW1000 stations - - New web tags <#AirQuality1-4> and <#AirQualityAvg1-4> - - Values are logged to extra log file - - Added custom captions available in Cumulus.ini [AirQualityCaptions] : Sensor1-4 and SensorAvg1-4 -- Added new web tags for <#LeakSensor1-4> to support GW1000 leak sensors - - No display or logging of these values is done -- Added new web tags for Lightning sensors on GW1000 stations - - <#LightningDistance> Distance to last strike (same units as wind run - miles/km/nm) - - <#LightningTime> Time of last strike (format customisable) - - <#LightningStrikesToday> Number of strikes since midnight - - Currently there is no logging or display of these values -- Enables alarms as per Cumulus 1 - - New Alarm page under Settings - - Alarms are shown visually on the dashboard - - Due to browser restrictions, alarm sounds on the browser page may require you to click a button on the first alarm in order to hear it. - - You can add the MX admin site to your browsers list of sites allowed to play sound automatically - - Your browser should "learn" that you want to allow sounds to play automatically - - Alarm sound files should be placed in the /interface/sounds folder, they must be a browser compatible format (mp3 are good) - - The alarm settings for the sound file should be just the filename without any path -- Add pressure multiplier calibration - - New ini file setting [Offsets] PressMult = 1.0 (default) -- Fix for the All Time Records editor hourly rain total from the monthly log file month -- Fixes missing Cumulus multipliers for Wind and Rain values on Davis WLL stations -- Changes All Time Records editor to only load data from the Day File by default. Monthly log file - processing is now optional as it can take a very long time on a slow machine e.g. Raspberry Pi -- Fix to catch badly formed Davis WLL broadcast messages -- Fix for Davis WLL edge cases producing an initial zero value wind chill on startup - -- Updated files - \CumulusMX.exe - \interface\json\StationOptions.json - \interface\json\StationSchema.json - \interface\.html - \interface\js\extrasensors.js - \interface\js\alltimerecseditor.js - \interface\js\dashboard.js - \interface\css\main.css - \interface\json\CalibrationSchema.json - -- New files - \interface\alarmsettings.html - \interface\js\alarmsettings.js - \interface\sounds\alarm.mp3 - - -3.1.2 - b3055 -————————————— -- Fix for the All Time Records editor monthly rain total from the monthly log file month -- Fix for some long timeouts in All Time Records editor - -- Updated files - \CumulusMX.exe - - - -3.1.1 - b3054 -————————————— -- Fixes WLL timestamps always being in UTC, now uses local time -- Adds web tags <#snowlying>, <#snowfalling>, both provide 1|0 responses -- Adds Current Conditions editor to admin interface -- Adds All Time Records editor to admin interface - - -- Updated files - \CumulusMX.exe - \interface\.html - --New files - \interface\currentcondeditor.html - \interface\alltimerecseditor.html - \interface\js\alltimerecseditor.js - \interface\lib\x-editable\ - \interface\img\loading.gif - - -3.1.0 - b3053 -————————————— -- Adds support for Davis WeatherLink Live device - - Supports Zero-Config, it should discover your WLL on the network (router support required) -- Adds support for Cumulus.ini file setting EWpressureoffset for Fine Offset stations as used in Cumulus1. - - This provides a manual override for the calculated absolute to relative pressure offset -- Adds reading of the interval Hi/Lo temperature readings when processing Davis logger archive records during catch-up -- Adds display and generation of NOAA Monthly and Yearly reports -- Reduces the Instromet live data read intervals to 1 second -- Applies "fix" for Mono 5.x generating short month names ending with "." as used for log file names - - -- Updated files - \CumulusMX.exe - \CumulusMX.exe.config - \interface\.html - \interface\json\StationOptions.json - \interface\json\StationSchema.json - -- New files - \Tmds.MDns.dll - - -3.0.2 - b3052 -————————————— -- Fixes Davis archive downloads from the the logger when the day rollover processing takes longer than 10 seconds. - This can happen on slow processors - Pi Zero for example - or if lengthy procedures are included - An extra archive processing run is scheduled for each day rollover that takes longer than 10 seconds - -- Updated files - \CumulusMX.exe - \CumulusMX.exe.config - - -3.0.1 - b3051 -————————————— -- Increases Davis DMPAFT Date/Time command timeout -- Much improved Davis serial port throughput (for USB and Serial loggers), should decrease the historic logger download time -- Adds Davis archive data processing progress indication -- Fixes firmware check for LOOP2 support -- Fixes a race condition on start-up of Davis VP2 stations without a logger. This could cause a crash in AstroLib.SolarMax() -- Fixes reading the Davis console clock after setting the time - -- Updated files - \CumulusMX.exe - - -3050 -———— -- Fixes MX not working with locales that use two character date separators - Eg. Croatia "29. 04. 19" - -- Updated files - \CumulusMX.exe - -3049 -———— -- Adds the ability to upload data to Windy.com - -- Updated files - \CumulusMX.exe - \interface\json\InternetOptions.json - \interface\json\InternetSchema.json - -3048 -———— -- You can now first time enable/disable Realtime FTP without having to restart CMX - -- Instromet stations now record and report rainfall (mm) and sunshine hours to 2 decimal places - -- Improved realtime FTP error handling - -- Improved Davis protocol handling - -- Fix Davis protocol mixing up LOOP1 and LOOP2 packets and consequently providing invalid rain and wind data. - -- Fix web tag <#YearLowDailyTempRangeD> broken in b3047 - -- Bug fixes to FTP Component, and internal changes to FTP transfer mechanism - -- Updated files - \CumulusMX.exe - \FluentFTP.dll - - - -3047 -———— -- Web token parser updated to cope with html tag characters "<>" in the format string. - - You can now do things like... - <#TapptempH format="dd' 'MMM' 'yyyy' at 'HH:mm''"> - which gives... - 04 Dec 2018 at 10:12 - - Note: that you have to use single quotes for HTML entity names, and they have to be escaped "\'" - -- New Davis Baud Rate setting - - Allows you to alter the speed of the serial connection to Davis loggers - - Configured manually in Cumulus.ini [Station] section - DavisBaudRate=19200 (default) Permitted values are 1200, 2400, 4800, 9600, 14400, 19200 - -- Added new option for the "Extra files" - End of Day - - Enabling this means that file will only be processed/copied/FTPed once a day during the end of day roll-over. - - There is a new Cumulus.ini file setting for each "extra" file associated with this setting - ExtraEOD[nn] - - Note there is currently no check between Realtime and End of Day settings, you could check both options and the file - will be processed at both the realtime interval AND end of day - which would not make much sense! - -- Improvement to Instromet logger protocol handling - -- Change the Fine Offset Synchronised Reads option to default to enabled - -- Change VP2 automatic disabling of LOOP2 to an advisory message, as the firmware version is not always detected. - -- Consistency: All record Value tags should now return '---' and Date tags '----' until they are first set. - -- The following web tags now support the "dp=N" "rc=y" parameters for the number of decimal places, and replace decimal commas with points. - #MoonPercent - #MoonPercentAbs - #MoonAge - -- Fix for Fine Offset & WMR100/200 stations on Mac operating systems (introduced in b3044) - -- Fix for invalid (extremely high) pressure readings from Fine Offset stations (thanks to 79669weather) - -- Fix to not updating the Instromet loggers memory pointer correctly - -- Fixed Weather Diary Time Zone issues - -- Bug fixes and performance improvements to the FTP component - -- Updated files - \CumulusMX.exe - \CumulusMX.exe.config - \FluentFTP.dll - \HidSharp.dll - \interface\js\diaryeditor.js - \interface\json\StationOptions.json - -- Removed files - \fastJSON.dll - - - -3046 -———— -- Weather Diary - - Added Weather Diary page to management interface - - Added diary.db file to daily backup files - - Removed diary.db from distribution (it is created on first use to avoid overwriting the file on CMX distro updates) - -- Web Tags - - Added <#snowdepth> tag processing - -- ET annual roll-over fix - -- Fix to TLS 1.2 FTPS of the 'periodic' files - - - -3045 -———— -- Internal Stuff - - Update the targeted .Net version to 4.5.2 (4.5 and 4.5.1 are no longer supported by Microsoft) - - Upgrade System.Net.FtpClient to replacement FluentFTP package - - Update Microsoft.Net.Http package to latest stable version - - Update fastJSON package to latest stable version - - Update embedIO package to latest stable version - - Remove Alchemy WS package from build - -- Davis TCP connections. - - Added additional error handling, should now be much more robust and attempt to reconnect on failure. - -- Astro calcs for Solar - - Added refraction correction. - - updated to add some extra terms. - -- Removed URL encode Twitter messages added in b3044 - -- Added second order humidity correction factor, works the same as the temperature. It has to be set manually in Cumulus.ini. - - Cumulus.ini - [Offsets] - HumMult2=0.0 - -- FTP Updates - - Now supports FTPS over TLS1.1 and 1.2 - - In Passive FTP, you can disable Enhanced Passive (EPSV) mode if it causes problems with your host. Some hosts are reporting they support it, but a firewall along the route cannot handle the connections. - Requires manually adding a new Cumulus.ini entry... - [FTP site] - DisableEPSV=1 - - RealtimeGaugesTxt is no longer automatically enabled for FTP. - -- Web Sockets are no longer on a separate port, it shares the same port as HTTP. - - The wsport command line switch is still recognised for backwards compatibility, but it is no longer used. - -- Fix Fine Offset with solar logger reading, now limited to the reduced number of logger entries available on solar stations. - -- Fix for Slovenian locale (and any other with a two character date separator) - - - -3044 -———— -- Added new solar calculation method "Bras" - - New Cumulus.ini entries... - SolarCalc=0 (0=Ryan-Stolzenbach, 1=Bras, default=0) - BrasTurbidity=2 (atmospheric turbidity factor (2=clear, 5=smoggy, default=2) - - Updated the Interface web files to reflect the new settings. - -- Updated HidSharp to ver 2.0.5 - - Now uses libudev1 for Fine Offset and WMR200 stations - -- Fixed corrupt/missing MySQL port causing CumulusMX to crash on start-up - -- Attempt to reconnect to Davis IP logger on connection failure (only a possible fix) - -- Updated the default forum URL to it's new home - -- Updated included website files to new URL - -- URL encode Twitter messages (I know Twitter is largely broken) - -- Davis VP2 - (Steve Loft) Added sanity checks for invalid wind speed/direction values - -- Davis VP2 - Added automatic disabling of the use of LOOP2 packets on firmware versions < 1.90 - -- Change RG11 devices to use a new Cumulus.ini file entry. The previous MX builds would not work on Linux. - For example: - RG11port=2 (depreciated) - RG11port2=3 - now use - RG11portName=COM2 (for Windows or /dev/ttyUSB2 for Linux) - RG11portName2=COM3 (for Windows or /dev/ttyUSB3 for Linux) - -- The default Comm port values are now set to either COM1 or /dev/ttyUSB0 depending on the platform. Hopefully this will prevent some of the confusion of new users. +3.11.2 - b3131 +—————————————— +- Fix: Send email failing if email logging is not enabled +- Fix: Solar Rad colour now displays as selected on first load in the Selectachart on the dashboard and default website +- Fix: Handlebars JS script updated due to severe security risk on previous version +- Fix: GW1000 for 433, 868, 915 MHz variants +- Fix: Bug in MySQL catch-up + + +- Change: Setting of ALL logging options via the GUI is now "sticky" - i.e. preserved across runs of Cumulus MX +- Change: Many of the main settings screens now alert to to the first invalid settings in a message, and the tree containing + the error is highlighted in red: + - Program Settings + - Station Settings + - Internet Settings + - Third Party Settings + - Extra Sensor Settings + - Calibration Settings + - NOAA Settings + - MySQL Settings +- Change: If any of the base units (wind, temp, rain, pressure) are changed via Station Settings (for instance during the initial configuration), then all the + thresholds and base values associated with that unit are reset to the defaults for the new unit +- Change: The main dashboard page now only shows alarm indicators for enabled alarms +- Change: Alarm Settings page now has added screen reader attributes (not visible on the page) +- Change: All HighCharts based pages (Dashboard and default web site) now use the latest stable release of HighCharts + +- New: Implements two new alarms for HTTP uploads failing, and MySQL uploads failing + - These plus Data Spike, have an additional setting via Cumulus.ini (for now) to set a trigger threshold. + This defaults to 1, so the alarm will trigger immediately. If set to 5, then it will take 5 trigger events happen within the Latch time to set the alarm. + The idea is to prevent an occasional error from triggering the alarm + Cumulus.ini settings: + [Alarms] + SpikeAlarmTriggerCount=1 + HttpUploadAlarmTriggerCount=1 + MySqlUploadAlarmTriggerCount=1 +- New: Web tags - <#HttpUploadAlarm>, <#MySqlUploadAlarm> +- New: Data Stopped, Data Spike,HTTP upload, MySQL upload emails now also report the error that triggered the alarm in the email text +- New: Adds two new variables for Extra Web Files - & to upload the last year and month reports. Like the day file variable + it only makes sense to use these with the EOD option +- New: You can now limit the real time MySQL table inserts to once a minute. Useful if you run a short real time interval (say 5 seconds), and do not want to + flood your MySQL real time table with many rows that hardly change. + + + +3.11.1 - b3130 +—————————————— +- Fix: Fix Test email logging "success" on failure +- Fix: Rollback FluentFTP package to previous 32.3.1 version due to crashes in Mono with newer versions + +- New: Pressure change in last 3 hours web tag - <#PressChangeLast3Hours> +- New: Enable additional Accessibility feature - configured via either Station Settings or Program Settings + The following pages now have some enhanced accessibility features: + - Program Settings + - Station Settings + - Internet Settings + - Third Party Settings + - Extra Sensor Settings + - Calibration Settings + - NOAA Settings + - MySQL Settings + Adds a new entry in Cumulus.ini + + + +3.11.0 - b3129 +—————————————— +- Fix: Remove THW Index in /web/websitedataT.json, and default web site index.htm +- Fix: OpenWeatherMap - create new station was not forcing the use of dot decimals for lat/long values +- Fix: WeatherCloud - date/time of reading should be UTC +- Fix: End of day backup now always runs at rollover +- Fix: FineOffset stations were not ignoring invalid wind speeds on historic catch up +- Fix: FineOffset stations historic catch up was failing when it reached a logger memory wrap around +- Fix: FineOffset stations historic catch up is now limited to the number of logger entries the console says are recorded +- Fix: FineOffset synchronise reads process improvements (attempt) +- Fix: Catch some errors that could occur in the start-up PING process +- Fix: MySQL connections that use encryption should now be supported (MySQL 8.0+ default) +- Fix: GW1000 System Information now correctly decodes 915 and 920MHz devices +- Fix: GW1000 occasional crash when an unexpected response was received +- Fix: All settings screens error handling messages now no longer display "[object object]" + +- New: Adds support for a THW Index calculation. This value is now available for all station types via the web tag <#THWindex> +- New: Adds support for Windguru uploads +- New: Adds Email support for Alarms + - Configured via Internet Settings (email server config), and Alarm Settings (from/to addresses for alarms, and a Test Email function) + - Creates a new section in Cumulus.ini: + [SMTP] + ServerName= + Port=587 + SSLOption=1 + RequiresAuthentication=0 + User= + Password= + Logging=0 + - Adds to the Alarms section in Cumulus.ini + [Alarms] + xxxxxAlarmEmail=0 + FromEmail= + DestEmail= + - Creates a new section in strings.ini, with email text + [AlarmEmails] +- New: WeatherCloud now supports uploading of Air Quality, Soil Moisture, and Leaf Wetness +- New: Adds support for two sets of Growing Degree Days data + - Configured via Station Settings|Growing Degree Days, visibility via Station Settings|Graphs|Data Series Visibility|Degree Days +- New: Adds support for Temperature Sum - annual running total of daily average temperatures + - Configured via Station Settings|Temperature Sum, visibility via Station Settings|Graphs|Data Series Visibility|Temperature Data +- New: The graphs pages - both dashboard and default web site - now set a page hash value depending on the graph being shown. + Benefit = you can now link directly to a particular graph to show on page load +- New: FineOffset stations now report a warning if the console logger interval does match the Cumulus MX interval +- New: FineOffset experimental feature to set the console logger interval to match Cumulus logging interval +- New: JavaScript encoded web tags. These tags were previously only available as HTML entity encoded strings: + <#latitudeJsEnc>, <#longitudeJsEnc> + <#locationJsEnc>, <#longlocationJsEnc> + <#forecastJsEnc>, <#cumulusforecastJsEnc>, <#wsforecastJsEnc> + <#currcondJsEnc>, +- New: Web tags with no HTML entity encoding: + <#tempunitnoenc>, <#altitudenoenc> + +- Changed: Moved FTP Logging option from Internet Settings to Program Settings to collect all the logging options in one place +- Changed: A Data Stopped state now stops all logging, MySQL and web activity +- Changed: Updates to various library components... + - Updated to latest versions: + - FTP: FluentFTP + - JSON: ServiceStack.Text + - Pointers: System.Runtime.CompilerServices.Unsafe + - MQTT: MQTTnet + - Removed/Added + - MySQL: Removed Devart, replaced with MySqlConnector + - MailKit: Added along with supporting packages MimeKit, and Portable.BouncyCastle +- Changed: FineOffset stations now attempt to reconnect the USB after a data stopped event. These stations now also keep retrying to connect to the station at start-up +- Changed: The MySQL real time table data retention settings reworked. +- Changed: Moved all third party web uploads from Internet Settings page to their own Third Party Settings page +- Changed: Internal optimisations +- Changed: JQuery library updated to v3.6.0 for both the Dashboard interface, and the default web site + + + +3.10.5 - b3122 +—————————————— +- Fix: Comma decimal issues with OpenWeatherMap and other third party uploads +- Fix: Occasional GW1000 crash on GetSensorIdsNew response timeout +- Fix: GW1000, WH40 rain gauge, removed battery status check as it does not send this information +- Fix: Adds missing WeatherCloud interval setting to Internet Settings +- Fix: Default web site Monthly NOAA reports before the year 2010 + +- New: Now determines the Ecowitt GW1000 device main sensor type (WH65/WH24) and prints system information to the log file +- New: Adds the free text station model to the Station settings page +- New: Adds a cache buster to default web site webpagedata.json downloads +- New: Adds optional UV index to Now, Today, Yesterday pages of the default web site +- New: All decimal web tag values now accept a dp=N and tc=y overrides for the number of decimal places +- New: Adds option to upload AQ data to WeatherCloud + + +3.10.4 - b3121 +—————————————— +- Fix: Issue introduced in v3.10.3 decoding Ecowitt live data +- Fix: Catch real time FTP updates that have hung for more than 5 minutes and restart them. + + + +3.10.3 - b3120 +—————————————— +- Fix: Problem with setpagedata.js in "/legacy-webfiles" +- Fix: Ecowitt GW1000 and clone auto-discovery extended to WH2650 and Ambient clones +- Fix: Ecowitt auto-discovery broken after a recent Ecowitt firmware update +- Fix: Ecowitt WH45 battery status on firmware levels below 1.6.5 +- Fix: Blake-Larsen sunshine recorder, the SRsunshine.dat file was using user locale decode, it should always use dot decimal +- Fix: MQTT only read the Update template file at start-up or if the filename was changed. It now reads the file at every update +- Fix: Davis WLL was not calculating chill hours and heating/cooling degree days on catch-up +- Fix: Add missing COM port setting for OS WM918/WMR928 stations + +- New: Adds Records Began Date to - Station Settings|General|Advanced +- New: Adds support for GW1000 firmware 1.6.6 +- New: Default web site - You can now use the "data-cmxdata" attribute on any HTML element, not just spans. BUT note, ALL the innerHTML will get replaced with the JSON data. +- New: Two new web tags that HTML encode the station description strings - <#locationenc>, <#longlocationenc> +- New: Adds a NOAA Reports page to the default web site +- New: Adds support for the EcoWitt WH35 8 channel leaf wetness sensor + - Note that only channels 1-4 will be displayed on the dashboard + - Extends the leaf wetness web tags with <#LeafWetness5> to <#LeafWetness8> added + - samplestrings.ini adds leaf wetness captions 5-8 +- New: Experimental - Enables Battery Low alarm for WMR100/WMR928 stations + +- Changed: The Default web site menu system has been rewritten. It is now all defined in the file menu.js + + +3.10.2 - b3117 +—————————————— +- Fix: Improve the AWEKAS fall back for upload interval to go right back to 5 minutes in two stages +- Fix: Occasional corrupt files output that have processed web tags in them +- Fix: Error creating the NOAA Year report for some people +- Fix: Missing station location on gauges.htm web site page + +- New: Default website now removes the "Current Conditions" element if the value is blank +- New: Default web site now auto updates the index.htm and today.htm pages every minute + +- Change: The current conditions is now HTML encoded in case it contains illegal characters + +- Updated default web site files + \web\websitedataT.json + \webfiles\index.htm + \webfiles\gauges.htm + \webfiles\js\setpagedata.js + + + +3.10.1 - b3116 +—————————————— +- Fix: Bug in temperature conversions introduced in v3.10.0 + + +3.10.0 - b3115 +—————————————— +- Fix: Catch error creating System Uptime counter on Windows +- Fix: The Local web server is now brought up before initialising the station. This allows you to correct a misconfigured station without resorting to editing the Cumulus.ini file. +- Fix: Diary Editor creating entries on the wrong day, and revamp the interface a bit +- Fix: WLL day average temp stats on historic catch-up +- Fix: GW1000 auto-discovery was triggering an erroneous IP address change +- Fix: Cumulus MX shutdown when running as a system service is now orderly +- Fix: The start-up ping now refreshes the DNS cache before every re-try to avoid using null entries cached before the internet comes up + +- New: Brand new default web site template courtesy of Neil Thomas. The original "legacy" web site is still included, but it has been moved to the /webfiles-legacy folder. + - The new web site is now data file driven as opposed to all pages being processed and uploaded. The legacy web site has also been updated to use this method. +- New: The previous Console log file is now copied to an "-old" file on start-up +- New: For Davis WLL stations using weatherlink.com, Cumulus now checks and reports the operational status of weatherlink.com on start-up and if an error occurs accessing the service +- New: Two web tags <#forumurl> and <#webcamurl>, which just return the respective URLs rather than the pre-canned HTML of <#forum> and <#webcam> +- New: The start of a Display Options section under Station, which controls what data is displayed on the default web site, implementing some of the Cumulus 1 options + - New web tags for this: <#Option_useApparent>, <#Option_showSolar>, <#Option_showUV> + +- Change: All the settings screens revamped, reorganised and extended. + - Many of the settings are now context sensitive, only showing items relevant to your station and configuration. + - Most of the previously config file "read-only" settings are now available in an Advanced section relevant to the configuration item. These settings are now read/write. + - Many new Cumulus.ini configuration entries, and some now depreciated. + - Virtually all the standard files that can be generated can now be controlled for enabling/disabling generation and FTP transfer independently. + - Added more graph data series controls. +- Change: The two graph config files availabledata.json and graphconfig.json are now only uploaded on program start-up and when the station config is changed. +- Change: Dayfile, Monthly Log, and Extra log file editors now have a selectable page length, and a goto-page feature +- Change: The default web site is now driven by a single data file (plus realtimegauges.txt), rather than every page being updated and uploaded each interval. +- Change: The various charting pages now hide buttons for graphs that do not contain any data - both on the dashboard and default web site. +- Change: Creation of the wxnow.txt file is now disabled by default for new installs +- Change: Clock sync (Davis VP2 & Instromet) now occurs at 2 minutes past the hour selected +- Change: Davis VP/VP2/Vue ReadReceptionStats now defaults to enabled for new installs +- Change: The default output file format is now UTF-8 for new installs + + +3.9.7 - b3107 +————————————— +- Fix: Unhandled exception in ProcessTemplateFile if cumulus cannot write to the output file +- Fix: Davis IP logger prevents Cumulus exiting if no station is found on the network +- Fix: Ecowitt AQ sensors not loading graph data from the log files on start-up +- Fix: Bug in dayfile parser that was using the time of highest wind speed as time of lowest humidity. + - The dayfile parser now outputs the first field number in a line that has failed to parse along with the field contents +- Fix: The GW1000 retains the previous lightning time/distance if the station resets the values to defaults +- Fix: Now opens the Davis WLL multi-cast listening port in shared mode to allow multiple copies of Cumulus to run on the same computer. + Only tested on Windows, may not work on macOS. + +- New: Adds a Select-a-Chart to the dashboard for recent data so you can plot different data on the same chart +- New: Adds a Select-a-Chart to the default web site for recent data + - Creates a new json data file - availabledata.json - that is uploaded to the remote site +- New: Adds a hot link to the Upgrade alarm on the dashboard +- New: Adds a start-up host ping escape time. Allows Cumulus to continue loading even if no ping response is received for the specified time + +- Change: The default web site gets a CSS change that dynamically alters the "content" width depending on the screen size. On smaller screens the white space either side will reduce in size. +- Change: The graph JSON data files are now always created locally, regardless of the Include Graph Data Files setting. That now setting only controls FTP like the standard files. + + +3.9.6 - b3101 +————————————— +- Fix: NOAA monthly report min temp/rain not formatting in dot decimal correctly +- Fix: Windows console log no longer being created +- Fix: Fix for exit handlers being called before cumulus instance is initialised causing an unhandled exception +- Fix: Airlink auto-discovery/update when you have more than one Airlink device +- Fix: Upgrade & Spike alarm latch hours not being saved/read correctly. You will need to check the values after installing this release. +- Fix: Rework both the interface graphs and default web site graphs JavaScript files +- Fix: SampleStrings.ini changes/fixes. The following entries should not longer be quoted - Davis Forecast strings and CO₂ captions + +- Change: Recent graph hours now defaults for new installs to 72 hours instead of 24 +- Change: All alarm latch hours now default to 24 hours + + +3.9.5 - b3100 +————————————— +- Fix: Alarms being cleared after their defined latch time even if latching was disabled +- Fix: Adds missing dew point calculation for GW1000 extra T/H sensors 1-8 +- Fix: Potential Object reference not set to an instance of an object fault on initial configuration [Goran Devic] +- Fix: Failed connections to some SFTP servers - Renchi.SshNet library reverted to older version +- Fix: Improved accessibility of the "Extra web files" page +- Fix: Tweak to Sunshine hours counter reset logic (primarily for Imet stations during a power cycle) +- Fix: Davis Console battery low threshold changed from 4.0 V to 3.5 V +- Fix: Rework station rain counter reset logic. Cope with Davis station start-up on the first day of new rain season which is not January + +- New: More comprehensive support for the Ecowitt WH45 CO₂ sensor... + - Added to Extra Sensors page + - Added to Strings.ini + - Can be selected as the primary AQ sensor + - New web tags: + <#CO2-pm2p5>, <#CO2-pm2p5-24h>, <#CO2-pm10>, <#CO2-pm10-24h>, <#CO2-temp>, <#CO2-hum> + - Added to ExtraLog file, columns for: CO2, CO2Avg, pm2.5, pm2.5Avg, pm10, pm10Avg, Temp, Hum +- New: Improved GW-1000 device auto-discovery, after initial discovery or user input of the IP address it locks onto the MAC address for further changes in IP + If multiple potential devices are found they are now listed and the user must add the appropriate IP into the config and restart Cumulus. + Discovery is now run whenever a Data Stopped condition is detected (and periodically thereafter until the data resumes) + New Cumulus.ini setting: + [GW1000] + MACAddress= +- New: Adds a browser cache time limit of 5 minutes to the Admin interface static files +- New: NOAA reports settings now have an option to force "dot" decimal points, this overrides your locale setting + New Cumulus.ini setting: + [NOAA] + UseDotDecimal=0 +- Tweaks the charts for both the admin interface and the default web site +- Rework of Airlink sensor implementation - no functional changes + + +3.9.4 - b3099 +————————————— +- Fix: WMR100 stations generated empty daily graph data files on start-up +- Fix: System uptime web tag for Linux systems - maybe! +- Fix: Historic charts wind run tooltip suffix +- Fix: High Min Temp and Low Max Temp records editors were not displaying and allowing input of the time +- Fix: This Month/This Year records editors were showing records for the entire day file rather than the specific period +- Fix: GW1000 station dewpoint calculation incorrect on first packet read at start-up + +- New: Settings page: Program Settings. Moved Debug/Data logging options and Prevent Second Instance to here from Station Settings. + Note: These settings still remain in the [Station] section of the Cumulus.ini file for backwards compatibility +- New: Two settings to delay the start-up of Cumulus MX. Both added to the new Program Settings page. + Start-up Host PING: Specify a remote hostname or IP address, Cumulus will wait for a successful PING from this address before continuing + Start-up Delay: Specify a fixed time in seconds to delay the start-up. The delay will be executed after the PING check is one is specified + Start-up Delay Max Uptime: Specify a maximum system uptime in seconds after which the delay will no longer be applied. Set to zero to always delay + New section in Cumulus.ini: + [Program] + StartupPingHost= // Default is no remote host - enter a host name or IP address + StartupDelaySecs=0 // Default is no delay = 0 + StartupDelayMaxUptime=300 // Default is 300 seconds (5 minutes) +- New: FTP Now - Adds a new option to regenerate the daily graph data files (if enabled), and include them in the manually invoked FTP session +- New: Adds support for OpenWeatherMap uploads (and catch-up). Using an existing station if the id is supplied, or your API key only has one station defined. + If no stations are defined CMX automatically creates a new one using your station details and starts uploading to it. + + + +3.9.3 - b3098 +————————————— +- Fix: Records check was triggering for very small value changes in derived values such as feels like temps +- Fix: Catch uncaught exception on SFTP disconnect +- Fix: FTP was inconsistent in empty remote server folder handling for real-time and interval files +- Fix: AirLink was not using the local vs WLL API data correctly +- Fix: EU CAQI AQI index calculations (based on the City/Urban scale) +- Fix: US EPA AQI calculation error of values between 51 and 100 +- Fix: Removes redundant alldailywdirdata.json file from daily uploads +- Fix: Corrects incorrect/invalid JSON being created in the alldailytempdata.json file when the dayfile contains blank entry's for dew point +- Fix: Attempted FTP uploads of files that do not exist caused the FTP session to become non-functional +- Fix: The monthly data log file editor erroring with 404 + +- New: You can now use "" in the extra files local filename to copy/upload the latest AirLink data log file +- New: You can now define your Indoor AirLink as the primary AQ sensor. Note this only applies to the admin interface, its data will not be + uploaded to the default web site +- New: Adds Cumulus MX update available alarm to the dashboard - enabled by default. This also has an associated web tag <#UpgradeAlarm> +- New: If enabled, the daily graph data files are now created at start-up and uploaded at first interval FTP. + Previously you had to wait for the first EOD rollover. +- New: If a FineOffset station is not found, MX now dumps a list of all the USB devices it can see to the MXdiags log + + + +3.9.2 - b3097 +————————————— +- Fix: Change log messages for number of SQL rows affected to Debug only messages +- Fix: AirLink web tags no longer error if the sensor is not enabled - all now return "--" +- Fix: Australia NEPM AQIs to allow values greater than 101 +- Fix: Canada AQHI - still based on PM2.5 3 hour values only +- Fix: Dutch and Belgian AQI calculations +- Fix: Default web site temperature Trends graph yAxis issue +- Fix: Broken logfile, extralogfile, and dayfile editors in b3096 +- Fix: Improve Instromet stations error handling of out of sequence responses +- Fix: Extra files without the EOD flag were not being transferred during the EOD upload period (first after rollover) + + +- Adds Notifications to Alarms + Note: Cumulus notifications only work by default if you run the browser on the same machine as Cumulus and connect using the + url: http://localhost:8998 + In order to get them working on a remote machine you will have to change an advanced flag in your browser. + This is at your own risk as advanced flags are unsupported features in Chrome and Edge. + In Chrome or Edge, open the url - "about:flags" + Find the entry "Insecure origins treated as secure" and enable it + The add the url you use to remotely access the Cumulus dashboard - eg. http://192.168.1.23:8998 + Then click the button to restart your browser. Notifications should now work. +- Adds Latch times to Alarms. You can now specify a period in hours that an alarm will remain active after the event that tripped it has reset. +- The default value for "DavisIncrementPressureDP" is changed from true to false + ** The effect of this is that for Davis VP/VP2 stations, the default number of decimal places used for pressure values changes + from 2 dp (hPa/mb) and 3 dp (kPa, inHg) to 1 dp (hPa/mb) and 2 dp (kPa, inHg) + ** If you wish to retain the previous behaviour, then you MUST add the setting "DavisIncrementPressureDP=1" to your Cumulus.ini file +- Wind Rose is now populated from data files at start-up (last 24 hours) +- New graphs option to show/hide sunshine hours data +- New admin interface historic graphs (and associated API) +- New default web site page to show historic graphs +- Adds Air Quality to Recent Graphs in Admin Interface and default web site - configure a primary AQ sensor in Station.Options +- Adds Air Quality upload to WeatherUnderground. Enable in WeatherUnderGround settings, and configure a primary AQ sensor in Station.Options +- Adds Air Quality upload to AWEKAS. Enable in AWEKAS settings, and configure a primary AQ sensor in Station.Options +- Adds ability to define the number of decimal places used for various weather units via read-only settings in Cumulus.ini. + The full list of existing and new values is: + [Station] + WindSpeedDecimals=1 // Existing entry + WindSpeedAvgDecimals=1 // Existing entry + WindRunDecimals=1 // NEW + SunshineHrsDecimals=1 // Existing entry + PressDecimals= // NEW [hPa/mb=1, inHg=2] + RainDecimals= // NEW [mm=1, in=2] + TempDecimals=1 // NEW + UVDecimals=1 // NEW + AirQualityDecimals=1 // NEW - affects AQI values only, not PM values +- Lots of internal code fettling +- New Cumulus.ini read-only entry to control the data read rate for Instromet stations. The value is in milliseconds, default is 500. + [Station] + ImetReadDelay=500 +- Admin interface tweaks +- Change WUnderground Password field name to Station Key in settings + + +3.9.1 - b3096 +————————————— +- Fix: AirLink - Limit the number of decimal places in the AirLink log file +- Fix: AirLink - The web tags for 1hr average AQI values were picking up the latest reading value in error +- Fix: Send rounded wind speed to Weather Underground/Windy/PWS/WoW if Cumulus "Round Wind Speeds" is set +- Fix: Replaces a bad copy of trendsT.htm that crept into b3095 +- Fix: Bug in wind spike removal that prevented it working +- Fix: Airlink caused premature day rollover processing during catch-up +- Workaround for the Chromium browser CSS bug that adds scroll bars to the settings screens +- Adds a new AirLink Sensors page to the admin interface +- Adds Netherlands LKI, and Belgian BelAQI AQI scales [6=Netherlands=LKI, 7=Belgium-BelAQI] to AirLink devices +- All AirLink AQI web tags now support the tc=y parameter, to properly truncate the AQI to an integer value +- Adds the ability to "latch" all alarms for a user configured number of hours +- Adds a new web tag <#CPUTemp> - only works on Linux, updates once a minute +- Change of JSON library from Newtonsoft to ServiceStack.Text +- Dashboard now updates every 2.5 seconds to match Davis stations (was 3 seconds) + +- Updated files + \AirLinkFileHeader.txt - NEW + \CumulusMX.exe + \MQTTnet.dll + \Newtonsoft.Json.dll - REMOVED + \ServiceStack.Text.dll - NEW + \System.Buffers.dll - NEW + \System.Memory.dll - NEW + \System.Runtime.CompilerServices.Unsafe.dll - NEW + \interface\*.html - ALL html files + \interface\css\cumulus.css + \interface\json\ExtraSensorOptions.json - NEW + \interface\json\ExtraSensorSchema.json - NEW + \interface\json\InternetSchema.json + \interface\json\InternetOptions.json + \interface\js\airlink.js + \interface\js\extrasensorsettings.js - NEW + \interface\alarmsettings.js + \interface\lib\alpaca\alpaca.css + \web\trendsT.htm + + + + +3.9.0 - b3095 +————————————— +- Fix: Running as a Windows service, now correctly reads command line parameters from the registry "ImagePath" field +- Fix: Cumulus forecast would sometimes start with a comma +- Fix: trendsT.htm location formatting +- Adds support for the Davis AirLink air quality monitor. + - Like the WLL it supports auto-discovery of the AirLink IP address, this can be disabled via config + - A new Settings page has been created for "Extra sensors", this is where you can configure the AirLink. + - Logs AirLink data to a new CSV data file "AirLinklog.txt" - see AirLinkFileHeader.txt for field information + - Historic catch-up is supported if the main station is a Davis WLL and the AirLink has been added as node to this station + - Historic catch-up for other stations will be a later release + - Creates a new Cumulus.ini file section + [AirLink] + WLv2ApiKey= + WLv2ApiSecret= + AutoUpdateIpAddress=1 + In-Enabled=0 + In-IPAddress=0.0.0.0 + In-IsNode=0 + In-WLStationId= + Out-Enabled=0 + Out-IPAddress=0.0.0.0 + Out-IsNode=0 + Out-WLStationId= + AQIformula=0 [0=US-EPA, 1=UK-COMEAP, 2=EU-AQI, 3=EU-CAQI, 4=Canada-AQHI, 5=Australia-NEMP] + - Adds new web tags for Davis AirLink + <#AirLinkFirmwareVersionIn>,<#AirLinkWifiRssiIn> - Requires Davis WeatherLink Live Pro subscription + <#AirLinkFirmwareVersionOut>,<#AirLinkWifiRssiOut> - Requires Davis WeatherLink Live Pro subscription + <#AirLinkTempIn>,<#AirLinkHumIn> + <#AirLinkTempOut>,<#AirLinkHumOut> + <#AirLinkPm1In>,<#AirLinkPm1Out> + <#AirLinkPm2p5In>,<#AirLinkPm2p5_1hrIn>,<#AirLinkPm2p5_3hrIn>,<#AirLinkPm2p5_24hrIn>,<#AirLinkPm2p5_NowcastIn> + <#AirLinkPm2p5Out>,<#AirLinkPm2p5_1hrOut>,<#AirLinkPm2p5_3hrOut>,<#AirLinkPm2p5_24hrOut>,<#AirLinkPm2p5_NowcastOut> + <#AirLinkPm10In>,<#AirLinkPm10_1hrIn>,<#AirLinkPm10_3hrIn>,<#AirLinkPm10_24hrIn>,<#AirLinkPm10_NowcastIn> + <#AirLinkPm10Out>,<#AirLinkPm10_1hrOut>,<#AirLinkPm10_3hrOut>,<#AirLinkPm10_24hrOut>,<#AirLinkPm10_NowcastOut> + <#AirLinkAqiPm2p5In>,<#AirLinkAqiPm2p5_1hrIn>,<#AirLinkAqiPm2p5_3hrIn>,<#AirLinkAqiPm2p5_24hrIn>,<#AirLinkAqiPm2p5_NowcastIn> + <#AirLinkAqiPm2p5Out>,<#AirLinkAqiPm2p5_1hrOut>,<#AirLinkAqiPm2p5_3hrOut>,<#AirLinkAqiPm2p5_24hrOut>,<#AirLinkAqiPm2p5_NowcastOut> + <#AirLinkAqiPm10In>,<#AirLinkAqiPm10_1hrIn>,<#AirLinkAqiPm10_3hrIn>,<#AirLinkAqiPm10_24hrIn>,<#AirLinkAqiPm10_NowcastIn> + <#AirLinkAqiPm10Out>,<#AirLinkAqiPm10_1hrOut>,<#AirLinkAqiPm10_3hrOut>,<#AirLinkAqiPm10_24hrOut>,<#AirLinkAqiPm10_NowcastOut> + <#AirLinkPct_1hrIn>,<#AirLinkPct_3hrIn>,<#AirLinkPct_24hrIn><#AirLinkPct_NowcastIn> + <#AirLinkPct_1hrOut>,<#AirLinkPct_3hrOut>,<#AirLinkPct_24hrOut><#AirLinkPct_NowcastOut> +- The Blake-Larsen sunshine recorder, and Hydreon RG-11 rain device configuration options can now be set on the new Extra Sensors page. + There are two new settings for RG-11 devices, if you use these devices you must set these via the configuration page as they will default to disabled. + [Station] + RG11Enabled=0 + RG11Enabled2=0 +- Adds new graph option - Solar Visible. Setting both Solar and UV to hidden means the solar graph data files will be empty. +- CumulusMX no longer re-writes the Cumulus.ini file on shutdown +- Adds the option to set the retained flag for the MQTT feeds + - New Cumulus.ini file settings + [MQTT] + UpdateRetained=0 + IntervalRetained=0 + +- Updated files + \CumulusMX.exe + \AirLinkFileHeader.txt + \interface - lots of changes, all *.html files, /scripts, and /json + \web\trendsT.htm + + +3.8.4 - b3094 +————————————— +- Fix: Unhandled exception in MySQL during the start-up catch-up process +- Fix: Graph buttons not wrapping on default web site trends page +- Fix: Calculating the trend rain rate when a rainfall multiplier is used +- Fix: Program Uptime when running under Mono and uptime > 24 days +- Fix: When running as a Windows service, the service was stopped unintentionally on non-critical system power events +- Fix: Davis VP receptions stats - ReceptionStats, FirmwareVersion and BaroRead functions rewritten +- Adds Ecowitt GW1000 decoding of WH34 battery status +- Adds Ecowitt GW1000 support for CO₂ sensor, no logging yet, but the decoding is there for when these sensors are released. + - New web tags <#CO2>, <#CO2-24h> +- Adds new Linux cumulusmx.service configuration file (thanks to @freddie), this allows you run Cumulus MX as a service under Linux using the + newer systemd control rather than the original init.d script supplied with v3.8.0 + - Edit the cumulusmx.service configuration file and change the path to match your Cumulus MX installation folder + - Copy cumulusmx.service to your /etc/systemd/system/ folder + - Cumulus can then be started and stopped with the following commands: + > systemctl start cumulusmx + > systemctl stop cumulusmx + - Status and restart are available too: + > systemctl restart cumulusmx + > systemctl status cumulusmx + - If you make a change to the unit file, you need to run the following command so that systemd re-reads your file: + > systemctl daemon-reload + - To have Cumulus start on reboot, issue the following command: + > systemctl enable cumulusmx + - Finally, to stop the service running on reboot, use the following: + > systemctl disable cumulusmx +- Adds a new web tag <#ProgramUpTimeMs> that returns the Cumulus run time in milliseconds + +- Updated files + \CumulusMX.exe + \web\trendsT.htm + +- New files + \MXutils\linux\cumulusmx.service + + +3.8.3 - b3093 +————————————— +- Fix: The dew point threshold limit (default value 40C) was incorrectly being applied to installations configured to use Fahrenheit + as if the value were in Fahrenheit. +- Fix: Davis VP/VP2 force 1 minute barometer updates no longer working correctly after v3.8.2 protocol changes. + Note: this setting should only be used for VP stations, or VP2 stations with very old firmware. +- Fix: Last rain date not updating for Davis stations (fixes the mixed unit/bucket size update in 3.8.2) +- Adds two new Cumulus.ini file settings to control the number of decimal places used for wind speeds. These override the built-in defaults. + These settings are read-only, they can to be manually added ONLY if you need to use them. Add either or both settings as required. + [Station] + WindSpeedDecimals=[0|1|2] + WindSpeedAvgDecimals=[0|1|2] + +- Updated files + \CumulusMX.exe + + +3.8.2 - b3092 +————————————— +- Fix: Davis VP2 stations - LastRainTip not being updated correctly with mixed metric/imperial gauges/units +- Fix: Davis VP2 stations - Bug that prevented the seconds being read from the console clock +- Fix: Dashboard temp/press trend arrows not working in locales with comma decimals +- MXdiags log files are now rotated if they exceed 20MB in size, 15 log files are now retained +- For Davis VP2 stations, setting the console clock now checks the console time and only sets it if it + differs from the current time by greater than 60 seconds +- Davis VP2 stations protocols have been extensively rewritten to make them more efficient and less timing sensitive + Third party IP loggers such as meteobridge and WiFi logger should now work more reliably +- Enables PSK authentication when using SFTP. Options are now Password, PSK, PSK with Password fall-back + New Cumulus.ini entries... + [FTP site] + SshFtpAuthentication=password [password (default), psk, password_psk] + SshFtpPskFile= [PSK filename, must include the full path is the file is not in the CumulusMX folder] +- CMX calculated dew point is automatically enabled for GW1000 stations + +- Updated files + \CumulusMX.exe + \interface\js\dashboard.js + \interface\json\InternetOptions.json + \interface\json\InternetSchema.json + + +3.8.1 - b3091 +————————————— +- Fix: Davis TCP loggers failing to connect correctly +- Fix: NOAA reports not viewable in Admin interface when Cumulus started as a service +- Fix: Prevents an Exception on shutdown when running as a service + +- Updated files + \CumulusMX.exe + + +3.8.0 - b3090 +————————————— +- Fix: Malformed API JSON returned by api/tags/process.json if no data output is generated +- Fix: Crash on start-up on the 1st of the month between 00:00 and rollover hour when using 9am rollover +- Fix: AWEKAS Sunshine hours were being truncated to integer hours +- New feature: Cumulus MX now supports running as a system service + - When running as a service the normal console output is logged to a file: \MXdiags\ServiceConsoleLog.txt + - On Windows + - To set Cumulus up as a service run it as an Administrator using the command line parameter -install + - To remove the Cumulus system service run it as an Administrator using the command line parameter -uninstall + - The service will stop itself if it detects that the computer is going into standby + - To add parameters to the service when it starts automatically you will have to edit the registry + HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\CumulusMX\ImagePath + Add the parameters after the quoted executable string. Eg. + "C:\CumulusMX\CumulusMX.exe" -port 9000 + - The service can be stopped/started using the usual utilities: + The Services applet:- services.msc (You can pass parameters using this tool, but they are not saved for future use) + The service command line tool:- sc start|stop|query CumulusMX [optional parameters for start-up here] + - On Linux + - Run Cumulus using the mono-service command instead of mono. You also need to add the command line parameter -service + > mono-service -l:/var/run/cmx.pid CumulusMX.exe -service (if pathed to CumulusMX folder) + or + > mono-service -l:/var/run/cmx.pid -d:<> CumulusMX.exe -service (from any location) + - To stop the service you use kill, which will perform an orderly shutdown of MX + > kill `cat /var/run/cmx.pid` +- New feature: + - When running on Linux there is now an init.d script to start/stop the service. + - The script can be found in the \MXutils\linux folder + - Copy it to your /etc/init.d folder + - Change the permissions to 0755 + > chmod 0755 /etc/init.d/cumulusmx.sh + - The script supports the following commands: start, stop, restart, status, removelock + - start: Starts MX as a service and creates a lock file to prevent it running again + - stop: Stops MX as a service and removes the lock file + - restart: Stops, then starts MX + - status: Reports if MX is running (only by checking the lock file at present), and dumps the latest console output to screen + - removelock: Removes a stray lock file if one gets left behind + - The command line would be: /etc/init.d/cumulusmx.sh start|stop|restart|status|removelock + - Script name: \MXutils\linux\cumulusmx.sh +- New feature: + - When running on Windows as a service, the service will stop if the computer goes to standby/hibernate mode. A script to set up + a Scheduled Task that will restart the service on resume from standby/hibernate is provided in the \MXutils\windows folder. + The script MUST be run with Administrator privileges. + - Script name: \MXutils\windows\CreateCmxResumeFromStandbyTask.ps1 +- Adds a new web tags for the Davis WLL device. + <#ConsoleSupplyV> - The WLL external supply voltage + <#MulticastBadCnt> - Count of multicast packets missed or have bad a CRC + <#MulticastGoodCnt> - Count of good multicast packets received + <#MulticastGoodPct> - Percentage of good multicast packets received +- On new installs of Cumulus MX, changes the default value of "WarnMultiple" from false to true +- Adds a download history limit of 50 bad packets for WMR200 station types. Previously it could get stuck in a loop downloading bad data. +- Adds Show UV-I to the graph hide/show options +- Updates ExportMySQL to include the new Humidex values from b3089 +- AWEKAS uploads updated to API v4 - now can include indoor data, soil temp/moisture 1-4, and leaf wetness 1-2. And it supports rapid + updates - minimum 15 seconds if you have a Stationsweb subscription, min 60 secs for Pro accounts, and 300 seconds for normal accounts + - IMPORTANT NOTE: THe AWEKAS interval has changed from being set in minutes, to being set in seconds. + You MUST update the AWEKAS interval in the Internet settings/Cumulus.ini file to reflect this change. + - New Cumulus.ini entries + [Awekas] + Language=en + SendIndoor=0 + SendSoilMoisture=0 + SendLeafWetness=0 +- Enabling/disabling and changing intervals for Twitter, WUnderground, Windy, PWS Weather, WOW, APRS, AWEKAS, Weather Cloud, MQTT from + Internet Settings now takes immediate effect and does not require a Cumulus restart. +- Adds a new Cumulus.ini file only, read-only setting + [Station] + SunshineHrsDecimals=1 +- Adds a new folder in Dist zip file: /MXutils + In here I will add the various scripts etc that are provided with the releases to update MySQL or aid moving from one release to the next. + See above for new Windows and Linux scripts when running as a service + + +- Updated files + \CumulusMX.exe + \ExportMySQL.exe + \interface\js\charts.js + \interface\json\InternetOptions.json + \interface\json\InternetSchema.json + \interface\json\StationOptions.json + \interface\json\StationSchema.json + \webfiles\js\cumuluscharts.js + +-New files + \MXutils + + +3.7.0 - b3089 +————————————— +- Fix: Humidex was treated by MX as a temperature and incorrectly converted using the Celsius to Fahrenheit + conversion (if that was the user selected temperature unit). + Humidex is a dimensionless scale that should not be converted to retain meaning +- Fix: Thread unsafe graph data access +- Fix: Real time SFTP not recovering from some reconnection situations +- Fix: Add missing "rc" function from <#battery> web tag +- Fix: Remove units from Humidex value on the chart tooltip +- Fix: Station Settings page menu +- Fix: Davis WLL devices were flagging sensor contact lost for lost packets, now it only flags contact lost + when resynchronising with a transmitter +- Fix: Monthly records editor not functioning correctly on Humidex in beta1 +- Adds a logger interval check for Davis VP2 stations - prints warning to console and log file if there is a mismatch +- Now supports some of the Cumulus 1 Graph plotting options, configured via Station Settings + - Cumulus.ini file settings + [Graphs] + TempVisible=1 + InTempVisible=1 *new? + HIVisible=1 + DPVisible=1 + WCVisible=1 + AppTempVisible=1 + FeelsLikeVisible=1 + HumidexVisible=1 *new + InHumVisible=1 + OutHumVisible=1 +- Remove defunct WeatherBug upload from Cumulus MX +- Humidex is now a fully fledged variable... + - Daily, monthly, yearly highs + - Recent data + - Graph data - added to MX interface + - Records + - New Humidex web tags: + <#HighHumidexRecordSet> + <#humidexTH>, <#ThumidexTH> + <#humidexYH>, <#ThumidexYH> + <#MonthHumidexH>, <#MonthHumidexHT>, <#MonthHumidexHD> + <#YearHumidexH>, <#YearHumidexHT>, <#YearHumidexHD> + <#humidexH>, <#ThumidexH> + <#ByMonthHumidexH>, <#ByMonthHumidexHT> + <#RecentHumidex> +- Add Humidex to default web site trend graphs +- Now only calculates Humidex above 10C +- Day file updated with four additional fields or Humidex high, high time +- Monthly log file has an additional field for Humidex +- Daily SQL table has four additional columns for Humidex high, high time +- Monthly SQL table has an additional column for Humidex +- Add new web tag <#timeUnix> = provides current date/time as a Unix timestamp (like <#timeJavaScript>) +- The web tags <#SunshineHours> and <#YSunshineHours> now accept the decimal place parameter "dp=n" +- Adds a new web token processor API - details available separately +- Spike removal settings are now active for all station types - TAKE CARE! Use with caution +- Spike/limit logging is now enabled by default for new installs +- Adds a new alarm - Spike removal triggered - it stays active for 12 hours, or it is cleared if you save the calibration settings + - New web tag <#DataSpikeAlarm> + - New Cumulus.ini file settings... + [Alarms] + DataSpikeAlarmSet=0 + DataSpikeAlarmSound=0 + DataSpikeAlarmSoundFile=alarm.mp3 +- New "Limits" implemented. Now MX ignores temperature, dew point, pressure, and wind values outside sensible value ranges from any station type (these also trigger a spike alarm) + - Set via the Calibration settings screen + - New Cumulus.ini file settings... + [Limits] + TempHighC=60 + TempLowC=-60 + DewHighC=40 + PressHighMB=1090 + PressLowMB=870 + WindHighMS=90 +- New web tags for latest build notification - these require internet access to be meaningful + <#NewBuildAvailable> - returns "1" or "0" + <#NewBuildNumber> - returns the latest available Cumulus MX build number + + +- Updated files + \CumulusMX.exe + \dayfileheader.txt + \monthlyfileheader.txt + \interface\alarmsettings.html + \interface\alltimerecseditor.html + \interface\index.html + \interface\monthlyrecseditor.html + \interface\stationsettings.html + \interface\thismonthrecseditor.html + \interface\thisyearrecseditor.html + \interface\js\alarmsettings.js + \interface\js\alltimerecseditor.js + \interface\js\charts.js + \interface\js\dashboard.js + \interface\js\datalogs.js + \interface\js\dayfileeditor.js + \interface\js\monthlyrecseditor.js + \interface\js\thismonthrecseditor.js + \interface\js\thisyearrecseditor.js + \interface\json\CalibrationOptions.json + \interface\json\CalibrationSchema.json + \interface\json\InternetOptions.json + \interface\json\InternetSchema.json + \webfiles\js\cumuluscharts.js + + +3.6.12 - b3088 +—————————————— +- Fix Davis stations not downloading historic logger data (in b3087) + +- Updated files + \CumulusMX.exe + + +3.6.11 - b3087 +—————————————— +- Fix Davis TCP logger connections not timing out occasionally +- Fix heading on interface Now page, remove units from Humidex +- Fix FTP log file handling in Extra Files, with EOD option on the first of the month +- Add Feels Like to the default web site trends temperature graph +- Add Extra Sensors log file to the backup routine +- Add previous months log files (monthly and extra) to the backup on the first of the month +- Add "" tag to Extra Web Files to specify the variable extra log file name +- Improve web tag token parser performance +- Cumulus (Zambretti) forecast now works with localised compass points +- Internal optimisations (watch out for new issues!) +- Uplift the SFTP component from a 2016 version to new beta version - supports more encryption methods and key file formats +- Further additions to shutdown code for all stations +- Adds new web tag <#RecentFeelsLike> + +- Updated files + \CumulusMX.exe + \Renchi.SshNet.dll + \interface\now.html + \webfiles\js\cumuluscharts.js + + +3.6.10 - b3086 +—————————————— +- Fix for Feels Like calculation broken in previous release +- Fix for Davis WLL wind values when using units other than mph +- Fix for poor performance of wind direction charts on the MX interface and base web site +- Make end of day SQL inserts asynchronous +- Use a fixed timestamp for all EOD operations + +- Updated files + \CumulusMX.exe + \interface\charts.html + \interface\js\charts.js + \web\trendsT.htm + \webfiles\js\cumuluscharts.js + +3.6.9 - b3085 +————————————— +- RELEASE WITHDRAWN + + +3.6.8 - b3084 +————————————— +- Simplify realtime SFTP error detection and recovery +- Change the default web site Gauges page to not show pop-up graphs by default +- Fix for Ecowitt GW1000 stations when sensors go offline/online (wind and rain values) +- Fix for GW1000 stations wind gust values when using units other than "mph" +- Fix for GW1000 stations with WH34 type sensors and firmware 1.6.0 or later. You *must* now use firmware 1.6.0+ with WH34 devices +- Fix crash when creating the graph JSON files when file in use by FTP +- Fix for rc=y parameter not working with the <#intemp> web tag +- Fix low contrast menus on admin interface +- Fix HighCharts theme on admin interface Charts page, and default web site Trends page +- Fix for web tags <#daylength> and <#daylightlength> to display "24:00" if they last all day (they still allow custom formats) + +- Updated files + \CumulusmX.exe + \interface\charts.html + \interface\css\cumulus.css + \web\trendsT.htm + \webfiles\lib\steelseries\scripts\gauges.js + + +3.6.7 - b3083 +————————————— +- Add catches for real time MySQL updates and all real time file failures +- Adds Station (Absolute) and Altimeter pressure values for Davis WLL stations + +- Updated files + \CumulusMX.exe + + +3.6.6 - b3082 +————————————— +- Change ini files to use 17 significant figures for decimal values (up from 15) +- Fix for Davis WLL health data decoding when the WLL is LAN attached +- Fix for real time SFTP not reconnecting after failure + +- Updated files + \CumulusMX.exe + + +3.6.5 - b3081 +————————————— +- Fix for sun rise/set and dawn/dusk calculations when there is one event but not the other in a single day +- Fix for realtime FTP timeout/recovery issues + +- Updated files + \CumulusMX.exe + + +3.6.4 - b3080 +————————————— +- Fix for Ctrl-C not being handled when running under Linux/mono. Now handles SIGTERM and console Ctrl-C +- Fix for realtime FTP getting stuck on "already in progress" +- Adds support for Ecowitt GW1000 WH34 8 channel "User" (soil and water) temperature sensors + New web tags <#UserTemp1> - <#UserTemp8> + ExtraLog file has eight new fields appended - UserTemp1-8 + +- Updated files + \CumulusMX.exe + \Extrafileheader.txt + \SampleStrings.ini + \interface\extra.html + \interface\js\extradatalogs.js + \interface\js\extrasensors.js + + +3.6.3 - b3079 +————————————— +- Reverts b3077 FluentFTP update +- Fix for the long standing random Cumulus.ini/today.ini corruption when shutting down on Windows +- Another fix for Oregon WMR928 extra temperature only sensors + + +- Updated files + \CumulusMX.exe + \FluentFTP.dll + + +3.6.2 - b3078 +————————————— +- Fix for badly formed realtime.txt in b3077 + + +- Updated files + \CumulusMX.exe + + + +3.6.1 - b3077 +————————————— +- Fix for Oregon WMR928 extra temperature only sensors +- Fix for yesterdays Feels Like values in Admin interface Today/Yesterday screen +- Adds Feels Like to realtime.txt file as field 59 +- Changes GW1000 default Lightning distance to 999 (all user units), and time to 1900-01-01 00:00:00 + The corresponding Webtags will output "--" and "---" respectively +- Adds a new web tag <#LastRainTip>, which unlike <#LastRainTipISO> will accept a date/time format string + + +- Updated files + \CumulusMX.exe + \FluentFTP.dll + + + +3.6.0 - b3076 +————————————— +- Fix for records editors failing to read log files from Cumulus 1 versions +- Fix for Ecowitt GW-1000 devices to bypass the auto-discovery mechanism if it is disabled in the config +- NOAA reports now include degree, minutes, seconds symbols +- Slightly enhanced program termination logging +- Implements highs/low/records for Feels like + - Changes to ini files to add Feels Like - Adds [FeelsLike] section to + today.ini, yesterday.ini, month.ini, year.ini, alltime.ini, monthlyalltime.ini + - Changes to log files + monthlog.txt - adds field 27, feels like + dayfile.txt - adds fields 47-50, high feels like, high time, low, low time + - Changes to the MYSQL database tables are required + Adds column FeelsLike to the Monthly table + Adds columns MaxFeelsLike, TMaxFeelsLike, MinFeelsLike, TMinFeelsLike to the Dayfile table + - New web tags + <#HighFeelsLikeRecordSet> + <#LowFeelsLikeRecordSet> + <#ByMonthFeelsLikeHT> + <#ByMonthFeelsLikeLT> + <#ByMonthFeelsLikeL> + <#ByMonthFeelsLikeH> + <#YearFeelsLikeHD> + <#YearFeelsLikeLD> + <#YearFeelsLikeHT> + <#YearFeelsLikeLT> + <#YearFeelsLikeH> + <#YearFeelsLikeL> + <#MonthFeelsLikeHD> + <#MonthFeelsLikeLD> + <#MonthFeelsLikeHT> + <#MonthFeelsLikeLT> + <#MonthFeelsLikeH> + <#MonthFeelsLikeL> + <#feelslikeH> + <#TfeelslikeH> + <#feelslikeL> + <#TfeelslikeL> + <#feelslikeYH> + <#TfeelslikeYH> + <#feelslikeYL> + <#TfeelslikeYL> + <#feelslikeTH> + <#TfeelslikeTH> + <#feelslikeTL> + <#TfeelslikeTL> + - Updated record editors + - Updated log file viewers/editors +- Adds battery and reception data for Davis WLL. It now logs battery and input voltages to the MXdiags. + These are updated every 15 minutes and require you to have a WeatherLink Pro subscription. + The WLL unlike the VP2 console provides individual data for each transmitter + - The following web tags have been updated to accept a "tx=n" parameter, where n=1-8 and equals the desired transmitter id. + Omitting the tx= parameter or using tx=0 makes the tag function as before for Davis VP2 systems + <#DavisTotalPacketsMissed tx=n> + <#DavisNumberOfResynchs tx=n> + <#DavisMaxInARow tx=n> + <#DavisNumCRCerrors tx=n> + - New web tags for WLL transmitter reception percentage and RSSI figure, these must be used with the tx=n parameter + <#DavisReceptionPercent tx=n> - defaults to tx=1, tx=0 is unused + <#DavisTxRssi tx=n> - defaults to tx=1, use tx=0 to get the WLL WiFi RSSI +- Updated ExportMySQL.exe to version 1.1.0 + - Incorporates the new Feels Like data + - Uses compass point "-" for Calm + - Reads customised compass points from strings.ini if set + + +- Updated files + \CumulusMX.exe + \dayfileheader.txt + \ExportMySQL.exe + \Extrafileheader.txt + \monthlyfileheader.txt + \interface\alltimerecseditor.html + \interface\extrawebfiles.html + \interface\index.html + \interface\monthlyrecseditor.html + \interface\now.html + \interface\thismonthrecseditor.html + \interface\thisyearrecseditor.html + \interface\todayest.html + \interface\js\alltimerecseditor.js + \interface\js\charts.js + \interface\js\datalogs.js + \interface\js\dayfileeditor.js + \interface\js\monthlyrecseditor.js + \interface\js\thismonthrecseditor.js + \interface\js\thisyearrecseditor.js + \interface\json\StationOptions.json + + + +3.5.4 - b3075 +————————————— +- Fix for bearing zero on the interface "Now" page +- Fix for admin interface charts popup +- Fix for "normal" Extra files not being FTP'd at rollover interval, only those flagged as EOD were being transferred +- Another attempt to rationalise the Moon Phase messages - each quarter (new, 1st, full, 3rd) should now show for approximately 12 hours either side of the event +- All web tags that produce decimal number output now support the "rc=y" option +- Additional Davis WLL health info dumped in the MXdiags log on start-up and every 10 minutes if debug logging is on (voltages, uptimes, WiFi RSSI etc) + Logs warnings to the command line console and log file if voltages are too low +- Adds BatteryLow Alarm for WLL (Console [if you have an API key] & Tx), VP2 (Console & Tx), GW1000 (Tx), and adds a new web tag <#BatteryLowAlarm> +- Adds new web tag <#feelslike> - calculated using the JAG/TI formula used in the UK, USA, Canada etc. Currently there are no stats for this value + +- Updated files + \CumulusMX.exe + \interface\alarmsettings.html + \interface\alltimerecseditor.html + \interface\index.html + \interface\now.html + \interface\todayest.html + \interface\js\alarmsettings.js + \interface\js\charts.js + \interface\js\dashboard.js + \interface\js\now.js + \mqtt\DataUpdateTemplate.txt + \mqtt\IntervalUpdate.txt + \webfiles\js\cumuluscharts.js + + + +3.5.3 - b3074 +————————————— +- Fix: Backs out changes that created bad file paths in b3073 + + +- Updated files + \CumulusMX.exe + \web\webfiles\lib\steelseries\scripts\gauges.js + + + +3.5.2 - b3073 +————————————— +- Fixes and improvements to MQTT processing +- Adds new Cumulus.ini setting for MQTT to force IPv4/IPv6 connectivity (default is System decides which to use) + [MQTT] + IPversion=0 (0=default, 4=IPv4, 6=IPv6) +- Adds Sensor Contact lost flag/alarm for Davis WLL devices +- GW1000 raw Lux value is now available via the <#Light> web tag like other Fine Offset stations +- Adds three new web tags... + <#timeJavaScript> - returns the current date/time in JavaScript milliseconds. Example use = "var dt = Date(<#timeJavaScript>)" + <#directionTM> - returns todays max wind gust direction as a compass point + <#directionYM> - returns yesterdays max wind gust direction as a compass point + + +- Updated files + \CumulusMX.exe + \MQTTnet.dll + + + +3.5.1 - b3072 +————————————— +- Fix for the "Stop second instance" option now working a bit too well, you could not disable it! +- Implements the "record set" web tags. These will be set from the time of the record until a timeout value (default 24 hours). + You can change the default timeout by adding a entry to Cumulus.ini + [Station] + RecordSetTimeoutHrs=24 + + The web tags enabled are: TempRecordSet, WindRecordSet, RainRecordSet, HumidityRecordSet, PressureRecordSet, HighTempRecordSet, + LowTempRecordSet, HighAppTempRecordSet, LowAppTempRecordSet, HighHeatIndexRecordSet, LowWindChillRecordSet, HighMinTempRecordSet, + LowMaxTempRecordSet, HighDewPointRecordSet, LowDewPointRecordSet, HighWindGustRecordSet, HighWindSpeedRecordSet, HighRainRateRecordSet, + HighHourlyRainRecordSet, HighDailyRainRecordSet, HighMonthlyRainRecordSet, HighHumidityRecordSet, HighWindrunRecordSet, LowHumidityRecordSet, + HighPressureRecordSet, LowPressureRecordSet, LongestDryPeriodRecordSet, LongestWetPeriodRecordSet, HighTempRangeRecordSet, LowTempRangeRecordSet + + +- Updated files + \CumulusMX.exe + + + +3.5.0 - b3071 +————————————— +- Fix to "Stop second instance" of Cumulus running +- Fix for hung update interval (S)FTP sessions getting hung +- Fix to <#moonage> web tag to improve accuracy +- Adds support for MQTT output + Two options, send an MQTT message whenever new data is received, or send a message at a fixed interval + The message format is defined in template files, using web tags, located in the \mqtt folder +- Tidy up of /interface folder to remove unused files +- Removal of Highcharts scripts from the distribution +- Add support for generating a Moon Phase image. This is disabled by default. The output image will be generated and + optionally FTP'd once an hour. The generated local image file is always \web\moon.png + [FTP site] + IncludeMoonImage=1 + + [Graphs] + MoonImageEnabled=1 + MoonImageSize=100 + MoonImageFtpDest=images/moon.png + + +- New files + \Licences-Additional.txt + \MQTTnet.dll + \mqtt\DataUpdateTemplate.txt + \mqtt\IntervalTemplate.txt + \web\MoonBaseImage.png + +- Updated files + \CumulusMX.exe + \FluentFTP.dll + \Newtonsoft.Json.dll + \interface\ - [Many changes, delete and replace] + \web\indexT.htm + \webfiles\js\cumuluscharts.js + + + +3.4.6 - b3070 +————————————— +- Fix for station wind chill on Davis WLL devices +- Fix for auto-discovered Davis WLL Station-Ids not being saved to the config file +- More robust failure handling for realtime FTP connections +- Additional diagnostics output in the console and log file for badly formed web tags + +- Updated files + \CumulusMX.exe + + + +3.4.5 - b3069 +————————————— +- Adds Editors for: Dayfile, Monthly Logs, Extra Logs +- Adds line numbers to the log file viewer/editors +- Widens the time windows for the Moons phase names +- Fix for <#MoonPercent> and <#MoonPercentAbs> always showing integer values even with the dp=n option + + +- Updated files + \CumulusMX.exe + \interface\ + + + +3.4.4 - b3068 +————————————— +- Fix for incorrect NOAA yearly report, annual averages for temperature and wind were calculated incorrectly +- Now detects invalid CumulusMX.exe command line parameters +- Adds a new command line parameter -debug. This switches on debug and data logging from the start-up of Cumulus MX. You no + longer have to edit Cumulus.ini to gather these diagnostics. + +- Updated files + \CumulusMX.exe + + + +3.4.3 - b3067 +————————————— +- Adds a new option to Davis WLL and Ecowitt GW1000 station settings to disable IP address auto-discovery. Use this option + if you have more than one of these devices on your network, then enter the IP address manually. + +- Updated files + \CumulusMX.exe + \interface\json\StationOptions.json + \interface\json\StationSchema.json + + + +3.4.2 - b3066 +————————————— +- Improved error handing for invalid Davis WLL Station Ids +- Improved error handling when the network connection to a Davis WLL is lost (and restored) +- Adds missing Data Stopped alarm to the Dashboard and Alarm Settings screens +- Adds auto-discovery for Ecowitt GW1000 devices IP addresses +- Adds DataStopped handling to Ecowitt GW1000 devices + +- Updated files + \CumulusMX.exe + \interface\alarmsettings.html + \interface\index.html + \interface\js\alarmsettings.js + \interface\js\dashboard.js + + + +3.4.1 - b3065 +————————————— +- Fix for WLL if you change the WL.com logging interval around a catch-up period +- Fix for gust values from WLL devices +- Add WLL broadcast data watchdog and warning, implements DataStopped flag +- Adds WLL Cumulus.ini readonly setting, AutoUpdateIpAddress, use this to switch off the WLL autodetection of IP + address. This is a workaround for a WLL firmware bug that does not update the IP address when it changes using DHCP. + AutoUpdateIpAddress=0 #default = 1 + +- Updated files + \CumulusMX.exe + + + +3.4.0 - b3064 +————————————— +- Adds the option for Davis WLL users who have a WL.com Pro subscription to use WL.com as a "logger" to catch up + missing data on Cumulus MX start-up. +- Adds to option to truncate the <#MoonAge> tag value to an integer value instead of rounding it. + Use <#MoonAge tc=y> - ideal if you use the tag for image selection +- Updates FTP Now so that it does a full file process and FTP cycle, previously it just ran the FTP process +- Adds a Cumulus MX version check at startup - if online - and logs a message in the console and diags when a newer + build is available +- Fixes the Monthly Records editor for dry/wet periods that end on the last day of a month being incorrectly recorded + against the following month. +- Fixes the Beaufort calculations - there were some rounding errors in edge cases. +- Fixes Davis VP2 and WLL that were not using peak speeds from LOOP2 (VP2) and live/historic data (WLL) when the Cumulus logging + interval is set to 10 minutes or more. +- Fix for FTPS on realtime FTP updates +- Adds new ini file only option to disable Explicit FTPS - ie use Implicit mode + DisableFtpsExplicit=1 + +- Updated files + \CumulusMX.exe + \interface\json\StationOptions.json + \interface\json\StationSchema.json + + +3.3.0 - b3063 +————————————— +- Adds support for SFTP (SSH FTP) + - Moves the FTP SSL option to the web server settings + - Adds SFTP to existing FTP, FTPS options +- Fixes Ecowitt Soil Temperature/Moisture/Leak detector channel numbering +- Fix for an occasional error in station logger data handling of today's rainfall during CMX start-up +- Fix for Ecowitt GW1000 Lightning data decode +- Fix for incorrect date on <#Snow*> web tags +- Now automatically fixes two Cumulus.ini changes from Cumulus 1 generated files + - Changes the [FTP Site] section name to [FTP site] + - Changes NOAA default monthly name (if still set) from "NOAAMO'mmyy'.txt" to "NOAAMO'MMyy'.txt" +- Additional diagnostic logging info for Lacrosse WS2300 stations + +- Updated files + \CumulusMX.exe + \FluentFTP.dll + \interface\json\InternetOptions.json + \interface\json\InternetSchema.json + +- New files + \Renci.SshNet.dll + + +3.2.6 - b3062 +————————————— +- Fixes monthly records editor for stations with a met day starting at 9am +- Adds range checks for latitude and longitude values + +- Updated files + \CumulusMX.exe + + +3.2.5 - b3061 +————————————— +- Adds This Month and This Year records editors +- Adds FTP Now function +- Fix to MonthlyAlltimeLog.txt to add line feeds +- Fix missing WLL station description from APRS data + +- Updated files + \CumulusMX.exe + \interface\.html + +- New files + \interface\ftpnow.html + \interface\thismonthrecseditor.html + \interface\thisyearrecseditor.html + \interface\js\thismonthrecseditor.js + \interface\js\thisyearrecseditor.js + + +3.2.4 - b3060 +————————————— +- Fix uncaught Web exceptions in Davis WLL +- Fix Monthly Records editor not saving updated date/time stamps +- Adds a log file for MonthlyAlltime.ini file changes + +- Updated files + \CumulusMX.exe + + +3.2.3 - b3059 +————————————— +- Adds the Cumulus.ini file to the files automatically backed up each day/program start +- Fixes to the Monthly Records editor monthly rainfall figures + +- Updated files + \CumulusMX.exe + \interface\alltimerecseditor.html + \interface\monthlyrecseditor.html + \interface\js\alltimerecseditor.js + \interface\js\monthlyrecseditor.js + + +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 +- Fix the dashboard for Internet Explorer +- Fix default website index page header not wrapping on small screens +- Fix for Davis stations connected via TCP/IP to detect failures and reopen the connection more quickly during loop data processing +- Adds Solar calibration settings offset and multiplier +- Updates the SampleStrings.ini file with the extra captions added in b3056 + +- Updated files + \CumulusMX.exe + \SampleStrings.ini + \HidSharp.dll + \interface\js\dashboard.js + \interface\json\CalibrationSchema.json + \web\indexT.htm + + +3.2.0 - b3056 +————————————— +- Adds support for Ecowitt GW1000 WiFi gateway +- New web tag <#GW1000FirmwareVersion> +- Added extra soil temp sensors 5-16 for GW1000 stations + - New web tags <#SoilTemp5-16> + - Value are logged to extra log file + - Added custom captions available in Cumulus.ini [SoilTempCaptions] : Sensor5-16 +- Added extra soil moisture sensors 5-16 for GW1000 stations + - New web tags <#SoilMoisture5-16> + - Values are logged to extra log file + - Added custom captions available in Cumulus.ini [SoilMoistureCaptions] : Sensors5-16 + - Units now change between cb for Davis and % for GW1000 sensors +- Added extra air quality sensors 1-4 for GW1000 stations + - New web tags <#AirQuality1-4> and <#AirQualityAvg1-4> + - Values are logged to extra log file + - Added custom captions available in Cumulus.ini [AirQualityCaptions] : Sensor1-4 and SensorAvg1-4 +- Added new web tags for <#LeakSensor1-4> to support GW1000 leak sensors + - No display or logging of these values is done +- Added new web tags for Lightning sensors on GW1000 stations + - <#LightningDistance> Distance to last strike (same units as wind run - miles/km/nm) + - <#LightningTime> Time of last strike (format customisable) + - <#LightningStrikesToday> Number of strikes since midnight + - Currently there is no logging or display of these values +- Enables alarms as per Cumulus 1 + - New Alarm page under Settings + - Alarms are shown visually on the dashboard + - Due to browser restrictions, alarm sounds on the browser page may require you to click a button on the first alarm in order to hear it. + - You can add the MX admin site to your browsers list of sites allowed to play sound automatically + - Your browser should "learn" that you want to allow sounds to play automatically + - Alarm sound files should be placed in the /interface/sounds folder, they must be a browser compatible format (mp3 are good) + - The alarm settings for the sound file should be just the filename without any path +- Add pressure multiplier calibration + - New ini file setting [Offsets] PressMult = 1.0 (default) +- Fix for the All Time Records editor hourly rain total from the monthly log file month +- Fixes missing Cumulus multipliers for Wind and Rain values on Davis WLL stations +- Changes All Time Records editor to only load data from the Day File by default. Monthly log file + processing is now optional as it can take a very long time on a slow machine e.g. Raspberry Pi +- Fix to catch badly formed Davis WLL broadcast messages +- Fix for Davis WLL edge cases producing an initial zero value wind chill on startup + +- Updated files + \CumulusMX.exe + \interface\json\StationOptions.json + \interface\json\StationSchema.json + \interface\.html + \interface\js\extrasensors.js + \interface\js\alltimerecseditor.js + \interface\js\dashboard.js + \interface\css\main.css + \interface\json\CalibrationSchema.json + +- New files + \interface\alarmsettings.html + \interface\js\alarmsettings.js + \interface\sounds\alarm.mp3 + + +3.1.2 - b3055 +————————————— +- Fix for the All Time Records editor monthly rain total from the monthly log file month +- Fix for some long timeouts in All Time Records editor + +- Updated files + \CumulusMX.exe + + + +3.1.1 - b3054 +————————————— +- Fixes WLL timestamps always being in UTC, now uses local time +- Adds web tags <#snowlying>, <#snowfalling>, both provide 1|0 responses +- Adds Current Conditions editor to admin interface +- Adds All Time Records editor to admin interface + + +- Updated files + \CumulusMX.exe + \interface\.html + +-New files + \interface\currentcondeditor.html + \interface\alltimerecseditor.html + \interface\js\alltimerecseditor.js + \interface\lib\x-editable\ + \interface\img\loading.gif + + +3.1.0 - b3053 +————————————— +- Adds support for Davis WeatherLink Live device + - Supports Zero-Config, it should discover your WLL on the network (router support required) +- Adds support for Cumulus.ini file setting EWpressureoffset for Fine Offset stations as used in Cumulus1. + - This provides a manual override for the calculated absolute to relative pressure offset +- Adds reading of the interval Hi/Lo temperature readings when processing Davis logger archive records during catch-up +- Adds display and generation of NOAA Monthly and Yearly reports +- Reduces the Instromet live data read intervals to 1 second +- Applies "fix" for Mono 5.x generating short month names ending with "." as used for log file names + + +- Updated files + \CumulusMX.exe + \CumulusMX.exe.config + \interface\.html + \interface\json\StationOptions.json + \interface\json\StationSchema.json + +- New files + \Tmds.MDns.dll + + +3.0.2 - b3052 +————————————— +- Fixes Davis archive downloads from the the logger when the day rollover processing takes longer than 10 seconds. + This can happen on slow processors - Pi Zero for example - or if lengthy procedures are included + An extra archive processing run is scheduled for each day rollover that takes longer than 10 seconds + +- Updated files + \CumulusMX.exe + \CumulusMX.exe.config + + +3.0.1 - b3051 +————————————— +- Increases Davis DMPAFT Date/Time command timeout +- Much improved Davis serial port throughput (for USB and Serial loggers), should decrease the historic logger download time +- Adds Davis archive data processing progress indication +- Fixes firmware check for LOOP2 support +- Fixes a race condition on start-up of Davis VP2 stations without a logger. This could cause a crash in AstroLib.SolarMax() +- Fixes reading the Davis console clock after setting the time + +- Updated files + \CumulusMX.exe + + +3050 +———— +- Fixes MX not working with locales that use two character date separators + Eg. Croatia "29. 04. 19" + +- Updated files + \CumulusMX.exe + +3049 +———— +- Adds the ability to upload data to Windy.com + +- Updated files + \CumulusMX.exe + \interface\json\InternetOptions.json + \interface\json\InternetSchema.json + +3048 +———— +- You can now first time enable/disable Realtime FTP without having to restart CMX + +- Instromet stations now record and report rainfall (mm) and sunshine hours to 2 decimal places + +- Improved realtime FTP error handling + +- Improved Davis protocol handling + +- Fix Davis protocol mixing up LOOP1 and LOOP2 packets and consequently providing invalid rain and wind data. + +- Fix web tag <#YearLowDailyTempRangeD> broken in b3047 + +- Bug fixes to FTP Component, and internal changes to FTP transfer mechanism + +- Updated files + \CumulusMX.exe + \FluentFTP.dll + + + +3047 +———— +- Web token parser updated to cope with html tag characters "<>" in the format string. + - You can now do things like... + <#TapptempH format="dd' 'MMM' 'yyyy' at 'HH:mm''"> + which gives... + 04 Dec 2018 at 10:12 + + Note: that you have to use single quotes for HTML entity names, and they have to be escaped "\'" + +- New Davis Baud Rate setting + - Allows you to alter the speed of the serial connection to Davis loggers + - Configured manually in Cumulus.ini [Station] section + DavisBaudRate=19200 (default) Permitted values are 1200, 2400, 4800, 9600, 14400, 19200 + +- Added new option for the "Extra files" - End of Day + - Enabling this means that file will only be processed/copied/FTPed once a day during the end of day roll-over. + - There is a new Cumulus.ini file setting for each "extra" file associated with this setting + ExtraEOD[nn] + - Note there is currently no check between Realtime and End of Day settings, you could check both options and the file + will be processed at both the realtime interval AND end of day - which would not make much sense! + +- Improvement to Instromet logger protocol handling + +- Change the Fine Offset Synchronised Reads option to default to enabled + +- Change VP2 automatic disabling of LOOP2 to an advisory message, as the firmware version is not always detected. + +- Consistency: All record Value tags should now return '---' and Date tags '----' until they are first set. + +- The following web tags now support the "dp=N" "rc=y" parameters for the number of decimal places, and replace decimal commas with points. + #MoonPercent + #MoonPercentAbs + #MoonAge + +- Fix for Fine Offset & WMR100/200 stations on Mac operating systems (introduced in b3044) + +- Fix for invalid (extremely high) pressure readings from Fine Offset stations (thanks to 79669weather) + +- Fix to not updating the Instromet loggers memory pointer correctly + +- Fixed Weather Diary Time Zone issues + +- Bug fixes and performance improvements to the FTP component + +- Updated files + \CumulusMX.exe + \CumulusMX.exe.config + \FluentFTP.dll + \HidSharp.dll + \interface\js\diaryeditor.js + \interface\json\StationOptions.json + +- Removed files + \fastJSON.dll + + + +3046 +———— +- Weather Diary + - Added Weather Diary page to management interface + - Added diary.db file to daily backup files + - Removed diary.db from distribution (it is created on first use to avoid overwriting the file on CMX distro updates) + +- Web Tags + - Added <#snowdepth> tag processing + +- ET annual roll-over fix + +- Fix to TLS 1.2 FTPS of the 'periodic' files + + + +3045 +———— +- Internal Stuff + - Update the targeted .Net version to 4.5.2 (4.5 and 4.5.1 are no longer supported by Microsoft) + - Upgrade System.Net.FtpClient to replacement FluentFTP package + - Update Microsoft.Net.Http package to latest stable version + - Update fastJSON package to latest stable version + - Update embedIO package to latest stable version + - Remove Alchemy WS package from build + +- Davis TCP connections. + - Added additional error handling, should now be much more robust and attempt to reconnect on failure. + +- Astro calcs for Solar + - Added refraction correction. + - updated to add some extra terms. + +- Removed URL encode Twitter messages added in b3044 + +- Added second order humidity correction factor, works the same as the temperature. It has to be set manually in Cumulus.ini. + - Cumulus.ini + [Offsets] + HumMult2=0.0 + +- FTP Updates + - Now supports FTPS over TLS1.1 and 1.2 + - In Passive FTP, you can disable Enhanced Passive (EPSV) mode if it causes problems with your host. Some hosts are reporting they support it, but a firewall along the route cannot handle the connections. + Requires manually adding a new Cumulus.ini entry... + [FTP site] + DisableEPSV=1 + - RealtimeGaugesTxt is no longer automatically enabled for FTP. + +- Web Sockets are no longer on a separate port, it shares the same port as HTTP. + - The wsport command line switch is still recognised for backwards compatibility, but it is no longer used. + +- Fix Fine Offset with solar logger reading, now limited to the reduced number of logger entries available on solar stations. + +- Fix for Slovenian locale (and any other with a two character date separator) + + + +3044 +———— +- Added new solar calculation method "Bras" + - New Cumulus.ini entries... + SolarCalc=0 (0=Ryan-Stolzenbach, 1=Bras, default=0) + BrasTurbidity=2 (atmospheric turbidity factor (2=clear, 5=smoggy, default=2) + - Updated the Interface web files to reflect the new settings. + +- Updated HidSharp to ver 2.0.5 + - Now uses libudev1 for Fine Offset and WMR200 stations + +- Fixed corrupt/missing MySQL port causing CumulusMX to crash on start-up + +- Attempt to reconnect to Davis IP logger on connection failure (only a possible fix) + +- Updated the default forum URL to it's new home + +- Updated included website files to new URL + +- URL encode Twitter messages (I know Twitter is largely broken) + +- Davis VP2 - (Steve Loft) Added sanity checks for invalid wind speed/direction values + +- Davis VP2 - Added automatic disabling of the use of LOOP2 packets on firmware versions < 1.90 + +- Change RG11 devices to use a new Cumulus.ini file entry. The previous MX builds would not work on Linux. + For example: + RG11port=2 (depreciated) + RG11port2=3 + now use + RG11portName=COM2 (for Windows or /dev/ttyUSB2 for Linux) + RG11portName2=COM3 (for Windows or /dev/ttyUSB3 for Linux) + +- The default Comm port values are now set to either COM1 or /dev/ttyUSB0 depending on the platform. Hopefully this will prevent some of the confusion of new users.