diff --git a/CumulusMX/AlarmSettings.cs b/CumulusMX/AlarmSettings.cs index 75dfe181..00eba60e 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,23 @@ 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(";"), + useHtml = cumulus.AlarmEmailHtml }; var retObject = new JsonAlarmSettings() { data = data, - units = alarmUnits + units = alarmUnits, + email = email }; return retObject.ToJson(); @@ -156,18 +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 settings = json.FromJson(); + 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"); @@ -176,6 +216,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 +226,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 +235,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 +244,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 +253,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 +262,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 +271,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 +280,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 +289,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 +298,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 +306,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 +314,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 +322,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 +330,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,20 +338,100 @@ 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; + cumulus.AlarmEmailHtml = result.email.useHtml; + // Save the settings cumulus.WriteIniFile(); context.Response.StatusCode = 200; } catch (Exception ex) + { + cumulus.LogMessage("Error processing Alarm settings: " + ex.Message); + cumulus.LogDebugMessage("Alarm Data: " + json); + context.Response.StatusCode = 500; + return ex.Message; + } + return "success"; + } + + public string TestEmail(IHttpContext context) + { + try + { + + var data = new StreamReader(context.Request.InputStream).ReadToEnd(); + + // Start at char 5 to skip the "json:" prefix + var json = WebUtility.UrlDecode(data); + + var result = json.FromJson(); + // process the settings + cumulus.LogMessage("Sending test email..."); + + // validate the from email + if (!EmailSender.CheckEmailAddress(result.fromEmail.Trim())) + { + var msg = "ERROR: Invalid Alarm from email address entered"; + cumulus.LogMessage(msg); + context.Response.StatusCode = 500; + return msg; + } + var from = result.fromEmail.Trim(); + + // validate the destination email(s) + var dest = result.destEmail.Trim().Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + for (var i = 0; i < dest.Length; i++) + { + dest[i] = dest[i].Trim(); + if (!EmailSender.CheckEmailAddress(dest[i])) + { + var msg = "ERROR: Invalid Alarm destination email address entered"; + cumulus.LogMessage(msg); + context.Response.StatusCode = 500; + return msg; + } + } + + 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"; } } @@ -309,6 +443,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 +452,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 +460,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 +470,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 +479,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 +488,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 +497,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 +506,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 +515,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 +524,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 +532,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 +540,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 +548,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 +556,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 +564,16 @@ 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 bool useHtml { get; set; } } public class JsonAlarmUnits @@ -433,5 +588,6 @@ public class JsonAlarmSettings { public JsonAlarmSettingsData data { get; set; } public JsonAlarmUnits units { get; set; } + public JsonAlarmEmail email { get; set; } } } 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/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/App.config b/CumulusMX/App.config index c6a7ecb0..a8b5174a 100644 --- a/CumulusMX/App.config +++ b/CumulusMX/App.config @@ -23,11 +23,11 @@ - + - + @@ -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 73236ffa..252b5ac2 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; @@ -491,6 +491,15 @@ 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 AlarmEmailHtml; + public bool ListWebTags; public bool RealtimeEnabled; // The timer is to be started @@ -506,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(); @@ -519,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(); @@ -575,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; @@ -623,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); @@ -677,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"; @@ -849,6 +871,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; @@ -964,7 +987,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, @@ -1001,6 +1024,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(); @@ -1033,6 +1068,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) { @@ -1043,8 +1079,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); @@ -1104,8 +1151,6 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) LogMessage("No start-up delay - disabled"); } - GetLatestVersion(); - GC.Collect(); LogMessage("Data path = " + Datapath); @@ -1122,7 +1167,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")); @@ -1178,11 +1223,7 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) if (MonthlyMySqlEnabled) { - MonthlyMySqlConn.Host = MySqlHost; - MonthlyMySqlConn.Port = MySqlPort; - MonthlyMySqlConn.UserId = MySqlUser; - MonthlyMySqlConn.Password = MySqlPass; - MonthlyMySqlConn.Database = MySqlDatabase; + MonthlyMySqlConn.ConnectionString = MySqlConnSettings.ToString(); SetStartOfMonthlyInsertSQL(); } @@ -1194,39 +1235,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 = MySqlConnSettings.ToString(); SetStartOfRealtimeInsertSQL(); } - CustomMysqlSecondsConn.Host = MySqlHost; - CustomMysqlSecondsConn.Port = MySqlPort; - CustomMysqlSecondsConn.UserId = MySqlUser; - CustomMysqlSecondsConn.Password = MySqlPass; - CustomMysqlSecondsConn.Database = MySqlDatabase; + + CustomMysqlSecondsConn.ConnectionString = MySqlConnSettings.ToString(); 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 = MySqlConnSettings.ToString(); customMysqlMinutesTokenParser.OnToken += TokenParserOnToken; CustomMysqlMinutesCommand.Connection = CustomMysqlMinutesConn; - CustomMysqlRolloverConn.Host = MySqlHost; - CustomMysqlRolloverConn.Port = MySqlPort; - CustomMysqlRolloverConn.UserId = MySqlUser; - CustomMysqlRolloverConn.Password = MySqlPass; - CustomMysqlRolloverConn.Database = MySqlDatabase; + CustomMysqlRolloverConn.ConnectionString = MySqlConnSettings.ToString(); customMysqlRolloverTokenParser.OnToken += TokenParserOnToken; CustomMysqlRolloverCommand.Connection = CustomMysqlRolloverConn; @@ -1238,6 +1263,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 +1286,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 @@ -1272,11 +1331,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); @@ -1391,16 +1451,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; @@ -1498,7 +1550,8 @@ internal void SetUpHttpProxy() private void CustomHttpSecondsTimerTick(object sender, ElapsedEventArgs e) { - CustomHttpSecondsUpdate(); + if (!station.DataStopped) + CustomHttpSecondsUpdate(); } internal void SetStartOfRealtimeInsertSQL() @@ -1680,23 +1733,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() @@ -1826,6 +1862,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"); @@ -1833,7 +1875,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"); @@ -1849,13 +1891,14 @@ private void WebTimerTick(object sender, ElapsedEventArgs e) } } - private void TwitterTimerTick(object sender, ElapsedEventArgs e) - { - UpdateTwitter(); - } - internal async void UpdateTwitter() { + if (station.DataStopped) + { + // No data coming in, do nothing + return; + } + LogDebugMessage("Starting Twitter update"); var auth = new XAuthAuthorizer { @@ -1947,11 +1990,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) { @@ -1959,21 +1997,10 @@ 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) { - MqttPublisher.UpdateMQTTfeed("Interval"); + if (!station.DataStopped) + MqttPublisher.UpdateMQTTfeed("Interval"); } /* @@ -1991,269 +2018,326 @@ 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) + 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("Wunderground: URL = " + logUrl); + } + + try + { + HttpResponseMessage response = await WUhttpClient.GetAsync(URL); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + if (!Wund.RapidFireEnabled || response.StatusCode != HttpStatusCode.OK) { - Wund.Updating = false; + LogDebugMessage("Wunderground: Response = " + response.StatusCode + ": " + responseBodyAsText); } } + catch (Exception ex) + { + LogMessage("Wunderground: ERROR - " + 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(); + LogDebugMessage("Windy: Response = " + response.StatusCode + ": " + responseBodyAsText); + } + catch (Exception ex) + { + LogMessage("Windy: ERROR - " + ex.Message); + } + finally + { + Windy.Updating = false; + } + } + + internal async void UpdateWindGuru(DateTime timestamp) + { + if (WindGuru.Updating || station.DataStopped) + { + // No data coming in, do not do anything + return; + } + + WindGuru.Updating = true; + + string apistring; + string url = station.GetWindGuruURL(out apistring, timestamp); + string logUrl = url.Replace(apistring, "<>"); + + LogDebugMessage("WindGuru: URL = " + logUrl); + + try + { + HttpResponseMessage response = await WindGuruhttpClient.GetAsync(url); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + LogDebugMessage("WindGuru: " + response.StatusCode + ": " + responseBodyAsText); + } + catch (Exception ex) + { + LogDebugMessage("WindGuru: ERROR - " + ex.Message); + } + finally + { + WindGuru.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) + LogDebugMessage("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 + LogDebugMessage("WeatherCloud: URL = " + logUrl); + + try + { + HttpResponseMessage response = await WCloudhttpClient.GetAsync(url); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + var msg = ""; + switch ((int)response.StatusCode) { - WCloud.Updating = false; + 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) + { + LogDebugMessage("WeatherCloud: ERROR - " + 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! + LogDebugMessage($"OpenWeatherMap: Response code = {status} - {response.StatusCode}"); + if (response.StatusCode != HttpStatusCode.NoContent) + LogDataMessage($"OpenWeatherMap: Response data = {responseBodyAsText}"); } } + catch (Exception ex) + { + LogMessage("OpenWeatherMap: ERROR - " + ex.Message); + } + finally + { + OpenWeatherMap.Updating = false; + } } // Find all stations associated with the users API key @@ -2267,7 +2351,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) { @@ -2278,7 +2362,7 @@ internal OpenWeatherMapStation[] GetOpenWeatherMapStations() } catch (Exception ex) { - LogMessage("OpenWeatherMap: Get stations - " + ex.Message); + LogMessage("OpenWeatherMap: Get Stations ERROR - " + ex.Message); } return retVal; @@ -2287,14 +2371,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"); @@ -2328,7 +2414,7 @@ internal void CreateOpenWeatherMapStation() } catch (Exception ex) { - LogMessage("OpenWeatherMap: Create station - " + ex.Message); + LogMessage("OpenWeatherMap: Create station ERROR - " + ex.Message); } } @@ -2367,8 +2453,6 @@ internal void EnableOpenWeatherMap() LogMessage("OpenWeatherMap: " + msg); } } - - OpenWeatherMapTimer.Enabled = OpenWeatherMap.Enabled && !OpenWeatherMap.SynchronisedUpdate; } } @@ -2377,6 +2461,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 { @@ -2613,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) @@ -2679,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) { @@ -3401,11 +3507,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); @@ -3555,9 +3663,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); @@ -3628,16 +3737,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); @@ -3662,6 +3771,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); @@ -3893,6 +4004,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); @@ -3910,6 +4023,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", ""); @@ -3935,7 +4053,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); @@ -3947,8 +4065,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); @@ -3966,15 +4082,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", ""); @@ -3985,8 +4110,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; @@ -3999,8 +4122,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); @@ -4010,8 +4131,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"); @@ -4022,16 +4141,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 @@ -4056,6 +4171,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 +4181,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 +4191,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 +4201,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 +4211,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 +4221,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 +4231,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 +4241,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 +4251,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 +4261,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 +4270,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 +4279,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 +4287,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 +4295,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 +4303,14 @@ 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(';'); + AlarmEmailHtml = ini.GetValue("Alarms", "UseHTML", false); + Calib.Press.Offset = ini.GetValue("Offsets", "PressOffset", 0.0); Calib.Temp.Offset = ini.GetValue("Offsets", "TempOffset", 0.0); Calib.Hum.Offset = ini.GetValue("Offsets", "HumOffset", 0); @@ -4320,11 +4454,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"); @@ -4385,6 +4520,28 @@ 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.SslOption = ini.GetValue("SMTP", "SSLOption", 1); + SmtpOptions.RequiresAuthentication = ini.GetValue("SMTP", "RequiresAuthentication", false); + SmtpOptions.User = ini.GetValue("SMTP", "User", ""); + SmtpOptions.Password = ini.GetValue("SMTP", "Password", ""); + + // Growing Degree Days + GrowingBase1 = ini.GetValue("GrowingDD", "BaseTemperature1", (Units.Temp == 0 ? 5.0 : 40.0)); + GrowingBase2 = ini.GetValue("GrowingDD", "BaseTemperature2", (Units.Temp == 0 ? 10.0 : 50.0)); + GrowingYearStarts = ini.GetValue("GrowingDD", "YearStarts", (Latitude >= 0 ? 1 : 7)); + GrowingCap30C = ini.GetValue("GrowingDD", "Cap30C", true); + + // Temperature Sum + TempSumYearStarts = ini.GetValue("TempSum", "TempSumYearStart", (Latitude >= 0 ? 1 : 7)); + if (TempSumYearStarts < 1 || TempSumYearStarts > 12) + TempSumYearStarts = 1; + TempSumBase1 = ini.GetValue("TempSum", "BaseTemperature1", GrowingBase1); + TempSumBase2 = ini.GetValue("TempSum", "BaseTemperature2", GrowingBase2); } internal void WriteIniFile() @@ -4441,13 +4598,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); @@ -4502,12 +4660,15 @@ 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); - 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); @@ -4709,7 +4870,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); @@ -4750,6 +4915,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); @@ -4770,6 +4941,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 +4950,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 +4959,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 +4968,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 +4977,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 +4986,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 +4995,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 +5004,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 +5013,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 +5022,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 +5030,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 +5038,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 +5046,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 +5054,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 +5062,15 @@ 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("Alarms", "UseHTML", AlarmEmailHtml); + + ini.SetValue("Offsets", "PressOffset", Calib.Press.Offset); ini.SetValue("Offsets", "TempOffset", Calib.Temp.Offset); ini.SetValue("Offsets", "HumOffset", Calib.Hum.Offset); @@ -4996,12 +5187,18 @@ internal void WriteIniFile() ini.SetValue("Graphs", "DailyAvgTempVisible", GraphOptions.DailyAvgTempVisible); ini.SetValue("Graphs", "DailyMaxTempVisible", GraphOptions.DailyMaxTempVisible); ini.SetValue("Graphs", "DailyMinTempVisible", GraphOptions.DailyMinTempVisible); - - 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("Graphs", "GrowingDegreeDaysVisible1", GraphOptions.GrowingDegreeDaysVisible1); + ini.SetValue("Graphs", "GrowingDegreeDaysVisible2", GraphOptions.GrowingDegreeDaysVisible2); + ini.SetValue("Graphs", "TempSumVisible0", GraphOptions.TempSumVisible0); + ini.SetValue("Graphs", "TempSumVisible1", GraphOptions.TempSumVisible1); + ini.SetValue("Graphs", "TempSumVisible2", GraphOptions.TempSumVisible2); + + + ini.SetValue("MySQL", "Host", MySqlConnSettings.Server); + ini.SetValue("MySQL", "Port", MySqlConnSettings.Port); + ini.SetValue("MySQL", "User", MySqlConnSettings.UserID); + ini.SetValue("MySQL", "Pass", MySqlConnSettings.Password); + ini.SetValue("MySQL", "Database", MySqlConnSettings.Database); ini.SetValue("MySQL", "MonthlyMySqlEnabled", MonthlyMySqlEnabled); ini.SetValue("MySQL", "RealtimeMySqlEnabled", RealtimeMySqlEnabled); ini.SetValue("MySQL", "DayfileMySqlEnabled", DayfileMySqlEnabled); @@ -5037,6 +5234,25 @@ 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", "SSLOption", SmtpOptions.SslOption); + ini.SetValue("SMTP", "RequiresAuthentication", SmtpOptions.RequiresAuthentication); + 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(); @@ -5045,318 +5261,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."); } @@ -5655,15 +5889,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(); @@ -5675,7 +5902,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; @@ -5887,7 +6113,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) @@ -5965,12 +6191,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(""); @@ -6014,31 +6234,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 { @@ -6336,13 +6532,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 { @@ -6354,7 +6550,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 @@ -6368,7 +6564,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"; @@ -6392,54 +6588,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) @@ -6454,18 +6614,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); @@ -6477,6 +6628,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 @@ -6875,15 +7041,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(); @@ -6980,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) { @@ -7170,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) @@ -7963,55 +8138,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); } } @@ -8091,18 +8227,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 @@ -8152,7 +8279,6 @@ public void StartTimersAndSensors() // No archived entries to upload WindyList = null; LogDebugMessage("Windylist count is zero"); - WindyTimer.Enabled = Windy.Enabled && !Windy.SynchronisedUpdate; } else { @@ -8170,7 +8296,6 @@ public void StartTimersAndSensors() { // No archived entries to upload PWSList = null; - PWSTimer.Enabled = PWS.Enabled && !PWS.SynchronisedUpdate; } else { @@ -8188,7 +8313,6 @@ public void StartTimersAndSensors() { // No archived entries to upload WOWList = null; - WOWTimer.Enabled = WOW.Enabled && !WOW.SynchronisedUpdate; } else { @@ -8206,7 +8330,6 @@ public void StartTimersAndSensors() { // No archived entries to upload OWMList = null; - OpenWeatherMapTimer.Enabled = OpenWeatherMap.Enabled && !OpenWeatherMap.SynchronisedUpdate; } else { @@ -8235,12 +8358,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; @@ -8254,99 +8371,76 @@ public void StartTimersAndSensors() private void CustomMysqlSecondsTimerTick(object sender, ElapsedEventArgs e) { + if (station.DataStopped) + { + // No data coming in, do not do anything + return; + } + + _ = CustomMysqlSecondsWork(); + } + + private async Task CustomMysqlSecondsWork() + { + if (station.DataStopped) + { + // No data coming in, do not do anything + return; + } + if (!customMySqlSecondsUpdateInProgress) { customMySqlSecondsUpdateInProgress = true; - 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) + { + // No data coming in, do not do anything + return; + } + if (!customMySqlMinutesUpdateInProgress) { 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) + { + // No data coming in, do not do anything + return; + } + 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; } } @@ -8375,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) { @@ -8441,39 +8543,11 @@ public void DoExtraEndOfDayFiles() private void MySqlCatchup() { - var mySqlConn = new MySqlConnection - { - Host = MySqlHost, - Port = MySqlPort, - UserId = MySqlUser, - Password = MySqlPass, - Database = MySqlDatabase - }; - try { - mySqlConn.Open(); - - for (int i = 0; i < MySqlList.Count; i++) + using (var mySqlConn = new MySqlConnection(MySqlConnSettings.ToString())) { - 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) @@ -8481,14 +8555,6 @@ private void MySqlCatchup() 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(); @@ -8655,7 +8721,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; } @@ -8684,7 +8749,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; } @@ -8713,7 +8777,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; } @@ -8755,7 +8818,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; } @@ -8822,6 +8884,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(); @@ -9341,9 +9496,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; } } @@ -9351,10 +9507,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 @@ -9385,6 +9541,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 @@ -9495,6 +9656,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 +9671,14 @@ 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 + "\r\n" + string.Format(EmailMsg, Value, Units); + cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); + } + // If we get a new trigger, record the time triggered = true; TriggeredTime = DateTime.Now; @@ -9533,8 +9704,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 +9724,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 + "\r\n" + string.Format(EmailMsgUp, Value, Units); + cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); + } + // If we get a new trigger, record the time upTriggered = true; UpTriggeredTime = DateTime.Now; @@ -9584,6 +9766,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 + "\n" + string.Format(EmailMsgDn, Value, Units); + cumulus.emailer.SendEmail(cumulus.AlarmDestEmail, cumulus.AlarmFromEmail, cumulus.AlarmEmailSubject, msg, cumulus.AlarmEmailHtml); + } + // If we get a new trigger, record the time downTriggered = true; DownTriggeredTime = DateTime.Now; @@ -9609,6 +9799,10 @@ public bool DownTriggered } public DateTime DownTriggeredTime { get; set; } + + public string EmailMsgUp { get; set; } + public string EmailMsgDn { get; set; } + } public class WebUploadService @@ -9624,7 +9818,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; @@ -9651,7 +9845,6 @@ public class WebUploadWund : WebUploadService public bool SendSoilMoisture4; public bool SendLeafWetness1; public bool SendLeafWetness2; - public bool SendAirQuality; } public class WebUploadWindy : WebUploadService @@ -9660,6 +9853,11 @@ public class WebUploadWindy : WebUploadService public int StationIdx; } + public class WebUploadWindGuru : WebUploadService + { + public bool SendRain; + } + public class WebUploadAwekas : WebUploadService { public bool RateLimited; @@ -9668,7 +9866,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 @@ -9682,4 +9887,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..ab8ae0ea 100644 --- a/CumulusMX/CumulusMX.csproj +++ b/CumulusMX/CumulusMX.csproj @@ -82,16 +82,11 @@ true - - False - ..\..\CumulusMX\CumulusMX\lib\Devart.Data.dll - - - False - lib\Devart.Data.MySql.dll + + ..\packages\Portable.BouncyCastle.1.8.10\lib\net40\BouncyCastle.Crypto.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 @@ -104,14 +99,23 @@ False ..\packages\linqtotwitter.3.1.1\lib\net45\LinqToTwitterPcl.dll - - ..\packages\MQTTnet.3.0.12\lib\net452\MQTTnet.dll + + ..\packages\MailKit.2.11.1\lib\net45\MailKit.dll + + + ..\packages\MimeKit.2.11.0\lib\net45\MimeKit.dll + + + ..\packages\MQTTnet.3.0.15\lib\net452\MQTTnet.dll + + + ..\packages\MySqlConnector.1.3.7\lib\net45\MySqlConnector.dll ..\packages\SSH.NET.2016.1.0\lib\net40\Renci.SshNet.dll - ..\packages\ServiceStack.Text.5.9.2\lib\net45\ServiceStack.Text.dll + ..\packages\ServiceStack.Text.5.10.4\lib\net45\ServiceStack.Text.dll @@ -147,11 +151,16 @@ ..\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 + + + ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll + + @@ -183,6 +192,7 @@ + @@ -214,6 +224,7 @@ + diff --git a/CumulusMX/DavisStation.cs b/CumulusMX/DavisStation.cs index f1ad70e9..c6faa35f 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 { @@ -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 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 new file mode 100644 index 00000000..b5cee8ea --- /dev/null +++ b/CumulusMX/EmailSender.cs @@ -0,0 +1,174 @@ +using System; +using MailKit.Net.Smtp; +using MimeKit; +using System.Text.RegularExpressions; +using MailKit; +using System.Threading; + +namespace CumulusMX +{ + public class EmailSender + { + static readonly Regex ValidEmailRegex = CreateValidEmailRegex(); + private static SemaphoreSlim _writeLock; + private readonly Cumulus cumulus; + + public EmailSender(Cumulus cumulus) + { + this.cumulus = cumulus; + _writeLock = new SemaphoreSlim(1); + } + + + public async void SendEmail(string[] to, string from, string subject, string message, bool isHTML) + { + try + { + cumulus.LogDebugMessage($"SendEmail: Waiting for lock..."); + await _writeLock.WaitAsync(); + cumulus.LogDebugMessage($"SendEmail: Has the lock"); + + cumulus.LogDebugMessage($"SendEmail: Sending email, to [{string.Join("; ", to)}], subject [{subject}], body [{message}]..."); + + var m = new MimeMessage(); + m.From.Add(new MailboxAddress("", from)); + foreach (var addr in to) + { + m.To.Add(new MailboxAddress("", addr)); + } + m.Subject = subject; + + BodyBuilder bodyBuilder = new BodyBuilder(); + if (isHTML) + { + bodyBuilder.HtmlBody = message; + } + else + { + bodyBuilder.TextBody = message; + } + + m.Body = bodyBuilder.ToMessageBody(); + + using (SmtpClient client = new SmtpClient(cumulus.SmtpOptions.Logging ? new ProtocolLogger("MXdiags/smtp.log") : null)) + { + await client.ConnectAsync(cumulus.SmtpOptions.Server, cumulus.SmtpOptions.Port, (MailKit.Security.SecureSocketOptions)cumulus.SmtpOptions.SslOption); + + // Note: since we don't have an OAuth2 token, disable + // the XOAUTH2 authentication mechanism. + client.AuthenticationMechanisms.Remove("XOAUTH2"); + + if (cumulus.SmtpOptions.RequiresAuthentication) + { + await client.AuthenticateAsync(cumulus.SmtpOptions.User, cumulus.SmtpOptions.Password); + //client.Authenticate(cumulus.SmtpOptions.User, cumulus.SmtpOptions.Password); + } + + await client.SendAsync(m); + client.Disconnect(true); + } + } + catch (Exception e) + { + cumulus.LogMessage("SendEmail: Error - " + e); + + } + finally + { + cumulus.LogDebugMessage($"SendEmail: Releasing lock..."); + _writeLock.Release(); + } + } + + public void SendTestEmail(string[] to, string from, string subject, string message, bool isHTML) + { + try + { + cumulus.LogDebugMessage($"SendEmail: Waiting for lock..."); + _writeLock.Wait(); + cumulus.LogDebugMessage($"SendEmail: Has the lock"); + + cumulus.LogDebugMessage($"SendEmail: Sending Test email, to [{string.Join("; ", to)}], subject [{subject}], body [{message}]..."); + + var m = new MimeMessage(); + m.From.Add(new MailboxAddress("", from)); + foreach (var addr in to) + { + m.To.Add(new MailboxAddress("", addr)); + } + m.Subject = subject; + + BodyBuilder bodyBuilder = new BodyBuilder(); + if (isHTML) + { + bodyBuilder.HtmlBody = message; + } + else + { + bodyBuilder.TextBody = message; + } + + m.Body = bodyBuilder.ToMessageBody(); + + using (SmtpClient client = 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); + + // 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); + + } + finally + { + cumulus.LogDebugMessage($"SendEmail: Releasing lock..."); + _writeLock.Release(); + } + } + + + private static Regex CreateValidEmailRegex() + { + string validEmailPattern = @"^(?!\.)(""([^""\r\\]|\\[""\r\\])*""|" + + @"([-a-z0-9!#$%&'*+/=?^_`{|}~]|(?(); - // 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 d6e4acc3..45b0d849 100644 --- a/CumulusMX/FOStation.cs +++ b/CumulusMX/FOStation.cs @@ -17,10 +17,10 @@ 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; + //private readonly int maxHistoryEntries; private int prevaddr = -1; private int prevraintotal = -1; private int ignoreraincount; @@ -61,39 +61,48 @@ internal FOStation(Cumulus cumulus) : base(cumulus) { foEntrysize = 0x14; foMaxAddr = 0xFFEC; - maxHistoryEntries = 3264; + //maxHistoryEntries = 3264; } else { foEntrysize = 0x10; foMaxAddr = 0xFFF0; - maxHistoryEntries = 4080; + //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 logging interval + cumulus.LogMessage("Reading station logging interval"); + if (ReadAddress(0x10, data)) + { + int logint = data[0]; + + if (logint != cumulus.logints[cumulus.DataLogInterval]) + { + var msg = $"Warning, your console logging interval ({logint} mins) does not match the Cumulus logging interval ({cumulus.logints[cumulus.DataLogInterval]} mins)"; + cumulus.LogConsoleMessage(msg); + cumulus.LogMessage(msg); + if (cumulus.FineOffsetOptions.SetLoggerInterval) + { + WriteAddress(0x10, (byte)cumulus.logints[cumulus.DataLogInterval]); // write the logging new logging interval + WriteAddress(0x1A, 0xAA); // tell the station to read the new parameter + do + { + Thread.Sleep(1000); // sleep to let it reconfigure + ReadAddress(0x10, data); + } while (data[9] != 0); + } + } + } + // Get the block of data containing the abs and rel pressures - cumulus.LogMessage("Reading 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; + cumulus.LogMessage("Reading station pressure offset"); + + double relpressure = (((data[17] & 0x3f) * 256) + data[16]) / 10.0f; + double abspressure = (((data[19] & 0x3f) * 256) + data[18]) / 10.0f; pressureOffset = relpressure - abspressure; cumulus.LogMessage("Rel pressure = " + relpressure); cumulus.LogMessage("Abs pressure = " + abspressure); @@ -110,26 +119,10 @@ internal FOStation(Cumulus cumulus) : base(cumulus) } else { - cumulus.LogMessage("Stream open failed"); - cumulus.LogConsoleMessage("Unable to connect to station"); + // pause for 10 seconds then try again + Thread.Sleep(10000); } - } - 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!"); - } - } + } while (hidDevice == null || stream == null || !stream.CanRead); } public override void startReadingHistoryData() @@ -197,14 +190,25 @@ public override void getAndProcessHistoryData() DateTime timestamp = DateTime.Now; //LastUpdateTime = DateTime.Now; // lastArchiveTimeUTC.ToLocalTime(); cumulus.LogMessage("Last Update = " + cumulus.LastUpdateTime); - ReadAddress(0, data); + cumulus.LogDebugMessage("Reading fixed memory block"); + if (!ReadAddress(0, data)) + { + return; + } // get address of current location - int addr = ((data[31])*256) + data[30]; - //int previousaddress = addr; + int addr = data[31] * 256 + data[30]; + int previousaddress = addr; + + // get the number of logger entries the console has recorded + int logEntries = data[28] * 256 + data[27]; + cumulus.LogDebugMessage($"Console has {logEntries} log entries"); cumulus.LogMessage("Reading current address " + addr.ToString("X4")); - ReadAddress(addr, data); + if (!ReadAddress(addr, data)) + { + return; + } bool moredata = true; @@ -214,35 +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 < maxHistoryEntries - 2)) + 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 == 0xF0) addr = foMaxAddr; // wrap around + if (addr < 0x100) + { + addr = foMaxAddr; // wrap around + } - ReadAddress(addr, data); + 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; @@ -252,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; @@ -275,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) @@ -316,7 +339,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()); @@ -332,7 +355,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(); @@ -340,18 +363,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)); } @@ -375,31 +402,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); } @@ -413,8 +441,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; @@ -554,12 +584,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,11 +656,28 @@ 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); - Thread.Sleep(cumulus.FineOffsetOptions.FineOffsetReadTime); + try + { + stream.Write(request); + } + catch (Exception ex) + { + cumulus.LogConsoleMessage("Error sending command to station - it may need resetting"); + cumulus.LogMessage(ex.Message); + cumulus.LogMessage("Error sending command to station - it may need resetting"); + DataStopped = true; + cumulus.DataStoppedAlarm.Triggered = true; + return false; + } + + Thread.Sleep(cumulus.FineOffsetOptions.ReadTime); for (int i = 1; i < 5; i++) { //cumulus.LogMessage("Reading 8 bytes"); @@ -592,6 +690,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 +702,61 @@ private void ReadAddress(int address, byte[] buff) } cumulus.LogDataMessage(recData); } + return true; + } + + private bool WriteAddress(int address, byte val) + { + var addrlowbyte = (byte)(address & 0xFF); + var addrhighbyte = (byte)(address >> 8); + + var request = new byte[] { 0, 0xa2, addrhighbyte, addrlowbyte, 0x20, 0xa2, val, 0, 0x20 }; + + if (hidDevice == null) + { + return false; + } + + //response = device.WriteRead(0x00, request); + try + { + stream.Write(request); + } + catch (Exception ex) + { + cumulus.LogConsoleMessage("Error sending command to station - it may need resetting"); + cumulus.LogMessage(ex.Message); + cumulus.LogMessage("Error sending command to station - it may need resetting"); + DataStopped = true; + cumulus.DataStoppedAlarm.Triggered = true; + return false; + } + + return true; } private void DataReadTimerTick(object state, ElapsedEventArgs elapsedEventArgs) { + if (DataStopped) + { + cumulus.LogMessage("Attempting to reopen the USB device..."); + // We are not getting any data from the station, try reopening the USB connection + if (stream != null) + { + try + { + stream.Close(); + } + catch { } + } + + if (!OpenHidDevice()) + { + cumulus.LogMessage("Failed to reopen the USB device"); + return; + } + } + if (!readingData) { readingData = true; @@ -646,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) { @@ -658,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; @@ -682,29 +834,39 @@ 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]; - cumulus.LogDataMessage("First block read, addr = " + addr.ToString("X8")); + cumulus.LogDataMessage("First block read, addr = " + addr.ToString("X4")); if (addr != prevaddr) { // location has changed, skip this read to give it chance to update //cumulus.LogMessage("Location changed, skipping"); + cumulus.LogDebugMessage("Address changed"); + cumulus.LogDebugMessage("addr=" + addr.ToString("X4") + " previous=" + prevaddr.ToString("X4")); + if (synchroPhase == 2) { - cumulus.LogDebugMessage("Address changed"); - cumulus.LogDebugMessage("addr=" + addr.ToString("X4") + "previous=" + prevaddr.ToString("X4")); FOStationClockTime = DateTime.Now; StopSynchronising(); } + + prevaddr = addr; + return; } else { - cumulus.LogDataMessage("Reading data, addr = " + addr.ToString("X8")); + cumulus.LogDataMessage("Reading data, addr = " + addr.ToString("X4")); - ReadAddress(addr, data); + if (!ReadAddress(addr, data)) + { + return; + } cumulus.LogDataMessage("Data read - " + BitConverter.ToString(data)); @@ -748,14 +910,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); @@ -782,7 +944,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)); } @@ -790,7 +956,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); @@ -802,7 +968,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(); @@ -822,7 +988,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); @@ -846,12 +1012,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); @@ -866,7 +1032,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); @@ -882,21 +1048,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); @@ -969,8 +1131,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/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/InternetSettings.cs b/CumulusMX/InternetSettings.cs index f8352ee8..dc014771 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 @@ -78,11 +94,6 @@ public string UpdateInternetConfig(IHttpContext context) cumulus.DeleteBeforeUpload = settings.websettings.general.ftpdelete; cumulus.FTPRename = settings.websettings.general.ftprename; cumulus.UTF8encode = settings.websettings.general.utf8encode; - if (settings.websettings.general.ftplogging != cumulus.FTPlogging) - { - cumulus.FTPlogging = settings.websettings.general.ftplogging; - cumulus.SetFtpLogging(cumulus.FTPlogging); - } cumulus.RealtimeEnabled = settings.websettings.realtime.enabled; if (cumulus.RealtimeEnabled) @@ -156,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 { @@ -437,6 +209,8 @@ public string UpdateInternetConfig(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; @@ -466,43 +240,29 @@ public string UpdateInternetConfig(IHttpContext context) context.Response.StatusCode = 500; } - // Custom HTTP + // email settings 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.SmtpOptions.Enabled = settings.emailsettings.enabled; + if (cumulus.SmtpOptions.Enabled) { - cumulus.CustomHttpMinutesString = settings.customhttp.customminutes.url ?? string.Empty; - cumulus.CustomHttpMinutesIntervalIndex = settings.customhttp.customminutes.intervalindex; - if (cumulus.CustomHttpMinutesIntervalIndex >= 0 && cumulus.CustomHttpMinutesIntervalIndex < cumulus.FactorsOf60.Length) + cumulus.SmtpOptions.Server = settings.emailsettings.server; + cumulus.SmtpOptions.Port = settings.emailsettings.port; + cumulus.SmtpOptions.SslOption = settings.emailsettings.ssloption; + cumulus.SmtpOptions.RequiresAuthentication = settings.emailsettings.authenticate; + cumulus.SmtpOptions.User = settings.emailsettings.user; + cumulus.SmtpOptions.Password = settings.emailsettings.password; + + if (cumulus.emailer == null) { - cumulus.CustomHttpMinutesInterval = cumulus.FactorsOf60[cumulus.CustomHttpMinutesIntervalIndex]; + cumulus.emailer = new EmailSender(cumulus); } - 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; + var msg = "Error processing Email settings: " + ex.Message; cumulus.LogMessage(msg); errorMsg += msg + "\n\n"; context.Response.StatusCode = 500; @@ -511,9 +271,6 @@ public string UpdateInternetConfig(IHttpContext context) // Save the settings cumulus.WriteIniFile(); - // Do OpenWeatherMap setup - cumulus.EnableOpenWeatherMap(); - cumulus.SetUpHttpProxy(); //cumulus.SetFtpLogging(cumulus.FTPlogging); @@ -542,15 +299,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() { @@ -580,7 +339,6 @@ public string GetInternetAlpacaFormData() ftpdelete = cumulus.DeleteBeforeUpload, ftprename = cumulus.FTPRename, utf8encode = cumulus.UTF8encode, - ftplogging = cumulus.FTPlogging }; var websettingsintervalstd = new JsonInternetSettingsWebSettingsIntervalFiles() @@ -664,8 +422,6 @@ public string GetInternetAlpacaFormData() realtime = websettingsrealtime }; - - var externalprograms = new JsonInternetSettingsExternalPrograms() { dailyprogram = cumulus.DailyProgram, @@ -676,109 +432,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, @@ -825,63 +478,43 @@ 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() + var email = new JsonEmailSettings() { - enabled = cumulus.CustomHttpRolloverEnabled, - url = cumulus.CustomHttpRolloverString + enabled = cumulus.SmtpOptions.Enabled, + server = cumulus.SmtpOptions.Server, + port = cumulus.SmtpOptions.Port, + ssloption = cumulus.SmtpOptions.SslOption, + authenticate = cumulus.SmtpOptions.RequiresAuthentication, + user = cumulus.SmtpOptions.User, + password = cumulus.SmtpOptions.Password }; - var customhttp = new JsonInternetSettingsCustomHttpSettings() { customseconds = customseconds, customminutes = customminutes, customrollover = customrollover }; - var data = new JsonInternetSettingsData() { 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; @@ -988,19 +621,10 @@ 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; } } public class JsonInternetSettingsWebsiteAdvanced @@ -1039,7 +663,6 @@ public class JsonInternetSettingsWebSettingsGeneral public bool ftprename { get; set; } public bool ftpdelete { get; set; } public bool utf8encode { get; set; } - public bool ftplogging { get; set; } } public class JsonInternetSettingsFileSettings @@ -1073,7 +696,6 @@ public class JsonInternetSettingsWebSettingsRealtime public JsonInternetSettingsFileSettings[] files { get; set; } } - public class JsonInternetSettingsExternalPrograms { public string program { get; set; } @@ -1084,111 +706,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; } @@ -1239,30 +756,14 @@ public class JsonInternetSettingsHTTPproxySettings 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 class JsonEmailSettings { - 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; } + public string server { get; set; } + public int port { 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/CumulusMX/MeteoLib.cs b/CumulusMX/MeteoLib.cs index 09057943..0ce7e836 100644 --- a/CumulusMX/MeteoLib.cs +++ b/CumulusMX/MeteoLib.cs @@ -13,19 +13,20 @@ 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); - 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 +45,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; } /// @@ -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 @@ -108,24 +110,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 +168,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 +206,7 @@ public static double CalculateWetBulbCIterative(double tempC, int humidity, doub private static double Sqr(double num) { - return num*num; + return num * num; } /// @@ -223,7 +225,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 +254,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 +282,53 @@ 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; + } + + /// + /// Calculates the Davis THW Index. + /// Uses method described for Heat Index and 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); + + // 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/MysqlSettings.cs b/CumulusMX/MysqlSettings.cs index e99bf727..0e80c646 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; @@ -10,25 +10,25 @@ 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() { - 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() @@ -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 }; @@ -84,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; @@ -103,33 +108,48 @@ 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 - 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) @@ -140,7 +160,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 +201,7 @@ 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; + cumulus.MonthlyMySqlConn.ConnectionString = cumulus.MySqlConnSettings.ToString(); cumulus.SetMonthlySqlCreateString(); cumulus.SetStartOfMonthlyInsertSQL(); @@ -193,84 +209,62 @@ 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 = cumulus.MySqlConnSettings.ToString(); 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 = cumulus.MySqlConnSettings.ToString(); 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 = cumulus.MySqlConnSettings.ToString(); - 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 = cumulus.MySqlConnSettings.ToString(); context.Response.StatusCode = 200; } 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"; } 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; - 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; } @@ -319,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; } @@ -333,7 +327,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/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 a46349b9..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() @@ -35,6 +35,8 @@ public string GetProgramAlpacaFormData() { debuglogging = cumulus.ProgramOptions.DebugLogging, datalogging = cumulus.ProgramOptions.DataLogging, + ftplogging = cumulus.FTPlogging, + emaillogging = cumulus.SmtpOptions.Logging, stopsecondinstance = cumulus.ProgramOptions.WarnMultiple }; @@ -48,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; @@ -67,52 +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); + 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; + } - // 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.ProgramOptions.WarnMultiple = settings.options.stopsecondinstance; - } - 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; } } @@ -135,6 +150,8 @@ public class JsonProgramSettingsGeneralOptions { public bool debuglogging { get; set; } public bool datalogging { get; set; } + public bool ftplogging { get; set; } + public bool emaillogging { get; set; } public bool stopsecondinstance { get; set; } } } diff --git a/CumulusMX/Properties/AssemblyInfo.cs b/CumulusMX/Properties/AssemblyInfo.cs index a223af94..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.10.5 - Build 3122")] +[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.10.5.3122")] -[assembly: AssemblyFileVersion("3.10.5.3122")] +[assembly: AssemblyVersion("3.11.0.3129")] +[assembly: AssemblyFileVersion("3.11.0.3129")] diff --git a/CumulusMX/StationSettings.cs b/CumulusMX/StationSettings.cs index 355d3088..91159a50 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() @@ -131,15 +131,16 @@ internal string GetStationAlpacaFormData() 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 GetStationAlpacaFormData() { 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 }; @@ -224,7 +225,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 +260,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 +278,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 +418,8 @@ internal string GetStationAlpacaFormData() Forecast = forecast, Solar = solar, AnnualRainfall = annualrainfall, + GrowingDD = growingdd, + TempSum = tempsum, Graphs = graphs, DisplayOptions = displayOptions }; @@ -395,18 +428,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 +493,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 +507,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 +546,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 +575,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 { @@ -834,9 +916,10 @@ internal string UpdateStationConfig(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; } @@ -855,12 +938,12 @@ internal string UpdateStationConfig(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) @@ -959,9 +1042,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 +1163,6 @@ internal string SetSelectaChartOptions(IHttpContext context) return context.Response.StatusCode == 200 ? "success" : errorMsg; } - internal string GetWSport() { return "{\"wsport\":\"" + cumulus.wsPort + "\"}"; @@ -1105,6 +1189,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; } } @@ -1213,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; } } @@ -1246,7 +1333,6 @@ internal class JsonStationSettingsWMR928 public string comportname { get; set; } } - internal class JsonStationSettingsImet { public string comportname { get; set; } @@ -1426,6 +1512,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 +1528,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 +1546,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 +1565,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 5ab0ce3c..108c4595 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 Devart.Data.MySql; +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(); @@ -602,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()); @@ -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; } @@ -1437,6 +1443,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 +1553,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); @@ -1552,6 +1571,7 @@ private void HourChanged(DateTime now) if (now.Hour == rollHour) { DayReset(now); + cumulus.BackupData(true, now); } if (now.Hour == 0) @@ -1711,7 +1731,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 @@ -1753,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(); } @@ -2111,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 @@ -2659,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; @@ -2844,6 +2893,7 @@ public void DoOutdoorTemp(double temp, DateTime timestamp) OutdoorTemperature = CalibrateTemp(temp); double tempinF = ConvertUserTempToF(OutdoorTemperature); + double tempinC = ConvertUserTempToC(OutdoorTemperature); first_temp = false; @@ -2906,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)); @@ -2932,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) @@ -2990,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; @@ -3293,7 +3343,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 { @@ -3711,7 +3761,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) { @@ -3754,7 +3804,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; @@ -4461,7 +4511,7 @@ public void DayReset(DateTime timestamp) if (cumulus.CustomMySqlRolloverEnabled) { - cumulus.CustomMysqlRolloverTimerTick(); + _ = cumulus.CustomMysqlRolloverTimerTick(); } if (cumulus.CustomHttpRolloverEnabled) @@ -4882,7 +4932,9 @@ public void DayReset(DateTime timestamp) rainthisyear = 0; } else + { rainthisyear += RainYesterday; + } if ((day == 1) && (month == cumulus.ChillHourSeasonStart)) { @@ -4891,6 +4943,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 @@ -5139,8 +5201,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); } @@ -5237,9 +5297,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; @@ -5405,12 +5462,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(cumulus.MySqlConnSettings.ToString()); var InvC = new CultureInfo(""); @@ -5474,33 +5526,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); } } @@ -5563,7 +5589,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 @@ -5581,7 +5607,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 @@ -5599,7 +5625,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 @@ -5621,7 +5647,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 @@ -5643,7 +5669,7 @@ public double ConvertWindMPHToUser(double value) } /// - /// Converts wind in units in use to m/s + /// Converts wind in user units to m/s /// /// /// @@ -5685,7 +5711,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 @@ -8489,81 +8515,169 @@ public string GetWCloudURL(out string pwstring, DateTime timestamp) } // aq - if (cumulus.WCloud.SendAQI) + if (cumulus.WCloud.SendAirQuality) { - 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")); + // soil moisture + if (cumulus.WCloud.SendSoilMoisture) + { + // Weathercloud wants soil moisture in centibar. Davis supplies this, but Ecowitt provide a percentage + int moist = 0; - // date - sb.Append("&date=" + timestamp.ToString("yyyyMMdd")); + switch (cumulus.WCloud.SoilMoistureSensor) + { + case 1: + moist = SoilMoisture1; + break; + case 2: + moist = SoilMoisture2; + break; + case 3: + moist = SoilMoisture3; + break; + case 4: + moist = SoilMoisture4; + break; + case 5: + moist = SoilMoisture5; + break; + case 6: + moist = SoilMoisture6; + break; + case 7: + moist = SoilMoisture7; + break; + case 8: + moist = SoilMoisture8; + break; + case 9: + moist = SoilMoisture9; + break; + case 10: + moist = SoilMoisture10; + break; + case 11: + moist = SoilMoisture11; + break; + case 12: + moist = SoilMoisture12; + break; + case 13: + moist = SoilMoisture13; + break; + case 14: + moist = SoilMoisture14; + break; + case 15: + moist = SoilMoisture15; + break; + case 16: + moist = SoilMoisture16; + break; + } + + if (cumulus.Manufacturer == cumulus.EW) + { + // very! approximate conversion from percentage to cb + moist = (100 - SoilMoisture1) * 2; + } + + sb.Append($"&soilmoist={moist}"); + } + + // leaf wetness + if (cumulus.WCloud.SendLeafWetness) + { + // Weathercloud wants soil moisture in centibar. Davis supplies this, but Ecowitt provide a percentage + int wet = 0; + + switch (cumulus.WCloud.LeafWetnessSensor) + { + case 1: + wet = LeafWetness1; + break; + case 2: + wet = LeafWetness2; + break; + case 3: + wet = LeafWetness3; + break; + case 4: + wet = LeafWetness4; + break; + case 5: + wet = LeafWetness5; + break; + case 6: + wet = LeafWetness6; + break; + case 7: + wet = LeafWetness7; + break; + case 8: + wet = LeafWetness8; + break; + } + + sb.Append($"&leafwet={wet}"); + } + + // time - UTC + sb.Append("&time=" + timestamp.ToUniversalTime().ToString("HHmm")); + + // date - UTC + sb.Append("&date=" + timestamp.ToUniversalTime().ToString("yyyyMMdd")); // software identification - sb.Append("&type=291&ver=" + cumulus.Version); + //sb.Append("&type=291&ver=" + cumulus.Version); + sb.Append($"&software=Cumulus_MX_v{cumulus.Version}&softwareid=142787ebe716"); 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) @@ -8954,6 +9068,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}\","); @@ -8964,7 +9142,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)}"); @@ -10207,7 +10385,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(); @@ -10263,7 +10441,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 @@ -10350,7 +10528,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(); @@ -10413,7 +10591,7 @@ public string GetAvailGraphData() //if (cumulus.GraphOptions.HumidexVisible) // json.Append("\"Humidex\","); - if (json.ToString().EndsWith(",")) + if (json[json.Length - 1] == ',') json.Length--; // humidity values @@ -10425,7 +10603,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 @@ -10449,7 +10627,7 @@ public string GetAvailGraphData() if (cumulus.GraphOptions.DailyMinTempVisible) json.Append("\"MinTemp\","); - if (json.ToString().EndsWith(",")) + if (json[json.Length - 1] == ',') json.Length--; json.Append("]"); @@ -10467,7 +10645,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("]"); @@ -10497,6 +10675,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(); } @@ -11111,6 +11322,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); @@ -11263,6 +11859,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/CumulusMX/packages.config b/CumulusMX/packages.config index 2d1e9c89..41c606b4 100644 --- a/CumulusMX/packages.config +++ b/CumulusMX/packages.config @@ -1,24 +1,29 @@  - + + - + + + + - + - + + \ No newline at end of file diff --git a/CumulusMX/webtags.cs b/CumulusMX/webtags.cs index b472b62f..6f957fa1 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") @@ -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) @@ -3105,7 +3187,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) @@ -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 11fe04a1..c827927d 100644 --- a/Updates.txt +++ b/Updates.txt @@ -1,3 +1,75 @@ +3.11.0 - b3129 +—————————————— +- Fix: Remove THW Index in /web/websitedataT.json, and default web site index.htm +- Fix: OpenWeatherMap - create new station was not forcing the use of dot decimals for lat/long values +- Fix: WeatherCloud - date/time of reading should be UTC +- Fix: End of day backup now always runs at rollover +- Fix: FineOffset stations were not ignoring invalid wind speeds on historic catch up +- Fix: FineOffset stations historic catch up was failing when it reached a logger memory wrap around +- Fix: FineOffset stations historic catch up is now limited to the number of logger entries the console says are recorded +- Fix: FineOffset synchronise reads process improvements (attempt) +- Fix: Catch some errors that could occur in the start-up PING process +- Fix: MySQL connections that use encryption should now be supported (MySQL 8.0+ default) +- Fix: GW1000 System Information now correctly decodes 915 and 920MHz devices +- Fix: GW1000 occasional crash when an unexpected response was received +- Fix: All settings screens error handling messages now no longer display "[object object]" + +- New: Adds support for a THW Index calculation. This value is now available for all station types via the web tag <#THWindex> +- New: Adds support for Windguru uploads +- New: Adds Email support for Alarms + - Configured via Internet Settings (email server config), and Alarm Settings (from/to addresses for alarms, and a Test Email function) + - Creates a new section in Cumulus.ini: + [SMTP] + ServerName= + Port=587 + SSLOption=1 + RequiresAuthentication=0 + User= + Password= + Logging=0 + - Adds to the Alarms section in Cumulus.ini + [Alarms] + xxxxxAlarmEmail=0 + FromEmail= + DestEmail= + - Creates a new section in strings.ini, with email text + [AlarmEmails] +- New: WeatherCloud now supports uploading of Air Quality, Soil Moisture, and Leaf Wetness +- New: Adds support for two sets of Growing Degree Days data + - Configured via Station Settings|Growing Degree Days, visibility via Station Settings|Graphs|Data Series Visibility|Degree Days +- New: Adds support for Temperature Sum - annual running total of daily average temperatures + - Configured via Station Settings|Temperature Sum, visibility via Station Settings|Graphs|Data Series Visibility|Temperature Data +- New: The graphs pages - both dashboard and default web site - now set a page hash value depending on the graph being shown. + Benefit = you can now link directly to a particular graph to show on page load +- New: FineOffset stations now report a warning if the console logger interval does match the Cumulus MX interval +- New: FineOffset experimental feature to set the console logger interval to match Cumulus logging interval +- New: JavaScript encoded web tags. These tags were previously only available as HTML entity encoded strings: + <#latitudeJsEnc>, <#longitudeJsEnc> + <#locationJsEnc>, <#longlocationJsEnc> + <#forecastJsEnc>, <#cumulusforecastJsEnc>, <#wsforecastJsEnc> + <#currcondJsEnc>, +- New: Web tags with no HTML entity encoding: + <#tempunitnoenc>, <#altitudenoenc> + +- Changed: Moved FTP Logging option from Internet Settings to Program Settings to collect all the logging options in one place +- Changed: A Data Stopped state now stops all logging, MySQL and web activity +- Changed: Updates to various library components... + - Updated to latest versions: + - FTP: FluentFTP + - JSON: ServiceStack.Text + - Pointers: System.Runtime.CompilerServices.Unsafe + - MQTT: MQTTnet + - Removed/Added + - MySQL: Removed Devart, replaced with MySqlConnector + - MailKit: Added along with supporting packages MimeKit, and Portable.BouncyCastle +- Changed: FineOffset stations now attempt to reconnect the USB after a data stopped event. These stations now also keep retrying to connect to the station at start-up +- Changed: The MySQL real time table data retention settings reworked. +- Changed: Moved all third party web uploads from Internet Settings page to their own Third Party Settings page +- Changed: Internal optimisations +- Changed: JQuery library updated to v3.6.0 for both the Dashboard interface, and the default web site + + + 3.10.5 - b3122 —————————————— - Fix: Comma decimal issues with OpenWeatherMap and other third party uploads