From ebf81e569cfb5c05e818e611defb44699e733ccf Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Thu, 8 Apr 2021 17:33:46 +0100 Subject: [PATCH 01/16] Basic Alarm Emails now working --- CumulusMX/AlarmSettings.cs | 93 +++- CumulusMX/Cumulus.cs | 804 +++++++++++++++++++++------------- CumulusMX/CumulusMX.csproj | 11 + CumulusMX/EmailSender.cs | 104 +++++ CumulusMX/InternetSettings.cs | 56 ++- CumulusMX/ProgramSettings.cs | 12 + CumulusMX/WeatherStation.cs | 2 +- CumulusMX/packages.config | 3 + CumulusMX/webtags.cs | 10 +- Updates.txt | 27 ++ 10 files changed, 788 insertions(+), 334 deletions(-) create mode 100644 CumulusMX/EmailSender.cs diff --git a/CumulusMX/AlarmSettings.cs b/CumulusMX/AlarmSettings.cs index 75dfe181..5b689b31 100644 --- a/CumulusMX/AlarmSettings.cs +++ b/CumulusMX/AlarmSettings.cs @@ -34,6 +34,7 @@ public string GetAlarmSettings() tempBelowSoundEnabled = cumulus.LowTempAlarm.Sound, tempBelowSound = cumulus.LowTempAlarm.SoundFile, tempBelowNotify = cumulus.LowTempAlarm.Notify, + tempBelowEmail = cumulus.LowTempAlarm.Email, tempBelowLatches = cumulus.LowTempAlarm.Latch, tempBelowLatchHrs = cumulus.LowTempAlarm.LatchHours, @@ -42,6 +43,7 @@ public string GetAlarmSettings() tempAboveSoundEnabled = cumulus.HighTempAlarm.Sound, tempAboveSound = cumulus.HighTempAlarm.SoundFile, tempAboveNotify = cumulus.HighTempAlarm.Notify, + tempAboveEmail = cumulus.HighTempAlarm.Email, tempAboveLatches = cumulus.HighTempAlarm.Latch, tempAboveLatchHrs = cumulus.HighTempAlarm.LatchHours, @@ -50,6 +52,7 @@ public string GetAlarmSettings() tempChangeSoundEnabled = cumulus.TempChangeAlarm.Sound, tempChangeSound = cumulus.TempChangeAlarm.SoundFile, tempChangeNotify = cumulus.TempChangeAlarm.Notify, + tempChangeEmail = cumulus.TempChangeAlarm.Email, tempChangeLatches = cumulus.TempChangeAlarm.Latch, tempChangeLatchHrs = cumulus.TempChangeAlarm.LatchHours, @@ -58,6 +61,7 @@ public string GetAlarmSettings() pressBelowSoundEnabled = cumulus.LowPressAlarm.Sound, pressBelowSound = cumulus.LowPressAlarm.SoundFile, pressBelowNotify = cumulus.LowPressAlarm.Notify, + pressBelowEmail = cumulus.LowPressAlarm.Email, pressBelowLatches = cumulus.LowPressAlarm.Latch, pressBelowLatchHrs = cumulus.LowPressAlarm.LatchHours, @@ -66,6 +70,7 @@ public string GetAlarmSettings() pressAboveSoundEnabled = cumulus.HighPressAlarm.Sound, pressAboveSound = cumulus.HighPressAlarm.SoundFile, pressAboveNotify = cumulus.HighPressAlarm.Notify, + pressAboveEmail = cumulus.HighPressAlarm.Email, pressAboveLatches = cumulus.HighPressAlarm.Latch, pressAboveLatchHrs = cumulus.HighPressAlarm.LatchHours, @@ -74,6 +79,7 @@ public string GetAlarmSettings() pressChangeSoundEnabled = cumulus.PressChangeAlarm.Sound, pressChangeSound = cumulus.PressChangeAlarm.SoundFile, pressChangeNotify = cumulus.PressChangeAlarm.Notify, + pressChangeEmail = cumulus.PressChangeAlarm.Email, pressChangeLatches = cumulus.PressChangeAlarm.Latch, pressChangeLatchHrs = cumulus.PressChangeAlarm.LatchHours, @@ -82,6 +88,7 @@ public string GetAlarmSettings() rainAboveSoundEnabled = cumulus.HighRainTodayAlarm.Sound, rainAboveSound = cumulus.HighRainTodayAlarm.SoundFile, rainAboveNotify = cumulus.HighRainTodayAlarm.Notify, + rainAboveEmail = cumulus.HighRainTodayAlarm.Email, rainAboveLatches = cumulus.HighRainTodayAlarm.Latch, rainAboveLatchHrs = cumulus.HighRainTodayAlarm.LatchHours, @@ -90,6 +97,7 @@ public string GetAlarmSettings() rainRateAboveSoundEnabled = cumulus.HighRainRateAlarm.Sound, rainRateAboveSound = cumulus.HighRainRateAlarm.SoundFile, rainRateAboveNotify = cumulus.HighRainRateAlarm.Notify, + rainRateAboveEmail = cumulus.HighRainRateAlarm.Email, rainRateAboveLatches = cumulus.HighRainRateAlarm.Latch, rainRateAboveLatchHrs = cumulus.HighRainRateAlarm.LatchHours, @@ -98,6 +106,7 @@ public string GetAlarmSettings() gustAboveSoundEnabled = cumulus.HighGustAlarm.Sound, gustAboveSound = cumulus.HighGustAlarm.SoundFile, gustAboveNotify = cumulus.HighGustAlarm.Notify, + gustAboveEmail = cumulus.HighGustAlarm.Email, gustAboveLatches = cumulus.HighGustAlarm.Latch, gustAboveLatchHrs = cumulus.HighGustAlarm.LatchHours, @@ -106,6 +115,7 @@ public string GetAlarmSettings() windAboveSoundEnabled = cumulus.HighWindAlarm.Sound, windAboveSound = cumulus.HighWindAlarm.SoundFile, windAboveNotify = cumulus.HighWindAlarm.Notify, + windAboveEmail = cumulus.HighWindAlarm.Email, windAboveLatches = cumulus.HighWindAlarm.Latch, windAboveLatchHrs = cumulus.HighWindAlarm.LatchHours, @@ -113,6 +123,7 @@ public string GetAlarmSettings() contactLostSoundEnabled = cumulus.SensorAlarm.Sound, contactLostSound = cumulus.SensorAlarm.SoundFile, contactLostNotify = cumulus.SensorAlarm.Notify, + contactLostEmail = cumulus.SensorAlarm.Email, contactLostLatches = cumulus.SensorAlarm.Latch, contactLostLatchHrs = cumulus.SensorAlarm.LatchHours, @@ -120,6 +131,7 @@ public string GetAlarmSettings() dataStoppedSoundEnabled = cumulus.DataStoppedAlarm.Sound, dataStoppedSound = cumulus.DataStoppedAlarm.SoundFile, dataStoppedNotify = cumulus.DataStoppedAlarm.Notify, + dataStoppedEmail = cumulus.DataStoppedAlarm.Email, dataStoppedLatches = cumulus.DataStoppedAlarm.Latch, dataStoppedLatchHrs = cumulus.DataStoppedAlarm.LatchHours, @@ -127,6 +139,7 @@ public string GetAlarmSettings() batteryLowSoundEnabled = cumulus.BatteryLowAlarm.Sound, batteryLowSound = cumulus.BatteryLowAlarm.SoundFile, batteryLowNotify = cumulus.BatteryLowAlarm.Notify, + batteryLowEmail = cumulus.BatteryLowAlarm.Email, batteryLowLatches = cumulus.BatteryLowAlarm.Latch, batteryLowLatchHrs = cumulus.BatteryLowAlarm.LatchHours, @@ -134,6 +147,7 @@ public string GetAlarmSettings() spikeSoundEnabled = cumulus.SpikeAlarm.Sound, spikeSound = cumulus.SpikeAlarm.SoundFile, spikeNotify = cumulus.SpikeAlarm.Notify, + spikeEmail = cumulus.SpikeAlarm.Email, spikeLatches = cumulus.SpikeAlarm.Latch, spikeLatchHrs = cumulus.SpikeAlarm.LatchHours, @@ -141,14 +155,22 @@ public string GetAlarmSettings() upgradeSoundEnabled = cumulus.UpgradeAlarm.Sound, upgradeSound = cumulus.UpgradeAlarm.SoundFile, upgradeNotify = cumulus.UpgradeAlarm.Notify, + upgradeEmail = cumulus.UpgradeAlarm.Email, upgradeLatches = cumulus.UpgradeAlarm.Latch, - upgradeLatchHrs = cumulus.UpgradeAlarm.LatchHours, + upgradeLatchHrs = cumulus.UpgradeAlarm.LatchHours + }; + + var email = new JsonAlarmEmail() + { + fromEmail = cumulus.AlarmFromEmail, + destEmail = cumulus.AlarmDestEmail.Join(";") }; var retObject = new JsonAlarmSettings() { data = data, - units = alarmUnits + units = alarmUnits, + email = email }; return retObject.ToJson(); @@ -167,7 +189,8 @@ public string UpdateAlarmSettings(IHttpContext context) //var settings = JsonConvert.DeserializeObject(json); //var settings = JsonSerializer.DeserializeFromString(json); - var settings = json.FromJson(); + var result = json.FromJson(); + var settings = result.data; // process the settings cumulus.LogMessage("Updating Alarm settings"); @@ -176,6 +199,7 @@ public string UpdateAlarmSettings(IHttpContext context) cumulus.LowTempAlarm.Sound = settings.tempBelowSoundEnabled; cumulus.LowTempAlarm.SoundFile = settings.tempBelowSound; cumulus.LowTempAlarm.Notify = settings.tempBelowNotify; + cumulus.LowTempAlarm.Email = settings.tempBelowEmail; cumulus.LowTempAlarm.Latch = settings.tempBelowLatches; cumulus.LowTempAlarm.LatchHours = settings.tempBelowLatchHrs; @@ -185,6 +209,7 @@ public string UpdateAlarmSettings(IHttpContext context) cumulus.HighTempAlarm.Sound = settings.tempAboveSoundEnabled; cumulus.HighTempAlarm.SoundFile = settings.tempAboveSound; cumulus.HighTempAlarm.Notify = settings.tempAboveNotify; + cumulus.HighTempAlarm.Email = settings.tempAboveEmail; cumulus.HighTempAlarm.Latch = settings.tempAboveLatches; cumulus.HighTempAlarm.LatchHours = settings.tempAboveLatchHrs; @@ -193,6 +218,7 @@ public string UpdateAlarmSettings(IHttpContext context) cumulus.TempChangeAlarm.Sound = settings.tempChangeSoundEnabled; cumulus.TempChangeAlarm.SoundFile = settings.tempChangeSound; cumulus.TempChangeAlarm.Notify = settings.tempChangeNotify; + cumulus.TempChangeAlarm.Email = settings.tempChangeEmail; cumulus.TempChangeAlarm.Latch = settings.tempChangeLatches; cumulus.TempChangeAlarm.LatchHours = settings.tempChangeLatchHrs; @@ -201,6 +227,7 @@ public string UpdateAlarmSettings(IHttpContext context) cumulus.LowPressAlarm.Sound = settings.pressBelowSoundEnabled; cumulus.LowPressAlarm.SoundFile = settings.pressBelowSound; cumulus.LowPressAlarm.Notify = settings.pressBelowNotify; + cumulus.LowPressAlarm.Email = settings.pressBelowEmail; cumulus.LowPressAlarm.Latch = settings.pressBelowLatches; cumulus.LowPressAlarm.LatchHours = settings.pressBelowLatchHrs; @@ -209,6 +236,7 @@ public string UpdateAlarmSettings(IHttpContext context) cumulus.HighPressAlarm.Sound = settings.pressAboveSoundEnabled; cumulus.HighPressAlarm.SoundFile = settings.pressAboveSound; cumulus.HighPressAlarm.Notify = settings.pressAboveNotify; + cumulus.HighPressAlarm.Email = settings.pressAboveEmail; cumulus.HighPressAlarm.Latch = settings.pressAboveLatches; cumulus.HighPressAlarm.LatchHours = settings.pressAboveLatchHrs; @@ -217,6 +245,7 @@ public string UpdateAlarmSettings(IHttpContext context) cumulus.PressChangeAlarm.Sound = settings.pressChangeSoundEnabled; cumulus.PressChangeAlarm.SoundFile = settings.pressChangeSound; cumulus.PressChangeAlarm.Notify = settings.pressChangeNotify; + cumulus.PressChangeAlarm.Email = settings.pressChangeEmail; cumulus.PressChangeAlarm.Latch = settings.pressChangeLatches; cumulus.PressChangeAlarm.LatchHours = settings.pressChangeLatchHrs; @@ -225,6 +254,7 @@ public string UpdateAlarmSettings(IHttpContext context) cumulus.HighRainTodayAlarm.Sound = settings.rainAboveSoundEnabled; cumulus.HighRainTodayAlarm.SoundFile = settings.rainAboveSound; cumulus.HighRainTodayAlarm.Notify = settings.rainAboveNotify; + cumulus.HighRainTodayAlarm.Email = settings.rainAboveEmail; cumulus.HighRainTodayAlarm.Latch = settings.rainAboveLatches; cumulus.HighRainTodayAlarm.LatchHours = settings.rainAboveLatchHrs; @@ -233,6 +263,7 @@ public string UpdateAlarmSettings(IHttpContext context) cumulus.HighRainRateAlarm.Sound = settings.rainRateAboveSoundEnabled; cumulus.HighRainRateAlarm.SoundFile = settings.rainRateAboveSound; cumulus.HighRainRateAlarm.Notify = settings.rainRateAboveNotify; + cumulus.HighRainRateAlarm.Email = settings.rainRateAboveEmail; cumulus.HighRainRateAlarm.Latch = settings.rainRateAboveLatches; cumulus.HighRainRateAlarm.LatchHours = settings.rainRateAboveLatchHrs; @@ -241,6 +272,7 @@ public string UpdateAlarmSettings(IHttpContext context) cumulus.HighGustAlarm.Sound = settings.gustAboveSoundEnabled; cumulus.HighGustAlarm.SoundFile = settings.gustAboveSound; cumulus.HighGustAlarm.Notify = settings.gustAboveNotify; + cumulus.HighGustAlarm.Email = settings.gustAboveEmail; cumulus.HighGustAlarm.Latch = settings.gustAboveLatches; cumulus.HighGustAlarm.LatchHours = settings.gustAboveLatchHrs; @@ -249,6 +281,7 @@ public string UpdateAlarmSettings(IHttpContext context) cumulus.HighWindAlarm.Sound = settings.windAboveSoundEnabled; cumulus.HighWindAlarm.SoundFile = settings.windAboveSound; cumulus.HighWindAlarm.Notify = settings.windAboveNotify; + cumulus.HighWindAlarm.Email = settings.windAboveEmail; cumulus.HighWindAlarm.Latch = settings.windAboveLatches; cumulus.HighWindAlarm.LatchHours = settings.windAboveLatchHrs; @@ -256,6 +289,7 @@ public string UpdateAlarmSettings(IHttpContext context) cumulus.SensorAlarm.Sound = settings.contactLostSoundEnabled; cumulus.SensorAlarm.SoundFile = settings.contactLostSound; cumulus.SensorAlarm.Notify = settings.contactLostNotify; + cumulus.SensorAlarm.Email = settings.contactLostEmail; cumulus.SensorAlarm.Latch = settings.contactLostLatches; cumulus.SensorAlarm.LatchHours = settings.contactLostLatchHrs; @@ -263,6 +297,7 @@ public string UpdateAlarmSettings(IHttpContext context) cumulus.DataStoppedAlarm.Sound = settings.dataStoppedSoundEnabled; cumulus.DataStoppedAlarm.SoundFile = settings.dataStoppedSound; cumulus.DataStoppedAlarm.Notify = settings.dataStoppedNotify; + cumulus.DataStoppedAlarm.Email = settings.dataStoppedEmail; cumulus.DataStoppedAlarm.Latch = settings.dataStoppedLatches; cumulus.DataStoppedAlarm.LatchHours = settings.dataStoppedLatchHrs; @@ -270,6 +305,7 @@ public string UpdateAlarmSettings(IHttpContext context) cumulus.BatteryLowAlarm.Sound = settings.batteryLowSoundEnabled; cumulus.BatteryLowAlarm.SoundFile = settings.batteryLowSound; cumulus.BatteryLowAlarm.Notify = settings.batteryLowNotify; + cumulus.BatteryLowAlarm.Email = settings.batteryLowEmail; cumulus.BatteryLowAlarm.Latch = settings.batteryLowLatches; cumulus.BatteryLowAlarm.LatchHours = settings.batteryLowLatchHrs; @@ -277,6 +313,7 @@ public string UpdateAlarmSettings(IHttpContext context) cumulus.SpikeAlarm.Sound = settings.spikeSoundEnabled; cumulus.SpikeAlarm.SoundFile = settings.spikeSound; cumulus.SpikeAlarm.Notify = settings.spikeNotify; + cumulus.SpikeAlarm.Email = settings.spikeEmail; cumulus.SpikeAlarm.Latch = settings.spikeLatches; cumulus.SpikeAlarm.LatchHours = settings.spikeLatchHrs; @@ -284,9 +321,36 @@ public string UpdateAlarmSettings(IHttpContext context) cumulus.UpgradeAlarm.Sound = settings.upgradeSoundEnabled; cumulus.UpgradeAlarm.SoundFile = settings.upgradeSound; cumulus.UpgradeAlarm.Notify = settings.upgradeNotify; + cumulus.UpgradeAlarm.Email = settings.upgradeEmail; cumulus.UpgradeAlarm.Latch = settings.upgradeLatches; cumulus.UpgradeAlarm.LatchHours = settings.upgradeLatchHrs; + // validate the from email + if (!EmailSender.CheckEmailAddress(result.email.fromEmail.Trim())) + { + var msg = "ERROR: Invalid Alarm from email address entered"; + cumulus.LogMessage(msg); + context.Response.StatusCode = 500; + return msg; + } + cumulus.AlarmFromEmail = result.email.fromEmail.Trim(); + + // validate the destination email(s) + var emails = result.email.destEmail.Trim().Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + for (var i = 0; i < emails.Length; i++) + { + emails[i] = emails[i].Trim(); + if (!EmailSender.CheckEmailAddress(emails[i])) + { + var msg = "ERROR: Invalid Alarm destination email address entered"; + cumulus.LogMessage(msg); + context.Response.StatusCode = 500; + return msg; + } + } + + cumulus.AlarmDestEmail = emails; + // Save the settings cumulus.WriteIniFile(); @@ -309,6 +373,7 @@ public class JsonAlarmSettingsData public bool tempBelowSoundEnabled { get; set; } public string tempBelowSound { get; set; } public bool tempBelowNotify { get; set; } + public bool tempBelowEmail { get; set; } public bool tempBelowLatches { get; set; } public int tempBelowLatchHrs { get; set; } @@ -317,6 +382,7 @@ public class JsonAlarmSettingsData public bool tempAboveSoundEnabled { get; set; } public string tempAboveSound { get; set; } public bool tempAboveNotify { get; set; } + public bool tempAboveEmail { get; set; } public bool tempAboveLatches { get; set; } public int tempAboveLatchHrs { get; set; } @@ -324,7 +390,8 @@ public class JsonAlarmSettingsData public double tempChangeVal { get; set; } public bool tempChangeSoundEnabled { get; set; } public string tempChangeSound { get; set; } - public bool tempChangeNotify{ get; set; } + public bool tempChangeNotify { get; set; } + public bool tempChangeEmail { get; set; } public bool tempChangeLatches { get; set; } public int tempChangeLatchHrs { get; set; } @@ -333,6 +400,7 @@ public class JsonAlarmSettingsData public bool pressBelowSoundEnabled { get; set; } public string pressBelowSound { get; set; } public bool pressBelowNotify { get; set; } + public bool pressBelowEmail { get; set; } public bool pressBelowLatches { get; set; } public int pressBelowLatchHrs { get; set; } @@ -341,6 +409,7 @@ public class JsonAlarmSettingsData public bool pressAboveSoundEnabled { get; set; } public string pressAboveSound { get; set; } public bool pressAboveNotify { get; set; } + public bool pressAboveEmail { get; set; } public bool pressAboveLatches { get; set; } public int pressAboveLatchHrs { get; set; } @@ -349,6 +418,7 @@ public class JsonAlarmSettingsData public bool pressChangeSoundEnabled { get; set; } public string pressChangeSound { get; set; } public bool pressChangeNotify { get; set; } + public bool pressChangeEmail { get; set; } public bool pressChangeLatches { get; set; } public int pressChangeLatchHrs { get; set; } @@ -357,6 +427,7 @@ public class JsonAlarmSettingsData public bool rainAboveSoundEnabled { get; set; } public string rainAboveSound { get; set; } public bool rainAboveNotify { get; set; } + public bool rainAboveEmail { get; set; } public bool rainAboveLatches { get; set; } public int rainAboveLatchHrs { get; set; } @@ -365,6 +436,7 @@ public class JsonAlarmSettingsData public bool rainRateAboveSoundEnabled { get; set; } public string rainRateAboveSound { get; set; } public bool rainRateAboveNotify { get; set; } + public bool rainRateAboveEmail { get; set; } public bool rainRateAboveLatches { get; set; } public int rainRateAboveLatchHrs { get; set; } @@ -373,6 +445,7 @@ public class JsonAlarmSettingsData public bool gustAboveSoundEnabled { get; set; } public string gustAboveSound { get; set; } public bool gustAboveNotify { get; set; } + public bool gustAboveEmail { get; set; } public bool gustAboveLatches { get; set; } public int gustAboveLatchHrs { get; set; } @@ -381,6 +454,7 @@ public class JsonAlarmSettingsData public bool windAboveSoundEnabled { get; set; } public string windAboveSound { get; set; } public bool windAboveNotify { get; set; } + public bool windAboveEmail { get; set; } public bool windAboveLatches { get; set; } public int windAboveLatchHrs { get; set; } @@ -388,6 +462,7 @@ public class JsonAlarmSettingsData public bool contactLostSoundEnabled { get; set; } public string contactLostSound { get; set; } public bool contactLostNotify { get; set; } + public bool contactLostEmail { get; set; } public bool contactLostLatches { get; set; } public int contactLostLatchHrs { get; set; } @@ -395,6 +470,7 @@ public class JsonAlarmSettingsData public bool dataStoppedSoundEnabled { get; set; } public string dataStoppedSound { get; set; } public bool dataStoppedNotify { get; set; } + public bool dataStoppedEmail { get; set; } public bool dataStoppedLatches { get; set; } public int dataStoppedLatchHrs { get; set; } @@ -402,6 +478,7 @@ public class JsonAlarmSettingsData public bool batteryLowSoundEnabled { get; set; } public string batteryLowSound { get; set; } public bool batteryLowNotify { get; set; } + public bool batteryLowEmail { get; set; } public bool batteryLowLatches { get; set; } public int batteryLowLatchHrs { get; set; } @@ -409,6 +486,7 @@ public class JsonAlarmSettingsData public bool spikeSoundEnabled { get; set; } public string spikeSound { get; set; } public bool spikeNotify { get; set; } + public bool spikeEmail { get; set; } public bool spikeLatches { get; set; } public int spikeLatchHrs { get; set; } @@ -416,9 +494,15 @@ public class JsonAlarmSettingsData public bool upgradeSoundEnabled { get; set; } public string upgradeSound { get; set; } public bool upgradeNotify { get; set; } + public bool upgradeEmail { get; set; } public bool upgradeLatches { get; set; } public int upgradeLatchHrs { get; set; } + } + public class JsonAlarmEmail + { + public string fromEmail { get; set; } + public string destEmail { get; set; } } public class JsonAlarmUnits @@ -433,5 +517,6 @@ public class JsonAlarmSettings { public JsonAlarmSettingsData data { get; set; } public JsonAlarmUnits units { get; set; } + public JsonAlarmEmail email { get; set; } } } diff --git a/CumulusMX/Cumulus.cs b/CumulusMX/Cumulus.cs index 73236ffa..aea1f2de 100644 --- a/CumulusMX/Cumulus.cs +++ b/CumulusMX/Cumulus.cs @@ -491,6 +491,14 @@ public struct TExtraFiles public DisplayOptions DisplayOptions = new DisplayOptions(); + public EmailSender emailer; + public EmailSender.SmtpOptions SmtpOptions = new EmailSender.SmtpOptions(); + + public string AlarmEmailPreamble; + public string AlarmEmailSubject; + public string AlarmFromEmail; + public string[] AlarmDestEmail; + public bool ListWebTags; public bool RealtimeEnabled; // The timer is to be started @@ -1104,8 +1112,6 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) LogMessage("No start-up delay - disabled"); } - GetLatestVersion(); - GC.Collect(); LogMessage("Data path = " + Datapath); @@ -1238,6 +1244,11 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) customHttpMinutesTokenParser.OnToken += TokenParserOnToken; customHttpRolloverTokenParser.OnToken += TokenParserOnToken; + if (SmtpOptions.Enabled) + { + emailer = new EmailSender(this); + } + DoSunriseAndSunset(); DoMoonPhase(); MoonAge = MoonriseMoonset.MoonAge(); @@ -1256,6 +1267,35 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) LogPrimaryAqSensor(); + // initialise the alarms + DataStoppedAlarm.cumulus = this; + BatteryLowAlarm.cumulus = this; + SensorAlarm.cumulus = this; + SpikeAlarm.cumulus = this; + HighWindAlarm.cumulus = this; + HighWindAlarm.Units = Units.WindText; + HighGustAlarm.cumulus = this; + HighGustAlarm.Units = Units.WindText; + HighRainRateAlarm.cumulus = this; + HighRainRateAlarm.Units = Units.RainTrendText; + HighRainTodayAlarm.cumulus = this; + HighRainTodayAlarm.Units = Units.RainText; + PressChangeAlarm.cumulus = this; + PressChangeAlarm.Units = Units.PressTrendText; + HighPressAlarm.cumulus = this; + HighPressAlarm.Units = Units.PressText; + LowPressAlarm.cumulus = this; + LowPressAlarm.Units = Units.PressText; + TempChangeAlarm.cumulus = this; + TempChangeAlarm.Units = Units.TempTrendText; + HighTempAlarm.cumulus = this; + HighTempAlarm.Units = Units.TempText; + LowTempAlarm.cumulus = this; + LowTempAlarm.Units = Units.TempText; + UpgradeAlarm.cumulus = this; + + GetLatestVersion(); + LogMessage("Cumulus Starting"); // switch off logging from Unosquare.Swan which underlies embedIO @@ -2287,14 +2327,16 @@ internal OpenWeatherMapStation[] GetOpenWeatherMapStations() // Create a new OpenWeatherMap station internal void CreateOpenWeatherMapStation() { + var invC = new CultureInfo(""); + string url = "http://api.openweathermap.org/data/3.0/stations?appid=" + OpenWeatherMap.PW; try { var datestr = DateTime.Now.ToUniversalTime().ToString("yyMMddHHmm"); StringBuilder sb = new StringBuilder($"{{\"external_id\":\"CMX-{datestr}\","); sb.Append($"\"name\":\"{LocationName}\","); - sb.Append($"\"latitude\":{Latitude},"); - sb.Append($"\"longitude\":{Longitude},"); + sb.Append($"\"latitude\":{Latitude.ToString(invC)},"); + sb.Append($"\"longitude\":{Longitude.ToString(invC)},"); sb.Append($"\"altitude\":{(int)station.AltitudeM(Altitude)}}}"); LogMessage($"OpenWeatherMap: Creating new station"); @@ -3401,11 +3443,13 @@ private void ReadIniFile() { ProgramOptions.DebugLogging = true; ProgramOptions.DataLogging = true; + SmtpOptions.Logging = true; } else { ProgramOptions.DebugLogging = ini.GetValue("Station", "Logging", false); ProgramOptions.DataLogging = ini.GetValue("Station", "DataLogging", false); + SmtpOptions.Logging = ini.GetValue("SMTP", "Logging", false); } ComportName = ini.GetValue("Station", "ComportName", DefaultComportName); @@ -4056,6 +4100,7 @@ private void ReadIniFile() LowTempAlarm.SoundFile = ini.GetValue("Alarms", "LowTempAlarmSoundFile", DefaultSoundFile); if (LowTempAlarm.SoundFile.Contains(DefaultSoundFileOld)) LowTempAlarm.SoundFile = DefaultSoundFile; LowTempAlarm.Notify = ini.GetValue("Alarms", "LowTempAlarmNotify", false); + LowTempAlarm.Email = ini.GetValue("Alarms", "LowTempAlarmEmail", false); LowTempAlarm.Latch = ini.GetValue("Alarms", "LowTempAlarmLatch", false); LowTempAlarm.LatchHours = ini.GetValue("Alarms", "LowTempAlarmLatchHours", 24); @@ -4065,6 +4110,7 @@ private void ReadIniFile() HighTempAlarm.SoundFile = ini.GetValue("Alarms", "HighTempAlarmSoundFile", DefaultSoundFile); if (HighTempAlarm.SoundFile.Contains(DefaultSoundFileOld)) HighTempAlarm.SoundFile = DefaultSoundFile; HighTempAlarm.Notify = ini.GetValue("Alarms", "HighTempAlarmNotify", false); + HighTempAlarm.Email = ini.GetValue("Alarms", "HighTempAlarmEmail", false); HighTempAlarm.Latch = ini.GetValue("Alarms", "HighTempAlarmLatch", false); HighTempAlarm.LatchHours = ini.GetValue("Alarms", "HighTempAlarmLatchHours", 24); @@ -4074,6 +4120,7 @@ private void ReadIniFile() TempChangeAlarm.SoundFile = ini.GetValue("Alarms", "TempChangeAlarmSoundFile", DefaultSoundFile); if (TempChangeAlarm.SoundFile.Contains(DefaultSoundFileOld)) TempChangeAlarm.SoundFile = DefaultSoundFile; TempChangeAlarm.Notify = ini.GetValue("Alarms", "TempChangeAlarmNotify", false); + TempChangeAlarm.Email = ini.GetValue("Alarms", "TempChangeAlarmEmail", false); TempChangeAlarm.Latch = ini.GetValue("Alarms", "TempChangeAlarmLatch", false); TempChangeAlarm.LatchHours = ini.GetValue("Alarms", "TempChangeAlarmLatchHours", 24); @@ -4083,6 +4130,7 @@ private void ReadIniFile() LowPressAlarm.SoundFile = ini.GetValue("Alarms", "LowPressAlarmSoundFile", DefaultSoundFile); if (LowPressAlarm.SoundFile.Contains(DefaultSoundFileOld)) LowPressAlarm.SoundFile = DefaultSoundFile; LowPressAlarm.Notify = ini.GetValue("Alarms", "LowPressAlarmNotify", false); + LowPressAlarm.Email = ini.GetValue("Alarms", "LowPressAlarmEmail", false); LowPressAlarm.Latch = ini.GetValue("Alarms", "LowPressAlarmLatch", false); LowPressAlarm.LatchHours = ini.GetValue("Alarms", "LowPressAlarmLatchHours", 24); @@ -4092,6 +4140,7 @@ private void ReadIniFile() HighPressAlarm.SoundFile = ini.GetValue("Alarms", "HighPressAlarmSoundFile", DefaultSoundFile); if (HighPressAlarm.SoundFile.Contains(DefaultSoundFileOld)) HighPressAlarm.SoundFile = DefaultSoundFile; HighPressAlarm.Notify = ini.GetValue("Alarms", "HighPressAlarmNotify", false); + HighPressAlarm.Email = ini.GetValue("Alarms", "HighPressAlarmEmail", false); HighPressAlarm.Latch = ini.GetValue("Alarms", "HighPressAlarmLatch", false); HighPressAlarm.LatchHours = ini.GetValue("Alarms", "HighPressAlarmLatchHours", 24); @@ -4101,6 +4150,7 @@ private void ReadIniFile() PressChangeAlarm.SoundFile = ini.GetValue("Alarms", "PressChangeAlarmSoundFile", DefaultSoundFile); if (PressChangeAlarm.SoundFile.Contains(DefaultSoundFileOld)) PressChangeAlarm.SoundFile = DefaultSoundFile; PressChangeAlarm.Notify = ini.GetValue("Alarms", "PressChangeAlarmNotify", false); + PressChangeAlarm.Email = ini.GetValue("Alarms", "PressChangeAlarmEmail", false); PressChangeAlarm.Latch = ini.GetValue("Alarms", "PressChangeAlarmLatch", false); PressChangeAlarm.LatchHours = ini.GetValue("Alarms", "PressChangeAlarmLatchHours", 24); @@ -4110,6 +4160,7 @@ private void ReadIniFile() HighRainTodayAlarm.SoundFile = ini.GetValue("Alarms", "HighRainTodayAlarmSoundFile", DefaultSoundFile); if (HighRainTodayAlarm.SoundFile.Contains(DefaultSoundFileOld)) HighRainTodayAlarm.SoundFile = DefaultSoundFile; HighRainTodayAlarm.Notify = ini.GetValue("Alarms", "HighRainTodayAlarmNotify", false); + HighRainTodayAlarm.Email = ini.GetValue("Alarms", "HighRainTodayAlarmEmail", false); HighRainTodayAlarm.Latch = ini.GetValue("Alarms", "HighRainTodayAlarmLatch", false); HighRainTodayAlarm.LatchHours = ini.GetValue("Alarms", "HighRainTodayAlarmLatchHours", 24); @@ -4119,6 +4170,7 @@ private void ReadIniFile() HighRainRateAlarm.SoundFile = ini.GetValue("Alarms", "HighRainRateAlarmSoundFile", DefaultSoundFile); if (HighRainRateAlarm.SoundFile.Contains(DefaultSoundFileOld)) HighRainRateAlarm.SoundFile = DefaultSoundFile; HighRainRateAlarm.Notify = ini.GetValue("Alarms", "HighRainRateAlarmNotify", false); + HighRainRateAlarm.Email = ini.GetValue("Alarms", "HighRainRateAlarmEmail", false); HighRainRateAlarm.Latch = ini.GetValue("Alarms", "HighRainRateAlarmLatch", false); HighRainRateAlarm.LatchHours = ini.GetValue("Alarms", "HighRainRateAlarmLatchHours", 24); @@ -4128,6 +4180,7 @@ private void ReadIniFile() HighGustAlarm.SoundFile = ini.GetValue("Alarms", "HighGustAlarmSoundFile", DefaultSoundFile); if (HighGustAlarm.SoundFile.Contains(DefaultSoundFileOld)) HighGustAlarm.SoundFile = DefaultSoundFile; HighGustAlarm.Notify = ini.GetValue("Alarms", "HighGustAlarmNotify", false); + HighGustAlarm.Email = ini.GetValue("Alarms", "HighGustAlarmEmail", false); HighGustAlarm.Latch = ini.GetValue("Alarms", "HighGustAlarmLatch", false); HighGustAlarm.LatchHours = ini.GetValue("Alarms", "HighGustAlarmLatchHours", 24); @@ -4137,6 +4190,7 @@ private void ReadIniFile() HighWindAlarm.SoundFile = ini.GetValue("Alarms", "HighWindAlarmSoundFile", DefaultSoundFile); if (HighWindAlarm.SoundFile.Contains(DefaultSoundFileOld)) HighWindAlarm.SoundFile = DefaultSoundFile; HighWindAlarm.Notify = ini.GetValue("Alarms", "HighWindAlarmNotify", false); + HighWindAlarm.Email = ini.GetValue("Alarms", "HighWindAlarmEmail", false); HighWindAlarm.Latch = ini.GetValue("Alarms", "HighWindAlarmLatch", false); HighWindAlarm.LatchHours = ini.GetValue("Alarms", "HighWindAlarmLatchHours", 24); @@ -4145,6 +4199,7 @@ private void ReadIniFile() SensorAlarm.SoundFile = ini.GetValue("Alarms", "SensorAlarmSoundFile", DefaultSoundFile); if (SensorAlarm.SoundFile.Contains(DefaultSoundFileOld)) SensorAlarm.SoundFile = DefaultSoundFile; SensorAlarm.Notify = ini.GetValue("Alarms", "SensorAlarmNotify", false); + SensorAlarm.Email = ini.GetValue("Alarms", "SensorAlarmEmail", false); SensorAlarm.Latch = ini.GetValue("Alarms", "SensorAlarmLatch", false); SensorAlarm.LatchHours = ini.GetValue("Alarms", "SensorAlarmLatchHours", 24); @@ -4153,6 +4208,7 @@ private void ReadIniFile() DataStoppedAlarm.SoundFile = ini.GetValue("Alarms", "DataStoppedAlarmSoundFile", DefaultSoundFile); if (DataStoppedAlarm.SoundFile.Contains(DefaultSoundFileOld)) SensorAlarm.SoundFile = DefaultSoundFile; DataStoppedAlarm.Notify = ini.GetValue("Alarms", "DataStoppedAlarmNotify", false); + DataStoppedAlarm.Email = ini.GetValue("Alarms", "DataStoppedAlarmEmail", false); DataStoppedAlarm.Latch = ini.GetValue("Alarms", "DataStoppedAlarmLatch", false); DataStoppedAlarm.LatchHours = ini.GetValue("Alarms", "DataStoppedAlarmLatchHours", 24); @@ -4160,6 +4216,7 @@ private void ReadIniFile() BatteryLowAlarm.Sound = ini.GetValue("Alarms", "BatteryLowAlarmSound", false); BatteryLowAlarm.SoundFile = ini.GetValue("Alarms", "BatteryLowAlarmSoundFile", DefaultSoundFile); BatteryLowAlarm.Notify = ini.GetValue("Alarms", "BatteryLowAlarmNotify", false); + BatteryLowAlarm.Email = ini.GetValue("Alarms", "BatteryLowAlarmEmail", false); BatteryLowAlarm.Latch = ini.GetValue("Alarms", "BatteryLowAlarmLatch", false); BatteryLowAlarm.LatchHours = ini.GetValue("Alarms", "BatteryLowAlarmLatchHours", 24); @@ -4167,6 +4224,7 @@ private void ReadIniFile() SpikeAlarm.Sound = ini.GetValue("Alarms", "DataSpikeAlarmSound", false); SpikeAlarm.SoundFile = ini.GetValue("Alarms", "DataSpikeAlarmSoundFile", DefaultSoundFile); SpikeAlarm.Notify = ini.GetValue("Alarms", "SpikeAlarmNotify", true); + SpikeAlarm.Email = ini.GetValue("Alarms", "SpikeAlarmEmail", true); SpikeAlarm.Latch = ini.GetValue("Alarms", "SpikeAlarmLatch", true); SpikeAlarm.LatchHours = ini.GetValue("Alarms", "SpikeAlarmLatchHours", 24); @@ -4174,9 +4232,13 @@ private void ReadIniFile() UpgradeAlarm.Sound = ini.GetValue("Alarms", "UpgradeAlarmSound", true); UpgradeAlarm.SoundFile = ini.GetValue("Alarms", "UpgradeAlarmSoundFile", DefaultSoundFile); UpgradeAlarm.Notify = ini.GetValue("Alarms", "UpgradeAlarmNotify", true); + UpgradeAlarm.Email = ini.GetValue("Alarms", "UpgradeAlarmEmail", false); UpgradeAlarm.Latch = ini.GetValue("Alarms", "UpgradeAlarmLatch", false); UpgradeAlarm.LatchHours = ini.GetValue("Alarms", "UpgradeAlarmLatchHours", 24); + AlarmFromEmail = ini.GetValue("Alarms", "FromEmail", ""); + AlarmDestEmail = ini.GetValue("Alarms", "DestEmail", "").Split(';'); + Calib.Press.Offset = ini.GetValue("Offsets", "PressOffset", 0.0); Calib.Temp.Offset = ini.GetValue("Offsets", "TempOffset", 0.0); Calib.Hum.Offset = ini.GetValue("Offsets", "HumOffset", 0); @@ -4385,6 +4447,15 @@ private void ReadIniFile() SelectaChartOptions.series[i] = ini.GetValue("Select-a-Chart", "Series" + i, "0"); SelectaChartOptions.colours[i] = ini.GetValue("Select-a-Chart", "Colour" + i, ""); } + + // Email settings + SmtpOptions.Enabled = ini.GetValue("SMTP", "Enabled", false); + SmtpOptions.Server = ini.GetValue("SMTP", "ServerName", ""); + SmtpOptions.Port = ini.GetValue("SMTP", "Port", 587); + SmtpOptions.UseSsl = ini.GetValue("SMTP", "UseSSL", false); + SmtpOptions.RequiresAuthentication = ini.GetValue("SMTP", "RequiresAuthentication", false); + SmtpOptions.User = ini.GetValue("SMTP", "User", ""); + SmtpOptions.Password = ini.GetValue("SMTP", "Password", ""); } internal void WriteIniFile() @@ -4770,6 +4841,7 @@ internal void WriteIniFile() ini.SetValue("Alarms", "LowTempAlarmSound", LowTempAlarm.Sound); ini.SetValue("Alarms", "LowTempAlarm.SoundFile", LowTempAlarm.SoundFile); ini.SetValue("Alarms", "LowTempAlarmNotify", LowTempAlarm.Notify); + ini.SetValue("Alarms", "LowTempAlarmEmail", LowTempAlarm.Email); ini.SetValue("Alarms", "LowTempAlarmLatch", LowTempAlarm.Latch); ini.SetValue("Alarms", "LowTempAlarmLatchHours", LowTempAlarm.LatchHours); @@ -4778,6 +4850,7 @@ internal void WriteIniFile() ini.SetValue("Alarms", "HighTempAlarmSound", HighTempAlarm.Sound); ini.SetValue("Alarms", "HighTempAlarmSoundFile", HighTempAlarm.SoundFile); ini.SetValue("Alarms", "HighTempAlarmNotify", HighTempAlarm.Notify); + ini.SetValue("Alarms", "HighTempAlarmEmail", HighTempAlarm.Email); ini.SetValue("Alarms", "HighTempAlarmLatch", HighTempAlarm.Latch); ini.SetValue("Alarms", "HighTempAlarmLatchHours", HighTempAlarm.LatchHours); @@ -4786,6 +4859,7 @@ internal void WriteIniFile() ini.SetValue("Alarms", "TempChangeAlarmSound", TempChangeAlarm.Sound); ini.SetValue("Alarms", "TempChangeAlarmSoundFile", TempChangeAlarm.SoundFile); ini.SetValue("Alarms", "TempChangeAlarmNotify", TempChangeAlarm.Notify); + ini.SetValue("Alarms", "TempChangeAlarmEmail", TempChangeAlarm.Email); ini.SetValue("Alarms", "TempChangeAlarmLatch", TempChangeAlarm.Latch); ini.SetValue("Alarms", "TempChangeAlarmLatchHours", TempChangeAlarm.LatchHours); @@ -4794,6 +4868,7 @@ internal void WriteIniFile() ini.SetValue("Alarms", "LowPressAlarmSound", LowPressAlarm.Sound); ini.SetValue("Alarms", "LowPressAlarmSoundFile", LowPressAlarm.SoundFile); ini.SetValue("Alarms", "LowPressAlarmNotify", LowPressAlarm.Notify); + ini.SetValue("Alarms", "LowPressAlarmEmail", LowPressAlarm.Email); ini.SetValue("Alarms", "LowPressAlarmLatch", LowPressAlarm.Latch); ini.SetValue("Alarms", "LowPressAlarmLatchHours", LowPressAlarm.LatchHours); @@ -4802,6 +4877,7 @@ internal void WriteIniFile() ini.SetValue("Alarms", "HighPressAlarmSound", HighPressAlarm.Sound); ini.SetValue("Alarms", "HighPressAlarmSoundFile", HighPressAlarm.SoundFile); ini.SetValue("Alarms", "HighPressAlarmNotify", HighPressAlarm.Notify); + ini.SetValue("Alarms", "HighPressAlarmEmail", HighPressAlarm.Email); ini.SetValue("Alarms", "HighPressAlarmLatch", HighPressAlarm.Latch); ini.SetValue("Alarms", "HighPressAlarmLatchHours", HighPressAlarm.LatchHours); @@ -4810,6 +4886,7 @@ internal void WriteIniFile() ini.SetValue("Alarms", "PressChangeAlarmSound", PressChangeAlarm.Sound); ini.SetValue("Alarms", "PressChangeAlarmSoundFile", PressChangeAlarm.SoundFile); ini.SetValue("Alarms", "PressChangeAlarmNotify", PressChangeAlarm.Notify); + ini.SetValue("Alarms", "PressChangeAlarmEmail", PressChangeAlarm.Email); ini.SetValue("Alarms", "PressChangeAlarmLatch", PressChangeAlarm.Latch); ini.SetValue("Alarms", "PressChangeAlarmLatchHours", PressChangeAlarm.LatchHours); @@ -4818,6 +4895,7 @@ internal void WriteIniFile() ini.SetValue("Alarms", "HighRainTodayAlarmSound", HighRainTodayAlarm.Sound); ini.SetValue("Alarms", "HighRainTodayAlarmSoundFile", HighRainTodayAlarm.SoundFile); ini.SetValue("Alarms", "HighRainTodayAlarmNotify", HighRainTodayAlarm.Notify); + ini.SetValue("Alarms", "HighRainTodayAlarmEmail", HighRainTodayAlarm.Email); ini.SetValue("Alarms", "HighRainTodayAlarmLatch", HighRainTodayAlarm.Latch); ini.SetValue("Alarms", "HighRainTodayAlarmLatchHours", HighRainTodayAlarm.LatchHours); @@ -4826,6 +4904,7 @@ internal void WriteIniFile() ini.SetValue("Alarms", "HighRainRateAlarmSound", HighRainRateAlarm.Sound); ini.SetValue("Alarms", "HighRainRateAlarmSoundFile", HighRainRateAlarm.SoundFile); ini.SetValue("Alarms", "HighRainRateAlarmNotify", HighRainRateAlarm.Notify); + ini.SetValue("Alarms", "HighRainRateAlarmEmail", HighRainRateAlarm.Email); ini.SetValue("Alarms", "HighRainRateAlarmLatch", HighRainRateAlarm.Latch); ini.SetValue("Alarms", "HighRainRateAlarmLatchHours", HighRainRateAlarm.LatchHours); @@ -4834,6 +4913,7 @@ internal void WriteIniFile() ini.SetValue("Alarms", "HighGustAlarmSound", HighGustAlarm.Sound); ini.SetValue("Alarms", "HighGustAlarmSoundFile", HighGustAlarm.SoundFile); ini.SetValue("Alarms", "HighGustAlarmNotify", HighGustAlarm.Notify); + ini.SetValue("Alarms", "HighGustAlarmEmail", HighGustAlarm.Email); ini.SetValue("Alarms", "HighGustAlarmLatch", HighGustAlarm.Latch); ini.SetValue("Alarms", "HighGustAlarmLatchHours", HighGustAlarm.LatchHours); @@ -4842,6 +4922,7 @@ internal void WriteIniFile() ini.SetValue("Alarms", "HighWindAlarmSound", HighWindAlarm.Sound); ini.SetValue("Alarms", "HighWindAlarmSoundFile", HighWindAlarm.SoundFile); ini.SetValue("Alarms", "HighWindAlarmNotify", HighWindAlarm.Notify); + ini.SetValue("Alarms", "HighWindAlarmEmail", HighWindAlarm.Email); ini.SetValue("Alarms", "HighWindAlarmLatch", HighWindAlarm.Latch); ini.SetValue("Alarms", "HighWindAlarmLatchHours", HighWindAlarm.LatchHours); @@ -4849,6 +4930,7 @@ internal void WriteIniFile() ini.SetValue("Alarms", "SensorAlarmSound", SensorAlarm.Sound); ini.SetValue("Alarms", "SensorAlarmSoundFile", SensorAlarm.SoundFile); ini.SetValue("Alarms", "SensorAlarmNotify", SensorAlarm.Notify); + ini.SetValue("Alarms", "SensorAlarmEmail", SensorAlarm.Email); ini.SetValue("Alarms", "SensorAlarmLatch", SensorAlarm.Latch); ini.SetValue("Alarms", "SensorAlarmLatchHours", SensorAlarm.LatchHours); @@ -4856,6 +4938,7 @@ internal void WriteIniFile() ini.SetValue("Alarms", "DataStoppedAlarmSound", DataStoppedAlarm.Sound); ini.SetValue("Alarms", "DataStoppedAlarmSoundFile", DataStoppedAlarm.SoundFile); ini.SetValue("Alarms", "DataStoppedAlarmNotify", DataStoppedAlarm.Notify); + ini.SetValue("Alarms", "DataStoppedAlarmEmail", DataStoppedAlarm.Email); ini.SetValue("Alarms", "DataStoppedAlarmLatch", DataStoppedAlarm.Latch); ini.SetValue("Alarms", "DataStoppedAlarmLatchHours", DataStoppedAlarm.LatchHours); @@ -4863,6 +4946,7 @@ internal void WriteIniFile() ini.SetValue("Alarms", "BatteryLowAlarmSound", BatteryLowAlarm.Sound); ini.SetValue("Alarms", "BatteryLowAlarmSoundFile", BatteryLowAlarm.SoundFile); ini.SetValue("Alarms", "BatteryLowAlarmNotify", BatteryLowAlarm.Notify); + ini.SetValue("Alarms", "BatteryLowAlarmEmail", BatteryLowAlarm.Email); ini.SetValue("Alarms", "BatteryLowAlarmLatch", BatteryLowAlarm.Latch); ini.SetValue("Alarms", "BatteryLowAlarmLatchHours", BatteryLowAlarm.LatchHours); @@ -4870,6 +4954,7 @@ internal void WriteIniFile() ini.SetValue("Alarms", "DataSpikeAlarmSound", SpikeAlarm.Sound); ini.SetValue("Alarms", "DataSpikeAlarmSoundFile", SpikeAlarm.SoundFile); ini.SetValue("Alarms", "DataSpikeAlarmNotify", SpikeAlarm.Notify); + ini.SetValue("Alarms", "DataSpikeAlarmEmail", SpikeAlarm.Email); ini.SetValue("Alarms", "DataSpikeAlarmLatch", SpikeAlarm.Latch); ini.SetValue("Alarms", "DataSpikeAlarmLatchHours", SpikeAlarm.LatchHours); @@ -4877,9 +4962,14 @@ internal void WriteIniFile() ini.SetValue("Alarms", "UpgradeAlarmSound", UpgradeAlarm.Sound); ini.SetValue("Alarms", "UpgradeAlarmSoundFile", UpgradeAlarm.SoundFile); ini.SetValue("Alarms", "UpgradeAlarmNotify", UpgradeAlarm.Notify); + ini.SetValue("Alarms", "UpgradeAlarmEmail", UpgradeAlarm.Email); ini.SetValue("Alarms", "UpgradeAlarmLatch", UpgradeAlarm.Latch); ini.SetValue("Alarms", "UpgradeAlarmLatchHours", UpgradeAlarm.LatchHours); + ini.SetValue("Alarms", "FromEmail", AlarmFromEmail); + ini.SetValue("Alarms", "DestEmail", AlarmDestEmail.Join(";")); + + ini.SetValue("Offsets", "PressOffset", Calib.Press.Offset); ini.SetValue("Offsets", "TempOffset", Calib.Temp.Offset); ini.SetValue("Offsets", "HumOffset", Calib.Hum.Offset); @@ -5037,6 +5127,14 @@ internal void WriteIniFile() ini.SetValue("Select-a-Chart", "Colour" + i, SelectaChartOptions.colours[i]); } + // Email settings + ini.SetValue("SMTP", "Enabled", SmtpOptions.Enabled); + ini.SetValue("SMTP", "ServerName", SmtpOptions.Server); + ini.SetValue("SMTP", "Port", SmtpOptions.Port); + ini.SetValue("SMTP", "UseSSL", SmtpOptions.UseSsl); + ini.SetValue("SMTP", "RequiresAuthentication", SmtpOptions.RequiresAuthentication); + ini.SetValue("SMTP", "User", SmtpOptions.User); + ini.SetValue("SMTP", "Password", SmtpOptions.Password); ini.Flush(); @@ -5045,318 +5143,336 @@ internal void WriteIniFile() private void ReadStringsFile() { - if (File.Exists("strings.ini")) - { - IniFile ini = new IniFile("strings.ini"); - - // forecast - - ForecastNotAvailable = ini.GetValue("Forecast", "notavailable", ForecastNotAvailable); - - exceptional = ini.GetValue("Forecast", "exceptional", exceptional); - zForecast[0] = ini.GetValue("Forecast", "forecast1", zForecast[0]); - zForecast[1] = ini.GetValue("Forecast", "forecast2", zForecast[1]); - zForecast[2] = ini.GetValue("Forecast", "forecast3", zForecast[2]); - zForecast[3] = ini.GetValue("Forecast", "forecast4", zForecast[3]); - zForecast[4] = ini.GetValue("Forecast", "forecast5", zForecast[4]); - zForecast[5] = ini.GetValue("Forecast", "forecast6", zForecast[5]); - zForecast[6] = ini.GetValue("Forecast", "forecast7", zForecast[6]); - zForecast[7] = ini.GetValue("Forecast", "forecast8", zForecast[7]); - zForecast[8] = ini.GetValue("Forecast", "forecast9", zForecast[8]); - zForecast[9] = ini.GetValue("Forecast", "forecast10", zForecast[9]); - zForecast[10] = ini.GetValue("Forecast", "forecast11", zForecast[10]); - zForecast[11] = ini.GetValue("Forecast", "forecast12", zForecast[11]); - zForecast[12] = ini.GetValue("Forecast", "forecast13", zForecast[12]); - zForecast[13] = ini.GetValue("Forecast", "forecast14", zForecast[13]); - zForecast[14] = ini.GetValue("Forecast", "forecast15", zForecast[14]); - zForecast[15] = ini.GetValue("Forecast", "forecast16", zForecast[15]); - zForecast[16] = ini.GetValue("Forecast", "forecast17", zForecast[16]); - zForecast[17] = ini.GetValue("Forecast", "forecast18", zForecast[17]); - zForecast[18] = ini.GetValue("Forecast", "forecast19", zForecast[18]); - zForecast[19] = ini.GetValue("Forecast", "forecast20", zForecast[19]); - zForecast[20] = ini.GetValue("Forecast", "forecast21", zForecast[20]); - zForecast[21] = ini.GetValue("Forecast", "forecast22", zForecast[21]); - zForecast[22] = ini.GetValue("Forecast", "forecast23", zForecast[22]); - zForecast[23] = ini.GetValue("Forecast", "forecast24", zForecast[23]); - zForecast[24] = ini.GetValue("Forecast", "forecast25", zForecast[24]); - zForecast[25] = ini.GetValue("Forecast", "forecast26", zForecast[25]); - // moon phases - Newmoon = ini.GetValue("MoonPhases", "Newmoon", Newmoon); - WaxingCrescent = ini.GetValue("MoonPhases", "WaxingCrescent", WaxingCrescent); - FirstQuarter = ini.GetValue("MoonPhases", "FirstQuarter", FirstQuarter); - WaxingGibbous = ini.GetValue("MoonPhases", "WaxingGibbous", WaxingGibbous); - Fullmoon = ini.GetValue("MoonPhases", "Fullmoon", Fullmoon); - WaningGibbous = ini.GetValue("MoonPhases", "WaningGibbous", WaningGibbous); - LastQuarter = ini.GetValue("MoonPhases", "LastQuarter", LastQuarter); - WaningCrescent = ini.GetValue("MoonPhases", "WaningCrescent", WaningCrescent); - // beaufort - Calm = ini.GetValue("Beaufort", "Calm", Calm); - Lightair = ini.GetValue("Beaufort", "Lightair", Lightair); - Lightbreeze = ini.GetValue("Beaufort", "Lightbreeze", Lightbreeze); - Gentlebreeze = ini.GetValue("Beaufort", "Gentlebreeze", Gentlebreeze); - Moderatebreeze = ini.GetValue("Beaufort", "Moderatebreeze", Moderatebreeze); - Freshbreeze = ini.GetValue("Beaufort", "Freshbreeze", Freshbreeze); - Strongbreeze = ini.GetValue("Beaufort", "Strongbreeze", Strongbreeze); - Neargale = ini.GetValue("Beaufort", "Neargale", Neargale); - Gale = ini.GetValue("Beaufort", "Gale", Gale); - Stronggale = ini.GetValue("Beaufort", "Stronggale", Stronggale); - Storm = ini.GetValue("Beaufort", "Storm", Storm); - Violentstorm = ini.GetValue("Beaufort", "Violentstorm", Violentstorm); - Hurricane = ini.GetValue("Beaufort", "Hurricane", Hurricane); - // trends - Risingveryrapidly = ini.GetValue("Trends", "Risingveryrapidly", Risingveryrapidly); - Risingquickly = ini.GetValue("Trends", "Risingquickly", Risingquickly); - Rising = ini.GetValue("Trends", "Rising", Rising); - Risingslowly = ini.GetValue("Trends", "Risingslowly", Risingslowly); - Steady = ini.GetValue("Trends", "Steady", Steady); - Fallingslowly = ini.GetValue("Trends", "Fallingslowly", Fallingslowly); - Falling = ini.GetValue("Trends", "Falling", Falling); - Fallingquickly = ini.GetValue("Trends", "Fallingquickly", Fallingquickly); - Fallingveryrapidly = ini.GetValue("Trends", "Fallingveryrapidly", Fallingveryrapidly); - // compass points - compassp[0] = ini.GetValue("Compass", "N", compassp[0]); - compassp[1] = ini.GetValue("Compass", "NNE", compassp[1]); - compassp[2] = ini.GetValue("Compass", "NE", compassp[2]); - compassp[3] = ini.GetValue("Compass", "ENE", compassp[3]); - compassp[4] = ini.GetValue("Compass", "E", compassp[4]); - compassp[5] = ini.GetValue("Compass", "ESE", compassp[5]); - compassp[6] = ini.GetValue("Compass", "SE", compassp[6]); - compassp[7] = ini.GetValue("Compass", "SSE", compassp[7]); - compassp[8] = ini.GetValue("Compass", "S", compassp[8]); - compassp[9] = ini.GetValue("Compass", "SSW", compassp[9]); - compassp[10] = ini.GetValue("Compass", "SW", compassp[10]); - compassp[11] = ini.GetValue("Compass", "WSW", compassp[11]); - compassp[12] = ini.GetValue("Compass", "W", compassp[12]); - compassp[13] = ini.GetValue("Compass", "WNW", compassp[13]); - compassp[14] = ini.GetValue("Compass", "NW", compassp[14]); - compassp[15] = ini.GetValue("Compass", "NNW", compassp[15]); - // graphs - /* - SmallGraphWindSpeedTitle = ini.GetValue("Graphs", "SmallGraphWindSpeedTitle", "Wind Speed"); - SmallGraphOutsideTemperatureTitle = ini.GetValue("Graphs", "SmallGraphOutsideTemperatureTitle", "Outside Temperature"); - SmallGraphInsideTemperatureTitle = ini.GetValue("Graphs", "SmallGraphInsideTemperatureTitle", "Inside Temperature"); - SmallGraphPressureTitle = ini.GetValue("Graphs", "SmallGraphPressureTitle", "Pressure"); - SmallGraphRainfallRateTitle = ini.GetValue("Graphs", "SmallGraphRainfallRateTitle", "Rainfall Rate"); - SmallGraphWindDirectionTitle = ini.GetValue("Graphs", "SmallGraphWindDirectionTitle", "Wind Direction"); - SmallGraphTempMinMaxAvgTitle = ini.GetValue("Graphs", "SmallGraphTempMinMaxAvgTitle", "Temp Min/Max/Avg"); - SmallGraphHumidityTitle = ini.GetValue("Graphs", "SmallGraphHumidityTitle", "Humidity"); - SmallGraphRainTodayTitle = ini.GetValue("Graphs", "SmallGraphRainTodayTitle", "Rain Today"); - SmallGraphDailyRainTitle = ini.GetValue("Graphs", "SmallGraphDailyRainTitle", "Daily Rain"); - SmallGraphSolarTitle = ini.GetValue("Graphs", "SmallGraphSolarTitle", "Solar Radiation"); - SmallGraphUVTitle = ini.GetValue("Graphs", "SmallGraphUVTitle", "UV Index"); - SmallGraphSunshineTitle = ini.GetValue("Graphs", "SmallGraphSunshineTitle", "Daily Sunshine (hrs)"); - - LargeGraphWindSpeedTitle = ini.GetValue("Graphs", "LargeGraphWindSpeedTitle", "Wind Speed"); - LargeGraphWindGustTitle = ini.GetValue("Graphs", "LargeGraphWindGustTitle", "Wind Gust"); - LargeGraphOutsideTempTitle = ini.GetValue("Graphs", "LargeGraphOutsideTempTitle", "Temperature"); - LargeGraphHeatIndexTitle = ini.GetValue("Graphs", "LargeGraphHeatIndexTitle", "Heat Index"); - LargeGraphDewPointTitle = ini.GetValue("Graphs", "LargeGraphDewPointTitle", "Dew Point"); - LargeGraphWindChillTitle = ini.GetValue("Graphs", "LargeGraphWindChillTitle", "Wind Chill"); - LargeGraphApparentTempTitle = ini.GetValue("Graphs", "LargeGraphApparentTempTitle", "Apparent Temperature"); - LargeGraphInsideTempTitle = ini.GetValue("Graphs", "LargeGraphInsideTempTitle", "Inside Temperature"); - LargeGraphPressureTitle = ini.GetValue("Graphs", "LargeGraphPressureTitle", "Pressure"); - LargeGraphRainfallRateTitle = ini.GetValue("Graphs", "LargeGraphRainfallRateTitle", "Rainfall Rate"); - LargeGraphWindDirectionTitle = ini.GetValue("Graphs", "LargeGraphWindDirectionTitle", "Wind Direction"); - LargeGraphWindAvgDirectionTitle = ini.GetValue("Graphs", "LargeGraphWindAvgDirectionTitle", "Average"); - LargeGraphMinTempTitle = ini.GetValue("Graphs", "LargeGraphMinTempTitle", "Min Temp"); - LargeGraphMaxTempTitle = ini.GetValue("Graphs", "LargeGraphMaxTempTitle", "Max Temp"); - LargeGraphAvgTempTitle = ini.GetValue("Graphs", "LargeGraphAvgTempTitle", "Avg Temp"); - LargeGraphInsideHumidityTitle = ini.GetValue("Graphs", "LargeGraphInsideHumidityTitle", "Inside Humidity"); - LargeGraphOutsideHumidityTitle = ini.GetValue("Graphs", "LargeGraphOutsideHumidityTitle", "Outside Humidity"); - LargeGraphRainfallTodayTitle = ini.GetValue("Graphs", "LargeGraphRainfallTodayTitle", "Rainfall Today"); - LargeGraphDailyRainfallTitle = ini.GetValue("Graphs", "LargeGraphDailyRainfallTitle", "Daily Rainfall"); - LargeGraphSolarTitle = ini.GetValue("Graphs", "LargeGraphSolarTitle", "Solar Radiation"); - LargeGraphMaxSolarTitle = ini.GetValue("Graphs", "LargeGraphMaxSolarTitle", "Theoretical Max"); - LargeGraphUVTitle = ini.GetValue("Graphs", "LargeGraphUVTitle", "UV Index"); - LargeGraphSunshineTitle = ini.GetValue("Graphs", "LargeGraphSunshineTitle", "Daily Sunshine (hrs)"); - */ - // Extra sensor captions - WMR200ExtraChannelCaptions[1] = ini.GetValue("ExtraSensorCaptions", "Solar", WMR200ExtraChannelCaptions[1]); - WMR200ExtraChannelCaptions[2] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel2", WMR200ExtraChannelCaptions[2]); - WMR200ExtraChannelCaptions[3] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel3", WMR200ExtraChannelCaptions[3]); - WMR200ExtraChannelCaptions[4] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel4", WMR200ExtraChannelCaptions[4]); - WMR200ExtraChannelCaptions[5] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel5", WMR200ExtraChannelCaptions[5]); - WMR200ExtraChannelCaptions[6] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel6", WMR200ExtraChannelCaptions[6]); - WMR200ExtraChannelCaptions[7] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel7", WMR200ExtraChannelCaptions[7]); - WMR200ExtraChannelCaptions[8] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel8", WMR200ExtraChannelCaptions[8]); - WMR200ExtraChannelCaptions[9] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel9", WMR200ExtraChannelCaptions[9]); - WMR200ExtraChannelCaptions[10] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel10", WMR200ExtraChannelCaptions[10]); - - // Extra temperature captions (for Extra Sensor Data screen) - ExtraTempCaptions[1] = ini.GetValue("ExtraTempCaptions", "Sensor1", ExtraTempCaptions[1]); - ExtraTempCaptions[2] = ini.GetValue("ExtraTempCaptions", "Sensor2", ExtraTempCaptions[2]); - ExtraTempCaptions[3] = ini.GetValue("ExtraTempCaptions", "Sensor3", ExtraTempCaptions[3]); - ExtraTempCaptions[4] = ini.GetValue("ExtraTempCaptions", "Sensor4", ExtraTempCaptions[4]); - ExtraTempCaptions[5] = ini.GetValue("ExtraTempCaptions", "Sensor5", ExtraTempCaptions[5]); - ExtraTempCaptions[6] = ini.GetValue("ExtraTempCaptions", "Sensor6", ExtraTempCaptions[6]); - ExtraTempCaptions[7] = ini.GetValue("ExtraTempCaptions", "Sensor7", ExtraTempCaptions[7]); - ExtraTempCaptions[8] = ini.GetValue("ExtraTempCaptions", "Sensor8", ExtraTempCaptions[8]); - ExtraTempCaptions[9] = ini.GetValue("ExtraTempCaptions", "Sensor9", ExtraTempCaptions[9]); - ExtraTempCaptions[10] = ini.GetValue("ExtraTempCaptions", "Sensor10", ExtraTempCaptions[10]); - - // Extra humidity captions (for Extra Sensor Data screen) - ExtraHumCaptions[1] = ini.GetValue("ExtraHumCaptions", "Sensor1", ExtraHumCaptions[1]); - ExtraHumCaptions[2] = ini.GetValue("ExtraHumCaptions", "Sensor2", ExtraHumCaptions[2]); - ExtraHumCaptions[3] = ini.GetValue("ExtraHumCaptions", "Sensor3", ExtraHumCaptions[3]); - ExtraHumCaptions[4] = ini.GetValue("ExtraHumCaptions", "Sensor4", ExtraHumCaptions[4]); - ExtraHumCaptions[5] = ini.GetValue("ExtraHumCaptions", "Sensor5", ExtraHumCaptions[5]); - ExtraHumCaptions[6] = ini.GetValue("ExtraHumCaptions", "Sensor6", ExtraHumCaptions[6]); - ExtraHumCaptions[7] = ini.GetValue("ExtraHumCaptions", "Sensor7", ExtraHumCaptions[7]); - ExtraHumCaptions[8] = ini.GetValue("ExtraHumCaptions", "Sensor8", ExtraHumCaptions[8]); - ExtraHumCaptions[9] = ini.GetValue("ExtraHumCaptions", "Sensor9", ExtraHumCaptions[9]); - ExtraHumCaptions[10] = ini.GetValue("ExtraHumCaptions", "Sensor10", ExtraHumCaptions[10]); - - // Extra dew point captions (for Extra Sensor Data screen) - ExtraDPCaptions[1] = ini.GetValue("ExtraDPCaptions", "Sensor1", ExtraDPCaptions[1]); - ExtraDPCaptions[2] = ini.GetValue("ExtraDPCaptions", "Sensor2", ExtraDPCaptions[2]); - ExtraDPCaptions[3] = ini.GetValue("ExtraDPCaptions", "Sensor3", ExtraDPCaptions[3]); - ExtraDPCaptions[4] = ini.GetValue("ExtraDPCaptions", "Sensor4", ExtraDPCaptions[4]); - ExtraDPCaptions[5] = ini.GetValue("ExtraDPCaptions", "Sensor5", ExtraDPCaptions[5]); - ExtraDPCaptions[6] = ini.GetValue("ExtraDPCaptions", "Sensor6", ExtraDPCaptions[6]); - ExtraDPCaptions[7] = ini.GetValue("ExtraDPCaptions", "Sensor7", ExtraDPCaptions[7]); - ExtraDPCaptions[8] = ini.GetValue("ExtraDPCaptions", "Sensor8", ExtraDPCaptions[8]); - ExtraDPCaptions[9] = ini.GetValue("ExtraDPCaptions", "Sensor9", ExtraDPCaptions[9]); - ExtraDPCaptions[10] = ini.GetValue("ExtraDPCaptions", "Sensor10", ExtraDPCaptions[10]); - - // soil temp captions (for Extra Sensor Data screen) - SoilTempCaptions[1] = ini.GetValue("SoilTempCaptions", "Sensor1", SoilTempCaptions[1]); - SoilTempCaptions[2] = ini.GetValue("SoilTempCaptions", "Sensor2", SoilTempCaptions[2]); - SoilTempCaptions[3] = ini.GetValue("SoilTempCaptions", "Sensor3", SoilTempCaptions[3]); - SoilTempCaptions[4] = ini.GetValue("SoilTempCaptions", "Sensor4", SoilTempCaptions[4]); - SoilTempCaptions[5] = ini.GetValue("SoilTempCaptions", "Sensor5", SoilTempCaptions[5]); - SoilTempCaptions[6] = ini.GetValue("SoilTempCaptions", "Sensor6", SoilTempCaptions[6]); - SoilTempCaptions[7] = ini.GetValue("SoilTempCaptions", "Sensor7", SoilTempCaptions[7]); - SoilTempCaptions[8] = ini.GetValue("SoilTempCaptions", "Sensor8", SoilTempCaptions[8]); - SoilTempCaptions[9] = ini.GetValue("SoilTempCaptions", "Sensor9", SoilTempCaptions[9]); - SoilTempCaptions[10] = ini.GetValue("SoilTempCaptions", "Sensor10", SoilTempCaptions[10]); - SoilTempCaptions[11] = ini.GetValue("SoilTempCaptions", "Sensor11", SoilTempCaptions[11]); - SoilTempCaptions[12] = ini.GetValue("SoilTempCaptions", "Sensor12", SoilTempCaptions[12]); - SoilTempCaptions[13] = ini.GetValue("SoilTempCaptions", "Sensor13", SoilTempCaptions[13]); - SoilTempCaptions[14] = ini.GetValue("SoilTempCaptions", "Sensor14", SoilTempCaptions[14]); - SoilTempCaptions[15] = ini.GetValue("SoilTempCaptions", "Sensor15", SoilTempCaptions[15]); - SoilTempCaptions[16] = ini.GetValue("SoilTempCaptions", "Sensor16", SoilTempCaptions[16]); - - // soil moisture captions (for Extra Sensor Data screen) - SoilMoistureCaptions[1] = ini.GetValue("SoilMoistureCaptions", "Sensor1", SoilMoistureCaptions[1]); - SoilMoistureCaptions[2] = ini.GetValue("SoilMoistureCaptions", "Sensor2", SoilMoistureCaptions[2]); - SoilMoistureCaptions[3] = ini.GetValue("SoilMoistureCaptions", "Sensor3", SoilMoistureCaptions[3]); - SoilMoistureCaptions[4] = ini.GetValue("SoilMoistureCaptions", "Sensor4", SoilMoistureCaptions[4]); - SoilMoistureCaptions[5] = ini.GetValue("SoilMoistureCaptions", "Sensor5", SoilMoistureCaptions[5]); - SoilMoistureCaptions[6] = ini.GetValue("SoilMoistureCaptions", "Sensor6", SoilMoistureCaptions[6]); - SoilMoistureCaptions[7] = ini.GetValue("SoilMoistureCaptions", "Sensor7", SoilMoistureCaptions[7]); - SoilMoistureCaptions[8] = ini.GetValue("SoilMoistureCaptions", "Sensor8", SoilMoistureCaptions[8]); - SoilMoistureCaptions[9] = ini.GetValue("SoilMoistureCaptions", "Sensor9", SoilMoistureCaptions[9]); - SoilMoistureCaptions[10] = ini.GetValue("SoilMoistureCaptions", "Sensor10", SoilMoistureCaptions[10]); - SoilMoistureCaptions[11] = ini.GetValue("SoilMoistureCaptions", "Sensor11", SoilMoistureCaptions[11]); - SoilMoistureCaptions[12] = ini.GetValue("SoilMoistureCaptions", "Sensor12", SoilMoistureCaptions[12]); - SoilMoistureCaptions[13] = ini.GetValue("SoilMoistureCaptions", "Sensor13", SoilMoistureCaptions[13]); - SoilMoistureCaptions[14] = ini.GetValue("SoilMoistureCaptions", "Sensor14", SoilMoistureCaptions[14]); - SoilMoistureCaptions[15] = ini.GetValue("SoilMoistureCaptions", "Sensor15", SoilMoistureCaptions[15]); - SoilMoistureCaptions[16] = ini.GetValue("SoilMoistureCaptions", "Sensor16", SoilMoistureCaptions[16]); - - // leaf temp captions (for Extra Sensor Data screen) - LeafTempCaptions[1] = ini.GetValue("LeafTempCaptions", "Sensor1", LeafTempCaptions[1]); - LeafTempCaptions[2] = ini.GetValue("LeafTempCaptions", "Sensor2", LeafTempCaptions[2]); - LeafTempCaptions[3] = ini.GetValue("LeafTempCaptions", "Sensor3", LeafTempCaptions[3]); - LeafTempCaptions[4] = ini.GetValue("LeafTempCaptions", "Sensor4", LeafTempCaptions[4]); - - // leaf wetness captions (for Extra Sensor Data screen) - LeafWetnessCaptions[1] = ini.GetValue("LeafWetnessCaptions", "Sensor1", LeafWetnessCaptions[1]); - LeafWetnessCaptions[2] = ini.GetValue("LeafWetnessCaptions", "Sensor2", LeafWetnessCaptions[2]); - LeafWetnessCaptions[3] = ini.GetValue("LeafWetnessCaptions", "Sensor3", LeafWetnessCaptions[3]); - LeafWetnessCaptions[4] = ini.GetValue("LeafWetnessCaptions", "Sensor4", LeafWetnessCaptions[4]); - LeafWetnessCaptions[5] = ini.GetValue("LeafWetnessCaptions", "Sensor5", LeafWetnessCaptions[5]); - LeafWetnessCaptions[6] = ini.GetValue("LeafWetnessCaptions", "Sensor6", LeafWetnessCaptions[6]); - LeafWetnessCaptions[7] = ini.GetValue("LeafWetnessCaptions", "Sensor7", LeafWetnessCaptions[7]); - LeafWetnessCaptions[8] = ini.GetValue("LeafWetnessCaptions", "Sensor8", LeafWetnessCaptions[8]); - - // air quality captions (for Extra Sensor Data screen) - AirQualityCaptions[1] = ini.GetValue("AirQualityCaptions", "Sensor1", AirQualityCaptions[1]); - AirQualityCaptions[2] = ini.GetValue("AirQualityCaptions", "Sensor2", AirQualityCaptions[2]); - AirQualityCaptions[3] = ini.GetValue("AirQualityCaptions", "Sensor3", AirQualityCaptions[3]); - AirQualityCaptions[4] = ini.GetValue("AirQualityCaptions", "Sensor4", AirQualityCaptions[4]); - AirQualityAvgCaptions[1] = ini.GetValue("AirQualityCaptions", "SensorAvg1", AirQualityAvgCaptions[1]); - AirQualityAvgCaptions[2] = ini.GetValue("AirQualityCaptions", "SensorAvg2", AirQualityAvgCaptions[2]); - AirQualityAvgCaptions[3] = ini.GetValue("AirQualityCaptions", "SensorAvg3", AirQualityAvgCaptions[3]); - AirQualityAvgCaptions[4] = ini.GetValue("AirQualityCaptions", "SensorAvg4", AirQualityAvgCaptions[4]); - - // CO2 captions - Ecowitt WH45 sensor - CO2_CurrentCaption = ini.GetValue("CO2Captions", "CO2-Current", CO2_CurrentCaption); - CO2_24HourCaption = ini.GetValue("CO2Captions", "CO2-24hr", CO2_24HourCaption); - CO2_pm2p5Caption = ini.GetValue("CO2Captions", "CO2-Pm2p5", CO2_pm2p5Caption); - CO2_pm2p5_24hrCaption = ini.GetValue("CO2Captions", "CO2-Pm2p5-24hr", CO2_pm2p5_24hrCaption); - CO2_pm10Caption = ini.GetValue("CO2Captions", "CO2-Pm10", CO2_pm10Caption); - CO2_pm10_24hrCaption = ini.GetValue("CO2Captions", "CO2-Pm10-24hr", CO2_pm10_24hrCaption); - - // User temperature captions (for Extra Sensor Data screen) - UserTempCaptions[1] = ini.GetValue("UserTempCaptions", "Sensor1", UserTempCaptions[1]); - UserTempCaptions[2] = ini.GetValue("UserTempCaptions", "Sensor2", UserTempCaptions[2]); - UserTempCaptions[3] = ini.GetValue("UserTempCaptions", "Sensor3", UserTempCaptions[3]); - UserTempCaptions[4] = ini.GetValue("UserTempCaptions", "Sensor4", UserTempCaptions[4]); - UserTempCaptions[5] = ini.GetValue("UserTempCaptions", "Sensor5", UserTempCaptions[5]); - UserTempCaptions[6] = ini.GetValue("UserTempCaptions", "Sensor6", UserTempCaptions[6]); - UserTempCaptions[7] = ini.GetValue("UserTempCaptions", "Sensor7", UserTempCaptions[7]); - UserTempCaptions[8] = ini.GetValue("UserTempCaptions", "Sensor8", UserTempCaptions[8]); - - thereWillBeMinSLessDaylightTomorrow = ini.GetValue("Solar", "LessDaylightTomorrow", thereWillBeMinSLessDaylightTomorrow); - thereWillBeMinSMoreDaylightTomorrow = ini.GetValue("Solar", "MoreDaylightTomorrow", thereWillBeMinSMoreDaylightTomorrow); - - DavisForecast1[0] = ini.GetValue("DavisForecast1", "forecast1", DavisForecast1[0]); - DavisForecast1[1] = ini.GetValue("DavisForecast1", "forecast2", DavisForecast1[1]) + " "; - DavisForecast1[2] = ini.GetValue("DavisForecast1", "forecast3", DavisForecast1[2]) + " "; - DavisForecast1[3] = ini.GetValue("DavisForecast1", "forecast4", DavisForecast1[3]) + " "; - DavisForecast1[4] = ini.GetValue("DavisForecast1", "forecast5", DavisForecast1[4]) + " "; - DavisForecast1[5] = ini.GetValue("DavisForecast1", "forecast6", DavisForecast1[5]) + " "; - DavisForecast1[6] = ini.GetValue("DavisForecast1", "forecast7", DavisForecast1[6]) + " "; - DavisForecast1[7] = ini.GetValue("DavisForecast1", "forecast8", DavisForecast1[7]) + " "; - DavisForecast1[8] = ini.GetValue("DavisForecast1", "forecast9", DavisForecast1[8]) + " "; - DavisForecast1[9] = ini.GetValue("DavisForecast1", "forecast10", DavisForecast1[9]) + " "; - DavisForecast1[10] = ini.GetValue("DavisForecast1", "forecast11", DavisForecast1[10]) + " "; - DavisForecast1[11] = ini.GetValue("DavisForecast1", "forecast12", DavisForecast1[11]) + " "; - DavisForecast1[12] = ini.GetValue("DavisForecast1", "forecast13", DavisForecast1[12]) + " "; - DavisForecast1[13] = ini.GetValue("DavisForecast1", "forecast14", DavisForecast1[13]) + " "; - DavisForecast1[14] = ini.GetValue("DavisForecast1", "forecast15", DavisForecast1[14]) + " "; - DavisForecast1[15] = ini.GetValue("DavisForecast1", "forecast16", DavisForecast1[15]) + " "; - DavisForecast1[16] = ini.GetValue("DavisForecast1", "forecast17", DavisForecast1[16]) + " "; - DavisForecast1[17] = ini.GetValue("DavisForecast1", "forecast18", DavisForecast1[17]) + " "; - DavisForecast1[18] = ini.GetValue("DavisForecast1", "forecast19", DavisForecast1[18]) + " "; - DavisForecast1[19] = ini.GetValue("DavisForecast1", "forecast20", DavisForecast1[19]) + " "; - DavisForecast1[20] = ini.GetValue("DavisForecast1", "forecast21", DavisForecast1[20]) + " "; - DavisForecast1[21] = ini.GetValue("DavisForecast1", "forecast22", DavisForecast1[21]) + " "; - DavisForecast1[22] = ini.GetValue("DavisForecast1", "forecast23", DavisForecast1[22]) + " "; - DavisForecast1[23] = ini.GetValue("DavisForecast1", "forecast24", DavisForecast1[23]) + " "; - DavisForecast1[24] = ini.GetValue("DavisForecast1", "forecast25", DavisForecast1[24]) + " "; - DavisForecast1[25] = ini.GetValue("DavisForecast1", "forecast26", DavisForecast1[25]) + " "; - DavisForecast1[26] = ini.GetValue("DavisForecast1", "forecast27", DavisForecast1[26]); - - DavisForecast2[0] = ini.GetValue("DavisForecast2", "forecast1", DavisForecast2[0]); - DavisForecast2[1] = ini.GetValue("DavisForecast2", "forecast2", DavisForecast2[1]) + " "; - DavisForecast2[2] = ini.GetValue("DavisForecast2", "forecast3", DavisForecast2[2]) + " "; - DavisForecast2[3] = ini.GetValue("DavisForecast2", "forecast4", DavisForecast2[3]) + " "; - DavisForecast2[4] = ini.GetValue("DavisForecast2", "forecast5", DavisForecast2[5]) + " "; - DavisForecast2[5] = ini.GetValue("DavisForecast2", "forecast6", DavisForecast2[5]) + " "; - DavisForecast2[6] = ini.GetValue("DavisForecast2", "forecast7", DavisForecast2[6]) + " "; - DavisForecast2[7] = ini.GetValue("DavisForecast2", "forecast8", DavisForecast2[7]) + " "; - DavisForecast2[8] = ini.GetValue("DavisForecast2", "forecast9", DavisForecast2[8]) + " "; - DavisForecast2[9] = ini.GetValue("DavisForecast2", "forecast10", DavisForecast2[9]) + " "; - DavisForecast2[10] = ini.GetValue("DavisForecast2", "forecast11", DavisForecast2[10]) + " "; - DavisForecast2[11] = ini.GetValue("DavisForecast2", "forecast12", DavisForecast2[11]) + " "; - DavisForecast2[12] = ini.GetValue("DavisForecast2", "forecast13", DavisForecast2[12]) + " "; - DavisForecast2[13] = ini.GetValue("DavisForecast2", "forecast14", DavisForecast2[13]) + " "; - DavisForecast2[14] = ini.GetValue("DavisForecast2", "forecast15", DavisForecast2[14]) + " "; - DavisForecast2[15] = ini.GetValue("DavisForecast2", "forecast16", DavisForecast2[15]) + " "; - DavisForecast2[16] = ini.GetValue("DavisForecast2", "forecast17", DavisForecast2[16]) + " "; - DavisForecast2[17] = ini.GetValue("DavisForecast2", "forecast18", DavisForecast2[17]) + " "; - DavisForecast2[18] = ini.GetValue("DavisForecast2", "forecast19", DavisForecast2[18]) + " "; - - DavisForecast3[0] = ini.GetValue("DavisForecast3", "forecast1", DavisForecast3[0]); - DavisForecast3[1] = ini.GetValue("DavisForecast3", "forecast2", DavisForecast3[1]); - DavisForecast3[2] = ini.GetValue("DavisForecast3", "forecast3", DavisForecast3[2]); - DavisForecast3[3] = ini.GetValue("DavisForecast3", "forecast4", DavisForecast3[3]); - DavisForecast3[4] = ini.GetValue("DavisForecast3", "forecast5", DavisForecast3[4]); - DavisForecast3[5] = ini.GetValue("DavisForecast3", "forecast6", DavisForecast3[5]); - DavisForecast3[6] = ini.GetValue("DavisForecast3", "forecast7", DavisForecast3[6]); - } + IniFile ini = new IniFile("strings.ini"); + + // forecast + + ForecastNotAvailable = ini.GetValue("Forecast", "notavailable", ForecastNotAvailable); + + exceptional = ini.GetValue("Forecast", "exceptional", exceptional); + zForecast[0] = ini.GetValue("Forecast", "forecast1", zForecast[0]); + zForecast[1] = ini.GetValue("Forecast", "forecast2", zForecast[1]); + zForecast[2] = ini.GetValue("Forecast", "forecast3", zForecast[2]); + zForecast[3] = ini.GetValue("Forecast", "forecast4", zForecast[3]); + zForecast[4] = ini.GetValue("Forecast", "forecast5", zForecast[4]); + zForecast[5] = ini.GetValue("Forecast", "forecast6", zForecast[5]); + zForecast[6] = ini.GetValue("Forecast", "forecast7", zForecast[6]); + zForecast[7] = ini.GetValue("Forecast", "forecast8", zForecast[7]); + zForecast[8] = ini.GetValue("Forecast", "forecast9", zForecast[8]); + zForecast[9] = ini.GetValue("Forecast", "forecast10", zForecast[9]); + zForecast[10] = ini.GetValue("Forecast", "forecast11", zForecast[10]); + zForecast[11] = ini.GetValue("Forecast", "forecast12", zForecast[11]); + zForecast[12] = ini.GetValue("Forecast", "forecast13", zForecast[12]); + zForecast[13] = ini.GetValue("Forecast", "forecast14", zForecast[13]); + zForecast[14] = ini.GetValue("Forecast", "forecast15", zForecast[14]); + zForecast[15] = ini.GetValue("Forecast", "forecast16", zForecast[15]); + zForecast[16] = ini.GetValue("Forecast", "forecast17", zForecast[16]); + zForecast[17] = ini.GetValue("Forecast", "forecast18", zForecast[17]); + zForecast[18] = ini.GetValue("Forecast", "forecast19", zForecast[18]); + zForecast[19] = ini.GetValue("Forecast", "forecast20", zForecast[19]); + zForecast[20] = ini.GetValue("Forecast", "forecast21", zForecast[20]); + zForecast[21] = ini.GetValue("Forecast", "forecast22", zForecast[21]); + zForecast[22] = ini.GetValue("Forecast", "forecast23", zForecast[22]); + zForecast[23] = ini.GetValue("Forecast", "forecast24", zForecast[23]); + zForecast[24] = ini.GetValue("Forecast", "forecast25", zForecast[24]); + zForecast[25] = ini.GetValue("Forecast", "forecast26", zForecast[25]); + // moon phases + Newmoon = ini.GetValue("MoonPhases", "Newmoon", Newmoon); + WaxingCrescent = ini.GetValue("MoonPhases", "WaxingCrescent", WaxingCrescent); + FirstQuarter = ini.GetValue("MoonPhases", "FirstQuarter", FirstQuarter); + WaxingGibbous = ini.GetValue("MoonPhases", "WaxingGibbous", WaxingGibbous); + Fullmoon = ini.GetValue("MoonPhases", "Fullmoon", Fullmoon); + WaningGibbous = ini.GetValue("MoonPhases", "WaningGibbous", WaningGibbous); + LastQuarter = ini.GetValue("MoonPhases", "LastQuarter", LastQuarter); + WaningCrescent = ini.GetValue("MoonPhases", "WaningCrescent", WaningCrescent); + // beaufort + Calm = ini.GetValue("Beaufort", "Calm", Calm); + Lightair = ini.GetValue("Beaufort", "Lightair", Lightair); + Lightbreeze = ini.GetValue("Beaufort", "Lightbreeze", Lightbreeze); + Gentlebreeze = ini.GetValue("Beaufort", "Gentlebreeze", Gentlebreeze); + Moderatebreeze = ini.GetValue("Beaufort", "Moderatebreeze", Moderatebreeze); + Freshbreeze = ini.GetValue("Beaufort", "Freshbreeze", Freshbreeze); + Strongbreeze = ini.GetValue("Beaufort", "Strongbreeze", Strongbreeze); + Neargale = ini.GetValue("Beaufort", "Neargale", Neargale); + Gale = ini.GetValue("Beaufort", "Gale", Gale); + Stronggale = ini.GetValue("Beaufort", "Stronggale", Stronggale); + Storm = ini.GetValue("Beaufort", "Storm", Storm); + Violentstorm = ini.GetValue("Beaufort", "Violentstorm", Violentstorm); + Hurricane = ini.GetValue("Beaufort", "Hurricane", Hurricane); + // trends + Risingveryrapidly = ini.GetValue("Trends", "Risingveryrapidly", Risingveryrapidly); + Risingquickly = ini.GetValue("Trends", "Risingquickly", Risingquickly); + Rising = ini.GetValue("Trends", "Rising", Rising); + Risingslowly = ini.GetValue("Trends", "Risingslowly", Risingslowly); + Steady = ini.GetValue("Trends", "Steady", Steady); + Fallingslowly = ini.GetValue("Trends", "Fallingslowly", Fallingslowly); + Falling = ini.GetValue("Trends", "Falling", Falling); + Fallingquickly = ini.GetValue("Trends", "Fallingquickly", Fallingquickly); + Fallingveryrapidly = ini.GetValue("Trends", "Fallingveryrapidly", Fallingveryrapidly); + // compass points + compassp[0] = ini.GetValue("Compass", "N", compassp[0]); + compassp[1] = ini.GetValue("Compass", "NNE", compassp[1]); + compassp[2] = ini.GetValue("Compass", "NE", compassp[2]); + compassp[3] = ini.GetValue("Compass", "ENE", compassp[3]); + compassp[4] = ini.GetValue("Compass", "E", compassp[4]); + compassp[5] = ini.GetValue("Compass", "ESE", compassp[5]); + compassp[6] = ini.GetValue("Compass", "SE", compassp[6]); + compassp[7] = ini.GetValue("Compass", "SSE", compassp[7]); + compassp[8] = ini.GetValue("Compass", "S", compassp[8]); + compassp[9] = ini.GetValue("Compass", "SSW", compassp[9]); + compassp[10] = ini.GetValue("Compass", "SW", compassp[10]); + compassp[11] = ini.GetValue("Compass", "WSW", compassp[11]); + compassp[12] = ini.GetValue("Compass", "W", compassp[12]); + compassp[13] = ini.GetValue("Compass", "WNW", compassp[13]); + compassp[14] = ini.GetValue("Compass", "NW", compassp[14]); + compassp[15] = ini.GetValue("Compass", "NNW", compassp[15]); + // graphs + /* + SmallGraphWindSpeedTitle = ini.GetValue("Graphs", "SmallGraphWindSpeedTitle", "Wind Speed"); + SmallGraphOutsideTemperatureTitle = ini.GetValue("Graphs", "SmallGraphOutsideTemperatureTitle", "Outside Temperature"); + SmallGraphInsideTemperatureTitle = ini.GetValue("Graphs", "SmallGraphInsideTemperatureTitle", "Inside Temperature"); + SmallGraphPressureTitle = ini.GetValue("Graphs", "SmallGraphPressureTitle", "Pressure"); + SmallGraphRainfallRateTitle = ini.GetValue("Graphs", "SmallGraphRainfallRateTitle", "Rainfall Rate"); + SmallGraphWindDirectionTitle = ini.GetValue("Graphs", "SmallGraphWindDirectionTitle", "Wind Direction"); + SmallGraphTempMinMaxAvgTitle = ini.GetValue("Graphs", "SmallGraphTempMinMaxAvgTitle", "Temp Min/Max/Avg"); + SmallGraphHumidityTitle = ini.GetValue("Graphs", "SmallGraphHumidityTitle", "Humidity"); + SmallGraphRainTodayTitle = ini.GetValue("Graphs", "SmallGraphRainTodayTitle", "Rain Today"); + SmallGraphDailyRainTitle = ini.GetValue("Graphs", "SmallGraphDailyRainTitle", "Daily Rain"); + SmallGraphSolarTitle = ini.GetValue("Graphs", "SmallGraphSolarTitle", "Solar Radiation"); + SmallGraphUVTitle = ini.GetValue("Graphs", "SmallGraphUVTitle", "UV Index"); + SmallGraphSunshineTitle = ini.GetValue("Graphs", "SmallGraphSunshineTitle", "Daily Sunshine (hrs)"); + + LargeGraphWindSpeedTitle = ini.GetValue("Graphs", "LargeGraphWindSpeedTitle", "Wind Speed"); + LargeGraphWindGustTitle = ini.GetValue("Graphs", "LargeGraphWindGustTitle", "Wind Gust"); + LargeGraphOutsideTempTitle = ini.GetValue("Graphs", "LargeGraphOutsideTempTitle", "Temperature"); + LargeGraphHeatIndexTitle = ini.GetValue("Graphs", "LargeGraphHeatIndexTitle", "Heat Index"); + LargeGraphDewPointTitle = ini.GetValue("Graphs", "LargeGraphDewPointTitle", "Dew Point"); + LargeGraphWindChillTitle = ini.GetValue("Graphs", "LargeGraphWindChillTitle", "Wind Chill"); + LargeGraphApparentTempTitle = ini.GetValue("Graphs", "LargeGraphApparentTempTitle", "Apparent Temperature"); + LargeGraphInsideTempTitle = ini.GetValue("Graphs", "LargeGraphInsideTempTitle", "Inside Temperature"); + LargeGraphPressureTitle = ini.GetValue("Graphs", "LargeGraphPressureTitle", "Pressure"); + LargeGraphRainfallRateTitle = ini.GetValue("Graphs", "LargeGraphRainfallRateTitle", "Rainfall Rate"); + LargeGraphWindDirectionTitle = ini.GetValue("Graphs", "LargeGraphWindDirectionTitle", "Wind Direction"); + LargeGraphWindAvgDirectionTitle = ini.GetValue("Graphs", "LargeGraphWindAvgDirectionTitle", "Average"); + LargeGraphMinTempTitle = ini.GetValue("Graphs", "LargeGraphMinTempTitle", "Min Temp"); + LargeGraphMaxTempTitle = ini.GetValue("Graphs", "LargeGraphMaxTempTitle", "Max Temp"); + LargeGraphAvgTempTitle = ini.GetValue("Graphs", "LargeGraphAvgTempTitle", "Avg Temp"); + LargeGraphInsideHumidityTitle = ini.GetValue("Graphs", "LargeGraphInsideHumidityTitle", "Inside Humidity"); + LargeGraphOutsideHumidityTitle = ini.GetValue("Graphs", "LargeGraphOutsideHumidityTitle", "Outside Humidity"); + LargeGraphRainfallTodayTitle = ini.GetValue("Graphs", "LargeGraphRainfallTodayTitle", "Rainfall Today"); + LargeGraphDailyRainfallTitle = ini.GetValue("Graphs", "LargeGraphDailyRainfallTitle", "Daily Rainfall"); + LargeGraphSolarTitle = ini.GetValue("Graphs", "LargeGraphSolarTitle", "Solar Radiation"); + LargeGraphMaxSolarTitle = ini.GetValue("Graphs", "LargeGraphMaxSolarTitle", "Theoretical Max"); + LargeGraphUVTitle = ini.GetValue("Graphs", "LargeGraphUVTitle", "UV Index"); + LargeGraphSunshineTitle = ini.GetValue("Graphs", "LargeGraphSunshineTitle", "Daily Sunshine (hrs)"); + */ + // Extra sensor captions + WMR200ExtraChannelCaptions[1] = ini.GetValue("ExtraSensorCaptions", "Solar", WMR200ExtraChannelCaptions[1]); + WMR200ExtraChannelCaptions[2] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel2", WMR200ExtraChannelCaptions[2]); + WMR200ExtraChannelCaptions[3] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel3", WMR200ExtraChannelCaptions[3]); + WMR200ExtraChannelCaptions[4] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel4", WMR200ExtraChannelCaptions[4]); + WMR200ExtraChannelCaptions[5] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel5", WMR200ExtraChannelCaptions[5]); + WMR200ExtraChannelCaptions[6] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel6", WMR200ExtraChannelCaptions[6]); + WMR200ExtraChannelCaptions[7] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel7", WMR200ExtraChannelCaptions[7]); + WMR200ExtraChannelCaptions[8] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel8", WMR200ExtraChannelCaptions[8]); + WMR200ExtraChannelCaptions[9] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel9", WMR200ExtraChannelCaptions[9]); + WMR200ExtraChannelCaptions[10] = ini.GetValue("ExtraSensorCaptions", "ExtraChannel10", WMR200ExtraChannelCaptions[10]); + + // Extra temperature captions (for Extra Sensor Data screen) + ExtraTempCaptions[1] = ini.GetValue("ExtraTempCaptions", "Sensor1", ExtraTempCaptions[1]); + ExtraTempCaptions[2] = ini.GetValue("ExtraTempCaptions", "Sensor2", ExtraTempCaptions[2]); + ExtraTempCaptions[3] = ini.GetValue("ExtraTempCaptions", "Sensor3", ExtraTempCaptions[3]); + ExtraTempCaptions[4] = ini.GetValue("ExtraTempCaptions", "Sensor4", ExtraTempCaptions[4]); + ExtraTempCaptions[5] = ini.GetValue("ExtraTempCaptions", "Sensor5", ExtraTempCaptions[5]); + ExtraTempCaptions[6] = ini.GetValue("ExtraTempCaptions", "Sensor6", ExtraTempCaptions[6]); + ExtraTempCaptions[7] = ini.GetValue("ExtraTempCaptions", "Sensor7", ExtraTempCaptions[7]); + ExtraTempCaptions[8] = ini.GetValue("ExtraTempCaptions", "Sensor8", ExtraTempCaptions[8]); + ExtraTempCaptions[9] = ini.GetValue("ExtraTempCaptions", "Sensor9", ExtraTempCaptions[9]); + ExtraTempCaptions[10] = ini.GetValue("ExtraTempCaptions", "Sensor10", ExtraTempCaptions[10]); + + // Extra humidity captions (for Extra Sensor Data screen) + ExtraHumCaptions[1] = ini.GetValue("ExtraHumCaptions", "Sensor1", ExtraHumCaptions[1]); + ExtraHumCaptions[2] = ini.GetValue("ExtraHumCaptions", "Sensor2", ExtraHumCaptions[2]); + ExtraHumCaptions[3] = ini.GetValue("ExtraHumCaptions", "Sensor3", ExtraHumCaptions[3]); + ExtraHumCaptions[4] = ini.GetValue("ExtraHumCaptions", "Sensor4", ExtraHumCaptions[4]); + ExtraHumCaptions[5] = ini.GetValue("ExtraHumCaptions", "Sensor5", ExtraHumCaptions[5]); + ExtraHumCaptions[6] = ini.GetValue("ExtraHumCaptions", "Sensor6", ExtraHumCaptions[6]); + ExtraHumCaptions[7] = ini.GetValue("ExtraHumCaptions", "Sensor7", ExtraHumCaptions[7]); + ExtraHumCaptions[8] = ini.GetValue("ExtraHumCaptions", "Sensor8", ExtraHumCaptions[8]); + ExtraHumCaptions[9] = ini.GetValue("ExtraHumCaptions", "Sensor9", ExtraHumCaptions[9]); + ExtraHumCaptions[10] = ini.GetValue("ExtraHumCaptions", "Sensor10", ExtraHumCaptions[10]); + + // Extra dew point captions (for Extra Sensor Data screen) + ExtraDPCaptions[1] = ini.GetValue("ExtraDPCaptions", "Sensor1", ExtraDPCaptions[1]); + ExtraDPCaptions[2] = ini.GetValue("ExtraDPCaptions", "Sensor2", ExtraDPCaptions[2]); + ExtraDPCaptions[3] = ini.GetValue("ExtraDPCaptions", "Sensor3", ExtraDPCaptions[3]); + ExtraDPCaptions[4] = ini.GetValue("ExtraDPCaptions", "Sensor4", ExtraDPCaptions[4]); + ExtraDPCaptions[5] = ini.GetValue("ExtraDPCaptions", "Sensor5", ExtraDPCaptions[5]); + ExtraDPCaptions[6] = ini.GetValue("ExtraDPCaptions", "Sensor6", ExtraDPCaptions[6]); + ExtraDPCaptions[7] = ini.GetValue("ExtraDPCaptions", "Sensor7", ExtraDPCaptions[7]); + ExtraDPCaptions[8] = ini.GetValue("ExtraDPCaptions", "Sensor8", ExtraDPCaptions[8]); + ExtraDPCaptions[9] = ini.GetValue("ExtraDPCaptions", "Sensor9", ExtraDPCaptions[9]); + ExtraDPCaptions[10] = ini.GetValue("ExtraDPCaptions", "Sensor10", ExtraDPCaptions[10]); + + // soil temp captions (for Extra Sensor Data screen) + SoilTempCaptions[1] = ini.GetValue("SoilTempCaptions", "Sensor1", SoilTempCaptions[1]); + SoilTempCaptions[2] = ini.GetValue("SoilTempCaptions", "Sensor2", SoilTempCaptions[2]); + SoilTempCaptions[3] = ini.GetValue("SoilTempCaptions", "Sensor3", SoilTempCaptions[3]); + SoilTempCaptions[4] = ini.GetValue("SoilTempCaptions", "Sensor4", SoilTempCaptions[4]); + SoilTempCaptions[5] = ini.GetValue("SoilTempCaptions", "Sensor5", SoilTempCaptions[5]); + SoilTempCaptions[6] = ini.GetValue("SoilTempCaptions", "Sensor6", SoilTempCaptions[6]); + SoilTempCaptions[7] = ini.GetValue("SoilTempCaptions", "Sensor7", SoilTempCaptions[7]); + SoilTempCaptions[8] = ini.GetValue("SoilTempCaptions", "Sensor8", SoilTempCaptions[8]); + SoilTempCaptions[9] = ini.GetValue("SoilTempCaptions", "Sensor9", SoilTempCaptions[9]); + SoilTempCaptions[10] = ini.GetValue("SoilTempCaptions", "Sensor10", SoilTempCaptions[10]); + SoilTempCaptions[11] = ini.GetValue("SoilTempCaptions", "Sensor11", SoilTempCaptions[11]); + SoilTempCaptions[12] = ini.GetValue("SoilTempCaptions", "Sensor12", SoilTempCaptions[12]); + SoilTempCaptions[13] = ini.GetValue("SoilTempCaptions", "Sensor13", SoilTempCaptions[13]); + SoilTempCaptions[14] = ini.GetValue("SoilTempCaptions", "Sensor14", SoilTempCaptions[14]); + SoilTempCaptions[15] = ini.GetValue("SoilTempCaptions", "Sensor15", SoilTempCaptions[15]); + SoilTempCaptions[16] = ini.GetValue("SoilTempCaptions", "Sensor16", SoilTempCaptions[16]); + + // soil moisture captions (for Extra Sensor Data screen) + SoilMoistureCaptions[1] = ini.GetValue("SoilMoistureCaptions", "Sensor1", SoilMoistureCaptions[1]); + SoilMoistureCaptions[2] = ini.GetValue("SoilMoistureCaptions", "Sensor2", SoilMoistureCaptions[2]); + SoilMoistureCaptions[3] = ini.GetValue("SoilMoistureCaptions", "Sensor3", SoilMoistureCaptions[3]); + SoilMoistureCaptions[4] = ini.GetValue("SoilMoistureCaptions", "Sensor4", SoilMoistureCaptions[4]); + SoilMoistureCaptions[5] = ini.GetValue("SoilMoistureCaptions", "Sensor5", SoilMoistureCaptions[5]); + SoilMoistureCaptions[6] = ini.GetValue("SoilMoistureCaptions", "Sensor6", SoilMoistureCaptions[6]); + SoilMoistureCaptions[7] = ini.GetValue("SoilMoistureCaptions", "Sensor7", SoilMoistureCaptions[7]); + SoilMoistureCaptions[8] = ini.GetValue("SoilMoistureCaptions", "Sensor8", SoilMoistureCaptions[8]); + SoilMoistureCaptions[9] = ini.GetValue("SoilMoistureCaptions", "Sensor9", SoilMoistureCaptions[9]); + SoilMoistureCaptions[10] = ini.GetValue("SoilMoistureCaptions", "Sensor10", SoilMoistureCaptions[10]); + SoilMoistureCaptions[11] = ini.GetValue("SoilMoistureCaptions", "Sensor11", SoilMoistureCaptions[11]); + SoilMoistureCaptions[12] = ini.GetValue("SoilMoistureCaptions", "Sensor12", SoilMoistureCaptions[12]); + SoilMoistureCaptions[13] = ini.GetValue("SoilMoistureCaptions", "Sensor13", SoilMoistureCaptions[13]); + SoilMoistureCaptions[14] = ini.GetValue("SoilMoistureCaptions", "Sensor14", SoilMoistureCaptions[14]); + SoilMoistureCaptions[15] = ini.GetValue("SoilMoistureCaptions", "Sensor15", SoilMoistureCaptions[15]); + SoilMoistureCaptions[16] = ini.GetValue("SoilMoistureCaptions", "Sensor16", SoilMoistureCaptions[16]); + + // leaf temp captions (for Extra Sensor Data screen) + LeafTempCaptions[1] = ini.GetValue("LeafTempCaptions", "Sensor1", LeafTempCaptions[1]); + LeafTempCaptions[2] = ini.GetValue("LeafTempCaptions", "Sensor2", LeafTempCaptions[2]); + LeafTempCaptions[3] = ini.GetValue("LeafTempCaptions", "Sensor3", LeafTempCaptions[3]); + LeafTempCaptions[4] = ini.GetValue("LeafTempCaptions", "Sensor4", LeafTempCaptions[4]); + + // leaf wetness captions (for Extra Sensor Data screen) + LeafWetnessCaptions[1] = ini.GetValue("LeafWetnessCaptions", "Sensor1", LeafWetnessCaptions[1]); + LeafWetnessCaptions[2] = ini.GetValue("LeafWetnessCaptions", "Sensor2", LeafWetnessCaptions[2]); + LeafWetnessCaptions[3] = ini.GetValue("LeafWetnessCaptions", "Sensor3", LeafWetnessCaptions[3]); + LeafWetnessCaptions[4] = ini.GetValue("LeafWetnessCaptions", "Sensor4", LeafWetnessCaptions[4]); + LeafWetnessCaptions[5] = ini.GetValue("LeafWetnessCaptions", "Sensor5", LeafWetnessCaptions[5]); + LeafWetnessCaptions[6] = ini.GetValue("LeafWetnessCaptions", "Sensor6", LeafWetnessCaptions[6]); + LeafWetnessCaptions[7] = ini.GetValue("LeafWetnessCaptions", "Sensor7", LeafWetnessCaptions[7]); + LeafWetnessCaptions[8] = ini.GetValue("LeafWetnessCaptions", "Sensor8", LeafWetnessCaptions[8]); + + // air quality captions (for Extra Sensor Data screen) + AirQualityCaptions[1] = ini.GetValue("AirQualityCaptions", "Sensor1", AirQualityCaptions[1]); + AirQualityCaptions[2] = ini.GetValue("AirQualityCaptions", "Sensor2", AirQualityCaptions[2]); + AirQualityCaptions[3] = ini.GetValue("AirQualityCaptions", "Sensor3", AirQualityCaptions[3]); + AirQualityCaptions[4] = ini.GetValue("AirQualityCaptions", "Sensor4", AirQualityCaptions[4]); + AirQualityAvgCaptions[1] = ini.GetValue("AirQualityCaptions", "SensorAvg1", AirQualityAvgCaptions[1]); + AirQualityAvgCaptions[2] = ini.GetValue("AirQualityCaptions", "SensorAvg2", AirQualityAvgCaptions[2]); + AirQualityAvgCaptions[3] = ini.GetValue("AirQualityCaptions", "SensorAvg3", AirQualityAvgCaptions[3]); + AirQualityAvgCaptions[4] = ini.GetValue("AirQualityCaptions", "SensorAvg4", AirQualityAvgCaptions[4]); + + // CO2 captions - Ecowitt WH45 sensor + CO2_CurrentCaption = ini.GetValue("CO2Captions", "CO2-Current", CO2_CurrentCaption); + CO2_24HourCaption = ini.GetValue("CO2Captions", "CO2-24hr", CO2_24HourCaption); + CO2_pm2p5Caption = ini.GetValue("CO2Captions", "CO2-Pm2p5", CO2_pm2p5Caption); + CO2_pm2p5_24hrCaption = ini.GetValue("CO2Captions", "CO2-Pm2p5-24hr", CO2_pm2p5_24hrCaption); + CO2_pm10Caption = ini.GetValue("CO2Captions", "CO2-Pm10", CO2_pm10Caption); + CO2_pm10_24hrCaption = ini.GetValue("CO2Captions", "CO2-Pm10-24hr", CO2_pm10_24hrCaption); + + // User temperature captions (for Extra Sensor Data screen) + UserTempCaptions[1] = ini.GetValue("UserTempCaptions", "Sensor1", UserTempCaptions[1]); + UserTempCaptions[2] = ini.GetValue("UserTempCaptions", "Sensor2", UserTempCaptions[2]); + UserTempCaptions[3] = ini.GetValue("UserTempCaptions", "Sensor3", UserTempCaptions[3]); + UserTempCaptions[4] = ini.GetValue("UserTempCaptions", "Sensor4", UserTempCaptions[4]); + UserTempCaptions[5] = ini.GetValue("UserTempCaptions", "Sensor5", UserTempCaptions[5]); + UserTempCaptions[6] = ini.GetValue("UserTempCaptions", "Sensor6", UserTempCaptions[6]); + UserTempCaptions[7] = ini.GetValue("UserTempCaptions", "Sensor7", UserTempCaptions[7]); + UserTempCaptions[8] = ini.GetValue("UserTempCaptions", "Sensor8", UserTempCaptions[8]); + + thereWillBeMinSLessDaylightTomorrow = ini.GetValue("Solar", "LessDaylightTomorrow", thereWillBeMinSLessDaylightTomorrow); + thereWillBeMinSMoreDaylightTomorrow = ini.GetValue("Solar", "MoreDaylightTomorrow", thereWillBeMinSMoreDaylightTomorrow); + + DavisForecast1[0] = ini.GetValue("DavisForecast1", "forecast1", DavisForecast1[0]); + DavisForecast1[1] = ini.GetValue("DavisForecast1", "forecast2", DavisForecast1[1]) + " "; + DavisForecast1[2] = ini.GetValue("DavisForecast1", "forecast3", DavisForecast1[2]) + " "; + DavisForecast1[3] = ini.GetValue("DavisForecast1", "forecast4", DavisForecast1[3]) + " "; + DavisForecast1[4] = ini.GetValue("DavisForecast1", "forecast5", DavisForecast1[4]) + " "; + DavisForecast1[5] = ini.GetValue("DavisForecast1", "forecast6", DavisForecast1[5]) + " "; + DavisForecast1[6] = ini.GetValue("DavisForecast1", "forecast7", DavisForecast1[6]) + " "; + DavisForecast1[7] = ini.GetValue("DavisForecast1", "forecast8", DavisForecast1[7]) + " "; + DavisForecast1[8] = ini.GetValue("DavisForecast1", "forecast9", DavisForecast1[8]) + " "; + DavisForecast1[9] = ini.GetValue("DavisForecast1", "forecast10", DavisForecast1[9]) + " "; + DavisForecast1[10] = ini.GetValue("DavisForecast1", "forecast11", DavisForecast1[10]) + " "; + DavisForecast1[11] = ini.GetValue("DavisForecast1", "forecast12", DavisForecast1[11]) + " "; + DavisForecast1[12] = ini.GetValue("DavisForecast1", "forecast13", DavisForecast1[12]) + " "; + DavisForecast1[13] = ini.GetValue("DavisForecast1", "forecast14", DavisForecast1[13]) + " "; + DavisForecast1[14] = ini.GetValue("DavisForecast1", "forecast15", DavisForecast1[14]) + " "; + DavisForecast1[15] = ini.GetValue("DavisForecast1", "forecast16", DavisForecast1[15]) + " "; + DavisForecast1[16] = ini.GetValue("DavisForecast1", "forecast17", DavisForecast1[16]) + " "; + DavisForecast1[17] = ini.GetValue("DavisForecast1", "forecast18", DavisForecast1[17]) + " "; + DavisForecast1[18] = ini.GetValue("DavisForecast1", "forecast19", DavisForecast1[18]) + " "; + DavisForecast1[19] = ini.GetValue("DavisForecast1", "forecast20", DavisForecast1[19]) + " "; + DavisForecast1[20] = ini.GetValue("DavisForecast1", "forecast21", DavisForecast1[20]) + " "; + DavisForecast1[21] = ini.GetValue("DavisForecast1", "forecast22", DavisForecast1[21]) + " "; + DavisForecast1[22] = ini.GetValue("DavisForecast1", "forecast23", DavisForecast1[22]) + " "; + DavisForecast1[23] = ini.GetValue("DavisForecast1", "forecast24", DavisForecast1[23]) + " "; + DavisForecast1[24] = ini.GetValue("DavisForecast1", "forecast25", DavisForecast1[24]) + " "; + DavisForecast1[25] = ini.GetValue("DavisForecast1", "forecast26", DavisForecast1[25]) + " "; + DavisForecast1[26] = ini.GetValue("DavisForecast1", "forecast27", DavisForecast1[26]); + + DavisForecast2[0] = ini.GetValue("DavisForecast2", "forecast1", DavisForecast2[0]); + DavisForecast2[1] = ini.GetValue("DavisForecast2", "forecast2", DavisForecast2[1]) + " "; + DavisForecast2[2] = ini.GetValue("DavisForecast2", "forecast3", DavisForecast2[2]) + " "; + DavisForecast2[3] = ini.GetValue("DavisForecast2", "forecast4", DavisForecast2[3]) + " "; + DavisForecast2[4] = ini.GetValue("DavisForecast2", "forecast5", DavisForecast2[5]) + " "; + DavisForecast2[5] = ini.GetValue("DavisForecast2", "forecast6", DavisForecast2[5]) + " "; + DavisForecast2[6] = ini.GetValue("DavisForecast2", "forecast7", DavisForecast2[6]) + " "; + DavisForecast2[7] = ini.GetValue("DavisForecast2", "forecast8", DavisForecast2[7]) + " "; + DavisForecast2[8] = ini.GetValue("DavisForecast2", "forecast9", DavisForecast2[8]) + " "; + DavisForecast2[9] = ini.GetValue("DavisForecast2", "forecast10", DavisForecast2[9]) + " "; + DavisForecast2[10] = ini.GetValue("DavisForecast2", "forecast11", DavisForecast2[10]) + " "; + DavisForecast2[11] = ini.GetValue("DavisForecast2", "forecast12", DavisForecast2[11]) + " "; + DavisForecast2[12] = ini.GetValue("DavisForecast2", "forecast13", DavisForecast2[12]) + " "; + DavisForecast2[13] = ini.GetValue("DavisForecast2", "forecast14", DavisForecast2[13]) + " "; + DavisForecast2[14] = ini.GetValue("DavisForecast2", "forecast15", DavisForecast2[14]) + " "; + DavisForecast2[15] = ini.GetValue("DavisForecast2", "forecast16", DavisForecast2[15]) + " "; + DavisForecast2[16] = ini.GetValue("DavisForecast2", "forecast17", DavisForecast2[16]) + " "; + DavisForecast2[17] = ini.GetValue("DavisForecast2", "forecast18", DavisForecast2[17]) + " "; + DavisForecast2[18] = ini.GetValue("DavisForecast2", "forecast19", DavisForecast2[18]) + " "; + + DavisForecast3[0] = ini.GetValue("DavisForecast3", "forecast1", DavisForecast3[0]); + DavisForecast3[1] = ini.GetValue("DavisForecast3", "forecast2", DavisForecast3[1]); + DavisForecast3[2] = ini.GetValue("DavisForecast3", "forecast3", DavisForecast3[2]); + DavisForecast3[3] = ini.GetValue("DavisForecast3", "forecast4", DavisForecast3[3]); + DavisForecast3[4] = ini.GetValue("DavisForecast3", "forecast5", DavisForecast3[4]); + DavisForecast3[5] = ini.GetValue("DavisForecast3", "forecast6", DavisForecast3[5]); + DavisForecast3[6] = ini.GetValue("DavisForecast3", "forecast7", DavisForecast3[6]); + + // alarm emails + AlarmEmailSubject = ini.GetValue("AlarmEmails", "subject", "Cumulus MX Alarm"); + AlarmEmailPreamble = ini.GetValue("AlarmEmails", "preamble", "A Cumulus MX alarm has been triggered."); + HighGustAlarm.EmailMsg = ini.GetValue("AlarmEmails", "windGustAbove", "A wind gust above {0} {1} has occurred."); + HighPressAlarm.EmailMsg = ini.GetValue("AlarmEmails", "pressureAbove", "The pressure has risen above {0} {1}."); + HighTempAlarm.EmailMsg = ini.GetValue("AlarmEmails", "tempAbove", "The temperature has risen above {0} {1}."); + LowPressAlarm.EmailMsg = ini.GetValue("AlarmEmails", "pressBelow", "The pressure has fallen below {0} {1}."); + LowTempAlarm.EmailMsg = ini.GetValue("AlarmEmails", "tempBelow", "The temperature has fallen below {0} {1}."); + PressChangeAlarm.EmailMsgDn = ini.GetValue("AlarmEmails", "pressDown", "The pressure has decreased by more than {0} {1}."); + PressChangeAlarm.EmailMsgUp = ini.GetValue("AlarmEmails", "pressUp", "The pressure has increased by more than {0} {1}."); + HighRainTodayAlarm.EmailMsg = ini.GetValue("AlarmEmails", "rainAbove", "The rainfall today has exceeded {0} {1}."); + HighRainRateAlarm.EmailMsg = ini.GetValue("AlarmEmails", "rainRateAbove", "The rainfall rate has exceeded {0} {1}."); + SensorAlarm.EmailMsg = ini.GetValue("AlarmEmails", "sensorLost", "Contact has been lost with a remote sensor,"); + TempChangeAlarm.EmailMsgDn = ini.GetValue("AlarmEmails", "tempDown", "The temperature decreased by more than {0} {1}."); + TempChangeAlarm.EmailMsgUp = ini.GetValue("AlarmEmails", "tempUp", "The temperature has increased by more than {0} {1}."); + HighWindAlarm.EmailMsg = ini.GetValue("AlarmEmails", "windAbove", "The average wind speed has exceeded {0} {1}."); + DataStoppedAlarm.EmailMsg = ini.GetValue("AlarmEmails", "dataStopped", "Cumulus has stopped receiving data from your weather station."); + BatteryLowAlarm.EmailMsg = ini.GetValue("AlarmEmails", "batteryLow", "A low battery condition has been detected."); + SpikeAlarm.EmailMsg = ini.GetValue("AlarmEmails", "dataSpike", "A data spike from your weather station has been suppressed."); + UpgradeAlarm.EmailMsg = ini.GetValue("AlarmEmails", "upgrade", "An upgrade to Cumulus MX is now available."); } @@ -9495,6 +9611,8 @@ public class OpenWeatherMapNewStation public class Alarm { + public Cumulus cumulus { get; set; } + public bool Enabled { get; set; } public double Value { get; set; } public bool Sound { get; set; } @@ -9508,6 +9626,15 @@ public bool Triggered { if (value) { + // If we were not set before, so we need to send an email? + if (!triggered && Enabled && Email && cumulus.SmtpOptions.Enabled) + { + // Construct the message - preamble, plus values + var msg = cumulus.AlarmEmailPreamble + " " + string.Format(EmailMsg, Value, Units); + cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, false); + } + + // If we get a new trigger, record the time triggered = true; TriggeredTime = DateTime.Now; @@ -9533,8 +9660,11 @@ public bool Triggered } public DateTime TriggeredTime { get; set; } public bool Notify { get; set; } + public bool Email { get; set; } public bool Latch { get; set; } public int LatchHours { get; set; } + public string EmailMsg { get; set; } + public string Units { get; set; } } public class AlarmChange : Alarm @@ -9550,6 +9680,14 @@ public bool UpTriggered { if (value) { + // If we were not set before, so we need to send an email? + if (!upTriggered && Enabled && Email && cumulus.SmtpOptions.Enabled) + { + // Construct the message - preamble, plus values + var msg = Program.cumulus.AlarmEmailPreamble + " " + string.Format(EmailMsgUp, Value, Units); + cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, false); + } + // If we get a new trigger, record the time upTriggered = true; UpTriggeredTime = DateTime.Now; @@ -9584,6 +9722,14 @@ public bool DownTriggered { if (value) { + // If we were not set before, so we need to send an email? + if (!downTriggered && Enabled && Email && cumulus.SmtpOptions.Enabled) + { + // Construct the message - preamble, plus values + var msg = Program.cumulus.AlarmEmailPreamble + " " + string.Format(EmailMsgDn, Value, Units); + cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, false); + } + // If we get a new trigger, record the time downTriggered = true; DownTriggeredTime = DateTime.Now; @@ -9609,6 +9755,10 @@ public bool DownTriggered } public DateTime DownTriggeredTime { get; set; } + + public string EmailMsgUp { get; set; } + public string EmailMsgDn { get; set; } + } public class WebUploadService @@ -9682,4 +9832,26 @@ public class DisplayOptions public bool ShowSolar { get; set; } public bool ShowUV { get; set; } } + + public class AlarmEmails + { + public string Preamble { get; set; } + public string HighGust { get; set; } + public string HighWind { get; set; } + public string HighTemp { get; set; } + public string LowTemp { get; set; } + public string TempDown { get; set; } + public string TempUp { get; set; } + public string HighPress { get; set; } + public string LowPress { get; set; } + public string PressDown { get; set; } + public string PressUp { get; set; } + public string Rain { get; set; } + public string RainRate { get; set; } + public string SensorLost { get; set; } + public string DataStopped { get; set; } + public string BatteryLow { get; set; } + public string DataSpike { get; set; } + public string Upgrade { get; set; } + } } diff --git a/CumulusMX/CumulusMX.csproj b/CumulusMX/CumulusMX.csproj index 1e36faf0..9e6f996e 100644 --- a/CumulusMX/CumulusMX.csproj +++ b/CumulusMX/CumulusMX.csproj @@ -82,6 +82,9 @@ true + + ..\packages\Portable.BouncyCastle.1.8.10\lib\net40\BouncyCastle.Crypto.dll + False ..\..\CumulusMX\CumulusMX\lib\Devart.Data.dll @@ -104,6 +107,12 @@ False ..\packages\linqtotwitter.3.1.1\lib\net45\LinqToTwitterPcl.dll + + ..\packages\MailKit.2.11.1\lib\net45\MailKit.dll + + + ..\packages\MimeKit.2.11.0\lib\net45\MimeKit.dll + ..\packages\MQTTnet.3.0.12\lib\net452\MQTTnet.dll @@ -151,6 +160,7 @@ ..\packages\System.Runtime.CompilerServices.Unsafe.4.7.1\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll + @@ -183,6 +193,7 @@ + diff --git a/CumulusMX/EmailSender.cs b/CumulusMX/EmailSender.cs new file mode 100644 index 00000000..4a3404a5 --- /dev/null +++ b/CumulusMX/EmailSender.cs @@ -0,0 +1,104 @@ +using System; +using MailKit.Net.Smtp; +using MailKit.Net.Pop3; +using MimeKit; +using System.Threading.Tasks; +using System.Text.RegularExpressions; +using MailKit; + +namespace CumulusMX +{ + public class EmailSender + { + static Regex ValidEmailRegex = CreateValidEmailRegex(); + + private Cumulus cumulus; + + public EmailSender(Cumulus cumulus) + { + this.cumulus = cumulus; + } + + + public async Task SendEmail(string[] to, string from, string subject, string message, bool isHTML) + { + try + { + cumulus.LogDebugMessage($"SendEmail: Sending email, to [{string.Join("; ", to)}], subject [{subject}], body [{message}]..."); + + var m = new MimeMessage(); + m.From.Add(new MailboxAddress("", from)); + foreach (var addr in to) + { + m.To.Add(new MailboxAddress("", addr)); + } + m.Subject = subject; + + BodyBuilder bodyBuilder = new BodyBuilder(); + if (isHTML) + { + bodyBuilder.HtmlBody = message; + } + else + { + bodyBuilder.TextBody = message; + } + + m.Body = bodyBuilder.ToMessageBody(); + + using (SmtpClient client = new SmtpClient(cumulus.SmtpOptions.Logging ? new ProtocolLogger("MXdiags/smtp.log") : null)) + { + await client.ConnectAsync(cumulus.SmtpOptions.Server, cumulus.SmtpOptions.Port, MailKit.Security.SecureSocketOptions.StartTlsWhenAvailable); + //client.Connect(cumulus.SmtpOptions.Server, cumulus.SmtpOptions.Port, MailKit.Security.SecureSocketOptions.StartTlsWhenAvailable); + + // Note: since we don't have an OAuth2 token, disable + // the XOAUTH2 authentication mechanism. + client.AuthenticationMechanisms.Remove("XOAUTH2"); + + if (cumulus.SmtpOptions.RequiresAuthentication) + { + await client.AuthenticateAsync(cumulus.SmtpOptions.User, cumulus.SmtpOptions.Password); + //client.Authenticate(cumulus.SmtpOptions.User, cumulus.SmtpOptions.Password); + } + + await client.SendAsync(m); + client.Disconnect(true); + } + } + catch (Exception e) + { + cumulus.LogMessage("SendEmail: Error - " + e); + + } + } + + private static Regex CreateValidEmailRegex() + { + string validEmailPattern = @"^(?!\.)(""([^""\r\\]|\\[""\r\\])*""|" + + @"([-a-z0-9!#$%&'*+/=?^_`{|}~]|(? + + + diff --git a/CumulusMX/webtags.cs b/CumulusMX/webtags.cs index b472b62f..d1713114 100644 --- a/CumulusMX/webtags.cs +++ b/CumulusMX/webtags.cs @@ -3133,10 +3133,10 @@ private string TagSunshineHours(Dictionary tagParams) return CheckRcDp(station.SunshineHours, tagParams, cumulus.SunshineDPlaces); } - private string TagThwIndex(Dictionary tagParams) - { - return CheckRcDp(station.THWIndex, tagParams, 1); - } + //private string TagThwIndex(Dictionary tagParams) + //{ + // return CheckRcDp(station.THWIndex, tagParams, 1); + //} private string TagThswIndex(Dictionary tagParams) { @@ -5379,7 +5379,7 @@ public void InitialiseWebtags() { "IsSunny", TagIsSunny }, { "IsRaining", TagIsRaining }, { "IsFreezing", TagIsFreezing }, - { "THWindex", TagThwIndex }, + //{ "THWindex", TagThwIndex }, { "THSWindex", TagThswIndex }, { "ExtraTemp1", TagExtraTemp1 }, { "ExtraTemp2", TagExtraTemp2 }, diff --git a/Updates.txt b/Updates.txt index 11fe04a1..d751a7bd 100644 --- a/Updates.txt +++ b/Updates.txt @@ -1,3 +1,30 @@ +3.11.0 - b3123 +—————————————— +- Fix: Remove THW Index in /web/websitedataT.json, and default web site index.htm +- Fix: Remove <#THWindex> web tag from Cumulus MX +- Fix: OpenWeatherMap - create new station was not forcing the use of dot decimals for lat/long values + +- New: Adds Email support for Alarms + - Creates a new section in Cumulus.ini: + [SMTP] + ServerName= + Port=587 + UseSSL=0 + RequiresAuthentication=0 + User= + Password= + Logging=0 + - Adds to the Alarms section in Cumulus.ini + [Alarms] + xxxxxAlarmEmail=0 + FromEmail= + DestEmail= + - Creates a new section in strings.ini, with email text + [AlarmEmails] + +- Changed: Moved FTP Logging option from Internet Settings to Program Setting to collect all the logging options in one place + + 3.10.5 - b3122 —————————————— - Fix: Comma decimal issues with OpenWeatherMap and other third party uploads From 12b8b15228fd758988ae78a2ab471b4337324654 Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Fri, 9 Apr 2021 12:12:03 +0100 Subject: [PATCH 02/16] Stop all activitiy on Data Stopped condition --- CumulusMX/Cumulus.cs | 424 ++++++++++++++++++++---------------- CumulusMX/WeatherStation.cs | 19 ++ Updates.txt | 1 + 3 files changed, 258 insertions(+), 186 deletions(-) diff --git a/CumulusMX/Cumulus.cs b/CumulusMX/Cumulus.cs index aea1f2de..17acb4c5 100644 --- a/CumulusMX/Cumulus.cs +++ b/CumulusMX/Cumulus.cs @@ -1538,7 +1538,8 @@ internal void SetUpHttpProxy() private void CustomHttpSecondsTimerTick(object sender, ElapsedEventArgs e) { - CustomHttpSecondsUpdate(); + if (!station.DataStopped) + CustomHttpSecondsUpdate(); } internal void SetStartOfRealtimeInsertSQL() @@ -1866,6 +1867,12 @@ private void APRSTimerTick(object sender, ElapsedEventArgs e) private void WebTimerTick(object sender, ElapsedEventArgs e) { + if (station.DataStopped) + { + // No data coming in, do not do anything else + return; + } + if (WebUpdating == 1) { LogMessage("Warning, previous web update is still in progress, first chance, skipping this interval"); @@ -1873,7 +1880,7 @@ private void WebTimerTick(object sender, ElapsedEventArgs e) } else if (WebUpdating >= 2) { - LogMessage("Warning, previous web update is still in progress,second chance, aborting connection"); + LogMessage("Warning, previous web update is still in progress, second chance, aborting connection"); if (ftpThread.ThreadState == System.Threading.ThreadState.Running) ftpThread.Abort(); LogMessage("Trying new web update"); @@ -1896,6 +1903,12 @@ private void TwitterTimerTick(object sender, ElapsedEventArgs e) internal async void UpdateTwitter() { + if (station.DataStopped) + { + // No data coming in, do nothing + return; + } + LogDebugMessage("Starting Twitter update"); var auth = new XAuthAuthorizer { @@ -2013,7 +2026,8 @@ private void OpenWeatherMapTimerTick(object sender, ElapsedEventArgs e) public void MQTTTimerTick(object sender, ElapsedEventArgs e) { - MqttPublisher.UpdateMQTTfeed("Interval"); + if (!station.DataStopped) + MqttPublisher.UpdateMQTTfeed("Interval"); } /* @@ -2045,255 +2059,269 @@ private void WowTimerTick(object sender, ElapsedEventArgs e) internal async void UpdateWunderground(DateTime timestamp) { - if (!Wund.Updating) + if (Wund.Updating || station.DataStopped) { - Wund.Updating = true; + // No data coming in, do not do anything + return; + } - string pwstring; - string URL = station.GetWundergroundURL(out pwstring, timestamp, false); + Wund.Updating = true; - string starredpwstring = "&PASSWORD=" + new string('*', Wund.PW.Length); + string pwstring; + string URL = station.GetWundergroundURL(out pwstring, timestamp, false); - string logUrl = URL.Replace(pwstring, starredpwstring); - if (!Wund.RapidFireEnabled) - { - LogDebugMessage("WU URL: " + logUrl); - } + string starredpwstring = "&PASSWORD=" + new string('*', Wund.PW.Length); - try - { - HttpResponseMessage response = await WUhttpClient.GetAsync(URL); - var responseBodyAsText = await response.Content.ReadAsStringAsync(); - if (!Wund.RapidFireEnabled) - { - LogMessage("WU Response: " + response.StatusCode + ": " + responseBodyAsText); - } - } - catch (Exception ex) - { - LogMessage("WU update: " + ex.Message); - } - finally + string logUrl = URL.Replace(pwstring, starredpwstring); + if (!Wund.RapidFireEnabled) + { + LogDebugMessage("WU URL: " + logUrl); + } + + try + { + HttpResponseMessage response = await WUhttpClient.GetAsync(URL); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + if (!Wund.RapidFireEnabled) { - Wund.Updating = false; + LogMessage("WU Response: " + response.StatusCode + ": " + responseBodyAsText); } } + catch (Exception ex) + { + LogMessage("WU update: " + ex.Message); + } + finally + { + Wund.Updating = false; + } } internal async void UpdateWindy(DateTime timestamp) { - if (!Windy.Updating) + if (Windy.Updating || station.DataStopped) { - Windy.Updating = true; + // No data coming in, do not do anything + return; + } - string apistring; - string url = station.GetWindyURL(out apistring, timestamp); - string logUrl = url.Replace(apistring, "<>"); + Windy.Updating = true; - LogDebugMessage("Windy URL: " + logUrl); + string apistring; + string url = station.GetWindyURL(out apistring, timestamp); + string logUrl = url.Replace(apistring, "<>"); - try - { - HttpResponseMessage response = await WindyhttpClient.GetAsync(url); - var responseBodyAsText = await response.Content.ReadAsStringAsync(); - LogMessage("Windy Response: " + response.StatusCode + ": " + responseBodyAsText); - } - catch (Exception ex) - { - LogMessage("Windy update: " + ex.Message); - } - finally - { - Windy.Updating = false; - } + LogDebugMessage("Windy URL: " + logUrl); + + try + { + HttpResponseMessage response = await WindyhttpClient.GetAsync(url); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + LogMessage("Windy Response: " + response.StatusCode + ": " + responseBodyAsText); + } + catch (Exception ex) + { + LogMessage("Windy update: " + ex.Message); + } + finally + { + Windy.Updating = false; } } internal async void UpdateAwekas(DateTime timestamp) { - if (!AWEKAS.Updating) + if (AWEKAS.Updating || station.DataStopped) { - AWEKAS.Updating = true; + // No data coming in, do not do anything + return; + } - string pwstring; - string url = station.GetAwekasURLv4(out pwstring, timestamp); + AWEKAS.Updating = true; - string starredpwstring = ""; + string pwstring; + string url = station.GetAwekasURLv4(out pwstring, timestamp); - string logUrl = url.Replace(pwstring, starredpwstring); + string starredpwstring = ""; - LogDebugMessage("AWEKAS: URL = " + logUrl); + string logUrl = url.Replace(pwstring, starredpwstring); - try + LogDebugMessage("AWEKAS: URL = " + logUrl); + + try + { + using (HttpResponseMessage response = await AwekashttpClient.GetAsync(url)) { - using (HttpResponseMessage response = await AwekashttpClient.GetAsync(url)) + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + LogDebugMessage("AWEKAS Response code = " + response.StatusCode); + LogDataMessage("AWEKAS: Response text = " + responseBodyAsText); + //var respJson = JsonConvert.DeserializeObject(responseBodyAsText); + var respJson = JsonSerializer.DeserializeFromString(responseBodyAsText); + + // Check the status response + if (respJson.status == 2) + LogMessage("AWEKAS: Data stored OK"); + else if (respJson.status == 1) { - var responseBodyAsText = await response.Content.ReadAsStringAsync(); - LogDebugMessage("AWEKAS Response code = " + response.StatusCode); - LogDataMessage("AWEKAS: Response text = " + responseBodyAsText); - //var respJson = JsonConvert.DeserializeObject(responseBodyAsText); - var respJson = JsonSerializer.DeserializeFromString(responseBodyAsText); - - // Check the status response - if (respJson.status == 2) - LogMessage("AWEKAS: Data stored OK"); - else if (respJson.status == 1) - { - LogMessage("AWEKAS: Data PARIALLY stored"); - // TODO: Check errors and disabled - } - else if (respJson.status == 0) // Authenication error or rate limited + LogMessage("AWEKAS: Data PARIALLY stored"); + // TODO: Check errors and disabled + } + else if (respJson.status == 0) // Authenication error or rate limited + { + if (respJson.minuploadtime > 0 && respJson.authentication == 0) { - if (respJson.minuploadtime > 0 && respJson.authentication == 0) + LogMessage("AWEKAS: Authentication error"); + if (AWEKAS.Interval < 300) { - LogMessage("AWEKAS: Authentication error"); - if (AWEKAS.Interval < 300) - { - AWEKAS.RateLimited = true; - AWEKAS.OriginalInterval = AWEKAS.Interval; - AWEKAS.Interval = 300; - AwekasTimer.Enabled = false; - AWEKAS.SynchronisedUpdate = true; - LogMessage("AWEKAS: Temporarily increasing AWEKAS upload interval to 300 seconds due to authenication error"); - } + AWEKAS.RateLimited = true; + AWEKAS.OriginalInterval = AWEKAS.Interval; + AWEKAS.Interval = 300; + AwekasTimer.Enabled = false; + AWEKAS.SynchronisedUpdate = true; + LogMessage("AWEKAS: Temporarily increasing AWEKAS upload interval to 300 seconds due to authenication error"); } - else if (respJson.minuploadtime == 0) + } + else if (respJson.minuploadtime == 0) + { + LogMessage("AWEKAS: Too many requests, rate limited"); + // AWEKAS PLus allows minimum of 60 second updates, try that first + if (!AWEKAS.RateLimited && AWEKAS.Interval < 60) { - LogMessage("AWEKAS: Too many requests, rate limited"); - // AWEKAS PLus allows minimum of 60 second updates, try that first - if (!AWEKAS.RateLimited && AWEKAS.Interval < 60) - { - AWEKAS.OriginalInterval = AWEKAS.Interval; - AWEKAS.RateLimited = true; - AWEKAS.Interval = 60; - AwekasTimer.Enabled = false; - AWEKAS.SynchronisedUpdate = true; - LogMessage("AWEKAS: Temporarily increasing AWEKAS upload interval to 60 seconds due to rate limit"); - } - // AWEKAS normal allows minimum of 300 second updates, revert to that - else - { - AWEKAS.RateLimited = true; - AWEKAS.Interval = 300; - AwekasTimer.Interval = AWEKAS.Interval * 1000; - AwekasTimer.Enabled = !AWEKAS.SynchronisedUpdate; - AWEKAS.SynchronisedUpdate = AWEKAS.Interval % 60 == 0; - LogMessage("AWEKAS: Temporarily increasing AWEKAS upload interval to 300 seconds due to rate limit"); - } + AWEKAS.OriginalInterval = AWEKAS.Interval; + AWEKAS.RateLimited = true; + AWEKAS.Interval = 60; + AwekasTimer.Enabled = false; + AWEKAS.SynchronisedUpdate = true; + LogMessage("AWEKAS: Temporarily increasing AWEKAS upload interval to 60 seconds due to rate limit"); } + // AWEKAS normal allows minimum of 300 second updates, revert to that else { - LogMessage("AWEKAS: Unknown error"); + AWEKAS.RateLimited = true; + AWEKAS.Interval = 300; + AwekasTimer.Interval = AWEKAS.Interval * 1000; + AwekasTimer.Enabled = !AWEKAS.SynchronisedUpdate; + AWEKAS.SynchronisedUpdate = AWEKAS.Interval % 60 == 0; + LogMessage("AWEKAS: Temporarily increasing AWEKAS upload interval to 300 seconds due to rate limit"); } } - - // check the min upload time is greater than our upload time - if (respJson.status > 0 && respJson.minuploadtime > AWEKAS.OriginalInterval) - { - LogMessage($"AWEKAS: The minimum upload time to AWEKAS for your station is {respJson.minuploadtime} sec, Cumulus is configured for {AWEKAS.OriginalInterval} sec, increasing Cumulus interval to match AWEKAS"); - AWEKAS.Interval = respJson.minuploadtime; - WriteIniFile(); - AwekasTimer.Interval = AWEKAS.Interval * 1000; - AWEKAS.SynchronisedUpdate = AWEKAS.Interval % 60 == 0; - AwekasTimer.Enabled = !AWEKAS.SynchronisedUpdate; - // we got a successful upload, and reset the interval, so clear the rate limited values - AWEKAS.OriginalInterval = AWEKAS.Interval; - AWEKAS.RateLimited = false; - } - else if (AWEKAS.RateLimited && respJson.status > 0) + else { - // We are currently rate limited, it could have been a transient thing because - // we just got a valid response, and our interval is >= the minimum allowed. - // So we just undo the limit, and resume as before - LogMessage($"AWEKAS: Removing temporary increase in upload interval to 60 secs, resuming uploads every {AWEKAS.OriginalInterval} secs"); - AWEKAS.Interval = AWEKAS.OriginalInterval; - AwekasTimer.Interval = AWEKAS.Interval * 1000; - AWEKAS.SynchronisedUpdate = AWEKAS.Interval % 60 == 0; - AwekasTimer.Enabled = !AWEKAS.SynchronisedUpdate; - AWEKAS.RateLimited = false; + LogMessage("AWEKAS: Unknown error"); } } + + // check the min upload time is greater than our upload time + if (respJson.status > 0 && respJson.minuploadtime > AWEKAS.OriginalInterval) + { + LogMessage($"AWEKAS: The minimum upload time to AWEKAS for your station is {respJson.minuploadtime} sec, Cumulus is configured for {AWEKAS.OriginalInterval} sec, increasing Cumulus interval to match AWEKAS"); + AWEKAS.Interval = respJson.minuploadtime; + WriteIniFile(); + AwekasTimer.Interval = AWEKAS.Interval * 1000; + AWEKAS.SynchronisedUpdate = AWEKAS.Interval % 60 == 0; + AwekasTimer.Enabled = !AWEKAS.SynchronisedUpdate; + // we got a successful upload, and reset the interval, so clear the rate limited values + AWEKAS.OriginalInterval = AWEKAS.Interval; + AWEKAS.RateLimited = false; + } + else if (AWEKAS.RateLimited && respJson.status > 0) + { + // We are currently rate limited, it could have been a transient thing because + // we just got a valid response, and our interval is >= the minimum allowed. + // So we just undo the limit, and resume as before + LogMessage($"AWEKAS: Removing temporary increase in upload interval to 60 secs, resuming uploads every {AWEKAS.OriginalInterval} secs"); + AWEKAS.Interval = AWEKAS.OriginalInterval; + AwekasTimer.Interval = AWEKAS.Interval * 1000; + AWEKAS.SynchronisedUpdate = AWEKAS.Interval % 60 == 0; + AwekasTimer.Enabled = !AWEKAS.SynchronisedUpdate; + AWEKAS.RateLimited = false; + } } - catch (Exception ex) - { - LogMessage("AWEKAS: Exception = " + ex.Message); - } - finally - { - AWEKAS.Updating = false; - } + } + catch (Exception ex) + { + LogMessage("AWEKAS: Exception = " + ex.Message); + } + finally + { + AWEKAS.Updating = false; } } internal async void UpdateWCloud(DateTime timestamp) { - if (!WCloud.Updating) + if (WCloud.Updating || station.DataStopped) { - WCloud.Updating = true; + // No data coming in, do not do anything + return; + } - string pwstring; - string url = station.GetWCloudURL(out pwstring, timestamp); + WCloud.Updating = true; - string starredpwstring = ""; + string pwstring; + string url = station.GetWCloudURL(out pwstring, timestamp); - string logUrl = url.Replace(pwstring, starredpwstring); + string starredpwstring = ""; - LogDebugMessage("WeatherCloud URL: " + logUrl); + string logUrl = url.Replace(pwstring, starredpwstring); - try - { - HttpResponseMessage response = await WCloudhttpClient.GetAsync(url); - var responseBodyAsText = await response.Content.ReadAsStringAsync(); - LogDebugMessage("WeatherCloud Response: " + response.StatusCode + ": " + responseBodyAsText); - } - catch (Exception ex) - { - LogDebugMessage("WeatherCloud update: " + ex.Message); - } - finally - { - WCloud.Updating = false; - } + LogDebugMessage("WeatherCloud URL: " + logUrl); + + try + { + HttpResponseMessage response = await WCloudhttpClient.GetAsync(url); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + LogDebugMessage("WeatherCloud Response: " + response.StatusCode + ": " + responseBodyAsText); + } + catch (Exception ex) + { + LogDebugMessage("WeatherCloud update: " + ex.Message); + } + finally + { + WCloud.Updating = false; } } - internal async void UpdateOpenWeatherMap(DateTime timestamp) { - if (!OpenWeatherMap.Updating) + if (OpenWeatherMap.Updating || station.DataStopped) { - OpenWeatherMap.Updating = true; + // No data coming in, do not do anything + return; + } - string url = "http://api.openweathermap.org/data/3.0/measurements?appid=" + OpenWeatherMap.PW; - string logUrl = url.Replace(OpenWeatherMap.PW, ""); + OpenWeatherMap.Updating = true; - string jsonData = station.GetOpenWeatherMapData(timestamp); + string url = "http://api.openweathermap.org/data/3.0/measurements?appid=" + OpenWeatherMap.PW; + string logUrl = url.Replace(OpenWeatherMap.PW, ""); - LogDebugMessage("OpenWeatherMap: URL = " + logUrl); - LogDataMessage("OpenWeatherMap: Body = " + jsonData); + string jsonData = station.GetOpenWeatherMapData(timestamp); - try - { - using (var client = new HttpClient()) - { - var data = new StringContent(jsonData, Encoding.UTF8, "application/json"); - HttpResponseMessage response = await client.PostAsync(url, data); - var responseBodyAsText = await response.Content.ReadAsStringAsync(); - var status = response.StatusCode == HttpStatusCode.NoContent ? "OK" : "Error"; // Returns a 204 reponse for OK! - LogMessage($"OpenWeatherMap: Response code = {status} - {response.StatusCode}"); - if (response.StatusCode != HttpStatusCode.NoContent) - LogDataMessage($"OpenWeatherMap: Response data = {responseBodyAsText}"); - } - } - catch (Exception ex) - { - LogMessage("OpenWeatherMap: Update error = " + ex.Message); - } - finally + LogDebugMessage("OpenWeatherMap: URL = " + logUrl); + LogDataMessage("OpenWeatherMap: Body = " + jsonData); + + try + { + using (var client = new HttpClient()) { - OpenWeatherMap.Updating = false; + var data = new StringContent(jsonData, Encoding.UTF8, "application/json"); + HttpResponseMessage response = await client.PostAsync(url, data); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + var status = response.StatusCode == HttpStatusCode.NoContent ? "OK" : "Error"; // Returns a 204 reponse for OK! + LogMessage($"OpenWeatherMap: Response code = {status} - {response.StatusCode}"); + if (response.StatusCode != HttpStatusCode.NoContent) + LogDataMessage($"OpenWeatherMap: Response data = {responseBodyAsText}"); } } + catch (Exception ex) + { + LogMessage("OpenWeatherMap: Update error = " + ex.Message); + } + finally + { + OpenWeatherMap.Updating = false; + } } // Find all stations associated with the users API key @@ -2419,6 +2447,12 @@ internal void RealtimeTimerTick(object sender, ElapsedEventArgs elapsedEventArgs bool connectionFailed = false; var cycle = RealtimeCycleCounter++; + if (station.DataStopped) + { + // No data coming in, do not do anything + return; + } + LogDebugMessage($"Realtime[{cycle}]: Start cycle"); try { @@ -8370,6 +8404,12 @@ public void StartTimersAndSensors() private void CustomMysqlSecondsTimerTick(object sender, ElapsedEventArgs e) { + if (station.DataStopped) + { + // No data coming in, do not do anything + return; + } + if (!customMySqlSecondsUpdateInProgress) { customMySqlSecondsUpdateInProgress = true; @@ -8402,6 +8442,12 @@ private void CustomMysqlSecondsTimerTick(object sender, ElapsedEventArgs e) internal void CustomMysqlMinutesTimerTick() { + if (station.DataStopped) + { + // No data coming in, do not do anything + return; + } + if (!customMySqlMinutesUpdateInProgress) { customMySqlMinutesUpdateInProgress = true; @@ -8434,6 +8480,12 @@ internal void CustomMysqlMinutesTimerTick() internal void CustomMysqlRolloverTimerTick() { + if (station.DataStopped) + { + // No data coming in, do not do anything + return; + } + if (!customMySqlRolloverUpdateInProgress) { customMySqlRolloverUpdateInProgress = true; diff --git a/CumulusMX/WeatherStation.cs b/CumulusMX/WeatherStation.cs index 6888698e..f22723cb 100644 --- a/CumulusMX/WeatherStation.cs +++ b/CumulusMX/WeatherStation.cs @@ -1437,6 +1437,12 @@ public void SecondTimer(object sender, ElapsedEventArgs e) } MinuteChanged(timeNow); + + if (DataStopped) + { + // No data coming in, do not do anything else + return; + } } if ((int)timeNow.TimeOfDay.TotalMilliseconds % 2500 <= 500) @@ -1541,6 +1547,13 @@ private void HourChanged(DateTime now) DoForecast("", true); } + if (DataStopped) + { + // No data coming in, do not do anything else + return; + } + + if (now.Hour == 0) { ResetMidnightRain(now); @@ -11263,6 +11276,12 @@ public double CalulateEvapotranspiration(DateTime ts) public void UpdateAPRS() { + if (DataStopped) + { + // No data coming in, do nothing + return; + } + cumulus.LogMessage("Updating CWOP"); using (var client = new TcpClient(cumulus.APRS.Server, cumulus.APRS.Port)) using (var ns = client.GetStream()) diff --git a/Updates.txt b/Updates.txt index d751a7bd..68b9c6f9 100644 --- a/Updates.txt +++ b/Updates.txt @@ -23,6 +23,7 @@ [AlarmEmails] - Changed: Moved FTP Logging option from Internet Settings to Program Setting to collect all the logging options in one place +- Changed: A Data Stopped state now stops all logging, MySQL and web activity 3.10.5 - b3122 From 777a798e18482c4bd3a1620c8a85e5e0295e986e Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Fri, 9 Apr 2021 13:13:10 +0100 Subject: [PATCH 03/16] WeatherCloud API updated --- CumulusMX/Cumulus.cs | 34 +++++++++++++++++++++++++++++++--- CumulusMX/WeatherStation.cs | 25 ++++++++++++------------- Updates.txt | 1 + 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/CumulusMX/Cumulus.cs b/CumulusMX/Cumulus.cs index 17acb4c5..85f36441 100644 --- a/CumulusMX/Cumulus.cs +++ b/CumulusMX/Cumulus.cs @@ -2271,7 +2271,29 @@ internal async void UpdateWCloud(DateTime timestamp) { HttpResponseMessage response = await WCloudhttpClient.GetAsync(url); var responseBodyAsText = await response.Content.ReadAsStringAsync(); - LogDebugMessage("WeatherCloud Response: " + response.StatusCode + ": " + responseBodyAsText); + var msg = ""; + switch ((int)response.StatusCode) + { + case 200: + msg = "Success"; + break; + case 400: + msg = "Bad reuest"; + break; + case 401: + msg = "Incorrect WID or Key"; + break; + case 429: + msg = "Too many requests"; + break; + case 500: + msg = "Server error"; + break; + default: + msg = "Unknown error"; + break; + } + LogDebugMessage($"WeatherCloud Response: {msg} ({response.StatusCode}): {responseBodyAsText}"); } catch (Exception ex) { @@ -9683,7 +9705,9 @@ public bool Triggered { // Construct the message - preamble, plus values var msg = cumulus.AlarmEmailPreamble + " " + string.Format(EmailMsg, Value, Units); +#pragma warning disable 4014 cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, false); +#pragma warning restore 4014 } @@ -9737,7 +9761,9 @@ public bool UpTriggered { // Construct the message - preamble, plus values var msg = Program.cumulus.AlarmEmailPreamble + " " + string.Format(EmailMsgUp, Value, Units); +#pragma warning disable 4014 cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, false); +#pragma warning restore 4014 } // If we get a new trigger, record the time @@ -9779,11 +9805,13 @@ public bool DownTriggered { // Construct the message - preamble, plus values var msg = Program.cumulus.AlarmEmailPreamble + " " + string.Format(EmailMsgDn, Value, Units); +#pragma warning disable 4014 cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, false); +#pragma warning restore 4014 } - // If we get a new trigger, record the time - downTriggered = true; + // If we get a new trigger, record the time + downTriggered = true; DownTriggeredTime = DateTime.Now; } else diff --git a/CumulusMX/WeatherStation.cs b/CumulusMX/WeatherStation.cs index f22723cb..327945e4 100644 --- a/CumulusMX/WeatherStation.cs +++ b/CumulusMX/WeatherStation.cs @@ -8504,50 +8504,49 @@ public string GetWCloudURL(out string pwstring, DateTime timestamp) // aq if (cumulus.WCloud.SendAQI) { - sb.Append("&"); - switch (cumulus.StationOptions.PrimaryAqSensor) { case (int)Cumulus.PrimaryAqSensor.AirLinkOutdoor: if (cumulus.airLinkDataOut != null) { - sb.Append($"pm25={cumulus.airLinkDataOut.pm2p5:F0}"); + sb.Append($"&pm25={cumulus.airLinkDataOut.pm2p5:F0}"); sb.Append($"&pm10={cumulus.airLinkDataOut.pm10:F0}"); sb.Append($"&aqi={AirQualityIndices.US_EPApm2p5(cumulus.airLinkDataOut.pm2p5_24hr)}"); } break; case (int)Cumulus.PrimaryAqSensor.Ecowitt1: - sb.Append($"pm25={AirQuality1:F0}"); + sb.Append($"&pm25={AirQuality1:F0}"); sb.Append($"&aqi={AirQualityIndices.US_EPApm2p5(AirQualityAvg1)}"); break; case (int)Cumulus.PrimaryAqSensor.Ecowitt2: - sb.Append($"pm25={AirQuality2:F0}"); + sb.Append($"&pm25={AirQuality2:F0}"); sb.Append($"&aqi={AirQualityIndices.US_EPApm2p5(AirQualityAvg2)}"); break; case (int)Cumulus.PrimaryAqSensor.Ecowitt3: - sb.Append($"pm25={AirQuality3:F0}"); + sb.Append($"&pm25={AirQuality3:F0}"); sb.Append($"&aqi={AirQualityIndices.US_EPApm2p5(AirQualityAvg3)}"); break; case (int)Cumulus.PrimaryAqSensor.Ecowitt4: - sb.Append($"pm25={AirQuality4:F0}"); + sb.Append($"&pm25={AirQuality4:F0}"); sb.Append($"&aqi={AirQualityIndices.US_EPApm2p5(AirQualityAvg4)}"); break; case (int)Cumulus.PrimaryAqSensor.EcowittCO2: - sb.Append($"pm25={CO2_pm2p5:F0}"); + sb.Append($"&pm25={CO2_pm2p5:F0}"); sb.Append($"&pm10={CO2_pm10:F0}"); sb.Append($"&aqi={AirQualityIndices.US_EPApm2p5(CO2_pm2p5_24h)}"); break; } } - // time - sb.Append("&time=" + timestamp.ToString("HHmm")); + // time - UTC + sb.Append("&time=" + timestamp.ToUniversalTime().ToString("HHmm")); - // date - sb.Append("&date=" + timestamp.ToString("yyyyMMdd")); + // date - UTC + sb.Append("&date=" + timestamp.ToUniversalTime().ToString("yyyyMMdd")); // software identification - sb.Append("&type=291&ver=" + cumulus.Version); + //sb.Append("&type=291&ver=" + cumulus.Version); + sb.Append($"&software=Cumulus_MX_v{cumulus.Version}&softwareid=142787ebe716"); return sb.ToString(); } diff --git a/Updates.txt b/Updates.txt index 68b9c6f9..502f1e39 100644 --- a/Updates.txt +++ b/Updates.txt @@ -3,6 +3,7 @@ - Fix: Remove THW Index in /web/websitedataT.json, and default web site index.htm - Fix: Remove <#THWindex> web tag from Cumulus MX - Fix: OpenWeatherMap - create new station was not forcing the use of dot decimals for lat/long values +- Fix: WeatherCloud - date/time of reading should be UTC - New: Adds Email support for Alarms - Creates a new section in Cumulus.ini: From fd46e2fcb7ba27ae1e8e3ebd9e6a3ef390a2698a Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Sun, 11 Apr 2021 12:11:35 +0100 Subject: [PATCH 04/16] Rollover Backup and FineOffset updates --- CumulusMX/AlarmSettings.cs | 6 +- CumulusMX/App.config | 2 +- CumulusMX/Cumulus.cs | 122 ++++++++-------------- CumulusMX/CumulusMX.csproj | 14 +-- CumulusMX/FOStation.cs | 201 +++++++++++++++++++++++++----------- CumulusMX/WeatherStation.cs | 3 +- CumulusMX/packages.config | 8 +- Updates.txt | 8 +- 8 files changed, 207 insertions(+), 157 deletions(-) diff --git a/CumulusMX/AlarmSettings.cs b/CumulusMX/AlarmSettings.cs index 5b689b31..6d9e65a2 100644 --- a/CumulusMX/AlarmSettings.cs +++ b/CumulusMX/AlarmSettings.cs @@ -163,7 +163,8 @@ public string GetAlarmSettings() var email = new JsonAlarmEmail() { fromEmail = cumulus.AlarmFromEmail, - destEmail = cumulus.AlarmDestEmail.Join(";") + destEmail = cumulus.AlarmDestEmail.Join(";"), + useHtml = cumulus.AlarmEmailHtml }; var retObject = new JsonAlarmSettings() @@ -348,8 +349,8 @@ public string UpdateAlarmSettings(IHttpContext context) return msg; } } - cumulus.AlarmDestEmail = emails; + cumulus.AlarmEmailHtml = result.email.useHtml; // Save the settings cumulus.WriteIniFile(); @@ -503,6 +504,7 @@ public class JsonAlarmEmail { public string fromEmail { get; set; } public string destEmail { get; set; } + public bool useHtml { get; set; } } public class JsonAlarmUnits diff --git a/CumulusMX/App.config b/CumulusMX/App.config index c6a7ecb0..f49d173c 100644 --- a/CumulusMX/App.config +++ b/CumulusMX/App.config @@ -27,7 +27,7 @@ - + diff --git a/CumulusMX/Cumulus.cs b/CumulusMX/Cumulus.cs index 85f36441..81f1d94d 100644 --- a/CumulusMX/Cumulus.cs +++ b/CumulusMX/Cumulus.cs @@ -498,6 +498,7 @@ public struct TExtraFiles public string AlarmEmailSubject; public string AlarmFromEmail; public string[] AlarmDestEmail; + public bool AlarmEmailHtml; public bool ListWebTags; @@ -1128,7 +1129,7 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) DiaryDB = new SQLiteConnection(diaryfile, flags); DiaryDB.CreateTable(); - Backupdata(false, DateTime.Now); + BackupData(false, DateTime.Now); LogMessage("Debug logging is " + (ProgramOptions.DebugLogging ? "enabled" : "disabled")); LogMessage("Data logging is " + (ProgramOptions.DataLogging ? "enabled" : "disabled")); @@ -4294,6 +4295,7 @@ private void ReadIniFile() AlarmFromEmail = ini.GetValue("Alarms", "FromEmail", ""); AlarmDestEmail = ini.GetValue("Alarms", "DestEmail", "").Split(';'); + AlarmEmailHtml = ini.GetValue("Alarms", "UseHTML", false); Calib.Press.Offset = ini.GetValue("Offsets", "PressOffset", 0.0); Calib.Temp.Offset = ini.GetValue("Offsets", "TempOffset", 0.0); @@ -5024,6 +5026,7 @@ internal void WriteIniFile() ini.SetValue("Alarms", "FromEmail", AlarmFromEmail); ini.SetValue("Alarms", "DestEmail", AlarmDestEmail.Join(";")); + ini.SetValue("Alarms", "UseHTML", AlarmEmailHtml); ini.SetValue("Offsets", "PressOffset", Calib.Press.Offset); @@ -5847,7 +5850,6 @@ private void ReadStringsFile() public int INSTROMET = 5; public int ECOWITT = 6; //public bool startingup = true; - public bool StartOfDayBackupNeeded; public string ReportPath; public string LatestError; public DateTime LatestErrorTS = DateTime.MinValue; @@ -6137,12 +6139,6 @@ public void DoLogFile(DateTime timestamp, bool live) LogMessage("DoLogFile: Written log entry for " + timestamp); station.WriteTodayFile(timestamp, true); - if (StartOfDayBackupNeeded) - { - Backupdata(true, timestamp); - StartOfDayBackupNeeded = false; - } - if (MonthlyMySqlEnabled) { var InvC = new CultureInfo(""); @@ -6508,13 +6504,13 @@ public void DoAirLinkLogFile(DateTime timestamp) } } - private void Backupdata(bool daily, DateTime timestamp) + public void BackupData(bool daily, DateTime timestamp) { string dirpath = daily ? backupPath + "daily" + DirectorySeparator : backupPath; if (!Directory.Exists(dirpath)) { - LogMessage("*** Error - backup folder does not exist - " + dirpath); + LogMessage("BackupData: *** Error - backup folder does not exist - " + dirpath); } else { @@ -6526,7 +6522,7 @@ private void Backupdata(bool daily, DateTime timestamp) { if (Path.GetFileName(dirlist[0]) == "daily") { - LogMessage("*** Error - the backup folder has unexpected contents"); + LogMessage("BackupData: *** Error - the backup folder has unexpected contents"); break; } else @@ -6540,7 +6536,7 @@ private void Backupdata(bool daily, DateTime timestamp) foldername = dirpath + foldername + DirectorySeparator; - LogMessage("Creating backup folder " + foldername); + LogMessage("BackupData: Creating backup folder " + foldername); var alltimebackup = foldername + "alltime.ini"; var monthlyAlltimebackup = foldername + "monthlyalltime.ini"; @@ -6564,54 +6560,18 @@ private void Backupdata(bool daily, DateTime timestamp) if (!Directory.Exists(foldername)) { Directory.CreateDirectory(foldername); - if (File.Exists(AlltimeIniFile)) - { - File.Copy(AlltimeIniFile, alltimebackup); - } - if (File.Exists(MonthlyAlltimeIniFile)) - { - File.Copy(MonthlyAlltimeIniFile, monthlyAlltimebackup); - } - if (File.Exists(DayFileName)) - { - File.Copy(DayFileName, daybackup); - } - if (File.Exists(TodayIniFile)) - { - File.Copy(TodayIniFile, todaybackup); - } - if (File.Exists(YesterdayFile)) - { - File.Copy(YesterdayFile, yesterdaybackup); - } - if (File.Exists(LogFile)) - { - File.Copy(LogFile, logbackup); - } - if (File.Exists(MonthIniFile)) - { - File.Copy(MonthIniFile, monthbackup); - } - if (File.Exists(YearIniFile)) - { - File.Copy(YearIniFile, yearbackup); - } - if (File.Exists(diaryfile)) - { - File.Copy(diaryfile, diarybackup); - } - if (File.Exists("Cumulus.ini")) - { - File.Copy("Cumulus.ini", configbackup); - } - if (File.Exists(extraFile)) - { - File.Copy(extraFile, extraBackup); - } - if (File.Exists(AirLinkFile)) - { - File.Copy(AirLinkFile, AirLinkBackup); - } + CopyBackupFile(AlltimeIniFile, alltimebackup); + CopyBackupFile(MonthlyAlltimeIniFile, monthlyAlltimebackup); + CopyBackupFile(DayFileName, daybackup); + CopyBackupFile(TodayIniFile, todaybackup); + CopyBackupFile(YesterdayFile, yesterdaybackup); + CopyBackupFile(LogFile, logbackup); + CopyBackupFile(MonthIniFile, monthbackup); + CopyBackupFile(YearIniFile, yearbackup); + CopyBackupFile(diaryfile, diarybackup); + CopyBackupFile("Cumulus.ini", configbackup); + CopyBackupFile(extraFile, extraBackup); + CopyBackupFile(AirLinkFile, AirLinkBackup); // Do not do this extra backup between 00:00 & Rollover hour on the first of the month // as the month has not yet rolled over - only applies for start-up backups if (timestamp.Day == 1 && timestamp.Hour >= RolloverHour) @@ -6626,18 +6586,9 @@ private void Backupdata(bool daily, DateTime timestamp) var AirLinkFile2 = GetAirLinkLogFileName(timestamp.AddDays(-1)); var AirLinkBackup2 = foldername + AirLinkFile2.Replace(logFilePath, ""); - if (File.Exists(LogFile2)) - { - File.Copy(LogFile2, logbackup2, true); - } - if (File.Exists(extraFile2)) - { - File.Copy(extraFile2, extraBackup2, true); - } - if (File.Exists(AirLinkFile2)) - { - File.Copy(AirLinkFile2, AirLinkBackup2, true); - } + CopyBackupFile(LogFile2, logbackup2, true); + CopyBackupFile(extraFile2, extraBackup2, true); + CopyBackupFile(AirLinkFile2, AirLinkBackup2, true); } LogMessage("Created backup folder " + foldername); @@ -6649,6 +6600,21 @@ private void Backupdata(bool daily, DateTime timestamp) } } + private void CopyBackupFile(string src, string dest, bool overwrite=false) + { + try + { + if (File.Exists(src)) + { + File.Copy(src, dest, overwrite); + } + } + catch (Exception e) + { + LogMessage($"BackupData: Error copying {src} - {e}"); + } + } + /* /// /// Get a snapshot of the current data values @@ -9704,9 +9670,9 @@ public bool Triggered if (!triggered && Enabled && Email && cumulus.SmtpOptions.Enabled) { // Construct the message - preamble, plus values - var msg = cumulus.AlarmEmailPreamble + " " + string.Format(EmailMsg, Value, Units); + var msg = cumulus.AlarmEmailPreamble + "\r\n" + string.Format(EmailMsg, Value, Units); #pragma warning disable 4014 - cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, false); + cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); #pragma warning restore 4014 } @@ -9760,9 +9726,9 @@ public bool UpTriggered if (!upTriggered && Enabled && Email && cumulus.SmtpOptions.Enabled) { // Construct the message - preamble, plus values - var msg = Program.cumulus.AlarmEmailPreamble + " " + string.Format(EmailMsgUp, Value, Units); + var msg = Program.cumulus.AlarmEmailPreamble + "\r\n" + string.Format(EmailMsgUp, Value, Units); #pragma warning disable 4014 - cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, false); + cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); #pragma warning restore 4014 } @@ -9804,9 +9770,9 @@ public bool DownTriggered if (!downTriggered && Enabled && Email && cumulus.SmtpOptions.Enabled) { // Construct the message - preamble, plus values - var msg = Program.cumulus.AlarmEmailPreamble + " " + string.Format(EmailMsgDn, Value, Units); + var msg = Program.cumulus.AlarmEmailPreamble + "\n" + string.Format(EmailMsgDn, Value, Units); #pragma warning disable 4014 - cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, false); + cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); #pragma warning restore 4014 } diff --git a/CumulusMX/CumulusMX.csproj b/CumulusMX/CumulusMX.csproj index 9e6f996e..07343c13 100644 --- a/CumulusMX/CumulusMX.csproj +++ b/CumulusMX/CumulusMX.csproj @@ -93,8 +93,8 @@ False lib\Devart.Data.MySql.dll - - ..\packages\FluentFTP.32.3.1\lib\net45\FluentFTP.dll + + ..\packages\FluentFTP.33.1.5\lib\net45\FluentFTP.dll ..\packages\HidSharp.2.1.0\lib\net35\HidSharp.dll @@ -113,14 +113,14 @@ ..\packages\MimeKit.2.11.0\lib\net45\MimeKit.dll - - ..\packages\MQTTnet.3.0.12\lib\net452\MQTTnet.dll + + ..\packages\MQTTnet.3.0.15\lib\net452\MQTTnet.dll ..\packages\SSH.NET.2016.1.0\lib\net40\Renci.SshNet.dll - ..\packages\ServiceStack.Text.5.9.2\lib\net45\ServiceStack.Text.dll + ..\packages\ServiceStack.Text.5.10.4\lib\net45\ServiceStack.Text.dll @@ -156,8 +156,8 @@ ..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.7.1\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll + + ..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll diff --git a/CumulusMX/FOStation.cs b/CumulusMX/FOStation.cs index d6e4acc3..0d33b690 100644 --- a/CumulusMX/FOStation.cs +++ b/CumulusMX/FOStation.cs @@ -17,7 +17,7 @@ internal class FOStation : WeatherStation private readonly double pressureOffset; private HidDevice hidDevice; - private readonly HidStream stream; + private HidStream stream; private List datalist; private readonly int maxHistoryEntries; @@ -70,66 +70,36 @@ internal FOStation(Cumulus cumulus) : base(cumulus) maxHistoryEntries = 4080; } - var devicelist = DeviceList.Local; - - int vid = (cumulus.FineOffsetOptions.VendorID < 0 ? DefaultVid : cumulus.FineOffsetOptions.VendorID); - int pid = (cumulus.FineOffsetOptions.ProductID < 0 ? DefaultPid : cumulus.FineOffsetOptions.ProductID); - - cumulus.LogMessage("Looking for Fine Offset station, VendorID=0x"+vid.ToString("X4")+" ProductID=0x"+pid.ToString("X4")); - cumulus.LogConsoleMessage("Looking for Fine Offset station"); - - hidDevice = devicelist.GetHidDeviceOrNull(vendorID: vid, productID: pid); - - if (hidDevice != null) + do { - cumulus.LogMessage("Fine Offset station found"); - cumulus.LogConsoleMessage("Fine Offset station found"); - - if (hidDevice.TryOpen(out stream)) + if (OpenHidDevice()) { - cumulus.LogMessage("Stream opened"); - cumulus.LogConsoleMessage("Connected to station"); // Get the block of data containing the abs and rel pressures cumulus.LogMessage("Reading pressure offset"); - ReadAddress(0x20, data); - double relpressure = (((data[1] & 0x3f)*256) + data[0])/10.0f; - double abspressure = (((data[3] & 0x3f)*256) + data[2])/10.0f; - pressureOffset = relpressure - abspressure; - cumulus.LogMessage("Rel pressure = " + relpressure); - cumulus.LogMessage("Abs pressure = " + abspressure); - cumulus.LogMessage("Calculated Offset = " + pressureOffset); - if (cumulus.EwOptions.PressOffset < 9999.0) + + if (ReadAddress(0x20, data)) { - cumulus.LogMessage("Ignoring calculated offset, using offset value from cumulus.ini file"); - cumulus.LogMessage("EWpressureoffset = " + cumulus.EwOptions.PressOffset); - pressureOffset = cumulus.EwOptions.PressOffset; - } + double relpressure = (((data[1] & 0x3f) * 256) + data[0]) / 10.0f; + double abspressure = (((data[3] & 0x3f) * 256) + data[2]) / 10.0f; + pressureOffset = relpressure - abspressure; + cumulus.LogMessage("Rel pressure = " + relpressure); + cumulus.LogMessage("Abs pressure = " + abspressure); + cumulus.LogMessage("Calculated Offset = " + pressureOffset); + if (cumulus.EwOptions.PressOffset < 9999.0) + { + cumulus.LogMessage("Ignoring calculated offset, using offset value from cumulus.ini file"); + cumulus.LogMessage("EWpressureoffset = " + cumulus.EwOptions.PressOffset); + pressureOffset = cumulus.EwOptions.PressOffset; + } - // Read the data from the logger - startReadingHistoryData(); - } - else - { - cumulus.LogMessage("Stream open failed"); - cumulus.LogConsoleMessage("Unable to connect to station"); - } - } - else - { - cumulus.LogMessage("*** Fine Offset station not found ***"); - cumulus.LogConsoleMessage("Fine Offset station not found"); - cumulus.LogMessage("Found the following USB HID Devices..."); - int cnt = 0; - foreach (HidDevice device in devicelist.GetHidDevices()) - { - cumulus.LogMessage($" {device}"); - cnt++; - } - if (cnt == 0) - { - cumulus.LogMessage("No USB HID devices found!"); + // Read the data from the logger + startReadingHistoryData(); + } } - } + + // pause for 10 seconds then try again + Thread.Sleep(10000); + } while (hidDevice == null || stream == null || !stream.CanRead); } public override void startReadingHistoryData() @@ -197,14 +167,20 @@ public override void getAndProcessHistoryData() DateTime timestamp = DateTime.Now; //LastUpdateTime = DateTime.Now; // lastArchiveTimeUTC.ToLocalTime(); cumulus.LogMessage("Last Update = " + cumulus.LastUpdateTime); - ReadAddress(0, data); + if (!ReadAddress(0, data)) + { + return; + } // get address of current location int addr = ((data[31])*256) + data[30]; //int previousaddress = addr; cumulus.LogMessage("Reading current address " + addr.ToString("X4")); - ReadAddress(addr, data); + if (!ReadAddress(addr, data)) + { + return; + } bool moredata = true; @@ -224,7 +200,10 @@ public override void getAndProcessHistoryData() addr -= foEntrysize; if (addr == 0xF0) addr = foMaxAddr; // wrap around - ReadAddress(addr, data); + if (!ReadAddress(addr, data)) + { + return; + } // add history data to collection @@ -554,12 +533,63 @@ public override void Start() tmrDataRead.Enabled = true; } + private bool OpenHidDevice() + { + var devicelist = DeviceList.Local; + + int vid = (cumulus.FineOffsetOptions.VendorID < 0 ? DefaultVid : cumulus.FineOffsetOptions.VendorID); + int pid = (cumulus.FineOffsetOptions.ProductID < 0 ? DefaultPid : cumulus.FineOffsetOptions.ProductID); + + cumulus.LogMessage("Looking for Fine Offset station, VendorID=0x" + vid.ToString("X4") + " ProductID=0x" + pid.ToString("X4")); + cumulus.LogConsoleMessage("Looking for Fine Offset station"); + + hidDevice = devicelist.GetHidDeviceOrNull(vendorID: vid, productID: pid); + + if (hidDevice != null) + { + cumulus.LogMessage("Fine Offset station found"); + cumulus.LogConsoleMessage("Fine Offset station found"); + + if (hidDevice.TryOpen(out stream)) + { + cumulus.LogMessage("Stream opened"); + cumulus.LogConsoleMessage("Connected to station"); + stream.Flush(); + return true; + } + else + { + cumulus.LogMessage("Stream open failed"); + return false; + } + } + else + { + cumulus.LogMessage("*** Fine Offset station not found ***"); + cumulus.LogMessage("Found the following USB HID Devices..."); + int cnt = 0; + foreach (HidDevice device in devicelist.GetHidDevices()) + { + cumulus.LogMessage($" {device}"); + cnt++; + } + + if (cnt == 0) + { + cumulus.LogMessage("No USB HID devices found!"); + } + + return false; + } + } + + /// /// Read the 32 bytes starting at 'address' /// /// The address of the data /// Where to return the data - private void ReadAddress(int address, byte[] buff) + private bool ReadAddress(int address, byte[] buff) { //cumulus.LogMessage("Reading address " + address.ToString("X6")); var lowbyte = (byte) (address & 0xFF); @@ -575,10 +605,27 @@ private void ReadAddress(int address, byte[] buff) int ptr = 0; if (hidDevice == null) - return; + { + DataStopped = true; + cumulus.DataStoppedAlarm.Triggered = true; + return false; + } //response = device.WriteRead(0x00, request); - stream.Write(request); + try + { + stream.Write(request); + } + catch (Exception ex) + { + cumulus.LogConsoleMessage("Error sending command to station - it may need resetting"); + cumulus.LogMessage(ex.Message); + cumulus.LogMessage("Error sending command to station - it may need resetting"); + DataStopped = true; + cumulus.DataStoppedAlarm.Triggered = true; + return false; + } + Thread.Sleep(cumulus.FineOffsetOptions.FineOffsetReadTime); for (int i = 1; i < 5; i++) { @@ -592,6 +639,9 @@ private void ReadAddress(int address, byte[] buff) cumulus.LogConsoleMessage("Error reading data from station - it may need resetting"); cumulus.LogMessage(ex.Message); cumulus.LogMessage("Error reading data from station - it may need resetting"); + DataStopped = true; + cumulus.DataStoppedAlarm.Triggered = true; + return false; } var recData = " Data" + i + ": " + BitConverter.ToString(response, startByte, responseLength - startByte); @@ -601,10 +651,31 @@ private void ReadAddress(int address, byte[] buff) } cumulus.LogDataMessage(recData); } + return true; } private void DataReadTimerTick(object state, ElapsedEventArgs elapsedEventArgs) { + if (DataStopped) + { + cumulus.LogMessage("Attempting to reopen the USB device..."); + // We are not getting any data from the station, try reopening the USB connection + if (stream != null) + { + try + { + stream.Close(); + } + catch { } + } + + if (!OpenHidDevice()) + { + cumulus.LogMessage("Failed to reopen the USB device"); + return; + } + } + if (!readingData) { readingData = true; @@ -682,7 +753,10 @@ private void GetAndProcessData() // get the block of memory containing the current data location cumulus.LogDataMessage("Reading first block"); - ReadAddress(0, data); + if (!ReadAddress(0, data)) + { + return; + } int addr = (data[31]*256) + data[30]; @@ -704,7 +778,10 @@ private void GetAndProcessData() { cumulus.LogDataMessage("Reading data, addr = " + addr.ToString("X8")); - ReadAddress(addr, data); + if (!ReadAddress(addr, data)) + { + return; + } cumulus.LogDataMessage("Data read - " + BitConverter.ToString(data)); diff --git a/CumulusMX/WeatherStation.cs b/CumulusMX/WeatherStation.cs index 327945e4..c611320a 100644 --- a/CumulusMX/WeatherStation.cs +++ b/CumulusMX/WeatherStation.cs @@ -1565,6 +1565,7 @@ private void HourChanged(DateTime now) if (now.Hour == rollHour) { DayReset(now); + cumulus.BackupData(true, now); } if (now.Hour == 0) @@ -5152,8 +5153,6 @@ public void DayReset(DateTime timestamp) CurrentDay = timestamp.Day; CurrentMonth = timestamp.Month; CurrentYear = timestamp.Year; - //Backupdata(true, timestamp); - cumulus.StartOfDayBackupNeeded = true; cumulus.LogMessage("=== Day reset complete"); cumulus.LogMessage("Now recording data for day=" + CurrentDay + " month=" + CurrentMonth + " year=" + CurrentYear); } diff --git a/CumulusMX/packages.config b/CumulusMX/packages.config index 6843d409..1b1c2ae3 100644 --- a/CumulusMX/packages.config +++ b/CumulusMX/packages.config @@ -1,7 +1,7 @@  - + @@ -11,17 +11,17 @@ - + - + - + \ No newline at end of file diff --git a/Updates.txt b/Updates.txt index 502f1e39..288d4b37 100644 --- a/Updates.txt +++ b/Updates.txt @@ -4,6 +4,7 @@ - Fix: Remove <#THWindex> web tag from Cumulus MX - Fix: OpenWeatherMap - create new station was not forcing the use of dot decimals for lat/long values - Fix: WeatherCloud - date/time of reading should be UTC +- Fix: End of day backup now always runs at rollover - New: Adds Email support for Alarms - Creates a new section in Cumulus.ini: @@ -25,7 +26,12 @@ - Changed: Moved FTP Logging option from Internet Settings to Program Setting to collect all the logging options in one place - Changed: A Data Stopped state now stops all logging, MySQL and web activity - +- Changed: Updated various components to latest version... + FTP - FluentFTP + JSON - ServiceStack.Text + Pointers - System.Runtime.CompilerServcies.Unsafe + MQTT - MQTTnet +- Changed: FineOffset stations now attempt to reconnect the USB after a data stopped event. These stations now also keep retrying to connect to the station at start-up 3.10.5 - b3122 —————————————— From 614f4c79c64ba6a5362043fb6115579c6e210a0c Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Mon, 12 Apr 2021 18:33:19 +0100 Subject: [PATCH 05/16] Replcae Devart package with MySqlConnector --- CumulusMX/App.config | 2 +- CumulusMX/Cumulus.cs | 370 +++++++++++---------------- CumulusMX/CumulusMX.csproj | 15 +- CumulusMX/EmailSender.cs | 4 +- CumulusMX/InternetSettings.cs | 5 + CumulusMX/MysqlSettings.cs | 62 ++--- CumulusMX/Properties/AssemblyInfo.cs | 6 +- CumulusMX/WeatherStation.cs | 41 +-- CumulusMX/packages.config | 2 + CumulusMX/webtags.cs | 2 +- Updates.txt | 16 +- 11 files changed, 208 insertions(+), 317 deletions(-) diff --git a/CumulusMX/App.config b/CumulusMX/App.config index f49d173c..1f9d3bde 100644 --- a/CumulusMX/App.config +++ b/CumulusMX/App.config @@ -23,7 +23,7 @@ - + diff --git a/CumulusMX/Cumulus.cs b/CumulusMX/Cumulus.cs index 81f1d94d..c70b27ff 100644 --- a/CumulusMX/Cumulus.cs +++ b/CumulusMX/Cumulus.cs @@ -16,7 +16,7 @@ using System.Threading; using System.Threading.Tasks; using System.Timers; -using Devart.Data.MySql; +using MySqlConnector; using FluentFTP; using LinqToTwitter; using ServiceStack.Text; @@ -1183,13 +1183,11 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) SetUpHttpProxy(); + var sqlConnectionString = $"server={MySqlHost};port={MySqlPort};user={MySqlUser};password={MySqlPass};database={MySqlDatabase}"; + if (MonthlyMySqlEnabled) { - MonthlyMySqlConn.Host = MySqlHost; - MonthlyMySqlConn.Port = MySqlPort; - MonthlyMySqlConn.UserId = MySqlUser; - MonthlyMySqlConn.Password = MySqlPass; - MonthlyMySqlConn.Database = MySqlDatabase; + MonthlyMySqlConn.ConnectionString = sqlConnectionString; SetStartOfMonthlyInsertSQL(); } @@ -1201,39 +1199,23 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) if (RealtimeMySqlEnabled) { - RealtimeSqlConn.Host = MySqlHost; - RealtimeSqlConn.Port = MySqlPort; - RealtimeSqlConn.UserId = MySqlUser; - RealtimeSqlConn.Password = MySqlPass; - RealtimeSqlConn.Database = MySqlDatabase; - + RealtimeSqlConn.ConnectionString = sqlConnectionString; SetStartOfRealtimeInsertSQL(); } - CustomMysqlSecondsConn.Host = MySqlHost; - CustomMysqlSecondsConn.Port = MySqlPort; - CustomMysqlSecondsConn.UserId = MySqlUser; - CustomMysqlSecondsConn.Password = MySqlPass; - CustomMysqlSecondsConn.Database = MySqlDatabase; + + CustomMysqlSecondsConn.ConnectionString = sqlConnectionString; customMysqlSecondsTokenParser.OnToken += TokenParserOnToken; CustomMysqlSecondsCommand.Connection = CustomMysqlSecondsConn; CustomMysqlSecondsTimer = new Timer { Interval = CustomMySqlSecondsInterval * 1000 }; CustomMysqlSecondsTimer.Elapsed += CustomMysqlSecondsTimerTick; CustomMysqlSecondsTimer.AutoReset = true; - CustomMysqlMinutesConn.Host = MySqlHost; - CustomMysqlMinutesConn.Port = MySqlPort; - CustomMysqlMinutesConn.UserId = MySqlUser; - CustomMysqlMinutesConn.Password = MySqlPass; - CustomMysqlMinutesConn.Database = MySqlDatabase; + CustomMysqlMinutesConn.ConnectionString = sqlConnectionString; customMysqlMinutesTokenParser.OnToken += TokenParserOnToken; CustomMysqlMinutesCommand.Connection = CustomMysqlMinutesConn; - CustomMysqlRolloverConn.Host = MySqlHost; - CustomMysqlRolloverConn.Port = MySqlPort; - CustomMysqlRolloverConn.UserId = MySqlUser; - CustomMysqlRolloverConn.Password = MySqlPass; - CustomMysqlRolloverConn.Database = MySqlDatabase; + CustomMysqlRolloverConn.ConnectionString = sqlConnectionString; customMysqlRolloverTokenParser.OnToken += TokenParserOnToken; CustomMysqlRolloverCommand.Connection = CustomMysqlRolloverConn; @@ -6061,7 +6043,7 @@ public string GetAirLinkLogFileName(DateTime thedate) public const int NumLogFileFields = 29; - public void DoLogFile(DateTime timestamp, bool live) + public async void DoLogFile(DateTime timestamp, bool live) { // Writes an entry to the n-minute logfile. Fields are comma-separated: // 0 Date in the form dd/mm/yy (the slash may be replaced by a dash in some cases) @@ -6182,31 +6164,7 @@ public void DoLogFile(DateTime timestamp, bool live) if (live) { // do the update - try - { - using (MySqlCommand cmd = new MySqlCommand()) - { - cmd.CommandText = queryString; - cmd.Connection = MonthlyMySqlConn; - LogDebugMessage("DoLogFile: MySQL - " + queryString); - MonthlyMySqlConn.Open(); - int aff = cmd.ExecuteNonQuery(); - LogDebugMessage("MySQL: Table " + MySqlMonthlyTable + " " + aff + " rows were affected."); - } - } - catch (Exception ex) - { - LogMessage("DoLogFile: Error encountered during Monthly MySQL operation."); - LogMessage(ex.Message); - } - finally - { - try - { - MonthlyMySqlConn.Close(); - } - catch { } - } + await MySqlCommandAsync(queryString, MonthlyMySqlConn, "DoLogFile", true, true); } else { @@ -8101,55 +8059,16 @@ 59 8.4 Feels Like temperature values.Append(station.FeelsLike.ToString(TempFormat, InvC)); values.Append(")"); - string queryString = values.ToString(); + string valuesString = values.ToString(); + List cmds = new List() { valuesString }; - // do the update - using (MySqlCommand cmd = new MySqlCommand()) + if (!string.IsNullOrEmpty(MySqlRealtimeRetention)) { - try - { - cmd.CommandText = queryString; - cmd.Connection = RealtimeSqlConn; - LogDebugMessage($"Realtime[{cycle}]: Running SQL command: {queryString}"); - - RealtimeSqlConn.Open(); - int aff1 = cmd.ExecuteNonQuery(); - LogDebugMessage($"Realtime[{cycle}]: {aff1} rows were added."); - - if (!string.IsNullOrEmpty(MySqlRealtimeRetention)) - { - // delete old entries - cmd.CommandText = $"DELETE IGNORE FROM {MySqlRealtimeTable} WHERE LogDateTime < DATE_SUB('{DateTime.Now:yyyy-MM-dd HH:mm:ss}', INTERVAL {MySqlRealtimeRetention})"; - LogDebugMessage($"Realtime[{cycle}]: Running SQL command: {cmd.CommandText}"); - - try - { - RealtimeSqlConn.Open(); - int aff2 = cmd.ExecuteNonQuery(); - LogDebugMessage($"Realtime[{cycle}]: {aff2} rows were deleted."); - } - catch (Exception ex) - { - LogMessage($"Realtime[{cycle}]: Error encountered during Realtime delete MySQL operation."); - LogMessage(ex.Message); - } - - } - } - catch (Exception ex) - { - LogMessage($"Realtime[{cycle}]: Error encountered during Realtime MySQL operation."); - LogMessage(ex.Message); - } - finally - { - try - { - RealtimeSqlConn.Close(); - } - catch {} - } + cmds.Add($"DELETE IGNORE FROM {MySqlRealtimeTable} WHERE LogDateTime < DATE_SUB('{DateTime.Now:yyyy-MM-dd HH:mm:ss}', INTERVAL {MySqlRealtimeRetention});"); } + + // do the update + MySqlCommandSync(cmds, RealtimeSqlConn, $"Realtime[{cycle}]", true, true); } } @@ -8398,37 +8317,32 @@ private void CustomMysqlSecondsTimerTick(object sender, ElapsedEventArgs e) return; } + _ = CustomMysqlSecondsWork(); + } + + private async Task CustomMysqlSecondsWork() + { + if (station.DataStopped) + { + // No data coming in, do not do anything + return; + } + if (!customMySqlSecondsUpdateInProgress) { customMySqlSecondsUpdateInProgress = true; - try - { - customMysqlSecondsTokenParser.InputText = CustomMySqlSecondsCommandString; - CustomMysqlSecondsCommand.CommandText = customMysqlSecondsTokenParser.ToStringFromString(); - LogDebugMessage($"Custom SQL sec: {CustomMysqlSecondsCommand.CommandText}"); - CustomMysqlSecondsConn.Open(); - int aff = CustomMysqlSecondsCommand.ExecuteNonQuery(); - LogDebugMessage($"Custom SQL sec: {aff} rows were affected."); - } - catch (Exception ex) - { - LogMessage("Custom SQL sec: Error encountered during custom seconds MySQL operation."); - LogMessage(ex.Message); - } - finally - { - try - { - CustomMysqlSecondsConn.Close(); - } - catch {} - customMySqlSecondsUpdateInProgress = false; - } + customMysqlSecondsTokenParser.InputText = CustomMySqlSecondsCommandString; + CustomMysqlSecondsCommand.CommandText = customMysqlSecondsTokenParser.ToStringFromString(); + + await MySqlCommandAsync(CustomMysqlSecondsCommand.CommandText, CustomMysqlSecondsConn, "CustomSqlSecs", true, true); + + customMySqlSecondsUpdateInProgress = false; } } - internal void CustomMysqlMinutesTimerTick() + + internal async Task CustomMysqlMinutesTimerTick() { if (station.DataStopped) { @@ -8440,33 +8354,16 @@ internal void CustomMysqlMinutesTimerTick() { customMySqlMinutesUpdateInProgress = true; - try - { - customMysqlMinutesTokenParser.InputText = CustomMySqlMinutesCommandString; - CustomMysqlMinutesCommand.CommandText = customMysqlMinutesTokenParser.ToStringFromString(); - LogDebugMessage(CustomMysqlMinutesCommand.CommandText); - CustomMysqlMinutesConn.Open(); - int aff = CustomMysqlMinutesCommand.ExecuteNonQuery(); - LogDebugMessage("MySQL: Custom minutes update " + aff + " rows were affected."); - } - catch (Exception ex) - { - LogMessage("Error encountered during custom minutes MySQL operation."); - LogMessage(ex.Message); - } - finally - { - try - { - CustomMysqlMinutesConn.Close(); - } - catch {} - customMySqlMinutesUpdateInProgress = false; - } + customMysqlMinutesTokenParser.InputText = CustomMySqlMinutesCommandString; + CustomMysqlMinutesCommand.CommandText = customMysqlMinutesTokenParser.ToStringFromString(); + + await MySqlCommandAsync(CustomMysqlMinutesCommand.CommandText, CustomMysqlMinutesConn, "CustomSqlMins", true, true); + + customMySqlMinutesUpdateInProgress = false; } } - internal void CustomMysqlRolloverTimerTick() + internal async Task CustomMysqlRolloverTimerTick() { if (station.DataStopped) { @@ -8477,32 +8374,13 @@ internal void CustomMysqlRolloverTimerTick() if (!customMySqlRolloverUpdateInProgress) { customMySqlRolloverUpdateInProgress = true; - Task.Run(() => - { - try - { - customMysqlRolloverTokenParser.InputText = CustomMySqlRolloverCommandString; - CustomMysqlRolloverCommand.CommandText = customMysqlRolloverTokenParser.ToStringFromString(); - LogDebugMessage(CustomMysqlRolloverCommand.CommandText); - CustomMysqlRolloverConn.Open(); - int aff = CustomMysqlRolloverCommand.ExecuteNonQuery(); - LogDebugMessage("MySQL: Custom rollover update " + aff + " rows were affected."); - } - catch (Exception ex) - { - LogMessage("Error encountered during custom Rollover MySQL operation."); - LogMessage(ex.Message); - } - finally - { - try - { - CustomMysqlRolloverConn.Close(); - } - catch {} - customMySqlRolloverUpdateInProgress = false; - } - }); + + customMysqlRolloverTokenParser.InputText = CustomMySqlRolloverCommandString; + CustomMysqlRolloverCommand.CommandText = customMysqlRolloverTokenParser.ToStringFromString(); + + await MySqlCommandAsync(CustomMysqlRolloverCommand.CommandText, CustomMysqlRolloverConn, "CustomSqlRollover", true, true); + + customMySqlRolloverUpdateInProgress = false; } } @@ -8597,54 +8475,17 @@ public void DoExtraEndOfDayFiles() private void MySqlCatchup() { - var mySqlConn = new MySqlConnection - { - Host = MySqlHost, - Port = MySqlPort, - UserId = MySqlUser, - Password = MySqlPass, - Database = MySqlDatabase - }; - try { - mySqlConn.Open(); + var mySqlConn = new MySqlConnection($"server={MySqlHost};port={MySqlPort};user={MySqlUser};password={MySqlPass};database={MySqlDatabase}"); - for (int i = 0; i < MySqlList.Count; i++) - { - LogMessage("MySQL Archive: Uploading archive #" + (i + 1)); - try - { - using (MySqlCommand cmd = new MySqlCommand()) - { - cmd.CommandText = MySqlList[i]; - cmd.Connection = mySqlConn; - LogDebugMessage(MySqlList[i]); - - int aff = cmd.ExecuteNonQuery(); - LogDebugMessage($"MySQL Archive: Table {MySqlMonthlyTable} - {aff} rows were affected."); - } - } - catch (Exception ex) - { - LogMessage("MySQL Archive: Error encountered during catchup MySQL operation."); - LogMessage(ex.Message); - } - } + MySqlCommandSync(MySqlList, mySqlConn, "MySQL Archive", true, true); } catch (Exception ex) { LogMessage("MySQL Archive: Error encountered during catchup MySQL operation."); LogMessage(ex.Message); } - finally - { - try - { - mySqlConn.Close(); - } - catch {} - } LogMessage("MySQL Archive: End of MySQL archive upload"); MySqlList.Clear(); @@ -8978,6 +8819,99 @@ public async void UpdateWOW(DateTime timestamp) } } + public async Task MySqlCommandAsync(string Cmd, MySqlConnection Connection, string CallingFunction, bool OpenConnection, bool CloseConnection) + { + try + { + if (OpenConnection) + { + LogDebugMessage($"{CallingFunction}: Opening MySQL Connection"); + await Connection.OpenAsync(); + } + + using (MySqlCommand cmd = new MySqlCommand(Cmd, Connection)) + { + LogDebugMessage($"{CallingFunction}: MySQL executing - {Cmd}"); + + int aff = await cmd.ExecuteNonQueryAsync(); + LogDebugMessage($"{CallingFunction}: MySQL {aff} rows were affected."); + } + } + catch (Exception ex) + { + LogMessage($"{CallingFunction}: Error encountered during MySQL operation."); + LogMessage($"{CallingFunction}: SQL was - \"{Cmd}\""); + LogMessage(ex.Message); + } + finally + { + if (CloseConnection) + { + try + { + Connection.Close(); + } + catch { } + } + } + + } + + public Task MySqlCommandSync(List Cmds, MySqlConnection Connection, string CallingFunction, bool OpenConnection, bool CloseConnection, bool ClearCommands=false) + { + return Task.Run(() => + { + try + { + if (OpenConnection) + { + LogDebugMessage($"{CallingFunction}: Opening MySQL Connection"); + Connection.Open(); + } + + for (var i = 0; i < Cmds.Count; i++) + { + try + { + using (MySqlCommand cmd = new MySqlCommand(Cmds[i], Connection)) + { + LogDebugMessage($"{CallingFunction}: MySQL executing[{i + 1}] - {Cmds[i]}"); + + int aff = cmd.ExecuteNonQuery(); + LogDebugMessage($"{CallingFunction}: MySQL {aff} rows were affected."); + } + } + catch (Exception ex) + { + LogMessage($"{CallingFunction}: Error encountered during MySQL operation."); + LogMessage($"{CallingFunction}: SQL was - \"{Cmds[i]}\""); + LogMessage(ex.Message); + } + } + + if (CloseConnection) + { + try + { + Connection.Close(); + } + catch + { } + } + } + catch (Exception e) + { + LogMessage($"{CallingFunction}: Error opening MySQL Connection"); + LogMessage(e.Message); + } + + if (ClearCommands) + { + Cmds.Clear(); + } + }); + } + public async void GetLatestVersion() { var http = new HttpClient(); @@ -9671,9 +9605,7 @@ public bool Triggered { // Construct the message - preamble, plus values var msg = cumulus.AlarmEmailPreamble + "\r\n" + string.Format(EmailMsg, Value, Units); -#pragma warning disable 4014 - cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); -#pragma warning restore 4014 + _ = cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); } @@ -9727,9 +9659,7 @@ public bool UpTriggered { // Construct the message - preamble, plus values var msg = Program.cumulus.AlarmEmailPreamble + "\r\n" + string.Format(EmailMsgUp, Value, Units); -#pragma warning disable 4014 - cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); -#pragma warning restore 4014 + _ = cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); } // If we get a new trigger, record the time @@ -9771,9 +9701,7 @@ public bool DownTriggered { // Construct the message - preamble, plus values var msg = Program.cumulus.AlarmEmailPreamble + "\n" + string.Format(EmailMsgDn, Value, Units); -#pragma warning disable 4014 - cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); -#pragma warning restore 4014 + _ = cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); } // If we get a new trigger, record the time diff --git a/CumulusMX/CumulusMX.csproj b/CumulusMX/CumulusMX.csproj index 07343c13..a1471afd 100644 --- a/CumulusMX/CumulusMX.csproj +++ b/CumulusMX/CumulusMX.csproj @@ -85,14 +85,6 @@ ..\packages\Portable.BouncyCastle.1.8.10\lib\net40\BouncyCastle.Crypto.dll - - False - ..\..\CumulusMX\CumulusMX\lib\Devart.Data.dll - - - False - lib\Devart.Data.MySql.dll - ..\packages\FluentFTP.33.1.5\lib\net45\FluentFTP.dll @@ -116,6 +108,9 @@ ..\packages\MQTTnet.3.0.15\lib\net452\MQTTnet.dll + + ..\packages\MySqlConnector.1.3.2\lib\net45\MySqlConnector.dll + ..\packages\SSH.NET.2016.1.0\lib\net40\Renci.SshNet.dll @@ -162,6 +157,10 @@ + + ..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll + + diff --git a/CumulusMX/EmailSender.cs b/CumulusMX/EmailSender.cs index 4a3404a5..9e167f03 100644 --- a/CumulusMX/EmailSender.cs +++ b/CumulusMX/EmailSender.cs @@ -10,9 +10,9 @@ namespace CumulusMX { public class EmailSender { - static Regex ValidEmailRegex = CreateValidEmailRegex(); + static readonly Regex ValidEmailRegex = CreateValidEmailRegex(); - private Cumulus cumulus; + private readonly Cumulus cumulus; public EmailSender(Cumulus cumulus) { diff --git a/CumulusMX/InternetSettings.cs b/CumulusMX/InternetSettings.cs index 69c6aace..fafeaffe 100644 --- a/CumulusMX/InternetSettings.cs +++ b/CumulusMX/InternetSettings.cs @@ -516,6 +516,11 @@ public string UpdateInternetConfig(IHttpContext context) cumulus.SmtpOptions.RequiresAuthentication = settings.emailsettings.authenticate; cumulus.SmtpOptions.User = settings.emailsettings.user; cumulus.SmtpOptions.Password = settings.emailsettings.password; + + if (cumulus.emailer == null) + { + cumulus.emailer = new EmailSender(cumulus); + } } } catch (Exception ex) diff --git a/CumulusMX/MysqlSettings.cs b/CumulusMX/MysqlSettings.cs index e99bf727..81c306e8 100644 --- a/CumulusMX/MysqlSettings.cs +++ b/CumulusMX/MysqlSettings.cs @@ -1,7 +1,7 @@ using System; using System.IO; using System.Net; -using Devart.Data.MySql; +using MySqlConnector; using ServiceStack; using Unosquare.Labs.EmbedIO; @@ -36,9 +36,14 @@ public string GetMySqlAlpacaFormData() table = cumulus.MySqlMonthlyTable }; + var reten = cumulus.MySqlRealtimeRetention.Split(' '); + var retenVal = string.IsNullOrEmpty(reten[0]) ? 7 : int.Parse(reten[0]); + var retenUnit = reten.Length > 1 && !string.IsNullOrEmpty(reten[1]) ? reten[1].ToUpper().TrimEnd('S') : "DAY"; + var realtime = new JsonMysqlSettingsRealtime() { - retention = cumulus.MySqlRealtimeRetention, + retentionVal = retenVal, + retentionUnit = retenUnit, table = cumulus.MySqlRealtimeTable }; @@ -140,7 +145,7 @@ public object UpdateMysqlConfig(IHttpContext context) cumulus.RealtimeMySqlEnabled = settings.realtimeenabled; if (cumulus.RealtimeMySqlEnabled) { - cumulus.MySqlRealtimeRetention = settings.realtime.retention; + cumulus.MySqlRealtimeRetention = settings.realtime.retentionVal + " " + settings.realtime.retentionUnit; cumulus.MySqlRealtimeTable = String.IsNullOrWhiteSpace(settings.realtime.table) ? "Realtime" : settings.realtime.table; } //dayfile @@ -181,11 +186,9 @@ public object UpdateMysqlConfig(IHttpContext context) // Save the settings cumulus.WriteIniFile(); - cumulus.MonthlyMySqlConn.Host = cumulus.MySqlHost; - cumulus.MonthlyMySqlConn.Port = cumulus.MySqlPort; - cumulus.MonthlyMySqlConn.UserId = cumulus.MySqlUser; - cumulus.MonthlyMySqlConn.Password = cumulus.MySqlPass; - cumulus.MonthlyMySqlConn.Database = cumulus.MySqlDatabase; + var connStr = $"server={cumulus.MySqlHost};port={cumulus.MySqlPort};user={cumulus.MySqlUser};password={cumulus.MySqlPass};database={cumulus.MySqlDatabase}"; + + cumulus.MonthlyMySqlConn.ConnectionString = connStr; cumulus.SetMonthlySqlCreateString(); cumulus.SetStartOfMonthlyInsertSQL(); @@ -193,34 +196,18 @@ public object UpdateMysqlConfig(IHttpContext context) cumulus.SetDayfileSqlCreateString(); cumulus.SetStartOfDayfileInsertSQL(); - cumulus.RealtimeSqlConn.Host = cumulus.MySqlHost; - cumulus.RealtimeSqlConn.Port = cumulus.MySqlPort; - cumulus.RealtimeSqlConn.UserId = cumulus.MySqlUser; - cumulus.RealtimeSqlConn.Password = cumulus.MySqlPass; - cumulus.RealtimeSqlConn.Database = cumulus.MySqlDatabase; + cumulus.RealtimeSqlConn.ConnectionString = connStr; cumulus.SetRealtimeSqlCreateString(); cumulus.SetStartOfRealtimeInsertSQL(); - cumulus.CustomMysqlSecondsConn.Host = cumulus.MySqlHost; - cumulus.CustomMysqlSecondsConn.Port = cumulus.MySqlPort; - cumulus.CustomMysqlSecondsConn.UserId = cumulus.MySqlUser; - cumulus.CustomMysqlSecondsConn.Password = cumulus.MySqlPass; - cumulus.CustomMysqlSecondsConn.Database = cumulus.MySqlDatabase; + cumulus.CustomMysqlSecondsConn.ConnectionString = connStr; cumulus.CustomMysqlSecondsTimer.Interval = cumulus.CustomMySqlSecondsInterval*1000; cumulus.CustomMysqlSecondsTimer.Enabled = cumulus.CustomMySqlSecondsEnabled; - cumulus.CustomMysqlMinutesConn.Host = cumulus.MySqlHost; - cumulus.CustomMysqlMinutesConn.Port = cumulus.MySqlPort; - cumulus.CustomMysqlMinutesConn.UserId = cumulus.MySqlUser; - cumulus.CustomMysqlMinutesConn.Password = cumulus.MySqlPass; - cumulus.CustomMysqlMinutesConn.Database = cumulus.MySqlDatabase; + cumulus.CustomMysqlMinutesConn.ConnectionString = connStr; - cumulus.CustomMysqlRolloverConn.Host = cumulus.MySqlHost; - cumulus.CustomMysqlRolloverConn.Port = cumulus.MySqlPort; - cumulus.CustomMysqlRolloverConn.UserId = cumulus.MySqlUser; - cumulus.CustomMysqlRolloverConn.Password = cumulus.MySqlPass; - cumulus.CustomMysqlRolloverConn.Database = cumulus.MySqlDatabase; + cumulus.CustomMysqlRolloverConn.ConnectionString = connStr; context.Response.StatusCode = 200; } @@ -235,16 +222,9 @@ public object UpdateMysqlConfig(IHttpContext context) private string CreateMySQLTable(string createSQL) { - var mySqlConn = new MySqlConnection(); - mySqlConn.Host = cumulus.MySqlHost; - mySqlConn.Port = cumulus.MySqlPort; - mySqlConn.UserId = cumulus.MySqlUser; - mySqlConn.Password = cumulus.MySqlPass; - mySqlConn.Database = cumulus.MySqlDatabase; - - MySqlCommand cmd = new MySqlCommand(); - cmd.CommandText = createSQL; - cmd.Connection = mySqlConn; + var mySqlConn = new MySqlConnection($"server={cumulus.MySqlHost};port={cumulus.MySqlPort};user={cumulus.MySqlUser};password={cumulus.MySqlPass};database={cumulus.MySqlDatabase}"); + + MySqlCommand cmd = new MySqlCommand(createSQL, mySqlConn); cumulus.LogMessage($"MySQL Create Table: {createSQL}"); string res; @@ -268,7 +248,8 @@ private string CreateMySQLTable(string createSQL) { mySqlConn.Close(); } - catch {} + catch + {} } return res; @@ -333,7 +314,8 @@ public class JsonMysqlSettingsMonthly public class JsonMysqlSettingsRealtime { public string table { get; set; } - public string retention { get; set; } + public int retentionVal { get; set; } + public string retentionUnit { get; set; } } public class JsonMysqlSettingsDayfile diff --git a/CumulusMX/Properties/AssemblyInfo.cs b/CumulusMX/Properties/AssemblyInfo.cs index a223af94..c0c0e17f 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.10.5 - Build 3122")] +[assembly: AssemblyDescription("Version 3.11.0 - Build 3123")] [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.10.5.3122")] -[assembly: AssemblyFileVersion("3.10.5.3122")] +[assembly: AssemblyVersion("3.11.0.3123")] +[assembly: AssemblyFileVersion("3.11.0.3123")] diff --git a/CumulusMX/WeatherStation.cs b/CumulusMX/WeatherStation.cs index c611320a..c8537bf3 100644 --- a/CumulusMX/WeatherStation.cs +++ b/CumulusMX/WeatherStation.cs @@ -15,7 +15,7 @@ using System.Threading; using System.Threading.Tasks; using System.Timers; -using Devart.Data.MySql; +using MySqlConnector; using SQLite; using Timer = System.Timers.Timer; using System.Security.Cryptography; @@ -1725,7 +1725,7 @@ private void MinuteChanged(DateTime now) // Custom MySQL update - minutes interval if (cumulus.CustomMySqlMinutesEnabled && now.Minute % cumulus.CustomMySqlMinutesInterval == 0) { - cumulus.CustomMysqlMinutesTimerTick(); + _ = cumulus.CustomMysqlMinutesTimerTick(); } // Custom HTTP update - minutes interval @@ -4475,7 +4475,7 @@ public void DayReset(DateTime timestamp) if (cumulus.CustomMySqlRolloverEnabled) { - cumulus.CustomMysqlRolloverTimerTick(); + _ = cumulus.CustomMysqlRolloverTimerTick(); } if (cumulus.CustomHttpRolloverEnabled) @@ -5417,12 +5417,7 @@ private void DoDayfile(DateTime timestamp) if (cumulus.DayfileMySqlEnabled) { - var mySqlConn = new MySqlConnection(); - mySqlConn.Host = cumulus.MySqlHost; - mySqlConn.Port = cumulus.MySqlPort; - mySqlConn.UserId = cumulus.MySqlUser; - mySqlConn.Password = cumulus.MySqlPass; - mySqlConn.Database = cumulus.MySqlDatabase; + var mySqlConn = new MySqlConnection($"server={cumulus.MySqlHost};port={cumulus.MySqlPort};user={cumulus.MySqlUser};password={cumulus.MySqlPass};database={cumulus.MySqlDatabase}"); var InvC = new CultureInfo(""); @@ -5486,33 +5481,7 @@ private void DoDayfile(DateTime timestamp) queryString.Append(")"); // run the query async so we do not block the main EOD processing - Task.Run(() => - { - try - { - MySqlCommand cmd = new MySqlCommand(); - cmd.CommandText = queryString.ToString(); - cmd.Connection = mySqlConn; - cumulus.LogMessage($"MySQL Dayfile: {cmd.CommandText}"); - - mySqlConn.Open(); - int aff = cmd.ExecuteNonQuery(); - cumulus.LogMessage($"MySQL Dayfile: Table {cumulus.MySqlDayfileTable} - {aff} rows were affected."); - } - catch (Exception ex) - { - cumulus.LogMessage("MySQL Dayfile: Error encountered during EOD MySQL operation."); - cumulus.LogMessage(ex.Message); - } - finally - { - try - { - mySqlConn.Close(); - } - catch { } - } - }); + _ = cumulus.MySqlCommandAsync(queryString.ToString(), mySqlConn, "MySQL Dayfile", true, true); } } diff --git a/CumulusMX/packages.config b/CumulusMX/packages.config index 1b1c2ae3..1f146797 100644 --- a/CumulusMX/packages.config +++ b/CumulusMX/packages.config @@ -12,6 +12,7 @@ + @@ -23,5 +24,6 @@ + \ No newline at end of file diff --git a/CumulusMX/webtags.cs b/CumulusMX/webtags.cs index d1713114..60a5757a 100644 --- a/CumulusMX/webtags.cs +++ b/CumulusMX/webtags.cs @@ -107,7 +107,7 @@ private static string CheckRc(string val, Dictionary tagParams) private static string CheckRcDp(double val, Dictionary tagParams, int decimals) { - string ret = ""; + string ret; try { if (tagParams.Get("tc") == "y") diff --git a/Updates.txt b/Updates.txt index 288d4b37..031a4fdb 100644 --- a/Updates.txt +++ b/Updates.txt @@ -26,12 +26,18 @@ - Changed: Moved FTP Logging option from Internet Settings to Program Setting to collect all the logging options in one place - Changed: A Data Stopped state now stops all logging, MySQL and web activity -- Changed: Updated various components to latest version... - FTP - FluentFTP - JSON - ServiceStack.Text - Pointers - System.Runtime.CompilerServcies.Unsafe - MQTT - MQTTnet +- Changed: Updates to various components... + - Updated to latest versions: + - FTP: FluentFTP + - JSON: ServiceStack.Text + - Pointers: System.Runtime.CompilerServcies.Unsafe + - MQTT: MQTTnet + - Removed/Added + - MySQL: Removed Devart, replaced with MySqlConnector & BouncyCastle.Crypto - Changed: FineOffset stations now attempt to reconnect the USB after a data stopped event. These stations now also keep retrying to connect to the station at start-up +- Changed: The MySQL real time table data retention settings reworked. + + 3.10.5 - b3122 —————————————— From 0291c1d46f80b8d37bd91db1f73077c30bfdad54 Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Wed, 14 Apr 2021 10:17:16 +0100 Subject: [PATCH 06/16] Change ET web tag to use same dp as rainfall --- CumulusMX/webtags.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CumulusMX/webtags.cs b/CumulusMX/webtags.cs index 60a5757a..49fe2ed0 100644 --- a/CumulusMX/webtags.cs +++ b/CumulusMX/webtags.cs @@ -3105,7 +3105,7 @@ private string TagRCpressTl(Dictionary tagParams) private string TagEt(Dictionary tagParams) { - return CheckRcDp(station.ET, tagParams, cumulus.RainDPlaces + 1); + return CheckRcDp(station.ET, tagParams, cumulus.RainDPlaces); } private string TagLight(Dictionary tagParams) From 32a3f30f9cf2eab361e3a082d23816e0e564cf20 Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Wed, 14 Apr 2021 18:14:00 +0100 Subject: [PATCH 07/16] FineOffset stations were not ignoring invalid wind speeds on historic catch up --- CumulusMX/FOStation.cs | 67 +++++++++++++++++++++++------------------- Updates.txt | 1 + 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/CumulusMX/FOStation.cs b/CumulusMX/FOStation.cs index 0d33b690..a89466de 100644 --- a/CumulusMX/FOStation.cs +++ b/CumulusMX/FOStation.cs @@ -295,7 +295,7 @@ private void ProcessHistoryData() } // In rollover hour and rollover not yet done - if ((h == rollHour) && !rolloverdone) + if (h == rollHour && !rolloverdone) { // do rollover cumulus.LogMessage("Day rollover " + timestamp.ToShortTimeString()); @@ -311,7 +311,7 @@ private void ProcessHistoryData() } // In midnight hour and midnight rain (and sun) not yet done - if ((h == 0) && !midnightraindone) + if (h == 0 && !midnightraindone) { ResetMidnightRain(timestamp); ResetSunshineHours(); @@ -319,18 +319,22 @@ private void ProcessHistoryData() } // Indoor Humidity ====================================================== - if ((historydata.inHum > 100) && (historydata.inHum != 255)) + if (historydata.inHum > 100 || historydata.inHum < 0) { + // 255 is the overflow value, when RH gets below 10% - ignore cumulus.LogMessage("Ignoring bad data: inhum = " + historydata.inHum); } - else if ((historydata.inHum > 0) && (historydata.inHum != 255)) + else { - // 255 is the overflow value, when RH gets below 10% - ignore DoIndoorHumidity(historydata.inHum); } // Indoor Temperature =================================================== - if ((historydata.inTemp > -50) && (historydata.inTemp < 50)) + if (historydata.inTemp < -50 || historydata.inTemp > 50) + { + cumulus.LogMessage("Ignoring bad data: intemp = " + historydata.inTemp); + } + else { DoIndoorTemp(ConvertTempCToUser(historydata.inTemp)); } @@ -354,31 +358,32 @@ private void ProcessHistoryData() else { // Outdoor Humidity ===================================================== - if ((historydata.outHum > 100) && (historydata.outHum != 255)) + if (historydata.outHum > 100 || historydata.outHum < 0) { + // 255 is the overflow value, when RH gets below 10% - ignore cumulus.LogMessage("Ignoring bad data: outhum = " + historydata.outHum); } - else if ((historydata.outHum > 0) && (historydata.outHum != 255)) + else { - // 255 is the overflow value, when RH gets below 10% - ignore DoOutdoorHumidity(historydata.outHum, timestamp); } // Wind ================================================================= - if ((historydata.windGust > 60) || (historydata.windGust < 0)) + if (historydata.windGust > 60 || historydata.windGust < 0) { cumulus.LogMessage("Ignoring bad data: gust = " + historydata.windGust); } - else if ((historydata.windSpeed > 60) || (historydata.windSpeed < 0)) + else if (historydata.windSpeed > 60 || historydata.windSpeed < 0) { cumulus.LogMessage("Ignoring bad data: speed = " + historydata.windSpeed); } + else { DoWind(ConvertWindMSToUser(historydata.windGust), historydata.windBearing, ConvertWindMSToUser(historydata.windSpeed), timestamp); } // Outdoor Temperature ================================================== - if ((historydata.outTemp < -50) || (historydata.outTemp > 70)) + if (historydata.outTemp < -50 || historydata.outTemp > 70) { cumulus.LogMessage("Ignoring bad data: outtemp = " + historydata.outTemp); } @@ -825,14 +830,14 @@ private void GetAndProcessData() prevdata[i] = data[i]; } - if ((!synchronising) || ((readCounter%20) == 0)) + if (!synchronising || (readCounter%20) == 0) { LatestFOReading = addr.ToString("X4") + ": " + BitConverter.ToString(data, 0, 16); cumulus.LogDataMessage(LatestFOReading); // Indoor Humidity ==================================================== int inhum = data[1]; - if ((inhum > 100) && (inhum != 255)) + if (inhum > 100 || inhum < 0) { // bad value cumulus.LogMessage("Ignoring bad data: inhum = " + inhum); @@ -859,7 +864,11 @@ private void GetAndProcessData() intemp = -intemp; } - if ((intemp > -50) && (intemp < 50)) + if (intemp < -50 || intemp > 50) + { + cumulus.LogMessage("Ignoring bad data: intemp = " + intemp); + } + else { DoIndoorTemp(ConvertTempCToUser(intemp)); } @@ -867,7 +876,7 @@ private void GetAndProcessData() // Pressure ========================================================= double pressure = (data[7] + ((data[8] & 0x3f)*256))/10.0f + pressureOffset; - if ((pressure < cumulus.EwOptions.MinPressMB) || (pressure > cumulus.EwOptions.MaxPressMB)) + if (pressure < cumulus.EwOptions.MinPressMB || pressure > cumulus.EwOptions.MaxPressMB) { // bad value cumulus.LogMessage("Ignoring bad data: pressure = " + pressure); @@ -899,7 +908,7 @@ private void GetAndProcessData() // Outdoor Humidity =================================================== int outhum = data[4]; - if ((outhum > 100) && (outhum != 255)) + if (outhum > 100 || outhum < 0) { // bad value cumulus.LogMessage("Ignoring bad data: outhum = " + outhum); @@ -923,12 +932,12 @@ private void GetAndProcessData() double windspeed = (data[9] + ((data[11] & 0x0F)*256))/10.0f; var winddir = (int) (data[12]*22.5f); - if ((gust > 60) || (gust < 0)) + if (gust > 60 || gust < 0) { // bad value cumulus.LogMessage("Ignoring bad data: gust = " + gust); } - else if ((windspeed > 60) || (windspeed < 0)) + else if (windspeed > 60 || windspeed < 0) { // bad value cumulus.LogMessage("Ignoring bad data: speed = " + gust); @@ -943,7 +952,7 @@ private void GetAndProcessData() sign = (byte) (data[6] & 0x80); if (sign == 0x80) outtemp = -outtemp; - if ((outtemp < -50) || (outtemp > 70)) + if (outtemp < -50 || (outtemp > 70) { // bad value cumulus.LogMessage("Ignoring bad data: outtemp = " + outtemp); @@ -959,21 +968,17 @@ private void GetAndProcessData() CheckForDewpointHighLow(now); } - ; // calculate wind chill - if ((outtemp > -50) || (outtemp < 70)) - { - // The 'global average speed will have been determined by the call of DoWind - // so use that in the wind chill calculation - double avgspeedKPH = ConvertUserWindToKPH(WindAverage); + // The 'global average speed will have been determined by the call of DoWind + // so use that in the wind chill calculation + double avgspeedKPH = ConvertUserWindToKPH(WindAverage); - // windinMPH = calibwind * 2.23693629; - // calculate wind chill from calibrated C temp and calibrated win in KPH - double val = MeteoLib.WindChill(ConvertUserTempToC(OutdoorTemperature), avgspeedKPH); + // windinMPH = calibwind * 2.23693629; + // calculate wind chill from calibrated C temp and calibrated win in KPH + double val = MeteoLib.WindChill(ConvertUserTempToC(OutdoorTemperature), avgspeedKPH); - DoWindChill(ConvertTempCToUser(val), now); - } + DoWindChill(ConvertTempCToUser(val), now); DoApparentTemp(now); DoFeelsLike(now); diff --git a/Updates.txt b/Updates.txt index 031a4fdb..69ea4605 100644 --- a/Updates.txt +++ b/Updates.txt @@ -5,6 +5,7 @@ - Fix: OpenWeatherMap - create new station was not forcing the use of dot decimals for lat/long values - Fix: WeatherCloud - date/time of reading should be UTC - Fix: End of day backup now always runs at rollover +- Fix: FineOffset stations were not ignoring invalid wind speeds on historic catch up - New: Adds Email support for Alarms - Creates a new section in Cumulus.ini: From 780436b1f991e8407a771492c4717afeb27cf40f Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Mon, 26 Apr 2021 16:07:09 +0100 Subject: [PATCH 08/16] Latest 3.11.0 changes --- CumulusMX/AlarmSettings.cs | 75 ++- CumulusMX/Api.cs | 73 +-- CumulusMX/App.config | 4 + CumulusMX/CalibrationSettings.cs | 44 +- CumulusMX/Cumulus.cs | 329 ++++++------ CumulusMX/CumulusMX.csproj | 7 +- CumulusMX/DavisStation.cs | 2 +- CumulusMX/DavisWllStation.cs | 4 +- CumulusMX/EmailSender.cs | 47 +- CumulusMX/ExtraSensorSettings.cs | 56 ++- CumulusMX/FOStation.cs | 16 +- CumulusMX/GW1000Station.cs | 248 ++++++---- CumulusMX/InternetSettings.cs | 612 ++--------------------- CumulusMX/MeteoLib.cs | 70 ++- CumulusMX/MysqlSettings.cs | 46 +- CumulusMX/NOAASettings.cs | 36 +- CumulusMX/ProgramSettings.cs | 87 ++-- CumulusMX/Properties/AssemblyInfo.cs | 6 +- CumulusMX/StationSettings.cs | 151 +++++- CumulusMX/ThirdPartySettings.cs | 686 ++++++++++++++++++++++++++ CumulusMX/Utils.cs | 45 ++ CumulusMX/WeatherStation.cs | 713 +++++++++++++++++++++++++-- CumulusMX/packages.config | 4 +- Updates.txt | 32 +- 24 files changed, 2310 insertions(+), 1083 deletions(-) create mode 100644 CumulusMX/ThirdPartySettings.cs diff --git a/CumulusMX/AlarmSettings.cs b/CumulusMX/AlarmSettings.cs index 6d9e65a2..00eba60e 100644 --- a/CumulusMX/AlarmSettings.cs +++ b/CumulusMX/AlarmSettings.cs @@ -179,19 +179,35 @@ public string GetAlarmSettings() public string UpdateAlarmSettings(IHttpContext context) { + var json = ""; + JsonAlarmSettings result; + JsonAlarmSettingsData settings; + try { var data = new StreamReader(context.Request.InputStream).ReadToEnd(); // Start at char 5 to skip the "json:" prefix - var json = WebUtility.UrlDecode(data); + json = WebUtility.UrlDecode(data); // de-serialize it to the settings structure //var settings = JsonConvert.DeserializeObject(json); //var settings = JsonSerializer.DeserializeFromString(json); - var result = json.FromJson(); - var settings = result.data; + result = json.FromJson(); + settings = result.data; + } + catch (Exception ex) + { + var msg = "Error deserializing Alarm Settings JSON: " + ex.Message; + cumulus.LogMessage(msg); + cumulus.LogDebugMessage("Alarm Data: " + json); + context.Response.StatusCode = 500; + return msg; + } + + try + { // process the settings cumulus.LogMessage("Updating Alarm settings"); @@ -358,11 +374,64 @@ public string UpdateAlarmSettings(IHttpContext context) context.Response.StatusCode = 200; } catch (Exception ex) + { + cumulus.LogMessage("Error processing Alarm settings: " + ex.Message); + cumulus.LogDebugMessage("Alarm Data: " + json); + context.Response.StatusCode = 500; + return ex.Message; + } + return "success"; + } + + public string TestEmail(IHttpContext context) + { + try + { + + var data = new StreamReader(context.Request.InputStream).ReadToEnd(); + + // Start at char 5 to skip the "json:" prefix + var json = WebUtility.UrlDecode(data); + + var result = json.FromJson(); + // process the settings + cumulus.LogMessage("Sending test email..."); + + // validate the from email + if (!EmailSender.CheckEmailAddress(result.fromEmail.Trim())) + { + var msg = "ERROR: Invalid Alarm from email address entered"; + cumulus.LogMessage(msg); + context.Response.StatusCode = 500; + return msg; + } + var from = result.fromEmail.Trim(); + + // validate the destination email(s) + var dest = result.destEmail.Trim().Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + for (var i = 0; i < dest.Length; i++) + { + dest[i] = dest[i].Trim(); + if (!EmailSender.CheckEmailAddress(dest[i])) + { + var msg = "ERROR: Invalid Alarm destination email address entered"; + cumulus.LogMessage(msg); + context.Response.StatusCode = 500; + return msg; + } + } + + cumulus.emailer.SendTestEmail(dest, from, "Cumulus MX Test Email", "A test email from Cumulus MX.", result.useHtml); + + cumulus.LogMessage("Test email sent without error"); + } + catch (Exception ex) { cumulus.LogMessage(ex.Message); context.Response.StatusCode = 500; return ex.Message; } + return "success"; } } diff --git a/CumulusMX/Api.cs b/CumulusMX/Api.cs index ec9a24e0..1ef7dd1d 100644 --- a/CumulusMX/Api.cs +++ b/CumulusMX/Api.cs @@ -18,6 +18,7 @@ public static class Api public static ProgramSettings programSettings; internal static StationSettings stationSettings; public static InternetSettings internetSettings; + public static ThirdPartySettings thirdpartySettings; public static ExtraSensorSettings extraSensorSettings; public static CalibrationSettings calibrationSettings; public static NOAASettings noaaSettings; @@ -449,6 +450,10 @@ public async Task GetDailyGraphData() return await this.JsonResponseAsync(Station.GetAllDailyHumGraphData()); case "solardata.json": return await this.JsonResponseAsync(Station.GetAllDailySolarGraphData()); + case "degdaydata.json": + return await this.JsonResponseAsync(Station.GetAllDegreeDaysGraphData()); + case "tempsumdata.json": + return await this.JsonResponseAsync(Station.GetAllTempSumGraphData()); case "units.json": return await this.JsonResponseAsync(Station.GetUnits()); case "graphconfig.json": @@ -821,48 +826,56 @@ public async Task SettingsGet() switch (lastSegment) { case "programdata.json": - return await this.JsonResponseAsync(programSettings.GetProgramAlpacaFormData()); + return await this.JsonResponseAsync(programSettings.GetAlpacaFormData()); case "programoptions.json": - return await this.JsonResponseAsync(programSettings.GetProgramAlpacaFormOptions()); + return await this.JsonResponseAsync(programSettings.GetAlpacaFormOptions()); case "programschema.json": - return await this.JsonResponseAsync(programSettings.GetProgramAlpacaFormSchema()); + return await this.JsonResponseAsync(programSettings.GetAlpacaFormSchema()); case "stationdata.json": - return await this.JsonResponseAsync(stationSettings.GetStationAlpacaFormData()); + return await this.JsonResponseAsync(stationSettings.GetAlpacaFormData()); case "stationoptions.json": - return await this.JsonResponseAsync(stationSettings.GetStationAlpacaFormOptions()); + return await this.JsonResponseAsync(stationSettings.GetAlpacaFormOptions()); case "stationschema.json": - return await this.JsonResponseAsync(stationSettings.GetStationAlpacaFormSchema()); + return await this.JsonResponseAsync(stationSettings.GetAlpacaFormSchema()); case "internetdata.json": - return await this.JsonResponseAsync(internetSettings.GetInternetAlpacaFormData()); + return await this.JsonResponseAsync(internetSettings.GetAlpacaFormData()); case "internetoptions.json": - return await this.JsonResponseAsync(internetSettings.GetInternetAlpacaFormOptions()); + return await this.JsonResponseAsync(internetSettings.GetAlpacaFormOptions()); case "internetschema.json": - return await this.JsonResponseAsync(internetSettings.GetInternetAlpacaFormSchema()); + return await this.JsonResponseAsync(internetSettings.GetAlpacaFormSchema()); + + case "thirdpartydata.json": + return await this.JsonResponseAsync(thirdpartySettings.GetAlpacaFormData()); + case "thirdpartyoptions.json": + return await this.JsonResponseAsync(thirdpartySettings.GetAlpacaFormOptions()); + case "thirdpartyschema.json": + return await this.JsonResponseAsync(thirdpartySettings.GetAlpacaFormSchema()); case "extrasensordata.json": - return await this.JsonResponseAsync(extraSensorSettings.GetExtraSensorAlpacaFormData()); + return await this.JsonResponseAsync(extraSensorSettings.GetAlpacaFormData()); case "extrasensoroptions.json": - return await this.JsonResponseAsync(extraSensorSettings.GetExtraSensorAlpacaFormOptions()); + return await this.JsonResponseAsync(extraSensorSettings.GetAlpacaFormOptions()); case "extrasensorschema.json": - return await this.JsonResponseAsync(extraSensorSettings.GetExtraSensorAlpacaFormSchema()); + return await this.JsonResponseAsync(extraSensorSettings.GetAlpacaFormSchema()); + case "extrawebfiles.json": return await this.JsonResponseAsync(internetSettings.GetExtraWebFilesData()); case "calibrationdata.json": - return await this.JsonResponseAsync(calibrationSettings.GetCalibrationAlpacaFormData()); + return await this.JsonResponseAsync(calibrationSettings.GetAlpacaFormData()); case "calibrationoptions.json": - return await this.JsonResponseAsync(calibrationSettings.GetCalibrationAlpacaFormOptions()); + return await this.JsonResponseAsync(calibrationSettings.GetAlpacaFormOptions()); case "calibrationschema.json": - return await this.JsonResponseAsync(calibrationSettings.GetCalibrationAlpacaFormSchema()); + return await this.JsonResponseAsync(calibrationSettings.GetAlpacaFormSchema()); case "noaadata.json": - return await this.JsonResponseAsync(noaaSettings.GetNoaaAlpacaFormData()); + return await this.JsonResponseAsync(noaaSettings.GetAlpacaFormData()); case "noaaoptions.json": - return await this.JsonResponseAsync(noaaSettings.GetNoaaAlpacaFormOptions()); + return await this.JsonResponseAsync(noaaSettings.GetAlpacaFormOptions()); case "noaaschema.json": - return await this.JsonResponseAsync(noaaSettings.GetNoaaAlpacaFormSchema()); + return await this.JsonResponseAsync(noaaSettings.GetAlpacaFormSchema()); case "wsport.json": return await this.JsonResponseAsync(stationSettings.GetWSport()); @@ -870,11 +883,11 @@ public async Task SettingsGet() return await this.JsonResponseAsync(stationSettings.GetVersion()); case "mysqldata.json": - return await this.JsonResponseAsync(mySqlSettings.GetMySqlAlpacaFormData()); + return await this.JsonResponseAsync(mySqlSettings.GetAlpacaFormData()); case "mysqloptions.json": - return await this.JsonResponseAsync(mySqlSettings.GetMySqAlpacaFormOptions()); + return await this.JsonResponseAsync(mySqlSettings.GetAlpacaFormOptions()); case "mysqlschema.json": - return await this.JsonResponseAsync(mySqlSettings.GetMySqAlpacaFormSchema()); + return await this.JsonResponseAsync(mySqlSettings.GetAlpacaFormSchema()); case "alarms.json": return await this.JsonResponseAsync(alarmSettings.GetAlarmSettings()); @@ -899,22 +912,24 @@ public async Task SettingsSet() switch (lastSegment) { case "updateprogramconfig.json": - return await this.JsonResponseAsync(programSettings.UpdateProgramConfig(this)); + return await this.JsonResponseAsync(programSettings.UpdateConfig(this)); case "updatestationconfig.json": - return await this.JsonResponseAsync(stationSettings.UpdateStationConfig(this)); + return await this.JsonResponseAsync(stationSettings.UpdateConfig(this)); case "updateinternetconfig.json": - return await this.JsonResponseAsync(internetSettings.UpdateInternetConfig(this)); + return await this.JsonResponseAsync(internetSettings.UpdateConfig(this)); + case "updatethirdpartyconfig.json": + return await this.JsonResponseAsync(thirdpartySettings.UpdateConfig(this)); case "updateextrasensorconfig.json": - return await this.JsonResponseAsync(extraSensorSettings.UpdateExtraSensorConfig(this)); + return await this.JsonResponseAsync(extraSensorSettings.UpdateConfig(this)); case "updatecalibrationconfig.json": - return await this.JsonResponseAsync(calibrationSettings.UpdateCalibrationConfig(this)); + return await this.JsonResponseAsync(calibrationSettings.UpdateConfig(this)); case "updatenoaaconfig.json": - return await this.JsonResponseAsync(noaaSettings.UpdateNoaaConfig(this)); + return await this.JsonResponseAsync(noaaSettings.UpdateConfig(this)); case "updateextrawebfiles.html": return await this.JsonResponseAsync(internetSettings.UpdateExtraWebFiles(this)); case "updatemysqlconfig.json": - return await this.JsonResponseAsync(mySqlSettings.UpdateMysqlConfig(this)); + return await this.JsonResponseAsync(mySqlSettings.UpdateConfig(this)); case "createmonthlysql.json": return await this.JsonResponseAsync(mySqlSettings.CreateMonthlySQL(this)); case "createdayfilesql.json": @@ -925,6 +940,8 @@ public async Task SettingsSet() return await this.JsonResponseAsync(alarmSettings.UpdateAlarmSettings(this)); case "ftpnow.json": return await this.JsonResponseAsync(stationSettings.FtpNow(this)); + case "testemail.json": + return await this.JsonResponseAsync(alarmSettings.TestEmail(this)); } throw new KeyNotFoundException("Key Not Found: " + lastSegment); diff --git a/CumulusMX/App.config b/CumulusMX/App.config index 1f9d3bde..a8b5174a 100644 --- a/CumulusMX/App.config +++ b/CumulusMX/App.config @@ -89,6 +89,10 @@ + + + + diff --git a/CumulusMX/CalibrationSettings.cs b/CumulusMX/CalibrationSettings.cs index 887b832f..86532440 100644 --- a/CumulusMX/CalibrationSettings.cs +++ b/CumulusMX/CalibrationSettings.cs @@ -10,28 +10,43 @@ namespace CumulusMX public class CalibrationSettings { private readonly Cumulus cumulus; - private readonly string calibrationOptionsFile; - private readonly string calibrationSchemaFile; + private readonly string optionsFile; + private readonly string schemaFile; public CalibrationSettings(Cumulus cumulus) { this.cumulus = cumulus; - calibrationOptionsFile = cumulus.AppDir + "interface"+Path.DirectorySeparatorChar+"json" + Path.DirectorySeparatorChar + "CalibrationOptions.json"; - calibrationSchemaFile = cumulus.AppDir + "interface"+Path.DirectorySeparatorChar+"json" + Path.DirectorySeparatorChar + "CalibrationSchema.json"; + optionsFile = cumulus.AppDir + "interface"+Path.DirectorySeparatorChar+"json" + Path.DirectorySeparatorChar + "CalibrationOptions.json"; + schemaFile = cumulus.AppDir + "interface"+Path.DirectorySeparatorChar+"json" + Path.DirectorySeparatorChar + "CalibrationSchema.json"; } //public string UpdateCalibrationConfig(HttpListenerContext context) - public string UpdateCalibrationConfig(IHttpContext context) + public string UpdateConfig(IHttpContext context) { + var json = ""; + JsonCalibrationSettingsData settings; + var invC = new CultureInfo(""); + try { - var invC = new CultureInfo(""); var data = new StreamReader(context.Request.InputStream).ReadToEnd(); - var json = WebUtility.UrlDecode(data.Substring(5)); + json = WebUtility.UrlDecode(data.Substring(5)); // de-serialize it to the settings structure - var settings = json.FromJson(); + settings = json.FromJson(); + } + catch (Exception ex) + { + var msg = "Error deserializing Calibration Settings JSON: " + ex.Message; + cumulus.LogMessage(msg); + cumulus.LogDebugMessage("Calibration Data: " + json); + context.Response.StatusCode = 500; + return msg; + } + + try + { // process the settings cumulus.LogMessage("Updating calibration settings"); @@ -91,14 +106,15 @@ public string UpdateCalibrationConfig(IHttpContext context) } catch (Exception ex) { - cumulus.LogMessage(ex.Message); + cumulus.LogMessage("Error setting Calibration settings: " + ex.Message); + cumulus.LogDebugMessage("Calibration Data: " + json); context.Response.StatusCode = 500; return ex.Message; } return "success"; } - public string GetCalibrationAlpacaFormData() + public string GetAlpacaFormData() { //var InvC = new CultureInfo(""); var offsets = new JsonCalibrationSettingsOffsets() @@ -161,18 +177,18 @@ public string GetCalibrationAlpacaFormData() return data.ToJson(); } - public string GetCalibrationAlpacaFormOptions() + public string GetAlpacaFormOptions() { - using (StreamReader sr = new StreamReader(calibrationOptionsFile)) + using (StreamReader sr = new StreamReader(optionsFile)) { string json = sr.ReadToEnd(); return json; } } - public string GetCalibrationAlpacaFormSchema() + public string GetAlpacaFormSchema() { - using (StreamReader sr = new StreamReader(calibrationSchemaFile)) + using (StreamReader sr = new StreamReader(schemaFile)) { string json = sr.ReadToEnd(); return json; diff --git a/CumulusMX/Cumulus.cs b/CumulusMX/Cumulus.cs index c70b27ff..ef38d8bc 100644 --- a/CumulusMX/Cumulus.cs +++ b/CumulusMX/Cumulus.cs @@ -515,6 +515,9 @@ public struct TExtraFiles // Windy.com settings public WebUploadWindy Windy = new WebUploadWindy(); + // Wind Guru settings + public WebUploadWindGuru WindGuru = new WebUploadWindGuru(); + // PWS Weather settings public WebUploadService PWS = new WebUploadService(); @@ -528,7 +531,7 @@ public struct TExtraFiles public WebUploadAwekas AWEKAS = new WebUploadAwekas(); // WeatherCloud settings - public WebUploadService WCloud = new WebUploadService(); + public WebUploadWCloud WCloud = new WebUploadWCloud(); // OpenWeatherMap settings public WebUploadService OpenWeatherMap = new WebUploadService(); @@ -584,6 +587,16 @@ public struct MqttSettings public double[] NOAARainNorms = new double[13]; + // Growing Degree Days + public double GrowingBase1; + public double GrowingBase2; + public int GrowingYearStarts; + public bool GrowingCap30C; + + public int TempSumYearStarts; + public double TempSumBase1; + public double TempSumBase2; + public bool EODfilesNeedFTP; public bool IsOSX; @@ -632,6 +645,9 @@ public struct MqttSettings private static readonly HttpClientHandler WindyhttpHandler = new HttpClientHandler(); private readonly HttpClient WindyhttpClient = new HttpClient(WindyhttpHandler); + private static readonly HttpClientHandler WindGuruhttpHandler = new HttpClientHandler(); + private readonly HttpClient WindGuruhttpClient = new HttpClient(WindGuruhttpHandler); + private static readonly HttpClientHandler AwekashttpHandler = new HttpClientHandler(); private readonly HttpClient AwekashttpClient = new HttpClient(AwekashttpHandler); @@ -858,6 +874,7 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) // Set the default upload intervals for web services Wund.DefaultInterval = 15; Windy.DefaultInterval = 15; + WindGuru.DefaultInterval = 1; PWS.DefaultInterval = 15; APRS.DefaultInterval = 9; AWEKAS.DefaultInterval = 15 * 60; @@ -973,7 +990,7 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) RemoteFileName = "airquality.json" }; - GraphDataEodFiles = new FileGenerationFtpOptions[6]; + GraphDataEodFiles = new FileGenerationFtpOptions[8]; GraphDataEodFiles[0] = new FileGenerationFtpOptions() { LocalPath = WebPath, @@ -1010,6 +1027,18 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) LocalFileName = "alldailysolardata.json", RemoteFileName = "alldailysolardata.json" }; + GraphDataEodFiles[6] = new FileGenerationFtpOptions() + { + LocalPath = WebPath, + LocalFileName = "alldailydegdaydata.json", + RemoteFileName = "alldailydegdaydata.json" + }; + GraphDataEodFiles[7] = new FileGenerationFtpOptions() + { + LocalPath = WebPath, + LocalFileName = "alltempsumdata.json", + RemoteFileName = "alltempsumdata.json" + }; ReadIniFile(); @@ -1042,6 +1071,7 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) { reply = ping.Send(ProgramOptions.StartupPingHost, 2000); // 2 second timeout + LogMessage($"PING response = {reply.Status}"); } catch (Exception e) { @@ -1052,8 +1082,19 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) { // no response wait 10 seconds before trying again Thread.Sleep(10000); - // Force a DNS refresh - Dns.GetHostEntry(ProgramOptions.StartupPingHost); + // Force a DNS refresh if not an IPv4 address + if (!Utils.ValidateIPv4(ProgramOptions.StartupPingHost)) + { + // catch and ignore IPv6 and invalid hostname for now + try + { + Dns.GetHostEntry(ProgramOptions.StartupPingHost); + } + catch (Exception ex) + { + LogMessage($"PING: Error with DNS refresh - {ex.Message}"); + } + } } } while ((reply == null || reply.Status != IPStatus.Success) && DateTime.Now < endTime); @@ -1295,11 +1336,12 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) httpServer.Module().UseRamCache = true; // Set up the API web server - // Soem APi functions require the station, so set them after station initialisation + // Some APi functions require the station, so set them after station initialisation Api.Setup(httpServer); Api.programSettings = new ProgramSettings(this); Api.stationSettings = new StationSettings(this); Api.internetSettings = new InternetSettings(this); + Api.thirdpartySettings = new ThirdPartySettings(this); Api.extraSensorSettings = new ExtraSensorSettings(this); Api.calibrationSettings = new CalibrationSettings(this); Api.noaaSettings = new NOAASettings(this); @@ -1414,16 +1456,8 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) SetFtpLogging(FTPlogging); - TwitterTimer.Elapsed += TwitterTimerTick; - WundTimer.Elapsed += WundTimerTick; - WindyTimer.Elapsed += WindyTimerTick; - PWSTimer.Elapsed += PWSTimerTick; - WOWTimer.Elapsed += WowTimerTick; AwekasTimer.Elapsed += AwekasTimerTick; - WCloudTimer.Elapsed += WCloudTimerTick; - APRStimer.Elapsed += APRSTimerTick; - OpenWeatherMapTimer.Elapsed += OpenWeatherMapTimerTick; WebTimer.Elapsed += WebTimerTick; xapsource = "sanday.cumulus." + Environment.MachineName; @@ -1704,23 +1738,6 @@ public void SetFtpLogging(bool isSet) } } - /* - internal string CalculateMD5Hash(string input) - { - // step 1, calculate MD5 hash from input - MD5 md5 = MD5.Create(); - byte[] inputBytes = Encoding.ASCII.GetBytes(input); - byte[] hash = md5.ComputeHash(inputBytes); - - // step 2, convert byte array to hex string - StringBuilder sb = new StringBuilder(); - foreach (var t in hash) - { - sb.Append(t.ToString("X2")); - } - return sb.ToString(); - } - */ /* private string LocalIPAddress() @@ -1879,11 +1896,6 @@ private void WebTimerTick(object sender, ElapsedEventArgs e) } } - private void TwitterTimerTick(object sender, ElapsedEventArgs e) - { - UpdateTwitter(); - } - internal async void UpdateTwitter() { if (station.DataStopped) @@ -1983,11 +1995,6 @@ private void WundTimerTick(object sender, ElapsedEventArgs e) UpdateWunderground(DateTime.Now); } - private void WindyTimerTick(object sender, ElapsedEventArgs e) - { - if (!string.IsNullOrWhiteSpace(Windy.ApiKey)) - UpdateWindy(DateTime.Now); - } private void AwekasTimerTick(object sender, ElapsedEventArgs e) { @@ -1995,18 +2002,6 @@ private void AwekasTimerTick(object sender, ElapsedEventArgs e) UpdateAwekas(DateTime.Now); } - private void WCloudTimerTick(object sender, ElapsedEventArgs e) - { - if (!string.IsNullOrWhiteSpace(WCloud.ID)) - UpdateWCloud(DateTime.Now); - } - - private void OpenWeatherMapTimerTick(object sender, ElapsedEventArgs e) - { - if (!string.IsNullOrWhiteSpace(OpenWeatherMap.ID) && !string.IsNullOrWhiteSpace(OpenWeatherMap.PW)) - UpdateOpenWeatherMap(DateTime.Now); - } - public void MQTTTimerTick(object sender, ElapsedEventArgs e) { if (!station.DataStopped) @@ -2028,18 +2023,6 @@ public void AirLinkTimerTick(object sender, ElapsedEventArgs e) } */ - private void PWSTimerTick(object sender, ElapsedEventArgs e) - { - if (!string.IsNullOrWhiteSpace(PWS.ID)) - UpdatePWSweather(DateTime.Now); - } - - private void WowTimerTick(object sender, ElapsedEventArgs e) - { - if (!string.IsNullOrWhiteSpace(WOW.ID)) - UpdateWOW(DateTime.Now); - } - internal async void UpdateWunderground(DateTime timestamp) { if (Wund.Updating || station.DataStopped) @@ -2058,21 +2041,21 @@ internal async void UpdateWunderground(DateTime timestamp) string logUrl = URL.Replace(pwstring, starredpwstring); if (!Wund.RapidFireEnabled) { - LogDebugMessage("WU URL: " + logUrl); + LogDebugMessage("Wunderground: URL = " + logUrl); } try { HttpResponseMessage response = await WUhttpClient.GetAsync(URL); var responseBodyAsText = await response.Content.ReadAsStringAsync(); - if (!Wund.RapidFireEnabled) + if (!Wund.RapidFireEnabled || response.StatusCode != HttpStatusCode.OK) { - LogMessage("WU Response: " + response.StatusCode + ": " + responseBodyAsText); + LogDebugMessage("Wunderground: Response = " + response.StatusCode + ": " + responseBodyAsText); } } catch (Exception ex) { - LogMessage("WU update: " + ex.Message); + LogMessage("Wunderground: ERROR - " + ex.Message); } finally { @@ -2094,17 +2077,17 @@ internal async void UpdateWindy(DateTime timestamp) string url = station.GetWindyURL(out apistring, timestamp); string logUrl = url.Replace(apistring, "<>"); - LogDebugMessage("Windy URL: " + logUrl); + LogDebugMessage("Windy: URL = " + logUrl); try { HttpResponseMessage response = await WindyhttpClient.GetAsync(url); var responseBodyAsText = await response.Content.ReadAsStringAsync(); - LogMessage("Windy Response: " + response.StatusCode + ": " + responseBodyAsText); + LogDebugMessage("Windy: Response = " + response.StatusCode + ": " + responseBodyAsText); } catch (Exception ex) { - LogMessage("Windy update: " + ex.Message); + LogMessage("Windy: ERROR - " + ex.Message); } finally { @@ -2112,6 +2095,39 @@ internal async void UpdateWindy(DateTime timestamp) } } + internal async void UpdateWindGuru(DateTime timestamp) + { + if (WindGuru.Updating || station.DataStopped) + { + // No data coming in, do not do anything + return; + } + + WindGuru.Updating = true; + + string apistring; + string url = station.GetWindGuruURL(out apistring, timestamp); + string logUrl = url.Replace(apistring, "<>"); + + LogDebugMessage("WindGuru: URL = " + logUrl); + + try + { + HttpResponseMessage response = await WindGuruhttpClient.GetAsync(url); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + LogDebugMessage("WindGuru: " + response.StatusCode + ": " + responseBodyAsText); + } + catch (Exception ex) + { + LogDebugMessage("WindGuru: ERROR - " + ex.Message); + } + finally + { + WindGuru.Updating = false; + } + } + + internal async void UpdateAwekas(DateTime timestamp) { if (AWEKAS.Updating || station.DataStopped) @@ -2143,7 +2159,7 @@ internal async void UpdateAwekas(DateTime timestamp) // Check the status response if (respJson.status == 2) - LogMessage("AWEKAS: Data stored OK"); + LogDebugMessage("AWEKAS: Data stored OK"); else if (respJson.status == 1) { LogMessage("AWEKAS: Data PARIALLY stored"); @@ -2248,7 +2264,7 @@ internal async void UpdateWCloud(DateTime timestamp) string logUrl = url.Replace(pwstring, starredpwstring); - LogDebugMessage("WeatherCloud URL: " + logUrl); + LogDebugMessage("WeatherCloud: URL = " + logUrl); try { @@ -2276,11 +2292,11 @@ internal async void UpdateWCloud(DateTime timestamp) msg = "Unknown error"; break; } - LogDebugMessage($"WeatherCloud Response: {msg} ({response.StatusCode}): {responseBodyAsText}"); + LogDebugMessage($"WeatherCloud: Response = {msg} ({response.StatusCode}): {responseBodyAsText}"); } catch (Exception ex) { - LogDebugMessage("WeatherCloud update: " + ex.Message); + LogDebugMessage("WeatherCloud: ERROR - " + ex.Message); } finally { @@ -2314,14 +2330,14 @@ internal async void UpdateOpenWeatherMap(DateTime timestamp) HttpResponseMessage response = await client.PostAsync(url, data); var responseBodyAsText = await response.Content.ReadAsStringAsync(); var status = response.StatusCode == HttpStatusCode.NoContent ? "OK" : "Error"; // Returns a 204 reponse for OK! - LogMessage($"OpenWeatherMap: Response code = {status} - {response.StatusCode}"); + LogDebugMessage($"OpenWeatherMap: Response code = {status} - {response.StatusCode}"); if (response.StatusCode != HttpStatusCode.NoContent) LogDataMessage($"OpenWeatherMap: Response data = {responseBodyAsText}"); } } catch (Exception ex) { - LogMessage("OpenWeatherMap: Update error = " + ex.Message); + LogMessage("OpenWeatherMap: ERROR - " + ex.Message); } finally { @@ -2340,7 +2356,7 @@ internal OpenWeatherMapStation[] GetOpenWeatherMapStations() { HttpResponseMessage response = client.GetAsync(url).Result; var responseBodyAsText = response.Content.ReadAsStringAsync().Result; - LogDataMessage("WeatherCloud Response: " + response.StatusCode + ": " + responseBodyAsText); + LogDataMessage("OpenWeatherMap: Get Stations Response: " + response.StatusCode + ": " + responseBodyAsText); if (responseBodyAsText.Length > 10) { @@ -2351,7 +2367,7 @@ internal OpenWeatherMapStation[] GetOpenWeatherMapStations() } catch (Exception ex) { - LogMessage("OpenWeatherMap: Get stations - " + ex.Message); + LogMessage("OpenWeatherMap: Get Stations ERROR - " + ex.Message); } return retVal; @@ -2403,7 +2419,7 @@ internal void CreateOpenWeatherMapStation() } catch (Exception ex) { - LogMessage("OpenWeatherMap: Create station - " + ex.Message); + LogMessage("OpenWeatherMap: Create station ERROR - " + ex.Message); } } @@ -2442,8 +2458,6 @@ internal void EnableOpenWeatherMap() LogMessage("OpenWeatherMap: " + msg); } } - - OpenWeatherMapTimer.Enabled = OpenWeatherMap.Enabled && !OpenWeatherMap.SynchronisedUpdate; } } @@ -3745,6 +3759,8 @@ private void ReadIniFile() if (RainSeasonStart < 1 || RainSeasonStart > 12) RainSeasonStart = 1; ChillHourSeasonStart = ini.GetValue("Station", "ChillHourSeasonStart", 10); + if (ChillHourSeasonStart < 1 || ChillHourSeasonStart > 12) + ChillHourSeasonStart = 1; ChillHourThreshold = ini.GetValue("Station", "ChillHourThreshold", -999.0); RG11Enabled = ini.GetValue("Station", "RG11Enabled", false); @@ -3993,6 +4009,11 @@ private void ReadIniFile() GraphOptions.DailyAvgTempVisible = ini.GetValue("Graphs", "DailyAvgTempVisible", true); GraphOptions.DailyMaxTempVisible = ini.GetValue("Graphs", "DailyMaxTempVisible", true); GraphOptions.DailyMinTempVisible = ini.GetValue("Graphs", "DailyMinTempVisible", true); + GraphOptions.GrowingDegreeDaysVisible1 = ini.GetValue("Graphs", "GrowingDegreeDaysVisible1", true); + GraphOptions.GrowingDegreeDaysVisible2 = ini.GetValue("Graphs", "GrowingDegreeDaysVisible2", true); + GraphOptions.TempSumVisible0 = ini.GetValue("Graphs", "TempSumVisible0", true); + GraphOptions.TempSumVisible1 = ini.GetValue("Graphs", "TempSumVisible1", true); + GraphOptions.TempSumVisible2 = ini.GetValue("Graphs", "TempSumVisible2", true); Wund.ID = ini.GetValue("Wunderground", "ID", ""); @@ -4018,7 +4039,7 @@ private void ReadIniFile() Wund.SendAverage = ini.GetValue("Wunderground", "SendAverage", false); Wund.CatchUp = ini.GetValue("Wunderground", "CatchUp", true); - Wund.SynchronisedUpdate = (!Wund.RapidFireEnabled) && (60 % Wund.Interval == 0); + Wund.SynchronisedUpdate = !Wund.RapidFireEnabled; Windy.ApiKey = ini.GetValue("Windy", "APIkey", ""); Windy.StationIdx = ini.GetValue("Windy", "StationIdx", 0); @@ -4030,8 +4051,6 @@ private void ReadIniFile() Windy.SendSolar = ini.GetValue("Windy", "SendSolar", false); Windy.CatchUp = ini.GetValue("Windy", "CatchUp", false); - Windy.SynchronisedUpdate = (60 % Windy.Interval == 0); - AWEKAS.ID = ini.GetValue("Awekas", "User", ""); AWEKAS.PW = ini.GetValue("Awekas", "Password", ""); AWEKAS.Enabled = ini.GetValue("Awekas", "Enabled", false); @@ -4049,15 +4068,24 @@ private void ReadIniFile() AWEKAS.SynchronisedUpdate = (AWEKAS.Interval % 60 == 0); + WindGuru.ID = ini.GetValue("WindGuru", "StationUID", ""); + WindGuru.PW = ini.GetValue("WindGuru", "Password", ""); + WindGuru.Enabled = ini.GetValue("WindGuru", "Enabled", false); + WindGuru.Interval = ini.GetValue("WindGuru", "Interval", WindGuru.DefaultInterval); + if (WindGuru.Interval < 1) { WindGuru.Interval = 1; } + WindGuru.SendRain = ini.GetValue("WindGuru", "SendRain", false); + WCloud.ID = ini.GetValue("WeatherCloud", "Wid", ""); WCloud.PW = ini.GetValue("WeatherCloud", "Key", ""); WCloud.Enabled = ini.GetValue("WeatherCloud", "Enabled", false); WCloud.Interval = ini.GetValue("WeatherCloud", "Interval", WCloud.DefaultInterval); WCloud.SendUV = ini.GetValue("WeatherCloud", "SendUV", false); WCloud.SendSolar = ini.GetValue("WeatherCloud", "SendSR", false); - WCloud.SendAQI = ini.GetValue("WeatherCloud", "SendAQI", false); - - WCloud.SynchronisedUpdate = (60 % WCloud.Interval == 0); + WCloud.SendAirQuality = ini.GetValue("WeatherCloud", "SendAirQuality", false); + WCloud.SendSoilMoisture = ini.GetValue("WeatherCloud", "SendSoilMoisture", false); + WCloud.SoilMoistureSensor= ini.GetValue("WeatherCloud", "SoilMoistureSensor", 1); + WCloud.SendLeafWetness = ini.GetValue("WeatherCloud", "SendLeafWetness", false); + WCloud.LeafWetnessSensor = ini.GetValue("WeatherCloud", "LeafWetnessSensor", 1); Twitter.ID = ini.GetValue("Twitter", "User", ""); Twitter.PW = ini.GetValue("Twitter", "Password", ""); @@ -4068,8 +4096,6 @@ private void ReadIniFile() Twitter.OauthTokenSecret = ini.GetValue("Twitter", "OauthTokenSecret", "unknown"); Twitter.SendLocation = ini.GetValue("Twitter", "SendLocation", true); - Twitter.SynchronisedUpdate = (60 % Twitter.Interval == 0); - //if HTTPLogging then // MainForm.WUHTTP.IcsLogger = MainForm.HTTPlogger; @@ -4082,8 +4108,6 @@ private void ReadIniFile() PWS.SendSolar = ini.GetValue("PWSweather", "SendSR", false); PWS.CatchUp = ini.GetValue("PWSweather", "CatchUp", true); - PWS.SynchronisedUpdate = (60 % PWS.Interval == 0); - WOW.ID = ini.GetValue("WOW", "ID", ""); WOW.PW = ini.GetValue("WOW", "Password", ""); WOW.Enabled = ini.GetValue("WOW", "Enabled", false); @@ -4093,8 +4117,6 @@ private void ReadIniFile() WOW.SendSolar = ini.GetValue("WOW", "SendSR", false); WOW.CatchUp = ini.GetValue("WOW", "CatchUp", true); - WOW.SynchronisedUpdate = (60 % WOW.Interval == 0); - APRS.ID = ini.GetValue("APRS", "ID", ""); APRS.PW = ini.GetValue("APRS", "pass", "-1"); APRS.Server = ini.GetValue("APRS", "server", "cwop.aprs.net"); @@ -4105,16 +4127,12 @@ private void ReadIniFile() APRS.HumidityCutoff = ini.GetValue("APRS", "APRSHumidityCutoff", false); APRS.SendSolar = ini.GetValue("APRS", "SendSR", false); - APRS.SynchronisedUpdate = (60 % APRS.Interval == 0); - OpenWeatherMap.Enabled = ini.GetValue("OpenWeatherMap", "Enabled", false); OpenWeatherMap.CatchUp = ini.GetValue("OpenWeatherMap", "CatchUp", true); OpenWeatherMap.PW = ini.GetValue("OpenWeatherMap", "APIkey", ""); OpenWeatherMap.ID = ini.GetValue("OpenWeatherMap", "StationId", ""); OpenWeatherMap.Interval = ini.GetValue("OpenWeatherMap", "Interval", OpenWeatherMap.DefaultInterval); - OpenWeatherMap.SynchronisedUpdate = (60 % OpenWeatherMap.Interval == 0); - MQTT.Server = ini.GetValue("MQTT", "Server", ""); MQTT.Port = ini.GetValue("MQTT", "Port", 1883); MQTT.IpVersion = ini.GetValue("MQTT", "IPversion", 0); // 0 = unspecified, 4 = force IPv4, 6 = force IPv6 @@ -4496,6 +4514,19 @@ private void ReadIniFile() SmtpOptions.RequiresAuthentication = ini.GetValue("SMTP", "RequiresAuthentication", false); SmtpOptions.User = ini.GetValue("SMTP", "User", ""); SmtpOptions.Password = ini.GetValue("SMTP", "Password", ""); + + // Growing Degree Days + GrowingBase1 = ini.GetValue("GrowingDD", "BaseTemperature1", (Units.Temp == 0 ? 5.0 : 40.0)); + GrowingBase2 = ini.GetValue("GrowingDD", "BaseTemperature2", (Units.Temp == 0 ? 10.0 : 50.0)); + GrowingYearStarts = ini.GetValue("GrowingDD", "YearStarts", (Latitude >= 0 ? 1 : 7)); + GrowingCap30C = ini.GetValue("GrowingDD", "Cap30C", true); + + // Temperature Sum + TempSumYearStarts = ini.GetValue("TempSum", "TempSumYearStart", (Latitude >= 0 ? 1 : 7)); + if (TempSumYearStarts < 1 || TempSumYearStarts > 12) + TempSumYearStarts = 1; + TempSumBase1 = ini.GetValue("TempSum", "BaseTemperature1", GrowingBase1); + TempSumBase2 = ini.GetValue("TempSum", "BaseTemperature2", GrowingBase2); } internal void WriteIniFile() @@ -4613,6 +4644,9 @@ internal void WriteIniFile() ini.SetValue("Station", "RainSeasonStart", RainSeasonStart); ini.SetValue("Station", "RainDayThreshold", RainDayThreshold); + ini.SetValue("Station", "ChillHourSeasonStart", ChillHourSeasonStart); + ini.SetValue("Station", "ChillHourThreshold", ChillHourThreshold); + ini.SetValue("Station", "ErrorLogSpikeRemoval", ErrorLogSpikeRemoval); ini.SetValue("Station", "ImetOptions.ImetBaudRate", ImetOptions.ImetBaudRate); @@ -4820,7 +4854,11 @@ internal void WriteIniFile() ini.SetValue("WeatherCloud", "Interval", WCloud.Interval); ini.SetValue("WeatherCloud", "SendUV", WCloud.SendUV); ini.SetValue("WeatherCloud", "SendSR", WCloud.SendSolar); - ini.SetValue("WeatherCloud", "SendAQI", WCloud.SendAQI); + ini.SetValue("WeatherCloud", "SendAQI", WCloud.SendAirQuality); + ini.SetValue("WeatherCloud", "SendSoilMoisture", WCloud.SendSoilMoisture); + ini.SetValue("WeatherCloud", "SoilMoistureSensor", WCloud.SoilMoistureSensor); + ini.SetValue("WeatherCloud", "SendLeafWetness", WCloud.SendLeafWetness); + ini.SetValue("WeatherCloud", "LeafWetnessSensor", WCloud.LeafWetnessSensor); ini.SetValue("Twitter", "User", Twitter.ID); ini.SetValue("Twitter", "Password", Twitter.PW); @@ -4861,6 +4899,12 @@ internal void WriteIniFile() ini.SetValue("OpenWeatherMap", "StationId", OpenWeatherMap.ID); ini.SetValue("OpenWeatherMap", "Interval", OpenWeatherMap.Interval); + ini.SetValue("WindGuru", "Enabled", WindGuru.Enabled); + ini.SetValue("WindGuru", "StationUID", WindGuru.ID); + ini.SetValue("WindGuru", "Password", WindGuru.PW); + ini.SetValue("WindGuru", "Interval", WindGuru.Interval); + ini.SetValue("WindGuru", "SendRain", WindGuru.SendRain); + ini.SetValue("MQTT", "Server", MQTT.Server); ini.SetValue("MQTT", "Port", MQTT.Port); ini.SetValue("MQTT", "UseTLS", MQTT.UseTLS); @@ -5127,6 +5171,12 @@ internal void WriteIniFile() ini.SetValue("Graphs", "DailyAvgTempVisible", GraphOptions.DailyAvgTempVisible); ini.SetValue("Graphs", "DailyMaxTempVisible", GraphOptions.DailyMaxTempVisible); ini.SetValue("Graphs", "DailyMinTempVisible", GraphOptions.DailyMinTempVisible); + ini.SetValue("Graphs", "GrowingDegreeDaysVisible1", GraphOptions.GrowingDegreeDaysVisible1); + ini.SetValue("Graphs", "GrowingDegreeDaysVisible2", GraphOptions.GrowingDegreeDaysVisible2); + ini.SetValue("Graphs", "TempSumVisible0", GraphOptions.TempSumVisible0); + ini.SetValue("Graphs", "TempSumVisible1", GraphOptions.TempSumVisible1); + ini.SetValue("Graphs", "TempSumVisible2", GraphOptions.TempSumVisible2); + ini.SetValue("MySQL", "Host", MySqlHost); ini.SetValue("MySQL", "Port", MySqlPort); @@ -5177,6 +5227,17 @@ internal void WriteIniFile() ini.SetValue("SMTP", "User", SmtpOptions.User); ini.SetValue("SMTP", "Password", SmtpOptions.Password); + // Growing Degree Days + ini.SetValue("GrowingDD", "BaseTemperature1", GrowingBase1); + ini.SetValue("GrowingDD", "BaseTemperature2", GrowingBase2); + ini.SetValue("GrowingDD", "YearStarts", GrowingYearStarts); + ini.SetValue("GrowingDD", "Cap30C", GrowingCap30C); + + // Temperature Sum + ini.SetValue("TempSum", "TempSumYearStart", TempSumYearStarts); + ini.SetValue("TempSum", "BaseTemperature1", TempSumBase1); + ini.SetValue("TempSum", "BaseTemperature2", TempSumBase2); + ini.Flush(); LogMessage("Completed writing Cumulus.ini file"); @@ -5812,15 +5873,8 @@ private void ReadStringsFile() public bool Gw1000AutoUpdateIpAddress = true; public Timer WundTimer = new Timer(); - public Timer WindyTimer = new Timer(); - public Timer PWSTimer = new Timer(); - public Timer WOWTimer = new Timer(); - public Timer APRStimer = new Timer(); public Timer WebTimer = new Timer(); - public Timer TwitterTimer = new Timer(); public Timer AwekasTimer = new Timer(); - public Timer WCloudTimer = new Timer(); - public Timer OpenWeatherMapTimer = new Timer(); public Timer MQTTTimer = new Timer(); //public Timer AirLinkTimer = new Timer(); @@ -6971,15 +7025,8 @@ public void Stop() LogMessage("Stopping timers"); RealtimeTimer.Stop(); WundTimer.Stop(); - WindyTimer.Stop(); - PWSTimer.Stop(); - WOWTimer.Stop(); - APRStimer.Stop(); WebTimer.Stop(); - TwitterTimer.Stop(); AwekasTimer.Stop(); - WCloudTimer.Stop(); - OpenWeatherMapTimer.Stop(); MQTTTimer.Stop(); //AirLinkTimer.Stop(); CustomHttpSecondsTimer.Stop(); @@ -8148,18 +8195,9 @@ public void StartTimersAndSensors() WundTimer.Interval = Wund.Interval * 60 * 1000; // mins to millisecs } - WindyTimer.Interval = Windy.Interval * 60 * 1000; // mins to millisecs - - PWSTimer.Interval = PWS.Interval * 60 * 1000; // mins to millisecs - - WOWTimer.Interval = WOW.Interval * 60 * 1000; // mins to millisecs AwekasTimer.Interval = AWEKAS.Interval * 1000; - WCloudTimer.Interval = WCloud.Interval * 60 * 1000; - - OpenWeatherMapTimer.Interval = OpenWeatherMap.Interval * 60 * 1000; - MQTTTimer.Interval = MQTT.IntervalTime * 1000; // secs to millisecs @@ -8209,7 +8247,6 @@ public void StartTimersAndSensors() // No archived entries to upload WindyList = null; LogDebugMessage("Windylist count is zero"); - WindyTimer.Enabled = Windy.Enabled && !Windy.SynchronisedUpdate; } else { @@ -8227,7 +8264,6 @@ public void StartTimersAndSensors() { // No archived entries to upload PWSList = null; - PWSTimer.Enabled = PWS.Enabled && !PWS.SynchronisedUpdate; } else { @@ -8245,7 +8281,6 @@ public void StartTimersAndSensors() { // No archived entries to upload WOWList = null; - WOWTimer.Enabled = WOW.Enabled && !WOW.SynchronisedUpdate; } else { @@ -8263,7 +8298,6 @@ public void StartTimersAndSensors() { // No archived entries to upload OWMList = null; - OpenWeatherMapTimer.Enabled = OpenWeatherMap.Enabled && !OpenWeatherMap.SynchronisedUpdate; } else { @@ -8292,12 +8326,6 @@ public void StartTimersAndSensors() MySqlCatchupThread.Start(); } - TwitterTimer.Interval = Twitter.Interval * 60 * 1000; // mins to millisecs - TwitterTimer.Enabled = Twitter.Enabled && !Twitter.SynchronisedUpdate; - - APRStimer.Interval = APRS.Interval * 60 * 1000; // mins to millisecs - APRStimer.Enabled = APRS.Enabled && !APRS.SynchronisedUpdate; - WebTimer.Interval = UpdateInterval * 60 * 1000; // mins to millisecs WebTimer.Enabled = WebIntervalEnabled && !SynchronisedWebUpdate; @@ -8652,7 +8680,6 @@ private async void WindyCatchUp() LogMessage("End of Windy archive upload"); WindyList.Clear(); Windy.CatchingUp = false; - WindyTimer.Enabled = Windy.Enabled && !Windy.SynchronisedUpdate; Windy.Updating = false; } @@ -8681,7 +8708,6 @@ private async void PWSCatchUp() LogMessage("End of PWS archive upload"); PWSList.Clear(); PWS.CatchingUp = false; - PWSTimer.Enabled = PWS.Enabled && !PWS.SynchronisedUpdate; PWS.Updating = false; } @@ -8710,7 +8736,6 @@ private async void WOWCatchUp() LogMessage("End of WOW archive upload"); WOWList.Clear(); WOW.CatchingUp = false; - WOWTimer.Enabled = WOW.Enabled && !WOW.SynchronisedUpdate; WOW.Updating = false; } @@ -8752,7 +8777,6 @@ private async void OpenWeatherMapCatchUp() LogMessage("End of OpenWeatherMap archive upload"); OWMList.Clear(); OpenWeatherMap.CatchingUp = false; - OpenWeatherMapTimer.Enabled = OpenWeatherMap.Enabled && !OpenWeatherMap.SynchronisedUpdate; OpenWeatherMap.Updating = false; } @@ -9475,6 +9499,11 @@ public class GraphOptions public bool DailyMaxTempVisible { get; set; } public bool DailyAvgTempVisible { get; set; } public bool DailyMinTempVisible { get; set; } + public bool GrowingDegreeDaysVisible1 { get; set; } + public bool GrowingDegreeDaysVisible2 { get; set; } + public bool TempSumVisible0 { get; set; } + public bool TempSumVisible1 { get; set; } + public bool TempSumVisible2 { get; set; } } public class SelectaChartOptions @@ -9605,10 +9634,9 @@ public bool Triggered { // Construct the message - preamble, plus values var msg = cumulus.AlarmEmailPreamble + "\r\n" + string.Format(EmailMsg, Value, Units); - _ = cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); + cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); } - // If we get a new trigger, record the time triggered = true; TriggeredTime = DateTime.Now; @@ -9659,7 +9687,7 @@ public bool UpTriggered { // Construct the message - preamble, plus values var msg = Program.cumulus.AlarmEmailPreamble + "\r\n" + string.Format(EmailMsgUp, Value, Units); - _ = cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); + cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); } // If we get a new trigger, record the time @@ -9701,11 +9729,11 @@ public bool DownTriggered { // Construct the message - preamble, plus values var msg = Program.cumulus.AlarmEmailPreamble + "\n" + string.Format(EmailMsgDn, Value, Units); - _ = cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); + cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); } - // If we get a new trigger, record the time - downTriggered = true; + // If we get a new trigger, record the time + downTriggered = true; DownTriggeredTime = DateTime.Now; } else @@ -9748,7 +9776,7 @@ public class WebUploadService public bool SendUV; public bool SendSolar; public bool SendIndoor; - public bool SendAQI; + public bool SendAirQuality; public bool CatchUp; public bool CatchingUp; public bool Updating; @@ -9775,7 +9803,6 @@ public class WebUploadWund : WebUploadService public bool SendSoilMoisture4; public bool SendLeafWetness1; public bool SendLeafWetness2; - public bool SendAirQuality; } public class WebUploadWindy : WebUploadService @@ -9784,6 +9811,11 @@ public class WebUploadWindy : WebUploadService public int StationIdx; } + public class WebUploadWindGuru : WebUploadService + { + public bool SendRain; + } + public class WebUploadAwekas : WebUploadService { public bool RateLimited; @@ -9792,7 +9824,14 @@ public class WebUploadAwekas : WebUploadService public bool SendSoilTemp; public bool SendSoilMoisture; public bool SendLeafWetness; - public bool SendAirQuality; + } + + public class WebUploadWCloud : WebUploadService + { + public bool SendSoilMoisture; + public int SoilMoistureSensor; + public bool SendLeafWetness; + public int LeafWetnessSensor; } public class WebUploadAprs : WebUploadService diff --git a/CumulusMX/CumulusMX.csproj b/CumulusMX/CumulusMX.csproj index a1471afd..43a33282 100644 --- a/CumulusMX/CumulusMX.csproj +++ b/CumulusMX/CumulusMX.csproj @@ -109,7 +109,7 @@ ..\packages\MQTTnet.3.0.15\lib\net452\MQTTnet.dll - ..\packages\MySqlConnector.1.3.2\lib\net45\MySqlConnector.dll + ..\packages\MySqlConnector.1.3.3\lib\net45\MySqlConnector.dll ..\packages\SSH.NET.2016.1.0\lib\net40\Renci.SshNet.dll @@ -157,8 +157,8 @@ - - ..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll + + ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll @@ -224,6 +224,7 @@ + diff --git a/CumulusMX/DavisStation.cs b/CumulusMX/DavisStation.cs index f1ad70e9..7d490549 100644 --- a/CumulusMX/DavisStation.cs +++ b/CumulusMX/DavisStation.cs @@ -1973,7 +1973,7 @@ private void GetAndProcessLoop2Data(int number) { previousPressStation = pressMB; StationPressure = ConvertPressINHGToUser(loopData.AbsolutePressure); - AltimeterPressure = ConvertPressMBToUser(StationToAltimeter(PressureHPa(StationPressure), AltitudeM(cumulus.Altitude))); + AltimeterPressure = ConvertPressMBToUser(StationToAltimeter(ConvertUserPressureToHPa(StationPressure), AltitudeM(cumulus.Altitude))); } else { diff --git a/CumulusMX/DavisWllStation.cs b/CumulusMX/DavisWllStation.cs index da2ea210..54097709 100644 --- a/CumulusMX/DavisWllStation.cs +++ b/CumulusMX/DavisWllStation.cs @@ -1109,7 +1109,7 @@ private void DecodeCurrent(string currentJson) StationPressure = ConvertPressINHGToUser(data3.bar_absolute); // Or do we use calibration? The VP2 code doesn't? //StationPressure = ConvertPressINHGToUser(rec.Value("bar_absolute")) * cumulus.Calib.Press.Mult + cumulus.Calib.Press.Offset; - AltimeterPressure = ConvertPressMBToUser(StationToAltimeter(PressureHPa(StationPressure), AltitudeM(cumulus.Altitude))); + AltimeterPressure = ConvertPressMBToUser(StationToAltimeter(ConvertUserPressureToHPa(StationPressure), AltitudeM(cumulus.Altitude))); } catch (Exception ex) { @@ -2286,7 +2286,7 @@ private void DecodeHistoric(int dataType, int sensorType, string json) StationPressure = ConvertPressINHGToUser(data13baro.bar_absolute); // Or do we use calibration? The VP2 code doesn't? //StationPressure = ConvertPressINHGToUser(data.Value("bar_absolute")) * cumulus.Calib.Press.Mult + cumulus.Calib.Press.Offset; - AltimeterPressure = ConvertPressMBToUser(StationToAltimeter(PressureHPa(StationPressure), AltitudeM(cumulus.Altitude))); + AltimeterPressure = ConvertPressMBToUser(StationToAltimeter(ConvertUserPressureToHPa(StationPressure), AltitudeM(cumulus.Altitude))); } catch (Exception ex) { diff --git a/CumulusMX/EmailSender.cs b/CumulusMX/EmailSender.cs index 9e167f03..8c368af7 100644 --- a/CumulusMX/EmailSender.cs +++ b/CumulusMX/EmailSender.cs @@ -20,7 +20,7 @@ public EmailSender(Cumulus cumulus) } - public async Task SendEmail(string[] to, string from, string subject, string message, bool isHTML) + public async void SendEmail(string[] to, string from, string subject, string message, bool isHTML) { try { @@ -72,6 +72,51 @@ public async Task SendEmail(string[] to, string from, string subject, string mes } } + public void SendTestEmail(string[] to, string from, string subject, string message, bool isHTML) + { + cumulus.LogDebugMessage($"SendEmail: Sending Test email, to [{string.Join("; ", to)}], subject [{subject}], body [{message}]..."); + + var m = new MimeMessage(); + m.From.Add(new MailboxAddress("", from)); + foreach (var addr in to) + { + m.To.Add(new MailboxAddress("", addr)); + } + m.Subject = subject; + + BodyBuilder bodyBuilder = new BodyBuilder(); + if (isHTML) + { + bodyBuilder.HtmlBody = message; + } + else + { + bodyBuilder.TextBody = message; + } + + m.Body = bodyBuilder.ToMessageBody(); + + using (SmtpClient client = new SmtpClient(cumulus.SmtpOptions.Logging ? new ProtocolLogger("MXdiags/smtp.log") : null)) + { + client.Connect(cumulus.SmtpOptions.Server, cumulus.SmtpOptions.Port, MailKit.Security.SecureSocketOptions.StartTlsWhenAvailable); + //client.Connect(cumulus.SmtpOptions.Server, cumulus.SmtpOptions.Port, MailKit.Security.SecureSocketOptions.StartTlsWhenAvailable); + + // Note: since we don't have an OAuth2 token, disable + // the XOAUTH2 authentication mechanism. + client.AuthenticationMechanisms.Remove("XOAUTH2"); + + if (cumulus.SmtpOptions.RequiresAuthentication) + { + client.Authenticate(cumulus.SmtpOptions.User, cumulus.SmtpOptions.Password); + //client.Authenticate(cumulus.SmtpOptions.User, cumulus.SmtpOptions.Password); + } + + client.Send(m); + client.Disconnect(true); + } + } + + private static Regex CreateValidEmailRegex() { string validEmailPattern = @"^(?!\.)(""([^""\r\\]|\\[""\r\\])*""|" diff --git a/CumulusMX/ExtraSensorSettings.cs b/CumulusMX/ExtraSensorSettings.cs index ffb78f0a..d66ee0d4 100644 --- a/CumulusMX/ExtraSensorSettings.cs +++ b/CumulusMX/ExtraSensorSettings.cs @@ -9,17 +9,17 @@ namespace CumulusMX public class ExtraSensorSettings { private readonly Cumulus cumulus; - private readonly string extraSensorOptionsFile; - private readonly string extraSensorSchemaFile; + private readonly string optionsFile; + private readonly string schemaFile; public ExtraSensorSettings(Cumulus cumulus) { this.cumulus = cumulus; - extraSensorOptionsFile = cumulus.AppDir + "interface" + Path.DirectorySeparatorChar + "json" + Path.DirectorySeparatorChar + "ExtraSensorOptions.json"; - extraSensorSchemaFile = cumulus.AppDir + "interface" + Path.DirectorySeparatorChar + "json" + Path.DirectorySeparatorChar + "ExtraSensorSchema.json"; + optionsFile = cumulus.AppDir + "interface" + Path.DirectorySeparatorChar + "json" + Path.DirectorySeparatorChar + "ExtraSensorOptions.json"; + schemaFile = cumulus.AppDir + "interface" + Path.DirectorySeparatorChar + "json" + Path.DirectorySeparatorChar + "ExtraSensorSchema.json"; } - public string GetExtraSensorAlpacaFormData() + public string GetAlpacaFormData() { var indoor = new JsonExtraSensorAirLinkDevice() { @@ -93,9 +93,11 @@ public string GetExtraSensorAlpacaFormData() return data.ToJson(); } - public string UpdateExtraSensorConfig(IHttpContext context) + public string UpdateConfig(IHttpContext context) { var errorMsg = ""; + var json = ""; + JsonExtraSensorSettings settings; context.Response.StatusCode = 200; try @@ -103,11 +105,23 @@ public string UpdateExtraSensorConfig(IHttpContext context) var data = new StreamReader(context.Request.InputStream).ReadToEnd(); // Start at char 5 to skip the "json:" prefix - var json = WebUtility.UrlDecode(data.Substring(5)); + json = WebUtility.UrlDecode(data.Substring(5)); // de-serialize it to the settings structure - var settings = json.FromJson(); - // process the settings + settings = json.FromJson(); + } + catch (Exception ex) + { + var msg = "Error deserializing ExtraSensor Settings JSON: " + ex.Message; + cumulus.LogMessage(msg); + cumulus.LogDebugMessage("ExtraSensor Data: " + json); + context.Response.StatusCode = 500; + return msg; + } + + // process the settings + try + { cumulus.LogMessage("Updating extra sensor settings"); // General settings @@ -207,33 +221,33 @@ public string UpdateExtraSensorConfig(IHttpContext context) context.Response.StatusCode = 500; } + // Save the settings + cumulus.WriteIniFile(); } catch (Exception ex) { - cumulus.LogMessage(ex.Message); + var msg = "Error processing Extra Sensor settings: " + ex.Message; + cumulus.LogMessage(msg); + cumulus.LogDebugMessage("Extra Sensor Data: " + json); + errorMsg += msg; context.Response.StatusCode = 500; - return ex.Message; } - // Save the settings - cumulus.WriteIniFile(); - return context.Response.StatusCode == 200 ? "success" : errorMsg; } - - public string GetExtraSensorAlpacaFormOptions() + public string GetAlpacaFormOptions() { - using (StreamReader sr = new StreamReader(extraSensorOptionsFile)) + using (StreamReader sr = new StreamReader(optionsFile)) { string json = sr.ReadToEnd(); return json; } } - public string GetExtraSensorAlpacaFormSchema() + public string GetAlpacaFormSchema() { - using (StreamReader sr = new StreamReader(extraSensorSchemaFile)) + using (StreamReader sr = new StreamReader(schemaFile)) { string json = sr.ReadToEnd(); return json; @@ -255,7 +269,6 @@ public class JsonExtraSensorAirQuality public int aqi { get; set; } } - public class JsonExtraSensorAirLinkSettings { public bool isNode { get; set; } @@ -280,7 +293,6 @@ public class JsonExtraSensorBlakeLarsen public bool enabled { get; set; } } - public class JsonExtraSensorRG11 { public bool dev1enabled { get; set; } @@ -297,4 +309,4 @@ public class JsonExtraSensorRG11device public bool ignoreFirst { get; set; } public bool dtrMode { get; set; } } -} +} \ No newline at end of file diff --git a/CumulusMX/FOStation.cs b/CumulusMX/FOStation.cs index a89466de..aca19761 100644 --- a/CumulusMX/FOStation.cs +++ b/CumulusMX/FOStation.cs @@ -397,8 +397,10 @@ private void ProcessHistoryData() // update chill hours if (OutdoorTemperature < cumulus.ChillHourThreshold) + { // add 1 minute to chill hours ChillHours += (historydata.interval / 60.0); + } var raindiff = prevraintotal == -1 ? 0 : historydata.rainCounter - prevraintotal; @@ -771,13 +773,17 @@ private void GetAndProcessData() { // location has changed, skip this read to give it chance to update //cumulus.LogMessage("Location changed, skipping"); + cumulus.LogDebugMessage("Address changed"); + cumulus.LogDebugMessage("addr=" + addr.ToString("X4") + " previous=" + prevaddr.ToString("X4")); + if (synchroPhase == 2) { - cumulus.LogDebugMessage("Address changed"); - cumulus.LogDebugMessage("addr=" + addr.ToString("X4") + "previous=" + prevaddr.ToString("X4")); FOStationClockTime = DateTime.Now; StopSynchronising(); } + + prevaddr = addr; + return; } else { @@ -888,7 +894,7 @@ private void GetAndProcessData() // Get station pressure in hPa by subtracting offset and calibrating // EWpressure offset is difference between rel and abs in hPa // PressOffset is user calibration in user units. - pressure = (pressure - pressureOffset) * PressureHPa(cumulus.Calib.Press.Mult) + PressureHPa(cumulus.Calib.Press.Offset); + pressure = (pressure - pressureOffset) * ConvertUserPressureToHPa(cumulus.Calib.Press.Mult) + ConvertUserPressureToHPa(cumulus.Calib.Press.Offset); StationPressure = ConvertPressMBToUser(pressure); UpdatePressureTrendString(); @@ -952,7 +958,7 @@ private void GetAndProcessData() sign = (byte) (data[6] & 0x80); if (sign == 0x80) outtemp = -outtemp; - if (outtemp < -50 || (outtemp > 70) + if (outtemp < -50 || outtemp > 70) { // bad value cumulus.LogMessage("Ignoring bad data: outtemp = " + outtemp); @@ -1051,8 +1057,6 @@ private void GetAndProcessData() } } } - - prevaddr = addr; } private void StartSynchronising() diff --git a/CumulusMX/GW1000Station.cs b/CumulusMX/GW1000Station.cs index 1a15c78a..d39c3d49 100644 --- a/CumulusMX/GW1000Station.cs +++ b/CumulusMX/GW1000Station.cs @@ -610,7 +610,7 @@ private string GetFirmwareVersion() var response = "???"; cumulus.LogMessage("Reading firmware version"); - var data = DoCommand((byte)Commands.CMD_READ_FIRMWARE_VERSION); + var data = DoCommand(Commands.CMD_READ_FIRMWARE_VERSION); if (null != data && data.Length > 0) { response = Encoding.ASCII.GetString(data, 5, data[4]); @@ -623,7 +623,7 @@ private bool GetSensorIds() { cumulus.LogMessage("Reading sensor ids"); - var data = DoCommand((byte)Commands.CMD_READ_SENSOR_ID); + var data = DoCommand(Commands.CMD_READ_SENSOR_ID); // expected response // 0 - 0xff - header @@ -693,7 +693,7 @@ private bool GetSensorIdsNew() { cumulus.LogMessage("Reading sensor ids"); - var data = DoCommand((byte)Commands.CMD_READ_SENSOR_ID_NEW); + var data = DoCommand(Commands.CMD_READ_SENSOR_ID_NEW); // expected response // 0 - 0xff - header @@ -747,77 +747,85 @@ private bool PrintSensorInfoNew(byte[] data, int idx) // ... etc // (??) - 0x?? - checksum - var id = ConvertBigEndianUInt32(data, idx + 1); - var type = Enum.GetName(typeof(SensorIds), data[idx]).ToUpper(); - var battPos = idx + 5; - var sigPos = idx + 6; var batteryLow = false; - if (string.IsNullOrEmpty(type)) - { - type = $"unknown type = {id}"; - } - // Wh65 could be a Wh65 or a Wh24, we found out using the System Info command - if (type == "WH65") - { - type = mainSensor; - } - - switch (id) - { - case 0xFFFFFFFE: - cumulus.LogDebugMessage($" - {type} sensor = disabled"); - return false; - case 0xFFFFFFFF: - cumulus.LogDebugMessage($" - {type} sensor = registering"); - return false; - default: - //cumulus.LogDebugMessage($" - {type} sensor id = {id} signal = {data[sigPos]} battery = {data[battPos]}"); - break; - } - string batt; - switch (type) + try { - case "WH40": // WH40 does not send any battery info :( - batt = "n/a"; - break; - - case "WH65": - case "WH24": - case "WH26": - batt = TestBattery1(data[battPos], 1); // 0 or 1 - break; + var id = ConvertBigEndianUInt32(data, idx + 1); + var type = Enum.GetName(typeof(SensorIds), data[idx]).ToUpper(); + var battPos = idx + 5; + var sigPos = idx + 6; + if (string.IsNullOrEmpty(type)) + { + type = $"unknown type = {id}"; + } + // Wh65 could be a Wh65 or a Wh24, we found out using the System Info command + if (type == "WH65") + { + type = mainSensor; + } - case "WH68": - case "WH80": - case string wh34 when wh34.StartsWith("WH34"): // ch 1-8 - case string wh35 when wh35.StartsWith("WH35"): // ch 1-8 - double battV = data[battPos] * 0.02; - batt = $"{battV:f1}V ({TestBattery4S(data[battPos])})"; // volts, low = 1.2V - break; + switch (id) + { + case 0xFFFFFFFE: + cumulus.LogDebugMessage($" - {type} sensor = disabled"); + return false; + case 0xFFFFFFFF: + cumulus.LogDebugMessage($" - {type} sensor = registering"); + return false; + default: + //cumulus.LogDebugMessage($" - {type} sensor id = {id} signal = {data[sigPos]} battery = {data[battPos]}"); + break; + } - case string wh31 when wh31.StartsWith("WH31"): // ch 1-8 - case string wh51 when wh51.StartsWith("WH51"): // ch 1-8 - batt = $"{data[battPos]} ({TestBattery1(data[battPos], 1)})"; - break; + string batt; + switch (type) + { + case "WH40": // WH40 does not send any battery info :( + batt = "n/a"; + break; + + case "WH65": + case "WH24": + case "WH26": + batt = TestBattery1(data[battPos], 1); // 0 or 1 + break; + + case "WH68": + case "WH80": + case string wh34 when wh34.StartsWith("WH34"): // ch 1-8 + case string wh35 when wh35.StartsWith("WH35"): // ch 1-8 + double battV = data[battPos] * 0.02; + batt = $"{battV:f1}V ({TestBattery4S(data[battPos])})"; // volts, low = 1.2V + break; + + case string wh31 when wh31.StartsWith("WH31"): // ch 1-8 + case string wh51 when wh51.StartsWith("WH51"): // ch 1-8 + batt = $"{data[battPos]} ({TestBattery1(data[battPos], 1)})"; + break; + + case "WH25": + case "WH45": + case "WH57": + case string wh41 when wh41.StartsWith("WH41"): // ch 1-4 + case string wh55 when wh55.StartsWith("WH55"): // ch 1-4 + batt = $"{data[battPos]} ({TestBattery3(data[battPos])})"; // 0-5, low = 1 + break; + + default: + batt = "???"; + break; + } - case "WH25": - case "WH45": - case "WH57": - case string wh41 when wh41.StartsWith("WH41"): // ch 1-4 - case string wh55 when wh55.StartsWith("WH55"): // ch 1-4 - batt = $"{data[battPos]} ({TestBattery3(data[battPos])})"; // 0-5, low = 1 - break; + if (batt.Contains("Low")) + batteryLow = true; - default: - batt = "???"; - break; + cumulus.LogDebugMessage($" - {type} sensor id = {id} signal = {data[sigPos]} battery = {batt}"); + } + catch (Exception ex) + { + cumulus.LogMessage("PrintSensorInfoNew: Error - " + ex.Message); } - - if (batt.Contains("Low")) - batteryLow = true; - - cumulus.LogDebugMessage($" - {type} sensor id = {id} signal = {data[sigPos]} battery = {batt}"); return batteryLow; } @@ -834,7 +842,7 @@ private void GetLiveData() tenMinuteChanged = (minute % 10) == 0; } - byte[] data = DoCommand((byte)Commands.CMD_GW1000_LIVEDATA); + byte[] data = DoCommand(Commands.CMD_GW1000_LIVEDATA); // sample data = in-temp, in-hum, abs-baro, rel-baro, temp, hum, dir, speed, gust, light, UV uW, UV-I, rain-rate, rain-day, rain-week, rain-month, rain-year, PM2.5, PM-ch1, Soil-1, temp-2, hum-2, temp-3, hum-3, batt //byte[] data = new byte[] { 0xFF,0xFF,0x27,0x00,0x5D,0x01,0x00,0x83,0x06,0x55,0x08,0x26,0xE7,0x09,0x26,0xDC,0x02,0x00,0x5D,0x07,0x61,0x0A,0x00,0x89,0x0B,0x00,0x19,0x0C,0x00,0x25,0x15,0x00,0x00,0x00,0x00,0x16,0x00,0x00,0x17,0x00,0x0E,0x00,0x3C,0x10,0x00,0x1E,0x11,0x01,0x4A,0x12,0x00,0x00,0x02,0x68,0x13,0x00,0x00,0x14,0xDC,0x2A,0x01,0x90,0x4D,0x00,0xE3,0x2C,0x34,0x1B,0x00,0xD3,0x23,0x3C,0x1C,0x00,0x60,0x24,0x5A,0x4C,0x04,0x00,0x00,0x00,0xFF,0x5C,0xFF,0x00,0xF4,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0xBA }; @@ -1286,14 +1294,14 @@ private void GetSystemInfo() { cumulus.LogMessage("Reading GW1000 system info"); - var data = DoCommand((byte)Commands.CMD_READ_SSSS); + var data = DoCommand(Commands.CMD_READ_SSSS); // expected response // 0 - 0xff - header // 1 - 0xff - header // 2 - 0x30 - system info // 3 - 0x?? - size of response - // 4 - frequency - 0=433, 1=868MHz + // 4 - frequency - 0=433, 1=868MHz, 2=915MHz, 3=920MHz // 5 - sensor type - 0=WH24, 1=WH65 // 6-9 - UTC time // 10 - timezone index (?) @@ -1302,26 +1310,42 @@ private void GetSystemInfo() if (data.Length != 13) { - cumulus.LogMessage("Unexpected response to Sysetm Info!"); + cumulus.LogMessage("Unexpected response to System Info!"); return; } + try + { + string freq; + if (data[4] == 0) + freq = "433MHz"; + else if (data[4] == 1) + freq = "868MHz"; + else if (data[4] == 2) + freq = "915MHz"; + else if (data[4] == 3) + freq = "920MHz"; + else + freq = $"Unknown [{data[4]}]"; - var freq = data[4] == 0 ? "433MHz" : "868MHz"; - mainSensor = data[5] == 0 ? "WH24" : "WH65"; + mainSensor = data[5] == 0 ? "WH24" : "WH65"; - var unix = ConvertBigEndianUInt32(data, 6); - var date = Utils.FromUnixTime(unix); - var dst = data[11] != 0; + var unix = ConvertBigEndianUInt32(data, 6); + var date = Utils.FromUnixTime(unix); + var dst = data[11] != 0; - cumulus.LogMessage($"GW1000 Info: freqency: {freq}, main sensor: {mainSensor}, date/time: {date:F}, Automatic DST adjustment: {dst}"); + cumulus.LogMessage($"GW1000 Info: freqency: {freq}, main sensor: {mainSensor}, date/time: {date:F}, Automatic DST adjustment: {dst}"); + } + catch (Exception ex) + { + cumulus.LogMessage("Error processing System Info: " + ex.Message); + } } - private byte[] DoCommand(byte command) + private byte[] DoCommand(Commands command) { var buffer = new byte[2028]; var bytesRead = 0; - - var cmdName = Enum.GetName(typeof(Commands), command); + var cmdName = command.ToString(); var payload = new CommandPayload(command); var tmrComm = new CommTimer(); @@ -1358,7 +1382,7 @@ private byte[] DoCommand(byte command) } // Check the response is to our command and checksum is OK - if (bytesRead == 0 || buffer[2] != command || !ChecksumOk(buffer, (int)Enum.Parse(typeof(CommandRespSize), cmdName))) + if (bytesRead == 0 || buffer[2] != (byte)command || !ChecksumOk(buffer, (int)Enum.Parse(typeof(CommandRespSize), cmdName))) { if (bytesRead > 0) { @@ -1370,6 +1394,7 @@ private byte[] DoCommand(byte command) { cumulus.LogMessage($"DoCommand({cmdName}): No response received"); } + return null; } else { @@ -1400,36 +1425,43 @@ private bool DoCO2Decode(byte[] data, int index) cumulus.LogDebugMessage("WH45 CO₂: Decoding..."); //CO2Data co2Data = (CO2Data)RawDeserialize(data, index, typeof(CO2Data)); - CO2_temperature = ConvertTempCToUser(ConvertBigEndianInt16(data, idx) / 10.0); - idx += 2; - CO2_humidity = data[idx++]; - CO2_pm10 = ConvertBigEndianUInt16(data, idx) / 10.0; - idx += 2; - CO2_pm10_24h = ConvertBigEndianUInt16(data, idx) / 10.0; - idx += 2; - CO2_pm2p5 = ConvertBigEndianUInt16(data, idx) / 10.0; - idx += 2; - CO2_pm2p5_24h = ConvertBigEndianUInt16(data, idx) / 10.0; - idx += 2; - CO2 = ConvertBigEndianUInt16(data, idx); - idx += 2; - CO2_24h = ConvertBigEndianUInt16(data, idx); - idx += 2; - var batt = TestBattery3(data[idx]); - var msg = $"WH45 CO₂: temp={CO2_temperature.ToString(cumulus.TempFormat)}, hum={CO2_humidity}, pm10={CO2_pm10:F1}, pm10_24h={CO2_pm10_24h:F1}, pm2.5={CO2_pm2p5:F1}, pm2.5_24h={CO2_pm2p5_24h:F1}, CO₂={CO2}, CO₂_24h={CO2_24h}"; - if (tenMinuteChanged) + try { - if (batt == "Low") - { - batteryLow = true; - msg += $", Battery={batt}"; - } - else + CO2_temperature = ConvertTempCToUser(ConvertBigEndianInt16(data, idx) / 10.0); + idx += 2; + CO2_humidity = data[idx++]; + CO2_pm10 = ConvertBigEndianUInt16(data, idx) / 10.0; + idx += 2; + CO2_pm10_24h = ConvertBigEndianUInt16(data, idx) / 10.0; + idx += 2; + CO2_pm2p5 = ConvertBigEndianUInt16(data, idx) / 10.0; + idx += 2; + CO2_pm2p5_24h = ConvertBigEndianUInt16(data, idx) / 10.0; + idx += 2; + CO2 = ConvertBigEndianUInt16(data, idx); + idx += 2; + CO2_24h = ConvertBigEndianUInt16(data, idx); + idx += 2; + var batt = TestBattery3(data[idx]); + var msg = $"WH45 CO₂: temp={CO2_temperature.ToString(cumulus.TempFormat)}, hum={CO2_humidity}, pm10={CO2_pm10:F1}, pm10_24h={CO2_pm10_24h:F1}, pm2.5={CO2_pm2p5:F1}, pm2.5_24h={CO2_pm2p5_24h:F1}, CO₂={CO2}, CO₂_24h={CO2_24h}"; + if (tenMinuteChanged) { - msg += $", Battery={batt}"; + if (batt == "Low") + { + batteryLow = true; + msg += $", Battery={batt}"; + } + else + { + msg += $", Battery={batt}"; + } } + cumulus.LogDebugMessage(msg); + } + catch (Exception ex) + { + cumulus.LogMessage("DoCO2Decode: Error - " + ex.Message); } - cumulus.LogDebugMessage(msg); return batteryLow; } @@ -1621,11 +1653,11 @@ private struct CommandPayload private readonly byte Checksum; //public CommandPayload(byte command, byte[] data) : this() - public CommandPayload(byte command) : this() + public CommandPayload(Commands command) : this() { //ushort header; Header = 0xffff; - Command = command; + Command = (byte)command; Size = (byte)(Marshal.SizeOf(typeof(CommandPayload)) - 3); Checksum = (byte)(Command + Size); } diff --git a/CumulusMX/InternetSettings.cs b/CumulusMX/InternetSettings.cs index fafeaffe..ced49f85 100644 --- a/CumulusMX/InternetSettings.cs +++ b/CumulusMX/InternetSettings.cs @@ -12,19 +12,22 @@ namespace CumulusMX public class InternetSettings { private readonly Cumulus cumulus; - private readonly string internetOptionsFile; - private readonly string internetSchemaFile; + private readonly string optionsFile; + private readonly string schemaFile; public InternetSettings(Cumulus cumulus) { this.cumulus = cumulus; - internetOptionsFile = cumulus.AppDir + "interface" + Path.DirectorySeparatorChar + "json" + Path.DirectorySeparatorChar + "InternetOptions.json"; - internetSchemaFile = cumulus.AppDir + "interface" + Path.DirectorySeparatorChar + "json" + Path.DirectorySeparatorChar + "InternetSchema.json"; + optionsFile = cumulus.AppDir + "interface" + Path.DirectorySeparatorChar + "json" + Path.DirectorySeparatorChar + "InternetOptions.json"; + schemaFile = cumulus.AppDir + "interface" + Path.DirectorySeparatorChar + "json" + Path.DirectorySeparatorChar + "InternetSchema.json"; } - public string UpdateInternetConfig(IHttpContext context) + public string UpdateConfig(IHttpContext context) { var errorMsg = ""; + var json = ""; + JsonInternetSettingsData settings; + context.Response.StatusCode = 200; try @@ -32,11 +35,24 @@ public string UpdateInternetConfig(IHttpContext context) var data = new StreamReader(context.Request.InputStream).ReadToEnd(); // Start at char 5 to skip the "json:" prefix - var json = WebUtility.UrlDecode(data.Substring(5)); + json = WebUtility.UrlDecode(data.Substring(5)); // de-serialize it to the settings structure - var settings = json.FromJson(); - // process the settings + settings = json.FromJson(); + } + catch (Exception ex) + { + var msg = "Error deserializing Internet Settings JSON: " + ex.Message; + cumulus.LogMessage(msg); + cumulus.LogDebugMessage("Internet Data: " + json); + context.Response.StatusCode = 500; + return msg; + } + + + // process the settings + try + { cumulus.LogMessage("Updating internet settings"); // website settings @@ -151,245 +167,6 @@ public string UpdateInternetConfig(IHttpContext context) context.Response.StatusCode = 500; } - // twitter - try - { - cumulus.Twitter.Enabled = settings.twitter.enabled; - if (cumulus.Twitter.Enabled) - { - cumulus.Twitter.Interval = settings.twitter.interval; - cumulus.Twitter.PW = settings.twitter.password ?? string.Empty; - cumulus.Twitter.SendLocation = settings.twitter.sendlocation; - cumulus.Twitter.ID = settings.twitter.user ?? string.Empty; - cumulus.Twitter.SynchronisedUpdate = (60 % cumulus.Twitter.Interval == 0); - - cumulus.TwitterTimer.Interval = cumulus.Twitter.Interval * 60 * 1000; - cumulus.TwitterTimer.Enabled = cumulus.Twitter.Enabled && !cumulus.Twitter.SynchronisedUpdate && !string.IsNullOrWhiteSpace(cumulus.Twitter.ID) && !string.IsNullOrWhiteSpace(cumulus.Twitter.PW); - } - } - catch (Exception ex) - { - var msg = "Error processing twitter settings: " + ex.Message; - cumulus.LogMessage(msg); - errorMsg += msg + "\n\n"; - context.Response.StatusCode = 500; - } - - // wunderground - try - { - cumulus.Wund.Enabled = settings.wunderground.enabled; - if (cumulus.Wund.Enabled) - { - cumulus.Wund.SendIndoor = settings.wunderground.includeindoor; - cumulus.Wund.SendSolar = settings.wunderground.includesolar; - cumulus.Wund.SendUV = settings.wunderground.includeuv; - cumulus.Wund.SendAirQuality = settings.wunderground.includeaq; - cumulus.Wund.Interval = settings.wunderground.interval; - cumulus.Wund.PW = settings.wunderground.password ?? string.Empty; - cumulus.Wund.RapidFireEnabled = settings.wunderground.rapidfire; - cumulus.Wund.SendAverage = settings.wunderground.sendavgwind; - cumulus.Wund.ID = settings.wunderground.stationid ?? string.Empty; - cumulus.Wund.CatchUp = settings.wunderground.catchup; - cumulus.Wund.SynchronisedUpdate = (!cumulus.Wund.RapidFireEnabled) && (60 % cumulus.Wund.Interval == 0); - - cumulus.WundTimer.Interval = cumulus.Wund.RapidFireEnabled ? 5000 : cumulus.Wund.Interval * 60 * 1000; - cumulus.WundTimer.Enabled = cumulus.Wund.Enabled && !cumulus.Wund.SynchronisedUpdate && !string.IsNullOrWhiteSpace(cumulus.Wund.ID) && !string.IsNullOrWhiteSpace(cumulus.Wund.PW); - } - } - catch (Exception ex) - { - var msg = "Error processing wunderground settings: " + ex.Message; - cumulus.LogMessage(msg); - errorMsg += msg + "\n\n"; - context.Response.StatusCode = 500; - } - - // Windy - try - { - cumulus.Windy.Enabled = settings.windy.enabled; - if (cumulus.Windy.Enabled) - { - //cumulus.WindySendSolar = settings.windy.includesolar; - cumulus.Windy.SendUV = settings.windy.includeuv; - cumulus.Windy.Interval = settings.windy.interval; - cumulus.Windy.ApiKey = settings.windy.apikey; - cumulus.Windy.StationIdx = settings.windy.stationidx; - cumulus.Windy.CatchUp = settings.windy.catchup; - cumulus.Windy.SynchronisedUpdate = (60 % cumulus.Windy.Interval == 0); - - cumulus.WindyTimer.Interval = cumulus.Windy.Interval * 60 * 1000; - cumulus.WindyTimer.Enabled = cumulus.Windy.Enabled && !cumulus.Windy.SynchronisedUpdate && !string.IsNullOrWhiteSpace(cumulus.Windy.ApiKey); - } - } - catch (Exception ex) - { - var msg = "Error processing Windy settings: " + ex.Message; - cumulus.LogMessage(msg); - errorMsg += msg + "\n\n"; - context.Response.StatusCode = 500; - } - - // Awekas - try - { - cumulus.AWEKAS.Enabled = settings.awekas.enabled; - if (cumulus.AWEKAS.Enabled) - { - cumulus.AWEKAS.Interval = settings.awekas.interval; - cumulus.AWEKAS.Lang = settings.awekas.lang; - cumulus.AWEKAS.PW = settings.awekas.password ?? string.Empty; - cumulus.AWEKAS.ID = settings.awekas.user ?? string.Empty; - cumulus.AWEKAS.SendSolar = settings.awekas.includesolar; - cumulus.AWEKAS.SendUV = settings.awekas.includeuv; - cumulus.AWEKAS.SendSoilTemp = settings.awekas.includesoiltemp; - cumulus.AWEKAS.SendSoilMoisture = settings.awekas.includesoilmoisture; - cumulus.AWEKAS.SendLeafWetness = settings.awekas.includeleafwetness; - cumulus.AWEKAS.SendIndoor = settings.awekas.includeindoor; - cumulus.AWEKAS.SendAirQuality = settings.awekas.includeaq; - cumulus.AWEKAS.SynchronisedUpdate = (cumulus.AWEKAS.Interval % 60 == 0); - - cumulus.AwekasTimer.Interval = cumulus.AWEKAS.Interval * 1000; - cumulus.AwekasTimer.Enabled = cumulus.AWEKAS.Enabled && !cumulus.AWEKAS.SynchronisedUpdate && !string.IsNullOrWhiteSpace(cumulus.AWEKAS.ID) && !string.IsNullOrWhiteSpace(cumulus.AWEKAS.PW); - } - } - catch (Exception ex) - { - var msg = "Error processing AWEKAS settings: " + ex.Message; - cumulus.LogMessage(msg); - errorMsg += msg + "\n\n"; - context.Response.StatusCode = 500; - } - - // WeatherCloud - try - { - cumulus.WCloud.Enabled = settings.weathercloud.enabled; - if (cumulus.WCloud.Enabled) - { - cumulus.WCloud.ID = settings.weathercloud.wid ?? string.Empty; - cumulus.WCloud.PW = settings.weathercloud.key ?? string.Empty; - cumulus.WCloud.Interval = settings.weathercloud.interval; - cumulus.WCloud.SendSolar = settings.weathercloud.includesolar; - cumulus.WCloud.SendUV = settings.weathercloud.includeuv; - cumulus.WCloud.SendAQI = settings.weathercloud.includeaqi; - cumulus.WCloud.SynchronisedUpdate = (60 % cumulus.WCloud.Interval == 0); - - cumulus.WCloudTimer.Interval = cumulus.WCloud.Interval * 60 * 1000; - cumulus.WCloudTimer.Enabled = cumulus.WCloud.Enabled && !cumulus.WCloud.SynchronisedUpdate && !String.IsNullOrWhiteSpace(cumulus.WCloud.ID) && !String.IsNullOrWhiteSpace(cumulus.WCloud.PW); - } - } - catch (Exception ex) - { - var msg = "Error processing WeatherCloud settings: " + ex.Message; - cumulus.LogMessage(msg); - errorMsg += msg + "\n\n"; - context.Response.StatusCode = 500; - } - - // PWS weather - try - { - cumulus.PWS.Enabled = settings.pwsweather.enabled; - if (cumulus.PWS.Enabled) - { - cumulus.PWS.Interval = settings.pwsweather.interval; - cumulus.PWS.SendSolar = settings.pwsweather.includesolar; - cumulus.PWS.SendUV = settings.pwsweather.includeuv; - cumulus.PWS.PW = settings.pwsweather.password ?? string.Empty; - cumulus.PWS.ID = settings.pwsweather.stationid ?? string.Empty; - cumulus.PWS.CatchUp = settings.pwsweather.catchup; - cumulus.PWS.SynchronisedUpdate = (60 % cumulus.PWS.Interval == 0); - - cumulus.PWSTimer.Interval = cumulus.PWS.Interval * 60 * 1000; - cumulus.PWSTimer.Enabled = cumulus.PWS.Enabled && !cumulus.PWS.SynchronisedUpdate && !string.IsNullOrWhiteSpace(cumulus.PWS.ID) && !string.IsNullOrWhiteSpace(cumulus.PWS.PW); - } - } - catch (Exception ex) - { - var msg = "Error processing PWS weather settings: " + ex.Message; - cumulus.LogMessage(msg); - errorMsg += msg + "\n\n"; - context.Response.StatusCode = 500; - } - - // WOW - try - { - cumulus.WOW.Enabled = settings.wow.enabled; - if (cumulus.WOW.Enabled) - { - cumulus.WOW.SendSolar = settings.wow.includesolar; - cumulus.WOW.SendUV = settings.wow.includeuv; - cumulus.WOW.Interval = settings.wow.interval; - cumulus.WOW.PW = settings.wow.password ?? string.Empty; ; - cumulus.WOW.ID = settings.wow.stationid ?? string.Empty; ; - cumulus.WOW.CatchUp = settings.wow.catchup; - cumulus.WOW.SynchronisedUpdate = (60 % cumulus.WOW.Interval == 0); - - cumulus.WOWTimer.Interval = cumulus.WOW.Interval * 60 * 1000; - cumulus.WOWTimer.Enabled = cumulus.WOW.Enabled && !cumulus.WOW.SynchronisedUpdate && !string.IsNullOrWhiteSpace(cumulus.WOW.ID) && !string.IsNullOrWhiteSpace(cumulus.WOW.PW); - } - } - catch (Exception ex) - { - var msg = "Error processing WOW settings: " + ex.Message; - cumulus.LogMessage(msg); - errorMsg += msg + "\n\n"; - context.Response.StatusCode = 500; - } - - // CWOP - try - { - cumulus.APRS.Enabled = settings.cwop.enabled; - if (cumulus.APRS.Enabled) - { - cumulus.APRS.ID = settings.cwop.id ?? string.Empty; ; - cumulus.APRS.Interval = settings.cwop.interval; - cumulus.APRS.SendSolar = settings.cwop.includesolar; - cumulus.APRS.PW = settings.cwop.password ?? string.Empty; ; - cumulus.APRS.Port = settings.cwop.port; - cumulus.APRS.Server = settings.cwop.server ?? string.Empty; ; - cumulus.APRS.SynchronisedUpdate = (60 % cumulus.APRS.Interval == 0); - - cumulus.APRStimer.Interval = cumulus.APRS.Interval * 60 * 1000; - cumulus.APRStimer.Enabled = cumulus.APRS.Enabled && !cumulus.APRS.SynchronisedUpdate && !string.IsNullOrWhiteSpace(cumulus.APRS.ID) && !string.IsNullOrWhiteSpace(cumulus.APRS.PW); - } - } - catch (Exception ex) - { - var msg = "Error processing CWOP settings: " + ex.Message; - cumulus.LogMessage(msg); - errorMsg += msg + "\n\n"; - context.Response.StatusCode = 500; - } - - // OpenWeatherMap - try - { - cumulus.OpenWeatherMap.Enabled = settings.openweathermap.enabled; - if (cumulus.OpenWeatherMap.Enabled) - { - cumulus.OpenWeatherMap.CatchUp = settings.openweathermap.catchup; - cumulus.OpenWeatherMap.PW = settings.openweathermap.apikey; - cumulus.OpenWeatherMap.ID = settings.openweathermap.stationid; - cumulus.OpenWeatherMap.Interval = settings.openweathermap.interval; - cumulus.OpenWeatherMap.SynchronisedUpdate = (60 % cumulus.OpenWeatherMap.Interval == 0); - - cumulus.OpenWeatherMapTimer.Interval = cumulus.OpenWeatherMap.Interval * 60 * 1000; - cumulus.OpenWeatherMapTimer.Enabled = cumulus.OpenWeatherMap.Enabled && !string.IsNullOrWhiteSpace(cumulus.OpenWeatherMap.PW); - } - } - catch (Exception ex) - { - var msg = "Error processing OpenWeatherMap settings: " + ex.Message; - cumulus.LogMessage(msg); - errorMsg += msg + "\n\n"; - context.Response.StatusCode = 500; - } - // MQTT try { @@ -461,48 +238,6 @@ public string UpdateInternetConfig(IHttpContext context) context.Response.StatusCode = 500; } - // Custom HTTP - try - { - // custom seconds - cumulus.CustomHttpSecondsEnabled = settings.customhttp.customseconds.enabled; - if (cumulus.CustomHttpSecondsEnabled) - { - cumulus.CustomHttpSecondsString = settings.customhttp.customseconds.url ?? string.Empty; - cumulus.CustomHttpSecondsInterval = settings.customhttp.customseconds.interval; - cumulus.CustomHttpSecondsTimer.Interval = cumulus.CustomHttpSecondsInterval * 1000; - cumulus.CustomHttpSecondsTimer.Enabled = cumulus.CustomHttpSecondsEnabled; - } - // custom minutes - cumulus.CustomHttpMinutesEnabled = settings.customhttp.customminutes.enabled; - if (cumulus.CustomHttpMinutesEnabled) - { - cumulus.CustomHttpMinutesString = settings.customhttp.customminutes.url ?? string.Empty; - cumulus.CustomHttpMinutesIntervalIndex = settings.customhttp.customminutes.intervalindex; - if (cumulus.CustomHttpMinutesIntervalIndex >= 0 && cumulus.CustomHttpMinutesIntervalIndex < cumulus.FactorsOf60.Length) - { - cumulus.CustomHttpMinutesInterval = cumulus.FactorsOf60[cumulus.CustomHttpMinutesIntervalIndex]; - } - else - { - cumulus.CustomHttpMinutesInterval = 10; - } - } - // custom rollover - cumulus.CustomHttpRolloverEnabled = settings.customhttp.customrollover.enabled; - if (cumulus.CustomHttpRolloverEnabled) - { - cumulus.CustomHttpRolloverString = settings.customhttp.customrollover.url ?? string.Empty; - } - } - catch (Exception ex) - { - var msg = "Error processing Custom settings: " + ex.Message; - cumulus.LogMessage(msg); - errorMsg += msg + "\n\n"; - context.Response.StatusCode = 500; - } - // email settings try { @@ -534,9 +269,6 @@ public string UpdateInternetConfig(IHttpContext context) // Save the settings cumulus.WriteIniFile(); - // Do OpenWeatherMap setup - cumulus.EnableOpenWeatherMap(); - cumulus.SetUpHttpProxy(); //cumulus.SetFtpLogging(cumulus.FTPlogging); @@ -565,15 +297,17 @@ public string UpdateInternetConfig(IHttpContext context) } catch (Exception ex) { - cumulus.LogMessage(ex.Message); + var msg = "Error processing Internet settings: " + ex.Message; + cumulus.LogMessage(msg); + cumulus.LogDebugMessage("Internet data: " + json); + errorMsg += msg; context.Response.StatusCode = 500; - return ex.Message; } return context.Response.StatusCode == 200 ? "success" : errorMsg; } - public string GetInternetAlpacaFormData() + public string GetAlpacaFormData() { var websettingsadvanced = new JsonInternetSettingsWebsiteAdvanced() { @@ -686,8 +420,6 @@ public string GetInternetAlpacaFormData() realtime = websettingsrealtime }; - - var externalprograms = new JsonInternetSettingsExternalPrograms() { dailyprogram = cumulus.DailyProgram, @@ -698,109 +430,6 @@ public string GetInternetAlpacaFormData() realtimeprogramparams = cumulus.RealtimeParams }; - var twittersettings = new JsonInternetSettingsTwitterSettings() - { - enabled = cumulus.Twitter.Enabled, - interval = cumulus.Twitter.Interval, - password = cumulus.Twitter.PW, - sendlocation = cumulus.Twitter.SendLocation, - user = cumulus.Twitter.ID - }; - - var wusettings = new JsonInternetSettingsWunderground() - { - catchup = cumulus.Wund.CatchUp, - enabled = cumulus.Wund.Enabled, - includeindoor = cumulus.Wund.SendIndoor, - includesolar = cumulus.Wund.SendSolar, - includeuv = cumulus.Wund.SendUV, - interval = cumulus.Wund.Interval, - password = cumulus.Wund.PW, - rapidfire = cumulus.Wund.RapidFireEnabled, - sendavgwind = cumulus.Wund.SendAverage, - stationid = cumulus.Wund.ID, - includeaq = cumulus.Wund.SendAirQuality - }; - - var windysettings = new JsonInternetSettingsWindy() - { - catchup = cumulus.Windy.CatchUp, - enabled = cumulus.Windy.Enabled, - includeuv = cumulus.Windy.SendUV, - interval = cumulus.Windy.Interval, - apikey = cumulus.Windy.ApiKey, - stationidx = cumulus.Windy.StationIdx - }; - - var awekassettings = new JsonInternetSettingsAwekas() - { - enabled = cumulus.AWEKAS.Enabled, - includesolar = cumulus.AWEKAS.SendSolar, - includesoiltemp = cumulus.AWEKAS.SendSoilTemp, - includesoilmoisture = cumulus.AWEKAS.SendSoilMoisture, - includeleafwetness = cumulus.AWEKAS.SendLeafWetness, - includeindoor = cumulus.AWEKAS.SendIndoor, - includeuv = cumulus.AWEKAS.SendUV, - includeaq = cumulus.AWEKAS.SendAirQuality, - interval = cumulus.AWEKAS.Interval, - lang = cumulus.AWEKAS.Lang, - password = cumulus.AWEKAS.PW, - user = cumulus.AWEKAS.ID - }; - - var wcloudsettings = new JsonInternetSettingsWCloud() - { - enabled = cumulus.WCloud.Enabled, - interval = cumulus.WCloud.Interval, - includesolar = cumulus.WCloud.SendSolar, - includeuv = cumulus.WCloud.SendUV, - includeaqi = cumulus.WCloud.SendAQI, - key = cumulus.WCloud.PW, - wid = cumulus.WCloud.ID - }; - - var pwssettings = new JsonInternetSettingsPWSweather() - { - catchup = cumulus.PWS.CatchUp, - enabled = cumulus.PWS.Enabled, - interval = cumulus.PWS.Interval, - includesolar = cumulus.PWS.SendSolar, - includeuv = cumulus.PWS.SendUV, - password = cumulus.PWS.PW, - stationid = cumulus.PWS.ID - }; - - var wowsettings = new JsonInternetSettingsWOW() - { - catchup = cumulus.WOW.CatchUp, - enabled = cumulus.WOW.Enabled, - includesolar = cumulus.WOW.SendSolar, - includeuv = cumulus.WOW.SendUV, - interval = cumulus.WOW.Interval, - password = cumulus.WOW.PW, - stationid = cumulus.WOW.ID - }; - - var cwopsettings = new JsonInternetSettingsCwop() - { - enabled = cumulus.APRS.Enabled, - id = cumulus.APRS.ID, - interval = cumulus.APRS.Interval, - includesolar = cumulus.APRS.SendSolar, - password = cumulus.APRS.PW, - port = cumulus.APRS.Port, - server = cumulus.APRS.Server - }; - - var openweathermapsettings = new JsonInternetSettingsOpenweatherMap() - { - enabled = cumulus.OpenWeatherMap.Enabled, - catchup = cumulus.OpenWeatherMap.CatchUp, - apikey = cumulus.OpenWeatherMap.PW, - stationid = cumulus.OpenWeatherMap.ID, - interval = cumulus.OpenWeatherMap.Interval - }; - var mqttUpdate = new JsonInternetSettingsMqttDataupdate() { enabled = cumulus.MQTT.EnableDataUpdate, @@ -847,28 +476,6 @@ public string GetInternetAlpacaFormData() var proxy = new JsonInternetSettingsProxySettings() { httpproxy = httpproxy }; - var customseconds = new JsonInternetSettingsCustomHttpSeconds() - { - enabled = cumulus.CustomHttpSecondsEnabled, - interval = cumulus.CustomHttpSecondsInterval, - url = cumulus.CustomHttpSecondsString - }; - - var customminutes = new JsonInternetSettingsCustomHttpMinutes() - { - enabled = cumulus.CustomHttpMinutesEnabled, - intervalindex = cumulus.CustomHttpMinutesIntervalIndex, - url = cumulus.CustomHttpMinutesString - }; - - var customrollover = new JsonInternetSettingsCustomHttpRollover() - { - enabled = cumulus.CustomHttpRolloverEnabled, - url = cumulus.CustomHttpRolloverString - }; - - var customhttp = new JsonInternetSettingsCustomHttpSettings() { customseconds = customseconds, customminutes = customminutes, customrollover = customrollover }; - var email = new JsonEmailSettings() { enabled = cumulus.SmtpOptions.Enabled, @@ -885,37 +492,27 @@ public string GetInternetAlpacaFormData() website = websitesettings, websettings = websettings, externalprograms = externalprograms, - twitter = twittersettings, - wunderground = wusettings, - windy = windysettings, - awekas = awekassettings, - weathercloud = wcloudsettings, - pwsweather = pwssettings, - wow = wowsettings, - cwop = cwopsettings, - openweathermap = openweathermapsettings, mqtt = mqttsettings, moonimage = moonimagesettings, proxies = proxy, - customhttp = customhttp, emailsettings = email }; return data.ToJson(); } - public string GetInternetAlpacaFormOptions() + public string GetAlpacaFormOptions() { - using (StreamReader sr = new StreamReader(internetOptionsFile)) + using (StreamReader sr = new StreamReader(optionsFile)) { string json = sr.ReadToEnd(); return json; } } - public string GetInternetAlpacaFormSchema() + public string GetAlpacaFormSchema() { - using (StreamReader sr = new StreamReader(internetSchemaFile)) + using (StreamReader sr = new StreamReader(schemaFile)) { string json = sr.ReadToEnd(); return json; @@ -1022,19 +619,9 @@ public class JsonInternetSettingsData public JsonInternetSettingsWebsite website { get; set; } public JsonInternetSettingsWebSettings websettings { get; set; } public JsonInternetSettingsExternalPrograms externalprograms { get; set; } - public JsonInternetSettingsTwitterSettings twitter { get; set; } - public JsonInternetSettingsWunderground wunderground { get; set; } - public JsonInternetSettingsWindy windy { get; set; } - public JsonInternetSettingsPWSweather pwsweather { get; set; } - public JsonInternetSettingsWOW wow { get; set; } - public JsonInternetSettingsCwop cwop { get; set; } - public JsonInternetSettingsAwekas awekas { get; set; } - public JsonInternetSettingsWCloud weathercloud { get; set; } - public JsonInternetSettingsOpenweatherMap openweathermap { get; set; } public JsonInternetSettingsMqtt mqtt { get; set; } public JsonInternetSettingsMoonImage moonimage { get; set; } public JsonInternetSettingsProxySettings proxies { get; set; } - public JsonInternetSettingsCustomHttpSettings customhttp { get; set; } public JsonEmailSettings emailsettings { get; set; } } @@ -1107,7 +694,6 @@ public class JsonInternetSettingsWebSettingsRealtime public JsonInternetSettingsFileSettings[] files { get; set; } } - public class JsonInternetSettingsExternalPrograms { public string program { get; set; } @@ -1118,111 +704,6 @@ public class JsonInternetSettingsExternalPrograms public string dailyprogramparams { get; set; } } - public class JsonInternetSettingsTwitterSettings - { - public bool enabled { get; set; } - public bool sendlocation { get; set; } - public int interval { get; set; } - public string user { get; set; } - public string password { get; set; } - } - - public class JsonInternetSettingsWunderground - { - public bool enabled { get; set; } - public bool includeindoor { get; set; } - public bool includeuv { get; set; } - public bool includesolar { get; set; } - public bool includeaq { get; set; } - public bool rapidfire { get; set; } - public bool sendavgwind { get; set; } - public bool catchup { get; set; } - public string stationid { get; set; } - public string password { get; set; } - public int interval { get; set; } - } - - public class JsonInternetSettingsWindy - { - public bool enabled { get; set; } - public bool includeuv { get; set; } - //public bool includesolar { get; set; } - public bool catchup { get; set; } - public int interval { get; set; } - public string apikey { get; set; } - public int stationidx { get; set; } - } - - public class JsonInternetSettingsAwekas - { - public bool enabled { get; set; } - public bool includeuv { get; set; } - public bool includesolar { get; set; } - public bool includesoiltemp { get; set; } - public bool includesoilmoisture { get; set; } - public bool includeleafwetness { get; set; } - public bool includeindoor { get; set; } - public bool includeaq { get; set; } - public string user { get; set; } - public string password { get; set; } - public string lang { get; set; } - public int interval { get; set; } - } - - public class JsonInternetSettingsWCloud - { - public bool enabled { get; set; } - public int interval { get; set; } - public bool includeuv { get; set; } - public bool includesolar { get; set; } - public bool includeaqi { get; set; } - public string wid { get; set; } - public string key { get; set; } - } - - public class JsonInternetSettingsPWSweather - { - public bool enabled { get; set; } - public bool includeuv { get; set; } - public bool includesolar { get; set; } - public bool catchup { get; set; } - public string stationid { get; set; } - public string password { get; set; } - public int interval { get; set; } - } - - public class JsonInternetSettingsWOW - { - public bool enabled { get; set; } - public bool includeuv { get; set; } - public bool includesolar { get; set; } - public bool catchup { get; set; } - public string stationid { get; set; } - public string password { get; set; } - public int interval { get; set; } - } - - public class JsonInternetSettingsCwop - { - public bool enabled { get; set; } - public bool includesolar { get; set; } - public string id { get; set; } - public string password { get; set; } - public string server { get; set; } - public int port { get; set; } - public int interval { get; set; } - } - - public class JsonInternetSettingsOpenweatherMap - { - public bool enabled { get; set; } - public string apikey { get; set; } - public string stationid { get; set; } - public int interval { get; set; } - public bool catchup { get; set; } - } - - public class JsonInternetSettingsMqtt { public string server { get; set; } @@ -1283,31 +764,4 @@ public class JsonEmailSettings public string user { get; set; } public string password { get; set; } } - - public class JsonInternetSettingsCustomHttpSeconds - { - public string url { get; set; } - public bool enabled { get; set; } - public int interval { get; set; } - } - - public class JsonInternetSettingsCustomHttpMinutes - { - public string url { get; set; } - public bool enabled { get; set; } - public int intervalindex { get; set; } - } - - public class JsonInternetSettingsCustomHttpRollover - { - public string url { get; set; } - public bool enabled { get; set; } - } - - public class JsonInternetSettingsCustomHttpSettings - { - public JsonInternetSettingsCustomHttpSeconds customseconds { get; set; } - public JsonInternetSettingsCustomHttpMinutes customminutes { get; set; } - public JsonInternetSettingsCustomHttpRollover customrollover { get; set; } - } } diff --git a/CumulusMX/MeteoLib.cs b/CumulusMX/MeteoLib.cs index 09057943..d0845360 100644 --- a/CumulusMX/MeteoLib.cs +++ b/CumulusMX/MeteoLib.cs @@ -25,7 +25,7 @@ public static double WindChill(double tempC, double windSpeedKph) double windPow = Math.Pow(windSpeedKph, 0.16); - double wc = 13.12 + (0.6215*tempC) - (11.37*windPow) + (0.3965*tempC*windPow); + double wc = 13.12 + (0.6215 * tempC) - (11.37 * windPow) + (0.3965 * tempC * windPow); if (wc > tempC) return tempC; @@ -44,9 +44,9 @@ public static double WindChill(double tempC, double windSpeedKph) /// 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 - //double avp = ActualVapourPressure(tempC, humidity); - return tempC + (0.33*avp) - (0.7*windspeedMS) - 4.0; + double avp = (humidity / 100.0) * 6.105 * Math.Exp(17.27 * tempC / (237.7 + tempC)); // hPa + //double avp = ActualVapourPressure(tempC, humidity); + return tempC + (0.33 * avp) - (0.7 * windspeedMS) - 4.0; } /// @@ -108,24 +108,24 @@ public static double HeatIndex(double tempC, int humidity) } else { - double tempSqrd = tempF*tempF; + double tempSqrd = tempF * tempF; - double humSqrd = humidity*humidity; + double humSqrd = humidity * humidity; - var result = FtoC(0 - 42.379 + (2.04901523*tempF) + (10.14333127*humidity) - (0.22475541*tempF*humidity) - (0.00683783*tempSqrd) - (0.05481717*humSqrd) + - (0.00122874*tempSqrd*humidity) + (0.00085282*tempF*humSqrd) - (0.00000199*tempSqrd*humSqrd)); + var result = FtoC(0 - 42.379 + (2.04901523 * tempF) + (10.14333127 * humidity) - (0.22475541 * tempF * humidity) - (0.00683783 * tempSqrd) - (0.05481717 * humSqrd) + + (0.00122874 * tempSqrd * humidity) + (0.00085282 * tempF * humSqrd) - (0.00000199 * tempSqrd * humSqrd)); - // Rothfusz adjustments - if ((humidity < 13) && (tempF >= 80) && (tempF <= 112)) - { - result -= ((13 - humidity) / 4.0) * Math.Sqrt((17 - Math.Abs(tempF - 95)) / 17.0); - } - else if ((humidity > 85) && (tempF >= 80) && (tempF <= 87)) - { - result += ((humidity - 85) / 10.0) * ((87 - tempF) / 5.0); - } + // Rothfusz adjustments + if ((humidity < 13) && (tempF >= 80) && (tempF <= 112)) + { + result -= ((13 - humidity) / 4.0) * Math.Sqrt((17 - Math.Abs(tempF - 95)) / 17.0); + } + else if ((humidity > 85) && (tempF >= 80) && (tempF <= 87)) + { + result += ((humidity - 85) / 10.0) * ((87 - tempF) / 5.0); + } - return result; + return result; } } @@ -166,7 +166,7 @@ public static double CalculateWetBulbC2(double tempC, int humidity) if (humidity == 100) return tempC; - return tempC * Math.Atan(0.151977 * Math.Sqrt(humidity + 8.313659)) + Math.Atan(tempC + humidity) - Math.Atan(humidity - 1.676331) + 0.00391838 * Math.Pow(humidity, 3/2) * Math.Atan(0.023101 * humidity) - 4.686035; + return tempC * Math.Atan(0.151977 * Math.Sqrt(humidity + 8.313659)) + Math.Atan(tempC + humidity) - Math.Atan(humidity - 1.676331) + 0.00391838 * Math.Pow(humidity, 3 / 2) * Math.Atan(0.023101 * humidity) - 4.686035; } @@ -204,7 +204,7 @@ public static double CalculateWetBulbCIterative(double tempC, int humidity, doub private static double Sqr(double num) { - return num*num; + return num * num; } /// @@ -223,7 +223,7 @@ public static double DewPoint(double tempC, double humidity) return tempC; // Davis algorithm - double lnVapor = Math.Log(ActualVapourPressure2008(tempC, (int) humidity)); + double lnVapor = Math.Log(ActualVapourPressure2008(tempC, (int)humidity)); return ((243.12 * lnVapor) - 440.1) / (19.43 - lnVapor); } @@ -252,7 +252,7 @@ public static double SaturationVapourPressure1980(double tempC) /// SVP in hPa public static double SaturationVapourPressure2008(double tempC) { - return 6.112*Math.Exp((17.62*tempC)/(243.12 + tempC)); + return 6.112 * Math.Exp((17.62 * tempC) / (243.12 + tempC)); } private static double ActualVapourPressure2008(double tempC, int humidity) @@ -280,12 +280,34 @@ public static double Humidex(double tempC, int humidity) public static double CToF(double tempC) { - return ((tempC*9.0)/5.0) + 32; + return ((tempC * 9.0) / 5.0) + 32; } public static double FtoC(double tempF) { - return ((tempF - 32)*5.0)/9.0; + return ((tempF - 32) * 5.0) / 9.0; + } + + /// + /// Calculates the Growing Degree Days. + /// Uses method A - https://en.wikipedia.org/wiki/Growing_degree-day + /// + /// The days maximum temperature (Celsius) + /// The days minimum temperature (Celsius) + /// The GDD base temperature (Celsius) + /// GrowingDegreeDay (Celsius) + public static double GrowingDegreeDays(double maxC, double minC, double baseC, bool cap30C) + { + + var hiC = cap30C && maxC > 30 ? 30 : maxC; + var loC = cap30C && minC > 30 ? 30 : minC; + var avgC = (hiC + loC) / 2; + var gdd = 0.0; + if (avgC > baseC) + { + gdd = avgC - baseC; + } + return gdd; } } } diff --git a/CumulusMX/MysqlSettings.cs b/CumulusMX/MysqlSettings.cs index 81c306e8..8fdfc538 100644 --- a/CumulusMX/MysqlSettings.cs +++ b/CumulusMX/MysqlSettings.cs @@ -10,17 +10,17 @@ namespace CumulusMX public class MysqlSettings { private readonly Cumulus cumulus; - private readonly string mySqlOptionsFile; - private readonly string mySqlSchemaFile; + private readonly string optionsFile; + private readonly string schemaFile; public MysqlSettings(Cumulus cumulus) { this.cumulus = cumulus; - mySqlOptionsFile = cumulus.AppDir + "interface" + Path.DirectorySeparatorChar + "json" + Path.DirectorySeparatorChar + "MySqlOptions.json"; - mySqlSchemaFile = cumulus.AppDir + "interface" + Path.DirectorySeparatorChar + "json" + Path.DirectorySeparatorChar + "MySqlSchema.json"; + optionsFile = cumulus.AppDir + "interface" + Path.DirectorySeparatorChar + "json" + Path.DirectorySeparatorChar + "MySqlOptions.json"; + schemaFile = cumulus.AppDir + "interface" + Path.DirectorySeparatorChar + "json" + Path.DirectorySeparatorChar + "MySqlSchema.json"; } - public string GetMySqlAlpacaFormData() + public string GetAlpacaFormData() { var server = new JsonMysqlSettingsServer() { @@ -89,18 +89,18 @@ public string GetMySqlAlpacaFormData() return data.ToJson(); } - public string GetMySqAlpacaFormOptions() + public string GetAlpacaFormOptions() { - using (StreamReader sr = new StreamReader(mySqlOptionsFile)) + using (StreamReader sr = new StreamReader(optionsFile)) { string json = sr.ReadToEnd(); return json; } } - public string GetMySqAlpacaFormSchema() + public string GetAlpacaFormSchema() { - using (StreamReader sr = new StreamReader(mySqlSchemaFile)) + using (StreamReader sr = new StreamReader(schemaFile)) { string json = sr.ReadToEnd(); return json; @@ -108,18 +108,33 @@ public string GetMySqAlpacaFormSchema() } //public object UpdateMysqlConfig(HttpListenerContext context) - public object UpdateMysqlConfig(IHttpContext context) + public object UpdateConfig(IHttpContext context) { + string json = ""; + JsonMysqlSettings settings; try { var data = new StreamReader(context.Request.InputStream).ReadToEnd(); // Start at char 5 to skip the "json:" prefix - var json = WebUtility.UrlDecode(data.Substring(5)); + json = WebUtility.UrlDecode(data.Substring(5)); // de-serialize it to the settings structure - var settings = json.FromJson(); - // process the settings + settings = json.FromJson(); + } + catch (Exception ex) + { + var msg = "Error deserializing MySQL Settings JSON: " + ex.Message; + cumulus.LogMessage(msg); + cumulus.LogDebugMessage("MySQL Data: " + json); + context.Response.StatusCode = 500; + return msg; + } + + + // process the settings + try + { cumulus.LogMessage("Updating MySQL settings"); // server @@ -213,9 +228,10 @@ public object UpdateMysqlConfig(IHttpContext context) } catch (Exception ex) { - cumulus.LogMessage(ex.Message); + var msg = "Error processing settings: " + ex.Message; + cumulus.LogMessage(msg); context.Response.StatusCode = 500; - return ex.Message; + return msg; } return "success"; } diff --git a/CumulusMX/NOAASettings.cs b/CumulusMX/NOAASettings.cs index 743c15ff..bb09ba28 100644 --- a/CumulusMX/NOAASettings.cs +++ b/CumulusMX/NOAASettings.cs @@ -20,7 +20,7 @@ public NOAASettings(Cumulus cumulus) noaaSchemaFile = cumulus.AppDir + "interface"+Path.DirectorySeparatorChar+"json" + Path.DirectorySeparatorChar + "NoaaSchema.json"; } - public string GetNoaaAlpacaFormData() + public string GetAlpacaFormData() { //var InvC = new CultureInfo(""); var normalmeantemps = new JsonNOAASettingsNormalMeanTemps() @@ -110,7 +110,7 @@ public string GetNoaaAlpacaFormData() return data.ToJson(); } - public string GetNoaaAlpacaFormOptions() + public string GetAlpacaFormOptions() { using (StreamReader sr = new StreamReader(noaaOptionsFile)) { @@ -119,7 +119,7 @@ public string GetNoaaAlpacaFormOptions() } } - public string GetNoaaAlpacaFormSchema() + public string GetAlpacaFormSchema() { using (StreamReader sr = new StreamReader(noaaSchemaFile)) { @@ -129,18 +129,34 @@ public string GetNoaaAlpacaFormSchema() } //public string UpdateNoaaConfig(HttpListenerContext context) - public string UpdateNoaaConfig(IHttpContext context) + public string UpdateConfig(IHttpContext context) { + var json = ""; + JsonNOAASettingsData settings; + try { var data = new StreamReader(context.Request.InputStream).ReadToEnd(); // Start at char 5 to skip the "json:" prefix - var json = WebUtility.UrlDecode(data.Substring(5)); + json = WebUtility.UrlDecode(data.Substring(5)); // de-serialize it to the settings structure - var settings = json.FromJson(); - // process the settings + settings = json.FromJson(); + } + catch (Exception ex) + { + var msg = "Error deserializing NOAA Settings JSON: " + ex.Message; + cumulus.LogMessage(msg); + cumulus.LogDebugMessage("NOAA Data: " + json); + context.Response.StatusCode = 500; + return msg; + } + + + // process the settings + try + { cumulus.LogMessage("Updating NOAA settings"); cumulus.NOAAAutoSave = settings.autosave; @@ -206,9 +222,11 @@ public string UpdateNoaaConfig(IHttpContext context) } catch (Exception ex) { - cumulus.LogMessage(ex.Message); + var msg = "Error processing NOAA settings: " + ex.Message; + cumulus.LogMessage(msg); + cumulus.LogDebugMessage("NOAA data: " + json); context.Response.StatusCode = 500; - return ex.Message; + return msg; } return "success"; } diff --git a/CumulusMX/ProgramSettings.cs b/CumulusMX/ProgramSettings.cs index 22992899..85c53b00 100644 --- a/CumulusMX/ProgramSettings.cs +++ b/CumulusMX/ProgramSettings.cs @@ -9,18 +9,18 @@ namespace CumulusMX public class ProgramSettings { private readonly Cumulus cumulus; - private readonly string programOptionsFile; - private readonly string programSchemaFile; + private readonly string optionsFile; + private readonly string schemaFile; public ProgramSettings(Cumulus cumulus) { this.cumulus = cumulus; - programOptionsFile = cumulus.AppDir + "interface"+Path.DirectorySeparatorChar+"json" + Path.DirectorySeparatorChar + "ProgramOptions.json"; - programSchemaFile = cumulus.AppDir + "interface"+Path.DirectorySeparatorChar+"json" + Path.DirectorySeparatorChar + "ProgramSchema.json"; + optionsFile = cumulus.AppDir + "interface"+Path.DirectorySeparatorChar+"json" + Path.DirectorySeparatorChar + "ProgramOptions.json"; + schemaFile = cumulus.AppDir + "interface"+Path.DirectorySeparatorChar+"json" + Path.DirectorySeparatorChar + "ProgramSchema.json"; } - public string GetProgramAlpacaFormData() + public string GetAlpacaFormData() { // Build the settings data, convert to JSON, and return it var startup = new JsonProgramSettingsStartupOptions() @@ -50,18 +50,18 @@ public string GetProgramAlpacaFormData() return JsonSerializer.SerializeToString(settings); } - public string GetProgramAlpacaFormOptions() + public string GetAlpacaFormOptions() { - using (StreamReader sr = new StreamReader(programOptionsFile)) + using (StreamReader sr = new StreamReader(optionsFile)) { string json = sr.ReadToEnd(); return json; } } - public string GetProgramAlpacaFormSchema() + public string GetAlpacaFormSchema() { - using (StreamReader sr = new StreamReader(programSchemaFile)) + using (StreamReader sr = new StreamReader(schemaFile)) { string json = sr.ReadToEnd(); return json; @@ -69,60 +69,65 @@ public string GetProgramAlpacaFormSchema() } - public string UpdateProgramConfig(IHttpContext context) + public string UpdateConfig(IHttpContext context) { var errorMsg = ""; + var json = ""; + JsonProgramSettings settings; context.Response.StatusCode = 200; + // get the response try { - cumulus.LogMessage("Updating program settings"); + cumulus.LogMessage("Updating Program settings"); var data = new StreamReader(context.Request.InputStream).ReadToEnd(); // Start at char 5 to skip the "json:" prefix - var json = WebUtility.UrlDecode(data.Substring(5)); + json = WebUtility.UrlDecode(data.Substring(5)); // de-serialize it to the settings structure - var settings = JsonSerializer.DeserializeFromString(json); - - // process the settings - try - { - cumulus.ProgramOptions.StartupPingHost = settings.startup.startuphostping; - cumulus.ProgramOptions.StartupPingEscapeTime = settings.startup.startuppingescape; - cumulus.ProgramOptions.StartupDelaySecs = settings.startup.startupdelay; - cumulus.ProgramOptions.StartupDelayMaxUptime = settings.startup.startupdelaymaxuptime; - cumulus.ProgramOptions.DebugLogging = settings.options.debuglogging; - cumulus.ProgramOptions.DataLogging = settings.options.datalogging; - cumulus.SmtpOptions.Logging = settings.options.emaillogging; - cumulus.ProgramOptions.WarnMultiple = settings.options.stopsecondinstance; - - if (settings.options.ftplogging != cumulus.FTPlogging) - { - cumulus.FTPlogging = settings.options.ftplogging; - cumulus.SetFtpLogging(cumulus.FTPlogging); - } + settings = JsonSerializer.DeserializeFromString(json); + } + catch (Exception ex) + { + var msg = "Error deserializing Program Settings JSON: " + ex.Message; + cumulus.LogMessage(msg); + cumulus.LogDebugMessage("Program Data: " + json); + context.Response.StatusCode = 500; + return msg; + } - } - catch (Exception ex) + // process the settings + try + { + cumulus.ProgramOptions.StartupPingHost = settings.startup.startuphostping; + cumulus.ProgramOptions.StartupPingEscapeTime = settings.startup.startuppingescape; + cumulus.ProgramOptions.StartupDelaySecs = settings.startup.startupdelay; + cumulus.ProgramOptions.StartupDelayMaxUptime = settings.startup.startupdelaymaxuptime; + cumulus.ProgramOptions.DebugLogging = settings.options.debuglogging; + cumulus.ProgramOptions.DataLogging = settings.options.datalogging; + cumulus.SmtpOptions.Logging = settings.options.emaillogging; + cumulus.ProgramOptions.WarnMultiple = settings.options.stopsecondinstance; + + if (settings.options.ftplogging != cumulus.FTPlogging) { - var msg = "Error processing Program Options: " + ex.Message; - cumulus.LogMessage(msg); - errorMsg += msg + "\n\n"; - context.Response.StatusCode = 500; + cumulus.FTPlogging = settings.options.ftplogging; + cumulus.SetFtpLogging(cumulus.FTPlogging); } - // Save the settings - cumulus.WriteIniFile(); } catch (Exception ex) { - cumulus.LogMessage(ex.Message); + var msg = "Error processing Program Options: " + ex.Message; + cumulus.LogMessage(msg); + errorMsg += msg + "\n\n"; context.Response.StatusCode = 500; - return ex.Message; } + // Save the settings + cumulus.WriteIniFile(); + return context.Response.StatusCode == 200 ? "success" : errorMsg; } } diff --git a/CumulusMX/Properties/AssemblyInfo.cs b/CumulusMX/Properties/AssemblyInfo.cs index c0c0e17f..e0132477 100644 --- a/CumulusMX/Properties/AssemblyInfo.cs +++ b/CumulusMX/Properties/AssemblyInfo.cs @@ -6,7 +6,7 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Cumulus MX")] -[assembly: AssemblyDescription("Version 3.11.0 - Build 3123")] +[assembly: AssemblyDescription("Version 3.11.0 - Build 3126 - BETA")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Cumulus MX")] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("3.11.0.3123")] -[assembly: AssemblyFileVersion("3.11.0.3123")] +[assembly: AssemblyVersion("3.11.0.3126")] +[assembly: AssemblyFileVersion("3.11.0.3126")] diff --git a/CumulusMX/StationSettings.cs b/CumulusMX/StationSettings.cs index 355d3088..872b42ae 100644 --- a/CumulusMX/StationSettings.cs +++ b/CumulusMX/StationSettings.cs @@ -12,15 +12,15 @@ internal class StationSettings { private readonly Cumulus cumulus; private WeatherStation station; - private readonly string stationOptionsFile; - private readonly string stationSchemaFile; + private readonly string optionsFile; + private readonly string schemaFile; internal StationSettings(Cumulus cumulus) { this.cumulus = cumulus; - stationOptionsFile = cumulus.AppDir + "interface"+Path.DirectorySeparatorChar+"json" + Path.DirectorySeparatorChar + "StationOptions.json"; - stationSchemaFile = cumulus.AppDir + "interface"+Path.DirectorySeparatorChar+"json" + Path.DirectorySeparatorChar + "StationSchema.json"; + optionsFile = cumulus.AppDir + "interface"+Path.DirectorySeparatorChar+"json" + Path.DirectorySeparatorChar + "StationOptions.json"; + schemaFile = cumulus.AppDir + "interface"+Path.DirectorySeparatorChar+"json" + Path.DirectorySeparatorChar + "StationSchema.json"; } internal void SetStation(WeatherStation station) @@ -28,7 +28,7 @@ internal void SetStation(WeatherStation station) this.station = station; } - internal string GetStationAlpacaFormData() + internal string GetAlpacaFormData() { // Build the settings data, convert to JSON, and return it var optionsAdv = new JsonStationSettingsOptionsAdvanced() @@ -224,7 +224,27 @@ internal string GetStationAlpacaFormData() turbidity = cumulus.BrasTurbidity }; - var annualrainfall = new JsonStationSettingsAnnualRainfall() {rainseasonstart = cumulus.RainSeasonStart, ytdamount = cumulus.YTDrain, ytdyear = cumulus.YTDrainyear}; + var annualrainfall = new JsonStationSettingsAnnualRainfall() + { + rainseasonstart = cumulus.RainSeasonStart, + ytdamount = cumulus.YTDrain, + ytdyear = cumulus.YTDrainyear + }; + + var growingdd = new JsonGrowingDDSettings() + { + basetemp1 = cumulus.GrowingBase1, + basetemp2 = cumulus.GrowingBase2, + starts = cumulus.GrowingYearStarts, + cap30C = cumulus.GrowingCap30C + }; + + var tempsum = new JsonTempSumSettings() + { + basetemp1 = cumulus.TempSumBase1, + basetemp2 = cumulus.TempSumBase2, + starts = cumulus.TempSumYearStarts + }; var graphDataTemp = new JsonStationSettingsGraphDataTemperature() { @@ -239,6 +259,9 @@ internal string GetStationAlpacaFormData() graphDailyAvgTempVis = cumulus.GraphOptions.DailyAvgTempVisible, graphDailyMaxTempVis = cumulus.GraphOptions.DailyMaxTempVisible, graphDailyMinTempVis = cumulus.GraphOptions.DailyMinTempVisible, + graphTempSumVis0 = cumulus.GraphOptions.TempSumVisible0, + graphTempSumVis1 = cumulus.GraphOptions.TempSumVisible1, + graphTempSumVis2 = cumulus.GraphOptions.TempSumVisible2 }; var graphDataHum = new JsonStationSettingsGraphDataHumidity() @@ -254,11 +277,18 @@ internal string GetStationAlpacaFormData() graphSunshineVis = cumulus.GraphOptions.SunshineVisible }; + var graphDataDegreeDays = new JsonStationSettingsGraphDataDegreeDays() + { + graphGrowingDegreeDaysVis1 = cumulus.GraphOptions.GrowingDegreeDaysVisible1, + graphGrowingDegreeDaysVis2 = cumulus.GraphOptions.GrowingDegreeDaysVisible2 + }; + var graphDataVis = new JsonStationSettingsGraphVisibility() { temperature = graphDataTemp, humidity = graphDataHum, - solar = graphDataSolar + solar = graphDataSolar, + degreedays = graphDataDegreeDays }; var graphs = new JsonStationSettingsGraphs() @@ -387,6 +417,8 @@ internal string GetStationAlpacaFormData() Forecast = forecast, Solar = solar, AnnualRainfall = annualrainfall, + GrowingDD = growingdd, + TempSum = tempsum, Graphs = graphs, DisplayOptions = displayOptions }; @@ -395,18 +427,18 @@ internal string GetStationAlpacaFormData() return JsonSerializer.SerializeToString(data); } - internal string GetStationAlpacaFormOptions() + internal string GetAlpacaFormOptions() { - using (StreamReader sr = new StreamReader(stationOptionsFile)) + using (StreamReader sr = new StreamReader(optionsFile)) { string json = sr.ReadToEnd(); return json; } } - internal string GetStationAlpacaFormSchema() + internal string GetAlpacaFormSchema() { - using (StreamReader sr = new StreamReader(stationSchemaFile)) + using (StreamReader sr = new StreamReader(schemaFile)) { string json = sr.ReadToEnd(); return json; @@ -460,10 +492,13 @@ private void LatToDMS(double latitude, out int d, out int m, out int s, out stri d = secs / 60; } - internal string UpdateStationConfig(IHttpContext context) + internal string UpdateConfig(IHttpContext context) { var errorMsg = ""; + var json = ""; context.Response.StatusCode = 200; + JsonStationSettingsData settings; + // get the response try { @@ -471,14 +506,24 @@ internal string UpdateStationConfig(IHttpContext context) var data = new StreamReader(context.Request.InputStream).ReadToEnd(); - // Start at char 5 to skip the "json:" prefix - var json = WebUtility.UrlDecode(data.Substring(5)); + // Start at char 5 to skip the "json=" prefix + json = WebUtility.UrlDecode(data.Substring(5)); // de-serialize it to the settings structure - var settings = JsonSerializer.DeserializeFromString(json); - - // process the settings + settings = JsonSerializer.DeserializeFromString(json); + } + catch (Exception ex) + { + var msg = "Error deserializing Station Settings JSON: " + ex.Message; + cumulus.LogMessage(msg); + cumulus.LogDebugMessage("Station Data: " + json); + context.Response.StatusCode = 500; + return msg; + } + // process the settings + try + { // Graph Config try { @@ -500,6 +545,11 @@ internal string UpdateStationConfig(IHttpContext context) cumulus.GraphOptions.DailyAvgTempVisible = settings.Graphs.datavisibility.temperature.graphDailyAvgTempVis; cumulus.GraphOptions.DailyMaxTempVisible = settings.Graphs.datavisibility.temperature.graphDailyMaxTempVis; cumulus.GraphOptions.DailyMinTempVisible = settings.Graphs.datavisibility.temperature.graphDailyMinTempVis; + cumulus.GraphOptions.TempSumVisible0 = settings.Graphs.datavisibility.temperature.graphTempSumVis0; + cumulus.GraphOptions.TempSumVisible1 = settings.Graphs.datavisibility.temperature.graphTempSumVis1; + cumulus.GraphOptions.TempSumVisible2 = settings.Graphs.datavisibility.temperature.graphTempSumVis2; + cumulus.GraphOptions.GrowingDegreeDaysVisible1 = settings.Graphs.datavisibility.degreedays.graphGrowingDegreeDaysVis1; + cumulus.GraphOptions.GrowingDegreeDaysVisible2 = settings.Graphs.datavisibility.degreedays.graphGrowingDegreeDaysVis2; } catch (Exception ex) { @@ -524,6 +574,37 @@ internal string UpdateStationConfig(IHttpContext context) context.Response.StatusCode = 500; } + // Growing Degree Day + try + { + cumulus.GrowingBase1 = settings.GrowingDD.basetemp1; + cumulus.GrowingBase2 = settings.GrowingDD.basetemp2; + cumulus.GrowingYearStarts = settings.GrowingDD.starts; + cumulus.GrowingCap30C = settings.GrowingDD.cap30C; + } + catch (Exception ex) + { + var msg = "Error processing Growing Degree Day settings: " + ex.Message; + cumulus.LogMessage(msg); + errorMsg += msg + "\n\n"; + context.Response.StatusCode = 500; + } + + // Temp Sum + try + { + cumulus.TempSumBase1 = settings.TempSum.basetemp1; + cumulus.TempSumBase2 = settings.TempSum.basetemp2; + cumulus.TempSumYearStarts = settings.TempSum.starts; + } + catch (Exception ex) + { + var msg = "Error processing Temperature Sum settings: " + ex.Message; + cumulus.LogMessage(msg); + errorMsg += msg + "\n\n"; + context.Response.StatusCode = 500; + } + // Solar try { @@ -959,9 +1040,11 @@ internal string UpdateStationConfig(IHttpContext context) } catch (Exception ex) { - cumulus.LogMessage(ex.Message); + var msg = "Error processing Station settings: " + ex.Message; + cumulus.LogMessage(msg); + cumulus.LogDebugMessage("Station Data: " + json); + errorMsg += msg; context.Response.StatusCode = 500; - return ex.Message; } return context.Response.StatusCode == 200 ? "success" : errorMsg; @@ -1078,7 +1161,6 @@ internal string SetSelectaChartOptions(IHttpContext context) return context.Response.StatusCode == 200 ? "success" : errorMsg; } - internal string GetWSport() { return "{\"wsport\":\"" + cumulus.wsPort + "\"}"; @@ -1105,6 +1187,8 @@ internal class JsonStationSettingsData public JsonStationSettingsForecast Forecast { get; set; } public JsonStationSettingsSolar Solar { get; set; } public JsonStationSettingsAnnualRainfall AnnualRainfall { get; set; } + public JsonGrowingDDSettings GrowingDD { get; set; } + public JsonTempSumSettings TempSum { get; set; } public JsonStationSettingsGraphs Graphs { get; set; } public JsonDisplayOptions DisplayOptions { get; set; } } @@ -1246,7 +1330,6 @@ internal class JsonStationSettingsWMR928 public string comportname { get; set; } } - internal class JsonStationSettingsImet { public string comportname { get; set; } @@ -1426,6 +1509,7 @@ public class JsonStationSettingsGraphVisibility public JsonStationSettingsGraphDataTemperature temperature { get; set; } public JsonStationSettingsGraphDataHumidity humidity { get; set; } public JsonStationSettingsGraphDataSolar solar { get; set; } + public JsonStationSettingsGraphDataDegreeDays degreedays { get; set; } } public class JsonStationSettingsGraphDataTemperature @@ -1441,6 +1525,9 @@ public class JsonStationSettingsGraphDataTemperature public bool graphDailyAvgTempVis { get; set; } public bool graphDailyMaxTempVis { get; set; } public bool graphDailyMinTempVis { get; set; } + public bool graphTempSumVis0 { get; set; } + public bool graphTempSumVis1 { get; set; } + public bool graphTempSumVis2 { get; set; } } public class JsonStationSettingsGraphDataHumidity @@ -1456,6 +1543,12 @@ public class JsonStationSettingsGraphDataSolar public bool graphSunshineVis { get; set; } } + public class JsonStationSettingsGraphDataDegreeDays + { + public bool graphGrowingDegreeDaysVis1 { get; set; } + public bool graphGrowingDegreeDaysVis2 { get; set; } + } + public class JsonSelectaChartSettings { public string[] series { get; set; } @@ -1469,4 +1562,20 @@ public class JsonDisplayOptions public bool displaysolar { get; set; } public bool displayuv { get; set; } } + + public class JsonGrowingDDSettings + { + public double basetemp1 { get; set; } + public double basetemp2 { get; set; } + public int starts { get; set; } + public bool cap30C { get; set; } + } + + public class JsonTempSumSettings + { + public int starts { get; set; } + public double basetemp1 { get; set; } + public double basetemp2 { get; set; } + } + } diff --git a/CumulusMX/ThirdPartySettings.cs b/CumulusMX/ThirdPartySettings.cs new file mode 100644 index 00000000..893b96e4 --- /dev/null +++ b/CumulusMX/ThirdPartySettings.cs @@ -0,0 +1,686 @@ +using ServiceStack; +using System; +using System.IO; +using System.Net; +using Unosquare.Labs.EmbedIO; + +namespace CumulusMX +{ + public class ThirdPartySettings + { + private readonly Cumulus cumulus; + private readonly string optionsFile; + private readonly string schemaFile; + + public ThirdPartySettings(Cumulus cumulus) + { + this.cumulus = cumulus; + optionsFile = cumulus.AppDir + "interface" + Path.DirectorySeparatorChar + "json" + Path.DirectorySeparatorChar + "ThirdPartyOptions.json"; + schemaFile = cumulus.AppDir + "interface" + Path.DirectorySeparatorChar + "json" + Path.DirectorySeparatorChar + "ThirdPartySchema.json"; + } + + public string UpdateConfig(IHttpContext context) + { + var errorMsg = ""; + var json = ""; + JsonThirdPartySettings settings; + context.Response.StatusCode = 200; + + try + { + var data = new StreamReader(context.Request.InputStream).ReadToEnd(); + + // Start at char 5 to skip the "json:" prefix + json = WebUtility.UrlDecode(data.Substring(5)); + + // de-serialize it to the settings structure + settings = json.FromJson(); + } + catch (Exception ex) + { + var msg = "Error deserializing 3rdParty Settings JSON: " + ex.Message; + cumulus.LogMessage(msg); + cumulus.LogDebugMessage("3rdParty Data: " + json); + context.Response.StatusCode = 500; + return msg; + } + + + // process the settings + try + { + cumulus.LogMessage("Updating third party settings"); + + // twitter + try + { + cumulus.Twitter.Enabled = settings.twitter.enabled; + if (cumulus.Twitter.Enabled) + { + cumulus.Twitter.Interval = settings.twitter.interval; + cumulus.Twitter.PW = settings.twitter.password ?? string.Empty; + cumulus.Twitter.SendLocation = settings.twitter.sendlocation; + cumulus.Twitter.ID = settings.twitter.user ?? string.Empty; + } + } + catch (Exception ex) + { + var msg = "Error processing twitter settings: " + ex.Message; + cumulus.LogMessage(msg); + errorMsg += msg + "\n\n"; + context.Response.StatusCode = 500; + } + + // wunderground + try + { + cumulus.Wund.Enabled = settings.wunderground.enabled; + if (cumulus.Wund.Enabled) + { + cumulus.Wund.SendIndoor = settings.wunderground.includeindoor; + cumulus.Wund.SendSolar = settings.wunderground.includesolar; + cumulus.Wund.SendUV = settings.wunderground.includeuv; + cumulus.Wund.SendAirQuality = settings.wunderground.includeaq; + cumulus.Wund.Interval = settings.wunderground.interval; + cumulus.Wund.PW = settings.wunderground.password ?? string.Empty; + cumulus.Wund.RapidFireEnabled = settings.wunderground.rapidfire; + cumulus.Wund.SendAverage = settings.wunderground.sendavgwind; + cumulus.Wund.ID = settings.wunderground.stationid ?? string.Empty; + cumulus.Wund.CatchUp = settings.wunderground.catchup; + cumulus.Wund.SynchronisedUpdate = (!cumulus.Wund.RapidFireEnabled) && (60 % cumulus.Wund.Interval == 0); + + cumulus.WundTimer.Interval = cumulus.Wund.RapidFireEnabled ? 5000 : cumulus.Wund.Interval * 60 * 1000; + cumulus.WundTimer.Enabled = cumulus.Wund.Enabled && !cumulus.Wund.SynchronisedUpdate && !string.IsNullOrWhiteSpace(cumulus.Wund.ID) && !string.IsNullOrWhiteSpace(cumulus.Wund.PW); + } + } + catch (Exception ex) + { + var msg = "Error processing wunderground settings: " + ex.Message; + cumulus.LogMessage(msg); + errorMsg += msg + "\n\n"; + context.Response.StatusCode = 500; + } + + // Windy + try + { + cumulus.Windy.Enabled = settings.windy.enabled; + if (cumulus.Windy.Enabled) + { + //cumulus.WindySendSolar = settings.windy.includesolar; + cumulus.Windy.SendUV = settings.windy.includeuv; + cumulus.Windy.Interval = settings.windy.interval; + cumulus.Windy.ApiKey = settings.windy.apikey; + cumulus.Windy.StationIdx = settings.windy.stationidx; + cumulus.Windy.CatchUp = settings.windy.catchup; + } + } + catch (Exception ex) + { + var msg = "Error processing Windy settings: " + ex.Message; + cumulus.LogMessage(msg); + errorMsg += msg + "\n\n"; + context.Response.StatusCode = 500; + } + + // Awekas + try + { + cumulus.AWEKAS.Enabled = settings.awekas.enabled; + if (cumulus.AWEKAS.Enabled) + { + cumulus.AWEKAS.Interval = settings.awekas.interval; + cumulus.AWEKAS.Lang = settings.awekas.lang; + cumulus.AWEKAS.PW = settings.awekas.password ?? string.Empty; + cumulus.AWEKAS.ID = settings.awekas.user ?? string.Empty; + cumulus.AWEKAS.SendSolar = settings.awekas.includesolar; + cumulus.AWEKAS.SendUV = settings.awekas.includeuv; + cumulus.AWEKAS.SendSoilTemp = settings.awekas.includesoiltemp; + cumulus.AWEKAS.SendSoilMoisture = settings.awekas.includesoilmoisture; + cumulus.AWEKAS.SendLeafWetness = settings.awekas.includeleafwetness; + cumulus.AWEKAS.SendIndoor = settings.awekas.includeindoor; + cumulus.AWEKAS.SendAirQuality = settings.awekas.includeaq; + cumulus.AWEKAS.SynchronisedUpdate = (cumulus.AWEKAS.Interval % 60 == 0); + + cumulus.AwekasTimer.Interval = cumulus.AWEKAS.Interval * 1000; + cumulus.AwekasTimer.Enabled = cumulus.AWEKAS.Enabled && !cumulus.AWEKAS.SynchronisedUpdate && !string.IsNullOrWhiteSpace(cumulus.AWEKAS.ID) && !string.IsNullOrWhiteSpace(cumulus.AWEKAS.PW); + } + } + catch (Exception ex) + { + var msg = "Error processing AWEKAS settings: " + ex.Message; + cumulus.LogMessage(msg); + errorMsg += msg + "\n\n"; + context.Response.StatusCode = 500; + } + + // WeatherCloud + try + { + cumulus.WCloud.Enabled = settings.weathercloud.enabled; + if (cumulus.WCloud.Enabled) + { + cumulus.WCloud.ID = settings.weathercloud.wid ?? string.Empty; + cumulus.WCloud.PW = settings.weathercloud.key ?? string.Empty; + cumulus.WCloud.Interval = settings.weathercloud.interval; + cumulus.WCloud.SendSolar = settings.weathercloud.includesolar; + cumulus.WCloud.SendUV = settings.weathercloud.includeuv; + cumulus.WCloud.SendAirQuality = settings.weathercloud.includeaqi; + cumulus.WCloud.SendSoilMoisture = settings.weathercloud.includesoilmoist; + cumulus.WCloud.SoilMoistureSensor = settings.weathercloud.moistsensor; + cumulus.WCloud.SendLeafWetness = settings.weathercloud.includeleafwet; + cumulus.WCloud.LeafWetnessSensor = settings.weathercloud.leafwetsensor; + } + } + catch (Exception ex) + { + var msg = "Error processing WeatherCloud settings: " + ex.Message; + cumulus.LogMessage(msg); + errorMsg += msg + "\n\n"; + context.Response.StatusCode = 500; + } + + // PWS weather + try + { + cumulus.PWS.Enabled = settings.pwsweather.enabled; + if (cumulus.PWS.Enabled) + { + cumulus.PWS.Interval = settings.pwsweather.interval; + cumulus.PWS.SendSolar = settings.pwsweather.includesolar; + cumulus.PWS.SendUV = settings.pwsweather.includeuv; + cumulus.PWS.PW = settings.pwsweather.password ?? string.Empty; + cumulus.PWS.ID = settings.pwsweather.stationid ?? string.Empty; + cumulus.PWS.CatchUp = settings.pwsweather.catchup; + } + } + catch (Exception ex) + { + var msg = "Error processing PWS weather settings: " + ex.Message; + cumulus.LogMessage(msg); + errorMsg += msg + "\n\n"; + context.Response.StatusCode = 500; + } + + // WOW + try + { + cumulus.WOW.Enabled = settings.wow.enabled; + if (cumulus.WOW.Enabled) + { + cumulus.WOW.SendSolar = settings.wow.includesolar; + cumulus.WOW.SendUV = settings.wow.includeuv; + cumulus.WOW.Interval = settings.wow.interval; + cumulus.WOW.PW = settings.wow.password ?? string.Empty; ; + cumulus.WOW.ID = settings.wow.stationid ?? string.Empty; ; + cumulus.WOW.CatchUp = settings.wow.catchup; + } + } + catch (Exception ex) + { + var msg = "Error processing WOW settings: " + ex.Message; + cumulus.LogMessage(msg); + errorMsg += msg + "\n\n"; + context.Response.StatusCode = 500; + } + + // CWOP + try + { + cumulus.APRS.Enabled = settings.cwop.enabled; + if (cumulus.APRS.Enabled) + { + cumulus.APRS.ID = settings.cwop.id ?? string.Empty; ; + cumulus.APRS.Interval = settings.cwop.interval; + cumulus.APRS.SendSolar = settings.cwop.includesolar; + cumulus.APRS.PW = settings.cwop.password ?? string.Empty; ; + cumulus.APRS.Port = settings.cwop.port; + cumulus.APRS.Server = settings.cwop.server ?? string.Empty; ; + } + } + catch (Exception ex) + { + var msg = "Error processing CWOP settings: " + ex.Message; + cumulus.LogMessage(msg); + errorMsg += msg + "\n\n"; + context.Response.StatusCode = 500; + } + + // OpenWeatherMap + try + { + cumulus.OpenWeatherMap.Enabled = settings.openweathermap.enabled; + if (cumulus.OpenWeatherMap.Enabled) + { + cumulus.OpenWeatherMap.CatchUp = settings.openweathermap.catchup; + cumulus.OpenWeatherMap.PW = settings.openweathermap.apikey; + cumulus.OpenWeatherMap.ID = settings.openweathermap.stationid; + cumulus.OpenWeatherMap.Interval = settings.openweathermap.interval; + } + } + catch (Exception ex) + { + var msg = "Error processing OpenWeatherMap settings: " + ex.Message; + cumulus.LogMessage(msg); + errorMsg += msg + "\n\n"; + context.Response.StatusCode = 500; + } + + // Wind Guru + try + { + cumulus.WindGuru.Enabled = settings.windguru.enabled; + if (cumulus.WindGuru.Enabled) + { + cumulus.WindGuru.ID = settings.windguru.uid; + cumulus.WindGuru.PW = settings.windguru.password; + cumulus.WindGuru.SendRain = settings.windguru.includerain; + cumulus.WindGuru.Interval = settings.windguru.interval; + } + } + catch (Exception ex) + { + var msg = "Error processing WindGuru settings: " + ex.Message; + cumulus.LogMessage(msg); + errorMsg += msg + "\n\n"; + context.Response.StatusCode = 500; + } + + // Custom HTTP + try + { + // custom seconds + cumulus.CustomHttpSecondsEnabled = settings.customhttp.customseconds.enabled; + if (cumulus.CustomHttpSecondsEnabled) + { + cumulus.CustomHttpSecondsString = settings.customhttp.customseconds.url ?? string.Empty; + cumulus.CustomHttpSecondsInterval = settings.customhttp.customseconds.interval; + cumulus.CustomHttpSecondsTimer.Interval = cumulus.CustomHttpSecondsInterval * 1000; + cumulus.CustomHttpSecondsTimer.Enabled = cumulus.CustomHttpSecondsEnabled; + } + // custom minutes + cumulus.CustomHttpMinutesEnabled = settings.customhttp.customminutes.enabled; + if (cumulus.CustomHttpMinutesEnabled) + { + cumulus.CustomHttpMinutesString = settings.customhttp.customminutes.url ?? string.Empty; + cumulus.CustomHttpMinutesIntervalIndex = settings.customhttp.customminutes.intervalindex; + if (cumulus.CustomHttpMinutesIntervalIndex >= 0 && cumulus.CustomHttpMinutesIntervalIndex < cumulus.FactorsOf60.Length) + { + cumulus.CustomHttpMinutesInterval = cumulus.FactorsOf60[cumulus.CustomHttpMinutesIntervalIndex]; + } + else + { + cumulus.CustomHttpMinutesInterval = 10; + } + } + // custom rollover + cumulus.CustomHttpRolloverEnabled = settings.customhttp.customrollover.enabled; + if (cumulus.CustomHttpRolloverEnabled) + { + cumulus.CustomHttpRolloverString = settings.customhttp.customrollover.url ?? string.Empty; + } + } + catch (Exception ex) + { + var msg = "Error processing Custom settings: " + ex.Message; + cumulus.LogMessage(msg); + errorMsg += msg + "\n\n"; + context.Response.StatusCode = 500; + } + + // Save the settings + cumulus.WriteIniFile(); + + // Do OpenWeatherMap setup + cumulus.EnableOpenWeatherMap(); + } + catch (Exception ex) + { + var msg = "Error processing Third Party settings: " + ex.Message; + cumulus.LogMessage(msg); + cumulus.LogDebugMessage("Third Party data: " + json); + errorMsg += msg; + context.Response.StatusCode = 500; + } + + return context.Response.StatusCode == 200 ? "success" : errorMsg; + } + + public string GetAlpacaFormData() + { + var twittersettings = new JsonThirdPartySettingsTwitterSettings() + { + enabled = cumulus.Twitter.Enabled, + interval = cumulus.Twitter.Interval, + password = cumulus.Twitter.PW, + sendlocation = cumulus.Twitter.SendLocation, + user = cumulus.Twitter.ID + }; + + var wusettings = new JsonThirdPartySettingsWunderground() + { + catchup = cumulus.Wund.CatchUp, + enabled = cumulus.Wund.Enabled, + includeindoor = cumulus.Wund.SendIndoor, + includesolar = cumulus.Wund.SendSolar, + includeuv = cumulus.Wund.SendUV, + interval = cumulus.Wund.Interval, + password = cumulus.Wund.PW, + rapidfire = cumulus.Wund.RapidFireEnabled, + sendavgwind = cumulus.Wund.SendAverage, + stationid = cumulus.Wund.ID, + includeaq = cumulus.Wund.SendAirQuality + }; + + var windysettings = new JsonThirdPartySettingsWindy() + { + catchup = cumulus.Windy.CatchUp, + enabled = cumulus.Windy.Enabled, + includeuv = cumulus.Windy.SendUV, + interval = cumulus.Windy.Interval, + apikey = cumulus.Windy.ApiKey, + stationidx = cumulus.Windy.StationIdx + }; + + var awekassettings = new JsonThirdPartySettingsAwekas() + { + enabled = cumulus.AWEKAS.Enabled, + includesolar = cumulus.AWEKAS.SendSolar, + includesoiltemp = cumulus.AWEKAS.SendSoilTemp, + includesoilmoisture = cumulus.AWEKAS.SendSoilMoisture, + includeleafwetness = cumulus.AWEKAS.SendLeafWetness, + includeindoor = cumulus.AWEKAS.SendIndoor, + includeuv = cumulus.AWEKAS.SendUV, + includeaq = cumulus.AWEKAS.SendAirQuality, + interval = cumulus.AWEKAS.Interval, + lang = cumulus.AWEKAS.Lang, + password = cumulus.AWEKAS.PW, + user = cumulus.AWEKAS.ID + }; + + var wcloudsettings = new JsonThirdPartySettingsWCloud() + { + enabled = cumulus.WCloud.Enabled, + interval = cumulus.WCloud.Interval, + includesolar = cumulus.WCloud.SendSolar, + includeuv = cumulus.WCloud.SendUV, + includeaqi = cumulus.WCloud.SendAirQuality, + includesoilmoist = cumulus.WCloud.SendSoilMoisture, + moistsensor = cumulus.WCloud.SoilMoistureSensor, + includeleafwet = cumulus.WCloud.SendLeafWetness, + leafwetsensor = cumulus.WCloud.LeafWetnessSensor, + key = cumulus.WCloud.PW, + wid = cumulus.WCloud.ID + }; + + var pwssettings = new JsonThirdPartySettingsPWSweather() + { + catchup = cumulus.PWS.CatchUp, + enabled = cumulus.PWS.Enabled, + interval = cumulus.PWS.Interval, + includesolar = cumulus.PWS.SendSolar, + includeuv = cumulus.PWS.SendUV, + password = cumulus.PWS.PW, + stationid = cumulus.PWS.ID + }; + + var wowsettings = new JsonThirdPartySettingsWOW() + { + catchup = cumulus.WOW.CatchUp, + enabled = cumulus.WOW.Enabled, + includesolar = cumulus.WOW.SendSolar, + includeuv = cumulus.WOW.SendUV, + interval = cumulus.WOW.Interval, + password = cumulus.WOW.PW, + stationid = cumulus.WOW.ID + }; + + var cwopsettings = new JsonThirdPartySettingsCwop() + { + enabled = cumulus.APRS.Enabled, + id = cumulus.APRS.ID, + interval = cumulus.APRS.Interval, + includesolar = cumulus.APRS.SendSolar, + password = cumulus.APRS.PW, + port = cumulus.APRS.Port, + server = cumulus.APRS.Server + }; + + var openweathermapsettings = new JsonThirdPartySettingsOpenweatherMap() + { + enabled = cumulus.OpenWeatherMap.Enabled, + catchup = cumulus.OpenWeatherMap.CatchUp, + apikey = cumulus.OpenWeatherMap.PW, + stationid = cumulus.OpenWeatherMap.ID, + interval = cumulus.OpenWeatherMap.Interval + }; + + var windgurusettings = new JsonThirdPartySettingsWindGuru() + { + enabled = cumulus.WindGuru.Enabled, + uid = cumulus.WindGuru.ID, + password = cumulus.WindGuru.PW, + includerain = cumulus.WindGuru.SendRain, + interval = cumulus.WindGuru.Interval + }; + + var customseconds = new JsonThirdPartySettingsCustomHttpSeconds() + { + enabled = cumulus.CustomHttpSecondsEnabled, + interval = cumulus.CustomHttpSecondsInterval, + url = cumulus.CustomHttpSecondsString + }; + + var customminutes = new JsonThirdPartySettingsCustomHttpMinutes() + { + enabled = cumulus.CustomHttpMinutesEnabled, + intervalindex = cumulus.CustomHttpMinutesIntervalIndex, + url = cumulus.CustomHttpMinutesString + }; + + var customrollover = new JsonThirdPartySettingsCustomHttpRollover() + { + enabled = cumulus.CustomHttpRolloverEnabled, + url = cumulus.CustomHttpRolloverString + }; + + var customhttp = new JsonThirdPartySettingsCustomHttpSettings() { customseconds = customseconds, customminutes = customminutes, customrollover = customrollover }; + + var data = new JsonThirdPartySettings() + { + twitter = twittersettings, + wunderground = wusettings, + windy = windysettings, + awekas = awekassettings, + weathercloud = wcloudsettings, + pwsweather = pwssettings, + wow = wowsettings, + cwop = cwopsettings, + openweathermap = openweathermapsettings, + windguru = windgurusettings, + customhttp = customhttp + }; + + return data.ToJson(); + } + + public string GetAlpacaFormOptions() + { + using (StreamReader sr = new StreamReader(optionsFile)) + { + string json = sr.ReadToEnd(); + return json; + } + } + + public string GetAlpacaFormSchema() + { + using (StreamReader sr = new StreamReader(schemaFile)) + { + string json = sr.ReadToEnd(); + return json; + } + } + } + + + public class JsonThirdPartySettings + { + public JsonThirdPartySettingsTwitterSettings twitter { get; set; } + public JsonThirdPartySettingsWunderground wunderground { get; set; } + public JsonThirdPartySettingsWindy windy { get; set; } + public JsonThirdPartySettingsPWSweather pwsweather { get; set; } + public JsonThirdPartySettingsWOW wow { get; set; } + public JsonThirdPartySettingsCwop cwop { get; set; } + public JsonThirdPartySettingsAwekas awekas { get; set; } + public JsonThirdPartySettingsWCloud weathercloud { get; set; } + public JsonThirdPartySettingsOpenweatherMap openweathermap { get; set; } + public JsonThirdPartySettingsWindGuru windguru { get; set; } + public JsonThirdPartySettingsCustomHttpSettings customhttp { get; set; } + } + + public class JsonThirdPartySettingsTwitterSettings + { + public bool enabled { get; set; } + public bool sendlocation { get; set; } + public int interval { get; set; } + public string user { get; set; } + public string password { get; set; } + } + + public class JsonThirdPartySettingsWunderground + { + public bool enabled { get; set; } + public bool includeindoor { get; set; } + public bool includeuv { get; set; } + public bool includesolar { get; set; } + public bool includeaq { get; set; } + public bool rapidfire { get; set; } + public bool sendavgwind { get; set; } + public bool catchup { get; set; } + public string stationid { get; set; } + public string password { get; set; } + public int interval { get; set; } + } + + public class JsonThirdPartySettingsWindy + { + public bool enabled { get; set; } + public bool includeuv { get; set; } + //public bool includesolar { get; set; } + public bool catchup { get; set; } + public int interval { get; set; } + public string apikey { get; set; } + public int stationidx { get; set; } + } + + public class JsonThirdPartySettingsAwekas + { + public bool enabled { get; set; } + public bool includeuv { get; set; } + public bool includesolar { get; set; } + public bool includesoiltemp { get; set; } + public bool includesoilmoisture { get; set; } + public bool includeleafwetness { get; set; } + public bool includeindoor { get; set; } + public bool includeaq { get; set; } + public string user { get; set; } + public string password { get; set; } + public string lang { get; set; } + public int interval { get; set; } + } + + public class JsonThirdPartySettingsWCloud + { + public bool enabled { get; set; } + public int interval { get; set; } + public bool includeuv { get; set; } + public bool includesolar { get; set; } + public bool includeaqi { get; set; } + public string wid { get; set; } + public string key { get; set; } + public bool includesoilmoist { get; set; } + public int moistsensor { get; set; } + public bool includeleafwet { get; set; } + public int leafwetsensor { get; set; } + + } + + public class JsonThirdPartySettingsPWSweather + { + public bool enabled { get; set; } + public bool includeuv { get; set; } + public bool includesolar { get; set; } + public bool catchup { get; set; } + public string stationid { get; set; } + public string password { get; set; } + public int interval { get; set; } + } + + public class JsonThirdPartySettingsWOW + { + public bool enabled { get; set; } + public bool includeuv { get; set; } + public bool includesolar { get; set; } + public bool catchup { get; set; } + public string stationid { get; set; } + public string password { get; set; } + public int interval { get; set; } + } + + public class JsonThirdPartySettingsCwop + { + public bool enabled { get; set; } + public bool includesolar { get; set; } + public string id { get; set; } + public string password { get; set; } + public string server { get; set; } + public int port { get; set; } + public int interval { get; set; } + } + + public class JsonThirdPartySettingsWindGuru + { + public bool enabled { get; set; } + public string uid { get; set; } + public string password { get; set; } + public bool includerain { get; set; } + public int interval { get; set; } + } + + public class JsonThirdPartySettingsOpenweatherMap + { + public bool enabled { get; set; } + public string apikey { get; set; } + public string stationid { get; set; } + public int interval { get; set; } + public bool catchup { get; set; } + } + + public class JsonThirdPartySettingsCustomHttpSeconds + { + public string url { get; set; } + public bool enabled { get; set; } + public int interval { get; set; } + } + + public class JsonThirdPartySettingsCustomHttpMinutes + { + public string url { get; set; } + public bool enabled { get; set; } + public int intervalindex { get; set; } + } + + public class JsonThirdPartySettingsCustomHttpRollover + { + public string url { get; set; } + public bool enabled { get; set; } + } + + public class JsonThirdPartySettingsCustomHttpSettings + { + public JsonThirdPartySettingsCustomHttpSeconds customseconds { get; set; } + public JsonThirdPartySettingsCustomHttpMinutes customminutes { get; set; } + public JsonThirdPartySettingsCustomHttpRollover customrollover { get; set; } + } +} diff --git a/CumulusMX/Utils.cs b/CumulusMX/Utils.cs index 518b7924..a16c4c2a 100644 --- a/CumulusMX/Utils.cs +++ b/CumulusMX/Utils.cs @@ -1,6 +1,9 @@ using System; +using System.Linq; using Unosquare.Swan; +// A rag tag of useful functions + namespace CumulusMX { internal class Utils @@ -16,5 +19,47 @@ public static int ToUnixTime(DateTime dateTime) { return (int)dateTime.ToUniversalTime().ToUnixEpochDate(); } + + + public static string ByteArrayToHexString(byte[] ba) + { + System.Text.StringBuilder hex = new System.Text.StringBuilder(ba.Length * 2); + foreach (byte b in ba) + hex.AppendFormat("{0:x2}", b); + return hex.ToString(); + } + + + public static string GetMd5String(byte[] bytes) + { + using (var md5 = System.Security.Cryptography.MD5.Create()) + { + var hashBytes = md5.ComputeHash(bytes); + return ByteArrayToHexString(hashBytes); + } + } + + public static string GetMd5String(string str) + { + return GetMd5String(System.Text.Encoding.ASCII.GetBytes(str)); + } + + public static bool ValidateIPv4(string ipString) + { + if (string.IsNullOrWhiteSpace(ipString)) + { + return false; + } + + string[] splitValues = ipString.Split('.'); + if (splitValues.Length != 4) + { + return false; + } + + byte tempForParsing; + + return splitValues.All(r => byte.TryParse(r, out tempForParsing)); + } } } diff --git a/CumulusMX/WeatherStation.cs b/CumulusMX/WeatherStation.cs index c8537bf3..edde00b0 100644 --- a/CumulusMX/WeatherStation.cs +++ b/CumulusMX/WeatherStation.cs @@ -13,13 +13,12 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading; -using System.Threading.Tasks; using System.Timers; using MySqlConnector; using SQLite; using Timer = System.Timers.Timer; -using System.Security.Cryptography; using ServiceStack.Text; +using System.Web; namespace CumulusMX { @@ -350,7 +349,7 @@ public WeatherStation(Cumulus cumulus) GetRainCounter(); GetRainFallTotals(); - RecentDataDb = new SQLiteConnection(":memory:"); + RecentDataDb = new SQLiteConnection(":memory:", true); RecentDataDb.CreateTable(); var rnd = new Random(); @@ -655,6 +654,8 @@ public void ReadTodayFile() tempsamplestoday = ini.GetValue("Temp", "Samples", 1); HeatingDegreeDays = ini.GetValue("Temp", "HeatingDegreeDays", 0.0); CoolingDegreeDays = ini.GetValue("Temp", "CoolingDegreeDays", 0.0); + GrowingDegreeDaysThisYear1 = ini.GetValue("Temp", "GrowingDegreeDaysThisYear1", 0.0); + GrowingDegreeDaysThisYear2 = ini.GetValue("Temp", "GrowingDegreeDaysThisYear2", 0.0); // PressureHighDewpoint HiLoToday.LowPress = ini.GetValue("Pressure", "Low", 9999.0); HiLoToday.LowPressTime = ini.GetValue("Pressure", "LTime", new DateTime(CurrentYear, CurrentMonth, CurrentDay, 0, 0, 0)); @@ -747,6 +748,8 @@ public void WriteTodayFile(DateTime timestamp, bool Log) ini.SetValue("Temp", "ChillHours", ChillHours); ini.SetValue("Temp", "HeatingDegreeDays", HeatingDegreeDays); ini.SetValue("Temp", "CoolingDegreeDays", CoolingDegreeDays); + ini.SetValue("Temp", "GrowingDegreeDaysThisYear1", GrowingDegreeDaysThisYear1); + ini.SetValue("Temp", "GrowingDegreeDaysThisYear2", GrowingDegreeDaysThisYear2); // Pressure ini.SetValue("Pressure", "Low", HiLoToday.LowPress); ini.SetValue("Pressure", "LTime", HiLoToday.LowPressTime.ToString("HH:mm")); @@ -1182,6 +1185,9 @@ private string FormatDateTime(string fmt, DateTime timestamp) public double CoolingDegreeDays { get; set; } + public double GrowingDegreeDaysThisYear1 { get; set; } + public double GrowingDegreeDaysThisYear2 { get; set; } + public int tempsamplestoday { get; set; } public double TempTotalToday { get; set; } @@ -1767,42 +1773,47 @@ private void MinuteChanged(DateTime now) cumulus.UpdateWunderground(now); } - if (cumulus.Windy.Enabled && (now.Minute % cumulus.Windy.Interval == 0) && cumulus.Windy.SynchronisedUpdate && !String.IsNullOrWhiteSpace(cumulus.Windy.ApiKey)) + if (cumulus.Windy.Enabled && (now.Minute % cumulus.Windy.Interval == 0) && !String.IsNullOrWhiteSpace(cumulus.Windy.ApiKey)) { cumulus.UpdateWindy(now); } + if (cumulus.WindGuru.Enabled && (now.Minute % cumulus.WindGuru.Interval == 0) && !String.IsNullOrWhiteSpace(cumulus.WindGuru.ID)) + { + cumulus.UpdateWindGuru(now); + } + if (cumulus.AWEKAS.Enabled && (now.Minute % ((double)cumulus.AWEKAS.Interval / 60) == 0) && cumulus.AWEKAS.SynchronisedUpdate && !String.IsNullOrWhiteSpace(cumulus.AWEKAS.ID)) { cumulus.UpdateAwekas(now); } - if (cumulus.WCloud.Enabled && (now.Minute % cumulus.WCloud.Interval == 0) && cumulus.WCloud.SynchronisedUpdate && !String.IsNullOrWhiteSpace(cumulus.WCloud.ID)) + if (cumulus.WCloud.Enabled && (now.Minute % cumulus.WCloud.Interval == 0) && !String.IsNullOrWhiteSpace(cumulus.WCloud.ID)) { cumulus.UpdateWCloud(now); } - if (cumulus.OpenWeatherMap.Enabled && (now.Minute % cumulus.OpenWeatherMap.Interval == 0) && cumulus.OpenWeatherMap.SynchronisedUpdate && !string.IsNullOrWhiteSpace(cumulus.OpenWeatherMap.ID)) + if (cumulus.OpenWeatherMap.Enabled && (now.Minute % cumulus.OpenWeatherMap.Interval == 0) && !string.IsNullOrWhiteSpace(cumulus.OpenWeatherMap.ID)) { cumulus.UpdateOpenWeatherMap(now); } - if (cumulus.PWS.Enabled && (now.Minute % cumulus.PWS.Interval == 0) && cumulus.PWS.SynchronisedUpdate && !String.IsNullOrWhiteSpace(cumulus.PWS.ID) && !String.IsNullOrWhiteSpace(cumulus.PWS.PW)) + if (cumulus.PWS.Enabled && (now.Minute % cumulus.PWS.Interval == 0) && !String.IsNullOrWhiteSpace(cumulus.PWS.ID) && !String.IsNullOrWhiteSpace(cumulus.PWS.PW)) { cumulus.UpdatePWSweather(now); } - if (cumulus.WOW.Enabled && (now.Minute % cumulus.WOW.Interval == 0) && cumulus.WOW.SynchronisedUpdate && !String.IsNullOrWhiteSpace(cumulus.WOW.ID) && !String.IsNullOrWhiteSpace(cumulus.WOW.PW)) + if (cumulus.WOW.Enabled && (now.Minute % cumulus.WOW.Interval == 0) && !String.IsNullOrWhiteSpace(cumulus.WOW.ID) && !String.IsNullOrWhiteSpace(cumulus.WOW.PW)) { cumulus.UpdateWOW(now); } - if (cumulus.APRS.Enabled && (now.Minute % cumulus.APRS.Interval == 0) && cumulus.APRS.SynchronisedUpdate && !String.IsNullOrWhiteSpace(cumulus.APRS.ID)) + if (cumulus.APRS.Enabled && (now.Minute % cumulus.APRS.Interval == 0) && !String.IsNullOrWhiteSpace(cumulus.APRS.ID)) { UpdateAPRS(); } - if (cumulus.Twitter.Enabled && (now.Minute % cumulus.Twitter.Interval == 0) && cumulus.Twitter.SynchronisedUpdate && !String.IsNullOrWhiteSpace(cumulus.Twitter.ID) && !String.IsNullOrWhiteSpace(cumulus.Twitter.PW)) + if (cumulus.Twitter.Enabled && (now.Minute % cumulus.Twitter.Interval == 0) && !String.IsNullOrWhiteSpace(cumulus.Twitter.ID) && !String.IsNullOrWhiteSpace(cumulus.Twitter.PW)) { cumulus.UpdateTwitter(); } @@ -2125,6 +2136,12 @@ public void CreateEodGraphDataFiles() case "alldailysolardata.json": json = GetAllDailySolarGraphData(); break; + case "alldailydegdaydata.json": + json = GetAllDegreeDaysGraphData(); + break; + case "alltempsumdata.json": + json = GetAllTempSumGraphData(); + break; } try @@ -2673,6 +2690,24 @@ public double ConvertUserWindToMPH(double value) } } + public double ConvertUserWindToKnots(double value) + { + switch (cumulus.Units.Wind) + { + case 0: + return value * 1.943844; + case 1: + return value * 0.8689758; + case 2: + return value * 0.5399565; + case 3: + return value; + default: + return 0; + } + } + + public void ResetSunshineHours() // called at midnight irrespective of rollover time { YestSunshineHours = SunshineHours; @@ -3307,7 +3342,7 @@ public void DoPressure(double sl, DateTime timestamp) { if (cumulus.Manufacturer == cumulus.OREGONUSB) { - AltimeterPressure = ConvertPressMBToUser(StationToAltimeter(PressureHPa(StationPressure), AltitudeM(cumulus.Altitude))); + AltimeterPressure = ConvertPressMBToUser(StationToAltimeter(ConvertUserPressureToHPa(StationPressure), AltitudeM(cumulus.Altitude))); } else { @@ -3725,7 +3760,7 @@ public void DoForecast(string forecast, bool hourly) hp = cumulus.FChighpress / 0.0295333727; } - CumulusForecast = BetelCast(PressureHPa(Pressure), DateTime.Now.Month, windDir, bartrend, cumulus.Latitude > 0, hp, lp); + CumulusForecast = BetelCast(ConvertUserPressureToHPa(Pressure), DateTime.Now.Month, windDir, bartrend, cumulus.Latitude > 0, hp, lp); if (cumulus.UseCumulusForecast) { @@ -3768,7 +3803,7 @@ public double AltitudeM(double altitude) /// /// /// - public double PressureHPa(double value) + public double ConvertUserPressureToHPa(double value) { if (cumulus.Units.Press == 2) return value / 0.0295333727; @@ -4896,7 +4931,9 @@ public void DayReset(DateTime timestamp) rainthisyear = 0; } else + { rainthisyear += RainYesterday; + } if ((day == 1) && (month == cumulus.ChillHourSeasonStart)) { @@ -4905,6 +4942,16 @@ public void DayReset(DateTime timestamp) ChillHours = 0; } + if ((day == 1) && (month == cumulus.GrowingYearStarts)) + { + cumulus.LogMessage(" New growing degree day season starting"); + GrowingDegreeDaysThisYear1 = 0; + GrowingDegreeDaysThisYear2 = 0; + } + + GrowingDegreeDaysThisYear1 += MeteoLib.GrowingDegreeDays(ConvertUserTempToC(HiLoToday.HighTemp), ConvertUserTempToC(HiLoToday.LowTemp), ConvertUserTempToC(cumulus.GrowingBase1), cumulus.GrowingCap30C); + GrowingDegreeDaysThisYear2 += MeteoLib.GrowingDegreeDays(ConvertUserTempToC(HiLoToday.HighTemp), ConvertUserTempToC(HiLoToday.LowTemp), ConvertUserTempToC(cumulus.GrowingBase2), cumulus.GrowingCap30C); + // Now reset all values to the current or default ones // We may be doing a rollover from the first logger entry, // && as we do the rollover before processing the entry, the @@ -5249,9 +5296,6 @@ private void DoDayfile(DateTime timestamp) // 50 High Humidex // 51 Time of high Humidex - // 52 Low Humidex - // 53 Time of low Humidex - double AvgTemp; if (tempsamplestoday > 0) AvgTemp = TempTotalToday / tempsamplestoday; @@ -5544,7 +5588,7 @@ public double ConvertTempFToUser(double value) } /// - /// Convert temp supplied in units in use to C + /// Convert temp supplied in user units to C /// /// Temp in configured units /// Temp in C @@ -5562,7 +5606,7 @@ public double ConvertUserTempToC(double value) } /// - /// Convert temp supplied in units in use to F + /// Convert temp supplied in user units to F /// /// Temp in configured units /// Temp in F @@ -5580,7 +5624,7 @@ public double ConvertUserTempToF(double value) } /// - /// Converts wind supplied in m/s to units in use + /// Converts wind supplied in m/s to user units /// /// Wind in m/s /// Wind in configured units @@ -5602,7 +5646,7 @@ public double ConvertWindMSToUser(double value) } /// - /// Converts wind supplied in mph to units in use + /// Converts wind supplied in mph to user units /// /// Wind in m/s /// Wind in configured units @@ -5624,7 +5668,7 @@ public double ConvertWindMPHToUser(double value) } /// - /// Converts wind in units in use to m/s + /// Converts wind in user units to m/s /// /// /// @@ -5666,7 +5710,7 @@ public double ConvertKmtoUserUnits(double val) } /// - /// Converts windrun supplied in units in use to km + /// Converts windrun supplied in user units to km /// /// Windrun in configured units /// Wind in km @@ -8470,7 +8514,7 @@ public string GetWCloudURL(out string pwstring, DateTime timestamp) } // aq - if (cumulus.WCloud.SendAQI) + if (cumulus.WCloud.SendAirQuality) { switch (cumulus.StationOptions.PrimaryAqSensor) { @@ -8506,6 +8550,110 @@ public string GetWCloudURL(out string pwstring, DateTime timestamp) } } + // soil moisture + if (cumulus.WCloud.SendSoilMoisture) + { + // Weathercloud wants soil moisture in centibar. Davis supplies this, but Ecowitt provide a percentage + int moist = 0; + + switch (cumulus.WCloud.SoilMoistureSensor) + { + case 1: + moist = SoilMoisture1; + break; + case 2: + moist = SoilMoisture2; + break; + case 3: + moist = SoilMoisture3; + break; + case 4: + moist = SoilMoisture4; + break; + case 5: + moist = SoilMoisture5; + break; + case 6: + moist = SoilMoisture6; + break; + case 7: + moist = SoilMoisture7; + break; + case 8: + moist = SoilMoisture8; + break; + case 9: + moist = SoilMoisture9; + break; + case 10: + moist = SoilMoisture10; + break; + case 11: + moist = SoilMoisture11; + break; + case 12: + moist = SoilMoisture12; + break; + case 13: + moist = SoilMoisture13; + break; + case 14: + moist = SoilMoisture14; + break; + case 15: + moist = SoilMoisture15; + break; + case 16: + moist = SoilMoisture16; + break; + } + + if (cumulus.Manufacturer == cumulus.EW) + { + // very! approximate conversion from percentage to cb + moist = (100 - SoilMoisture1) * 2; + } + + sb.Append($"&soilmoist={moist}"); + } + + // leaf wetness + if (cumulus.WCloud.SendLeafWetness) + { + // Weathercloud wants soil moisture in centibar. Davis supplies this, but Ecowitt provide a percentage + int wet = 0; + + switch (cumulus.WCloud.LeafWetnessSensor) + { + case 1: + wet = LeafWetness1; + break; + case 2: + wet = LeafWetness2; + break; + case 3: + wet = LeafWetness3; + break; + case 4: + wet = LeafWetness4; + break; + case 5: + wet = LeafWetness5; + break; + case 6: + wet = LeafWetness6; + break; + case 7: + wet = LeafWetness7; + break; + case 8: + wet = LeafWetness8; + break; + } + + sb.Append($"&leafwet={wet}"); + } + // time - UTC sb.Append("&time=" + timestamp.ToUniversalTime().ToString("HHmm")); @@ -8519,31 +8667,16 @@ public string GetWCloudURL(out string pwstring, DateTime timestamp) return sb.ToString(); } - private string ByteArrayToString(byte[] ba) - { - StringBuilder hex = new StringBuilder(ba.Length * 2); - foreach (byte b in ba) - hex.AppendFormat("{0:x2}", b); - return hex.ToString(); - } - - public string GetAwekasURLv4(out string pwstring, DateTime timestamp) { var InvC = new CultureInfo(""); - byte[] hashPW; string sep = ";"; - // password is sent as MD5 hash - using (MD5 md5 = MD5.Create()) - { - hashPW = md5.ComputeHash(Encoding.ASCII.GetBytes(cumulus.AWEKAS.PW)); - } - - pwstring = ByteArrayToString(hashPW); - int presstrend; + // password is passed as a MD5 hash - not very secure, but better than plain text I guess + pwstring = Utils.GetMd5String(cumulus.AWEKAS.PW); + double threeHourlyPressureChangeMb = 0; switch (cumulus.Units.Press) @@ -8934,6 +9067,70 @@ public string GetWindyURL(out string apistring, DateTime timestamp) return URL.ToString(); } + + // Documentation on the API can be found here... + // https://stations.windguru.cz/upload_api.php + // + public string GetWindGuruURL(out string uidstring, DateTime timestamp) + { + var InvC = new CultureInfo(""); + + string salt = timestamp.ToUnixTime().ToString(); + string hash = Utils.GetMd5String(salt + cumulus.WindGuru.ID + cumulus.WindGuru.PW); + + uidstring = cumulus.WindGuru.ID; + + int numvalues = 0; + double totalwind = 0; + double maxwind = 0; + double minwind = 999; + for (int i = 0; i < MaxWindRecent; i++) + { + if (WindRecent[i].Timestamp >= DateTime.Now.AddMinutes(-cumulus.WindGuru.Interval)) + { + numvalues++; + totalwind += WindRecent[i].Gust; + + if (WindRecent[i].Gust > maxwind) + { + maxwind = WindRecent[i].Gust; + } + + if (WindRecent[i].Gust < minwind) + { + minwind = WindRecent[i].Gust; + } + } + } + // average the values + double avgwind = totalwind / numvalues * cumulus.Calib.WindSpeed.Mult; + + maxwind *= cumulus.Calib.WindGust.Mult; + minwind *= cumulus.Calib.WindGust.Mult; + + + StringBuilder URL = new StringBuilder("http://www.windguru.cz/upload/api.php?", 1024); + + URL.Append("uid=" + HttpUtility.UrlEncode(cumulus.WindGuru.ID)); + URL.Append("&salt=" + salt); + URL.Append("&hash=" + hash); + URL.Append("&interval=" + cumulus.WindGuru.Interval * 60); + URL.Append("&wind_avg=" + ConvertUserWindToKnots(avgwind).ToString("F1", InvC)); + URL.Append("&wind_max=" + ConvertUserWindToKnots(maxwind).ToString("F1", InvC)); + URL.Append("&wind_min=" + ConvertUserWindToKnots(minwind).ToString("F1", InvC)); + URL.Append("&wind_direction=" + AvgBearing); + URL.Append("&temperature=" + ConvertUserTempToC(OutdoorTemperature).ToString("F1", InvC)); + URL.Append("&rh=" + OutdoorHumidity); + URL.Append("&mslp=" + ConvertUserPressureToHPa(Pressure).ToString("F1", InvC)); + if (cumulus.WindGuru.SendRain) + { + URL.Append("&precip=" + ConvertUserRainToMM(RainLastHour).ToString("F1", InvC)); + URL.Append("&precip_interval=3600"); + } + + return URL.ToString(); + } + public string GetOpenWeatherMapData(DateTime timestamp) { StringBuilder sb = new StringBuilder($"[{{\"station_id\":\"{cumulus.OpenWeatherMap.ID}\","); @@ -8944,7 +9141,7 @@ public string GetOpenWeatherMapData(DateTime timestamp) sb.Append($"\"wind_deg\":{AvgBearing},"); sb.Append($"\"wind_speed\":{Math.Round(ConvertUserWindToMS(WindAverage), 1).ToString(invC)},"); sb.Append($"\"wind_gust\":{Math.Round(ConvertUserWindToMS(RecentMaxGust), 1).ToString(invC)},"); - sb.Append($"\"pressure\":{Math.Round(PressureHPa(Pressure), 1).ToString(invC)},"); + sb.Append($"\"pressure\":{Math.Round(ConvertUserPressureToHPa(Pressure), 1).ToString(invC)},"); sb.Append($"\"humidity\":{OutdoorHumidity},"); sb.Append($"\"rain_1h\":{Math.Round(ConvertUserRainToMM(RainLastHour), 1).ToString(invC)},"); sb.Append($"\"rain_24h\":{Math.Round(ConvertUserRainToMM(RainLast24Hour), 1).ToString(invC)}"); @@ -10187,7 +10384,7 @@ public string GetDayfile(string draw, int start, int length) } // trim last "," - json.Remove(json.Length - 1, 1); + json.Length--; json.Append("]}"); return json.ToString(); @@ -10243,7 +10440,7 @@ internal string GetDiarySummary() json.Append(result[i].Timestamp.ToUniversalTime().ToString("yyy-MM-dd")); json.Append("\","); } - json.Remove(json.Length - 1, 1); + json.Length--; json.Append("]}"); } else @@ -10330,7 +10527,7 @@ public string GetLogfile(string date, string draw, int start, int length, bool e } // trim trailing "," - json.Remove(json.Length - 1, 1); + json.Length--; json.Append("]}"); return json.ToString(); @@ -10393,7 +10590,7 @@ public string GetAvailGraphData() //if (cumulus.GraphOptions.HumidexVisible) // json.Append("\"Humidex\","); - if (json.ToString().EndsWith(",")) + if (json[json.Length - 1] == ',') json.Length--; // humidity values @@ -10405,7 +10602,7 @@ public string GetAvailGraphData() if (cumulus.GraphOptions.InHumVisible) json.Append("\"Indoor Hum\","); - if (json.ToString().EndsWith(",")) + if (json[json.Length - 1] == ',') json.Length--; // fixed values @@ -10429,7 +10626,7 @@ public string GetAvailGraphData() if (cumulus.GraphOptions.DailyMinTempVisible) json.Append("\"MinTemp\","); - if (json.ToString().EndsWith(",")) + if (json[json.Length - 1] == ',') json.Length--; json.Append("]"); @@ -10447,7 +10644,7 @@ public string GetAvailGraphData() if (cumulus.GraphOptions.UVVisible) json.Append("\"UV Index\","); - if (json.ToString().EndsWith(",")) + if (json[json.Length - 1] == ',') json.Length--; json.Append("]"); @@ -10477,6 +10674,39 @@ public string GetAvailGraphData() json.Append("]"); } + // Degree Days + if (cumulus.GraphOptions.GrowingDegreeDaysVisible1 || cumulus.GraphOptions.GrowingDegreeDaysVisible2) + { + json.Append(",\"DegreeDays\":["); + if (cumulus.GraphOptions.GrowingDegreeDaysVisible1) + json.Append("\"GDD1\","); + + if (cumulus.GraphOptions.GrowingDegreeDaysVisible2) + json.Append("\"GDD2\""); + + if (json[json.Length - 1] == ',') + json.Length--; + + json.Append("]"); + } + + // Temp Sum + if (cumulus.GraphOptions.TempSumVisible0 || cumulus.GraphOptions.TempSumVisible1 || cumulus.GraphOptions.TempSumVisible2) + { + json.Append(",\"TempSum\":["); + if (cumulus.GraphOptions.TempSumVisible0) + json.Append("\"Sum0\","); + if (cumulus.GraphOptions.TempSumVisible1) + json.Append("\"Sum1\","); + if (cumulus.GraphOptions.TempSumVisible2) + json.Append("\"Sum2\""); + + if (json[json.Length - 1] == ',') + json.Length--; + + json.Append("]"); + } + json.Append("}"); return json.ToString(); } @@ -11091,6 +11321,391 @@ public string GetAllDailySolarGraphData() return sb.ToString(); } + public string GetAllDegreeDaysGraphData() + { + var InvC = new CultureInfo(""); + + StringBuilder sb = new StringBuilder("{"); + StringBuilder growdegdaysYears1 = new StringBuilder("{", 32768); + StringBuilder growdegdaysYears2 = new StringBuilder("{", 32768); + + StringBuilder growYear1 = new StringBuilder("[", 8600); + StringBuilder growYear2 = new StringBuilder("[", 8600); + + var options = $"\"options\":{{\"gddBase1\":{cumulus.GrowingBase1},\"gddBase2\":{cumulus.GrowingBase2},\"startMon\":{cumulus.GrowingYearStarts}}}"; + + DateTime nextYear; + + // 2000 was a leap year, so make sure February falls in 2000 + // for Southern hemisphere this means the start year must be 1999 + var plotYear = cumulus.GrowingYearStarts < 3 ? 2000 : 1999; + + int startYear; + + var annualGrowingDegDays1 = 0.0; + var annualGrowingDegDays2 = 0.0; + + // Read the day file list and extract the data from there + if (DayFile.Count() > 0 && (cumulus.GraphOptions.GrowingDegreeDaysVisible1 || cumulus.GraphOptions.GrowingDegreeDaysVisible2)) + { + // we have to detect a new growing deg day year is starting + nextYear = new DateTime(DayFile[0].Date.Year, cumulus.GrowingYearStarts, 1); + + if (DayFile[0].Date >= nextYear) + { + nextYear = nextYear.AddYears(1); + } + + // are we starting part way through a year that does not start in January? + if (DayFile[0].Date.Year == nextYear.Year) + { + startYear = DayFile[0].Date.Year - 1; + } + else + { + startYear = DayFile[0].Date.Year; + } + + if (cumulus.GraphOptions.GrowingDegreeDaysVisible1) + { + growdegdaysYears1.Append($"\"{startYear}\":"); + } + if (cumulus.GraphOptions.GrowingDegreeDaysVisible2) + { + growdegdaysYears2.Append($"\"{startYear}\":"); + } + + + for (var i = 0; i < DayFile.Count(); i++) + { + // we have rolled over into a new GDD year, write out what we have and reset + if (DayFile[i].Date >= nextYear) + { + if (cumulus.GraphOptions.GrowingDegreeDaysVisible1 && growYear1.Length > 10) + { + // remove last comma + growYear1.Length--; + // close the year data + growYear1.Append("],"); + // apend to years array + growdegdaysYears1.Append(growYear1); + + growYear1.Clear().Append($"\"{DayFile[i].Date.Year}\":["); + } + if (cumulus.GraphOptions.GrowingDegreeDaysVisible2 && growYear2.Length > 10) + { + // remove last comma + growYear2.Length--; + // close the year data + growYear2.Append("],"); + // apend to years array + growdegdaysYears2.Append(growYear2); + + growYear2.Clear().Append($"\"{DayFile[i].Date.Year}\":["); + } + + // reset the plot year for Southern hemi + plotYear = cumulus.GrowingYearStarts < 3 ? 2000 : 1999; + + annualGrowingDegDays1 = 0; + annualGrowingDegDays2 = 0; + do + { + nextYear = nextYear.AddYears(1); + } + while (DayFile[i].Date >= nextYear); + } + + // make all series the same year so they plot together + // 2000 was a leap year, so make sure February falls in 2000 + // for Southern hemisphere this means the start year must be 1999 + if (cumulus.GrowingYearStarts > 2 && plotYear == 1999 && DayFile[i].Date.Month == 1) + { + plotYear++; + } + + // make all series the same year so they plot together + long recDate = DateTimeToUnix(new DateTime(plotYear, DayFile[i].Date.Month, DayFile[i].Date.Day)) * 1000; + + if (cumulus.GraphOptions.GrowingDegreeDaysVisible1) + { + // growing degree days + var gdd = MeteoLib.GrowingDegreeDays(ConvertUserTempToC(DayFile[i].HighTemp), ConvertUserTempToC(DayFile[i].LowTemp), ConvertUserTempToC(cumulus.GrowingBase1), cumulus.GrowingCap30C); + + // annual accumulation + annualGrowingDegDays1 += gdd; + + growYear1.Append($"[{recDate},{annualGrowingDegDays1.ToString("F1", InvC)}],"); + } + + if (cumulus.GraphOptions.GrowingDegreeDaysVisible2) + { + // growing degree days + var gdd = MeteoLib.GrowingDegreeDays(ConvertUserTempToC(DayFile[i].HighTemp), ConvertUserTempToC(DayFile[i].LowTemp), ConvertUserTempToC(cumulus.GrowingBase2), cumulus.GrowingCap30C); + + // annual accumulation + annualGrowingDegDays2 += gdd; + + growYear2.Append($"[{recDate},{annualGrowingDegDays2.ToString("F1", InvC)}],"); + } + } + } + + // remove last commas from the years arrays and close them off + if (cumulus.GraphOptions.GrowingDegreeDaysVisible1) + { + if (growYear1[growYear1.Length - 1] == ',') + { + growYear1.Length--; + } + + // have previous years been appended? + if (growdegdaysYears1[growdegdaysYears1.Length - 1] == ']') + { + growdegdaysYears1.Append(","); + } + + growdegdaysYears1.Append(growYear1 + "]"); + + // add to main json + sb.Append("\"GDD1\":" + growdegdaysYears1 + "},"); + } + if (cumulus.GraphOptions.GrowingDegreeDaysVisible2) + { + if (growYear2[growYear2.Length - 1] == ',') + { + growYear2.Length--; + } + + // have previous years been appended? + if (growdegdaysYears2[growdegdaysYears2.Length - 1] == ']') + { + growdegdaysYears2.Append(","); + } + growdegdaysYears2.Append(growYear2 + "]"); + + // add to main json + sb.Append("\"GDD2\":" + growdegdaysYears2 + "},"); + } + + sb.Append(options); + + sb.Append("}"); + + return sb.ToString(); + } + + public string GetAllTempSumGraphData() + { + var InvC = new CultureInfo(""); + + StringBuilder sb = new StringBuilder("{"); + StringBuilder tempSumYears0 = new StringBuilder("{", 32768); + StringBuilder tempSumYears1 = new StringBuilder("{", 32768); + StringBuilder tempSumYears2 = new StringBuilder("{", 32768); + + StringBuilder tempSum0 = new StringBuilder("[", 8600); + StringBuilder tempSum1 = new StringBuilder("[", 8600); + StringBuilder tempSum2 = new StringBuilder("[", 8600); + + DateTime nextYear; + + // 2000 was a leap year, so make sure February falls in 2000 + // for Southern hemisphere this means the start year must be 1999 + var plotYear = cumulus.TempSumYearStarts < 3 ? 2000 : 1999; + + int startYear; + var annualTempSum0 = 0.0; + var annualTempSum1 = 0.0; + var annualTempSum2 = 0.0; + + var options = $"\"options\":{{\"sumBase1\":{cumulus.TempSumBase1},\"sumBase2\":{cumulus.TempSumBase2},\"startMon\":{cumulus.TempSumYearStarts}}}"; + + // Read the day file list and extract the data from there + if (DayFile.Count() > 0 && (cumulus.GraphOptions.TempSumVisible0 || cumulus.GraphOptions.TempSumVisible1 || cumulus.GraphOptions.TempSumVisible2)) + { + // we have to detect a new year is starting + nextYear = new DateTime(DayFile[0].Date.Year, cumulus.TempSumYearStarts, 1); + + if (DayFile[0].Date >= nextYear) + { + nextYear = nextYear.AddYears(1); + } + + // are we starting part way through a year that does not start in January? + if (DayFile[0].Date.Year == nextYear.Year) + { + startYear = DayFile[0].Date.Year - 1; + } + else + { + startYear = DayFile[0].Date.Year; + } + + if (cumulus.GraphOptions.TempSumVisible0) + { + tempSumYears0.Append($"\"{startYear}\":"); + } + if (cumulus.GraphOptions.TempSumVisible1) + { + tempSumYears1.Append($"\"{startYear}\":"); + } + if (cumulus.GraphOptions.TempSumVisible2) + { + tempSumYears2.Append($"\"{startYear}\":"); + } + + for (var i = 0; i < DayFile.Count(); i++) + { + // we have rolled over into a new GDD year, write out what we have and reset + if (DayFile[i].Date >= nextYear) + { + if (cumulus.GraphOptions.TempSumVisible0 && tempSum0.Length > 10) + { + // remove last comma + tempSum0.Length--; + // close the year data + tempSum0.Append("],"); + // apend to years array + tempSumYears0.Append(tempSum0); + + tempSum0.Clear().Append($"\"{DayFile[i].Date.Year}\":["); + } + if (cumulus.GraphOptions.TempSumVisible1 && tempSum1.Length > 10) + { + // remove last comma + tempSum1.Length--; + // close the year data + tempSum1.Append("],"); + // apend to years array + tempSumYears1.Append(tempSum1); + + tempSum1.Clear().Append($"\"{DayFile[i].Date.Year}\":["); + } + if (cumulus.GraphOptions.TempSumVisible2 && tempSum2.Length > 10) + { + // remove last comma + tempSum2.Length--; + // close the year data + tempSum2.Append("],"); + // apend to years array + tempSumYears2.Append(tempSum2); + + tempSum2.Clear().Append($"\"{DayFile[i].Date.Year}\":["); + } + + // reset the plot year for Southern hemi + plotYear = cumulus.TempSumYearStarts < 3 ? 2000 : 1999; + + annualTempSum0 = 0; + annualTempSum1 = 0; + annualTempSum2 = 0; + + do + { + nextYear = nextYear.AddYears(1); + } + while (DayFile[i].Date >= nextYear); + } + // make all series the same year so they plot together + // 2000 was a leap year, so make sure February falls in 2000 + // for Southern hemisphere this means the start year must be 1999 + if (cumulus.TempSumYearStarts > 2 && plotYear == 1999 && DayFile[i].Date.Month == 1) + { + plotYear++; + } + + long recDate = DateTimeToUnix(new DateTime(plotYear, DayFile[i].Date.Month, DayFile[i].Date.Day)) * 1000; + + if (cumulus.GraphOptions.TempSumVisible0) + { + // annual accumulation + annualTempSum0 += DayFile[i].AvgTemp; + tempSum0.Append($"[{recDate},{annualTempSum0.ToString("F0", InvC)}],"); + } + if (cumulus.GraphOptions.TempSumVisible1) + { + // annual accumulation + annualTempSum1 += DayFile[i].AvgTemp - cumulus.TempSumBase1; + tempSum1.Append($"[{recDate},{annualTempSum1.ToString("F0", InvC)}],"); + } + if (cumulus.GraphOptions.TempSumVisible2) + { + // annual accumulation + annualTempSum2 += DayFile[i].AvgTemp - cumulus.TempSumBase2; + tempSum2.Append($"[{recDate},{annualTempSum2.ToString("F0", InvC)}],"); + } + } + } + + // remove last commas from the years arrays and close them off + if (cumulus.GraphOptions.TempSumVisible0) + { + if (tempSum0[tempSum0.Length - 1] == ',') + { + tempSum0.Length--; + } + + // have previous years been appended? + if (tempSumYears0[tempSumYears0.Length - 1] == ']') + { + tempSumYears0.Append(","); + } + + tempSumYears0.Append(tempSum0 + "]"); + + // add to main json + sb.Append("\"Sum0\":" + tempSumYears0 + "}"); + + if (cumulus.GraphOptions.TempSumVisible1 || cumulus.GraphOptions.TempSumVisible2) + sb.Append(","); + } + if (cumulus.GraphOptions.TempSumVisible1) + { + if (tempSum1[tempSum1.Length - 1] == ',') + { + tempSum1.Length--; + } + + // have previous years been appended? + if (tempSumYears1[tempSumYears1.Length - 1] == ']') + { + tempSumYears1.Append(","); + } + + tempSumYears1.Append(tempSum1 + "]"); + + // add to main json + sb.Append("\"Sum1\":" + tempSumYears1 + "},"); + } + if (cumulus.GraphOptions.TempSumVisible2) + { + if (tempSum2[tempSum2.Length - 1] == ',') + { + tempSum2.Length--; + } + + // have previous years been appended? + if (tempSumYears2[tempSumYears2.Length - 1] == ']') + { + tempSumYears2.Append(","); + } + + tempSumYears2.Append(tempSum2 + "]"); + + // add to main json + sb.Append("\"Sum2\":" + tempSumYears2 + "},"); + } + + sb.Append(options); + + sb.Append("}"); + + return sb.ToString(); + } + + + internal string GetCurrentData() { StringBuilder windRoseData = new StringBuilder((windcounts[0] * cumulus.Calib.WindGust.Mult).ToString(cumulus.WindFormat, CultureInfo.InvariantCulture), 4096); diff --git a/CumulusMX/packages.config b/CumulusMX/packages.config index 1f146797..c8a69cb5 100644 --- a/CumulusMX/packages.config +++ b/CumulusMX/packages.config @@ -12,7 +12,7 @@ - + @@ -24,6 +24,6 @@ - + \ No newline at end of file diff --git a/Updates.txt b/Updates.txt index 69ea4605..faf301b8 100644 --- a/Updates.txt +++ b/Updates.txt @@ -1,4 +1,4 @@ -3.11.0 - b3123 +3.11.0 - b3125 —————————————— - Fix: Remove THW Index in /web/websitedataT.json, and default web site index.htm - Fix: Remove <#THWindex> web tag from Cumulus MX @@ -6,8 +6,16 @@ - Fix: WeatherCloud - date/time of reading should be UTC - Fix: End of day backup now always runs at rollover - Fix: FineOffset stations were not ignoring invalid wind speeds on historic catch up - +- Fix: FineOffset synchronise reads process improvements (attempt) +- Fix: Catch some errors that could occur in the start-up PING process +- Fix: MySQL connections that use encryption should now be supported (MySQL 8.0 default) +- Fix: GW1000 System Information now correctly decodes 915 and 920MHz devices +- Fix: GW1000 occasional crash when an unexpected response was received +- Fix: All settings screens error handling messages now no longer display "[object][object]" + +- New: Adds support for Windguru uploads - New: Adds Email support for Alarms + - Configured via Internet Settings (email server config), and Alarm Settings (from/to addresses for alarms, and a Test Email function) - Creates a new section in Cumulus.ini: [SMTP] ServerName= @@ -24,19 +32,29 @@ DestEmail= - Creates a new section in strings.ini, with email text [AlarmEmails] - -- Changed: Moved FTP Logging option from Internet Settings to Program Setting to collect all the logging options in one place +- New: WeatherCloud now supports uploading of Air Quality, Soil Moisture, and Leaf Wetness +- New: Adds support for two sets of Growing Degree Days data + - Configured via Station Settings|Growing Degree Days, visibility via Station Settings|Graphs|Data Series Visibility|Degree Days +- New: Adds support for Temperature Sum - annual running total of daily average temperatures + - Configured via Station Settings|Temperature Sum, visibility via Station Settings|Graphs|Data Series Visibility|Temperature Data +- New: The graphs pages - both dashboard and default web site - now set a page hash value depending on the graph being shown. + Benefit = you can now link directly to a particular graph to show on page load + +- Changed: Moved FTP Logging option from Internet Settings to Program Settings to collect all the logging options in one place - Changed: A Data Stopped state now stops all logging, MySQL and web activity -- Changed: Updates to various components... +- Changed: Updates to various library components... - Updated to latest versions: - FTP: FluentFTP - JSON: ServiceStack.Text - - Pointers: System.Runtime.CompilerServcies.Unsafe + - Pointers: System.Runtime.CompilerServices.Unsafe - MQTT: MQTTnet - Removed/Added - - MySQL: Removed Devart, replaced with MySqlConnector & BouncyCastle.Crypto + - MySQL: Removed Devart, replaced with MySqlConnector + - MailKit: Added along with supporting packages MimeKit, and Portable.BouncyCastle - Changed: FineOffset stations now attempt to reconnect the USB after a data stopped event. These stations now also keep retrying to connect to the station at start-up - Changed: The MySQL real time table data retention settings reworked. +- Changed: Moved all third party web uploads from Internet Settings page to their own Third Party Settings page +- Changed: Internal optimisations From 92e002112e6a9803185e56c18f907ee3063a8d23 Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Tue, 27 Apr 2021 11:27:23 +0100 Subject: [PATCH 09/16] Adds THW Index calculation --- CumulusMX/MeteoLib.cs | 18 ++++++++++++++++++ CumulusMX/WeatherStation.cs | 11 ++++++----- CumulusMX/webtags.cs | 10 +++++----- Updates.txt | 6 +++--- 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/CumulusMX/MeteoLib.cs b/CumulusMX/MeteoLib.cs index d0845360..3191801b 100644 --- a/CumulusMX/MeteoLib.cs +++ b/CumulusMX/MeteoLib.cs @@ -309,5 +309,23 @@ public static double GrowingDegreeDays(double maxC, double minC, double baseC, b } return gdd; } + + /// + /// Calculates the Davis THW Index. + /// Uses method described for THSW Index in AN28 + /// + /// The current temperature (Celsius) + /// The current RH + /// The current average wind speed (KPH) + /// THWIndex (Celsius) + + public static double THWIndex(double tempC, int hum, double windKph) + { + var hindex = HeatIndex(tempC, hum); + + var wind = tempC - WindChill(tempC, windKph); + + return hindex - wind; + } } } diff --git a/CumulusMX/WeatherStation.cs b/CumulusMX/WeatherStation.cs index edde00b0..485fa56c 100644 --- a/CumulusMX/WeatherStation.cs +++ b/CumulusMX/WeatherStation.cs @@ -168,7 +168,7 @@ public struct dayfilerec // Current values - //public double THWIndex = 0; + public double THWIndex = 0; public double THSWIndex = 0; public double raindaystart = 0.0; @@ -2893,6 +2893,7 @@ public void DoOutdoorTemp(double temp, DateTime timestamp) OutdoorTemperature = CalibrateTemp(temp); double tempinF = ConvertUserTempToF(OutdoorTemperature); + double tempinC = ConvertUserTempToC(OutdoorTemperature); first_temp = false; @@ -2955,12 +2956,9 @@ public void DoOutdoorTemp(double temp, DateTime timestamp) // Calculate temperature range HiLoToday.TempRange = HiLoToday.HighTemp - HiLoToday.LowTemp; - double tempinC; - if ((cumulus.StationOptions.CalculatedDP || cumulus.DavisStation) && (OutdoorHumidity != 0) && (!cumulus.FineOffsetStation)) { // Calculate DewPoint. - tempinC = ConvertUserTempToC(OutdoorTemperature); // dewpoint = TempinC + ((0.13 * TempinC) + 13.6) * Ln(humidity / 100); OutdoorDewpoint = ConvertTempCToUser(MeteoLib.DewPoint(tempinC, OutdoorHumidity)); @@ -2981,7 +2979,6 @@ public void DoOutdoorTemp(double temp, DateTime timestamp) CloudBase = 0; } - tempinC = ConvertUserTempToC(OutdoorTemperature); HeatIndex = ConvertTempCToUser(MeteoLib.HeatIndex(tempinC, OutdoorHumidity)); if (HeatIndex > HiLoToday.HighHeatIndex) @@ -3039,6 +3036,10 @@ public void DoApparentTemp(DateTime timestamp) // (0.7 * ConvertUserWindToMS(WindAverage)) - 4); ApparentTemperature = ConvertTempCToUser(MeteoLib.ApparentTemperature(ConvertUserTempToC(OutdoorTemperature), ConvertUserWindToMS(WindAverage), OutdoorHumidity)); + + // we will tag on the THW Index here + THWIndex = ConvertTempCToUser(MeteoLib.THWIndex(ConvertUserTempToC(OutdoorTemperature), OutdoorHumidity, ConvertUserWindToKPH(WindAverage))); + if (ApparentTemperature > HiLoToday.HighAppTemp) { HiLoToday.HighAppTemp = ApparentTemperature; diff --git a/CumulusMX/webtags.cs b/CumulusMX/webtags.cs index 49fe2ed0..4afde0b8 100644 --- a/CumulusMX/webtags.cs +++ b/CumulusMX/webtags.cs @@ -3133,10 +3133,10 @@ private string TagSunshineHours(Dictionary tagParams) return CheckRcDp(station.SunshineHours, tagParams, cumulus.SunshineDPlaces); } - //private string TagThwIndex(Dictionary tagParams) - //{ - // return CheckRcDp(station.THWIndex, tagParams, 1); - //} + private string TagThwIndex(Dictionary tagParams) + { + return CheckRcDp(station.THWIndex, tagParams, 1); + } private string TagThswIndex(Dictionary tagParams) { @@ -5379,7 +5379,7 @@ public void InitialiseWebtags() { "IsSunny", TagIsSunny }, { "IsRaining", TagIsRaining }, { "IsFreezing", TagIsFreezing }, - //{ "THWindex", TagThwIndex }, + { "THWindex", TagThwIndex }, { "THSWindex", TagThswIndex }, { "ExtraTemp1", TagExtraTemp1 }, { "ExtraTemp2", TagExtraTemp2 }, diff --git a/Updates.txt b/Updates.txt index faf301b8..296310e6 100644 --- a/Updates.txt +++ b/Updates.txt @@ -1,7 +1,6 @@ -3.11.0 - b3125 +3.11.0 - b3126 —————————————— - Fix: Remove THW Index in /web/websitedataT.json, and default web site index.htm -- Fix: Remove <#THWindex> web tag from Cumulus MX - Fix: OpenWeatherMap - create new station was not forcing the use of dot decimals for lat/long values - Fix: WeatherCloud - date/time of reading should be UTC - Fix: End of day backup now always runs at rollover @@ -11,8 +10,9 @@ - Fix: MySQL connections that use encryption should now be supported (MySQL 8.0 default) - Fix: GW1000 System Information now correctly decodes 915 and 920MHz devices - Fix: GW1000 occasional crash when an unexpected response was received -- Fix: All settings screens error handling messages now no longer display "[object][object]" +- Fix: All settings screens error handling messages now no longer display "[object object]" +- New: Adds support for a THW Index calculation. This value is now available for all station types via the web tag <#THWindex> - New: Adds support for Windguru uploads - New: Adds Email support for Alarms - Configured via Internet Settings (email server config), and Alarm Settings (from/to addresses for alarms, and a Test Email function) From 64f14a5302c6bcf11562b1bd6ceac3c650269733 Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Tue, 27 Apr 2021 16:31:33 +0100 Subject: [PATCH 10/16] - Fix: FineOffset stations historic catch up was failing when it reached a logger memory wrap around - Fix: FineOffset stations historic catch up is now limited to the number of logger entries the console says are recorded - New: FineOffset stations now report a warning if the console logger interval does match the Cumulus MX interval --- CumulusMX/CumulusMX.csproj | 2 +- CumulusMX/FOStation.cs | 25 +++++++++++++++++++++---- CumulusMX/MeteoLib.cs | 13 ++++++++----- CumulusMX/packages.config | 2 +- Updates.txt | 5 ++++- 5 files changed, 35 insertions(+), 12 deletions(-) diff --git a/CumulusMX/CumulusMX.csproj b/CumulusMX/CumulusMX.csproj index 43a33282..ab8ae0ea 100644 --- a/CumulusMX/CumulusMX.csproj +++ b/CumulusMX/CumulusMX.csproj @@ -109,7 +109,7 @@ ..\packages\MQTTnet.3.0.15\lib\net452\MQTTnet.dll - ..\packages\MySqlConnector.1.3.3\lib\net45\MySqlConnector.dll + ..\packages\MySqlConnector.1.3.7\lib\net45\MySqlConnector.dll ..\packages\SSH.NET.2016.1.0\lib\net40\Renci.SshNet.dll diff --git a/CumulusMX/FOStation.cs b/CumulusMX/FOStation.cs index aca19761..e3377e59 100644 --- a/CumulusMX/FOStation.cs +++ b/CumulusMX/FOStation.cs @@ -74,8 +74,22 @@ internal FOStation(Cumulus cumulus) : base(cumulus) { if (OpenHidDevice()) { + // Get the block of data containing the logging interval + cumulus.LogMessage("Reading station logging interval"); + if (ReadAddress(0x10, data)) + { + int logint = data[0]; + + if (logint != cumulus.logints[cumulus.DataLogInterval]) + { + var msg = $"Warning, your console logging interval ({logint} mins) does not match the Cumulus logging interval ({cumulus.logints[cumulus.DataLogInterval]} mins)"; + cumulus.LogConsoleMessage(msg); + cumulus.LogMessage(msg); + } + } + // Get the block of data containing the abs and rel pressures - cumulus.LogMessage("Reading pressure offset"); + cumulus.LogMessage("Reading station pressure offset"); if (ReadAddress(0x20, data)) { @@ -173,9 +187,12 @@ public override void getAndProcessHistoryData() } // get address of current location - int addr = ((data[31])*256) + data[30]; + int addr = data[31] * 256 + data[30]; //int previousaddress = addr; + // get the number of logger entries the console has recorded + int logEntries = data[28] * 256 + data[27]; + cumulus.LogMessage("Reading current address " + addr.ToString("X4")); if (!ReadAddress(addr, data)) { @@ -194,11 +211,11 @@ public override void getAndProcessHistoryData() // calculate timestamp of previous history data timestamp = timestamp.AddMinutes(-interval); - if ((interval != 255) && (timestamp > cumulus.LastUpdateTime) && (datalist.Count < maxHistoryEntries - 2)) + if ((interval != 255) && (timestamp > cumulus.LastUpdateTime) && (datalist.Count < logEntries)) { // Read previous data addr -= foEntrysize; - if (addr == 0xF0) addr = foMaxAddr; // wrap around + if (addr < 0x100) addr = foMaxAddr; // wrap around if (!ReadAddress(addr, data)) { diff --git a/CumulusMX/MeteoLib.cs b/CumulusMX/MeteoLib.cs index 3191801b..0ce7e836 100644 --- a/CumulusMX/MeteoLib.cs +++ b/CumulusMX/MeteoLib.cs @@ -13,14 +13,15 @@ internal class MeteoLib /// /// Temp in C /// Average wind speed in km/h + /// Use the 10C cutoff /// Wind Chill in Celcius - public static double WindChill(double tempC, double windSpeedKph) + public static double WindChill(double tempC, double windSpeedKph, bool tempCutoff = true) { // see American Meteorological Society Journal // see http://www.msc.ec.gc.ca/education/windchill/science_equations_e.cfm // see http://www.weather.gov/os/windchill/index.shtml - if ((tempC >= 10.0) || (windSpeedKph <= 4.8)) + if ((tempC >= 10.0 && tempCutoff) || (windSpeedKph <= 4.8)) return tempC; double windPow = Math.Pow(windSpeedKph, 0.16); @@ -62,7 +63,8 @@ public static double ApparentTemperature(double tempC, double windspeedMS, int h public static double FeelsLike(double tempC, double windSpeedKph, int humidity) { // Cannot use the WindChill function as we need the chill above 10 C - double chill = windSpeedKph < 4.828 ? tempC : 13.12 + 0.6215 * tempC - 11.37 * Math.Pow(windSpeedKph, 0.16) + 0.3965 * tempC * Math.Pow(windSpeedKph, 0.16); + //double chill = windSpeedKph < 4.828 ? tempC : 13.12 + 0.6215 * tempC - 11.37 * Math.Pow(windSpeedKph, 0.16) + 0.3965 * tempC * Math.Pow(windSpeedKph, 0.16); + double chill = WindChill(tempC, windSpeedKph, false); double svp = SaturationVapourPressure1980(tempC); // Saturation Vapour Pressure in hPa double avp = (float)humidity / 100.0 * svp / 10.0; // Actual Vapour Pressure in kPa if (windSpeedKph > 72) windSpeedKph = 72; // Windspeed limited to 20 m/s = 72 km/h @@ -312,7 +314,7 @@ public static double GrowingDegreeDays(double maxC, double minC, double baseC, b /// /// Calculates the Davis THW Index. - /// Uses method described for THSW Index in AN28 + /// Uses method described for Heat Index and THSW Index in AN28 /// /// The current temperature (Celsius) /// The current RH @@ -323,7 +325,8 @@ public static double THWIndex(double tempC, int hum, double windKph) { var hindex = HeatIndex(tempC, hum); - var wind = tempC - WindChill(tempC, windKph); + // above 144F/62.22C wind factor is zero + var wind = tempC > 62.22 ? 0 : tempC - WindChill(tempC, windKph, false); return hindex - wind; } diff --git a/CumulusMX/packages.config b/CumulusMX/packages.config index c8a69cb5..41c606b4 100644 --- a/CumulusMX/packages.config +++ b/CumulusMX/packages.config @@ -12,7 +12,7 @@ - + diff --git a/Updates.txt b/Updates.txt index 296310e6..7b8b9fe4 100644 --- a/Updates.txt +++ b/Updates.txt @@ -5,9 +5,11 @@ - Fix: WeatherCloud - date/time of reading should be UTC - Fix: End of day backup now always runs at rollover - Fix: FineOffset stations were not ignoring invalid wind speeds on historic catch up +- Fix: FineOffset stations historic catch up was failing when it reached a logger memory wrap around +- Fix: FineOffset stations historic catch up is now limited to the number of logger entries the console says are recorded - Fix: FineOffset synchronise reads process improvements (attempt) - Fix: Catch some errors that could occur in the start-up PING process -- Fix: MySQL connections that use encryption should now be supported (MySQL 8.0 default) +- Fix: MySQL connections that use encryption should now be supported (MySQL 8.0+ default) - Fix: GW1000 System Information now correctly decodes 915 and 920MHz devices - Fix: GW1000 occasional crash when an unexpected response was received - Fix: All settings screens error handling messages now no longer display "[object object]" @@ -39,6 +41,7 @@ - Configured via Station Settings|Temperature Sum, visibility via Station Settings|Graphs|Data Series Visibility|Temperature Data - New: The graphs pages - both dashboard and default web site - now set a page hash value depending on the graph being shown. Benefit = you can now link directly to a particular graph to show on page load +- New: FineOffset stations now report a warning if the console logger interval does match the Cumulus MX interval - Changed: Moved FTP Logging option from Internet Settings to Program Settings to collect all the logging options in one place - Changed: A Data Stopped state now stops all logging, MySQL and web activity From 53ff68ca7a772d66be7731874f46ed9b2ea4f9df Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Wed, 28 Apr 2021 17:57:18 +0100 Subject: [PATCH 11/16] Fix FineOffset catch-up logger issues. Add ability to set FO logger interval --- CumulusMX/Cumulus.cs | 51 +++++++------ CumulusMX/FOStation.cs | 141 ++++++++++++++++++++++++----------- CumulusMX/ImetStation.cs | 16 ++-- CumulusMX/StationSettings.cs | 31 ++++---- CumulusMX/WeatherStation.cs | 2 +- Updates.txt | 2 + 6 files changed, 154 insertions(+), 89 deletions(-) diff --git a/CumulusMX/Cumulus.cs b/CumulusMX/Cumulus.cs index ef38d8bc..0a02a007 100644 --- a/CumulusMX/Cumulus.cs +++ b/CumulusMX/Cumulus.cs @@ -3652,9 +3652,10 @@ private void ReadIniFile() DataLogInterval = 2; } - FineOffsetOptions.FineOffsetSyncReads = ini.GetValue("Station", "SyncFOReads", true); - FineOffsetOptions.FineOffsetReadAvoidPeriod = ini.GetValue("Station", "FOReadAvoidPeriod", 3); - FineOffsetOptions.FineOffsetReadTime = ini.GetValue("Station", "FineOffsetReadTime", 150); + FineOffsetOptions.SyncReads = ini.GetValue("Station", "SyncFOReads", true); + FineOffsetOptions.ReadAvoidPeriod = ini.GetValue("Station", "FOReadAvoidPeriod", 3); + FineOffsetOptions.ReadTime = ini.GetValue("Station", "FineOffsetReadTime", 150); + FineOffsetOptions.SetLoggerInterval = ini.GetValue("Station", "FineOffsetSetLoggerInterval", false); FineOffsetOptions.VendorID = ini.GetValue("Station", "VendorID", -1); FineOffsetOptions.ProductID = ini.GetValue("Station", "ProductID", -1); @@ -3725,16 +3726,16 @@ private void ReadIniFile() LogMessage("Cumulus start date: " + RecordsBeganDate); - ImetOptions.ImetWaitTime = ini.GetValue("Station", "ImetWaitTime", 500); // delay to wait for a reply to a command - ImetOptions.ImetReadDelay = ini.GetValue("Station", "ImetReadDelay", 500); // delay between sending read live data commands - ImetOptions.ImetUpdateLogPointer = ini.GetValue("Station", "ImetUpdateLogPointer", true); // keep the logger pointer pointing at last data read - ImetOptions.ImetBaudRate = ini.GetValue("Station", "ImetOptions.ImetBaudRate", 19200); + ImetOptions.WaitTime = ini.GetValue("Station", "ImetWaitTime", 500); // delay to wait for a reply to a command + ImetOptions.ReadDelay = ini.GetValue("Station", "ImetReadDelay", 500); // delay between sending read live data commands + ImetOptions.UpdateLogPointer = ini.GetValue("Station", "ImetUpdateLogPointer", true); // keep the logger pointer pointing at last data read + ImetOptions.BaudRate = ini.GetValue("Station", "ImetBaudRate", 19200); // Check we have a valid value - if (!ImetOptions.BaudRates.Contains(ImetOptions.ImetBaudRate)) + if (!ImetOptions.BaudRates.Contains(ImetOptions.BaudRate)) { // nope, that isn't allowed, set the default - LogMessage("Error, the value for ImetOptions.ImetBaudRate in the ini file " + ImetOptions.ImetBaudRate + " is not valid, using default 19200."); - ImetOptions.ImetBaudRate = 19200; + LogMessage("Error, the value for ImetOptions.ImetBaudRate in the ini file " + ImetOptions.BaudRate + " is not valid, using default 19200."); + ImetOptions.BaudRate = 19200; } UseDataLogger = ini.GetValue("Station", "UseDataLogger", true); @@ -4583,13 +4584,14 @@ internal void WriteIniFile() //ini.SetValue("Station", "RestartIfDataStops", RestartIfDataStops); ini.SetValue("Station", "SyncDavisClock", StationOptions.SyncTime); ini.SetValue("Station", "ClockSettingHour", StationOptions.ClockSettingHour); - ini.SetValue("Station", "SyncFOReads", FineOffsetOptions.FineOffsetSyncReads); ini.SetValue("Station", "WS2300IgnoreStationClock", StationOptions.WS2300IgnoreStationClock); ini.SetValue("Station", "LogExtraSensors", StationOptions.LogExtraSensors); ini.SetValue("Station", "DataLogInterval", DataLogInterval); - ini.SetValue("Station", "FOReadAvoidPeriod", FineOffsetOptions.FineOffsetReadAvoidPeriod); - ini.SetValue("Station", "FineOffsetReadTime", FineOffsetOptions.FineOffsetReadTime); + ini.SetValue("Station", "SyncFOReads", FineOffsetOptions.SyncReads); + ini.SetValue("Station", "FOReadAvoidPeriod", FineOffsetOptions.ReadAvoidPeriod); + ini.SetValue("Station", "FineOffsetReadTime", FineOffsetOptions.ReadTime); + ini.SetValue("Station", "FineOffsetSetLoggerInterval", FineOffsetOptions.SetLoggerInterval); ini.SetValue("Station", "VendorID", FineOffsetOptions.VendorID); ini.SetValue("Station", "ProductID", FineOffsetOptions.ProductID); @@ -4649,10 +4651,10 @@ internal void WriteIniFile() ini.SetValue("Station", "ErrorLogSpikeRemoval", ErrorLogSpikeRemoval); - ini.SetValue("Station", "ImetOptions.ImetBaudRate", ImetOptions.ImetBaudRate); - ini.SetValue("Station", "ImetWaitTime", ImetOptions.ImetWaitTime); // delay to wait for a reply to a command - ini.SetValue("Station", "ImetReadDelay", ImetOptions.ImetReadDelay); // delay between sending read live data commands - ini.SetValue("Station", "ImetUpdateLogPointer", ImetOptions.ImetUpdateLogPointer); // keep the logger pointer pointing at last data read + ini.SetValue("Station", "ImetBaudRate", ImetOptions.BaudRate); + ini.SetValue("Station", "ImetWaitTime", ImetOptions.WaitTime); // delay to wait for a reply to a command + ini.SetValue("Station", "ImetReadDelay", ImetOptions.ReadDelay); // delay between sending read live data commands + ini.SetValue("Station", "ImetUpdateLogPointer", ImetOptions.UpdateLogPointer); // keep the logger pointer pointing at last data read ini.SetValue("Station", "RG11Enabled", RG11Enabled); ini.SetValue("Station", "RG11portName", RG11Port); @@ -9455,9 +9457,10 @@ public class DavisOptions public class FineOffsetOptions { - public bool FineOffsetSyncReads { get; set; } - public int FineOffsetReadAvoidPeriod { get; set; } - public int FineOffsetReadTime { get; set; } + public bool SyncReads { get; set; } + public int ReadAvoidPeriod { get; set; } + public int ReadTime { get; set; } + public bool SetLoggerInterval { get; set; } public int VendorID { get; set; } public int ProductID { get; set; } } @@ -9465,10 +9468,10 @@ public class FineOffsetOptions public class ImetOptions { public List BaudRates { get; set; } - public int ImetBaudRate { get; set; } - public int ImetWaitTime { get; set; } - public int ImetReadDelay { get; set; } - public bool ImetUpdateLogPointer { get; set; } + public int BaudRate { get; set; } + public int WaitTime { get; set; } + public int ReadDelay { get; set; } + public bool UpdateLogPointer { get; set; } } public class EasyWeatherOptions diff --git a/CumulusMX/FOStation.cs b/CumulusMX/FOStation.cs index e3377e59..45b0d849 100644 --- a/CumulusMX/FOStation.cs +++ b/CumulusMX/FOStation.cs @@ -20,7 +20,7 @@ internal class FOStation : WeatherStation private HidStream stream; private List datalist; - private readonly int maxHistoryEntries; + //private readonly int maxHistoryEntries; private int prevaddr = -1; private int prevraintotal = -1; private int ignoreraincount; @@ -61,13 +61,13 @@ internal FOStation(Cumulus cumulus) : base(cumulus) { foEntrysize = 0x14; foMaxAddr = 0xFFEC; - maxHistoryEntries = 3264; + //maxHistoryEntries = 3264; } else { foEntrysize = 0x10; foMaxAddr = 0xFFF0; - maxHistoryEntries = 4080; + //maxHistoryEntries = 4080; } do @@ -85,34 +85,43 @@ internal FOStation(Cumulus cumulus) : base(cumulus) var msg = $"Warning, your console logging interval ({logint} mins) does not match the Cumulus logging interval ({cumulus.logints[cumulus.DataLogInterval]} mins)"; cumulus.LogConsoleMessage(msg); cumulus.LogMessage(msg); + if (cumulus.FineOffsetOptions.SetLoggerInterval) + { + WriteAddress(0x10, (byte)cumulus.logints[cumulus.DataLogInterval]); // write the logging new logging interval + WriteAddress(0x1A, 0xAA); // tell the station to read the new parameter + do + { + Thread.Sleep(1000); // sleep to let it reconfigure + ReadAddress(0x10, data); + } while (data[9] != 0); + } } } // Get the block of data containing the abs and rel pressures cumulus.LogMessage("Reading station pressure offset"); - if (ReadAddress(0x20, data)) + double relpressure = (((data[17] & 0x3f) * 256) + data[16]) / 10.0f; + double abspressure = (((data[19] & 0x3f) * 256) + data[18]) / 10.0f; + pressureOffset = relpressure - abspressure; + cumulus.LogMessage("Rel pressure = " + relpressure); + cumulus.LogMessage("Abs pressure = " + abspressure); + cumulus.LogMessage("Calculated Offset = " + pressureOffset); + if (cumulus.EwOptions.PressOffset < 9999.0) { - double relpressure = (((data[1] & 0x3f) * 256) + data[0]) / 10.0f; - double abspressure = (((data[3] & 0x3f) * 256) + data[2]) / 10.0f; - pressureOffset = relpressure - abspressure; - cumulus.LogMessage("Rel pressure = " + relpressure); - cumulus.LogMessage("Abs pressure = " + abspressure); - cumulus.LogMessage("Calculated Offset = " + pressureOffset); - if (cumulus.EwOptions.PressOffset < 9999.0) - { - cumulus.LogMessage("Ignoring calculated offset, using offset value from cumulus.ini file"); - cumulus.LogMessage("EWpressureoffset = " + cumulus.EwOptions.PressOffset); - pressureOffset = cumulus.EwOptions.PressOffset; - } - - // Read the data from the logger - startReadingHistoryData(); + cumulus.LogMessage("Ignoring calculated offset, using offset value from cumulus.ini file"); + cumulus.LogMessage("EWpressureoffset = " + cumulus.EwOptions.PressOffset); + pressureOffset = cumulus.EwOptions.PressOffset; } - } - // pause for 10 seconds then try again - Thread.Sleep(10000); + // Read the data from the logger + startReadingHistoryData(); + } + else + { + // pause for 10 seconds then try again + Thread.Sleep(10000); + } } while (hidDevice == null || stream == null || !stream.CanRead); } @@ -181,6 +190,7 @@ public override void getAndProcessHistoryData() DateTime timestamp = DateTime.Now; //LastUpdateTime = DateTime.Now; // lastArchiveTimeUTC.ToLocalTime(); cumulus.LogMessage("Last Update = " + cumulus.LastUpdateTime); + cumulus.LogDebugMessage("Reading fixed memory block"); if (!ReadAddress(0, data)) { return; @@ -188,10 +198,11 @@ public override void getAndProcessHistoryData() // get address of current location int addr = data[31] * 256 + data[30]; - //int previousaddress = addr; + int previousaddress = addr; // get the number of logger entries the console has recorded int logEntries = data[28] * 256 + data[27]; + cumulus.LogDebugMessage($"Console has {logEntries} log entries"); cumulus.LogMessage("Reading current address " + addr.ToString("X4")); if (!ReadAddress(addr, data)) @@ -207,38 +218,53 @@ public override void getAndProcessHistoryData() { followinginterval = interval; interval = data[0]; + cumulus.LogDebugMessage($"This logger record interval = {interval} mins"); // calculate timestamp of previous history data timestamp = timestamp.AddMinutes(-interval); if ((interval != 255) && (timestamp > cumulus.LastUpdateTime) && (datalist.Count < logEntries)) { + // Test if the current address has changed + cumulus.LogDebugMessage("Reading fixed memory block"); + if (!ReadAddress(0, data)) + { + return; + } + var newAddr = data[31] * 256 + data[30]; + if (newAddr != previousaddress) + { + // The current logger address has changed, pause to allow console to sort itself out + cumulus.LogDebugMessage("Console logger location changed, pausing for a sort while"); + previousaddress = newAddr; + Thread.Sleep(2000); + } + // Read previous data addr -= foEntrysize; - if (addr < 0x100) addr = foMaxAddr; // wrap around + if (addr < 0x100) + { + addr = foMaxAddr; // wrap around + } + cumulus.LogMessage("Read logger entry for " + timestamp + " address " + addr.ToString("X4")); if (!ReadAddress(addr, data)) { return; } + cumulus.LogDebugMessage("Logger Data block: " + BitConverter.ToString(data, 0, foEntrysize)); // add history data to collection var histData = new HistoryData(); - string msg = "Read logger entry for " + timestamp + " address " + addr.ToString("X4"); - int numBytes = hasSolar ? 20 : 16; - - cumulus.LogMessage(msg); - - cumulus.LogDataMessage("Data block: " + BitConverter.ToString(data, 0, numBytes)); histData.timestamp = timestamp; histData.interval = interval; histData.followinginterval = followinginterval; histData.inHum = data[1] == 255 ? 10 : data[1]; histData.outHum = data[4] == 255 ? 10 : data[4]; - double outtemp = ((data[5]) + (data[6] & 0x7F)*256)/10.0f; + double outtemp = (data[5] + (data[6] & 0x7F)*256)/10.0f; var sign = (byte) (data[6] & 0x80); if (sign == 0x80) outtemp = -outtemp; if (outtemp > -200) histData.outTemp = outtemp; @@ -248,7 +274,7 @@ public override void getAndProcessHistoryData() histData.rainCounter = data[13] + (data[14]*256); - double intemp = ((data[2]) + (data[3] & 0x7F)*256)/10.0f; + double intemp = (data[2] + (data[3] & 0x7F)*256)/10.0f; sign = (byte) (data[3] & 0x80); if (sign == 0x80) intemp = -intemp; histData.inTemp = intemp; @@ -271,6 +297,7 @@ public override void getAndProcessHistoryData() } } + cumulus.LogMessage("Completed read of history data from the console"); cumulus.LogMessage("Number of history entries = " + datalist.Count); if (datalist.Count > 0) @@ -650,7 +677,7 @@ private bool ReadAddress(int address, byte[] buff) return false; } - Thread.Sleep(cumulus.FineOffsetOptions.FineOffsetReadTime); + Thread.Sleep(cumulus.FineOffsetOptions.ReadTime); for (int i = 1; i < 5; i++) { //cumulus.LogMessage("Reading 8 bytes"); @@ -678,6 +705,36 @@ private bool ReadAddress(int address, byte[] buff) return true; } + private bool WriteAddress(int address, byte val) + { + var addrlowbyte = (byte)(address & 0xFF); + var addrhighbyte = (byte)(address >> 8); + + var request = new byte[] { 0, 0xa2, addrhighbyte, addrlowbyte, 0x20, 0xa2, val, 0, 0x20 }; + + if (hidDevice == null) + { + return false; + } + + //response = device.WriteRead(0x00, request); + try + { + stream.Write(request); + } + catch (Exception ex) + { + cumulus.LogConsoleMessage("Error sending command to station - it may need resetting"); + cumulus.LogMessage(ex.Message); + cumulus.LogMessage("Error sending command to station - it may need resetting"); + DataStopped = true; + cumulus.DataStoppedAlarm.Triggered = true; + return false; + } + + return true; + } + private void DataReadTimerTick(object state, ElapsedEventArgs elapsedEventArgs) { if (DataStopped) @@ -741,7 +798,7 @@ private void GetAndProcessData() var data = new byte[32]; - if (cumulus.FineOffsetOptions.FineOffsetSyncReads && !synchronising) + if (cumulus.FineOffsetOptions.SyncReads && !synchronising) { if ((DateTime.Now - FOSensorClockTime).TotalDays > 1) { @@ -753,21 +810,21 @@ private void GetAndProcessData() } // Check that were not within N seconds of the station updating memory - bool sensorclockOK = ((int)(Math.Floor((DateTime.Now - FOSensorClockTime).TotalSeconds))%48 >= (cumulus.FineOffsetOptions.FineOffsetReadAvoidPeriod - 1)) && - ((int)(Math.Floor((DateTime.Now - FOSensorClockTime).TotalSeconds))%48 <= (47 - cumulus.FineOffsetOptions.FineOffsetReadAvoidPeriod)); - bool stationclockOK = ((int)(Math.Floor((DateTime.Now - FOStationClockTime).TotalSeconds))%60 >= (cumulus.FineOffsetOptions.FineOffsetReadAvoidPeriod - 1)) && - ((int)(Math.Floor((DateTime.Now - FOStationClockTime).TotalSeconds))%60 <= (59 - cumulus.FineOffsetOptions.FineOffsetReadAvoidPeriod)); + bool sensorclockOK = ((int)(Math.Floor((DateTime.Now - FOSensorClockTime).TotalSeconds))%48 >= (cumulus.FineOffsetOptions.ReadAvoidPeriod - 1)) && + ((int)(Math.Floor((DateTime.Now - FOSensorClockTime).TotalSeconds))%48 <= (47 - cumulus.FineOffsetOptions.ReadAvoidPeriod)); + bool stationclockOK = ((int)(Math.Floor((DateTime.Now - FOStationClockTime).TotalSeconds))%60 >= (cumulus.FineOffsetOptions.ReadAvoidPeriod - 1)) && + ((int)(Math.Floor((DateTime.Now - FOStationClockTime).TotalSeconds))%60 <= (59 - cumulus.FineOffsetOptions.ReadAvoidPeriod)); if (!sensorclockOK || !stationclockOK) { if (!sensorclockOK) { - cumulus.LogDebugMessage("Within "+cumulus.FineOffsetOptions.FineOffsetReadAvoidPeriod +" seconds of sensor data change, skipping read"); + cumulus.LogDebugMessage("Within "+cumulus.FineOffsetOptions.ReadAvoidPeriod +" seconds of sensor data change, skipping read"); } if (!stationclockOK) { - cumulus.LogDebugMessage("Within " + cumulus.FineOffsetOptions.FineOffsetReadAvoidPeriod + " seconds of station clock minute change, skipping read"); + cumulus.LogDebugMessage("Within " + cumulus.FineOffsetOptions.ReadAvoidPeriod + " seconds of station clock minute change, skipping read"); } return; @@ -784,7 +841,7 @@ private void GetAndProcessData() int addr = (data[31]*256) + data[30]; - cumulus.LogDataMessage("First block read, addr = " + addr.ToString("X8")); + cumulus.LogDataMessage("First block read, addr = " + addr.ToString("X4")); if (addr != prevaddr) { @@ -804,7 +861,7 @@ private void GetAndProcessData() } else { - cumulus.LogDataMessage("Reading data, addr = " + addr.ToString("X8")); + cumulus.LogDataMessage("Reading data, addr = " + addr.ToString("X4")); if (!ReadAddress(addr, data)) { diff --git a/CumulusMX/ImetStation.cs b/CumulusMX/ImetStation.cs index 05691410..c184d50e 100644 --- a/CumulusMX/ImetStation.cs +++ b/CumulusMX/ImetStation.cs @@ -22,10 +22,10 @@ internal class ImetStation : WeatherStation public ImetStation(Cumulus cumulus) : base(cumulus) { cumulus.Manufacturer = cumulus.INSTROMET; - cumulus.LogMessage("ImetUpdateLogPointer=" + cumulus.ImetOptions.ImetUpdateLogPointer); - cumulus.LogMessage("ImetWaitTime=" + cumulus.ImetOptions.ImetWaitTime); - cumulus.LogMessage("ImetReadDelay=" + cumulus.ImetOptions.ImetReadDelay); - cumulus.LogMessage("ImetOptions.ImetBaudRate=" + cumulus.ImetOptions.ImetBaudRate); + cumulus.LogMessage("ImetUpdateLogPointer=" + cumulus.ImetOptions.UpdateLogPointer); + cumulus.LogMessage("ImetWaitTime=" + cumulus.ImetOptions.WaitTime); + cumulus.LogMessage("ImetReadDelay=" + cumulus.ImetOptions.ReadDelay); + cumulus.LogMessage("ImetBaudRate=" + cumulus.ImetOptions.BaudRate); cumulus.LogMessage("Instromet: Attempting to open " + cumulus.ComportName); calculaterainrate = true; @@ -36,7 +36,7 @@ public ImetStation(Cumulus cumulus) : base(cumulus) cumulus.RainDPlaceDefaults[1] = 3; // in cumulus.RainFormat = cumulus.SunFormat = "F2"; - comport = new SerialPort(cumulus.ComportName, cumulus.ImetOptions.ImetBaudRate, Parity.None, 8, StopBits.One) {Handshake = Handshake.None, RtsEnable = true, DtrEnable = true}; + comport = new SerialPort(cumulus.ComportName, cumulus.ImetOptions.BaudRate, Parity.None, 8, StopBits.One) {Handshake = Handshake.None, RtsEnable = true, DtrEnable = true}; try { @@ -247,7 +247,7 @@ private void SendCommand(string command) } finally { - Thread.Sleep(cumulus.ImetOptions.ImetWaitTime); + Thread.Sleep(cumulus.ImetOptions.WaitTime); } } @@ -834,7 +834,7 @@ public override void Start() } else { - Thread.Sleep(cumulus.ImetOptions.ImetReadDelay); + Thread.Sleep(cumulus.ImetOptions.ReadDelay); } } } @@ -1013,7 +1013,7 @@ private void ImetGetData() cumulus.LogMessage(response); } - if (!cumulus.ImetOptions.ImetUpdateLogPointer || stop) + if (!cumulus.ImetOptions.UpdateLogPointer || stop) return; // Keep the log pointer current, to avoid large numbers of logs diff --git a/CumulusMX/StationSettings.cs b/CumulusMX/StationSettings.cs index 872b42ae..91159a50 100644 --- a/CumulusMX/StationSettings.cs +++ b/CumulusMX/StationSettings.cs @@ -131,15 +131,16 @@ internal string GetAlpacaFormData() var fineoffsetadvanced = new JsonStationSettingsFineOffsetAdvanced() { - readtime = cumulus.FineOffsetOptions.FineOffsetReadTime, + readtime = cumulus.FineOffsetOptions.ReadTime, + setlogger = cumulus.FineOffsetOptions.SetLoggerInterval, vid = cumulus.FineOffsetOptions.VendorID, pid = cumulus.FineOffsetOptions.ProductID }; var fineoffset = new JsonStationSettingsFineOffset() { - syncreads = cumulus.FineOffsetOptions.FineOffsetSyncReads, - readavoid = cumulus.FineOffsetOptions.FineOffsetReadAvoidPeriod, + syncreads = cumulus.FineOffsetOptions.SyncReads, + readavoid = cumulus.FineOffsetOptions.ReadAvoidPeriod, advanced = fineoffsetadvanced }; @@ -162,15 +163,15 @@ internal string GetAlpacaFormData() { syncstationclock = cumulus.StationOptions.SyncTime, syncclockhour = cumulus.StationOptions.ClockSettingHour, - readdelay = cumulus.ImetOptions.ImetReadDelay, - waittime = cumulus.ImetOptions.ImetWaitTime, - updatelogpointer = cumulus.ImetOptions.ImetUpdateLogPointer + readdelay = cumulus.ImetOptions.ReadDelay, + waittime = cumulus.ImetOptions.WaitTime, + updatelogpointer = cumulus.ImetOptions.UpdateLogPointer }; var imet = new JsonStationSettingsImet() { comportname = cumulus.ComportName, - baudrate = cumulus.ImetOptions.ImetBaudRate, + baudrate = cumulus.ImetOptions.BaudRate, advanced = imetAdvanced }; @@ -915,9 +916,10 @@ internal string UpdateConfig(IHttpContext context) { if (settings.fineoffset != null) { - cumulus.FineOffsetOptions.FineOffsetSyncReads = settings.fineoffset.syncreads; - cumulus.FineOffsetOptions.FineOffsetReadAvoidPeriod = settings.fineoffset.readavoid; - cumulus.FineOffsetOptions.FineOffsetReadTime = settings.fineoffset.advanced.readtime; + cumulus.FineOffsetOptions.SyncReads = settings.fineoffset.syncreads; + cumulus.FineOffsetOptions.ReadAvoidPeriod = settings.fineoffset.readavoid; + cumulus.FineOffsetOptions.ReadTime = settings.fineoffset.advanced.readtime; + cumulus.FineOffsetOptions.SetLoggerInterval = settings.fineoffset.advanced.setlogger; cumulus.FineOffsetOptions.VendorID = settings.fineoffset.advanced.vid; cumulus.FineOffsetOptions.ProductID = settings.fineoffset.advanced.pid; } @@ -936,12 +938,12 @@ internal string UpdateConfig(IHttpContext context) if (settings.imet != null) { cumulus.ComportName = settings.imet.comportname ?? cumulus.ComportName; - cumulus.ImetOptions.ImetBaudRate = settings.imet.baudrate; + cumulus.ImetOptions.BaudRate = settings.imet.baudrate; cumulus.StationOptions.SyncTime = settings.imet.advanced.syncstationclock; cumulus.StationOptions.ClockSettingHour = settings.imet.advanced.syncclockhour; - cumulus.ImetOptions.ImetReadDelay = settings.imet.advanced.readdelay; - cumulus.ImetOptions.ImetWaitTime = settings.imet.advanced.waittime; - cumulus.ImetOptions.ImetUpdateLogPointer = settings.imet.advanced.updatelogpointer; + cumulus.ImetOptions.ReadDelay = settings.imet.advanced.readdelay; + cumulus.ImetOptions.WaitTime = settings.imet.advanced.waittime; + cumulus.ImetOptions.UpdateLogPointer = settings.imet.advanced.updatelogpointer; } } catch (Exception ex) @@ -1297,6 +1299,7 @@ internal class JsonStationSettingsDavisVp2Advanced internal class JsonStationSettingsFineOffsetAdvanced { public int readtime { get; set; } + public bool setlogger { get; set; } public int vid { get; set; } public int pid { get; set; } } diff --git a/CumulusMX/WeatherStation.cs b/CumulusMX/WeatherStation.cs index 485fa56c..d1dbcaec 100644 --- a/CumulusMX/WeatherStation.cs +++ b/CumulusMX/WeatherStation.cs @@ -601,7 +601,7 @@ public void ReadTodayFile() FOSensorClockTime = ini.GetValue("FineOffset", "FOSensorClockTime", DateTime.MinValue); FOStationClockTime = ini.GetValue("FineOffset", "FOStationClockTime", DateTime.MinValue); - if (cumulus.FineOffsetOptions.FineOffsetSyncReads) + if (cumulus.FineOffsetOptions.SyncReads) { cumulus.LogMessage("Sensor clock " + FOSensorClockTime.ToLongTimeString()); cumulus.LogMessage("Station clock " + FOStationClockTime.ToLongTimeString()); diff --git a/Updates.txt b/Updates.txt index 7b8b9fe4..a0c4a409 100644 --- a/Updates.txt +++ b/Updates.txt @@ -42,6 +42,7 @@ - New: The graphs pages - both dashboard and default web site - now set a page hash value depending on the graph being shown. Benefit = you can now link directly to a particular graph to show on page load - New: FineOffset stations now report a warning if the console logger interval does match the Cumulus MX interval +- New: FineOffset experimental feature to set the console logger interval to match Cumulus logging interval - Changed: Moved FTP Logging option from Internet Settings to Program Settings to collect all the logging options in one place - Changed: A Data Stopped state now stops all logging, MySQL and web activity @@ -58,6 +59,7 @@ - Changed: The MySQL real time table data retention settings reworked. - Changed: Moved all third party web uploads from Internet Settings page to their own Third Party Settings page - Changed: Internal optimisations +- Changed: JQuery library updated to v3.6.0 for both the Dashboard interface, and the default web site From ae4ae91ce7e390792a53820bd94464c1ea05c83a Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Thu, 29 Apr 2021 23:14:37 +0100 Subject: [PATCH 12/16] Change MySQL connection string to use the connection string builder --- CumulusMX/Cumulus.cs | 49 ++++++++-------- CumulusMX/InternetSettings.cs | 2 + CumulusMX/MysqlSettings.cs | 83 ++++++++++++++-------------- CumulusMX/Properties/AssemblyInfo.cs | 6 +- CumulusMX/WeatherStation.cs | 2 +- 5 files changed, 70 insertions(+), 72 deletions(-) diff --git a/CumulusMX/Cumulus.cs b/CumulusMX/Cumulus.cs index 0a02a007..5fb8f0cd 100644 --- a/CumulusMX/Cumulus.cs +++ b/CumulusMX/Cumulus.cs @@ -702,11 +702,8 @@ public struct MqttSettings public MySqlCommand CustomMysqlMinutesCommand = new MySqlCommand(); public MySqlConnection CustomMysqlRolloverConn = new MySqlConnection(); public MySqlCommand CustomMysqlRolloverCommand = new MySqlCommand(); - public string MySqlHost; - public int MySqlPort; - public string MySqlUser; - public string MySqlPass; - public string MySqlDatabase; + + public MySqlConnectionStringBuilder MySqlConnSettings = new MySqlConnectionStringBuilder(); public string LatestBuild = "n/a"; @@ -1224,11 +1221,9 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) SetUpHttpProxy(); - var sqlConnectionString = $"server={MySqlHost};port={MySqlPort};user={MySqlUser};password={MySqlPass};database={MySqlDatabase}"; - if (MonthlyMySqlEnabled) { - MonthlyMySqlConn.ConnectionString = sqlConnectionString; + MonthlyMySqlConn.ConnectionString = MySqlConnSettings.ToString(); SetStartOfMonthlyInsertSQL(); } @@ -1240,23 +1235,23 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) if (RealtimeMySqlEnabled) { - RealtimeSqlConn.ConnectionString = sqlConnectionString; + RealtimeSqlConn.ConnectionString = MySqlConnSettings.ToString(); SetStartOfRealtimeInsertSQL(); } - CustomMysqlSecondsConn.ConnectionString = sqlConnectionString; + CustomMysqlSecondsConn.ConnectionString = MySqlConnSettings.ToString(); customMysqlSecondsTokenParser.OnToken += TokenParserOnToken; CustomMysqlSecondsCommand.Connection = CustomMysqlSecondsConn; CustomMysqlSecondsTimer = new Timer { Interval = CustomMySqlSecondsInterval * 1000 }; CustomMysqlSecondsTimer.Elapsed += CustomMysqlSecondsTimerTick; CustomMysqlSecondsTimer.AutoReset = true; - CustomMysqlMinutesConn.ConnectionString = sqlConnectionString; + CustomMysqlMinutesConn.ConnectionString = MySqlConnSettings.ToString(); customMysqlMinutesTokenParser.OnToken += TokenParserOnToken; CustomMysqlMinutesCommand.Connection = CustomMysqlMinutesConn; - CustomMysqlRolloverConn.ConnectionString = sqlConnectionString; + CustomMysqlRolloverConn.ConnectionString = MySqlConnSettings.ToString(); customMysqlRolloverTokenParser.OnToken += TokenParserOnToken; CustomMysqlRolloverCommand.Connection = CustomMysqlRolloverConn; @@ -3993,6 +3988,8 @@ private void ReadIniFile() GraphHours = ini.GetValue("Graphs", "GraphHours", 72); MoonImageEnabled = ini.GetValue("Graphs", "MoonImageEnabled", false); MoonImageSize = ini.GetValue("Graphs", "MoonImageSize", 100); + if (MoonImageSize < 10) + MoonImageSize = 10; MoonImageFtpDest = ini.GetValue("Graphs", "MoonImageFtpDest", "images/moon.png"); GraphOptions.TempVisible = ini.GetValue("Graphs", "TempVisible", true); GraphOptions.InTempVisible = ini.GetValue("Graphs", "InTempVisible", true); @@ -4441,11 +4438,12 @@ private void ReadIniFile() DisplayOptions.ShowUV = ini.GetValue("Display", "DisplayUvData", false); // MySQL - common - MySqlHost = ini.GetValue("MySQL", "Host", "127.0.0.1"); - MySqlPort = ini.GetValue("MySQL", "Port", 3306); - MySqlUser = ini.GetValue("MySQL", "User", ""); - MySqlPass = ini.GetValue("MySQL", "Pass", ""); - MySqlDatabase = ini.GetValue("MySQL", "Database", "database"); + MySqlConnSettings.Server = ini.GetValue("MySQL", "Host", "127.0.0.1"); + MySqlConnSettings.Port = (uint)ini.GetValue("MySQL", "Port", 3306); + MySqlConnSettings.UserID = ini.GetValue("MySQL", "User", ""); + MySqlConnSettings.Password = ini.GetValue("MySQL", "Pass", ""); + MySqlConnSettings.Database = ini.GetValue("MySQL", "Database", "database"); + // MySQL - monthly log file MonthlyMySqlEnabled = ini.GetValue("MySQL", "MonthlyMySqlEnabled", false); MySqlMonthlyTable = ini.GetValue("MySQL", "MonthlyTable", "Monthly"); @@ -5180,11 +5178,11 @@ internal void WriteIniFile() ini.SetValue("Graphs", "TempSumVisible2", GraphOptions.TempSumVisible2); - ini.SetValue("MySQL", "Host", MySqlHost); - ini.SetValue("MySQL", "Port", MySqlPort); - ini.SetValue("MySQL", "User", MySqlUser); - ini.SetValue("MySQL", "Pass", MySqlPass); - ini.SetValue("MySQL", "Database", MySqlDatabase); + ini.SetValue("MySQL", "Host", MySqlConnSettings.Server); + ini.SetValue("MySQL", "Port", MySqlConnSettings.Port); + ini.SetValue("MySQL", "User", MySqlConnSettings.UserID); + ini.SetValue("MySQL", "Pass", MySqlConnSettings.Password); + ini.SetValue("MySQL", "Database", MySqlConnSettings.Database); ini.SetValue("MySQL", "MonthlyMySqlEnabled", MonthlyMySqlEnabled); ini.SetValue("MySQL", "RealtimeMySqlEnabled", RealtimeMySqlEnabled); ini.SetValue("MySQL", "DayfileMySqlEnabled", DayfileMySqlEnabled); @@ -8507,9 +8505,10 @@ private void MySqlCatchup() { try { - var mySqlConn = new MySqlConnection($"server={MySqlHost};port={MySqlPort};user={MySqlUser};password={MySqlPass};database={MySqlDatabase}"); - - MySqlCommandSync(MySqlList, mySqlConn, "MySQL Archive", true, true); + using (var mySqlConn = new MySqlConnection(MySqlConnSettings.ToString())) + { + MySqlCommandSync(MySqlList, mySqlConn, "MySQL Archive", true, true); + } } catch (Exception ex) { diff --git a/CumulusMX/InternetSettings.cs b/CumulusMX/InternetSettings.cs index ced49f85..abb2eed4 100644 --- a/CumulusMX/InternetSettings.cs +++ b/CumulusMX/InternetSettings.cs @@ -209,6 +209,8 @@ public string UpdateConfig(IHttpContext context) if (cumulus.MoonImageEnabled) { cumulus.MoonImageSize = settings.moonimage.size; + if (cumulus.MoonImageSize < 10) + cumulus.MoonImageSize = 10; cumulus.IncludeMoonImage = settings.moonimage.includemoonimage; if (cumulus.IncludeMoonImage) cumulus.MoonImageFtpDest = settings.moonimage.ftpdest; diff --git a/CumulusMX/MysqlSettings.cs b/CumulusMX/MysqlSettings.cs index 8fdfc538..0e80c646 100644 --- a/CumulusMX/MysqlSettings.cs +++ b/CumulusMX/MysqlSettings.cs @@ -24,11 +24,11 @@ public string GetAlpacaFormData() { var server = new JsonMysqlSettingsServer() { - database = cumulus.MySqlDatabase, - host = cumulus.MySqlHost, - pass = cumulus.MySqlPass, - port = cumulus.MySqlPort, - user = cumulus.MySqlUser + database = cumulus.MySqlConnSettings.Database, + host = cumulus.MySqlConnSettings.Server, + pass = cumulus.MySqlConnSettings.Password, + port = cumulus.MySqlConnSettings.Port, + user = cumulus.MySqlConnSettings.UserID }; var monthly = new JsonMysqlSettingsMonthly() @@ -138,18 +138,18 @@ public object UpdateConfig(IHttpContext context) cumulus.LogMessage("Updating MySQL settings"); // server - cumulus.MySqlHost = settings.server.host; + cumulus.MySqlConnSettings.Server = settings.server.host; if (settings.server.port > 0 && settings.server.port < 65536) { - cumulus.MySqlPort = settings.server.port; + cumulus.MySqlConnSettings.Port = settings.server.port; } else { - cumulus.MySqlPort = 3306; + cumulus.MySqlConnSettings.Port = 3306; } - cumulus.MySqlDatabase = settings.server.database; - cumulus.MySqlUser = settings.server.user; - cumulus.MySqlPass = settings.server.pass; + cumulus.MySqlConnSettings.Database = settings.server.database; + cumulus.MySqlConnSettings.UserID = settings.server.user; + cumulus.MySqlConnSettings.Password = settings.server.pass; //monthly cumulus.MonthlyMySqlEnabled = settings.monthenabled; if (cumulus.MonthlyMySqlEnabled) @@ -201,9 +201,7 @@ public object UpdateConfig(IHttpContext context) // Save the settings cumulus.WriteIniFile(); - var connStr = $"server={cumulus.MySqlHost};port={cumulus.MySqlPort};user={cumulus.MySqlUser};password={cumulus.MySqlPass};database={cumulus.MySqlDatabase}"; - - cumulus.MonthlyMySqlConn.ConnectionString = connStr; + cumulus.MonthlyMySqlConn.ConnectionString = cumulus.MySqlConnSettings.ToString(); cumulus.SetMonthlySqlCreateString(); cumulus.SetStartOfMonthlyInsertSQL(); @@ -211,18 +209,18 @@ public object UpdateConfig(IHttpContext context) cumulus.SetDayfileSqlCreateString(); cumulus.SetStartOfDayfileInsertSQL(); - cumulus.RealtimeSqlConn.ConnectionString = connStr; + cumulus.RealtimeSqlConn.ConnectionString = cumulus.MySqlConnSettings.ToString(); cumulus.SetRealtimeSqlCreateString(); cumulus.SetStartOfRealtimeInsertSQL(); - cumulus.CustomMysqlSecondsConn.ConnectionString = connStr; + cumulus.CustomMysqlSecondsConn.ConnectionString = cumulus.MySqlConnSettings.ToString(); cumulus.CustomMysqlSecondsTimer.Interval = cumulus.CustomMySqlSecondsInterval*1000; cumulus.CustomMysqlSecondsTimer.Enabled = cumulus.CustomMySqlSecondsEnabled; - cumulus.CustomMysqlMinutesConn.ConnectionString = connStr; + cumulus.CustomMysqlMinutesConn.ConnectionString = cumulus.MySqlConnSettings.ToString(); - cumulus.CustomMysqlRolloverConn.ConnectionString = connStr; + cumulus.CustomMysqlRolloverConn.ConnectionString = cumulus.MySqlConnSettings.ToString(); context.Response.StatusCode = 200; } @@ -238,36 +236,35 @@ public object UpdateConfig(IHttpContext context) private string CreateMySQLTable(string createSQL) { - var mySqlConn = new MySqlConnection($"server={cumulus.MySqlHost};port={cumulus.MySqlPort};user={cumulus.MySqlUser};password={cumulus.MySqlPass};database={cumulus.MySqlDatabase}"); - - MySqlCommand cmd = new MySqlCommand(createSQL, mySqlConn); - cumulus.LogMessage($"MySQL Create Table: {createSQL}"); - string res; - - try - { - mySqlConn.Open(); - int aff = cmd.ExecuteNonQuery(); - cumulus.LogMessage($"MySQL Create Table: {aff} items were affected."); - res = "Database table created successfully"; - } - catch (Exception ex) - { - cumulus.LogMessage("MySQL Create Table: Error encountered during MySQL operation."); - cumulus.LogMessage(ex.Message); - res = "Error: " + ex.Message; - } - finally + using (var mySqlConn = new MySqlConnection(cumulus.MySqlConnSettings.ToString())) + using (MySqlCommand cmd = new MySqlCommand(createSQL, mySqlConn)) { + cumulus.LogMessage($"MySQL Create Table: {createSQL}"); + try { - mySqlConn.Close(); + mySqlConn.Open(); + int aff = cmd.ExecuteNonQuery(); + cumulus.LogMessage($"MySQL Create Table: {aff} items were affected."); + res = "Database table created successfully"; + } + catch (Exception ex) + { + cumulus.LogMessage("MySQL Create Table: Error encountered during MySQL operation."); + cumulus.LogMessage(ex.Message); + res = "Error: " + ex.Message; + } + finally + { + try + { + mySqlConn.Close(); + } + catch + { } } - catch - {} } - return res; } @@ -316,7 +313,7 @@ public class JsonMysqlSettings public class JsonMysqlSettingsServer { public string host { get; set; } - public int port { get; set; } + public uint port { get; set; } public string user { get; set; } public string pass { get; set; } public string database { get; set; } diff --git a/CumulusMX/Properties/AssemblyInfo.cs b/CumulusMX/Properties/AssemblyInfo.cs index e0132477..63b34678 100644 --- a/CumulusMX/Properties/AssemblyInfo.cs +++ b/CumulusMX/Properties/AssemblyInfo.cs @@ -6,7 +6,7 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Cumulus MX")] -[assembly: AssemblyDescription("Version 3.11.0 - Build 3126 - BETA")] +[assembly: AssemblyDescription("Version 3.11.0 - Build 3127 - BETA")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Cumulus MX")] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("3.11.0.3126")] -[assembly: AssemblyFileVersion("3.11.0.3126")] +[assembly: AssemblyVersion("3.11.0.3127")] +[assembly: AssemblyFileVersion("3.11.0.3127")] diff --git a/CumulusMX/WeatherStation.cs b/CumulusMX/WeatherStation.cs index d1dbcaec..108c4595 100644 --- a/CumulusMX/WeatherStation.cs +++ b/CumulusMX/WeatherStation.cs @@ -5462,7 +5462,7 @@ private void DoDayfile(DateTime timestamp) if (cumulus.DayfileMySqlEnabled) { - var mySqlConn = new MySqlConnection($"server={cumulus.MySqlHost};port={cumulus.MySqlPort};user={cumulus.MySqlUser};password={cumulus.MySqlPass};database={cumulus.MySqlDatabase}"); + var mySqlConn = new MySqlConnection(cumulus.MySqlConnSettings.ToString()); var InvC = new CultureInfo(""); From 9ddc71b8dd6301ff07b313babbd4d2471556719e Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Sun, 2 May 2021 11:34:30 +0100 Subject: [PATCH 13/16] SMTP and MySQL fixes --- CumulusMX/Cumulus.cs | 4 ++-- CumulusMX/EmailSender.cs | 7 +++---- CumulusMX/InternetSettings.cs | 6 +++--- Updates.txt | 21 ++++++++++++++++++++- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/CumulusMX/Cumulus.cs b/CumulusMX/Cumulus.cs index 5fb8f0cd..9f66331c 100644 --- a/CumulusMX/Cumulus.cs +++ b/CumulusMX/Cumulus.cs @@ -4509,7 +4509,7 @@ private void ReadIniFile() SmtpOptions.Enabled = ini.GetValue("SMTP", "Enabled", false); SmtpOptions.Server = ini.GetValue("SMTP", "ServerName", ""); SmtpOptions.Port = ini.GetValue("SMTP", "Port", 587); - SmtpOptions.UseSsl = ini.GetValue("SMTP", "UseSSL", false); + SmtpOptions.SslOption = ini.GetValue("SMTP", "SSLOption", 1); SmtpOptions.RequiresAuthentication = ini.GetValue("SMTP", "RequiresAuthentication", false); SmtpOptions.User = ini.GetValue("SMTP", "User", ""); SmtpOptions.Password = ini.GetValue("SMTP", "Password", ""); @@ -5222,7 +5222,7 @@ internal void WriteIniFile() ini.SetValue("SMTP", "Enabled", SmtpOptions.Enabled); ini.SetValue("SMTP", "ServerName", SmtpOptions.Server); ini.SetValue("SMTP", "Port", SmtpOptions.Port); - ini.SetValue("SMTP", "UseSSL", SmtpOptions.UseSsl); + ini.SetValue("SMTP", "SSLOption", SmtpOptions.SslOption); ini.SetValue("SMTP", "RequiresAuthentication", SmtpOptions.RequiresAuthentication); ini.SetValue("SMTP", "User", SmtpOptions.User); ini.SetValue("SMTP", "Password", SmtpOptions.Password); diff --git a/CumulusMX/EmailSender.cs b/CumulusMX/EmailSender.cs index 8c368af7..67a5ced3 100644 --- a/CumulusMX/EmailSender.cs +++ b/CumulusMX/EmailSender.cs @@ -48,8 +48,7 @@ public async void SendEmail(string[] to, string from, string subject, string mes using (SmtpClient client = new SmtpClient(cumulus.SmtpOptions.Logging ? new ProtocolLogger("MXdiags/smtp.log") : null)) { - await client.ConnectAsync(cumulus.SmtpOptions.Server, cumulus.SmtpOptions.Port, MailKit.Security.SecureSocketOptions.StartTlsWhenAvailable); - //client.Connect(cumulus.SmtpOptions.Server, cumulus.SmtpOptions.Port, MailKit.Security.SecureSocketOptions.StartTlsWhenAvailable); + await client.ConnectAsync(cumulus.SmtpOptions.Server, cumulus.SmtpOptions.SslOption); // Note: since we don't have an OAuth2 token, disable // the XOAUTH2 authentication mechanism. @@ -98,7 +97,7 @@ public void SendTestEmail(string[] to, string from, string subject, string messa using (SmtpClient client = new SmtpClient(cumulus.SmtpOptions.Logging ? new ProtocolLogger("MXdiags/smtp.log") : null)) { - client.Connect(cumulus.SmtpOptions.Server, cumulus.SmtpOptions.Port, MailKit.Security.SecureSocketOptions.StartTlsWhenAvailable); + client.Connect(cumulus.SmtpOptions.Server, cumulus.SmtpOptions.Port, (MailKit.Security.SecureSocketOptions)cumulus.SmtpOptions.SslOption); //client.Connect(cumulus.SmtpOptions.Server, cumulus.SmtpOptions.Port, MailKit.Security.SecureSocketOptions.StartTlsWhenAvailable); // Note: since we don't have an OAuth2 token, disable @@ -141,7 +140,7 @@ public class SmtpOptions public int Port; public string User; public string Password; - public bool UseSsl; + public int SslOption; public bool RequiresAuthentication; public bool Logging; } diff --git a/CumulusMX/InternetSettings.cs b/CumulusMX/InternetSettings.cs index abb2eed4..dc014771 100644 --- a/CumulusMX/InternetSettings.cs +++ b/CumulusMX/InternetSettings.cs @@ -249,7 +249,7 @@ public string UpdateConfig(IHttpContext context) { cumulus.SmtpOptions.Server = settings.emailsettings.server; cumulus.SmtpOptions.Port = settings.emailsettings.port; - cumulus.SmtpOptions.UseSsl = settings.emailsettings.usessl; + cumulus.SmtpOptions.SslOption = settings.emailsettings.ssloption; cumulus.SmtpOptions.RequiresAuthentication = settings.emailsettings.authenticate; cumulus.SmtpOptions.User = settings.emailsettings.user; cumulus.SmtpOptions.Password = settings.emailsettings.password; @@ -483,7 +483,7 @@ public string GetAlpacaFormData() enabled = cumulus.SmtpOptions.Enabled, server = cumulus.SmtpOptions.Server, port = cumulus.SmtpOptions.Port, - usessl = cumulus.SmtpOptions.UseSsl, + ssloption = cumulus.SmtpOptions.SslOption, authenticate = cumulus.SmtpOptions.RequiresAuthentication, user = cumulus.SmtpOptions.User, password = cumulus.SmtpOptions.Password @@ -761,7 +761,7 @@ public class JsonEmailSettings public bool enabled { get; set; } public string server { get; set; } public int port { get; set; } - public bool usessl { get; set; } + public int ssloption { get; set; } public bool authenticate { get; set; } public string user { get; set; } public string password { get; set; } diff --git a/Updates.txt b/Updates.txt index a0c4a409..e29fb37a 100644 --- a/Updates.txt +++ b/Updates.txt @@ -1,3 +1,22 @@ +b3127 beta changes + +- SNMP: The MX Option "Use SSL/TLS" has changed to allow you to select from all the options available. This means MX does not have to second guess which option + to use, but does make it more complex to configure. + NOTE: After installing this release, YOU MAY HAVE TO ADJUST THIS SETTING, it will revert to "Auto", which should work for most servers + + For example GMail, port 465 is TLS only, port 587 is plain text connection, but then starts TLS + The following options work... + GMail Port: 465 587 + —————————————————————————————————————— + None No No + Auto Yes Yes + TlsOnConnect Yes No + StartTls No Yes + StartTlsWhenAvail No Yes + +- MySQL: Special characters in usernames and passwords are now handled correctly + + 3.11.0 - b3126 —————————————— - Fix: Remove THW Index in /web/websitedataT.json, and default web site index.htm @@ -22,7 +41,7 @@ [SMTP] ServerName= Port=587 - UseSSL=0 + SSLOption=1 RequiresAuthentication=0 User= Password= From 88b8f3e1931e5bdee5cc1de88bcf477f0b44bbc6 Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Tue, 4 May 2021 11:57:34 +0100 Subject: [PATCH 14/16] Alarm email fix. Emails now sent sequentially New JS encoded web tags --- CumulusMX/ApiTagProcessor.cs | 77 ++++++++++------ CumulusMX/EmailSender.cs | 94 ++++++++++++------- CumulusMX/Properties/AssemblyInfo.cs | 6 +- CumulusMX/webtags.cs | 131 +++++++++++++++++++++++---- Updates.txt | 24 ++++- 5 files changed, 243 insertions(+), 89 deletions(-) diff --git a/CumulusMX/ApiTagProcessor.cs b/CumulusMX/ApiTagProcessor.cs index e4060a81..466f993b 100644 --- a/CumulusMX/ApiTagProcessor.cs +++ b/CumulusMX/ApiTagProcessor.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Text; using Unosquare.Labs.EmbedIO; @@ -32,38 +33,47 @@ public string ProcessJson(string query) cumulus.LogDebugMessage("API tag: Processing API JSON tag request"); cumulus.LogDataMessage("API tag: Input string = " + query); - // remove leading "?" and split on "&" - var input = new List(query.Substring(1).Split('&')); - var parms = new Dictionary(); - if (input[0] == "rc") - { - input.RemoveAt(0); - rc = true; - } var output = new StringBuilder("{", query.Length * 2); - foreach(var tag in input) + try { - if (rc) + // remove leading "?" and split on "&" + var input = new List(query.Substring(1).Split('&')); + var parms = new Dictionary(); + if (input[0] == "rc") + { + input.RemoveAt(0); + rc = true; + } + + foreach (var tag in input) { - parms.Add("webtag", tag); - parms.Add("rc", "y"); + if (rc) + { + parms.Add("webtag", tag); + parms.Add("rc", "y"); + } + var val = webtags.GetWebTagText(tag, parms); + output.Append($"\"{tag}\":\"{val}\","); + if (rc) + { + parms.Clear(); + } } - var val = webtags.GetWebTagText(tag, parms); - output.Append($"\"{tag}\":\"{val}\","); - if (rc) + if (output.Length > 1) { - parms.Clear(); + // remove trailing "," + output.Remove(output.Length - 1, 1); } + output.Append("}"); + + cumulus.LogDataMessage("API tag: Output string = " + output); } - if (output.Length > 1) + catch (Exception ex) { - // remove trailing "," - output.Remove(output.Length - 1, 1); + cumulus.LogMessage($"API tag: Error - {ex.Message}"); + output.Append($"\"ERROR\":\"{ex.Message}\"}}"); } - output.Append("}"); - - cumulus.LogDataMessage("API tag: Output string = " + output); return output.ToString(); } @@ -73,17 +83,24 @@ public string ProcessText(IHttpContext context) { cumulus.LogDebugMessage("API tag: Processing API Text tag request"); - var data = new StreamReader(context.Request.InputStream).ReadToEnd(); + try + { + var data = new StreamReader(context.Request.InputStream).ReadToEnd(); - cumulus.LogDataMessage("API tag: Input string = " + data); + cumulus.LogDataMessage("API tag: Input string = " + data); - tokenParser.InputText = data; - var output = tokenParser.ToStringFromString(); + tokenParser.InputText = data; + var output = tokenParser.ToStringFromString(); - cumulus.LogDataMessage("API tag: Output string = " + output); + cumulus.LogDataMessage("API tag: Output string = " + output); - return output; + return output; + } + catch (Exception ex) + { + cumulus.LogMessage($"API tag: Error - {ex.Message}"); + return $"{{\"ERROR\":\"{ex.Message}\"}}"; + } } - } } diff --git a/CumulusMX/EmailSender.cs b/CumulusMX/EmailSender.cs index 67a5ced3..b5cee8ea 100644 --- a/CumulusMX/EmailSender.cs +++ b/CumulusMX/EmailSender.cs @@ -1,22 +1,22 @@ using System; using MailKit.Net.Smtp; -using MailKit.Net.Pop3; using MimeKit; -using System.Threading.Tasks; using System.Text.RegularExpressions; using MailKit; +using System.Threading; namespace CumulusMX { public class EmailSender { static readonly Regex ValidEmailRegex = CreateValidEmailRegex(); - + private static SemaphoreSlim _writeLock; private readonly Cumulus cumulus; public EmailSender(Cumulus cumulus) { this.cumulus = cumulus; + _writeLock = new SemaphoreSlim(1); } @@ -24,6 +24,10 @@ public async void SendEmail(string[] to, string from, string subject, string mes { try { + cumulus.LogDebugMessage($"SendEmail: Waiting for lock..."); + await _writeLock.WaitAsync(); + cumulus.LogDebugMessage($"SendEmail: Has the lock"); + cumulus.LogDebugMessage($"SendEmail: Sending email, to [{string.Join("; ", to)}], subject [{subject}], body [{message}]..."); var m = new MimeMessage(); @@ -48,7 +52,7 @@ public async void SendEmail(string[] to, string from, string subject, string mes using (SmtpClient client = new SmtpClient(cumulus.SmtpOptions.Logging ? new ProtocolLogger("MXdiags/smtp.log") : null)) { - await client.ConnectAsync(cumulus.SmtpOptions.Server, cumulus.SmtpOptions.SslOption); + await client.ConnectAsync(cumulus.SmtpOptions.Server, cumulus.SmtpOptions.Port, (MailKit.Security.SecureSocketOptions)cumulus.SmtpOptions.SslOption); // Note: since we don't have an OAuth2 token, disable // the XOAUTH2 authentication mechanism. @@ -69,49 +73,71 @@ public async void SendEmail(string[] to, string from, string subject, string mes cumulus.LogMessage("SendEmail: Error - " + e); } + finally + { + cumulus.LogDebugMessage($"SendEmail: Releasing lock..."); + _writeLock.Release(); + } } public void SendTestEmail(string[] to, string from, string subject, string message, bool isHTML) { - cumulus.LogDebugMessage($"SendEmail: Sending Test email, to [{string.Join("; ", to)}], subject [{subject}], body [{message}]..."); - - var m = new MimeMessage(); - m.From.Add(new MailboxAddress("", from)); - foreach (var addr in to) + try { - m.To.Add(new MailboxAddress("", addr)); - } - m.Subject = subject; + cumulus.LogDebugMessage($"SendEmail: Waiting for lock..."); + _writeLock.Wait(); + cumulus.LogDebugMessage($"SendEmail: Has the lock"); - BodyBuilder bodyBuilder = new BodyBuilder(); - if (isHTML) - { - bodyBuilder.HtmlBody = message; - } - else - { - bodyBuilder.TextBody = message; - } + cumulus.LogDebugMessage($"SendEmail: Sending Test email, to [{string.Join("; ", to)}], subject [{subject}], body [{message}]..."); - m.Body = bodyBuilder.ToMessageBody(); + var m = new MimeMessage(); + m.From.Add(new MailboxAddress("", from)); + foreach (var addr in to) + { + m.To.Add(new MailboxAddress("", addr)); + } + m.Subject = subject; - using (SmtpClient client = new SmtpClient(cumulus.SmtpOptions.Logging ? new ProtocolLogger("MXdiags/smtp.log") : null)) - { - client.Connect(cumulus.SmtpOptions.Server, cumulus.SmtpOptions.Port, (MailKit.Security.SecureSocketOptions)cumulus.SmtpOptions.SslOption); - //client.Connect(cumulus.SmtpOptions.Server, cumulus.SmtpOptions.Port, MailKit.Security.SecureSocketOptions.StartTlsWhenAvailable); + BodyBuilder bodyBuilder = new BodyBuilder(); + if (isHTML) + { + bodyBuilder.HtmlBody = message; + } + else + { + bodyBuilder.TextBody = message; + } - // Note: since we don't have an OAuth2 token, disable - // the XOAUTH2 authentication mechanism. - client.AuthenticationMechanisms.Remove("XOAUTH2"); + m.Body = bodyBuilder.ToMessageBody(); - if (cumulus.SmtpOptions.RequiresAuthentication) + using (SmtpClient client = new SmtpClient(cumulus.SmtpOptions.Logging ? new ProtocolLogger("MXdiags/smtp.log") : null)) { - client.Authenticate(cumulus.SmtpOptions.User, cumulus.SmtpOptions.Password); - //client.Authenticate(cumulus.SmtpOptions.User, cumulus.SmtpOptions.Password); + client.Connect(cumulus.SmtpOptions.Server, cumulus.SmtpOptions.Port, (MailKit.Security.SecureSocketOptions)cumulus.SmtpOptions.SslOption); + //client.Connect(cumulus.SmtpOptions.Server, cumulus.SmtpOptions.Port, MailKit.Security.SecureSocketOptions.StartTlsWhenAvailable); + + // Note: since we don't have an OAuth2 token, disable + // the XOAUTH2 authentication mechanism. + client.AuthenticationMechanisms.Remove("XOAUTH2"); + + if (cumulus.SmtpOptions.RequiresAuthentication) + { + client.Authenticate(cumulus.SmtpOptions.User, cumulus.SmtpOptions.Password); + //client.Authenticate(cumulus.SmtpOptions.User, cumulus.SmtpOptions.Password); + } + + client.Send(m); + client.Disconnect(true); } + } + catch (Exception e) + { + cumulus.LogMessage("SendEmail: Error - " + e); - client.Send(m); - client.Disconnect(true); + } + finally + { + cumulus.LogDebugMessage($"SendEmail: Releasing lock..."); + _writeLock.Release(); } } diff --git a/CumulusMX/Properties/AssemblyInfo.cs b/CumulusMX/Properties/AssemblyInfo.cs index 63b34678..6a5960ad 100644 --- a/CumulusMX/Properties/AssemblyInfo.cs +++ b/CumulusMX/Properties/AssemblyInfo.cs @@ -6,7 +6,7 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Cumulus MX")] -[assembly: AssemblyDescription("Version 3.11.0 - Build 3127 - BETA")] +[assembly: AssemblyDescription("Version 3.11.0 - Build 3128 - BETA")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Cumulus MX")] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("3.11.0.3127")] -[assembly: AssemblyFileVersion("3.11.0.3127")] +[assembly: AssemblyVersion("3.11.0.3128")] +[assembly: AssemblyFileVersion("3.11.0.3128")] diff --git a/CumulusMX/webtags.cs b/CumulusMX/webtags.cs index 4afde0b8..6f957fa1 100644 --- a/CumulusMX/webtags.cs +++ b/CumulusMX/webtags.cs @@ -182,10 +182,16 @@ private int GetSnowFalling(DateTime day) private static string EncodeForWeb(string aStr) { - string result = HttpUtility.HtmlEncode(aStr); - return result; + return HttpUtility.HtmlEncode(aStr); + } + + private static string EncodeForJs(string aStr) + { + var str = HttpUtility.HtmlDecode(aStr); + return HttpUtility.JavaScriptStringEncode(str); } + private DateTime GetRecentTs(Dictionary tagParams) { var daysagostr = tagParams.Get("d"); @@ -611,14 +617,24 @@ private static string TagMinute(Dictionary tagParams) return DateTime.Now.ToString("mm"); } + private string Tagforecastnumber(Dictionary tagParams) + { + return station.Forecastnumber.ToString(); + } + private string Tagforecast(Dictionary tagParams) { return station.forecaststr; } - private string Tagforecastnumber(Dictionary tagParams) + private string Tagforecastenc(Dictionary tagParams) { - return station.Forecastnumber.ToString(); + return EncodeForWeb(station.forecaststr); + } + + private string TagforecastJsEnc(Dictionary tagParams) + { + return EncodeForJs(station.forecaststr); } private string Tagcumulusforecast(Dictionary tagParams) @@ -626,19 +642,19 @@ private string Tagcumulusforecast(Dictionary tagParams) return station.CumulusForecast; } - private string Tagwsforecast(Dictionary tagParams) + private string Tagcumulusforecastenc(Dictionary tagParams) { - return station.wsforecast; + return EncodeForWeb(station.CumulusForecast); } - private string Tagforecastenc(Dictionary tagParams) + private string TagcumulusforecastJsEnc(Dictionary tagParams) { - return EncodeForWeb(station.forecaststr); + return EncodeForJs(station.CumulusForecast); } - private string Tagcumulusforecastenc(Dictionary tagParams) + private string Tagwsforecast(Dictionary tagParams) { - return EncodeForWeb(station.CumulusForecast); + return station.wsforecast; } private string Tagwsforecastenc(Dictionary tagParams) @@ -646,6 +662,12 @@ private string Tagwsforecastenc(Dictionary tagParams) return EncodeForWeb(station.wsforecast); } + private string TagwsforecastJsEnc(Dictionary tagParams) + { + return EncodeForJs(station.wsforecast); + } + + private string Tagtemp(Dictionary tagParams) { return CheckRcDp(station.OutdoorTemperature, tagParams, cumulus.TempDPlaces); @@ -1854,6 +1876,12 @@ private string Tagcurrcondenc(Dictionary tagParams) return EncodeForWeb(GetCurrCondText()); } + private string TagcurrcondJsEnc(Dictionary tagParams) + { + return EncodeForJs(GetCurrCondText()); + } + + private string TagtempYh(Dictionary tagParams) { return CheckRcDp(station.HiLoYest.HighTemp, tagParams, cumulus.TempDPlaces); @@ -2702,6 +2730,16 @@ private string Tagstationtype(Dictionary tagParams) return cumulus.StationType == -1 ? "undefined" : cumulus.StationDesc[cumulus.StationType]; } + private string TagstationtypeJsEnc(Dictionary tagParams) + { + if (cumulus.StationModel != string.Empty) + { + return EncodeForJs(cumulus.StationModel); + } + + return cumulus.StationType == -1 ? "undefined" : EncodeForJs(cumulus.StationDesc[cumulus.StationType]); + } + private string Taglatitude(Dictionary tagParams) { var dpstr = tagParams.Get("dp"); @@ -2720,6 +2758,24 @@ private string Taglatitude(Dictionary tagParams) } } + private string TaglatitudeJsEnc(Dictionary tagParams) + { + var dpstr = tagParams.Get("dp"); + if (dpstr == null) + { + return EncodeForJs(cumulus.LatTxt); + } + + try + { + return CheckRcDp(cumulus.Latitude, tagParams, 2); + } + catch + { + return "error"; + } + } + private string Taglongitude(Dictionary tagParams) { var dpstr = tagParams.Get("dp"); @@ -2739,14 +2795,14 @@ private string Taglongitude(Dictionary tagParams) } } - private string Taglocation(Dictionary tagParams) + private string TaglongitudeJsEnc(Dictionary tagParams) { - return cumulus.LocationName; + return EncodeForJs(cumulus.LonTxt); } - private string Taglonglocation(Dictionary tagParams) + private string Taglocation(Dictionary tagParams) { - return cumulus.LocationDesc; + return cumulus.LocationName; } private string Taglocationenc(Dictionary tagParams) @@ -2754,11 +2810,27 @@ private string Taglocationenc(Dictionary tagParams) return EncodeForWeb(cumulus.LocationName); } + private string TaglocationJsEnc(Dictionary tagParams) + { + return EncodeForJs(cumulus.LocationName); + } + + + private string Taglonglocation(Dictionary tagParams) + { + return cumulus.LocationDesc; + } + private string Taglonglocationenc(Dictionary tagParams) { return EncodeForWeb(cumulus.LocationDesc); } + private string TaglonglocationJsEnc(Dictionary tagParams) + { + return EncodeForJs(cumulus.LocationDesc); + } + private string Tagsunrise(Dictionary tagParams) { return GetFormattedDateTime(SunriseSunset.RoundToMinute(cumulus.SunRiseTime), "HH:mm", tagParams); @@ -2833,6 +2905,11 @@ private string Tagaltitude(Dictionary tagParams) return cumulus.Altitude + (cumulus.AltitudeInFeet ? " ft" :" m"); } + private string Tagaltitudenoenc(Dictionary tagParams) + { + return cumulus.Altitude + (cumulus.AltitudeInFeet ? " ft" : " m"); + } + private string Tagforum(Dictionary tagParams) { if (string.IsNullOrEmpty(cumulus.ForumURL)) @@ -2878,9 +2955,14 @@ private string Tagtempunit(Dictionary tagParams) return EncodeForWeb(cumulus.Units.TempText); } + private string Tagtempunitnoenc(Dictionary tagParams) + { + return cumulus.Units.TempText; + } + private string Tagtempunitnodeg(Dictionary tagParams) { - return EncodeForWeb(cumulus.Units.TempText.Substring(1,1)); + return cumulus.Units.TempText.Substring(1,1); } private string Tagwindunit(Dictionary tagParams) @@ -5023,13 +5105,16 @@ public void InitialiseWebtags() { "shortyear", TagShortyear }, { "hour", TagHour }, { "minute", TagMinute }, - { "forecast", Tagforecast }, { "forecastnumber", Tagforecastnumber }, - { "cumulusforecast", Tagcumulusforecast }, - { "wsforecast", Tagwsforecast }, + { "forecast", Tagforecast }, { "forecastenc", Tagforecastenc }, + { "forecastJsEnc", TagforecastJsEnc }, + { "cumulusforecast", Tagcumulusforecast }, { "cumulusforecastenc", Tagcumulusforecastenc }, + { "cumulusforecastJsEnc", TagcumulusforecastJsEnc }, + { "wsforecast", Tagwsforecast }, { "wsforecastenc", Tagwsforecastenc }, + { "wsforecastJsEnc", TagwsforecastJsEnc }, { "temp", Tagtemp }, { "apptemp", Tagapptemp }, { "feelslike", Tagfeelsliketemp }, @@ -5195,6 +5280,7 @@ public void InitialiseWebtags() { "rollovertime", Tagrollovertime }, { "currcond", Tagcurrcond }, { "currcondenc", Tagcurrcondenc }, + { "currcondJsEnc", TagcurrcondJsEnc }, { "tempYH", TagtempYh }, { "TtempYH", TagTtempYh }, { "tempYL", TagtempYl }, @@ -5300,12 +5386,17 @@ public void InitialiseWebtags() { "THighDailyTempRange", TagTHighDailyTempRange }, { "graphperiod", Taggraphperiod }, { "stationtype", Tagstationtype }, + { "stationtypeJsEnc", TagstationtypeJsEnc }, { "latitude", Taglatitude }, + { "latitudeJsEnc", TaglatitudeJsEnc }, { "longitude", Taglongitude }, + { "longitudeJsEnc", TaglongitudeJsEnc }, { "location", Taglocation }, - { "longlocation", Taglonglocation }, { "locationenc", Taglocationenc }, + { "locationJsEnc", TaglocationJsEnc }, + { "longlocation", Taglonglocation }, { "longlocationenc", Taglonglocationenc }, + { "longlocationJsEnc", TaglonglocationJsEnc }, { "sunrise", Tagsunrise }, { "sunset", Tagsunset }, { "daylength", Tagdaylength }, @@ -5320,12 +5411,14 @@ public void InitialiseWebtags() { "moonphase", Tagmoonphase }, { "chillhours", TagChillHours }, { "altitude", Tagaltitude }, + { "altitudenoenc", Tagaltitudenoenc }, { "forum", Tagforum }, { "forumurl", Tagforumurl }, { "webcam", Tagwebcam }, { "webcamurl", Tagwebcamurl }, { "tempunit", Tagtempunit }, { "tempunitnodeg", Tagtempunitnodeg }, + { "tempunitnoenc", Tagtempunitnoenc }, { "windunit", Tagwindunit }, { "windrununit", Tagwindrununit }, { "pressunit", Tagpressunit }, diff --git a/Updates.txt b/Updates.txt index e29fb37a..a102e853 100644 --- a/Updates.txt +++ b/Updates.txt @@ -1,6 +1,17 @@ -b3127 beta changes +b3128 beta changes +—————————————————— +- Default web site: Changes to how text is handled to improve accessibility. + /js/setpagedata.js + /webfiles/webpagedata.js + +- New web tags (see main list below) for JavaScript encoded strings to support the above change +- Internal improvements in error handling of the CMX API +- Fix: SMTP Alarm emails. Add locking so only one email is sent concurrently -- SNMP: The MX Option "Use SSL/TLS" has changed to allow you to select from all the options available. This means MX does not have to second guess which option + +b3127 beta changes +—————————————————— +- SMTP: The MX Option "Use SSL/TLS" has changed to allow you to select from all the options available. This means MX does not have to second guess which option to use, but does make it more complex to configure. NOTE: After installing this release, YOU MAY HAVE TO ADJUST THIS SETTING, it will revert to "Auto", which should work for most servers @@ -17,7 +28,7 @@ b3127 beta changes - MySQL: Special characters in usernames and passwords are now handled correctly -3.11.0 - b3126 +3.11.0 - b3128 —————————————— - Fix: Remove THW Index in /web/websitedataT.json, and default web site index.htm - Fix: OpenWeatherMap - create new station was not forcing the use of dot decimals for lat/long values @@ -62,6 +73,13 @@ b3127 beta changes Benefit = you can now link directly to a particular graph to show on page load - New: FineOffset stations now report a warning if the console logger interval does match the Cumulus MX interval - New: FineOffset experimental feature to set the console logger interval to match Cumulus logging interval +- New: JavaScript encoded web tags. These tags were previously only available as HTML entity encoded strings: + <#latitudeJsEnc>, <#longitudeJsEnc> + <#locationJsEnc>, <#longlocationJsEnc> + <#forecastJsEnc>, <#cumulusforecastJsEnc>, <#wsforecastJsEnc> + <#currcondJsEnc>, +- New: Web tags with no HTML entity encoding: + <#tempunitnoenc>, <#altitudenoenc> - Changed: Moved FTP Logging option from Internet Settings to Program Settings to collect all the logging options in one place - Changed: A Data Stopped state now stops all logging, MySQL and web activity From 6a70e07ad5d3f0f2fae140fbec38cc18f47dbefd Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Thu, 6 May 2021 19:03:01 +0100 Subject: [PATCH 15/16] Some more error catching for Davis IP loggers --- CumulusMX/Cumulus.cs | 40 +++++++++++++++++++++++++++ CumulusMX/DavisStation.cs | 58 +++++++++++++++++++++++++++------------ 2 files changed, 80 insertions(+), 18 deletions(-) diff --git a/CumulusMX/Cumulus.cs b/CumulusMX/Cumulus.cs index 9f66331c..252b5ac2 100644 --- a/CumulusMX/Cumulus.cs +++ b/CumulusMX/Cumulus.cs @@ -2703,6 +2703,10 @@ private void RealtimeFTPUpload(byte cycle) { uploadfile = GetExtraLogFileName(DateTime.Now); } + else if (uploadfile == "", Path.GetFileName(GetExtraLogFileName(DateTime.Now))); } + else if (remotefile.Contains("", Path.GetFileName(GetAirLinkLogFileName(DateTime.Now))); + } // all checks OK, file needs to be uploaded if (ExtraFiles[i].process) @@ -2769,6 +2777,10 @@ private void CreateRealtimeHTMLfiles(int cycle) { uploadfile = GetExtraLogFileName(DateTime.Now); } + else if (uploadfile == "", Path.GetFileName(GetExtraLogFileName(DateTime.Now))); } + else if (remotefile.Contains("", Path.GetFileName(GetAirLinkLogFileName(DateTime.Now))); + } if (ExtraFiles[i].process) { @@ -7123,6 +7139,10 @@ public void DoHTMLFiles() { uploadfile = GetExtraLogFileName(DateTime.Now); } + else if (uploadfile == "", Path.GetFileName(GetExtraLogFileName(DateTime.Now))); } + else if (remotefile.Contains("", Path.GetFileName(GetAirLinkLogFileName(DateTime.Now))); + } if (ExtraFiles[i].process) { @@ -7313,6 +7337,10 @@ public void DoFTPLogin() { uploadfile = GetExtraLogFileName(logDay); } + else if (uploadfile == "", Path.GetFileName(GetExtraLogFileName(logDay))); } + else if (remotefile.Contains("", Path.GetFileName(GetAirLinkLogFileName(logDay))); + } // all checks OK, file needs to be uploaded if (ExtraFiles[i].process) @@ -8437,6 +8469,10 @@ public void DoExtraEndOfDayFiles() { uploadfile = GetExtraLogFileName(logDay); } + else if (uploadfile == "", Path.GetFileName(GetExtraLogFileName(logDay))); } + else if (remotefile.Contains("", Path.GetFileName(GetAirLinkLogFileName(logDay))); + } if (ExtraFiles[i].process) { diff --git a/CumulusMX/DavisStation.cs b/CumulusMX/DavisStation.cs index 7d490549..c6faa35f 100644 --- a/CumulusMX/DavisStation.cs +++ b/CumulusMX/DavisStation.cs @@ -2099,27 +2099,39 @@ private void GetArchiveData() } else { - stream = socket.GetStream(); - int retries = 0; - - do + try { - if (!WakeVP(socket)) + stream = socket.GetStream(); + int retries = 0; + + do { - cumulus.LogMessage("GetArchiveData: Unable to wake VP"); - } + if (!WakeVP(socket)) + { + cumulus.LogMessage("GetArchiveData: Unable to wake VP"); + } - cumulus.LogMessage("GetArchiveData: Sending DMPAFT"); - const string dmpaft = "DMPAFT\n"; - stream.Write(Encoding.ASCII.GetBytes(dmpaft), 0, dmpaft.Length); + cumulus.LogMessage("GetArchiveData: Sending DMPAFT"); + const string dmpaft = "DMPAFT\n"; + stream.Write(Encoding.ASCII.GetBytes(dmpaft), 0, dmpaft.Length); - ack = WaitForACK(stream); - if (!ack) - { - cumulus.LogMessage("GetArchiveData: No Ack in response to DMPAFT"); - retries++; - } - } while (!ack && retries < 2); + ack = WaitForACK(stream); + if (!ack) + { + cumulus.LogMessage("GetArchiveData: No Ack in response to DMPAFT"); + retries++; + } + } while (!ack && retries < 2); + } + catch (Exception ex) + { + cumulus.LogMessage("GetArchiveData: Error sending LOOP command [DMPAFT]: " + ex.Message); + cumulus.LogDebugMessage("GetArchiveData: Attempting to reconnect to station"); + InitTCP(); + cumulus.LogDebugMessage("GetArchiveData: Reconnected to station"); + + return; + } } if (!ack) @@ -3389,7 +3401,17 @@ private bool WaitForACK(NetworkStream stream, int timeoutMs = -1) if (timeoutMs > -1) { - stream.ReadTimeout = timeoutMs; + try + { + stream.ReadTimeout = timeoutMs; + } + catch (Exception ex) + { + cumulus.LogDebugMessage($"WaitForAck: {tryCount} Error - {ex.Message}"); + cumulus.LogDebugMessage("WaitForAck: Attempting to reconnect to logger"); + InitTCP(); + cumulus.LogDebugMessage("WaitForAck: Reconnected to logger"); + } } do From c6d70ebee81c697be120aa69220350e18bc01d1a Mon Sep 17 00:00:00 2001 From: Mark Crossley <1196094+mcrossley@users.noreply.github.com> Date: Thu, 6 May 2021 19:04:38 +0100 Subject: [PATCH 16/16] Final v3.11.0 -b3129 --- CumulusMX/Properties/AssemblyInfo.cs | 6 +++--- Updates.txt | 32 +--------------------------- 2 files changed, 4 insertions(+), 34 deletions(-) diff --git a/CumulusMX/Properties/AssemblyInfo.cs b/CumulusMX/Properties/AssemblyInfo.cs index 6a5960ad..603d75dd 100644 --- a/CumulusMX/Properties/AssemblyInfo.cs +++ b/CumulusMX/Properties/AssemblyInfo.cs @@ -6,7 +6,7 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Cumulus MX")] -[assembly: AssemblyDescription("Version 3.11.0 - Build 3128 - BETA")] +[assembly: AssemblyDescription("Version 3.11.0 - Build 3129")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Cumulus MX")] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("3.11.0.3128")] -[assembly: AssemblyFileVersion("3.11.0.3128")] +[assembly: AssemblyVersion("3.11.0.3129")] +[assembly: AssemblyFileVersion("3.11.0.3129")] diff --git a/Updates.txt b/Updates.txt index a102e853..c827927d 100644 --- a/Updates.txt +++ b/Updates.txt @@ -1,34 +1,4 @@ -b3128 beta changes -—————————————————— -- Default web site: Changes to how text is handled to improve accessibility. - /js/setpagedata.js - /webfiles/webpagedata.js - -- New web tags (see main list below) for JavaScript encoded strings to support the above change -- Internal improvements in error handling of the CMX API -- Fix: SMTP Alarm emails. Add locking so only one email is sent concurrently - - -b3127 beta changes -—————————————————— -- SMTP: The MX Option "Use SSL/TLS" has changed to allow you to select from all the options available. This means MX does not have to second guess which option - to use, but does make it more complex to configure. - NOTE: After installing this release, YOU MAY HAVE TO ADJUST THIS SETTING, it will revert to "Auto", which should work for most servers - - For example GMail, port 465 is TLS only, port 587 is plain text connection, but then starts TLS - The following options work... - GMail Port: 465 587 - —————————————————————————————————————— - None No No - Auto Yes Yes - TlsOnConnect Yes No - StartTls No Yes - StartTlsWhenAvail No Yes - -- MySQL: Special characters in usernames and passwords are now handled correctly - - -3.11.0 - b3128 +3.11.0 - b3129 —————————————— - Fix: Remove THW Index in /web/websitedataT.json, and default web site index.htm - Fix: OpenWeatherMap - create new station was not forcing the use of dot decimals for lat/long values