From d70acae76e44db92bb82f9e27eec96523cbb9c84 Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Tue, 10 May 2022 19:41:44 +0100 Subject: [PATCH 1/5] Add Simulator Remove lock debug messages --- CumulusMX/Cumulus.cs | 19 +- CumulusMX/CumulusMX.csproj | 1 + CumulusMX/DavisAirLink.cs | 6 +- CumulusMX/DavisStation.cs | 12 +- CumulusMX/DavisWllStation.cs | 47 ++--- CumulusMX/EcowittApi.cs | 10 +- CumulusMX/EmailSender.cs | 12 +- CumulusMX/FOStation.cs | 6 +- CumulusMX/GW1000Station.cs | 14 +- CumulusMX/HttpStationEcowitt.cs | 17 +- CumulusMX/Properties/AssemblyInfo.cs | 6 +- CumulusMX/Simulator.cs | 285 +++++++++++++++++++++++++++ CumulusMX/TempestStation.cs | 6 +- CumulusMX/WeatherStation.cs | 41 +++- Updates.txt | 9 + 15 files changed, 423 insertions(+), 68 deletions(-) create mode 100644 CumulusMX/Simulator.cs diff --git a/CumulusMX/Cumulus.cs b/CumulusMX/Cumulus.cs index f969ebd5..ee47ed88 100644 --- a/CumulusMX/Cumulus.cs +++ b/CumulusMX/Cumulus.cs @@ -749,10 +749,11 @@ public struct MqttSettings "HTTP WUnderground", // 13 "HTTP Ecowitt", // 14 "HTTP Ambient", // 15 - "WeatherFlow Tempest" // 16 + "WeatherFlow Tempest", // 16 + "Simulator" }; - public string[] APRSstationtype = { "DsVP", "DsVP", "WMR928", "WM918", "EW", "FO", "WS2300", "FOs", "WMR100", "WMR200", "IMET", "DsVP", "Ecow", "Unkn", "Ecow", "Ambt", "Tmpt" }; + public string[] APRSstationtype = { "DsVP", "DsVP", "WMR928", "WM918", "EW", "FO", "WS2300", "FOs", "WMR100", "WMR200", "IMET", "DsVP", "Ecow", "Unkn", "Ecow", "Ambt", "Tmpt", "Simul" }; public string loggingfile; @@ -1478,9 +1479,9 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) } Console.WriteLine(); - LogDebugMessage("Lock: Cumulus waiting for the lock"); + //LogDebugMessage("Lock: Cumulus waiting for the lock"); syncInit.Wait(); - LogDebugMessage("Lock: Cumulus has lock"); + //LogDebugMessage("Lock: Cumulus has lock"); LogMessage("Opening station"); @@ -1549,6 +1550,10 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) Manufacturer = AMBIENT; station = new HttpStationAmbient(this); break; + case StationTypes.Simulator: + Manufacturer = SIMULATOR; + station = new Simulator(this); + break; default: LogConsoleMessage("Station type not set", ConsoleColor.Red); LogMessage("Station type not set"); @@ -1652,7 +1657,7 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) StartTimersAndSensors(); } - if ((StationType == StationTypes.WMR100) || (StationType == StationTypes.EasyWeather) || (Manufacturer == OREGON)) + if ((StationType == StationTypes.WMR100) || (StationType == StationTypes.EasyWeather) || (Manufacturer == OREGON) || StationType == StationTypes.Simulator) { station.StartLoop(); } @@ -1662,7 +1667,7 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) station.CreateEodGraphDataFiles(); } - LogDebugMessage("Lock: Cumulus releasing the lock"); + //LogDebugMessage("Lock: Cumulus releasing the lock"); syncInit.Release(); } @@ -6638,6 +6643,7 @@ private void ReadStringsFile() public int HTTPSTATION = 7; public int AMBIENT = 8; public int WEATHERFLOW = 9; + public int SIMULATOR = 10; //public bool startingup = true; public string ReportPath; @@ -10577,6 +10583,7 @@ public static class StationTypes public const int HttpEcowitt = 14; public const int HttpAmbient = 15; public const int Tempest = 16; + public const int Simulator = 17; } /* diff --git a/CumulusMX/CumulusMX.csproj b/CumulusMX/CumulusMX.csproj index e3d89f50..485d89da 100644 --- a/CumulusMX/CumulusMX.csproj +++ b/CumulusMX/CumulusMX.csproj @@ -237,6 +237,7 @@ Component + diff --git a/CumulusMX/DavisAirLink.cs b/CumulusMX/DavisAirLink.cs index e0f5da34..0e5f0c58 100644 --- a/CumulusMX/DavisAirLink.cs +++ b/CumulusMX/DavisAirLink.cs @@ -312,9 +312,9 @@ private async void GetAlCurrent(object source, ElapsedEventArgs e) var urlCurrent = $"http://{ip}/v1/current_conditions"; - cumulus.LogDebugMessage($"GetAlCurrent: {locationStr} - Waiting for lock"); + //cumulus.LogDebugMessage($"GetAlCurrent: {locationStr} - Waiting for lock"); WebReq.Wait(); - cumulus.LogDebugMessage($"GetAlCurrent: {locationStr} - Has the lock"); + //cumulus.LogDebugMessage($"GetAlCurrent: {locationStr} - Has the lock"); // The AL will error if already responding to a request from another device, so add a retry do @@ -356,7 +356,7 @@ private async void GetAlCurrent(object source, ElapsedEventArgs e) } } while (retry < 3); - cumulus.LogDebugMessage($"GetAlCurrent: {locationStr} - Releasing lock"); + //cumulus.LogDebugMessage($"GetAlCurrent: {locationStr} - Releasing lock"); WebReq.Release(); } else diff --git a/CumulusMX/DavisStation.cs b/CumulusMX/DavisStation.cs index 93a28f23..99869d83 100644 --- a/CumulusMX/DavisStation.cs +++ b/CumulusMX/DavisStation.cs @@ -772,24 +772,24 @@ private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) private void bw_DoStart(object sender, DoWorkEventArgs e) { - cumulus.LogDebugMessage("Lock: Station waiting for lock"); + //cumulus.LogDebugMessage("Lock: Station waiting for lock"); Cumulus.syncInit.Wait(); - cumulus.LogDebugMessage("Lock: Station has the lock"); + //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.LogDebugMessage("Lock: Station releasing lock"); Cumulus.syncInit.Release(); } private void bw_DoWork(object sender, DoWorkEventArgs e) { int archiveRun = 0; - cumulus.LogDebugMessage("Lock: Station waiting for the lock"); + //cumulus.LogDebugMessage("Lock: Station waiting for the lock"); Cumulus.syncInit.Wait(); - cumulus.LogDebugMessage("Lock: Station has the lock"); + //cumulus.LogDebugMessage("Lock: Station has the lock"); try { // set this temporarily, so speed is done from average and not peak gust from logger @@ -805,7 +805,7 @@ private void bw_DoWork(object sender, DoWorkEventArgs e) { cumulus.LogMessage("Exception occurred reading archive data: "+ex.Message); } - cumulus.LogDebugMessage("Lock: Station releasing the lock"); + //cumulus.LogDebugMessage("Lock: Station releasing the lock"); Cumulus.syncInit.Release(); } diff --git a/CumulusMX/DavisWllStation.cs b/CumulusMX/DavisWllStation.cs index f8818283..93cf8545 100644 --- a/CumulusMX/DavisWllStation.cs +++ b/CumulusMX/DavisWllStation.cs @@ -243,9 +243,9 @@ public override void Start() try { // Wait for the lock - cumulus.LogDebugMessage("Lock: Station waiting for lock"); + //cumulus.LogDebugMessage("Lock: Station waiting for lock"); Cumulus.syncInit.Wait(); - cumulus.LogDebugMessage("Lock: Station has the lock"); + //cumulus.LogDebugMessage("Lock: Station has the lock"); // Create a realtime thread to periodically restart broadcasts GetWllRealtime(null, null); @@ -352,7 +352,7 @@ public override void Start() } finally { - cumulus.LogDebugMessage("Lock: Station releasing lock"); + //cumulus.LogDebugMessage("Lock: Station releasing lock"); Cumulus.syncInit.Release(); } } @@ -402,9 +402,9 @@ private async void GetWllRealtime(object source, ElapsedEventArgs e) { var retry = 2; - cumulus.LogDebugMessage("GetWllRealtime: GetWllRealtime waiting for lock"); + //cumulus.LogDebugMessage("GetWllRealtime: GetWllRealtime waiting for lock"); WebReq.Wait(); - cumulus.LogDebugMessage("GetWllRealtime: GetWllRealtime has the lock"); + //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 @@ -465,7 +465,7 @@ private async void GetWllRealtime(object source, ElapsedEventArgs e) } } while (retry > 0); - cumulus.LogDebugMessage("GetWllRealtime: Releasing lock"); + //cumulus.LogDebugMessage("GetWllRealtime: Releasing lock"); WebReq.Release(); } @@ -486,9 +486,9 @@ private async void GetWllCurrent(object source, ElapsedEventArgs e) // wait a random time of 0 to 5 seconds before making the request to try and avoid continued clashes with other software or instances of MX await Task.Delay(random.Next(0, 5000)); - cumulus.LogDebugMessage("GetWllCurrent: Waiting for lock"); + //cumulus.LogDebugMessage("GetWllCurrent: Waiting for lock"); WebReq.Wait(); - cumulus.LogDebugMessage("GetWllCurrent: Has the lock"); + //cumulus.LogDebugMessage("GetWllCurrent: Has the lock"); // The WLL will error if already responding to a request from another device, so add a retry do @@ -524,7 +524,7 @@ private async void GetWllCurrent(object source, ElapsedEventArgs e) } } while (retry < 3); - cumulus.LogDebugMessage("GetWllCurrent: Releasing lock"); + //cumulus.LogDebugMessage("GetWllCurrent: Releasing lock"); WebReq.Release(); } else @@ -814,12 +814,12 @@ private void DecodeCurrent(string currentJson) // pesky null values from WLL when it is calm int wdir = data1.wind_dir_last.HasValue ? data1.wind_dir_last.Value : 0; - double wind = data1.wind_speed_last.HasValue ? data1.wind_speed_last.Value : 0; + double wind = ConvertWindMPHToUser(data1.wind_speed_last ?? 0); double wspdAvg10min = ConvertWindMPHToUser(data1.wind_speed_avg_last_10_min ?? 0); - DoWind(ConvertWindMPHToUser(wind), wdir, wspdAvg10min, dateTime); + DoWind(wind, wdir, wspdAvg10min, dateTime); - WindAverage = wspdAvg10min * cumulus.Calib.WindSpeed.Mult; + //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 /* @@ -1245,11 +1245,6 @@ private void DecodeCurrent(string currentJson) cumulus.LogDebugMessage($"WLL current: found an unknown transmitter type [{type}]!"); break; } - - DoForecast(string.Empty, false); - - UpdateStatusPanel(DateTime.Now); - UpdateMQTT(); } // Now we have the primary data, calculate the derived data @@ -1271,6 +1266,11 @@ private void DecodeCurrent(string currentJson) DoFeelsLike(dateTime); DoHumidex(dateTime); + DoForecast(string.Empty, false); + + UpdateStatusPanel(DateTime.Now); + UpdateMQTT(); + 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 @@ -1442,15 +1442,15 @@ private void bw_ReadHistoryCompleted(object sender, RunWorkerCompletedEventArgs /* private void bw_DoStart(object sender, DoWorkEventArgs e) { - cumulus.LogDebugMessage("Lock: Station waiting for lock"); + //cumulus.LogDebugMessage("Lock: Station waiting for lock"); Cumulus.syncInit.Wait(); - cumulus.LogDebugMessage("Lock: Station has the lock"); + //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.LogDebugMessage("Lock: Station releasing lock"); Cumulus.syncInit.Release(); } */ @@ -1460,9 +1460,9 @@ private void bw_ReadHistory(object sender, DoWorkEventArgs e) BackgroundWorker worker = sender as BackgroundWorker; int archiveRun = 0; - cumulus.LogDebugMessage("Lock: Station waiting for the lock"); + //cumulus.LogDebugMessage("Lock: Station waiting for the lock"); Cumulus.syncInit.Wait(); - cumulus.LogDebugMessage("Lock: Station has the lock"); + //cumulus.LogDebugMessage("Lock: Station has the lock"); try { @@ -1495,7 +1495,8 @@ private void bw_ReadHistory(object sender, DoWorkEventArgs e) { cumulus.LogMessage("Exception occurred reading archive data: " + ex.Message); } - cumulus.LogDebugMessage("Lock: Station releasing the lock"); + + //cumulus.LogDebugMessage("Lock: Station releasing the lock"); Cumulus.syncInit.Release(); bwDoneEvent.Set(); } diff --git a/CumulusMX/EcowittApi.cs b/CumulusMX/EcowittApi.cs index c6511518..9ad62cfe 100644 --- a/CumulusMX/EcowittApi.cs +++ b/CumulusMX/EcowittApi.cs @@ -415,6 +415,7 @@ private void ProcessHistoryData(EcowittHistoricData data) } } } + // Dewpoint if (data.outdoor.dew_point != null && data.outdoor.dew_point.list != null) { @@ -493,6 +494,7 @@ private void ProcessHistoryData(EcowittHistoricData data) } } } + // Direction if (data.wind.wind_direction != null && data.wind.wind_direction.list != null) { @@ -688,6 +690,7 @@ private void ProcessHistoryData(EcowittHistoricData data) } } } + // uvi if (data.solar_and_uvi.uvi != null && data.solar_and_uvi.uvi.list != null) { @@ -802,6 +805,7 @@ private void ProcessHistoryData(EcowittHistoricData data) } } } + // humidity if (srcTH.humidity != null && srcTH.humidity.list != null) { @@ -949,6 +953,7 @@ private void ProcessHistoryData(EcowittHistoricData data) } } } + // 24 Avg if (data.indoor_co2.average24h != null && data.indoor_co2.average24h.list != null) { @@ -1004,6 +1009,7 @@ private void ProcessHistoryData(EcowittHistoricData data) } } } + // 24 Avg if (data.co2_aqi_combo.average24h != null && data.co2_aqi_combo.average24h.list != null) { @@ -1186,11 +1192,13 @@ private void ProcessHistoryData(EcowittHistoricData data) ApplyHistoricData(rec); // add in archive period worth of sunshine, if sunny - if (station.SolarRad > station.CurrentSolarMax * cumulus.SolarOptions.SunThreshold / 100 && + if (station.CurrentSolarMax > 0 && + station.SolarRad > station.CurrentSolarMax * cumulus.SolarOptions.SunThreshold / 100 && station.SolarRad >= cumulus.SolarOptions.SolarMinimum && !cumulus.SolarOptions.UseBlakeLarsen) { station.SunshineHours += 5 / 60.0; + cumulus.LogDebugMessage("Adding 5 minutes to Sunshine Hours"); } diff --git a/CumulusMX/EmailSender.cs b/CumulusMX/EmailSender.cs index 3b1b0c61..eb2f4a72 100644 --- a/CumulusMX/EmailSender.cs +++ b/CumulusMX/EmailSender.cs @@ -27,9 +27,9 @@ public async void SendEmail(string[] to, string from, string subject, string mes { try { - cumulus.LogDebugMessage($"SendEmail: Waiting for lock..."); + //cumulus.LogDebugMessage($"SendEmail: Waiting for lock..."); await _writeLock.WaitAsync(); - cumulus.LogDebugMessage($"SendEmail: Has the lock"); + //cumulus.LogDebugMessage($"SendEmail: Has the lock"); var logMessage = ToLiteral(message); @@ -80,7 +80,7 @@ public async void SendEmail(string[] to, string from, string subject, string mes } finally { - cumulus.LogDebugMessage($"SendEmail: Releasing lock..."); + //cumulus.LogDebugMessage($"SendEmail: Releasing lock..."); _writeLock.Release(); } } @@ -91,9 +91,9 @@ public string SendTestEmail(string[] to, string from, string subject, string mes try { - cumulus.LogDebugMessage($"SendEmail: Waiting for lock..."); + //cumulus.LogDebugMessage($"SendEmail: Waiting for lock..."); _writeLock.Wait(); - cumulus.LogDebugMessage($"SendEmail: Has the lock"); + //cumulus.LogDebugMessage($"SendEmail: Has the lock"); cumulus.LogDebugMessage($"SendEmail: Sending Test email, to [{string.Join("; ", to)}], subject [{subject}], body [{message}]..."); @@ -145,7 +145,7 @@ public string SendTestEmail(string[] to, string from, string subject, string mes } finally { - cumulus.LogDebugMessage($"SendEmail: Releasing lock..."); + //cumulus.LogDebugMessage($"SendEmail: Releasing lock..."); _writeLock.Release(); } diff --git a/CumulusMX/FOStation.cs b/CumulusMX/FOStation.cs index 888d6dec..66c6799a 100644 --- a/CumulusMX/FOStation.cs +++ b/CumulusMX/FOStation.cs @@ -161,9 +161,9 @@ 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.LogDebugMessage("Lock: Station waiting for the lock"); Cumulus.syncInit.Wait(); - cumulus.LogDebugMessage("Lock: Station has the lock"); + //cumulus.LogDebugMessage("Lock: Station has the lock"); try { getAndProcessHistoryData(); @@ -172,7 +172,7 @@ private void bw_DoWork(object sender, DoWorkEventArgs e) { cumulus.LogMessage("Exception occurred reading archive data: " + ex.Message); } - cumulus.LogDebugMessage("Lock: Station releasing the lock"); + //cumulus.LogDebugMessage("Lock: Station releasing the lock"); Cumulus.syncInit.Release(); } diff --git a/CumulusMX/GW1000Station.cs b/CumulusMX/GW1000Station.cs index 626c49b0..72a93897 100644 --- a/CumulusMX/GW1000Station.cs +++ b/CumulusMX/GW1000Station.cs @@ -54,6 +54,10 @@ public GW1000Station(Cumulus cumulus) : base(cumulus) // GW1000 does not provide 10 min average wind speeds cumulus.StationOptions.UseWind10MinAvg = true; + // GW1000 does not provide an interval gust value, it gives us a 30 second high + // so force using the wind speed for the average calculation + cumulus.StationOptions.UseSpeedForAvgCalc = true; + LightningTime = DateTime.MinValue; LightningDistance = 999; @@ -249,9 +253,9 @@ public override void Stop() public override void getAndProcessHistoryData() { - cumulus.LogDebugMessage("Lock: Station waiting for the lock"); + //cumulus.LogDebugMessage("Lock: Station waiting for the lock"); Cumulus.syncInit.Wait(); - cumulus.LogDebugMessage("Lock: Station has the lock"); + //cumulus.LogDebugMessage("Lock: Station has the lock"); if (string.IsNullOrEmpty(cumulus.EcowittApplicationKey) || string.IsNullOrEmpty(cumulus.EcowittUserApiKey) || string.IsNullOrEmpty(cumulus.EcowittMacAddress)) { @@ -279,7 +283,7 @@ public override void getAndProcessHistoryData() } } - cumulus.LogDebugMessage("Lock: Station releasing the lock"); + //cumulus.LogDebugMessage("Lock: Station releasing the lock"); _ = Cumulus.syncInit.Release(); if (cancellationToken.IsCancellationRequested) @@ -1173,8 +1177,9 @@ private void GetLiveData() if (gustLast > -999 && windSpeedLast > -999 && windDirLast > -999) { - //DoWind(gustLast, windDirLast, windSpeedLast, dateTime); + DoWind(gustLast, windDirLast, windSpeedLast, dateTime); + /* // The protocol does not provide an average value // so feed in current MX average DoWind(windSpeedLast, windDirLast, WindAverage / cumulus.Calib.WindSpeed.Mult, dateTime); @@ -1192,6 +1197,7 @@ private void GetLiveData() RecentMaxGust = gustLastCal; } + */ } if (rainLast > -999 && rainRateLast > -999) diff --git a/CumulusMX/HttpStationEcowitt.cs b/CumulusMX/HttpStationEcowitt.cs index 29dbf3e8..655fdb99 100644 --- a/CumulusMX/HttpStationEcowitt.cs +++ b/CumulusMX/HttpStationEcowitt.cs @@ -46,6 +46,11 @@ public HttpStationEcowitt(Cumulus cumulus, WeatherStation station = null) : base // does not provide 10 min average wind speeds cumulus.StationOptions.UseWind10MinAvg = true; + // GW1000 does not provide an interval gust value, it gives us a 2 minute high + // So CMX porces the latest speed into the gust + // Therefore we need to force using the gust (but we actually input the speed) for the average calculation + cumulus.StationOptions.UseSpeedForAvgCalc = false; + // does not send DP, so force MX to calculate it cumulus.StationOptions.CalculatedDP = true; // Same for Wind Chill @@ -149,9 +154,9 @@ public override void Stop() public override void getAndProcessHistoryData() { - cumulus.LogDebugMessage("Lock: Station waiting for the lock"); + //cumulus.LogDebugMessage("Lock: Station waiting for the lock"); Cumulus.syncInit.Wait(); - cumulus.LogDebugMessage("Lock: Station has the lock"); + //cumulus.LogDebugMessage("Lock: Station has the lock"); if (string.IsNullOrEmpty(cumulus.EcowittApplicationKey) || string.IsNullOrEmpty(cumulus.EcowittUserApiKey) || string.IsNullOrEmpty(cumulus.EcowittMacAddress)) { @@ -179,7 +184,7 @@ public override void getAndProcessHistoryData() } } - cumulus.LogDebugMessage("Lock: Station releasing the lock"); + //cumulus.LogDebugMessage("Lock: Station releasing the lock"); _ = Cumulus.syncInit.Release(); StartLoop(); @@ -323,6 +328,7 @@ public string ApplyData(string dataString, bool main, DateTime? ts = null) DoWind(spdVal, dirVal, WindAverage / cumulus.Calib.WindSpeed.Mult, recDate); var gustLastCal = gustVal * cumulus.Calib.WindGust.Mult; + if (gustLastCal > RecentMaxGust) { cumulus.LogDebugMessage("Setting max gust from current value: " + gustLastCal.ToString(cumulus.WindFormat)); @@ -1196,6 +1202,10 @@ private void SetCustomServer(GW1000Api api, bool main) idx = 5 + data2[4]; var wuPath = Encoding.ASCII.GetString(data2, idx + 1, data2[idx]); + // Ecowitt actually sends data at interval + 1 seconds! :( + customIntv -= 1; + if (customIntv < 1) + customIntv = 1; cumulus.LogMessage($"Ecowitt Gateway Custom Server config: Server={server}, Port={port}, Path={ecPath}, Interval={intv}, Protocol={type}, Enabled={active}"); @@ -1256,6 +1266,7 @@ private void SetCustomServer(GW1000Api api, bool main) else { cumulus.LogMessage($"Set Ecowitt Gateway Custom Server config to: Server={customServer}, Port={customPort}, Interval={customIntv}, Protocol={0}, Enabled={1}"); + cumulus.LogMessage("Ecowitt Gateway Custom Server. Note, the set interval should be 1 less than the value set in the CMX configuration"); } } diff --git a/CumulusMX/Properties/AssemblyInfo.cs b/CumulusMX/Properties/AssemblyInfo.cs index 09994fda..1aed9117 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.16.1 - Build 3183")] +[assembly: AssemblyDescription("Version 3.17.0 - Build 3184")] [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.16.1.3183")] -[assembly: AssemblyFileVersion("3.16.1.3183")] +[assembly: AssemblyVersion("3.17.0.3184")] +[assembly: AssemblyFileVersion("3.17.0.3184")] diff --git a/CumulusMX/Simulator.cs b/CumulusMX/Simulator.cs new file mode 100644 index 00000000..16d4daa9 --- /dev/null +++ b/CumulusMX/Simulator.cs @@ -0,0 +1,285 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CumulusMX +{ + internal class Simulator : WeatherStation + { + private bool stop; + private CancellationTokenSource tokenSource = new CancellationTokenSource(); + private CancellationToken cancellationToken; + + private DataSet currData; + + private int dataUpdateRate = 5000; // 5 second data update rate + + private new readonly Random random; + private bool solarIntialised; + + public Simulator(Cumulus cumulus) : base(cumulus) + { + + cumulus.LogMessage("Station type = Simulator"); + + cumulus.LogMessage("Last update time = " + cumulus.LastUpdateTime); + + cancellationToken = tokenSource.Token; + + random = new Random(); + + currData = new DataSet(); + + cumulus.StationOptions.CalculatedDP = true; + cumulus.StationOptions.CalculatedET = true; + cumulus.StationOptions.CalculatedWC = true; + cumulus.StationOptions.UseWind10MinAvg = true; + cumulus.StationOptions.UseCumulusPresstrendstr = true; + cumulus.StationOptions.UseSpeedForAvgCalc = false; + + WindAverage = 0; + + timerStartNeeded = true; + LoadLastHoursFromDataLogs(cumulus.LastUpdateTime); + DoDayResetIfNeeded(); + DoTrendValues(DateTime.Now); + + } + + + public override void Start() + { + while (!stop) + { + try + { + var now = DateTime.Now; + + currData.SetNewData(now); + + applyData(now); + + DoForecast(string.Empty, false); + + UpdateStatusPanel(now); + UpdateMQTT(); + + if (cancellationToken.WaitHandle.WaitOne(TimeSpan.FromMilliseconds(dataUpdateRate))) + { + break; + } + } + catch (ThreadAbortException) // Catch the ThreadAbortException + { + cumulus.LogMessage("Simulator Start: ThreadAbortException"); + // and exit + stop = true; + } + catch (Exception ex) + { + // any others, log them and carry on + cumulus.LogMessage("Simulator Start: Exception = " + ex.Message); + } + } + + cumulus.LogMessage("Ending normal reading loop"); + } + + public override void Stop() + { + StopMinuteTimer(); + + cumulus.LogMessage("Stopping data generation task"); + try + { + if (tokenSource != null) + tokenSource.Cancel(); + cumulus.LogMessage("Waiting for data generation to complete"); + } + catch (Exception ex) + { + cumulus.LogMessage("Error stopping the simulator" + ex.Message); + } + } + + + private void applyData(DateTime recDate) + { + cumulus.LogDataMessage($"Simulated data: temp={ConvertTempCToUser(currData.tempVal):f1}, hum={currData.humVal}, gust={ConvertWindMPHToUser(currData.windSpeedVal):f2}, dir={currData.windBearingVal}, press={ConvertPressMBToUser(currData.pressureVal):f2}, r.rate={ConvertRainMMToUser(currData.rainRateVal):f2}"); + + DoWind(ConvertWindMPHToUser(currData.windSpeedVal), currData.windBearingVal, WindAverage / cumulus.Calib.WindSpeed.Mult, recDate); + + var rain = Raincounter + ConvertRainMMToUser(currData.rainRateVal * dataUpdateRate / 1000 / 3600); + + DoRain(rain, ConvertRainMMToUser(currData.rainRateVal), recDate); + + DoIndoorTemp(ConvertTempCToUser(currData.tempInVal)); + DoIndoorHumidity(currData.humInVal); + + DoOutdoorHumidity(currData.humVal, recDate); + DoOutdoorTemp(ConvertTempCToUser(currData.tempVal), recDate); + + DoPressure(ConvertPressMBToUser(currData.pressureVal), recDate); + UpdatePressureTrendString(); + + doSolar(recDate); + + DoOutdoorDewpoint(0, recDate); + DoWindChill(0, recDate); + DoHumidex(recDate); + DoApparentTemp(recDate); + DoFeelsLike(recDate); + } + + private void doSolar(DateTime recDate) + { + // For the solar random walk we are chasing the theoretical solat max value + double solar = SolarRad; + + // if we are starting up, set the intial solar rad value to 90% of theoretical + if (!solarIntialised) + { + CurrentSolarMax = AstroLib.SolarMax(recDate, cumulus.Longitude, cumulus.Latitude, AltitudeM(cumulus.Altitude), out SolarElevation, cumulus.SolarOptions); + solar = CurrentSolarMax * 0.9; + solarIntialised = true; + } + + // aim for 85% of theoretical in the morning, 75% after local noon + double factor; + if (recDate.IsDaylightSavingTime()) + { + factor = recDate.Hour < 13 ? 0.85 : 0.75; + } + else + { + factor = recDate.Hour < 12 ? 0.85 : 0.75; + } + + // If it's raining, make it dull! + if (RainRate > 0) + { + factor = 0.3; + } + + var volatility = CurrentSolarMax * 0.05; + if (volatility < 2) + volatility = 2; + else if (volatility > 30) + volatility = 30; + + solar -= (solar - CurrentSolarMax * factor) * 0.02; + solar += volatility * (2 * random.NextDouble() - 1); + if (solar < 0 || CurrentSolarMax == 0) + solar = 0; + DoSolarRad((int)solar, recDate); + } + + + private class DataSet + { + private MeanRevertingRandomWalk temperature; + private MeanRevertingRandomWalk humidity; + private MeanRevertingRandomWalk windSpeed; + private MeanRevertingRandomWalk windDirection; + private MeanRevertingRandomWalk insideTemp; + private MeanRevertingRandomWalk insideHum; + private MeanRevertingRandomWalk pressure; + private MeanRevertingRandomWalk rainRate; + + public double tempVal { get; set; } + public int humVal { get; set; } + public double windSpeedVal { get; set; } + public int windBearingVal { get; set; } + public double rainRateVal { get; set; } + public double pressureVal { get; set; } + public double tempInVal { get; set; } + public int humInVal { get; set; } + + + public DataSet() + { + // Temperature - both annual and daily variations, daily offset by 0.1 of a day + var tempMean = new Func((x) => 15 + 10 * Math.Cos(x.DayOfYear / 365.0 * 2 * Math.PI) - 10 * Math.Cos((x.TimeOfDay.TotalDays - 0.1) * 2 * Math.PI)); + // Wind - daily variation, offset by 0.1 of a day + var windMean = new Func((x) => 10 - 9.5 * Math.Cos((x.TimeOfDay.TotalDays - 0.1) * 2 * Math.PI)); + var windVolatility = new Func((x) => 2 - 1.5 * Math.Cos((x.TimeOfDay.TotalDays - 0.1) * 2 * Math.PI)); + // Humidity - daily variation, offset by 0.1 of a day + var humMean = new Func((x) => 60 + 30 * Math.Cos((x.TimeOfDay.TotalDays - 0.1) * 2 * Math.PI)); + // Pressure - vary the range over a two day period + var pressMean = new Func((x) => 1010 + 25 * Math.Cos((x.DayOfYear + x.TimeOfDay.TotalDays + 0.2) % 4 / 4.0 * 2 * Math.PI) - 6 * Math.Cos((x.TimeOfDay.TotalDays + 0.65) * 2 * Math.PI)); + // Inside Temp - assume heating between 07:00 and 23:00 + var inTempMean = new Func((x) => (x.Hour < 7 || x.Hour > 22) ? 16 : 21); + // RainRate - lets try two blocks of rain per day, determined by day of the year, mean rate to be 3 mm/hr + var rainRateMean = new Func((x) => x.Hour == x.DayOfYear % 24 || x.Hour == (x.DayOfYear + 1) % 24 || x.Hour == (x.DayOfYear + 12) % 24 || x.Hour == (x.DayOfYear + 13) % 24 ? 3 : -100); + + temperature = new MeanRevertingRandomWalk(tempMean, (x) => 0.1, 0.01, -10, 30); + humidity = new MeanRevertingRandomWalk(humMean, (x) => 1, 0.01, 10, 100); + windSpeed = new MeanRevertingRandomWalk(windMean, windVolatility, 0.02, 0, 50); + windDirection = new MeanRevertingRandomWalk((x) => 191, (x) => 10, 0.005, 0, 720); + pressure = new MeanRevertingRandomWalk(pressMean, (x) => .1, 0.05, 950, 1050); + rainRate = new MeanRevertingRandomWalk(rainRateMean, (x) => 5, 0.05, 0, 30); + insideTemp = new MeanRevertingRandomWalk(inTempMean, (x) => 0.1, 0.01, 15, 25); + insideHum = new MeanRevertingRandomWalk((x) => 50, (x) => 0.5, 0.005, 35, 75); + } + + public void SetNewData(DateTime readTime) + { + tempVal = temperature.GetValue(readTime); + humVal = (int)humidity.GetValue(readTime); + windSpeedVal = Math.Round(windSpeed.GetValue(readTime), 1); + if (windSpeedVal > 0) + { + windBearingVal = ((int)windDirection.GetValue(readTime) % 360) + 1; + } + rainRateVal = rainRate.GetValue(readTime); + pressureVal = pressure.GetValue(readTime); + tempInVal = insideTemp.GetValue(readTime); + humInVal = (int)insideHum.GetValue(readTime); + } + } + + private class MeanRevertingRandomWalk + { + private readonly Func _meanCurve; + private readonly Func _volatility; + private readonly double _meanReversion; + private readonly double _cropMin; + private readonly double _cropMax; + + private double _value; + private bool _initialised = false; + private readonly Random _random; + + public MeanRevertingRandomWalk(Func meanCurve, Func volatility, double meanReversion, double cropMin, double cropMax) + { + _meanCurve = meanCurve; + _volatility = volatility; + _meanReversion = meanReversion; + _cropMin = cropMin; + _cropMax = cropMax; + _random = new Random(); + } + + public double GetValue(DateTime date) + { + if (!_initialised) + { + _value = _meanCurve(date); + _initialised = true; + } + + + _value -= (_value - _meanCurve(date)) * _meanReversion; + _value += _volatility(date) * (2 * _random.NextDouble() - 1); + if (_value < _cropMin) return _cropMin; + if (_value > _cropMax) return _cropMax; + return _value; + } + } + + } +} diff --git a/CumulusMX/TempestStation.cs b/CumulusMX/TempestStation.cs index 962ac582..6c0928b2 100644 --- a/CumulusMX/TempestStation.cs +++ b/CumulusMX/TempestStation.cs @@ -36,9 +36,9 @@ public TempestStation(Cumulus cumulus) : base(cumulus) public override void getAndProcessHistoryData() { - cumulus.LogDebugMessage("Lock: Station waiting for the lock"); + //cumulus.LogDebugMessage("Lock: Station waiting for the lock"); Cumulus.syncInit.Wait(); - cumulus.LogDebugMessage("Lock: Station has the lock"); + //cumulus.LogDebugMessage("Lock: Station has the lock"); try { var stTime = cumulus.LastUpdateTime; @@ -61,7 +61,7 @@ public override void getAndProcessHistoryData() te = te.InnerException; } } - cumulus.LogDebugMessage("Lock: Station releasing the lock"); + //cumulus.LogDebugMessage("Lock: Station releasing the lock"); Cumulus.syncInit.Release(); StartLoop(); } diff --git a/CumulusMX/WeatherStation.cs b/CumulusMX/WeatherStation.cs index 0a44c162..b397cc0d 100644 --- a/CumulusMX/WeatherStation.cs +++ b/CumulusMX/WeatherStation.cs @@ -18,6 +18,7 @@ using Timer = System.Timers.Timer; using ServiceStack.Text; using System.Web; +using System.Threading.Tasks; namespace CumulusMX { @@ -42,6 +43,7 @@ public struct TWindVec public readonly Object yearIniThreadLock = new Object(); public readonly Object alltimeIniThreadLock = new Object(); public readonly Object monthlyalltimeIniThreadLock = new Object(); + private readonly Object webSocketThreadLock = new Object(); // holds all time highs and lows public AllTimeRecords AllTime = new AllTimeRecords(); @@ -1426,11 +1428,26 @@ public void SecondTimer(object sender, ElapsedEventArgs e) } } - if ((int)timeNow.TimeOfDay.TotalMilliseconds % 2500 <= 500) + // send current data to web-socket every 5 seconds, unless it has already been sent within the 10 seconds + if (LastDataReadTimestamp.AddSeconds(5) < timeNow && (int)timeNow.TimeOfDay.TotalMilliseconds % 10000 <= 500) { - // send current data to web-socket every 3 seconds - try + _ = sendWebSocketData(); + } + } + + private async Task sendWebSocketData() + { + // Return control to the calling method immediately. + await Task.Yield(); + + // send current data to web-socket + try + { + // wait for the ws lock object + lock (webSocketThreadLock); { + //cumulus.LogDebugMessage("WebSocket: Sending message"); + StringBuilder windRoseData = new StringBuilder(80); lock (windcounts) @@ -1478,12 +1495,21 @@ public void SecondTimer(object sender, ElapsedEventArgs e) stream.Position = 0; + cumulus.LogDebugMessage("WebSocket: Send message"); WebSocket.SendMessage(new StreamReader(stream).ReadToEnd()); + + // We can't be sure when the broadcast completes because it is async internally, so the best we can do is wait a short time + await Task.Delay(500); } - catch (Exception ex) - { - cumulus.LogMessage(ex.Message); - } + } + catch (Exception ex) + { + cumulus.LogMessage("sendWebSocketData: Error - " + ex.Message); + } + finally + { + //cumulus.LogDebugMessage("WebSocket: End message"); + //webSocketLocked = false; } } @@ -7109,6 +7135,7 @@ public logfilerec ParseLogFileRec(string data, bool minMax) internal void UpdateStatusPanel(DateTime timestamp) { LastDataReadTimestamp = timestamp; + sendWebSocketData(); } diff --git a/Updates.txt b/Updates.txt index efd783e4..e229099a 100644 --- a/Updates.txt +++ b/Updates.txt @@ -1,3 +1,12 @@ +3.17.0 - b3184 +—————————————— + +- New: Adds a PWS Simulator station type for testing or trial purposes + +- Change: The "live" dashboard screens now refresh whenever new data is received, or every five seconds + + + 3.16.1 - b3183 —————————————— - Fix: Error message about Ecowitt sensor mapping when saving the station settings for non-Ecowitt stations From 70bdb31f18de7b82894513b7448a5b7b3d28e76e Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Wed, 11 May 2022 15:45:51 +0100 Subject: [PATCH 2/5] - Fix: Cloud base being set to large value at start-up --- CumulusMX/Cumulus.cs | 8 +++--- CumulusMX/DavisAirLink.cs | 8 +++--- CumulusMX/DavisStation.cs | 2 ++ CumulusMX/DavisWllStation.cs | 8 +++--- CumulusMX/EasyWeather.cs | 1 + CumulusMX/EcowittApi.cs | 29 ++++++-------------- CumulusMX/FOStation.cs | 10 ++++--- CumulusMX/GW1000Api.cs | 4 +-- CumulusMX/GW1000Station.cs | 45 +++---------------------------- CumulusMX/HttpStationAmbient.cs | 1 + CumulusMX/HttpStationEcowitt.cs | 28 +++++--------------- CumulusMX/HttpStationWund.cs | 1 + CumulusMX/ImetStation.cs | 5 ++++ CumulusMX/Simulator.cs | 23 ++++++++-------- CumulusMX/StationSettings.cs | 6 ++--- CumulusMX/TempestStation.cs | 14 ++++------ CumulusMX/WM918Station.cs | 1 + CumulusMX/WMR100Station.cs | 1 + CumulusMX/WMR200Station.cs | 2 ++ CumulusMX/WMR928Station.cs | 2 ++ CumulusMX/WS2300Station.cs | 3 +++ CumulusMX/WeatherStation.cs | 47 ++++++++++++++++----------------- Updates.txt | 4 ++- 23 files changed, 103 insertions(+), 150 deletions(-) diff --git a/CumulusMX/Cumulus.cs b/CumulusMX/Cumulus.cs index ee47ed88..31e1701a 100644 --- a/CumulusMX/Cumulus.cs +++ b/CumulusMX/Cumulus.cs @@ -492,8 +492,8 @@ public struct TExtraFiles private List OWMList = new List(); // Use thread safe queues for the MySQL command lists - private ConcurrentQueue MySqlList = new ConcurrentQueue(); - private ConcurrentQueue MySqlFailedList = new ConcurrentQueue(); + private readonly ConcurrentQueue MySqlList = new ConcurrentQueue(); + private readonly ConcurrentQueue MySqlFailedList = new ConcurrentQueue(); // Calibration settings /// @@ -757,7 +757,7 @@ public struct MqttSettings public string loggingfile; - private PingReply pingReply; + //private PingReply pingReply; public Cumulus(int HTTPport, bool DebugEnabled, string startParms) { @@ -10512,7 +10512,7 @@ private void PingCompletedCallback(object sender, PingCompletedEventArgs e) LogMessage("Ping reply: " + e.Reply.Status); } - pingReply = e.Reply; + //pingReply = e.Reply; } private void CreateRequiredFolders() diff --git a/CumulusMX/DavisAirLink.cs b/CumulusMX/DavisAirLink.cs index 0e5f0c58..4a352f86 100644 --- a/CumulusMX/DavisAirLink.cs +++ b/CumulusMX/DavisAirLink.cs @@ -16,10 +16,10 @@ namespace CumulusMX { internal class DavisAirLink { - private Cumulus cumulus; - private WeatherStation station; + private readonly Cumulus cumulus; + private readonly WeatherStation station; - private string ipaddr; + private readonly string ipaddr; private readonly System.Timers.Timer tmrCurrent; private System.Timers.Timer tmrHealth; private readonly object threadSafer = new object(); @@ -42,7 +42,7 @@ internal class DavisAirLink private readonly bool standaloneHistory; // Used to flag if we need to get history data on catch-up private DateTime airLinkLastUpdateTime; - private DiscoveredDevices discovered = new DiscoveredDevices(); + private readonly DiscoveredDevices discovered = new DiscoveredDevices(); public DavisAirLink(Cumulus cumulus, bool indoor, WeatherStation station) { diff --git a/CumulusMX/DavisStation.cs b/CumulusMX/DavisStation.cs index 99869d83..4f83d2c3 100644 --- a/CumulusMX/DavisStation.cs +++ b/CumulusMX/DavisStation.cs @@ -1636,6 +1636,7 @@ private void GetAndProcessLoopData(int number) DoApparentTemp(now); DoFeelsLike(now); DoHumidex(now); + DoCloudBaseHeatIndex(now); var forecastRule = loopData.ForecastRule < cumulus.DavisForecastLookup.Length ? loopData.ForecastRule : cumulus.DavisForecastLookup.Length - 1; @@ -2507,6 +2508,7 @@ private void GetArchiveData() DoApparentTemp(timestamp); DoFeelsLike(timestamp); DoHumidex(timestamp); + DoCloudBaseHeatIndex(timestamp); // add in 'archivePeriod' minutes worth of wind speed to windrun WindRunToday += ((WindAverage * WindRunHourMult[cumulus.Units.Wind] * interval) / 60.0); diff --git a/CumulusMX/DavisWllStation.cs b/CumulusMX/DavisWllStation.cs index 93cf8545..33226f81 100644 --- a/CumulusMX/DavisWllStation.cs +++ b/CumulusMX/DavisWllStation.cs @@ -40,10 +40,10 @@ internal class DavisWllStation : WeatherStation 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 CancellationTokenSource tokenSource = new CancellationTokenSource(); + private readonly CancellationTokenSource tokenSource = new CancellationTokenSource(); private CancellationToken cancellationToken; private Task broadcastTask; - private AutoResetEvent bwDoneEvent = new AutoResetEvent(false); + private readonly AutoResetEvent bwDoneEvent = new AutoResetEvent(false); private readonly List sensorList = new List(); private readonly bool useWeatherLinkDotCom = true; @@ -733,7 +733,7 @@ private void DecodeCurrent(string currentJson) DoOutdoorTemp(ConvertTempFToUser(data1.temp.Value), dateTime); if (data1.dew_point.HasValue) - DoOutdoorDewpoint(ConvertTempFToUser(data1.dew_point.Value), dateTime); + DoOutdoorDewpoint(ConvertTempFToUser(data1.dew_point.Value), dateTime); if (!cumulus.StationOptions.CalculatedWC && data1.wind_chill.HasValue) { @@ -1265,6 +1265,7 @@ private void DecodeCurrent(string currentJson) DoApparentTemp(dateTime); DoFeelsLike(dateTime); DoHumidex(dateTime); + DoCloudBaseHeatIndex(dateTime); DoForecast(string.Empty, false); @@ -1814,6 +1815,7 @@ private void GetWlHistoricData(BackgroundWorker worker) DoApparentTemp(timestamp); DoFeelsLike(timestamp); DoHumidex(timestamp); + DoCloudBaseHeatIndex(timestamp); // Log all the data cumulus.DoLogFile(timestamp, false); diff --git a/CumulusMX/EasyWeather.cs b/CumulusMX/EasyWeather.cs index 4940b42b..675ab45f 100644 --- a/CumulusMX/EasyWeather.cs +++ b/CumulusMX/EasyWeather.cs @@ -161,6 +161,7 @@ private void EWGetData(object sender, ElapsedEventArgs elapsedEventArgs) DoApparentTemp(now); DoFeelsLike(now); DoHumidex(now); + DoCloudBaseHeatIndex(now); DoForecast(string.Empty, false); diff --git a/CumulusMX/EcowittApi.cs b/CumulusMX/EcowittApi.cs index 9ad62cfe..6cf43ab4 100644 --- a/CumulusMX/EcowittApi.cs +++ b/CumulusMX/EcowittApi.cs @@ -85,8 +85,8 @@ internal bool GetHistoricData(DateTime startTime, DateTime endTime) sb.Append($"application_key={cumulus.EcowittApplicationKey}"); sb.Append($"&api_key={cumulus.EcowittUserApiKey}"); sb.Append($"&mac={cumulus.EcowittMacAddress}"); - sb.Append($"&start_date={apiStartDate.ToString("yyyy-MM-dd'%20'HH:mm:ss")}"); - sb.Append($"&end_date={apiEndDate.ToString("yyyy-MM-dd'%20'HH:mm:ss")}"); + sb.Append($"&start_date={apiStartDate:yyyy-MM-dd'%20'HH:mm:ss}"); + sb.Append($"&end_date={apiEndDate:yyyy-MM-dd'%20'HH:mm:ss}"); // Request the data in the correct units sb.Append($"&temp_unitid={cumulus.Units.Temp + 1}"); // 1=C, 2=F @@ -182,7 +182,7 @@ internal bool GetHistoricData(DateTime startTime, DateTime endTime) var url = sb.ToString(); - var msg = $"Processing history data from {startTime.ToString("yyyy-MM-dd HH:mm")} to {endTime.AddMinutes(5).ToString("yyyy-MM-dd HH:mm")}..."; + var msg = $"Processing history data from {startTime:yyyy-MM-dd HH:mm} to {endTime.AddMinutes(5):yyyy-MM-dd HH:mm}..."; cumulus.LogMessage($"API.GetHistoricData: " + msg); cumulus.LogConsoleMessage(msg); @@ -260,8 +260,10 @@ internal bool GetHistoricData(DateTime startTime, DateTime endTime) // have we reached the retry limit? if (--retries <= 0) + { cumulus.LastUpdateTime = endTime; - return false; + return false; + } cumulus.LogMessage("API.GetHistoricData: System Busy or Rate Limited, waiting 5 secs before retry..."); System.Threading.Thread.Sleep(5000); @@ -1255,24 +1257,8 @@ private void ApplyHistoricData(KeyValuePair r var spdVal = (double)rec.Value.WindSpd; var dirVal = (int)rec.Value.WindDir.Value; - // The protocol does not provide an average value - // so feed in current MX average - station.DoWind(spdVal, dirVal, station.WindAverage / cumulus.Calib.WindSpeed.Mult, rec.Key); + station.DoWind(gustVal, dirVal, spdVal, rec.Key); - var gustLastCal = gustVal * cumulus.Calib.WindGust.Mult; - if (gustLastCal > station.RecentMaxGust) - { - cumulus.LogDebugMessage("Setting max gust from current value: " + gustLastCal.ToString(cumulus.WindFormat)); - station.CheckHighGust(gustLastCal, dirVal, rec.Key); - - // add to recent values so normal calculation includes this value - station.WindRecent[station.nextwind].Gust = gustVal; // use uncalibrated value - station.WindRecent[station.nextwind].Speed = station.WindAverage / cumulus.Calib.WindSpeed.Mult; - station.WindRecent[station.nextwind].Timestamp = rec.Key; - station.nextwind = (station.nextwind + 1) % WeatherStation.MaxWindRecent; - - station.RecentMaxGust = gustLastCal; - } } } catch (Exception ex) @@ -1580,6 +1566,7 @@ private void ApplyHistoricData(KeyValuePair r try { station.DoHumidex(rec.Key); + station.DoCloudBaseHeatIndex(rec.Key); // === Apparent & Feels Like === - requires temp, hum, and windspeed if (rec.Value.WindSpd.HasValue) diff --git a/CumulusMX/FOStation.cs b/CumulusMX/FOStation.cs index 66c6799a..63dfffc9 100644 --- a/CumulusMX/FOStation.cs +++ b/CumulusMX/FOStation.cs @@ -526,6 +526,7 @@ private void ProcessHistoryData() DoApparentTemp(timestamp); DoFeelsLike(timestamp); DoHumidex(timestamp); + DoCloudBaseHeatIndex(timestamp); if (hasSolar) { @@ -1007,10 +1008,8 @@ private void GetAndProcessData() StationPressure = ConvertPressMBToUser(pressure); UpdatePressureTrendString(); - UpdateStatusPanel(now); - UpdateMQTT(); - DoForecast(string.Empty, false); } + var status = data[15]; if ((status & 0x40) != 0) { @@ -1098,6 +1097,7 @@ private void GetAndProcessData() DoApparentTemp(now); DoFeelsLike(now); DoHumidex(now); + DoCloudBaseHeatIndex(now); } // Rain ============================================================ @@ -1163,6 +1163,10 @@ private void GetAndProcessData() DoUV(UVreading, now); } } + + UpdateStatusPanel(now); + UpdateMQTT(); + DoForecast(string.Empty, false); } if (cumulus.SensorAlarm.Enabled) { diff --git a/CumulusMX/GW1000Api.cs b/CumulusMX/GW1000Api.cs index 1a64b486..c7f935ad 100644 --- a/CumulusMX/GW1000Api.cs +++ b/CumulusMX/GW1000Api.cs @@ -11,7 +11,7 @@ namespace CumulusMX { internal class GW1000Api { - private Cumulus cumulus; + private readonly Cumulus cumulus; private NetworkStream stream; private TcpClient socket; private string ipAddress = null; @@ -130,8 +130,6 @@ internal byte[] DoCommand(Commands command, byte[] data = null) var bytesRead = 0; var cmdName = command.ToString(); - var readBuffer = new byte[2028]; - byte[] bytes; if (data == null) { diff --git a/CumulusMX/GW1000Station.cs b/CumulusMX/GW1000Station.cs index 72a93897..0fa906c4 100644 --- a/CumulusMX/GW1000Station.cs +++ b/CumulusMX/GW1000Station.cs @@ -27,17 +27,15 @@ internal class GW1000Station : WeatherStation private int maxArchiveRuns = 1; - private TcpClient socket; - private NetworkStream stream; private bool connectedOk = false; private bool dataReceived = false; private readonly System.Timers.Timer tmrDataWatchdog; - private CancellationTokenSource tokenSource = new CancellationTokenSource(); + private readonly CancellationTokenSource tokenSource = new CancellationTokenSource(); private CancellationToken cancellationToken; - private Task historyTask; + private readonly Task historyTask; private Task liveTask; //private readonly NumberFormatInfo invNum = CultureInfo.InvariantCulture.NumberFormat; @@ -1220,6 +1218,7 @@ private void GetLiveData() DoApparentTemp(dateTime); DoFeelsLike(dateTime); DoHumidex(dateTime); + DoCloudBaseHeatIndex(dateTime); } DoForecast("", false); @@ -1576,44 +1575,6 @@ public Discovery() } } - 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 = GW1000Api.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 void DataTimeout(object source, ElapsedEventArgs e) { if (dataReceived) diff --git a/CumulusMX/HttpStationAmbient.cs b/CumulusMX/HttpStationAmbient.cs index 2c6351d6..837da487 100644 --- a/CumulusMX/HttpStationAmbient.cs +++ b/CumulusMX/HttpStationAmbient.cs @@ -368,6 +368,7 @@ public string ProcessData(IHttpContext context, bool main) if (data["tempf"] != null && data["humidity"] != null) { DoHumidex(recDate); + DoCloudBaseHeatIndex(recDate); // === Apparent === - requires temp, hum, and windspeed if (data["windspeedmph"] != null) diff --git a/CumulusMX/HttpStationEcowitt.cs b/CumulusMX/HttpStationEcowitt.cs index 655fdb99..164957f3 100644 --- a/CumulusMX/HttpStationEcowitt.cs +++ b/CumulusMX/HttpStationEcowitt.cs @@ -47,9 +47,9 @@ public HttpStationEcowitt(Cumulus cumulus, WeatherStation station = null) : base cumulus.StationOptions.UseWind10MinAvg = true; // GW1000 does not provide an interval gust value, it gives us a 2 minute high - // So CMX porces the latest speed into the gust - // Therefore we need to force using the gust (but we actually input the speed) for the average calculation - cumulus.StationOptions.UseSpeedForAvgCalc = false; + // The speed is the average for that update + // Therefore we need to force using the speed for the average calculation + cumulus.StationOptions.UseSpeedForAvgCalc = true; // does not send DP, so force MX to calculate it cumulus.StationOptions.CalculatedDP = true; @@ -323,25 +323,8 @@ public string ApplyData(string dataString, bool main, DateTime? ts = null) var dirVal = Convert.ToInt32(dir, invNum); var spdVal = ConvertWindMPHToUser(Convert.ToDouble(spd, invNum)); - // The protocol does not provide an average value - // so feed in current MX average - DoWind(spdVal, dirVal, WindAverage / cumulus.Calib.WindSpeed.Mult, recDate); + DoWind(gustVal, dirVal, spdVal, recDate); - var gustLastCal = gustVal * cumulus.Calib.WindGust.Mult; - - if (gustLastCal > RecentMaxGust) - { - cumulus.LogDebugMessage("Setting max gust from current value: " + gustLastCal.ToString(cumulus.WindFormat)); - CheckHighGust(gustLastCal, dirVal, recDate); - - // add to recent values so normal calculation includes this value - WindRecent[nextwind].Gust = gustVal; // use uncalibrated value - WindRecent[nextwind].Speed = WindAverage / cumulus.Calib.WindSpeed.Mult; - WindRecent[nextwind].Timestamp = recDate; - nextwind = (nextwind + 1) % MaxWindRecent; - - RecentMaxGust = gustLastCal; - } } } catch (Exception ex) @@ -851,6 +834,7 @@ public string ApplyData(string dataString, bool main, DateTime? ts = null) if (thisTemp != null && thisHum != null) { DoHumidex(recDate); + DoCloudBaseHeatIndex(recDate); // === Apparent === - requires temp, hum, and windspeed if (data["windspeedmph"] != null) @@ -1289,7 +1273,7 @@ private void SetCustomServer(GW1000Api api, bool main) } else { - cumulus.LogMessage($"Set Ecowitt Gateway Custom Server path={path}"); + cumulus.LogMessage($"Set Ecowitt Gateway Custom Server Path={customPath}"); } } } diff --git a/CumulusMX/HttpStationWund.cs b/CumulusMX/HttpStationWund.cs index dbb6a858..41314421 100644 --- a/CumulusMX/HttpStationWund.cs +++ b/CumulusMX/HttpStationWund.cs @@ -317,6 +317,7 @@ GET Parameters - all fields are URL escaped if (data["tempf"] != null && data["humidity"] != null && data["tempf"] != "-9999" && data["humidity"] != "-9999") { DoHumidex(recDate); + DoCloudBaseHeatIndex(recDate); } else { diff --git a/CumulusMX/ImetStation.cs b/CumulusMX/ImetStation.cs index a114940b..49ff1883 100644 --- a/CumulusMX/ImetStation.cs +++ b/CumulusMX/ImetStation.cs @@ -756,9 +756,11 @@ public override void getAndProcessHistoryData() // Cause wind chill calc DoWindChill(0, timestamp); + DoOutdoorDewpoint(0, timestamp); DoApparentTemp(timestamp); DoFeelsLike(timestamp); DoHumidex(timestamp); + DoCloudBaseHeatIndex(timestamp); // sunshine hours if (sl[SUNPOS].Length > 0) @@ -990,7 +992,10 @@ private void ImetGetData() if (temp1 > -999 && humidity > -999) { + DoOutdoorDewpoint(0, now); DoHumidex(now); + DoCloudBaseHeatIndex(now); + if (windspeed > -999) { DoApparentTemp(now); diff --git a/CumulusMX/Simulator.cs b/CumulusMX/Simulator.cs index 16d4daa9..d49f0ef7 100644 --- a/CumulusMX/Simulator.cs +++ b/CumulusMX/Simulator.cs @@ -11,12 +11,12 @@ namespace CumulusMX internal class Simulator : WeatherStation { private bool stop; - private CancellationTokenSource tokenSource = new CancellationTokenSource(); + private readonly CancellationTokenSource tokenSource = new CancellationTokenSource(); private CancellationToken cancellationToken; - private DataSet currData; + private readonly DataSet currData; - private int dataUpdateRate = 5000; // 5 second data update rate + private readonly int dataUpdateRate = 5000; // 5 second data update rate private new readonly Random random; private bool solarIntialised; @@ -133,6 +133,7 @@ private void applyData(DateTime recDate) DoHumidex(recDate); DoApparentTemp(recDate); DoFeelsLike(recDate); + DoCloudBaseHeatIndex(recDate); } private void doSolar(DateTime recDate) @@ -181,14 +182,14 @@ private void doSolar(DateTime recDate) private class DataSet { - private MeanRevertingRandomWalk temperature; - private MeanRevertingRandomWalk humidity; - private MeanRevertingRandomWalk windSpeed; - private MeanRevertingRandomWalk windDirection; - private MeanRevertingRandomWalk insideTemp; - private MeanRevertingRandomWalk insideHum; - private MeanRevertingRandomWalk pressure; - private MeanRevertingRandomWalk rainRate; + private readonly MeanRevertingRandomWalk temperature; + private readonly MeanRevertingRandomWalk humidity; + private readonly MeanRevertingRandomWalk windSpeed; + private readonly MeanRevertingRandomWalk windDirection; + private readonly MeanRevertingRandomWalk insideTemp; + private readonly MeanRevertingRandomWalk insideHum; + private readonly MeanRevertingRandomWalk pressure; + private readonly MeanRevertingRandomWalk rainRate; public double tempVal { get; set; } public int humVal { get; set; } diff --git a/CumulusMX/StationSettings.cs b/CumulusMX/StationSettings.cs index 8988ddd6..cad3d574 100644 --- a/CumulusMX/StationSettings.cs +++ b/CumulusMX/StationSettings.cs @@ -28,6 +28,7 @@ internal string GetAlpacaFormData() // Build the settings data, convert to JSON, and return it var optionsAdv = new JsonStationSettingsOptionsAdvanced() { + usespeedforavg = cumulus.StationOptions.UseSpeedForAvgCalc, avgbearingmins = cumulus.StationOptions.AvgBearingMinutes, avgspeedmins = cumulus.StationOptions.AvgSpeedMinutes, peakgustmins = cumulus.StationOptions.PeakGustMinutes, @@ -41,7 +42,6 @@ internal string GetAlpacaFormData() { usezerobearing = cumulus.StationOptions.UseZeroBearing, calcwindaverage = cumulus.StationOptions.UseWind10MinAvg, - usespeedforavg = cumulus.StationOptions.UseSpeedForAvgCalc, use100for98hum = cumulus.StationOptions.Humidity98Fix, calculatedewpoint = cumulus.StationOptions.CalculatedDP, calculatewindchill = cumulus.StationOptions.CalculatedWC, @@ -743,7 +743,6 @@ internal string UpdateConfig(IHttpContext context) { cumulus.StationOptions.UseZeroBearing = settings.Options.usezerobearing; cumulus.StationOptions.UseWind10MinAvg = settings.Options.calcwindaverage; - cumulus.StationOptions.UseSpeedForAvgCalc = settings.Options.usespeedforavg; cumulus.StationOptions.Humidity98Fix = settings.Options.use100for98hum; cumulus.StationOptions.CalculatedDP = settings.Options.calculatedewpoint; cumulus.StationOptions.CalculatedWC = settings.Options.calculatewindchill; @@ -754,6 +753,7 @@ internal string UpdateConfig(IHttpContext context) cumulus.StationOptions.RoundWindSpeed = settings.Options.roundwindspeeds; cumulus.StationOptions.NoSensorCheck = settings.Options.nosensorcheck; + cumulus.StationOptions.UseSpeedForAvgCalc = settings.Options.advanced.usespeedforavg; cumulus.StationOptions.AvgBearingMinutes = settings.Options.advanced.avgbearingmins; cumulus.StationOptions.AvgSpeedMinutes = settings.Options.advanced.avgspeedmins; cumulus.StationOptions.PeakGustMinutes = settings.Options.advanced.peakgustmins; @@ -1553,6 +1553,7 @@ internal class JsonStationSettingsUnits internal class JsonStationSettingsOptionsAdvanced { + public bool usespeedforavg { get; set; } public int avgbearingmins { get; set; } public int avgspeedmins { get; set; } public int peakgustmins { get; set; } @@ -1566,7 +1567,6 @@ internal class JsonStationSettingsOptions { public bool usezerobearing { get; set; } public bool calcwindaverage { get; set; } - public bool usespeedforavg { get; set; } public bool use100for98hum { get; set; } public bool calculatedewpoint { get; set; } public bool calculatewindchill { get; set; } diff --git a/CumulusMX/TempestStation.cs b/CumulusMX/TempestStation.cs index 6c0928b2..73adb2d7 100644 --- a/CumulusMX/TempestStation.cs +++ b/CumulusMX/TempestStation.cs @@ -165,7 +165,7 @@ private void ProcessHistoryData(List datalist) DoApparentTemp(timestamp); DoFeelsLike(timestamp); DoHumidex(timestamp); - + DoCloudBaseHeatIndex(timestamp); DoUV((double) historydata.UV, timestamp); @@ -309,6 +309,8 @@ private void WeatherPacketReceived(WeatherPacket wp) DoFeelsLike(ts); DoWindChill(userTemp,ts); DoHumidex(ts); + DoCloudBaseHeatIndex(ts); + UpdateStatusPanel(ts); UpdateMQTT(); DoForecast(string.Empty, false); @@ -746,10 +748,7 @@ public DeviceStatus(WeatherPacket packet) if (!int.TryParse(packet.firmware_revision.ToString(), out var i)) i = -1; FirmwareRevision = i; } - catch (Exception e) - { - var ex = e.Message; - } + catch {} RSSI = packet.rssi; HubRSSI = packet.hub_rssi; @@ -858,10 +857,7 @@ public Observation(WeatherPacket packet) if (!int.TryParse(packet.firmware_revision.ToString(), out i)) i = -1; FirmwareRevision = i; } - catch (Exception e) - { - var ex = e.Message; - } + catch {} if (packet.obs[0].Length >= 18) { diff --git a/CumulusMX/WM918Station.cs b/CumulusMX/WM918Station.cs index ea6225a1..d3fa72ab 100644 --- a/CumulusMX/WM918Station.cs +++ b/CumulusMX/WM918Station.cs @@ -377,6 +377,7 @@ private void WM918Temp(List buff) DoApparentTemp(DateTime.Now); DoFeelsLike(DateTime.Now); DoHumidex(DateTime.Now); + DoCloudBaseHeatIndex(DateTime.Now); } private void WM918Rain(List buff) diff --git a/CumulusMX/WMR100Station.cs b/CumulusMX/WMR100Station.cs index 71e2c43a..7b4ee86c 100644 --- a/CumulusMX/WMR100Station.cs +++ b/CumulusMX/WMR100Station.cs @@ -463,6 +463,7 @@ private void ProcessTempPacket() DoApparentTemp(Now); DoFeelsLike(Now); DoHumidex(Now); + DoCloudBaseHeatIndex(Now); // battery status //if (PacketBuffer[0] & 0x40 == 0x40 ) diff --git a/CumulusMX/WMR200Station.cs b/CumulusMX/WMR200Station.cs index fe39bb1b..f57f47fc 100644 --- a/CumulusMX/WMR200Station.cs +++ b/CumulusMX/WMR200Station.cs @@ -593,6 +593,7 @@ private void ProcessTempHumPacket() DoApparentTemp(now); DoFeelsLike(now); DoHumidex(now); + DoCloudBaseHeatIndex(now); } else if (sensor == 0) { @@ -1598,6 +1599,7 @@ private void ProcessHistoryDataPacket() DoApparentTemp(timestamp); DoFeelsLike(timestamp); DoHumidex(timestamp); + DoCloudBaseHeatIndex(timestamp); cumulus.DoLogFile(timestamp,false); cumulus.MySqlRealtimeFile(999, false, timestamp); diff --git a/CumulusMX/WMR928Station.cs b/CumulusMX/WMR928Station.cs index 468db982..95c396db 100644 --- a/CumulusMX/WMR928Station.cs +++ b/CumulusMX/WMR928Station.cs @@ -316,6 +316,7 @@ private void WMR928ExtraTempOnly(List buff) DoApparentTemp(DateTime.Now); DoFeelsLike(DateTime.Now); DoHumidex(DateTime.Now); + DoCloudBaseHeatIndex(DateTime.Now); } } @@ -475,6 +476,7 @@ private void WMR928Outdoor(List buff) DoApparentTemp(DateTime.Now); DoFeelsLike(DateTime.Now); DoHumidex(DateTime.Now); + DoCloudBaseHeatIndex(DateTime.Now); } } diff --git a/CumulusMX/WS2300Station.cs b/CumulusMX/WS2300Station.cs index 6caad28a..8ccec9c6 100644 --- a/CumulusMX/WS2300Station.cs +++ b/CumulusMX/WS2300Station.cs @@ -374,6 +374,7 @@ private void ProcessHistoryData() DoApparentTemp(timestamp); DoFeelsLike(timestamp); DoHumidex(timestamp); + DoCloudBaseHeatIndex(timestamp); CalculateDominantWindBearing(Bearing, WindAverage, historydata.interval); @@ -724,6 +725,8 @@ private void GetAndProcessData() DoApparentTemp(now); DoFeelsLike(now); DoHumidex(now); + DoCloudBaseHeatIndex(now); + UpdateStatusPanel(now); UpdateMQTT(); } diff --git a/CumulusMX/WeatherStation.cs b/CumulusMX/WeatherStation.cs index b397cc0d..a7f65710 100644 --- a/CumulusMX/WeatherStation.cs +++ b/CumulusMX/WeatherStation.cs @@ -1444,7 +1444,7 @@ private async Task sendWebSocketData() try { // wait for the ws lock object - lock (webSocketThreadLock); + lock (webSocketThreadLock) { //cumulus.LogDebugMessage("WebSocket: Sending message"); @@ -1499,7 +1499,7 @@ private async Task sendWebSocketData() WebSocket.SendMessage(new StreamReader(stream).ReadToEnd()); // We can't be sure when the broadcast completes because it is async internally, so the best we can do is wait a short time - await Task.Delay(500); + Thread.Sleep(500); } } catch (Exception ex) @@ -2976,7 +2976,6 @@ public void DoOutdoorTemp(double temp, DateTime timestamp) // update global temp OutdoorTemperature = CalibrateTemp(temp); - double tempinF = ConvertUserTempToF(OutdoorTemperature); double tempinC = ConvertUserTempToC(OutdoorTemperature); first_temp = false; @@ -3043,25 +3042,25 @@ public void DoOutdoorTemp(double temp, DateTime timestamp) 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); } + TempReadyToPlot = true; + HaveReadData = true; + } + + + public void DoCloudBaseHeatIndex(DateTime timestamp) + { + var tempinF = ConvertUserTempToF(OutdoorTemperature); + var tempinC = ConvertUserTempToC(OutdoorTemperature); + // Calculate cloud base - 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; - } + CloudBase = (int)Math.Floor((tempinF - ConvertUserTempToF(OutdoorDewpoint)) / 4.4 * 1000 / (cumulus.CloudBaseInFeet ? 1 : 3.2808399)); + if (CloudBase < 0) + CloudBase = 0; HeatIndex = ConvertTempCToUser(MeteoLib.HeatIndex(tempinC, OutdoorHumidity)); @@ -3091,7 +3090,6 @@ public void DoOutdoorTemp(double temp, DateTime 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 @@ -3102,9 +3100,6 @@ public void DoOutdoorTemp(double temp, DateTime timestamp) { WetBulb = OutdoorTemperature; } - - TempReadyToPlot = true; - HaveReadData = true; } public void DoApparentTemp(DateTime timestamp) @@ -3993,8 +3988,12 @@ public void DoWind(double gustpar, int bearingpar, double speedpar, DateTime tim } var uncalibratedgust = gustpar; calibratedgust = uncalibratedgust * cumulus.Calib.WindGust.Mult; - WindLatest = calibratedgust; - windspeeds[nextwindvalue] = uncalibratedgust; + + // If we are using speed for average it means the gustpar is a period gust value not a latest. + // So we have to use the speed for the latest + WindLatest = cumulus.StationOptions.UseSpeedForAvgCalc ? speedpar * cumulus.Calib.WindSpeed.Mult : calibratedgust; + + windspeeds[nextwindvalue] = gustpar; windbears[nextwindvalue] = Bearing; // Recalculate wind rose data @@ -4046,7 +4045,7 @@ public void DoWind(double gustpar, int bearingpar, double speedpar, DateTime tim // check for monthly all time records (and set) CheckMonthlyAlltime("HighGust", calibratedgust, true, timestamp); - WindRecent[nextwind].Gust = uncalibratedgust; + WindRecent[nextwind].Gust = gustpar; WindRecent[nextwind].Speed = speedpar; WindRecent[nextwind].Timestamp = timestamp; nextwind = (nextwind + 1) % MaxWindRecent; @@ -7135,7 +7134,7 @@ public logfilerec ParseLogFileRec(string data, bool minMax) internal void UpdateStatusPanel(DateTime timestamp) { LastDataReadTimestamp = timestamp; - sendWebSocketData(); + _ = sendWebSocketData(); } diff --git a/Updates.txt b/Updates.txt index e229099a..1bd67b62 100644 --- a/Updates.txt +++ b/Updates.txt @@ -1,10 +1,12 @@ 3.17.0 - b3184 —————————————— +- Fix: Cloud base being set to large value at start-up - New: Adds a PWS Simulator station type for testing or trial purposes - Change: The "live" dashboard screens now refresh whenever new data is received, or every five seconds - +- Change: The "Speed for average calc" station option has been moved from Common Options to Common Options | Advanced Options +- Change: The Ecowitt stations now force the "Speed for average calc" option to be enabled at start-up 3.16.1 - b3183 From e4d5874bfaaef9a07042368ee0a337ef7fea67f2 Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Fri, 13 May 2022 14:39:57 +0100 Subject: [PATCH 3/5] web socket locking changes --- CumulusMX/CumulusMX.csproj | 1 - CumulusMX/EcowittApi.cs | 9 +++ CumulusMX/GW1000Station.cs | 8 ++- CumulusMX/HttpStationEcowitt.cs | 21 +++++++ CumulusMX/WeatherStation.cs | 103 ++++++++++++++++---------------- CumulusMX/webtags.cs | 4 +- Updates.txt | 1 + 7 files changed, 91 insertions(+), 56 deletions(-) diff --git a/CumulusMX/CumulusMX.csproj b/CumulusMX/CumulusMX.csproj index 485d89da..00520b40 100644 --- a/CumulusMX/CumulusMX.csproj +++ b/CumulusMX/CumulusMX.csproj @@ -266,7 +266,6 @@ Designer - diff --git a/CumulusMX/EcowittApi.cs b/CumulusMX/EcowittApi.cs index 6cf43ab4..61e970bd 100644 --- a/CumulusMX/EcowittApi.cs +++ b/CumulusMX/EcowittApi.cs @@ -1249,6 +1249,9 @@ private void ProcessHistoryData(EcowittHistoricData data) private void ApplyHistoricData(KeyValuePair rec) { // === Wind == + // WindGust = max for period + // WindSpd = avg for period + // WindDir = avg for period try { if (rec.Value.WindGust.HasValue && rec.Value.WindSpd.HasValue && rec.Value.WindDir.HasValue) @@ -1267,6 +1270,7 @@ private void ApplyHistoricData(KeyValuePair r } // === Humidity === + // = avg for period try { if (rec.Value.IndoorHum.HasValue) @@ -1285,6 +1289,7 @@ private void ApplyHistoricData(KeyValuePair r } // === Pressure === + // = avg for period try { if (rec.Value.Pressure.HasValue) @@ -1300,6 +1305,7 @@ private void ApplyHistoricData(KeyValuePair r } // === Indoor temp === + // = avg for period try { if (rec.Value.IndoorTemp.HasValue) @@ -1314,6 +1320,7 @@ private void ApplyHistoricData(KeyValuePair r } // === Outdoor temp === + // = avg for period try { if (rec.Value.Temp.HasValue && cumulus.Gw1000PrimaryTHSensor == 0) @@ -1356,6 +1363,7 @@ private void ApplyHistoricData(KeyValuePair r } // === Solar === + // = max for period try { if (rec.Value.Solar.HasValue) @@ -1369,6 +1377,7 @@ private void ApplyHistoricData(KeyValuePair r } // === UVI === + // = max for period try { if (rec.Value.UVI.HasValue) diff --git a/CumulusMX/GW1000Station.cs b/CumulusMX/GW1000Station.cs index 0fa906c4..a321effa 100644 --- a/CumulusMX/GW1000Station.cs +++ b/CumulusMX/GW1000Station.cs @@ -41,6 +41,7 @@ internal class GW1000Station : WeatherStation //private readonly NumberFormatInfo invNum = CultureInfo.InvariantCulture.NumberFormat; private readonly Version fwVersion; + private readonly string gatewayType; public GW1000Station(Cumulus cumulus) : base(cumulus) @@ -57,7 +58,7 @@ public GW1000Station(Cumulus cumulus) : base(cumulus) cumulus.StationOptions.UseSpeedForAvgCalc = true; LightningTime = DateTime.MinValue; - LightningDistance = 999; + LightningDistance = -1.0; tmrDataWatchdog = new System.Timers.Timer(); @@ -126,6 +127,7 @@ public GW1000Station(Cumulus cumulus) : base(cumulus) var fwString = GW1000FirmwareVersion.Split(new string[] { "_V" }, StringSplitOptions.None); if (fwString.Length > 1) { + gatewayType = fwString[0]; fwVersion = new Version(fwString[1]); } else @@ -639,7 +641,7 @@ private bool PrintSensorInfoNew(byte[] data, int idx) // if a WS90 is connected, it has a 8.8 second update rate, so reduce the MX update rate from the default 10 seconds if (updateRate > 8000 && updateRate != 8000) { - cumulus.LogMessage($"PrintSensorInfoNew: WS90 sensor detected, changing the update rate from {updateRate / 1000} seconds to 8 seconds"); + cumulus.LogMessage($"PrintSensorInfoNew: WS90 sensor detected, changing the update rate from {(updateRate / 1000):D} seconds to 8 seconds"); updateRate = 8000; } battV = data[battPos] * 0.02; @@ -669,7 +671,7 @@ private bool PrintSensorInfoNew(byte[] data, int idx) // if a WS80 is connected, it has a 4.75 second update rate, so reduce the MX update rate from the default 10 seconds if (updateRate > 4000 && updateRate != 4000) { - cumulus.LogMessage($"PrintSensorInfoNew: WS80 sensor detected, changing the update rate from {updateRate/1000} seconds to 4 seconds"); + cumulus.LogMessage($"PrintSensorInfoNew: WS80 sensor detected, changing the update rate from {(updateRate/1000):D} seconds to 4 seconds"); updateRate = 4000; } battV = data[battPos] * 0.02; diff --git a/CumulusMX/HttpStationEcowitt.cs b/CumulusMX/HttpStationEcowitt.cs index 164957f3..596dc3e0 100644 --- a/CumulusMX/HttpStationEcowitt.cs +++ b/CumulusMX/HttpStationEcowitt.cs @@ -22,6 +22,7 @@ class HttpStationEcowitt : WeatherStation private bool stopping = false; private readonly NumberFormatInfo invNum = CultureInfo.InvariantCulture.NumberFormat; private bool reportStationType = true; + private int lastMinute = -1; private EcowittApi ecowittApi; private int maxArchiveRuns = 1; @@ -220,6 +221,8 @@ POST Parameters - all fields are URL escaped PASSKEY=&stationtype=GW1000A_V1.6.8&dateutc=2021-07-23+17:13:34&tempinf=80.6&humidityin=50&baromrelin=29.940&baromabsin=29.081&tempf=81.3&humidity=43&winddir=296&windspeedmph=2.46&windgustmph=4.25&maxdailygust=14.09&solarradiation=226.28&uv=1&rainratein=0.000&eventrainin=0.000&hourlyrainin=0.000&dailyrainin=0.000&weeklyrainin=0.000&monthlyrainin=4.118&yearlyrainin=29.055&totalrainin=29.055&temp1f=83.48&humidity1=39&temp2f=87.98&humidity2=40&temp3f=82.04&humidity3=40&temp4f=93.56&humidity4=34&temp5f=-11.38&temp6f=87.26&humidity6=38&temp7f=45.50&humidity7=40&soilmoisture1=51&soilmoisture2=65&soilmoisture3=72&soilmoisture4=36&soilmoisture5=48&pm25_ch1=11.0&pm25_avg_24h_ch1=10.8&pm25_ch2=13.0&pm25_avg_24h_ch2=15.0&tf_co2=80.8&humi_co2=48&pm25_co2=4.8&pm25_24h_co2=6.1&pm10_co2=4.9&pm10_24h_co2=6.5&co2=493&co2_24h=454&lightning_time=1627039348&lightning_num=3&lightning=24&wh65batt=0&wh80batt=3.06&batt1=0&batt2=0&batt3=0&batt4=0&batt5=0&batt6=0&batt7=0&soilbatt1=1.5&soilbatt2=1.4&soilbatt3=1.5&soilbatt4=1.5&soilbatt5=1.6&pm25batt1=4&pm25batt2=4&wh57batt=4&co2_batt=6&freq=868M&model=GW1000_Pro PASSKEY=&stationtype=GW1100A_V2.0.2&dateutc=2021-09-08+11:58:39&tempinf=80.8&humidityin=42&baromrelin=29.864&baromabsin=29.415&temp1f=87.8&tf_ch1=64.4&batt1=0&tf_batt1=1.48&freq=868M&model=GW1100A + PASSKEY=&stationtype=GW1100A_V2.1.4&runtime=2336207&dateutc=2022-05-13+08:55:11&tempinf=73.0&humidityin=38&baromrelin=30.156&baromabsin=29.297&tempf=63.0&humidity=49&winddir=71&windspeedmph=2.68&windgustmph=11.86&maxdailygust=15.21&solarradiation=694.87&uv=5&rainratein=0.000&eventrainin=0.000&hourlyrainin=0.000&dailyrainin=0.000&weeklyrainin=0.000&monthlyrainin=0.591&yearlyrainin=14.591&temp1f=67.8&humidity1=43&temp2f=68.7&humidity2=47&temp3f=62.6&humidity3=51&temp4f=62.6&humidity4=51&temp5f=-1.5&temp6f=76.1&humidity6=47&temp7f=44.4&humidity7=49&soilmoisture1=14&soilmoisture2=50&soilmoisture3=20&soilmoisture4=25&pm25_ch1=7.0&pm25_avg_24h_ch1=7.6&pm25_ch2=6.0&pm25_avg_24h_ch2=8.3&tf_co2=72.9&humi_co2=44&pm25_co2=1.2&pm25_24h_co2=2.6&pm10_co2=1.5&pm10_24h_co2=3.0&co2=387&co2_24h=536&lightning_num=0&lightning=31&lightning_time=1652304268&leak_ch2=0&wh65batt=0&wh80batt=2.74&wh26batt=0&batt1=0&batt2=0&batt3=0&batt4=0&batt5=0&batt6=0&batt7=0&soilbatt1=1.3&soilbatt2=1.4&soilbatt3=1.4&soilbatt4=1.4&pm25batt1=4&pm25batt2=4&wh57batt=5&leakbatt2=4&co2_batt=6&freq=868M&model=GW1100A + */ var procName = main ? "ProcessData" : "ProcessExtraData"; @@ -283,6 +286,21 @@ public string ApplyData(string dataString, bool main, DateTime? ts = null) // We will ignore the dateutc field if this "live" data to avoid any clock issues recDate = ts.HasValue ? ts.Value : DateTime.Now; + if (recDate.Minute != lastMinute) + { + + // at start-up or every 10 minutes trigger output of uptime + if ((recDate.Minute % 10) == 0 || lastMinute == -1 && data["runtime"] != null) + { + var runtime = Convert.ToInt32(data["runtime"]); + var uptime = TimeSpan.FromSeconds(runtime); + + cumulus.LogMessage($"Ecowitt Gateway uptime = {runtime} secs - {uptime:c}"); + } + + lastMinute = recDate.Minute; + } + // we only really want to do this once if (reportStationType && !ts.HasValue) { @@ -526,6 +544,7 @@ public string ApplyData(string dataString, bool main, DateTime? ts = null) } } + // === Extra Humidity === if (main || cumulus.EcowittExtraUseTempHum) { @@ -744,6 +763,7 @@ public string ApplyData(string dataString, bool main, DateTime? ts = null) } } + // === Firmware Version === try { @@ -794,6 +814,7 @@ public string ApplyData(string dataString, bool main, DateTime? ts = null) return "Failed: Error in dew point data - " + ex.Message; } + // === Wind Chill === try { diff --git a/CumulusMX/WeatherStation.cs b/CumulusMX/WeatherStation.cs index a7f65710..c9087114 100644 --- a/CumulusMX/WeatherStation.cs +++ b/CumulusMX/WeatherStation.cs @@ -43,7 +43,8 @@ public struct TWindVec public readonly Object yearIniThreadLock = new Object(); public readonly Object alltimeIniThreadLock = new Object(); public readonly Object monthlyalltimeIniThreadLock = new Object(); - private readonly Object webSocketThreadLock = new Object(); + + private static readonly SemaphoreSlim webSocketSemaphore = new SemaphoreSlim(1, 1); // holds all time highs and lows public AllTimeRecords AllTime = new AllTimeRecords(); @@ -697,7 +698,7 @@ public void ReadTodayFile() AlltimeRecordTimestamp = ini.GetValue("Records", "Alltime", DateTime.MinValue); // Lightning (GW1000 for now) - LightningDistance = ini.GetValue("Lightning", "Distance", -1); + LightningDistance = ini.GetValue("Lightning", "Distance", -1.0); LightningTime = ini.GetValue("Lightning", "LastStrike", DateTime.MinValue); } @@ -1443,64 +1444,67 @@ private async Task sendWebSocketData() // send current data to web-socket try { - // wait for the ws lock object - lock (webSocketThreadLock) + // if we already have an update queued, don't add to the wait queue. Otherwise we get hundreds queued up during catch-up + if (webSocketSemaphore.CurrentCount == 0) { - //cumulus.LogDebugMessage("WebSocket: Sending message"); + cumulus.LogDebugMessage("sendWebSocketData: Update already queued, dropping this one"); + return; + } - StringBuilder windRoseData = new StringBuilder(80); + // wait for the ws lock object + webSocketSemaphore.Wait(); - lock (windcounts) - { - windRoseData.Append((windcounts[0] * cumulus.Calib.WindGust.Mult).ToString(cumulus.WindFormat, CultureInfo.InvariantCulture)); + StringBuilder windRoseData = new StringBuilder(80); - for (var i = 1; i < cumulus.NumWindRosePoints; i++) - { - windRoseData.Append(","); - windRoseData.Append((windcounts[i] * cumulus.Calib.WindGust.Mult).ToString(cumulus.WindFormat, CultureInfo.InvariantCulture)); - } + 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"); + 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 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 json = jss.Serialize(data); - var ser = new DataContractJsonSerializer(typeof(DataStruct)); + var ser = new DataContractJsonSerializer(typeof(DataStruct)); - var stream = new MemoryStream(); + var stream = new MemoryStream(); - ser.WriteObject(stream, data); + ser.WriteObject(stream, data); - stream.Position = 0; + stream.Position = 0; - cumulus.LogDebugMessage("WebSocket: Send message"); - WebSocket.SendMessage(new StreamReader(stream).ReadToEnd()); + WebSocket.SendMessage(new StreamReader(stream).ReadToEnd()); - // We can't be sure when the broadcast completes because it is async internally, so the best we can do is wait a short time - Thread.Sleep(500); - } + // We can't be sure when the broadcast completes because it is async internally, so the best we can do is wait a short time + Thread.Sleep(500); } catch (Exception ex) { @@ -1508,8 +1512,7 @@ private async Task sendWebSocketData() } finally { - //cumulus.LogDebugMessage("WebSocket: End message"); - //webSocketLocked = false; + webSocketSemaphore.Release(); } } @@ -9844,8 +9847,8 @@ 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($"[\"Distance to last strike\",\"{(LightningDistance == -1 ? "-" : LightningDistance.ToString(cumulus.WindRunFormat))}\",\"{cumulus.Units.WindRunText}\"],"); + json.Append($"[\"Time of last strike\",\"{(DateTime.Equals(LightningTime, DateTime.MinValue) ? "-" : LightningTime.ToString("g"))}\",\"\"],"); json.Append($"[\"Number of strikes today\",\"{LightningStrikesToday}\",\"\"]"); json.Append("]}"); return json.ToString(); diff --git a/CumulusMX/webtags.cs b/CumulusMX/webtags.cs index e52d14b7..64300e5a 100644 --- a/CumulusMX/webtags.cs +++ b/CumulusMX/webtags.cs @@ -3890,12 +3890,12 @@ private string TagLeakSensor4(Dictionary tagParams) private string TagLightningDistance(Dictionary tagParams) { - return station.LightningDistance == 999 ? "--" : CheckRcDp(station.LightningDistance, tagParams, cumulus.WindRunDPlaces); + return station.LightningDistance == -1 ? "--" : CheckRcDp(station.LightningDistance, tagParams, cumulus.WindRunDPlaces); } private string TagLightningTime(Dictionary tagParams) { - return DateTime.Compare(station.LightningTime, new DateTime(1900, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)) == 0 ? "---" : GetFormattedDateTime(station.LightningTime, "t", tagParams); + return DateTime.Equals(station.LightningTime, DateTime.MinValue) ? "---" : GetFormattedDateTime(station.LightningTime, "t", tagParams); } private string TagLightningStrikesToday(Dictionary tagParams) diff --git a/Updates.txt b/Updates.txt index 1bd67b62..40c38ad2 100644 --- a/Updates.txt +++ b/Updates.txt @@ -1,6 +1,7 @@ 3.17.0 - b3184 —————————————— - Fix: Cloud base being set to large value at start-up +- Fix: Reading lightning distance from today.ini - New: Adds a PWS Simulator station type for testing or trial purposes From edc5b331adf112428d6ca80d7b5431cc194c39e7 Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Fri, 20 May 2022 09:39:47 +0100 Subject: [PATCH 4/5] Improved solar position calculation Fixed potential crash getting local IP --- CumulusMX/AstroLib.cs | 571 +++++++++++++++++++++++++++++--- CumulusMX/Cumulus.cs | 4 + CumulusMX/HttpStationEcowitt.cs | 16 +- CumulusMX/Utils.cs | 41 ++- CumulusMX/WeatherStation.cs | 5 +- Updates.txt | 2 + 6 files changed, 573 insertions(+), 66 deletions(-) diff --git a/CumulusMX/AstroLib.cs b/CumulusMX/AstroLib.cs index 3cb20357..cde6eb68 100644 --- a/CumulusMX/AstroLib.cs +++ b/CumulusMX/AstroLib.cs @@ -4,10 +4,10 @@ namespace CumulusMX { internal class AstroLib { - public static double BrasSolar(double el, double r, double nfac) + public static double BrasSolar(double el, double erv, double nfac) { // el solar elevation deg from horizon - // r distance from earth to sun in AU + // erv distance from earth to sun in AU // nfac atmospheric turbidity parameter (2=clear, 4-5=smoggy) double sinal = Math.Sin(DegToRad(el)); // Sine of the solar elevation angle @@ -16,10 +16,10 @@ public static double BrasSolar(double el, double r, double nfac) return 0; // solar radiation on horizontal surface at top of atmosphere - double i0 = (1367 / (r * r)) * sinal; + double i0 = (1367 / (erv * erv)) * sinal; // optical air mass - double m = 1/(sinal + (0.15 * Math.Pow(el + 3.885, -1.253))); + double m = 1 / (sinal + (0.15 * Math.Pow(el + 3.885, -1.253))); // molecular scattering coefficient double al = 0.128 - (0.054 * Math.Log(m) / Math.Log(10)); @@ -43,21 +43,21 @@ public static double RyanStolzSolar(double el, double erv, double atc, double z) double al = Math.Asin(sinal); double a0 = RadToDeg(al); // convert the radians to degree - double rm = Math.Pow(((288.0 - 0.0065*z)/288.0), 5.256)/(sinal + 0.15*Math.Pow((a0 + 3.885), (-1.253))); + double rm = Math.Pow(((288.0 - 0.0065 * z) / 288.0), 5.256) / (sinal + 0.15 * Math.Pow((a0 + 3.885), (-1.253))); - double rsToa = 1360*sinal/(erv*erv); // RS on the top of atmosphere + double rsToa = 1360 * sinal / (erv * erv); // RS on the top of atmosphere return rsToa * Math.Pow(atc, rm); //RS on the ground } private static double DegToRad(double angle) { - return Math.PI*angle/180.0; + return Math.PI * angle / 180.0; } private static double RadToDeg(double angle) { - return angle*(180.0/Math.PI); + return angle * (180.0 / Math.PI); } public static double SolarMax(DateTime timestamp, double longitude, double latitude, double altitude, @@ -76,7 +76,7 @@ public static double SolarMax(DateTime timestamp, double longitude, double latit } - public static double SolarMax(DateTime timestamp, double longitude, double latitude, double altitude, + private static double SolarMax(DateTime timestamp, double longitude, double latitude, double altitude, out double solarelevation, double factor, int method) { DateTime utctime = timestamp.ToUniversalTime(); @@ -106,10 +106,478 @@ private static double GetFactor(DateTime timestamp, double jun, double dec) return dec + Math.Cos((doy - 172) / 183.0 * Math.PI / 2) * range; } + + + // Uses the VBA from NOAA solrad_ver16 spreadsheet + #region solrad calculations + + /* + private static void CalculateSunPosition(DateTime dateTime, double latitude, double lon, out double altitude, out double azimuth) + { + // change sign convention for longitude from negative to positive in western hemisphere + var longitude = lon * -1.0; + + if (latitude > 89.8) + latitude = 89.8; + + if (latitude < -89.8) + latitude = -89.8; + + // timenow is GMT time for calculation in hours since 0Z + //var timenow = dateTime.TimeOfDay.TotalHours; + + double julianDate = 367.0 * dateTime.Year - + (int)((7.0 / 4.0) * (dateTime.Year + (int)((dateTime.Month + 9.0) / 12.0))) + + (int)((275.0 * dateTime.Month) / 9.0) + + dateTime.Day - 730531.5; + + double t = julianDate / 36525.0; + //double r = calcSunRadVector(t); + //double alpha = calcSunRtAscension(t); + double solardec = calcSunDeclination(t); + double eqtime = calcEquationOfTime(t); + + double zone = TimeZoneInfo.Local.BaseUtcOffset.Hours; + zone = -7; + + double solarTimeFix = eqtime - 4.0 * longitude + 60.0 * zone; + double trueSolarTime = dateTime.Hour * 60 + dateTime.Minute + dateTime.Second / 60.0 + solarTimeFix; // in minutes + + while (trueSolarTime > 1440) + { + trueSolarTime -= 1440; + } + + double hourangle = trueSolarTime / 4.0 - 180; + + if (hourangle < -180) + hourangle += 360; + + double harad = DegToRad(hourangle); + + double csz = Math.Sin(DegToRad(latitude)) * Math.Sin(DegToRad(solardec)) + Math.Cos(DegToRad(latitude)) * Math.Cos(DegToRad(solardec)) * Math.Cos(harad); + + if (csz > 1) + csz = 1; + else if (csz < -1) + csz = -1; + + double zenith = RadToDeg(Math.Acos(csz)); + double azDenom = Math.Cos(DegToRad(latitude)) * Math.Sin(DegToRad(zenith)); + + double azRad; + + if (Math.Abs(azDenom) > 0.001) + { + azRad = (Math.Sin(DegToRad(latitude) * Math.Cos(DegToRad(zenith))) - Math.Sin(DegToRad(solardec))) / azDenom; + + if (Math.Abs(azRad) > 1) + { + if (azRad < 0) + azRad = -1; + else + azRad = 1; + } + + azimuth = 180.0 - RadToDeg(Math.Acos(azRad)); + + if (hourangle > 0) + azimuth = -azimuth; + } + else + { + if (latitude > 0) + azimuth = 180; + else + azimuth = 0; + + } + + if (azimuth < 0) + azimuth += 360; + + var exoatmElevation = 90.0 - zenith; + double refractionCorrection; + + if (exoatmElevation > 85) + { + refractionCorrection = 0; + } + else + { + + double te = Math.Tan(DegToRad(exoatmElevation)); + + if (exoatmElevation > 5) + { + refractionCorrection = 58.1 / te - 0.07 / (te * te * te) + 0.000086 / (te * te * te * te * te); + } + else if (exoatmElevation > -0.575) + { + double step1 = -12.79 + exoatmElevation * 0.711; + double step2 = 103.4 + exoatmElevation * step1; + double step3 = -518.2 + exoatmElevation * step2; + + refractionCorrection = 1735.0 + exoatmElevation * step3; + } + else + { + refractionCorrection = -20.774 / te; + } + + refractionCorrection = refractionCorrection / 3600.0; + } + + double solarzen = zenith - refractionCorrection; + + altitude = 90.0 - solarzen; + } + + + + private static double calcSunRadVector(double t) + { + // *********************************************************************** + // Name: calcSunRadVector (not used by sunrise, solarnoon, sunset) + // Type: Function + // Purpose: calculate the distance to the sun in AU + // Arguments: + // t : number of Julian centuries since J2000.0 + // Return value: + // sun radius vector in AUs + // *********************************************************************** + + double v = calcSunTrueAnomaly(t); + + double e = calcEccentricityEarthOrbit(t); + + return 1.000001018 * (1 - e * e) / (1 + e * Math.Cos(DegToRad(v))); + } + + private static double calcSunTrueAnomaly(double t) + { + // *********************************************************************** + // Name: calcSunTrueAnomaly (not used by sunrise, solarnoon, sunset) + // Type: Function + //Purpose: calculate the true anamoly of the sun + // Arguments: + // t : number of Julian centuries since J2000.0 + // Return value: + // sun's true anamoly in degrees + // *********************************************************************** + + double m = calcGeomMeanAnomalySun(t); + + double c = calcSunEqOfCenter(t); + + return m + c; + } + + private static double calcGeomMeanAnomalySun(double t) + { + // *********************************************************************** + // Name: calGeomAnomalySun + // Type: Function + // Purpose: calculate the Geometric Mean Anomaly of the Sun + // Arguments: + // t : number of Julian centuries since J2000.0 + // Return value: + // the Geometric Mean Anomaly of the Sun in degrees + // *********************************************************************** + + return 357.52911 + t * (35999.05029 - 0.0001537 * t); + } + + private static double calcSunEqOfCenter(double t) + { + // *********************************************************************** + // Name: calcSunEqOfCenter + // Type: Function + // Purpose: calculate the equation of center for the sun + // Arguments: + // t : number of Julian centuries since J2000.0 + // Return value: + // in degrees + // *********************************************************************** + + double m = calcGeomMeanAnomalySun(t); + + double mrad = DegToRad(m); + double sinm = Math.Sin(mrad); + double sin2m = Math.Sin(mrad + mrad); + double sin3m = Math.Sin(mrad + mrad + mrad); + + return sinm * (1.914602 - t * (0.004817 + 0.000014 * t)) + sin2m * (0.019993 - 0.000101 * t) + sin3m * 0.000289; + } + + private static double calcEccentricityEarthOrbit(double t) + { + // *********************************************************************** + // Name: calcEccentricityEarthOrbit + // Type: Function + // Purpose: calculate the eccentricity of earth's orbit + // Arguments: + // t : number of Julian centuries since J2000.0 + // Return value: + // the unitless eccentricity + // *********************************************************************** + + return 0.016708634 - t * (0.000042037 + 0.0000001267 * t); + } + + + private static double calcSunRtAscension(double t) + { + // *********************************************************************** + // Name: calcSunRtAscension (not used by sunrise, solarnoon, sunset) + // Type: Function + // Purpose: calculate the right ascension of the sun + // Arguments: + // t : number of Julian centuries since J2000.0 + // Return value: + // sun's right ascension in degrees + // *********************************************************************** + + double e = calcObliquityCorrection(t); + + double lambda = calcSunApparentLong(t); + double tananum = Math.Cos(DegToRad(e)) * Math.Sin(DegToRad(lambda)); + double tanadenom = Math.Cos(DegToRad(lambda)); + + //original NOAA code using javascript Math.Atan2(y,x) convention: + // var alpha = radToDeg(Math.atan2(tananum, tanadenom)); + // alpha = radToDeg(Application.WorksheetFunction.Atan2(tananum, tanadenom)) + // + //translated using Excel VBA Application.WorksheetFunction.Atan2(x,y) convention: + + return RadToDeg(Math.Atan2(tanadenom, tananum)); + } + + private static double calcSunDeclination(double t) + { + // *********************************************************************** + // Name: calcSunDeclination + // Type: Function + // Purpose: calculate the declination of the sun + // Arguments: + // t : number of Julian centuries since J2000.0 + // Return value: + // sun's declination in degrees + // *********************************************************************** + + double e = calcObliquityCorrection(t); + + double lambda = calcSunApparentLong(t); + double sint = Math.Sin(DegToRad(e)) * Math.Sin(DegToRad(lambda)); + + return RadToDeg(Math.Asin(sint)); + } + + private static double calcSunApparentLong(double t) + { + // *********************************************************************** + // Name: calcSunApparentLong (not used by sunrise, solarnoon, sunset) + // Type: Function + // Purpose: calculate the apparent longitude of the sun + // Arguments: + // t : number of Julian centuries since J2000.0 + // Return value: + // sun's apparent longitude in degrees + // *********************************************************************** + + double O = calcSunTrueLong(t); + + double omega = 125.04 - 1934.136 * t; + + return O - 0.00569 - 0.00478 * Math.Sin(DegToRad(omega)); + } + + private static double calcSunTrueLong(double t) + { + // *********************************************************************** + // Name: calcSunTrueLong + // Type: Function + // Purpose: calculate the true longitude of the sun + // Arguments: + // t : number of Julian centuries since J2000.0 + // Return value: + // sun's true longitude in degrees + // *********************************************************************** + + double l0 = calcGeomMeanLongSun(t); + + double c = calcSunEqOfCenter(t); + + return l0 + c; + } + + private static double calcGeomMeanLongSun(double t) + { + // *********************************************************************** + // Name: calGeomMeanLongSun + // Type: Function + // Purpose: calculate the Geometric Mean Longitude of the Sun + // Arguments: + // t : number of Julian centuries since J2000.0 + // Return value: + // the Geometric Mean Longitude of the Sun in degrees + // *********************************************************************** + + double l0 = 280.46646 + t * (36000.76983 + 0.0003032 * t); + + do + { + if (l0 <= 360 && l0 >= 0) + break; + + if (l0 > 360) + l0 -= 360; + + if (l0 < 0) + l0 += 360; + } while (true); + + return l0; + } + + private static double calcEquationOfTime(double t) + { + // *********************************************************************** + // Name: calcEquationOfTime + // Type: Function + // Purpose: calculate the difference between true solar time and mean + // solar time + // Arguments: + // t : number of Julian centuries since J2000.0 + // Return value: + // equation of time in minutes of time + // *********************************************************************** + + double epsilon = calcObliquityCorrection(t); + double l0 = calcGeomMeanLongSun(t); + double e = calcEccentricityEarthOrbit(t); + double m = calcGeomMeanAnomalySun(t); + + double y = Math.Tan(DegToRad(epsilon) / 2.0); + y = y * y; + + double sin2l0 = Math.Sin(2.0 * DegToRad(l0)); + double sinm = Math.Sin(DegToRad(m)); + double cos2l0 = Math.Cos(2.0 * DegToRad(l0)); + double sin4l0 = Math.Sin(4.0 * DegToRad(l0)); + double sin2m = Math.Sin(2.0 * DegToRad(m)); + + double Etime = y * sin2l0 - 2.0 * e * sinm + 4.0 * e * y * sinm * cos2l0 - 0.5 * y * y * sin4l0 - 1.25 * e * e * sin2m; + + return RadToDeg(Etime) * 4.0; + } + + private static double calcMeanObliquityOfEcliptic(double t) + { + // *********************************************************************** + // Name: calcMeanObliquityOfEcliptic + // Type: Function + // Purpose: calculate the mean obliquity of the ecliptic + // Arguments: + // t : number of Julian centuries since J2000.0 + // Return value: + // mean obliquity in degrees + // *********************************************************************** + + double seconds = 21.448 - t * (46.815 + t * (0.00059 - t * (0.001813))); + + return 23.0 + (26.0 + (seconds / 60.0)) / 60.0; + } + + private static double calcObliquityCorrection(double t) + { + // *********************************************************************** + // Name: calcObliquityCorrection + // Type: Function + // Purpose: calculate the corrected obliquity of the ecliptic + // Arguments: + // t : number of Julian centuries since J2000.0 + // Return value: + // corrected obliquity in degrees + // *********************************************************************** + + double e0 = calcMeanObliquityOfEcliptic(t); + + double omega = 125.04 - 1934.136 * t; + + return e0 + 0.00256 * Math.Cos(DegToRad(omega)); + } + + */ + + #endregion + + + + // From the NOAA_SolarCalculations_day spreadsheet + // https://gml.noaa.gov/grad/solcalc/calcdetails.html + #region NOAA_Solar + + private static void CalculateSunPosition(DateTime dateTime, double latitude, double longitude, out double altitude, out double azimuth) + { + // We will use DateTime.ToOADate() which automatically includes the TZ + var zone = 0.0; + + var julianDate = dateTime.ToOADate() + 2415018.5; + var julianCentry = (julianDate - 2451545 ) / 36525.0; + var geoMeanLongSun = PutIn360Deg(280.46646 + julianCentry * (36000.76983 + julianCentry * 0.0003032)); + var geoMeanAnomSun = 357.52911 + julianCentry * (35999.05029 - 0.0001537 * julianCentry); + var eccEarthOrbit = 0.016708634 - julianCentry * (0.000042037 + 0.0000001267 * julianCentry); + var sunEqCentre = Math.Sin(DegToRad(geoMeanAnomSun)) * (1.914602 - julianCentry * (0.004817 + 0.000014 * julianCentry)) + Math.Sin(DegToRad(2 * geoMeanAnomSun)) * (0.019993 - 0.000101 * julianCentry) + Math.Sin(DegToRad(3 * geoMeanAnomSun)) * 0.000289; + var sunTrueLong = geoMeanLongSun + sunEqCentre; + //var sunTrueAnom = geoMeanAnomSun + sunEqCentre; + //var sunRadVector = (1.000001018 * (1 - eccEarthOrbit * eccEarthOrbit)) / (1 + eccEarthOrbit * Math.Cos(DegToRad(sunTrueAnom))); + var sunAppLong = sunTrueLong - 0.00569 - 0.00478 * Math.Sin(DegToRad(125.04 - 1934.136 * julianCentry)); + var meanObliqEcplitic = 23 + (26 + ((21.448 - julianCentry * (46.815 + julianCentry * (0.00059 - julianCentry * 0.001813)))) / 60.0) / 60.0; + var obliqCorr = meanObliqEcplitic + 0.00256 * Math.Cos(DegToRad(125.04 - 1934.136 * julianCentry)); + //var sunRA = RadToDeg(Math.Atan2(Math.Cos(DegToRad(sunAppLong)), Math.Cos(DegToRad(obliqCorr)) * Math.Sin(DegToRad(obliqCorr)))); + var sunDec = RadToDeg(Math.Asin(Math.Sin(DegToRad(obliqCorr)) * Math.Sin(DegToRad(sunAppLong)))); + var varY = Math.Tan(DegToRad(obliqCorr / 2.0)) * Math.Tan(DegToRad(obliqCorr / 2.0)); + var eqOfTime = 4 * RadToDeg(varY * Math.Sin(2 * DegToRad(geoMeanLongSun)) - 2 * eccEarthOrbit * Math.Sin(DegToRad(geoMeanAnomSun)) + 4 * eccEarthOrbit * varY * Math.Sin(DegToRad(geoMeanAnomSun)) * Math.Cos(2 * DegToRad(geoMeanLongSun)) - 0.5 * varY * varY * Math.Sin(4 * DegToRad(geoMeanLongSun)) - 1.25 * eccEarthOrbit * eccEarthOrbit * Math.Sin(2 * DegToRad(geoMeanAnomSun))); + //var haSunRise = RadToDeg(Math.Acos(Math.Cos(DegToRad(90.833)) / (Math.Cos(DegToRad(latitude)) * Math.Cos(DegToRad(sunDec))) - Math.Tan(DegToRad(latitude)) * Math.Tan(DegToRad(sunDec)))); + //var solarNoonLst = (720.0 - 4 * longitude - eqOfTime + zone * 60.0) / 1440.0; + //var sunriseTimeLst = solarNoonLst - haSunRise * 4 / 1440.0; + //var sunsetTimeLst = solarNoonLst + haSunRise * 4 / 1440.0; + //var sunlightDurationMins = 8 * haSunRise; + var trueSolarTime = PutInRange(dateTime.TimeOfDay.TotalMinutes + eqOfTime + 4 * longitude - 60 * zone, 1440); + var hourAngle = trueSolarTime / 4.0 < 0 ? trueSolarTime / 4.0 + 180 : trueSolarTime / 4.0 - 180; + var solarZenithAngle = RadToDeg(Math.Acos(Math.Sin(DegToRad(latitude)) * Math.Sin(DegToRad(sunDec)) + Math.Cos(DegToRad(latitude)) * Math.Cos(DegToRad(sunDec)) * Math.Cos(DegToRad(hourAngle)))); + var solarElevation = 90 - solarZenithAngle; + + double refraction; + + if (solarElevation > 85) + refraction = 0; + else if (solarElevation > 5) + refraction = 58.1 / Math.Tan(DegToRad(solarElevation)) - 0.07 / Math.Pow(Math.Tan(DegToRad(solarElevation)), 3) + 0.000086 / Math.Pow(Math.Tan(DegToRad(solarElevation)), 5); + else if (solarElevation > -0.575) + refraction = 1735.0 + solarElevation * (-518.2 + solarElevation * (103.4 + solarElevation * (-12.79 + solarElevation * 0.711))); + else + refraction = -20.772 / Math.Tan(DegToRad(solarElevation)); + + altitude = solarElevation + refraction / 3600.0; + + if (hourAngle > 0) + azimuth = PutIn360Deg(DegToRad(Math.Acos(((Math.Sin(DegToRad(latitude)) * Math.Cos(DegToRad(solarZenithAngle))) - Math.Sin(DegToRad(sunDec))) / (Math.Sin(DegToRad(latitude)) * Math.Sin(DegToRad(solarZenithAngle))))) + 180); + else + azimuth = PutIn360Deg(540 - DegToRad(Math.Acos(((Math.Sin(DegToRad(latitude)) * Math.Cos(DegToRad(solarZenithAngle))) - Math.Sin(DegToRad(sunDec))) / (Math.Cos(DegToRad(latitude)) * Math.Sin(DegToRad(solarZenithAngle)))))); + + } + + + #endregion + + // http://guideving.blogspot.co.uk/2010/08/sun-position-in-c.html + #region sun-position-in-c - public static void CalculateSunPosition( - DateTime dateTime, double latitude, double longitude, out double altitude, out double azimuth) + /* + public static void CalculateSunPosition(DateTime dateTime, double latitude, double longitude, out double altitude, out double azimuth) { const double Deg2Rad = Math.PI / 180.0; const double Rad2Deg = 180.0 / Math.PI; @@ -130,7 +598,7 @@ public static void CalculateSunPosition( double siderealTime = siderealTimeUT * 15 + longitude; // Refine to number of days (fractional) to specific time. - julianDate += dateTime.TimeOfDay.TotalHours / 24.0; + julianDate += dateTime.TimeOfDay.TotalDays; julianCenturies = julianDate / 36525.0; // Solar Coordinates @@ -164,9 +632,13 @@ public static void CalculateSunPosition( hourAngle -= 2 * Math.PI; } - altitude = Math.Asin(Math.Sin(latitude * Deg2Rad) * - Math.Sin(declination) + Math.Cos(latitude * Deg2Rad) * - Math.Cos(declination) * Math.Cos(hourAngle)); + altitude = Math.Asin( + Math.Sin(latitude * Deg2Rad) * + Math.Sin(declination) + + Math.Cos(latitude * Deg2Rad) * + Math.Cos(declination) * + Math.Cos(hourAngle) + ); // refraction correction // work in degrees @@ -185,10 +657,10 @@ public static void CalculateSunPosition( } else if (altitude > -0.575) { - double step1 = (-12.79 + altitude * 0.711); - double step2 = (103.4 + altitude * (step1)); - double step3 = (-518.2 + altitude * (step2)); - refractionCorrection = 1735.0 + altitude * (step3); + double step1 = -12.79 + altitude * 0.711; + double step2 = 103.4 + altitude * step1; + double step3 = -518.2 + altitude * step2; + refractionCorrection = 1735.0 + altitude * step3; } else { @@ -217,17 +689,17 @@ public static void CalculateSunPosition( } azimuth *= Rad2Deg; - //altitude = altitude * Rad2Deg; } - /*! - * \brief Corrects an angle. - * - * \param angleInRadians An angle expressed in radians. - * \return An angle in the range 0 to 2*PI. - * - * http://guideving.blogspot.co.uk/2010/08/sun-position-in-c.html - */ + + // + // \brief Corrects an angle. + // + // \param angleInRadians An angle expressed in radians. + // \return An angle in the range 0 to 2*PI. + // + // http://guideving.blogspot.co.uk/2010/08/sun-position-in-c.html + // private static double CorrectAngle(double angleInRadians) { @@ -243,8 +715,12 @@ private static double CorrectAngle(double angleInRadians) return angleInRadians; } + */ + + #endregion - public static double CalcSunDistance(DateTime dDate, DateTime dEpoch) + + private static double CalcSunDistance(DateTime dDate, DateTime dEpoch) { const double fAcc = 0.0000001; double fD = GetDaysBetween(dDate, dEpoch); @@ -268,7 +744,7 @@ public static double CalcSunDistance(DateTime dDate, DateTime dEpoch) return fDistance/149597871.0; } - public static double GetSunEarthEcc(DateTime dDate, bool b0Epoch) + private static double GetSunEarthEcc(DateTime dDate, bool b0Epoch) { //Returns the eccentricity of Earth's orbit around the sun for the specified date @@ -283,7 +759,7 @@ public static double GetSunEarthEcc(DateTime dDate, bool b0Epoch) } /* - public static double GetEarthObliquity(DateTime dDate, bool b0Epoch) + private static double GetEarthObliquity(DateTime dDate, bool b0Epoch) { //Returns the obliquity of Earth's orbit around the sun for the specified date @@ -300,7 +776,7 @@ public static double GetEarthObliquity(DateTime dDate, bool b0Epoch) } */ - public static double CalcEccentricAnomaly(double fEGuess, double fMA, double fEcc, double fAcc) + private static double CalcEccentricAnomaly(double fEGuess, double fMA, double fEcc, double fAcc) { //Calc Eccentric Anomaly to specified accuracy double fE; @@ -338,7 +814,7 @@ public static long GetDaysBetween(DateTime dDate, DateTime dSecDate) return (long) Math.Floor(fDays); } - public static double GetJulianDay(DateTime dDate, int iZone) + private static double GetJulianDay(DateTime dDate, int iZone) { double iGreg; double fC; @@ -375,7 +851,7 @@ public static double GetJulianDay(DateTime dDate, int iZone) return fJD; } - public static DateTime CalcUTFromZT(DateTime dDate, int iZone) + private static DateTime CalcUTFromZT(DateTime dDate, int iZone) { if (iZone >= 0) { @@ -385,7 +861,7 @@ public static DateTime CalcUTFromZT(DateTime dDate, int iZone) return dDate.AddHours(Math.Abs(iZone)); } - public static double GetSolarMEL(DateTime dDate, bool b0Epoch) + private static double GetSolarMEL(DateTime dDate, bool b0Epoch) { //Returns the Sun's Mean Ecliptic Longitude for the specified date @@ -401,7 +877,7 @@ public static double GetSolarMEL(DateTime dDate, bool b0Epoch) return fLong; } - public static double GetSolarPerigeeLong(DateTime dDate, bool b0Epoch) + private static double GetSolarPerigeeLong(DateTime dDate, bool b0Epoch) { //Returns the Sun's Perigee Longitude for the specified date @@ -418,17 +894,20 @@ public static double GetSolarPerigeeLong(DateTime dDate, bool b0Epoch) return fLong; } - public static double PutIn360Deg(double pfDeg) + private static double PutIn360Deg(double pfDeg) { - while (pfDeg >= 360) - { - pfDeg -= 360; - } - while (pfDeg < 0) - { - pfDeg += 360; - } - return pfDeg; + return PutInRange(pfDeg, 360); + } + + private static double PutInRange(double val, double range) + { + while (val >= range) + val -= range; + + while (val < 0) + val += range; + + return val; } //public static int cSunrise = 1; diff --git a/CumulusMX/Cumulus.cs b/CumulusMX/Cumulus.cs index 31e1701a..8b3d3af5 100644 --- a/CumulusMX/Cumulus.cs +++ b/CumulusMX/Cumulus.cs @@ -10144,6 +10144,10 @@ public async void CustomHttpSecondsUpdate() updatingCustomHttpSeconds = false; } } + else + { + LogDebugMessage("CustomHttpSeconds: Query already in progress, skipping this attempt"); + } } public async void CustomHttpMinutesUpdate() diff --git a/CumulusMX/HttpStationEcowitt.cs b/CumulusMX/HttpStationEcowitt.cs index 596dc3e0..be790126 100644 --- a/CumulusMX/HttpStationEcowitt.cs +++ b/CumulusMX/HttpStationEcowitt.cs @@ -289,8 +289,8 @@ public string ApplyData(string dataString, bool main, DateTime? ts = null) if (recDate.Minute != lastMinute) { - // at start-up or every 10 minutes trigger output of uptime - if ((recDate.Minute % 10) == 0 || lastMinute == -1 && data["runtime"] != null) + // at start-up or every 20 minutes trigger output of uptime + if ((recDate.Minute % 20) == 0 || lastMinute == -1 && data["runtime"] != null) { var runtime = Convert.ToInt32(data["runtime"]); var uptime = TimeSpan.FromSeconds(runtime); @@ -333,7 +333,7 @@ public string ApplyData(string dataString, bool main, DateTime? ts = null) if (gust == null || dir == null || spd == null) { - cumulus.LogMessage($"ProcessData: Error, missing wind data"); + cumulus.LogDebugMessage($"ProcessData: Error, missing wind data"); } else { @@ -374,7 +374,7 @@ public string ApplyData(string dataString, bool main, DateTime? ts = null) { if (thisHum == null) { - cumulus.LogMessage("ProcessData: Error, missing outdoor humidity"); + cumulus.LogDebugMessage("ProcessData: Error, missing outdoor humidity"); } else { @@ -400,7 +400,7 @@ public string ApplyData(string dataString, bool main, DateTime? ts = null) if (press == null) { - cumulus.LogMessage($"ProcessData: Error, missing baro pressure"); + cumulus.LogDebugMessage($"ProcessData: Error, missing baro pressure"); } else { @@ -425,7 +425,7 @@ public string ApplyData(string dataString, bool main, DateTime? ts = null) if (temp == null) { - cumulus.LogMessage($"ProcessData: Error, missing indoor temp"); + cumulus.LogDebugMessage($"ProcessData: Error, missing indoor temp"); } else { @@ -448,7 +448,7 @@ public string ApplyData(string dataString, bool main, DateTime? ts = null) { if (thisTemp == null) { - cumulus.LogMessage($"ProcessData: Error, missing outdoor temp"); + cumulus.LogDebugMessage($"ProcessData: Error, missing outdoor temp"); } else { @@ -514,7 +514,7 @@ public string ApplyData(string dataString, bool main, DateTime? ts = null) if (rain == null) { - cumulus.LogMessage($"ProcessData: Error, missing rainfall"); + cumulus.LogDebugMessage($"ProcessData: Error, missing rainfall"); } else { diff --git a/CumulusMX/Utils.cs b/CumulusMX/Utils.cs index fe26319e..db3c7ee6 100644 --- a/CumulusMX/Utils.cs +++ b/CumulusMX/Utils.cs @@ -127,16 +127,37 @@ public static string GetLogFileSeparator(string line, string defSep) public static IPAddress GetIpWithDefaultGateway() { - return NetworkInterface - .GetAllNetworkInterfaces() - .Where(n => n.OperationalStatus == OperationalStatus.Up) - .Where(n => n.NetworkInterfaceType != NetworkInterfaceType.Loopback) - .Where(n => n.GetIPProperties().GatewayAddresses.Count > 0) - .SelectMany(n => n.GetIPProperties().UnicastAddresses) - .Where(n => n.Address.AddressFamily == AddressFamily.InterNetwork) - .Where(n => n.IPv4Mask.ToString() != "0.0.0.0") - .Select(g => g.Address) - .First(); + try + { + // First try and find the IPv4 address that also has the default gateway + return NetworkInterface + .GetAllNetworkInterfaces() + .Where(n => n.OperationalStatus == OperationalStatus.Up) + .Where(n => n.NetworkInterfaceType != NetworkInterfaceType.Loopback) + .Where(n => n.GetIPProperties().GatewayAddresses.Count > 0) + .SelectMany(n => n.GetIPProperties().UnicastAddresses) + .Where(n => n.Address.AddressFamily == AddressFamily.InterNetwork) + .Where(n => n.IPv4Mask.ToString() != "0.0.0.0") + .Select(g => g.Address) + .First(); + } + catch {} + try + { + // next just return the first IPv4 address found + var host = Dns.GetHostEntry(Dns.GetHostName()); + foreach (var ip in host.AddressList) + { + if (ip.AddressFamily == AddressFamily.InterNetwork) + { + return ip; + } + } + } + catch {} + + // finally, give up and just return a 0.0.0.0 IP! + return IPAddress.Any; } } } diff --git a/CumulusMX/WeatherStation.cs b/CumulusMX/WeatherStation.cs index c9087114..bbe240ac 100644 --- a/CumulusMX/WeatherStation.cs +++ b/CumulusMX/WeatherStation.cs @@ -1619,9 +1619,10 @@ private void MinuteChanged(DateTime now) { CheckForDataStopped(); + CurrentSolarMax = AstroLib.SolarMax(now, cumulus.Longitude, cumulus.Latitude, AltitudeM(cumulus.Altitude), out SolarElevation, cumulus.SolarOptions); + if (!DataStopped) { - CurrentSolarMax = AstroLib.SolarMax(now, cumulus.Longitude, cumulus.Latitude, AltitudeM(cumulus.Altitude), out SolarElevation, cumulus.SolarOptions); if (((Pressure > 0) && TempReadyToPlot && WindReadyToPlot) || cumulus.StationOptions.NoSensorCheck) { // increment wind run by one minute's worth of average speed @@ -1677,7 +1678,7 @@ private void MinuteChanged(DateTime now) AddRecentDataWithAq(now, WindAverage, RecentMaxGust, WindLatest, Bearing, AvgBearing, OutdoorTemperature, WindChill, OutdoorDewpoint, HeatIndex, OutdoorHumidity, Pressure, RainToday, SolarRad, UV, Raincounter, FeelsLike, Humidex, ApparentTemperature, IndoorTemperature, IndoorHumidity, CurrentSolarMax, RainRate); DoTrendValues(now); - DoPressTrend("Pressure trend"); + DoPressTrend("Enable Cumulus pressure trend"); // calculate ET just before the hour so it is included in the correct day at roll over - only affects 9am met days really if (cumulus.StationOptions.CalculatedET && now.Minute == 59) diff --git a/Updates.txt b/Updates.txt index 40c38ad2..7f5aee2b 100644 --- a/Updates.txt +++ b/Updates.txt @@ -2,12 +2,14 @@ —————————————— - Fix: Cloud base being set to large value at start-up - Fix: Reading lightning distance from today.ini +- Fix: Potential crash obtaining the local IP address - New: Adds a PWS Simulator station type for testing or trial purposes - Change: The "live" dashboard screens now refresh whenever new data is received, or every five seconds - Change: The "Speed for average calc" station option has been moved from Common Options to Common Options | Advanced Options - Change: The Ecowitt stations now force the "Speed for average calc" option to be enabled at start-up +- Change: Slightly improved solar position calculations 3.16.1 - b3183 From 42211f07bee0ee68797958466634966cb2d008a5 Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Mon, 23 May 2022 09:17:24 +0100 Subject: [PATCH 5/5] Minor code tidy --- CumulusMX/DataEditor.cs | 4 ++-- CumulusMX/HttpStationAmbient.cs | 22 ++++++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/CumulusMX/DataEditor.cs b/CumulusMX/DataEditor.cs index cd2a56cb..4d833fc8 100644 --- a/CumulusMX/DataEditor.cs +++ b/CumulusMX/DataEditor.cs @@ -12,7 +12,7 @@ namespace CumulusMX internal class DataEditor { private WeatherStation station; - private Cumulus cumulus; + private readonly Cumulus cumulus; private WebTags webtags; private readonly List hourRainLog = new List(); @@ -1758,7 +1758,7 @@ internal string GetMonthlyRecDayFile() var isDryNow = false; var thisDateDry = DateTime.MinValue; var thisDateWet = DateTime.MinValue; - var monthOffset = 0; + int monthOffset; var firstEntry = true; var json = new StringBuilder("{", 25500); diff --git a/CumulusMX/HttpStationAmbient.cs b/CumulusMX/HttpStationAmbient.cs index 837da487..ebc73205 100644 --- a/CumulusMX/HttpStationAmbient.cs +++ b/CumulusMX/HttpStationAmbient.cs @@ -692,22 +692,23 @@ private void ProcessAirQuality(NameValueCollection data, WeatherStation station) } } + + /* + * Not yet used private void ProcessCo2(NameValueCollection data, WeatherStation station) { // co2 - [int, ppm] // co2_in - [int, ppm] // co2_in_24h - [float, ppm] - /* - if (data["co2_in"] != null) - { - station.CO2 = Convert.ToInt32(data["co2_in"], CultureInfo.InvariantCulture); - } - if (data["co2_in_24"] != null) - { - station.CO2_24h = Convert.ToInt32(data["co2_in_24"], CultureInfo.InvariantCulture); - } - */ + //if (data["co2_in"] != null) + //{ + // station.CO2 = Convert.ToInt32(data["co2_in"], CultureInfo.InvariantCulture); + //} + //if (data["co2_in_24"] != null) + //{ + // station.CO2_24h = Convert.ToInt32(data["co2_in_24"], CultureInfo.InvariantCulture); + //} // From FOSKplugin // co2lvl @@ -738,6 +739,7 @@ private void ProcessCo2(NameValueCollection data, WeatherStation station) station.CO2_pm10_24h = Convert.ToDouble(data["pm10_AQIlvl_24h_co2"], CultureInfo.InvariantCulture); } } + */ private void ProcessLightning(NameValueCollection data, WeatherStation station) {