From 950991b0c042de1b6aa4649f92604b9d3faf78a6 Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Mon, 8 Jun 2020 15:52:16 +0100 Subject: [PATCH] b3083 --- CumulusMX/Cumulus.cs | 297 +++++++++++++++----------- CumulusMX/DavisWllStation.cs | 299 ++++++++++++++------------- CumulusMX/GW1000Station.cs | 2 +- CumulusMX/MeteoLib.cs | 35 +++- CumulusMX/Properties/AssemblyInfo.cs | 6 +- CumulusMX/WeatherStation.cs | 5 + Updates.txt | 12 +- 7 files changed, 380 insertions(+), 276 deletions(-) diff --git a/CumulusMX/Cumulus.cs b/CumulusMX/Cumulus.cs index 8fe0fab0..8a3eb74f 100644 --- a/CumulusMX/Cumulus.cs +++ b/CumulusMX/Cumulus.cs @@ -31,8 +31,8 @@ namespace CumulusMX public class Cumulus { ///////////////////////////////// - public string Version = "3.6.6"; - public string Build = "3082"; + public string Version = "3.6.7"; + public string Build = "3083"; ///////////////////////////////// public static SemaphoreSlim syncInit = new SemaphoreSlim(1); @@ -2163,42 +2163,56 @@ internal async void UpdateWCloud(DateTime timestamp) internal void RealtimeTimerTick(object sender, ElapsedEventArgs elapsedEventArgs) { bool connectionFailed = false; - bool allowNextFtpCycle = false; + bool allowFTP = true; var cycle = RealtimeCycleCounter++; - LogDebugMessage($"Realtime FTP[{cycle}]: Start upload cycle"); + LogDebugMessage($"Realtime[{cycle}]: Start upload cycle"); + try { // Process any files - CreateRealtimeFile(); - CreateRealtimeHTMLfiles(); + CreateRealtimeFile(cycle); + CreateRealtimeHTMLfiles(cycle); if (RealtimeFTPEnabled) { - // are we currently trying to connect - if (RealtimeFtpReconnecting) - { - LogDebugMessage($"Realtime FTP[{cycle}]: Warning, previous FTP is still reconnecting"); - } - // The previous cycle is still running - else if (RealtimeInProgress) + // Is a previous cycle still running? + if (RealtimeInProgress) { + // Is a previous cycle still trying to reconnect? + if (RealtimeFtpReconnecting) + { + if (++RealtimeFTPRetries < 60000 / RealtimeInterval) + { + LogDebugMessage($"Realtime[{cycle}]: Warning, previous Realtime process still trying to connect to FTP server, skip count = {RealtimeFTPRetries}"); + allowFTP = false; + } + else + { + LogMessage($"Realtime[{cycle}]: Error, realtime has been reconnecting for too long, attempting to reconnect."); + RealtimeFtpReconnecting = true; + RealtimeFTPConnectionTest(cycle); + RealtimeFtpReconnecting = false; + } + } // Allow retries to continue for 1 minute - if (++RealtimeFTPRetries < 60000 / RealtimeInterval) + else if (++RealtimeFTPRetries < 60000 / RealtimeInterval) { - LogMessage($"Realtime FTP[{cycle}]: Warning, previous FTP still in progress, skipping this period - {RealtimeFTPRetries}"); + LogMessage($"Realtime[{cycle}]: Warning, previous Realtime process still in progress, skip count = {RealtimeFTPRetries}"); + allowFTP = false; } else { - LogMessage($"Realtime FTP[{cycle}]: Error, FTP been failing for 1 minute, attempting to reconnect."); + LogMessage($"Realtime[{cycle}]: Error, previous upload has been running for too long, attempting to reconnect."); + RealtimeFtpReconnecting = true; RealtimeFTPConnectionTest(cycle); - // we should be reconnected, or not, reset the retry counter anyway to restart the process - RealtimeFTPRetries = 0; - allowNextFtpCycle = true; + RealtimeFtpReconnecting = false; } } - // We are good to go - else + + + // are we good to go? + if (allowFTP) { RealtimeFTPRetries = 0; RealtimeInProgress = true; @@ -2251,13 +2265,15 @@ internal void RealtimeTimerTick(object sender, ElapsedEventArgs elapsedEventArgs } catch (Exception ex) { - LogDebugMessage($"Realtime FTP[{cycle}]: Test of FTP connection failed: {ex.Message}"); + LogDebugMessage($"Realtime[{cycle}]: Test of FTP connection failed: {ex.Message}"); connectionFailed = true; } if (connectionFailed) { + RealtimeFtpReconnecting = true; RealtimeFTPConnectionTest(cycle); + RealtimeFtpReconnecting = false; } try @@ -2266,27 +2282,33 @@ internal void RealtimeTimerTick(object sender, ElapsedEventArgs elapsedEventArgs } catch (Exception ex) { - LogMessage($"Realtime FTP[{cycle}]: Error during realtime FTP update: {ex.Message}"); + LogMessage($"Realtime[{cycle}]: Error during realtime FTP update: {ex.Message}"); + RealtimeFtpReconnecting = true; RealtimeFTPConnectionTest(cycle); + RealtimeFtpReconnecting = false; } - allowNextFtpCycle = true; + RealtimeInProgress = false; + } + else + { + LogDebugMessage($"Realtime[{cycle}]: No FTP attempted this cycle"); } } if (!string.IsNullOrEmpty(RealtimeProgram)) { - //LogDebugMessage("Execute realtime program"); + LogDebugMessage($"Realtime[{cycle}]: Execute realtime program - {RealtimeProgram}"); ExecuteProgram(RealtimeProgram, RealtimeParams); } } catch (Exception ex) { - LogMessage($"Realtime FTP[{cycle}]: Error during update: {ex.Message}"); + LogMessage($"Realtime[{cycle}]: Error during update: {ex.Message}"); + RealtimeFtpReconnecting = true; if (ex is NullReferenceException) { // If we haven't initialised the object (eg. user enables realtime FTP after starting Cumulus) // then start from the beginning - RealtimeFtpReconnecting = true; if (Sslftp == FtpProtocols.SFTP) { RealtimeSSHLogin(); @@ -2295,22 +2317,20 @@ internal void RealtimeTimerTick(object sender, ElapsedEventArgs elapsedEventArgs { RealtimeFTPLogin(); } - RealtimeFtpReconnecting = false; } else { RealtimeFTPConnectionTest(cycle); } + RealtimeFtpReconnecting = false; } - LogDebugMessage($"Realtime FTP[{cycle}]: End upload cycle"); - if (allowNextFtpCycle) - RealtimeInProgress = false; + LogDebugMessage($"Realtime[{cycle}]: End upload cycle"); } private void RealtimeFTPConnectionTest(uint cycle) { RealtimeFtpReconnecting = true; - LogDebugMessage($"Realtime FTP[{cycle}]: Realtime ftp attempting disconnect"); + LogDebugMessage($"Realtime[{cycle}]: Realtime ftp attempting disconnect"); try { if (Sslftp == FtpProtocols.SFTP) @@ -2321,14 +2341,14 @@ private void RealtimeFTPConnectionTest(uint cycle) { RealtimeFTP.Disconnect(); } - LogDebugMessage($"Realtime FTP[{cycle}]: Realtime ftp disconnected OK"); + LogDebugMessage($"Realtime[{cycle}]: Realtime ftp disconnected OK"); } catch(Exception ex) { - LogDebugMessage($"Realtime FTP[{cycle}]: Error disconnecting from server - " + ex.Message); + LogDebugMessage($"Realtime[{cycle}]: Error disconnecting from server - " + ex.Message); } - LogDebugMessage($"Realtime FTP[{cycle}]: Realtime ftp attempting to reconnect"); + LogDebugMessage($"Realtime[{cycle}]: Realtime ftp attempting to reconnect"); try { if (Sslftp == FtpProtocols.SFTP) @@ -2339,12 +2359,12 @@ private void RealtimeFTPConnectionTest(uint cycle) { RealtimeFTP.Connect(); } - LogDebugMessage($"Realtime FTP[{cycle}]: Reconnected with server OK"); + LogDebugMessage($"Realtime[{cycle}]: Reconnected with server OK"); } catch (Exception ex) { - LogMessage($"Realtime FTP[{cycle}]: Error reconnecting ftp server - " + ex.Message); - LogDebugMessage($"Realtime FTP[{cycle}]: Realtime ftp attempting to reinitialise the connection"); + LogMessage($"Realtime[{cycle}]: Error reconnecting ftp server - " + ex.Message); + LogDebugMessage($"Realtime[{cycle}]: Realtime ftp attempting to reinitialise the connection"); if (Sslftp == FtpProtocols.SFTP) { RealtimeSSHLogin(); @@ -2375,7 +2395,7 @@ private void RealtimeFTPUpload(uint cycle) if (RealtimeTxtFTP) { - LogFtpMessage($"Realtime FTP[{cycle}]: Uploading - realtime.txt"); + LogFtpMessage($"Realtime[{cycle}]: Uploading - realtime.txt"); if (Sslftp == FtpProtocols.SFTP) { UploadFile(RealtimeSSH, RealtimeFile, filepath); @@ -2389,7 +2409,7 @@ private void RealtimeFTPUpload(uint cycle) if (RealtimeGaugesTxtFTP) { ProcessTemplateFile(RealtimeGaugesTxtTFile, RealtimeGaugesTxtFile, realtimeTokenParser); - LogFtpMessage($"Realtime FTP[{cycle}]: Uploading - realtimegauges.txt"); + LogFtpMessage($"Realtime[{cycle}]: Uploading - realtimegauges.txt"); if (Sslftp == FtpProtocols.SFTP) { UploadFile(RealtimeSSH, RealtimeGaugesTxtFile, gaugesfilepath); @@ -2423,7 +2443,7 @@ private void RealtimeFTPUpload(uint cycle) // we've already processed the file uploadfile += "tmp"; } - LogFtpMessage($"Realtime FTP[{cycle}]: Uploading - " + uploadfile); + LogFtpMessage($"Realtime[{cycle}]: Uploading extra web file[{i}] - {uploadfile}"); if (Sslftp == FtpProtocols.SFTP) { UploadFile(RealtimeSSH, uploadfile, remotefile); @@ -2435,13 +2455,13 @@ private void RealtimeFTPUpload(uint cycle) } else { - LogMessage($"Realtime FTP[{cycle}]: Extra web file #" + i + " [" + uploadfile + "] not found!"); + LogMessage($"Realtime[{cycle}]: Warning, extra web file[{i}] not found! - {uploadfile}"); } } } } - private void CreateRealtimeHTMLfiles() + private void CreateRealtimeHTMLfiles(int cycle) { for (int i = 0; i < numextrafiles; i++) { @@ -2464,17 +2484,24 @@ private void CreateRealtimeHTMLfiles() if (ExtraFiles[i].process) { // process the file + LogDebugMessage($"Realtime[{cycle}]: Processing extra file[{i}] - {uploadfile}"); var utf8WithoutBom = new UTF8Encoding(false); var encoding = UTF8encode ? utf8WithoutBom : Encoding.GetEncoding("iso-8859-1"); realtimeTokenParser.encoding = encoding; realtimeTokenParser.SourceFile = uploadfile; var output = realtimeTokenParser.ToString(); uploadfile += "tmp"; - using (StreamWriter file = new StreamWriter(uploadfile, false, encoding)) + try { - file.Write(output); - - file.Close(); + using (StreamWriter file = new StreamWriter(uploadfile, false, encoding)) + { + file.Write(output); + file.Close(); + } + } + catch (Exception ex) + { + LogMessage($"Error writing to extra realtime file[{i}] - {uploadfile}: {ex.Message}"); } } @@ -2483,17 +2510,18 @@ private void CreateRealtimeHTMLfiles() // just copy the file try { + LogDebugMessage($"Realtime[{cycle}]: Copying extra file[{i}] - {uploadfile}"); File.Copy(uploadfile, remotefile, true); } catch (Exception ex) { - LogDebugMessage($"Copying extra realtime file - {uploadfile}: {ex.Message}"); + LogDebugMessage($"Copying extra realtime file[{i}] - {uploadfile}: {ex.Message}"); } } } else { - LogMessage($"Extra realtime web file #{i} [{uploadfile}] not found!"); + LogMessage($"Extra realtime web file[{i}] not found - {uploadfile}"); } } @@ -6124,7 +6152,7 @@ public void DoHTMLFiles() { if (!RealtimeEnabled) { - CreateRealtimeFile(); + CreateRealtimeFile(999); } //LogDebugMessage("Creating standard HTML files"); @@ -6167,7 +6195,7 @@ public void DoHTMLFiles() if (ExtraFiles[i].process) { - //LogDebugMessage("Processing extra file "+uploadfile); + LogDebugMessage($"Processing extra file[{i}] - {uploadfile}"); // process the file var utf8WithoutBom = new System.Text.UTF8Encoding(false); var encoding = UTF8encode ? utf8WithoutBom : System.Text.Encoding.GetEncoding("iso-8859-1"); @@ -6202,14 +6230,14 @@ public void DoHTMLFiles() } catch (Exception ex) { - LogDebugMessage("Error copying extra file: " + ex.Message); + LogDebugMessage($"Error copying extra file[{i}]: " + ex.Message); } //LogDebugMessage("Finished copying extra file " + uploadfile); } } else { - LogMessage("Extra web file #" + i + " [" + uploadfile + "] not found!"); + LogMessage($"Warning, extra web file[{i}] not found - {uploadfile}"); } } } @@ -6636,7 +6664,7 @@ private void UploadFile(FtpClient conn, string localfile, string remotefile) } using (Stream ostream = conn.OpenWrite(remotefilename)) - using (Stream istream = new FileStream(localfile, FileMode.Open)) + using (Stream istream = new FileStream(localfile, FileMode.Open, FileAccess.Read)) { try { @@ -6706,10 +6734,11 @@ private void UploadFile(SftpClient conn, string localfile, string remotefile) */ //LogFtpMessage($"SFTP: Uploading {remotefilename}"); - using (Stream istream = new FileStream(localfile, FileMode.Open)) + using (Stream istream = new FileStream(localfile, FileMode.Open, FileAccess.Read)) { try { + conn.OperationTimeout = TimeSpan.FromSeconds(15); conn.UploadFile(istream, remotefilename, true); } catch (Exception ex) @@ -6776,7 +6805,7 @@ public string ReplaceCommas(string AStr) return AStr.Replace(',', '.'); } - private void CreateRealtimeFile() + private void CreateRealtimeFile(int cycle) { /* Example: 18/10/08 16:03:45 8.4 84 5.8 24.2 33.0 261 0.0 1.0 999.7 W 6 mph C mb mm 146.6 +0.1 85.2 588.4 11.6 20.3 57 3.6 -0.7 10.9 12:00 7.8 14:41 37.4 14:38 44.0 14:28 999.8 16:01 998.4 12:06 1.8.2 448 36.0 10.3 10.5 0 9.3 @@ -6845,75 +6874,85 @@ 59 8.4 Feels Like temperature var filename = AppDir + RealtimeFile; DateTime timestamp = DateTime.Now; - using (StreamWriter file = new StreamWriter(filename, false)) + try { - var InvC = new CultureInfo(""); - - file.Write(timestamp.ToString("dd/MM/yy HH:mm:ss ")); // 1, 2 - file.Write(station.OutdoorTemperature.ToString(TempFormat, InvC) + ' '); // 3 - file.Write(station.OutdoorHumidity.ToString() + ' '); // 4 - file.Write(station.OutdoorDewpoint.ToString(TempFormat, InvC) + ' '); // 5 - file.Write(station.WindAverage.ToString(WindFormat, InvC) + ' '); // 6 - file.Write(station.WindLatest.ToString(WindFormat, InvC) + ' '); // 7 - file.Write(station.Bearing.ToString() + ' '); // 8 - file.Write(station.RainRate.ToString(RainFormat, InvC) + ' '); // 9 - file.Write(station.RainToday.ToString(RainFormat, InvC) + ' '); // 10 - file.Write(station.Pressure.ToString(PressFormat, InvC) + ' '); // 11 - file.Write(station.CompassPoint(station.Bearing) + ' '); // 12 - file.Write(Beaufort(station.WindAverage) + ' '); // 13 - file.Write(WindUnitText + ' '); // 14 - file.Write(TempUnitText[1].ToString() + ' '); // 15 - file.Write(PressUnitText + ' '); // 16 - file.Write(RainUnitText + ' '); // 17 - file.Write(station.WindRunToday.ToString(WindRunFormat, InvC) + ' '); // 18 - if (station.presstrendval > 0) - file.Write('+' + station.presstrendval.ToString(PressFormat, InvC) + ' '); // 19 - else - file.Write(station.presstrendval.ToString(PressFormat, InvC) + ' '); - file.Write(station.RainMonth.ToString(RainFormat, InvC) + ' '); // 20 - file.Write(station.RainYear.ToString(RainFormat, InvC) + ' '); // 21 - file.Write(station.RainYesterday.ToString(RainFormat, InvC) + ' '); // 22 - file.Write(station.IndoorTemperature.ToString(TempFormat, InvC) + ' '); // 23 - file.Write(station.IndoorHumidity.ToString() + ' '); // 24 - file.Write(station.WindChill.ToString(TempFormat, InvC) + ' '); // 25 - file.Write(station.temptrendval.ToString(TempTrendFormat, InvC) + ' '); // 26 - file.Write(station.HighTempToday.ToString(TempFormat, InvC) + ' '); // 27 - file.Write(station.hightemptodaytime.ToString("HH:mm") + ' '); // 28 - file.Write(station.LowTempToday.ToString(TempFormat, InvC) + ' '); // 29 - file.Write(station.lowtemptodaytime.ToString("HH:mm") + ' '); // 30 - file.Write(station.highwindtoday.ToString(WindFormat, InvC) + ' '); // 31 - file.Write(station.highwindtodaytime.ToString("HH:mm") + ' '); // 32 - file.Write(station.highgusttoday.ToString(WindFormat, InvC) + ' '); // 33 - file.Write(station.highgusttodaytime.ToString("HH:mm") + ' '); // 34 - file.Write(station.highpresstoday.ToString(PressFormat, InvC) + ' '); // 35 - file.Write(station.highpresstodaytime.ToString("HH:mm") + ' '); // 36 - file.Write(station.lowpresstoday.ToString(PressFormat, InvC) + ' '); // 37 - file.Write(station.lowpresstodaytime.ToString("HH:mm") + ' '); // 38 - file.Write(Version + ' '); // 39 - file.Write(Build + ' '); // 40 - file.Write(station.RecentMaxGust.ToString(WindFormat, InvC) + ' '); // 41 - file.Write(station.HeatIndex.ToString(TempFormat, InvC) + ' '); // 42 - file.Write(station.Humidex.ToString(TempFormat, InvC) + ' '); // 43 - file.Write(station.UV.ToString(UVFormat, InvC) + ' '); // 44 - file.Write(station.ET.ToString(ETFormat, InvC) + ' '); // 45 - file.Write((Convert.ToInt32(station.SolarRad)).ToString() + ' '); // 46 - file.Write(station.AvgBearing.ToString() + ' '); // 47 - file.Write(station.RainLastHour.ToString(RainFormat, InvC) + ' '); // 48 - file.Write(station.Forecastnumber.ToString() + ' '); // 49 - file.Write(IsDaylight() ? "1 " : "0 "); - file.Write(station.SensorContactLost ? "1 " : "0 "); - file.Write(station.CompassPoint(station.AvgBearing) + ' '); // 52 - file.Write((Convert.ToInt32(station.CloudBase)).ToString() + ' '); // 53 - file.Write(CloudBaseInFeet ? "ft " : "m "); - file.Write(station.ApparentTemperature.ToString(TempFormat, InvC) + ' '); // 55 - file.Write(station.SunshineHours.ToString(SunFormat, InvC) + ' '); // 56 - file.Write(Convert.ToInt32(station.CurrentSolarMax).ToString() + ' '); // 57 - file.Write(station.IsSunny ? "1 " : "0 "); // 58 - file.WriteLine(station.FeelsLike.ToString(TempFormat, InvC)); // 59 + LogDebugMessage($"Realtime[{cycle}]: Creating realtime.txt"); + using (StreamWriter file = new StreamWriter(filename, false)) + { + var InvC = new CultureInfo(""); + + file.Write(timestamp.ToString("dd/MM/yy HH:mm:ss ")); // 1, 2 + file.Write(station.OutdoorTemperature.ToString(TempFormat, InvC) + ' '); // 3 + file.Write(station.OutdoorHumidity.ToString() + ' '); // 4 + file.Write(station.OutdoorDewpoint.ToString(TempFormat, InvC) + ' '); // 5 + file.Write(station.WindAverage.ToString(WindFormat, InvC) + ' '); // 6 + file.Write(station.WindLatest.ToString(WindFormat, InvC) + ' '); // 7 + file.Write(station.Bearing.ToString() + ' '); // 8 + file.Write(station.RainRate.ToString(RainFormat, InvC) + ' '); // 9 + file.Write(station.RainToday.ToString(RainFormat, InvC) + ' '); // 10 + file.Write(station.Pressure.ToString(PressFormat, InvC) + ' '); // 11 + file.Write(station.CompassPoint(station.Bearing) + ' '); // 12 + file.Write(Beaufort(station.WindAverage) + ' '); // 13 + file.Write(WindUnitText + ' '); // 14 + file.Write(TempUnitText[1].ToString() + ' '); // 15 + file.Write(PressUnitText + ' '); // 16 + file.Write(RainUnitText + ' '); // 17 + file.Write(station.WindRunToday.ToString(WindRunFormat, InvC) + ' '); // 18 + if (station.presstrendval > 0) + file.Write('+' + station.presstrendval.ToString(PressFormat, InvC) + ' '); // 19 + else + file.Write(station.presstrendval.ToString(PressFormat, InvC) + ' '); + file.Write(station.RainMonth.ToString(RainFormat, InvC) + ' '); // 20 + file.Write(station.RainYear.ToString(RainFormat, InvC) + ' '); // 21 + file.Write(station.RainYesterday.ToString(RainFormat, InvC) + ' '); // 22 + file.Write(station.IndoorTemperature.ToString(TempFormat, InvC) + ' '); // 23 + file.Write(station.IndoorHumidity.ToString() + ' '); // 24 + file.Write(station.WindChill.ToString(TempFormat, InvC) + ' '); // 25 + file.Write(station.temptrendval.ToString(TempTrendFormat, InvC) + ' '); // 26 + file.Write(station.HighTempToday.ToString(TempFormat, InvC) + ' '); // 27 + file.Write(station.hightemptodaytime.ToString("HH:mm") + ' '); // 28 + file.Write(station.LowTempToday.ToString(TempFormat, InvC) + ' '); // 29 + file.Write(station.lowtemptodaytime.ToString("HH:mm") + ' '); // 30 + file.Write(station.highwindtoday.ToString(WindFormat, InvC) + ' '); // 31 + file.Write(station.highwindtodaytime.ToString("HH:mm") + ' '); // 32 + file.Write(station.highgusttoday.ToString(WindFormat, InvC) + ' '); // 33 + file.Write(station.highgusttodaytime.ToString("HH:mm") + ' '); // 34 + file.Write(station.highpresstoday.ToString(PressFormat, InvC) + ' '); // 35 + file.Write(station.highpresstodaytime.ToString("HH:mm") + ' '); // 36 + file.Write(station.lowpresstoday.ToString(PressFormat, InvC) + ' '); // 37 + file.Write(station.lowpresstodaytime.ToString("HH:mm") + ' '); // 38 + file.Write(Version + ' '); // 39 + file.Write(Build + ' '); // 40 + file.Write(station.RecentMaxGust.ToString(WindFormat, InvC) + ' '); // 41 + file.Write(station.HeatIndex.ToString(TempFormat, InvC) + ' '); // 42 + file.Write(station.Humidex.ToString(TempFormat, InvC) + ' '); // 43 + file.Write(station.UV.ToString(UVFormat, InvC) + ' '); // 44 + file.Write(station.ET.ToString(ETFormat, InvC) + ' '); // 45 + file.Write((Convert.ToInt32(station.SolarRad)).ToString() + ' '); // 46 + file.Write(station.AvgBearing.ToString() + ' '); // 47 + file.Write(station.RainLastHour.ToString(RainFormat, InvC) + ' '); // 48 + file.Write(station.Forecastnumber.ToString() + ' '); // 49 + file.Write(IsDaylight() ? "1 " : "0 "); + file.Write(station.SensorContactLost ? "1 " : "0 "); + file.Write(station.CompassPoint(station.AvgBearing) + ' '); // 52 + file.Write((Convert.ToInt32(station.CloudBase)).ToString() + ' '); // 53 + file.Write(CloudBaseInFeet ? "ft " : "m "); + file.Write(station.ApparentTemperature.ToString(TempFormat, InvC) + ' '); // 55 + file.Write(station.SunshineHours.ToString(SunFormat, InvC) + ' '); // 56 + file.Write(Convert.ToInt32(station.CurrentSolarMax).ToString() + ' '); // 57 + file.Write(station.IsSunny ? "1 " : "0 "); // 58 + file.WriteLine(station.FeelsLike.ToString(TempFormat, InvC)); // 59 - file.Close(); + file.Close(); + } + } + catch (Exception ex) + { + LogMessage("Error encountered during Realtime file update."); + LogMessage(ex.Message); } + if (RealtimeMySqlEnabled) { var InvC = new CultureInfo(""); @@ -6986,7 +7025,7 @@ 59 8.4 Feels Like temperature { cmd.CommandText = queryString; cmd.Connection = RealtimeSqlConn; - LogDebugMessage(queryString); + LogDebugMessage($"Realtime[{cycle}]: Running SQL command: {queryString}"); try { @@ -7021,7 +7060,14 @@ 59 8.4 Feels Like temperature } finally { - RealtimeSqlConn.Close(); + try + { + RealtimeSqlConn.Close(); + } + catch + { + // do nothing + } } } } @@ -7057,6 +7103,7 @@ public void StartTimers() if (RealtimeFTPEnabled) { + Console.WriteLine("Connecting real time FTP"); if (Sslftp == FtpProtocols.SFTP) { RealtimeSSHLogin(); @@ -7518,15 +7565,15 @@ private void RealtimeSSHLogin() } RealtimeSSH = new SftpClient(connectionInfo); */ + if (RealtimeSSH != null) RealtimeSSH.Dispose(); RealtimeSSH = new SftpClient(ftp_host, ftp_port, ftp_user, ftp_password); RealtimeSSH.Connect(); + RealtimeSSH.ConnectionInfo.Timeout = TimeSpan.FromSeconds(15); // 15 seconds to match FTP default timeout LogMessage("Realtime SFTP connected"); } catch (Exception ex) { LogMessage("Error connecting sftp - " + ex.Message); - //RealtimeSSH.Disconnect(); - //RealtimeSSH.Dispose(); } } } diff --git a/CumulusMX/DavisWllStation.cs b/CumulusMX/DavisWllStation.cs index 6a7af07e..4ff0cb04 100644 --- a/CumulusMX/DavisWllStation.cs +++ b/CumulusMX/DavisWllStation.cs @@ -33,6 +33,7 @@ internal class DavisWllStation : WeatherStation private int MaxArchiveRuns = 1; private static readonly HttpClientHandler HistoricHttpHandler = new HttpClientHandler(); private readonly HttpClient WlHttpClient = new HttpClient(HistoricHttpHandler); + private readonly HttpClient dogsBodyClient = new HttpClient(); private readonly bool checkWllGustValues; private bool broadcastReceived = false; private int weatherLinkArchiveInterval = 16 * 60; // Used to get historic Health, 16 minutes in seconds only for initial fetch after load @@ -55,8 +56,8 @@ public DavisWllStation(Cumulus cumulus) : base(cumulus) tmrBroadcastWatchdog = new System.Timers.Timer(); tmrHealth = new System.Timers.Timer(); - // Get the firmware version from WL.com API - //_ = GetWlCurrentData(true); + // used for kicking real time, and getting current conditions + dogsBodyClient.DefaultRequestHeaders.Add("Connection", "close"); // If the user is using the default 10 minute Wind gust, always use gust data from the WLL - simple if (cumulus.PeakGustMinutes == 10) @@ -82,12 +83,13 @@ public DavisWllStation(Cumulus cumulus) : base(cumulus) // Perform zero-config // If it works - check IP address in config file and set/update if required // If it fails - just use the IP address from config file + const string serviceType = "_weatherlinklive._tcp"; var serviceBrowser = new ServiceBrowser(); serviceBrowser.ServiceAdded += OnServiceAdded; serviceBrowser.ServiceRemoved += OnServiceRemoved; serviceBrowser.ServiceChanged += OnServiceChanged; - serviceBrowser.QueryParameters.QueryInterval = cumulus.WllBroadcastDuration * 1000 / 2; + serviceBrowser.QueryParameters.QueryInterval = cumulus.WllBroadcastDuration * 1000 * 4; // query at 4x the multicast time (default 20 mins) //Console.WriteLine($"Browsing for type: {serviceType}"); serviceBrowser.StartBrowse(serviceType); @@ -132,7 +134,7 @@ public override void Start() // Create a realtime thread to periodically restart broadcasts GetWllRealtime(null, null); tmrRealtime.Elapsed += GetWllRealtime; - tmrRealtime.Interval = cumulus.WllBroadcastDuration * 1000; + tmrRealtime.Interval = cumulus.WllBroadcastDuration * 1000 / 3 * 2; // give the multicasts a kick after 2/3 of the duration (default 200 secs) tmrRealtime.AutoReset = true; tmrRealtime.Start(); @@ -200,9 +202,8 @@ public override void Start() if (cumulus.UseDataLogger) { // get the health data every 15 minutes - //tmrHealth.Elapsed += GetWlHealth; tmrHealth.Elapsed += HealthTimerTick; - tmrHealth.Interval = 60 * 1000; // Every minute + tmrHealth.Interval = 60 * 1000; // Tick every minute tmrHealth.AutoReset = true; tmrHealth.Start(); } @@ -244,33 +245,29 @@ private async void GetWllRealtime(object source, ElapsedEventArgs e) WebReq.Wait(); cumulus.LogDebugMessage("Lock: GetWllRealtime has the lock"); - using (HttpClient client = new HttpClient()) + // The WLL will error if already responding to a request from another device, so add a retry + do { - // The WLL will error if already responding to a request from another device, so add a retry - do + // Call asynchronous network methods in a try/catch block to handle exceptions + try { - // Call asynchronous network methods in a try/catch block to handle exceptions - try - { - string ip; + string ip; - lock (threadSafer) - { - ip = cumulus.VP2IPAddr; - } - - if (CheckIpValid(ip)) - { - var urlRealtime = "http://" + ip + "/v1/real_time?duration=" + cumulus.WllBroadcastDuration; + lock (threadSafer) + { + ip = cumulus.VP2IPAddr; + } - cumulus.LogDebugMessage($"Sending GET realtime request to WLL: {urlRealtime} ..."); + if (CheckIpValid(ip)) + { + var urlRealtime = "http://" + ip + "/v1/real_time?duration=" + cumulus.WllBroadcastDuration; - client.DefaultRequestHeaders.Add("Connection", "close"); + cumulus.LogDebugMessage($"Sending GET realtime request to WLL: {urlRealtime} ..."); - var response = await client.GetAsync(urlRealtime); + using (HttpResponseMessage response = await dogsBodyClient.GetAsync(urlRealtime)) + { //response.EnsureSuccessStatusCode(); var responseBody = await response.Content.ReadAsStringAsync(); - responseBody = responseBody.TrimEnd('\r', '\n'); cumulus.LogDataMessage("WLL Real_time response: " + responseBody); @@ -291,27 +288,24 @@ private async void GetWllRealtime(object source, ElapsedEventArgs e) cumulus.WllBroadcastPort = port; } } - else - { - cumulus.LogMessage($"WLL realtime: Invalid IP address: {ip}"); - } - retry = 0; } - catch (Exception exp) + else { - retry--; - cumulus.LogDebugMessage("GetRealtime(): Exception Caught!"); - cumulus.LogDebugMessage($"Message :{exp.Message}"); - //Console.ForegroundColor = ConsoleColor.Red; - //Console.WriteLine("GetRealtime():Exception Caught!"); - //Console.ForegroundColor = ConsoleColor.White; - Thread.Sleep(2000); + cumulus.LogMessage($"WLL realtime: Invalid IP address: {ip}"); } - } while (retry > 0); + retry = 0; + } + catch (Exception exp) + { + retry--; + cumulus.LogDebugMessage("GetRealtime(): Exception Caught!"); + cumulus.LogDebugMessage($"Message :{exp.Message}"); + Thread.Sleep(2000); + } + } while (retry > 0); - cumulus.LogDebugMessage("Lock: GetWllRealtime releasing lock"); - WebReq.Release(); - } + cumulus.LogDebugMessage("Lock: GetWllRealtime releasing lock"); + WebReq.Release(); } private async void GetWllCurrent(object source, ElapsedEventArgs e) @@ -338,17 +332,13 @@ private async void GetWllCurrent(object source, ElapsedEventArgs e) { cumulus.LogDebugMessage($"Sending GET current conditions request to WLL: {urlCurrent} ..."); // First time run it synchronously - using (HttpClient client = new HttpClient()) + // Call asynchronous network methods in a try/catch block to handle exceptions + try { - // Call asynchronous network methods in a try/catch block to handle exceptions - try + using (HttpResponseMessage response = await dogsBodyClient.GetAsync(urlCurrent)) { - client.DefaultRequestHeaders.Add("Connection", "close"); - - var response = await client.GetAsync(urlCurrent); response.EnsureSuccessStatusCode(); var responseBody = await response.Content.ReadAsStringAsync(); - //Console.WriteLine($" - Current conds response: {responseBody}"); // sanity check if (responseBody.StartsWith("{\"data\":{\"did\":")) @@ -367,17 +357,17 @@ private async void GetWllCurrent(object source, ElapsedEventArgs e) } retry = 0; } - catch (Exception exp) - { - retry--; - cumulus.LogDebugMessage("GetWllCurrent(): Exception Caught!"); - cumulus.LogDebugMessage($"Message :" + exp.Message); - - //Console.ForegroundColor = ConsoleColor.Red; - //Console.WriteLine("GetWllCurrent():Exception Caught!"); - //Console.ForegroundColor = ConsoleColor.White; - Thread.Sleep(2000); - } + } + catch (Exception exp) + { + retry--; + cumulus.LogDebugMessage("GetWllCurrent(): Exception Caught!"); + cumulus.LogDebugMessage($"Message :" + exp.Message); + + //Console.ForegroundColor = ConsoleColor.Red; + //Console.WriteLine("GetWllCurrent():Exception Caught!"); + //Console.ForegroundColor = ConsoleColor.White; + Thread.Sleep(2000); } } while (retry > 0); @@ -399,7 +389,7 @@ private void DecodeBroadcast(string broadcastJson) // sanity check if (broadcastJson.StartsWith("{\"did\":")) { - var data = Newtonsoft.Json.Linq.JObject.Parse(broadcastJson)["conditions"]; + var data = JObject.Parse(broadcastJson).GetValue("conditions"); // The WLL sends the timestamp in Unix ticks, and in UTC // rather than rely on the WLL clock being correct, we will use our local time @@ -520,7 +510,8 @@ private void DecodeCurrent(string currentJson) cumulus.LogDataMessage("WLL Current conditions: " + currentJson); // Convert JSON string to an object - var data = Newtonsoft.Json.Linq.JObject.Parse(currentJson)["data"]["conditions"]; + //var data = Newtonsoft.Json.Linq.JObject.Parse(currentJson)["data"]["conditions"]; + var data = Newtonsoft.Json.Linq.JObject.Parse(currentJson).SelectToken("data.conditions"); // The WLL sends the timestamp in Unix ticks, and in UTC // rather than rely on the WLL clock being correct, we will use our local time @@ -996,6 +987,11 @@ private void DecodeCurrent(string currentJson) { DoPressure(ConvertPressINHGToUser(rec.Value("bar_sea_level")), dateTime); DoPressTrend("Pressure trend"); + // Altimeter from absolute + StationPressure = ConvertPressINHGToUser(rec.Value("bar_absolute")); + // Or do we use calibration? The VP2 code doesn't? + //StationPressure = ConvertPressINHGToUser(rec.Value("bar_absolute")) * cumulus.PressMult + cumulus.PressOffset; + AltimeterPressure = ConvertPressMBToUser(StationToAltimeter(PressureHPa(StationPressure), AltitudeM(cumulus.Altitude))); } break; @@ -1057,8 +1053,6 @@ private void DecodeCurrent(string currentJson) } } - data = null; - DoApparentTemp(dateTime); DoFeelsLike(dateTime); @@ -1459,61 +1453,63 @@ private void GetWlHistoricData() try { // we want to do this synchronously, so .Result - var response = WlHttpClient.GetAsync(historicUrl.ToString()).Result; - var responseBody = response.Content.ReadAsStringAsync().Result; - cumulus.LogDebugMessage($"WeatherLink API Historic Response: {response.StatusCode}: {responseBody}"); - - jObject = Newtonsoft.Json.Linq.JObject.Parse(responseBody); - - if ((int)response.StatusCode != 200) + using (HttpResponseMessage response = WlHttpClient.GetAsync(historicUrl.ToString()).Result) { - cumulus.LogMessage($"WeatherLink API Historic Error: {jObject.Value("code")}, {jObject.Value("message")}"); - Console.WriteLine($" - Error {jObject.Value("code")}: {jObject.Value("message")}"); - cumulus.LastUpdateTime = FromUnixTime(endTime); - return; - } + var responseBody = response.Content.ReadAsStringAsync().Result; + cumulus.LogDebugMessage($"WeatherLink API Historic Response: {response.StatusCode}: {responseBody}"); - if (responseBody == "{}") - { - cumulus.LogMessage("WeatherLink API Historic: No data was returned. Check your Device Id."); - Console.WriteLine(" - No historic data available"); - cumulus.LastUpdateTime = FromUnixTime(endTime); - return; - } - else if (responseBody.StartsWith("{\"sensors\":[{\"lsid\"")) // sanity check - { - // get the sensor data - sensorData = jObject["sensors"]; + jObject = Newtonsoft.Json.Linq.JObject.Parse(responseBody); - foreach (Newtonsoft.Json.Linq.JToken sensor in sensorData) + if ((int)response.StatusCode != 200) { - if (sensor.Value("sensor_type") != 504) - { - var recs = sensor["data"].Count(); - if (recs > noOfRecs) - noOfRecs = recs; - } + cumulus.LogMessage($"WeatherLink API Historic Error: {jObject.Value("code")}, {jObject.Value("message")}"); + Console.WriteLine($" - Error {jObject.Value("code")}: {jObject.Value("message")}"); + cumulus.LastUpdateTime = FromUnixTime(endTime); + return; } - if (noOfRecs == 0) + if (responseBody == "{}") { - cumulus.LogMessage("No historic data available"); + cumulus.LogMessage("WeatherLink API Historic: No data was returned. Check your Device Id."); Console.WriteLine(" - No historic data available"); cumulus.LastUpdateTime = FromUnixTime(endTime); return; } - else + else if (responseBody.StartsWith("{\"sensors\":[{\"lsid\"")) // sanity check + { + // get the sensor data + sensorData = jObject.GetValue("sensors"); + + foreach (Newtonsoft.Json.Linq.JToken sensor in sensorData) + { + if (sensor.Value("sensor_type") != 504) + { + var recs = sensor["data"].Count(); + if (recs > noOfRecs) + noOfRecs = recs; + } + } + + if (noOfRecs == 0) + { + cumulus.LogMessage("No historic data available"); + Console.WriteLine(" - No historic data available"); + cumulus.LastUpdateTime = FromUnixTime(endTime); + return; + } + else + { + cumulus.LogMessage($"Found {noOfRecs} historic records to process"); + } + } + else // No idea what we got, dump it to the log { - cumulus.LogMessage($"Found {noOfRecs} historic records to process"); + cumulus.LogMessage("Invalid historic message received"); + cumulus.LogDataMessage("Received: " + responseBody); + cumulus.LastUpdateTime = FromUnixTime(endTime); + return; } } - else // No idea what we got, dump it to the log - { - cumulus.LogMessage("Invalid historic message received"); - cumulus.LogDataMessage("Received: " + responseBody); - cumulus.LastUpdateTime = FromUnixTime(endTime); - return; - } } catch (Exception ex) { @@ -1527,7 +1523,7 @@ private void GetWlHistoricData() try { DateTime timestamp = new DateTime(); - foreach (Newtonsoft.Json.Linq.JToken sensor in sensorData) + foreach (var sensor in sensorData) { if (sensor.Value("sensor_type") != 504) { @@ -1609,9 +1605,6 @@ private void GetWlHistoricData() } } - sensorData = null; - jObject = null; - Console.WriteLine(""); // flush the progress line return; } @@ -1635,7 +1628,7 @@ private void DecodeHistoric(int dataType, int sensorType, JToken data) if (cumulus.WllPrimaryTempHum == txid) { /* - * Avaialble fields + * Avaialable fields * "cooling_degree_days" * "dew_point_hi_at" * "dew_point_hi" @@ -1844,6 +1837,14 @@ private void DecodeHistoric(int dataType, int sensorType, JToken data) // UV if (cumulus.WllPrimaryUV == txid) { + /* + * Available fields + * "uv_dose" + * "uv_index_avg" + * "uv_index_hi_at" + * "uv_index_hi" + * "uv_volt_last" + */ if (string.IsNullOrEmpty(data.Value("uv_index_avg"))) { cumulus.LogDebugMessage($"WL.com historic: no valid UV value found [{data.Value("uv_index_avg")}] on TxId {txid}"); @@ -1859,6 +1860,16 @@ private void DecodeHistoric(int dataType, int sensorType, JToken data) // Solar if (cumulus.WllPrimarySolar == txid) { + /* + * Available fields + * "solar_energy" + * "solar_rad_avg" + * "solar_rad_hi_at" + * "solar_rad_hi" + * "solar_rad_volt_last" + * "solar_volt_last" + * "et" + */ if (string.IsNullOrEmpty(data.Value("solar_rad_avg"))) { cumulus.LogDebugMessage($"WL.com historic: no valid Solar value found [{data.Value("solar_rad_avg")}] on TxId {txid}"); @@ -2131,6 +2142,11 @@ private void DecodeHistoric(int dataType, int sensorType, JToken data) ts = FromUnixTime(data.Value("ts")); DoPressure(ConvertPressINHGToUser(data.Value("bar_sea_level")), ts); DoPressTrend("Pressure trend"); + // Altimeter from absolute + StationPressure = ConvertPressINHGToUser(data.Value("bar_absolute")); + // Or do we use calibration? The VP2 code doesn't? + //StationPressure = ConvertPressINHGToUser(data.Value("bar_absolute")) * cumulus.PressMult + cumulus.PressOffset; + AltimeterPressure = ConvertPressMBToUser(StationToAltimeter(PressureHPa(StationPressure), AltitudeM(cumulus.Altitude))); } break; @@ -2458,8 +2474,9 @@ private async void GetWlHealth(object source, ElapsedEventArgs e) */ private void HealthTimerTick(object source, ElapsedEventArgs e) { - // Only run every 15 + 1 minutes + // Only run every 15 minutes // The WLL only reports its health every 15 mins, on the hour, :15, :30 and :45 + // We run at :01, :16, :31, :46 to allow time for wl.com to generate the stats if (DateTime.Now.Minute % 15 == 1) { GetWlHistoricHealth(); @@ -2539,43 +2556,45 @@ private void GetWlHistoricHealth() try { // we want to do this synchronously, so .Result - var response = WlHttpClient.GetAsync(historicUrl.ToString()).Result; - var responseBody = response.Content.ReadAsStringAsync().Result; - cumulus.LogDataMessage($"WLL Health: WeatherLink API Response: {response.StatusCode}: {responseBody}"); - - jObject = JObject.Parse(responseBody); - - if ((int)response.StatusCode != 200) + using (HttpResponseMessage response = WlHttpClient.GetAsync(historicUrl.ToString()).Result) { - cumulus.LogMessage($"WLL Health: WeatherLink API Error: {jObject.Value("code")}, {jObject.Value("message")}"); - return; - } + var responseBody = response.Content.ReadAsStringAsync().Result; + cumulus.LogDataMessage($"WLL Health: WeatherLink API Response: {response.StatusCode}: {responseBody}"); - if (responseBody == "{}") - { - cumulus.LogMessage("WLL Health: WeatherLink API: No data was returned. Check your Device Id."); - cumulus.LastUpdateTime = FromUnixTime(endTime); - return; - } - else if (responseBody.StartsWith("{\"sensors\":[{\"lsid\"")) // sanity check - { - // get the sensor data - sensorData = jObject["sensors"]; + jObject = JObject.Parse(responseBody); - if (sensorData.Count() == 0) + if ((int)response.StatusCode != 200) { - cumulus.LogMessage("WLL Health: No historic data available"); + cumulus.LogMessage($"WLL Health: WeatherLink API Error: {jObject.Value("code")}, {jObject.Value("message")}"); return; } - else + + if (responseBody == "{}") { - cumulus.LogDebugMessage($"WLL Health: Found {sensorData.Count()} sensor records to process"); + cumulus.LogMessage("WLL Health: WeatherLink API: No data was returned. Check your Device Id."); + cumulus.LastUpdateTime = FromUnixTime(endTime); + return; + } + else if (responseBody.StartsWith("{\"sensors\":[{\"lsid\"")) // sanity check + { + // get the sensor data + sensorData = jObject["sensors"]; + + if (sensorData.Count() == 0) + { + cumulus.LogMessage("WLL Health: No historic data available"); + return; + } + else + { + cumulus.LogDebugMessage($"WLL Health: Found {sensorData.Count()} sensor records to process"); + } + } + else // No idea what we got, dump it to the log + { + cumulus.LogMessage("WLL Health: Invalid historic message received"); + cumulus.LogDataMessage("WLL Health: Received: " + responseBody); } - } - else // No idea what we got, dump it to the log - { - cumulus.LogMessage("WLL Health: Invalid historic message received"); - cumulus.LogDataMessage("WLL Health: Received: " + responseBody); } } catch (Exception ex) diff --git a/CumulusMX/GW1000Station.cs b/CumulusMX/GW1000Station.cs index 1ef29418..51e11c22 100644 --- a/CumulusMX/GW1000Station.cs +++ b/CumulusMX/GW1000Station.cs @@ -328,7 +328,7 @@ public override void Start() tenMinuteChanged = true; lastMinute = DateTime.Now.Minute; - // Start a broadcast watchdog to warn if WLL broadcast messages are not being received + // Start a broadcast watchdog to warn if messages are not being received tmrDataWatchdog.Elapsed += DataTimeout; tmrDataWatchdog.Interval = 1000 * 30; // timeout after 30 seconds tmrDataWatchdog.AutoReset = true; diff --git a/CumulusMX/MeteoLib.cs b/CumulusMX/MeteoLib.cs index 5bea882a..f4607914 100644 --- a/CumulusMX/MeteoLib.cs +++ b/CumulusMX/MeteoLib.cs @@ -23,20 +23,31 @@ public static double WindChill(double tempC, double windSpeedKph) } /// - /// Calculates Apparent Temperature - /// See http://www.bom.gov.au/info/thermal_stress/#atapproximation + /// Calculates Apparent Temperature in Celcius /// + /// + /// See http://www.bom.gov.au/info/thermal_stress/#atapproximation + /// /// Temp in C /// Wind speed in m/s /// Relative humidity - /// + /// Apparent temperature in Celcius public static double ApparentTemperature(double tempC, double windspeedMS, int humidity) { double avp = (humidity/100.0)*6.105*Math.Exp(17.27*tempC/(237.7 + tempC)); // hPa return tempC + (0.33*avp) - (0.7*windspeedMS) - 4.0; } - // Joint Action Group for Temerature Indices (JAG/TI) formula + /// + /// Calculates the Feels Like temperature in Celcius + /// + /// + /// Joint Action Group for Temerature Indices (JAG/TI) formula + /// + /// Temp in C + /// Windspeed in kph + /// Relative humidity + /// Feels Like temperture in Celcius public static double FeelsLike(double tempC, double windSpeedKph, int humidity) { // Cannot use the WindChill function as we need the chill above 10 C @@ -107,6 +118,14 @@ public static double CalculateWetBulbC(double TempC, double DewPointC, double Pr // WBc = (((0.00066 * P ) * Tc ) + ((4098 * E ) / ( (Tdc + 237.7 ) ^ 2) * Tdc )) / ((0.00066 * P ) + (4098 * E ) / ( (Tdc + 237.7 ) ^ 2)) } + /// + /// Calculates the Saturated Vapour Pressure in hPa + /// + /// + /// Bolton(1980) + /// + /// Temp in C + /// SVP in hPa public static double SaturationVaporPressure(double tempC) { return 6.112*Math.Exp(17.67*tempC/(tempC + 243.5)); // Bolton(1980) @@ -125,6 +144,14 @@ public static double DewPoint(double tempC, double humidity) return ((243.12 * lnVapor) - 440.1) / (19.43 - lnVapor); } + /// + /// Calculates the Saturated Vapour Pressure in hPa + /// + /// + /// WMO - CIMO Guide - 2008 + /// + /// Temp in C + /// SVP in hPa public static double SaturationVapourPressure(double tempC) { return 6.112*Math.Exp((17.62*tempC)/(243.12 + tempC)); diff --git a/CumulusMX/Properties/AssemblyInfo.cs b/CumulusMX/Properties/AssemblyInfo.cs index e0ef1afb..411158f5 100644 --- a/CumulusMX/Properties/AssemblyInfo.cs +++ b/CumulusMX/Properties/AssemblyInfo.cs @@ -6,7 +6,7 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("CumulusMX")] -[assembly: AssemblyDescription("Build 3082")] +[assembly: AssemblyDescription("Build 3083")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("CumulusMX")] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("3.6.6.3082")] -[assembly: AssemblyFileVersion("3.6.6.3082")] +[assembly: AssemblyVersion("3.6.7.3083")] +[assembly: AssemblyFileVersion("3.6.7.3083")] diff --git a/CumulusMX/WeatherStation.cs b/CumulusMX/WeatherStation.cs index 77447c7b..1922c142 100644 --- a/CumulusMX/WeatherStation.cs +++ b/CumulusMX/WeatherStation.cs @@ -3536,6 +3536,11 @@ public void DoForecast(string forecast, bool hourly) public bool FirstForecastDone = false; + /// + /// Convert altitude from user units to metres + /// + /// + /// public double AltitudeM(double altitude) { if (cumulus.AltitudeInFeet) diff --git a/Updates.txt b/Updates.txt index da2d20af..76c2460a 100644 --- a/Updates.txt +++ b/Updates.txt @@ -1,15 +1,22 @@ +3.6.7 - b3083 +============= +- Add catches for real time MySQL updates and all real time file failures +- Adds Station (Absolute) and Altimeter pressure values for Davis WLL stations + +- Updated files + \CumulusMX.exe + + 3.6.6 - b3082 ============= - Change ini files to use 17 significant figures for decimal values (up from 15) - Fix for Davis WLL health data decoding when the WLL is LAN attached - Fix for real time SFTP not reconnecting after failure - - Updated files \CumulusMX.exe - 3.6.5 - b3081 ============= - Fix for sun rise/set and dawn/dusk calcuations when there is one event but not the other in a single day @@ -36,7 +43,6 @@ \interface\js\extrasensors.js - 3.6.3 - b3079 ============= - Reverts b3077 FluentFTP update