diff --git a/CumulusMX/AlarmSettings.cs b/CumulusMX/AlarmSettings.cs index 3416e487..fe880535 100644 --- a/CumulusMX/AlarmSettings.cs +++ b/CumulusMX/AlarmSettings.cs @@ -154,7 +154,6 @@ public string GetAlarmSettings() return retObject.ToJson(); } - //public string UpdateNoaaConfig(HttpListenerContext context) public string UpdateAlarmSettings(IHttpContext context) { try diff --git a/CumulusMX/Api.cs b/CumulusMX/Api.cs index cf55ae7b..77d21fd2 100644 --- a/CumulusMX/Api.cs +++ b/CumulusMX/Api.cs @@ -6,8 +6,8 @@ using System.Threading.Tasks; using System.Web; using Unosquare.Labs.EmbedIO; -using Unosquare.Labs.EmbedIO.Modules; using Unosquare.Labs.EmbedIO.Constants; +using Unosquare.Labs.EmbedIO.Modules; namespace CumulusMX { @@ -15,7 +15,8 @@ public static class Api { private const string RelativePath = "/api/"; internal static WeatherStation Station; - public static StationSettings stationSettings; + public static ProgramSettings programSettings; + internal static StationSettings stationSettings; public static InternetSettings internetSettings; public static ExtraSensorSettings extraSensorSettings; public static CalibrationSettings calibrationSettings; @@ -48,22 +49,21 @@ public static void Setup(WebServer server) server.Module().RegisterController(); server.Module().RegisterController(); server.Module().RegisterController(); - server.Module().RegisterController(); - server.Module().RegisterController(); - server.Module().RegisterController(); - server.Module().RegisterController(); + server.Module().RegisterController(); + server.Module().RegisterController(); server.Module().RegisterController(); server.Module().RegisterController(); } - public class EditControllerGet : WebApiController + // Get/Post Edit data + public class EditController : WebApiController { - public EditControllerGet(IHttpContext context) : base(context) + public EditController(IHttpContext context) : base(context) { } [WebApiHandler(HttpVerbs.Get, RelativePath + "edit/*")] - public async Task EditData() + public async Task GetEditData() { try { @@ -126,28 +126,8 @@ public async Task EditData() } } - private async Task HandleError(Exception ex, int statusCode) - { - var errorResponse = new - { - Title = "Unexpected Error", - ErrorCode = ex.GetType().Name, - Description = ex.Message, - }; - - this.Response.StatusCode = statusCode; - return await this.JsonResponseAsync(errorResponse); - } - } - - public class EditControllerPost : WebApiController - { - public EditControllerPost(IHttpContext context) : base(context) - { - } - [WebApiHandler(HttpVerbs.Post, RelativePath + "edit/*")] - public async Task EditData() + public async Task PostEditData() { try { @@ -212,6 +192,8 @@ private async Task HandleError(Exception ex, int statusCode) } } + + // Get log and diary Data public class DataController : WebApiController { public DataController(IHttpContext context) : base(context) @@ -272,7 +254,7 @@ private async Task HandleError(Exception ex, int statusCode) } } - + // Get/Post Tag body data public class TagController : WebApiController { public TagController(IHttpContext context) : base(context) @@ -323,8 +305,6 @@ public async Task GetTags() } } - - private async Task HandleError(Exception ex, int statusCode) { var errorResponse = new @@ -339,6 +319,7 @@ private async Task HandleError(Exception ex, int statusCode) } } + // Get recent/daily graph data public class GraphDataController : WebApiController { public GraphDataController(IHttpContext context) : base(context) @@ -391,7 +372,6 @@ public async Task GetGraphData() } } - [WebApiHandler(HttpVerbs.Get, RelativePath + "dailygraphdata/*")] public async Task GetDailyGraphData() { @@ -430,7 +410,6 @@ public async Task GetDailyGraphData() } } - private async Task HandleError(Exception ex, int statusCode) { var errorResponse = new @@ -445,6 +424,7 @@ private async Task HandleError(Exception ex, int statusCode) } } + // Get Records data public class RecordsController : WebApiController { public RecordsController(IHttpContext context) : base(context) @@ -587,6 +567,7 @@ private async Task HandleError(Exception ex, int statusCode) } } + // Get today/yesterday data public class TodayYestDataController : WebApiController { public TodayYestDataController(IHttpContext context) : base(context) {} @@ -637,6 +618,7 @@ private async Task HandleError(Exception ex, int statusCode) } } + // Get Extra data public class ExtraDataController : WebApiController { public ExtraDataController(IHttpContext context) : base(context) { } @@ -709,71 +691,10 @@ private async Task HandleError(Exception ex, int statusCode) } } - public class SetSettingsController : WebApiController - { - public SetSettingsController(IHttpContext context) : base(context) { } - - [WebApiHandler(HttpVerbs.Post, RelativePath + "setsettings/*")] - public async Task SettingsSet() - { - try - { - // read the last segment of the URL to determine what data the caller wants - var lastSegment = Request.Url.Segments.Last(); - - switch (lastSegment) - { - case "updatestationconfig.json": - return await this.JsonResponseAsync(stationSettings.UpdateStationConfig(this)); - case "updateinternetconfig.json": - return await this.JsonResponseAsync(internetSettings.UpdateInternetConfig(this)); - case "updateextrasensorconfig.json": - return await this.JsonResponseAsync(extraSensorSettings.UpdateExtraSensorConfig(this)); - case "updatecalibrationconfig.json": - return await this.JsonResponseAsync(calibrationSettings.UpdateCalibrationConfig(this)); - case "updatenoaaconfig.json": - return await this.JsonResponseAsync(noaaSettings.UpdateNoaaConfig(this)); - case "updateextrawebfiles.html": - return await this.JsonResponseAsync(internetSettings.UpdateExtraWebFiles(this)); - case "updatemysqlconfig.json": - return await this.JsonResponseAsync(mySqlSettings.UpdateMysqlConfig(this)); - case "createmonthlysql.json": - return await this.JsonResponseAsync(mySqlSettings.CreateMonthlySQL(this)); - case "createdayfilesql.json": - return await this.JsonResponseAsync(mySqlSettings.CreateDayfileSQL(this)); - case "createrealtimesql.json": - return await this.JsonResponseAsync(mySqlSettings.CreateRealtimeSQL(this)); - case "updatealarmconfig.json": - return await this.JsonResponseAsync(alarmSettings.UpdateAlarmSettings(this)); - case "ftpnow.json": - return await this.JsonResponseAsync(stationSettings.FtpNow()); - } - - throw new KeyNotFoundException("Key Not Found: " + lastSegment); - } - catch (Exception ex) - { - return await HandleError(ex, 404); - } - } - - private async Task HandleError(Exception ex, int statusCode) - { - var errorResponse = new - { - Title = "Unexpected Error", - ErrorCode = ex.GetType().Name, - Description = ex.Message, - }; - - this.Response.StatusCode = statusCode; - return await this.JsonResponseAsync(errorResponse); - } - } - - public class GetSettingsController : WebApiController + // Get/Post settings data + public class SettingsController : WebApiController { - public GetSettingsController(IHttpContext context) : base(context) { } + public SettingsController(IHttpContext context) : base(context) { } [WebApiHandler(HttpVerbs.Get, RelativePath + "settings/*")] public async Task SettingsGet() @@ -811,6 +732,13 @@ public async Task SettingsGet() switch (lastSegment) { + case "programdata.json": + return await this.JsonResponseAsync(programSettings.GetProgramAlpacaFormData()); + case "programoptions.json": + return await this.JsonResponseAsync(programSettings.GetProgramAlpacaFormOptions()); + case "programschema.json": + return await this.JsonResponseAsync(programSettings.GetProgramAlpacaFormSchema()); + case "stationdata.json": return await this.JsonResponseAsync(stationSettings.GetStationAlpacaFormData()); case "stationoptions.json": @@ -872,6 +800,53 @@ public async Task SettingsGet() } } + [WebApiHandler(HttpVerbs.Post, RelativePath + "setsettings/*")] + public async Task SettingsSet() + { + try + { + // read the last segment of the URL to determine what data the caller wants + var lastSegment = Request.Url.Segments.Last(); + + switch (lastSegment) + { + case "updateprogramconfig.json": + return await this.JsonResponseAsync(programSettings.UpdateProgramConfig(this)); + + case "updatestationconfig.json": + return await this.JsonResponseAsync(stationSettings.UpdateStationConfig(this)); + case "updateinternetconfig.json": + return await this.JsonResponseAsync(internetSettings.UpdateInternetConfig(this)); + case "updateextrasensorconfig.json": + return await this.JsonResponseAsync(extraSensorSettings.UpdateExtraSensorConfig(this)); + case "updatecalibrationconfig.json": + return await this.JsonResponseAsync(calibrationSettings.UpdateCalibrationConfig(this)); + case "updatenoaaconfig.json": + return await this.JsonResponseAsync(noaaSettings.UpdateNoaaConfig(this)); + case "updateextrawebfiles.html": + return await this.JsonResponseAsync(internetSettings.UpdateExtraWebFiles(this)); + case "updatemysqlconfig.json": + return await this.JsonResponseAsync(mySqlSettings.UpdateMysqlConfig(this)); + case "createmonthlysql.json": + return await this.JsonResponseAsync(mySqlSettings.CreateMonthlySQL(this)); + case "createdayfilesql.json": + return await this.JsonResponseAsync(mySqlSettings.CreateDayfileSQL(this)); + case "createrealtimesql.json": + return await this.JsonResponseAsync(mySqlSettings.CreateRealtimeSQL(this)); + case "updatealarmconfig.json": + return await this.JsonResponseAsync(alarmSettings.UpdateAlarmSettings(this)); + case "ftpnow.json": + return await this.JsonResponseAsync(stationSettings.FtpNow(this)); + } + + throw new KeyNotFoundException("Key Not Found: " + lastSegment); + } + catch (Exception ex) + { + return await HandleError(ex, 404); + } + } + private async Task HandleError(Exception ex, int statusCode) { var errorResponse = new @@ -886,6 +861,7 @@ private async Task HandleError(Exception ex, int statusCode) } } + // Get reports data public class ReportsController : WebApiController { public ReportsController(IHttpContext context) : base(context) diff --git a/CumulusMX/Cumulus.cs b/CumulusMX/Cumulus.cs index 0c26a255..40e828f3 100644 --- a/CumulusMX/Cumulus.cs +++ b/CumulusMX/Cumulus.cs @@ -8,6 +8,7 @@ using System.Net; using System.Net.Http; using System.Net.Sockets; +using System.Net.NetworkInformation; using System.Reflection; using System.Runtime.InteropServices; using System.Security.Authentication; @@ -31,8 +32,8 @@ namespace CumulusMX public class Cumulus { ///////////////////////////////// - public string Version = "3.9.3"; - public string Build = "3098"; + public string Version = "3.9.4"; + public string Build = "3099"; ///////////////////////////////// public static SemaphoreSlim syncInit = new SemaphoreSlim(1); @@ -197,6 +198,8 @@ public struct TExtraFiles public DateTime LastUpdateTime; + public PerformanceCounter UpTime; + private readonly WebTags webtags; private readonly TokenParser tokenParser; private readonly TokenParser realtimeTokenParser; @@ -493,6 +496,7 @@ public struct TExtraFiles private List WindyList = new List(); private List PWSList = new List(); private List WOWList = new List(); + private List OWMList = new List(); private List MySqlList = new List(); @@ -512,6 +516,8 @@ public struct TExtraFiles /// public Spikes Spike = new Spikes(); + public ProgramOptions ProgramOptions = new ProgramOptions(); + public StationOptions StationOptions = new StationOptions(); public GraphOptions GraphOptions = new GraphOptions(); @@ -548,6 +554,9 @@ public struct TExtraFiles // WeatherCloud settings public WebUploadService WCloud = new WebUploadService(); + // OpenWeatherMap settings + public WebUploadService OpenWeatherMap = new WebUploadService(); + // MQTT settings public struct MqttSettings @@ -604,6 +613,7 @@ public struct MqttSettings public bool IsOSX; public double CPUtemp = -999; + // Alarms public Alarm DataStoppedAlarm = new Alarm(); public Alarm BatteryLowAlarm = new Alarm(); public Alarm SensorAlarm = new Alarm(); @@ -899,8 +909,6 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) LogMessage("OS version: " + Environment.OSVersion); - GetLatestVersion(); - Type type = Type.GetType("Mono.Runtime"); if (type != null) { @@ -909,6 +917,13 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) LogMessage("Mono version: "+displayName.Invoke(null, null)); } + // determine system uptime based on OS + if (Platform.Substring(0, 3) == "Win") + { + // Windows enable the performance counter method + UpTime = new PerformanceCounter("System", "System Up Time"); + } + LogMessage("Current culture: " + CultureInfo.CurrentCulture.DisplayName); ListSeparator = CultureInfo.CurrentCulture.TextInfo.ListSeparator; @@ -984,10 +999,12 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) APRS.DefaultInterval = 9; AWEKAS.DefaultInterval = 15 * 60; WCloud.DefaultInterval = 10; + OpenWeatherMap.DefaultInterval = 15; ReadIniFile(); - if (StationOptions.WarnMultiple && !Program.appMutex.WaitOne(0, false)) + // Do we prevent more than one copy of CumulusMX running? + if (ProgramOptions.WarnMultiple && !Program.appMutex.WaitOne(0, false)) { LogConsoleMessage("Cumulus is already running - terminating"); LogConsoleMessage("Program exit"); @@ -996,39 +1013,110 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) Environment.Exit(1); } + // Do we wait for a ping response from a remote host before starting? + if (!string.IsNullOrWhiteSpace(ProgramOptions.StartupPingHost)) + { + var msg1 = $"Waiting for PING reply from {ProgramOptions.StartupPingHost}"; + var msg2 = $"Received PING response from {ProgramOptions.StartupPingHost}, continuing..."; + LogConsoleMessage(msg1); + LogMessage(msg1); + using (var ping = new Ping()) + { + PingReply reply; + try + { + do + { + reply = ping.Send(ProgramOptions.StartupPingHost, 5000); // 5 second timeout + } while (reply.Status != IPStatus.Success); + } + catch(Exception e) + { + LogErrorMessage($"PING to {ProgramOptions.StartupPingHost} failed with error: {e.InnerException.Message}"); + } + } + LogConsoleMessage(msg2); + LogMessage(msg2); + } + else + { + LogMessage("No start-up PING"); + } + + // Do we delay the start of Cumulus MX for a fixed period? + if (ProgramOptions.StartupDelaySecs > 0) + { + // Check uptime + double ts = 0; + if (Platform.Substring(0, 3) == "Win") + { + UpTime.NextValue(); + ts = UpTime.NextValue(); + } + else if (File.Exists(@"/proc/uptime")) + { + var text = File.ReadAllText(@"/proc/uptime"); + var strTime = text.Split(' ')[0]; + double.TryParse(strTime, out ts); + } + + // Only delay if the delay uptime is undefined (0), or the current uptime is less than the user specified max uptime to apply the delay + LogMessage($"System uptime = {(int)ts} secs"); + if (ProgramOptions.StartupDelayMaxUptime == 0 || ProgramOptions.StartupDelayMaxUptime > ts) + { + var msg1 = $"Delaying start for {ProgramOptions.StartupDelaySecs} seconds"; + var msg2 = $"Start-up delay complete, continuing..."; + LogConsoleMessage(msg1); + LogMessage(msg1); + Thread.Sleep(ProgramOptions.StartupDelaySecs * 1000); + LogConsoleMessage(msg2); + LogMessage(msg2); + } + else + { + LogMessage("No start-up delay, max uptime exceeded"); + } + } + else + { + LogMessage("No start-up delay - disabled"); + } + + GetLatestVersion(); + GC.Collect(); remoteGraphdataFiles = new[] - { - "graphconfig.json", - "tempdata.json", - "pressdata.json", - "winddata.json", - "wdirdata.json", - "humdata.json", - "raindata.json", - "dailyrain.json", - "dailytemp.json", - "solardata.json", - "sunhours.json", - "airquality.json" - }; + { + "graphconfig.json", + "tempdata.json", + "pressdata.json", + "winddata.json", + "wdirdata.json", + "humdata.json", + "raindata.json", + "dailyrain.json", + "dailytemp.json", + "solardata.json", + "sunhours.json", + "airquality.json" + }; localGraphdataFiles = new[] - { - "web" + DirectorySeparator + remoteGraphdataFiles[0], // 0 - "web" + DirectorySeparator + remoteGraphdataFiles[1], // 1 - "web" + DirectorySeparator + remoteGraphdataFiles[2], // 2 - "web" + DirectorySeparator + remoteGraphdataFiles[3], // 3 - "web" + DirectorySeparator + remoteGraphdataFiles[4], // 4 - "web" + DirectorySeparator + remoteGraphdataFiles[5], // 5 - "web" + DirectorySeparator + remoteGraphdataFiles[6], // 6 - "web" + DirectorySeparator + remoteGraphdataFiles[7], // 7 - "web" + DirectorySeparator + remoteGraphdataFiles[8], // 8 - "web" + DirectorySeparator + remoteGraphdataFiles[9], // 9 - "web" + DirectorySeparator + remoteGraphdataFiles[10], // 10 - "web" + DirectorySeparator + remoteGraphdataFiles[11] // 11 - }; + { + "web" + DirectorySeparator + remoteGraphdataFiles[0], // 0 + "web" + DirectorySeparator + remoteGraphdataFiles[1], // 1 + "web" + DirectorySeparator + remoteGraphdataFiles[2], // 2 + "web" + DirectorySeparator + remoteGraphdataFiles[3], // 3 + "web" + DirectorySeparator + remoteGraphdataFiles[4], // 4 + "web" + DirectorySeparator + remoteGraphdataFiles[5], // 5 + "web" + DirectorySeparator + remoteGraphdataFiles[6], // 6 + "web" + DirectorySeparator + remoteGraphdataFiles[7], // 7 + "web" + DirectorySeparator + remoteGraphdataFiles[8], // 8 + "web" + DirectorySeparator + remoteGraphdataFiles[9], // 9 + "web" + DirectorySeparator + remoteGraphdataFiles[10], // 10 + "web" + DirectorySeparator + remoteGraphdataFiles[11] // 11 + }; remoteDailyGraphdataFiles = new[] { @@ -1085,8 +1173,8 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) Backupdata(false, DateTime.Now); - LogMessage("Debug logging is " + (StationOptions.DebugLogging ? "enabled" : "disabled")); - LogMessage("Data logging is " + (StationOptions.DataLogging ? "enabled" : "disabled")); + LogMessage("Debug logging is " + (ProgramOptions.DebugLogging ? "enabled" : "disabled")); + LogMessage("Data logging is " + (ProgramOptions.DataLogging ? "enabled" : "disabled")); LogMessage("FTP logging is " + (FTPlogging ? "enabled" : "disabled")); LogMessage("Spike logging is " + (ErrorLogSpikeRemoval ? "enabled" : "disabled")); LogMessage("Logging interval = " + logints[DataLogInterval] + " mins"); @@ -1316,7 +1404,8 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) // Set up the API web server Api.Setup(httpServer); Api.Station = station; - Api.stationSettings = new StationSettings(this); + Api.programSettings = new ProgramSettings(this); + Api.stationSettings = new StationSettings(this, station); Api.internetSettings = new InternetSettings(this); Api.extraSensorSettings = new ExtraSensorSettings(this); Api.calibrationSettings = new CalibrationSettings(this); @@ -1351,6 +1440,7 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) AwekasTimer.Elapsed += AwekasTimerTick; WCloudTimer.Elapsed += WCloudTimerTick; APRStimer.Elapsed += APRSTimerTick; + OpenWeatherMapTimer.Elapsed += OpenWeatherMapTimerTick; WebTimer.Elapsed += WebTimerTick; xapsource = "sanday.cumulus." + Environment.MachineName; @@ -1394,6 +1484,7 @@ public Cumulus(int HTTPport, bool DebugEnabled, string startParms) // If enabled generate the daily graph data files, and upload at first opportunity if (IncludeGraphDataFiles) { + LogDebugMessage("Generating the daily graph data files"); station.CreateEodGraphDataFiles(); DailyGraphDataFilesNeedFTP = true; } @@ -1905,6 +1996,12 @@ private void WCloudTimerTick(object sender, ElapsedEventArgs e) 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"); @@ -2125,11 +2222,11 @@ internal async void UpdateWCloud(DateTime timestamp) { HttpResponseMessage response = await WCloudhttpClient.GetAsync(url); var responseBodyAsText = await response.Content.ReadAsStringAsync(); - LogMessage("WeatherCloud Response: " + response.StatusCode + ": " + responseBodyAsText); + LogDebugMessage("WeatherCloud Response: " + response.StatusCode + ": " + responseBodyAsText); } catch (Exception ex) { - LogMessage("WeatherCloud update: " + ex.Message); + LogDebugMessage("WeatherCloud update: " + ex.Message); } finally { @@ -2139,6 +2236,160 @@ internal async void UpdateWCloud(DateTime timestamp) } + internal async void UpdateOpenWeatherMap(DateTime timestamp) + { + if (!OpenWeatherMap.Updating) + { + OpenWeatherMap.Updating = true; + + string url = "http://api.openweathermap.org/data/3.0/measurements?appid=" + OpenWeatherMap.PW; + string logUrl = url.Replace(OpenWeatherMap.PW, ""); + + string jsonData = station.GetOpenWeatherMapData(timestamp); + + LogDebugMessage("OpenWeatherMap: URL = " + logUrl); + LogDataMessage("OpenWeatherMap: Body = " + jsonData); + + try + { + using (var client = new HttpClient()) + { + var data = new StringContent(jsonData, Encoding.UTF8, "application/json"); + HttpResponseMessage response = await client.PostAsync(url, data); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + var status = response.StatusCode == HttpStatusCode.NoContent ? "OK" : "Error"; // Returns a 204 reponse for OK! + LogMessage($"OpenWeatherMap: Response code = {status} - {response.StatusCode}"); + if (response.StatusCode != HttpStatusCode.NoContent) + LogDataMessage($"OpenWeatherMap: Response data = {responseBodyAsText}"); + } + } + catch (Exception ex) + { + LogMessage("OpenWeatherMap: Update error = " + ex.Message); + } + finally + { + OpenWeatherMap.Updating = false; + } + } + } + + // Find all stations associated with the users API key + internal OpenWeatherMapStation[] GetOpenWeatherMapStations() + { + OpenWeatherMapStation[] retVal = new OpenWeatherMapStation[0]; + string url = "http://api.openweathermap.org/data/3.0/stations?appid=" + OpenWeatherMap.PW; + try + { + using (var client = new HttpClient()) + { + HttpResponseMessage response = client.GetAsync(url).Result; + var responseBodyAsText = response.Content.ReadAsStringAsync().Result; + LogDataMessage("WeatherCloud Response: " + response.StatusCode + ": " + responseBodyAsText); + + if (responseBodyAsText.Length > 10) + { + var respJson = JsonSerializer.DeserializeFromString(responseBodyAsText); + retVal = respJson; + } + } + } + catch (Exception ex) + { + LogMessage("OpenWeatherMap: Get stations - " + ex.Message); + } + + return retVal; + } + + // Create a new OpenWeatherMap station + internal void CreateOpenWeatherMapStation() + { + 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($"\"altitude\":{(int)station.AltitudeM(Altitude)}}}"); + + LogMessage($"OpenWeatherMap: Creating new station"); + LogMessage($"OpenWeatherMap: - {sb}"); + + + using (var client = new HttpClient()) + { + var data = new StringContent(sb.ToString(), Encoding.UTF8, "application/json"); + + HttpResponseMessage response = client.PostAsync(url, data).Result; + var responseBodyAsText = response.Content.ReadAsStringAsync().Result; + var status = response.StatusCode == HttpStatusCode.Created ? "OK" : "Error"; // Returns a 201 reponse for OK + LogDebugMessage($"OpenWeatherMap: Create station response code = {status} - {response.StatusCode}"); + LogDataMessage($"OpenWeatherMap: Create station response data = {responseBodyAsText}"); + + if (response.StatusCode == HttpStatusCode.Created) + { + // It worked, save the result + var respJson = JsonSerializer.DeserializeFromString(responseBodyAsText); + + LogMessage($"OpenWeatherMap: Created new station, id = {respJson.ID}, name = {respJson.name}"); + OpenWeatherMap.ID = respJson.ID; + WriteIniFile(); + } + else + { + LogMessage($"OpenWeatherMap: Failed to create new station. Error - {response.StatusCode}, text - {responseBodyAsText}"); + } + } + } + catch (Exception ex) + { + LogMessage("OpenWeatherMap: Create station - " + ex.Message); + } + } + + internal void EnableOpenWeatherMap() + { + if (OpenWeatherMap.Enabled && string.IsNullOrWhiteSpace(OpenWeatherMap.ID)) + { + // oh, oh! OpenWeatherMap is enabled, but we do not have a station id + // first check if one already exists + var stations = GetOpenWeatherMapStations(); + + if (stations.Length == 0) + { + // No stations defined, we will create one + LogMessage($"OpenWeatherMap: No station defined, attempting to create one"); + CreateOpenWeatherMapStation(); + } + else if (stations.Length == 1) + { + // We have one station defined, lets use it! + LogMessage($"OpenWeatherMap: No station defined, but found one associated with this API key, using this station - {stations[0].id} : {stations[0].name}"); + OpenWeatherMap.ID = stations[0].id; + // save the setting + WriteIniFile(); + } + else + { + // multiple stations defined, the user must select which one to use + var msg = $"Multiple OpenWeatherMap stations found, please select the correct station id and enter it into your configuration"; + Console.WriteLine(msg); + LogMessage("OpenWeatherMap: " + msg); + foreach (var station in stations) + { + msg = $" Station Id = {station.id}, Name = {station.name}"; + Console.WriteLine(msg); + LogMessage("OpenWeatherMap: " + msg); + } + } + + OpenWeatherMapTimer.Enabled = OpenWeatherMap.Enabled && !OpenWeatherMap.SynchronisedUpdate; + } + } + internal void RealtimeTimerTick(object sender, ElapsedEventArgs elapsedEventArgs) { bool connectionFailed = false; @@ -3154,6 +3405,21 @@ private void ReadIniFile() } } + ProgramOptions.StartupPingHost = ini.GetValue("Program", "StartupPingHost", ""); + ProgramOptions.StartupDelaySecs = ini.GetValue("Program", "StartupDelaySecs", 0); + ProgramOptions.StartupDelayMaxUptime = ini.GetValue("Program", "StartupDelayMaxUptime", 300); + ProgramOptions.WarnMultiple = ini.GetValue("Station", "WarnMultiple", true); + if (DebuggingEnabled) + { + ProgramOptions.DebugLogging = true; + ProgramOptions.DataLogging = true; + } + else + { + ProgramOptions.DebugLogging = ini.GetValue("Station", "Logging", false); + ProgramOptions.DataLogging = ini.GetValue("Station", "DataLogging", false); + } + StationType = ini.GetValue("Station", "Type", -1); StationModel = ini.GetValue("Station", "Model", ""); @@ -3430,23 +3696,10 @@ private void ReadIniFile() special_logging = ini.GetValue("Station", "SpecialLog", false); solar_logging = ini.GetValue("Station", "SolarLog", false); - if (DebuggingEnabled) - { - StationOptions.DebugLogging = true; - StationOptions.DataLogging = true; - } - else - { - StationOptions.DebugLogging = ini.GetValue("Station", "Logging", false); - StationOptions.DataLogging = ini.GetValue("Station", "DataLogging", false); - } - VP2ConnectionType = ini.GetValue("Station", "VP2ConnectionType", VP2SERIALCONNECTION); VP2TCPPort = ini.GetValue("Station", "VP2TCPPort", 22222); VP2IPAddr = ini.GetValue("Station", "VP2IPAddr", "0.0.0.0"); - StationOptions.WarnMultiple = ini.GetValue("Station", "WarnMultiple", true); - VPClosedownTime = ini.GetValue("Station", "VPClosedownTime", 99999999); VP2SleepInterval = ini.GetValue("Station", "VP2SleepInterval", 0); @@ -3646,7 +3899,7 @@ private void ReadIniFile() //WindyHTTPLogging = ini.GetValue("Windy", "Logging", false); Windy.SendUV = ini.GetValue("Windy", "SendUV", false); Windy.SendSolar = ini.GetValue("Windy", "SendSolar", false); - Windy.CatchUp = ini.GetValue("Windy", "CatchUp", true); + Windy.CatchUp = ini.GetValue("Windy", "CatchUp", false); Windy.SynchronisedUpdate = (60 % Windy.Interval == 0); @@ -3724,6 +3977,14 @@ private void ReadIniFile() 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 @@ -3908,6 +4169,8 @@ private void ReadIniFile() UseBlakeLarsen = ini.GetValue("Solar", "UseBlakeLarsen", false); SolarCalc = ini.GetValue("Solar", "SolarCalc", 0); BrasTurbidity = ini.GetValue("Solar", "BrasTurbidity", 2.0); + //SolarFactorSummer = ini.GetValue("Solar", "SolarFactorSummer", -1); + //SolarFactorWinter = ini.GetValue("Solar", "SolarFactorWinter", -1); NOAAname = ini.GetValue("NOAA", "Name", " "); NOAAcity = ini.GetValue("NOAA", "City", " "); @@ -4072,6 +4335,12 @@ internal void WriteIniFile() IniFile ini = new IniFile("Cumulus.ini"); + ini.SetValue("Program", "StartupPingHost", ProgramOptions.StartupPingHost); + ini.SetValue("Program", "StartupDelaySecs", ProgramOptions.StartupDelaySecs); + ini.SetValue("Program", "StartupDelayMaxUptime", ProgramOptions.StartupDelayMaxUptime); + ini.SetValue("Station", "WarnMultiple", ProgramOptions.WarnMultiple); + + ini.SetValue("Station", "Type", StationType); ini.SetValue("Station", "Model", StationModel); ini.SetValue("Station", "ComportName", ComportName); @@ -4122,7 +4391,6 @@ internal void WriteIniFile() ini.SetValue("Station", "VP2ConnectionType", VP2ConnectionType); ini.SetValue("Station", "VP2TCPPort", VP2TCPPort); ini.SetValue("Station", "VP2IPAddr", VP2IPAddr); - ini.SetValue("Station", "WarnMultiple", StationOptions.WarnMultiple); ini.SetValue("Station", "RoundWindSpeed", StationOptions.RoundWindSpeed); ini.SetValue("Station", "PrimaryAqSensor", StationOptions.PrimaryAqSensor); ini.SetValue("Station", "VP2PeriodicDisconnectInterval", VP2PeriodicDisconnectInterval); @@ -4356,6 +4624,11 @@ internal void WriteIniFile() ini.SetValue("APRS", "SendSR", APRS.SendSolar); ini.SetValue("APRS", "APRSHumidityCutoff", APRS.HumidityCutoff); + ini.SetValue("OpenWeatherMap", "Enabled", OpenWeatherMap.Enabled); + ini.SetValue("OpenWeatherMap", "CatchUp", OpenWeatherMap.CatchUp); + ini.SetValue("OpenWeatherMap", "APIkey", OpenWeatherMap.PW); + ini.SetValue("OpenWeatherMap", "StationId", OpenWeatherMap.ID); + ini.SetValue("OpenWeatherMap", "Interval", OpenWeatherMap.Interval); ini.SetValue("MQTT", "Server", MQTT.Server); ini.SetValue("MQTT", "Port", MQTT.Port); @@ -4947,6 +5220,9 @@ private void ReadStringsFile() public double BrasTurbidity { get; set; } + //public double SolarFactorSummer { get; set; } + //public double SolarFactorWinter { get; set; } + public int xapPort { get; set; } public string xapUID { get; set; } @@ -5279,6 +5555,7 @@ private void ReadStringsFile() 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(); @@ -6484,6 +6761,7 @@ public void Stop() TwitterTimer.Stop(); AwekasTimer.Stop(); WCloudTimer.Stop(); + OpenWeatherMapTimer.Stop(); MQTTTimer.Stop(); //AirLinkTimer.Stop(); CustomHttpSecondsTimer.Stop(); @@ -7293,7 +7571,7 @@ public void LogMessage(string message) public void LogDebugMessage(string message) { - if (StationOptions.DebugLogging || StationOptions.DataLogging) + if (ProgramOptions.DebugLogging || ProgramOptions.DataLogging) { Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff ") + message); } @@ -7301,7 +7579,7 @@ public void LogDebugMessage(string message) public void LogDataMessage(string message) { - if (StationOptions.DataLogging) + if (ProgramOptions.DataLogging) { Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff ") + message); } @@ -7690,6 +7968,8 @@ public void StartTimersAndSensors() WCloudTimer.Interval = WCloud.Interval * 60 * 1000; + OpenWeatherMapTimer.Interval = OpenWeatherMap.Interval * 60 * 1000; + MQTTTimer.Interval = MQTT.IntervalTime * 1000; // secs to millisecs @@ -7784,6 +8064,24 @@ public void StartTimersAndSensors() WOWCatchUp(); } + if (OWMList == null) + { + // we've already been through here + // do nothing + } + else if (OWMList.Count == 0) + { + // No archived entries to upload + OWMList = null; + OpenWeatherMapTimer.Enabled = OpenWeatherMap.Enabled && !OpenWeatherMap.SynchronisedUpdate; + } + else + { + // start the archive upload thread + OpenWeatherMap.CatchingUp = true; + OpenWeatherMapCatchUp(); + } + if (MySqlList == null) { // we've already been through here @@ -7815,6 +8113,8 @@ public void StartTimersAndSensors() AwekasTimer.Enabled = AWEKAS.Enabled && !AWEKAS.SynchronisedUpdate; + EnableOpenWeatherMap(); + LogMessage("Normal running"); LogConsoleMessage("Normal running"); } @@ -8267,6 +8567,49 @@ private async void WOWCatchUp() WOW.Updating = false; } + /// + /// Process the list of OpenWeatherMap updates created at startup from logger entries + /// + private async void OpenWeatherMapCatchUp() + { + OpenWeatherMap.Updating = true; + + string url = "http://api.openweathermap.org/data/3.0/measurements?appid=" + OpenWeatherMap.PW; + string logUrl = url.Replace(OpenWeatherMap.PW, ""); + + using (var client = new HttpClient()) + { + for (int i = 0; i < OWMList.Count; i++) + { + LogMessage("Uploading OpenWeatherMap archive #" + (i + 1)); + LogDebugMessage("OpenWeatherMap: URL = " + logUrl); + LogDataMessage("OpenWeatherMap: Body = " + OWMList[i]); + + try + { + var data = new StringContent(OWMList[i], Encoding.UTF8, "application/json"); + HttpResponseMessage response = await client.PostAsync(url, data); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + var status = response.StatusCode == HttpStatusCode.NoContent ? "OK" : "Error"; // Returns a 204 reponse for OK! + LogDebugMessage($"OpenWeatherMap: Response code = {status} - {response.StatusCode}"); + if (response.StatusCode != HttpStatusCode.NoContent) + LogDataMessage($"OpenWeatherMap: Response data = {responseBodyAsText}"); + } + catch (Exception ex) + { + LogMessage("OpenWeatherMap: Update error = " + ex.Message); + } + } + } + + LogMessage("End of OpenWeatherMap archive upload"); + OWMList.Clear(); + OpenWeatherMap.CatchingUp = false; + OpenWeatherMapTimer.Enabled = OpenWeatherMap.Enabled && !OpenWeatherMap.SynchronisedUpdate; + OpenWeatherMap.Updating = false; + } + + public async void UpdatePWSweather(DateTime timestamp) { if (!PWS.Updating) @@ -8457,6 +8800,7 @@ public void AddToWebServiceLists(DateTime timestamp) AddToWindyList(timestamp); AddToPWSList(timestamp); AddToWOWList(timestamp); + AddToOpenWeatherMapList(timestamp); } /// @@ -8537,6 +8881,16 @@ private void AddToWOWList(DateTime timestamp) } } + private void AddToOpenWeatherMapList(DateTime timestamp) + { + if (OpenWeatherMap.Enabled && OpenWeatherMap.CatchUp) + { + OWMList.Add(station.GetOpenWeatherMapData(timestamp)); + + LogMessage("Creating OpenWeatherMap data #" + OWMList.Count); + } + } + public void SetMonthlySqlCreateString() { StringBuilder strb = new StringBuilder("CREATE TABLE " + MySqlMonthlyTable + " (", 1500); @@ -8711,6 +9065,17 @@ public class DiaryData public double snowDepth { get; set; } } + public class ProgramOptions + { + public string StartupPingHost { get; set; } + public int StartupDelaySecs { get; set; } + public int StartupDelayMaxUptime { get; set; } + public bool DebugLogging { get; set; } + public bool DataLogging { get; set; } + public bool WarnMultiple { get; set; } + } + + public class StationOptions { public bool UseZeroBearing { get; set; } @@ -8726,9 +9091,6 @@ public class StationOptions public bool WS2300IgnoreStationClock { get; set; } public bool RoundWindSpeed { get; set; } public bool SyncFOReads { get; set; } - public bool DebugLogging { get; set; } - public bool DataLogging { get; set; } - public bool WarnMultiple { get; set; } public bool DavisReadReceptionStats { get; set; } public int PrimaryAqSensor { get; set; } } @@ -8817,6 +9179,33 @@ public class AwekasDisabled public int report { get; set; } } + public class OpenWeatherMapStation + { + public string id { get; set; } + public string created_at { get; set; } + public string updated_at { get; set; } + public string external_id { get; set; } + public string name { get; set; } + public double longitude { get; set; } + public double latitude { get; set; } + public int altitude { get; set; } + public int rank { get; set; } + } + + public class OpenWeatherMapNewStation + { + public string ID { get; set; } + public string created_at { get; set; } + public string updated_at { get; set; } + public string user_id { get; set; } + public string external_id { get; set; } + public string name { get; set; } + public double longitude { get; set; } + public double latitude { get; set; } + public int altitude { get; set; } + public int source_type { get; set; } + } + public class Alarm { public bool Enabled { get; set; } diff --git a/CumulusMX/CumulusMX.csproj b/CumulusMX/CumulusMX.csproj index 65f3b5ca..e0f8b317 100644 --- a/CumulusMX/CumulusMX.csproj +++ b/CumulusMX/CumulusMX.csproj @@ -211,11 +211,13 @@ Component + + diff --git a/CumulusMX/DataEditor.cs b/CumulusMX/DataEditor.cs index b31d55f1..fcd2e4e6 100644 --- a/CumulusMX/DataEditor.cs +++ b/CumulusMX/DataEditor.cs @@ -163,8 +163,8 @@ internal string GetAllTimeRecData() json.Append($"\"highHumidexTime\":\"{station.AllTime.HighHumidex.Ts.ToString(timeStampFormat)}\","); json.Append($"\"lowWindChillTime\":\"{station.AllTime.LowChill.Ts.ToString(timeStampFormat)}\","); json.Append($"\"highHeatIndexTime\":\"{station.AllTime.HighHeatIndex.Ts.ToString(timeStampFormat)}\","); - json.Append($"\"highMinTempTime\":\"{station.AllTime.HighMinTemp.Ts.ToString(dateStampFormat)}\","); - json.Append($"\"lowMaxTempTime\":\"{station.AllTime.LowMaxTemp.Ts.ToString(dateStampFormat)}\","); + json.Append($"\"highMinTempTime\":\"{station.AllTime.HighMinTemp.Ts.ToString(timeStampFormat)}\","); + json.Append($"\"lowMaxTempTime\":\"{station.AllTime.LowMaxTemp.Ts.ToString(timeStampFormat)}\","); json.Append($"\"highDailyTempRangeTime\":\"{station.AllTime.HighDailyTempRange.Ts.ToString(dateStampFormat)}\","); json.Append($"\"lowDailyTempRangeTime\":\"{station.AllTime.LowDailyTempRange.Ts.ToString(dateStampFormat)}\","); // Records - Humidity values @@ -304,7 +304,7 @@ internal string GetRecordsDayFile(string recordType) if (station.DayFile.Count() > 0) { var data = station.DayFile.Where(r => r.Date >= startDate).ToList(); - foreach (var rec in station.DayFile) + foreach (var rec in data) { // This assumes the day file is in date order! if (thisDate.Month != rec.Date.Month) @@ -341,13 +341,13 @@ internal string GetRecordsDayFile(string recordType) if (rec.LowTemp > highMinTempVal) { highMinTempVal = rec.LowTemp; - highMinTempTime = rec.Date; + highMinTempTime = rec.LowTempTime; } // lo max temp if (rec.HighTemp < lowMaxTempVal) { lowMaxTempVal = rec.HighTemp; - lowMaxTempTime = rec.Date; + lowMaxTempTime = rec.HighTempTime; } // hi temp range if ((rec.HighTemp - rec.LowTemp) > highTempRangeVal) @@ -549,9 +549,9 @@ internal string GetRecordsDayFile(string recordType) json.Append($"\"highHeatIndexValDayfile\":\"{highHeatIndVal.ToString(cumulus.TempFormat)}\","); json.Append($"\"highHeatIndexTimeDayfile\":\"{highHeatIndTime.ToString(timeStampFormat)}\","); json.Append($"\"highMinTempValDayfile\":\"{highMinTempVal.ToString(cumulus.TempFormat)}\","); - json.Append($"\"highMinTempTimeDayfile\":\"{highMinTempTime.ToString(dateStampFormat)}\","); + json.Append($"\"highMinTempTimeDayfile\":\"{highMinTempTime.ToString(timeStampFormat)}\","); json.Append($"\"lowMaxTempValDayfile\":\"{lowMaxTempVal.ToString(cumulus.TempFormat)}\","); - json.Append($"\"lowMaxTempTimeDayfile\":\"{lowMaxTempTime.ToString(dateStampFormat)}\","); + json.Append($"\"lowMaxTempTimeDayfile\":\"{lowMaxTempTime.ToString(timeStampFormat)}\","); json.Append($"\"highDailyTempRangeValDayfile\":\"{highTempRangeVal.ToString(cumulus.TempFormat)}\","); json.Append($"\"highDailyTempRangeTimeDayfile\":\"{highTempRangeTime.ToString(dateStampFormat)}\","); json.Append($"\"lowDailyTempRangeValDayfile\":\"{lowTempRangeVal.ToString(cumulus.TempFormat)}\","); @@ -629,12 +629,6 @@ internal string GetRecordsLogFile(string recordType) var finished = false; var lastentrydate = datefrom; - var currentDay = datefrom; - double dayHighTemp = -999; - double dayLowTemp = 999; - double dayWindRun = 0; - double dayRain = 0; - var isDryNow = false; var currentDryPeriod = 0; var currentWetPeriod = 0; @@ -701,6 +695,15 @@ internal string GetRecordsLogFile(string recordType) var dryPeriodTime = highTempTime; var wetPeriodTime = highTempTime; + var currentDay = datefrom; + var dayHighTemp = highTempVal; + DateTime dayHighTempTime = highTempTime; + double dayLowTemp = lowTempVal; + DateTime dayLowTempTime = highTempTime; + double dayWindRun = 0; + double dayRain = 0; + + var thisDateDry = highTempTime; var thisDateWet = highTempTime; @@ -878,10 +881,16 @@ internal string GetRecordsLogFile(string recordType) if (currentDay.Day == metoDate.Day && currentDay.Month == metoDate.Month && currentDay.Year == metoDate.Year) { if (outsidetemp > dayHighTemp) + { dayHighTemp = outsidetemp; + dayHighTempTime = entrydate; + } if (outsidetemp < dayLowTemp) + { dayLowTemp = outsidetemp; + dayLowTempTime = entrydate; + } if (dayRain < raintoday) dayRain = raintoday; @@ -893,12 +902,12 @@ internal string GetRecordsLogFile(string recordType) if (dayHighTemp < lowMaxTempVal) { lowMaxTempVal = dayHighTemp; - lowMaxTempTime = currentDay; + lowMaxTempTime = dayHighTempTime; } if (dayLowTemp > highMinTempVal) { highMinTempVal = dayLowTemp; - highMinTempTime = currentDay; + highMinTempTime = dayLowTempTime; } if (dayHighTemp - dayLowTemp > highTempRangeVal) { @@ -1047,9 +1056,9 @@ internal string GetRecordsLogFile(string recordType) json.Append($"\"highHeatIndexValLogfile\":\"{highHeatIndVal.ToString(cumulus.TempFormat)}\","); json.Append($"\"highHeatIndexTimeLogfile\":\"{highHeatIndTime.ToString(timeStampFormat)}\","); json.Append($"\"highMinTempValLogfile\":\"{highMinTempVal.ToString(cumulus.TempFormat)}\","); - json.Append($"\"highMinTempTimeLogfile\":\"{highMinTempTime.ToString(dateStampFormat)}\","); + json.Append($"\"highMinTempTimeLogfile\":\"{highMinTempTime.ToString(timeStampFormat)}\","); json.Append($"\"lowMaxTempValLogfile\":\"{lowMaxTempVal.ToString(cumulus.TempFormat)}\","); - json.Append($"\"lowMaxTempTimeLogfile\":\"{lowMaxTempTime.ToString(dateStampFormat)}\","); + json.Append($"\"lowMaxTempTimeLogfile\":\"{lowMaxTempTime.ToString(timeStampFormat)}\","); json.Append($"\"highDailyTempRangeValLogfile\":\"{highTempRangeVal.ToString(cumulus.TempFormat)}\","); json.Append($"\"highDailyTempRangeTimeLogfile\":\"{highTempRangeTime.ToString(dateStampFormat)}\","); json.Append($"\"lowDailyTempRangeValLogfile\":\"{lowTempRangeVal.ToString(cumulus.TempFormat)}\","); @@ -1098,11 +1107,13 @@ internal string GetRecordsLogFile(string recordType) return json.ToString(); } + /* private static DateTime GetDateTime(DateTime date, string time) { var tim = time.Split(CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator.ToCharArray()[0]); return new DateTime(date.Year, date.Month, date.Day, int.Parse(tim[0]), int.Parse(tim[1]), 0); } + */ internal string EditAllTimeRecs(IHttpContext context) { @@ -1204,13 +1215,15 @@ internal string EditAllTimeRecs(IHttpContext context) station.SetAlltime(station.AllTime.HighMinTemp, double.Parse(value), station.AllTime.HighMinTemp.Ts); break; case "highMinTempTime": - station.SetAlltime(station.AllTime.HighMinTemp, station.AllTime.HighMinTemp.Val, station.ddmmyyStrToDate(value)); + dt = value.Split('+'); + station.SetAlltime(station.AllTime.HighMinTemp, station.AllTime.HighMinTemp.Val, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); break; case "lowMaxTempVal": station.SetAlltime(station.AllTime.LowMaxTemp, double.Parse(value), station.AllTime.LowMaxTemp.Ts); break; case "lowMaxTempTime": - station.SetAlltime(station.AllTime.LowMaxTemp, station.AllTime.LowMaxTemp.Val, station.ddmmyyStrToDate(value)); + dt = value.Split('+'); + station.SetAlltime(station.AllTime.LowMaxTemp, station.AllTime.LowMaxTemp.Val, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); break; case "highDailyTempRangeVal": station.SetAlltime(station.AllTime.HighDailyTempRange, double.Parse(value), station.AllTime.HighDailyTempRange.Ts); @@ -1428,13 +1441,15 @@ internal string EditMonthlyRecs(IHttpContext context) station.SetMonthlyAlltime(station.MonthlyRecs[month].HighMinTemp, double.Parse(value), station.MonthlyRecs[month].HighMinTemp.Ts); break; case "highMinTempTime": - station.SetMonthlyAlltime(station.MonthlyRecs[month].HighMinTemp, station.MonthlyRecs[month].HighMinTemp.Val, station.ddmmyyStrToDate(value)); + dt = value.Split('+'); + station.SetMonthlyAlltime(station.MonthlyRecs[month].HighMinTemp, station.MonthlyRecs[month].HighMinTemp.Val, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); break; case "lowMaxTempVal": station.SetMonthlyAlltime(station.MonthlyRecs[month].LowMaxTemp, double.Parse(value), station.MonthlyRecs[month].LowMaxTemp.Ts); break; case "lowMaxTempTime": - station.SetMonthlyAlltime(station.MonthlyRecs[month].LowMaxTemp, station.MonthlyRecs[month].LowMaxTemp.Val, station.ddmmyyStrToDate(value)); + dt = value.Split('+'); + station.SetMonthlyAlltime(station.MonthlyRecs[month].LowMaxTemp, station.MonthlyRecs[month].LowMaxTemp.Val, station.ddmmyyhhmmStrToDate(dt[0], dt[1])); break; case "highDailyTempRangeVal": station.SetMonthlyAlltime(station.MonthlyRecs[month].HighDailyTempRange, double.Parse(value), station.MonthlyRecs[month].HighDailyTempRange.Ts); @@ -1585,8 +1600,8 @@ internal string GetMonthlyRecData() json.Append($"\"{m}-highHumidexTime\":\"{station.MonthlyRecs[m].HighHumidex.Ts.ToString(timeStampFormat)}\","); json.Append($"\"{m}-lowWindChillTime\":\"{station.MonthlyRecs[m].LowChill.Ts.ToString(timeStampFormat)}\","); json.Append($"\"{m}-highHeatIndexTime\":\"{station.MonthlyRecs[m].HighHeatIndex.Ts.ToString(timeStampFormat)}\","); - json.Append($"\"{m}-highMinTempTime\":\"{station.MonthlyRecs[m].HighMinTemp.Ts.ToString(dateStampFormat)}\","); - json.Append($"\"{m}-lowMaxTempTime\":\"{station.MonthlyRecs[m].LowMaxTemp.Ts.ToString(dateStampFormat)}\","); + json.Append($"\"{m}-highMinTempTime\":\"{station.MonthlyRecs[m].HighMinTemp.Ts.ToString(timeStampFormat)}\","); + json.Append($"\"{m}-lowMaxTempTime\":\"{station.MonthlyRecs[m].LowMaxTemp.Ts.ToString(timeStampFormat)}\","); json.Append($"\"{m}-highDailyTempRangeTime\":\"{station.MonthlyRecs[m].HighDailyTempRange.Ts.ToString(dateStampFormat)}\","); json.Append($"\"{m}-lowDailyTempRangeTime\":\"{station.MonthlyRecs[m].LowDailyTempRange.Ts.ToString(dateStampFormat)}\","); // Records - Humidity values @@ -1752,7 +1767,7 @@ internal string GetMonthlyRecDayFile() if (station.DayFile[i].LowTemp > highMinTempVal[monthOffset]) { highMinTempVal[monthOffset] = station.DayFile[i].LowTemp; - highMinTempTime[monthOffset] = loggedDate; + highMinTempTime[monthOffset] = station.DayFile[i].LowTempTime; } // hi temp if (station.DayFile[i].HighTemp > highTempVal[monthOffset]) @@ -1764,7 +1779,7 @@ internal string GetMonthlyRecDayFile() if (station.DayFile[i].HighTemp < lowMaxTempVal[monthOffset]) { lowMaxTempVal[monthOffset] = station.DayFile[i].HighTemp; - lowMaxTempTime[monthOffset] = loggedDate; + lowMaxTempTime[monthOffset] = station.DayFile[i].HighTempTime; } // temp ranges @@ -1971,9 +1986,9 @@ internal string GetMonthlyRecDayFile() json.Append($"\"{m}-highHeatIndexValDayfile\":\"{highHeatIndVal[i].ToString(cumulus.TempFormat)}\","); json.Append($"\"{m}-highHeatIndexTimeDayfile\":\"{highHeatIndTime[i].ToString(timeStampFormat)}\","); json.Append($"\"{m}-highMinTempValDayfile\":\"{highMinTempVal[i].ToString(cumulus.TempFormat)}\","); - json.Append($"\"{m}-highMinTempTimeDayfile\":\"{highMinTempTime[i].ToString(dateStampFormat)}\","); + json.Append($"\"{m}-highMinTempTimeDayfile\":\"{highMinTempTime[i].ToString(timeStampFormat)}\","); json.Append($"\"{m}-lowMaxTempValDayfile\":\"{lowMaxTempVal[i].ToString(cumulus.TempFormat)}\","); - json.Append($"\"{m}-lowMaxTempTimeDayfile\":\"{lowMaxTempTime[i].ToString(dateStampFormat)}\","); + json.Append($"\"{m}-lowMaxTempTimeDayfile\":\"{lowMaxTempTime[i].ToString(timeStampFormat)}\","); json.Append($"\"{m}-highDailyTempRangeValDayfile\":\"{highTempRangeVal[i].ToString(cumulus.TempFormat)}\","); json.Append($"\"{m}-highDailyTempRangeTimeDayfile\":\"{highTempRangeTime[i].ToString(dateStampFormat)}\","); json.Append($"\"{m}-lowDailyTempRangeValDayfile\":\"{lowTempRangeVal[i].ToString(cumulus.TempFormat)}\","); @@ -2033,12 +2048,6 @@ internal string GetMonthlyRecLogFile() var finished = false; var lastentrydate = datefrom; - var currentDay = datefrom; - double dayHighTemp = -999; - double dayLowTemp = 999; - double dayWindRun = 0; - double dayRain = 0; - var isDryNow = false; var currentDryPeriod = 0; var currentWetPeriod = 0; @@ -2109,6 +2118,14 @@ internal string GetMonthlyRecLogFile() var thisDateDry = thisDate; var thisDateWet = thisDate; + var currentDay = datefrom; + double dayHighTemp = -999; + var dayHighTempTime = thisDate; + double dayLowTemp = 999; + var dayLowTempTime = thisDate; + double dayWindRun = 0; + double dayRain = 0; + var monthlyRain = 0.0; var totalRainfall = 0.0; @@ -2290,10 +2307,16 @@ internal string GetMonthlyRecLogFile() if (currentDay.Day == metoDate.Day && currentDay.Month == metoDate.Month && currentDay.Year == metoDate.Year) { if (outsidetemp > dayHighTemp) + { dayHighTemp = outsidetemp; + dayHighTempTime = entrydate; + } if (outsidetemp < dayLowTemp) + { dayLowTemp = outsidetemp; + dayLowTempTime = entrydate; + } if (dayRain < raintoday) dayRain = raintoday; @@ -2306,12 +2329,12 @@ internal string GetMonthlyRecLogFile() if (dayHighTemp < lowMaxTempVal[lastEntryMonthOffset]) { lowMaxTempVal[lastEntryMonthOffset] = dayHighTemp; - lowMaxTempTime[lastEntryMonthOffset] = currentDay; + lowMaxTempTime[lastEntryMonthOffset] = dayHighTempTime; } if (dayLowTemp > highMinTempVal[lastEntryMonthOffset]) { highMinTempVal[lastEntryMonthOffset] = dayLowTemp; - highMinTempTime[lastEntryMonthOffset] = currentDay; + highMinTempTime[lastEntryMonthOffset] = dayLowTempTime; } if (dayHighTemp - dayLowTemp > highTempRangeVal[lastEntryMonthOffset]) { @@ -2466,9 +2489,9 @@ internal string GetMonthlyRecLogFile() json.Append($"\"{m}-highHeatIndexValLogfile\":\"{highHeatIndVal[i].ToString(cumulus.TempFormat)}\","); json.Append($"\"{m}-highHeatIndexTimeLogfile\":\"{highHeatIndTime[i].ToString(timeStampFormat)}\","); json.Append($"\"{m}-highMinTempValLogfile\":\"{highMinTempVal[i].ToString(cumulus.TempFormat)}\","); - json.Append($"\"{m}-highMinTempTimeLogfile\":\"{highMinTempTime[i].ToString(dateStampFormat)}\","); + json.Append($"\"{m}-highMinTempTimeLogfile\":\"{highMinTempTime[i].ToString(timeStampFormat)}\","); json.Append($"\"{m}-lowMaxTempValLogfile\":\"{lowMaxTempVal[i].ToString(cumulus.TempFormat)}\","); - json.Append($"\"{m}-lowMaxTempTimeLogfile\":\"{lowMaxTempTime[i].ToString(dateStampFormat)}\","); + json.Append($"\"{m}-lowMaxTempTimeLogfile\":\"{lowMaxTempTime[i].ToString(timeStampFormat)}\","); json.Append($"\"{m}-highDailyTempRangeValLogfile\":\"{highTempRangeVal[i].ToString(cumulus.TempFormat)}\","); json.Append($"\"{m}-highDailyTempRangeTimeLogfile\":\"{highTempRangeTime[i].ToString(dateStampFormat)}\","); json.Append($"\"{m}-lowDailyTempRangeValLogfile\":\"{lowTempRangeVal[i].ToString(cumulus.TempFormat)}\","); @@ -2541,9 +2564,9 @@ internal string GetThisMonthRecData() json.Append($"\"highHeatIndexVal\":\"{station.ThisMonth.HighHeatIndex.Val.ToString(cumulus.TempFormat)}\","); json.Append($"\"highHeatIndexTime\":\"{station.ThisMonth.HighHeatIndex.Ts.ToString(timeStampFormat)}\","); json.Append($"\"highMinTempVal\":\"{station.ThisMonth.HighMinTemp.Val.ToString(cumulus.TempFormat)}\","); - json.Append($"\"highMinTempTime\":\"{station.ThisMonth.HighMinTemp.Ts.ToString(dateStampFormat)}\","); + json.Append($"\"highMinTempTime\":\"{station.ThisMonth.HighMinTemp.Ts.ToString(timeStampFormat)}\","); json.Append($"\"lowMaxTempVal\":\"{station.ThisMonth.LowMaxTemp.Val.ToString(cumulus.TempFormat)}\","); - json.Append($"\"lowMaxTempTime\":\"{station.ThisMonth.LowMaxTemp.Ts.ToString(dateStampFormat)}\","); + json.Append($"\"lowMaxTempTime\":\"{station.ThisMonth.LowMaxTemp.Ts.ToString(timeStampFormat)}\","); json.Append($"\"highDailyTempRangeVal\":\"{station.ThisMonth.HighDailyTempRange.Val.ToString(cumulus.TempFormat)}\","); json.Append($"\"highDailyTempRangeTime\":\"{station.ThisMonth.HighDailyTempRange.Ts.ToString(dateStampFormat)}\","); json.Append($"\"lowDailyTempRangeVal\":\"{station.ThisMonth.LowDailyTempRange.Val.ToString(cumulus.TempFormat)}\","); @@ -2682,13 +2705,15 @@ internal string EditThisMonthRecs(IHttpContext context) station.ThisMonth.HighMinTemp.Val = double.Parse(value); break; case "highMinTempTime": - station.ThisMonth.HighMinTemp.Ts = station.ddmmyyStrToDate(value); + dt = value.Split('+'); + station.ThisMonth.HighMinTemp.Ts = station.ddmmyyhhmmStrToDate(dt[0], dt[1]); break; case "lowMaxTempVal": station.ThisMonth.LowMaxTemp.Val = double.Parse(value); break; case "lowMaxTempTime": - station.ThisMonth.LowMaxTemp.Ts = station.ddmmyyStrToDate(value); + dt = value.Split('+'); + station.ThisMonth.LowMaxTemp.Ts = station.ddmmyyhhmmStrToDate(dt[0], dt[1]); break; case "highDailyTempRangeVal": station.ThisMonth.HighDailyTempRange.Val = double.Parse(value); @@ -2825,9 +2850,9 @@ internal string GetThisYearRecData() json.Append($"\"highHeatIndexVal\":\"{station.ThisYear.HighHeatIndex.Val.ToString(cumulus.TempFormat)}\","); json.Append($"\"highHeatIndexTime\":\"{station.ThisYear.HighHeatIndex.Ts.ToString(timeStampFormat)}\","); json.Append($"\"highMinTempVal\":\"{station.ThisYear.HighMinTemp.Val.ToString(cumulus.TempFormat)}\","); - json.Append($"\"highMinTempTime\":\"{station.ThisYear.HighMinTemp.Ts.ToString(dateStampFormat)}\","); + json.Append($"\"highMinTempTime\":\"{station.ThisYear.HighMinTemp.Ts.ToString(timeStampFormat)}\","); json.Append($"\"lowMaxTempVal\":\"{station.ThisYear.LowMaxTemp.Val.ToString(cumulus.TempFormat)}\","); - json.Append($"\"lowMaxTempTime\":\"{station.ThisYear.LowMaxTemp.Ts.ToString(dateStampFormat)}\","); + json.Append($"\"lowMaxTempTime\":\"{station.ThisYear.LowMaxTemp.Ts.ToString(timeStampFormat)}\","); json.Append($"\"highDailyTempRangeVal\":\"{station.ThisYear.HighDailyTempRange.Val.ToString(cumulus.TempFormat)}\","); json.Append($"\"highDailyTempRangeTime\":\"{station.ThisYear.HighDailyTempRange.Ts.ToString(dateStampFormat)}\","); json.Append($"\"lowDailyTempRangeVal\":\"{station.ThisYear.LowDailyTempRange.Val.ToString(cumulus.TempFormat)}\","); @@ -2968,13 +2993,15 @@ internal string EditThisYearRecs(IHttpContext context) station.ThisYear.HighMinTemp.Val = double.Parse(value); break; case "highMinTempTime": - station.ThisYear.HighMinTemp.Ts = station.ddmmyyStrToDate(value); + dt = value.Split('+'); + station.ThisYear.HighMinTemp.Ts = station.ddmmyyhhmmStrToDate(dt[0], dt[1]); break; case "lowMaxTempVal": station.ThisYear.LowMaxTemp.Val = double.Parse(value); break; case "lowMaxTempTime": - station.ThisYear.LowMaxTemp.Ts = station.ddmmyyStrToDate(value); + dt = value.Split('+'); + station.ThisYear.LowMaxTemp.Ts = station.ddmmyyhhmmStrToDate(dt[0], dt[1]); break; case "highDailyTempRangeVal": station.ThisYear.HighDailyTempRange.Val = double.Parse(value); diff --git a/CumulusMX/DavisAirLink.cs b/CumulusMX/DavisAirLink.cs index 1cbcb443..f611075a 100644 --- a/CumulusMX/DavisAirLink.cs +++ b/CumulusMX/DavisAirLink.cs @@ -239,7 +239,7 @@ private void DecodeAlCurrent(string currentJson) // The WLL sends the timestamp in Unix ticks, and in UTC // rather than rely on the WLL clock being correct, we will use our local time - //var dateTime = FromUnixTime(data.Value("ts")); + //var dateTime = Utils.FromUnixTime(data.Value("ts")); // The current conditions is sent as an array, even though it only contains 1 record var rec = json.data.conditions.First(); @@ -470,8 +470,8 @@ private void GetWlHistoricData() //int passCount; //const int maxPasses = 4; - var unixDateTime = ToUnixTime(DateTime.Now); - var startTime = ToUnixTime(airLinkLastUpdateTime); + var unixDateTime = Utils.ToUnixTime(DateTime.Now); + var startTime = Utils.ToUnixTime(airLinkLastUpdateTime); int endTime = unixDateTime; int unix24hrs = 24 * 60 * 60; @@ -483,8 +483,8 @@ private void GetWlHistoricData() maxArchiveRuns++; } - cumulus.LogConsoleMessage($"Downloading Historic Data from WL.com from: {airLinkLastUpdateTime:s} to: {FromUnixTime(endTime):s}"); - cumulus.LogMessage($"GetWlHistoricData: Downloading Historic Data from WL.com from: {airLinkLastUpdateTime:s} to: {FromUnixTime(endTime):s}"); + cumulus.LogConsoleMessage($"Downloading Historic Data from WL.com from: {airLinkLastUpdateTime:s} to: {Utils.FromUnixTime(endTime):s}"); + cumulus.LogMessage($"GetWlHistoricData: Downloading Historic Data from WL.com from: {airLinkLastUpdateTime:s} to: {Utils.FromUnixTime(endTime):s}"); SortedDictionary parameters = new SortedDictionary { @@ -549,7 +549,7 @@ private void GetWlHistoricData() var errObj = responseBody.FromJson(); cumulus.LogMessage($"GetWlHistoricData: WeatherLink API Historic Error: {errObj.code}, {errObj.message}"); cumulus.LogConsoleMessage($" - Error {errObj.code}: {errObj.message}"); - airLinkLastUpdateTime = FromUnixTime(endTime); + airLinkLastUpdateTime = Utils.FromUnixTime(endTime); return; } @@ -557,7 +557,7 @@ private void GetWlHistoricData() { cumulus.LogMessage("GetWlHistoricData: WeatherLink API Historic: No data was returned. Check your Device Id."); cumulus.LogConsoleMessage(" - No historic data available"); - airLinkLastUpdateTime = FromUnixTime(endTime); + airLinkLastUpdateTime = Utils.FromUnixTime(endTime); return; } @@ -565,7 +565,7 @@ private void GetWlHistoricData() { cumulus.LogMessage("GetWlHistoricData: Invalid historic message received"); cumulus.LogDataMessage("GetWlHistoricData: Received: " + responseBody); - airLinkLastUpdateTime = FromUnixTime(endTime); + airLinkLastUpdateTime = Utils.FromUnixTime(endTime); return; } @@ -592,7 +592,7 @@ private void GetWlHistoricData() { cumulus.LogMessage("GetWlHistoricData: No historic data available"); cumulus.LogConsoleMessage(" - No historic data available"); - airLinkLastUpdateTime = FromUnixTime(endTime); + airLinkLastUpdateTime = Utils.FromUnixTime(endTime); return; } @@ -601,7 +601,7 @@ private void GetWlHistoricData() catch (Exception ex) { cumulus.LogMessage("GetWlHistoricData: Exception: " + ex.Message); - airLinkLastUpdateTime = FromUnixTime(endTime); + airLinkLastUpdateTime = Utils.FromUnixTime(endTime); return; } @@ -811,7 +811,7 @@ public void DecodeAlHistoric(int dataType, string json) // then add the PM data into the graphdata list if (cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.AirLinkIndoor && standaloneHistory) { - UpdateGraphDataAqEntry(FromUnixTime(data17.ts), cumulus.airLinkDataIn.pm2p5, cumulus.airLinkDataIn.pm10); + UpdateGraphDataAqEntry(Utils.FromUnixTime(data17.ts), cumulus.airLinkDataIn.pm2p5, cumulus.airLinkDataIn.pm10); } } else @@ -841,7 +841,7 @@ public void DecodeAlHistoric(int dataType, string json) // then add the PM data into the graphdata list if (cumulus.StationOptions.PrimaryAqSensor == (int)Cumulus.PrimaryAqSensor.AirLinkOutdoor && standaloneHistory) { - UpdateGraphDataAqEntry(FromUnixTime(data17.ts), cumulus.airLinkDataOut.pm2p5, cumulus.airLinkDataOut.pm10); + UpdateGraphDataAqEntry(Utils.FromUnixTime(data17.ts), cumulus.airLinkDataOut.pm2p5, cumulus.airLinkDataOut.pm10); } } } @@ -920,11 +920,11 @@ private void GetWlHistoricHealth() cumulus.LogMessage("AirLinkHealth: Get WL.com Historic Data"); - var unixDateTime = ToUnixTime(DateTime.Now); + var unixDateTime = Utils.ToUnixTime(DateTime.Now); var startTime = unixDateTime - WeatherLinkArchiveInterval; int endTime = unixDateTime; - cumulus.LogDebugMessage($"AirLinkHealth: Downloading the historic record from WL.com from: {FromUnixTime(startTime):s} to: {FromUnixTime(endTime):s}"); + cumulus.LogDebugMessage($"AirLinkHealth: Downloading the historic record from WL.com from: {Utils.FromUnixTime(startTime):s} to: {Utils.FromUnixTime(endTime):s}"); SortedDictionary parameters = new SortedDictionary { @@ -987,7 +987,7 @@ private void GetWlHistoricHealth() if (responseBody == "{}") { cumulus.LogMessage("AirLinkHealth: WeatherLink API: No data was returned. Check your Device Id."); - airLinkLastUpdateTime = FromUnixTime(endTime); + airLinkLastUpdateTime = Utils.FromUnixTime(endTime); return; } @@ -1088,7 +1088,7 @@ internal void DecodeWlApiHealth(WlHistorySensor sensor, bool startingup) try { // Davis are changing the API, from air_quality_firmware_version to firmware_version - var dat = FromUnixTime(data.air_quality_firmware_version ?? data.firmware_version.Value); + var dat = Utils.FromUnixTime(data.air_quality_firmware_version ?? data.firmware_version.Value); if (indoor) cumulus.airLinkDataIn.firmwareVersion = dat.ToUniversalTime().ToString("yyyy-MM-dd"); else @@ -1191,7 +1191,7 @@ private bool GetAvailableStationIds() { WlStationList stationsObj; - var unixDateTime = ToUnixTime(DateTime.Now); + var unixDateTime = Utils.ToUnixTime(DateTime.Now); // Are we using the same WL APIv2 as a WLL device? if (cumulus.StationType == 11 && cumulus.WllApiKey == cumulus.AirLinkApiKey) @@ -1296,7 +1296,7 @@ private void GetAvailableSensors() { WlSensorList sensorsObj; - var unixDateTime = ToUnixTime(DateTime.Now); + var unixDateTime = Utils.ToUnixTime(DateTime.Now); if (cumulus.WllApiKey == string.Empty || cumulus.WllApiSecret == string.Empty) { @@ -1471,18 +1471,6 @@ private static bool CheckIpValid(string strIp) return arrOctets.All(strOctet => byte.TryParse(strOctet, out result)); } - private static DateTime FromUnixTime(long unixTime) - { - // WWL uses UTC ticks, convert to local time - var utcTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(unixTime); - return utcTime.ToLocalTime(); - } - - private static int ToUnixTime(DateTime dateTime) - { - return (int)dateTime.ToUniversalTime().ToUnixEpochDate(); - } - private void DoAqi(AirLinkData data) { switch (cumulus.airQualityIndex) diff --git a/CumulusMX/DavisWllStation.cs b/CumulusMX/DavisWllStation.cs index 282dcfb3..cd6986ea 100644 --- a/CumulusMX/DavisWllStation.cs +++ b/CumulusMX/DavisWllStation.cs @@ -815,7 +815,7 @@ private void DecodeCurrent(string currentJson) try { StormRain = ConvertRainClicksToUser(data1.rain_storm.Value, data1.rain_size) * cumulus.Calib.Rain.Mult; - StartOfStorm = FromUnixTime(data1.rain_storm_start_at.Value); + StartOfStorm = Utils.FromUnixTime(data1.rain_storm_start_at.Value); } catch (Exception ex) { @@ -1130,18 +1130,6 @@ private void DecodeCurrent(string currentJson) } } - private static DateTime FromUnixTime(long unixTime) - { - // WWL uses UTC ticks, convert to local time - var utcTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(unixTime); - return utcTime.ToLocalTime(); - } - - private static int ToUnixTime(DateTime dateTime) - { - return (int)dateTime.ToUniversalTime().ToUnixEpochDate(); - } - private void OnServiceChanged(object sender, ServiceAnnouncementEventArgs e) { PrintService('~', e.Announcement); @@ -1374,8 +1362,8 @@ private void GetWlHistoricData() //int passCount; //const int maxPasses = 4; - var unixDateTime = ToUnixTime(DateTime.Now); - var startTime = ToUnixTime(cumulus.LastUpdateTime); + var unixDateTime = Utils.ToUnixTime(DateTime.Now); + var startTime = Utils.ToUnixTime(cumulus.LastUpdateTime); int endTime = unixDateTime; int unix24hrs = 24 * 60 * 60; @@ -1387,8 +1375,8 @@ private void GetWlHistoricData() maxArchiveRuns++; } - cumulus.LogConsoleMessage($"Downloading Historic Data from WL.com from: {cumulus.LastUpdateTime:s} to: {FromUnixTime(endTime):s}"); - cumulus.LogMessage($"GetWlHistoricData: Downloading Historic Data from WL.com from: {cumulus.LastUpdateTime:s} to: {FromUnixTime(endTime):s}"); + cumulus.LogConsoleMessage($"Downloading Historic Data from WL.com from: {cumulus.LastUpdateTime:s} to: {Utils.FromUnixTime(endTime):s}"); + cumulus.LogMessage($"GetWlHistoricData: Downloading Historic Data from WL.com from: {cumulus.LastUpdateTime:s} to: {Utils.FromUnixTime(endTime):s}"); SortedDictionary parameters = new SortedDictionary { @@ -1462,7 +1450,7 @@ private void GetWlHistoricData() var historyError = responseBody.FromJson(); cumulus.LogMessage($"GetWlHistoricData: WeatherLink API Historic Error: {historyError.code}, {historyError.message}"); cumulus.LogConsoleMessage($" - Error {historyError.code}: {historyError.message}"); - cumulus.LastUpdateTime = FromUnixTime(endTime); + cumulus.LastUpdateTime = Utils.FromUnixTime(endTime); return; } @@ -1472,7 +1460,7 @@ private void GetWlHistoricData() { cumulus.LogMessage("GetWlHistoricData: WeatherLink API Historic: No data was returned. Check your Device Id."); cumulus.LogConsoleMessage(" - No historic data available"); - cumulus.LastUpdateTime = FromUnixTime(endTime); + cumulus.LastUpdateTime = Utils.FromUnixTime(endTime); return; } else if (responseBody.StartsWith("{\"sensors\":[{\"lsid\"")) // sanity check @@ -1498,7 +1486,7 @@ private void GetWlHistoricData() { cumulus.LogMessage("GetWlHistoricData: No historic data available"); cumulus.LogConsoleMessage(" - No historic data available"); - cumulus.LastUpdateTime = FromUnixTime(endTime); + cumulus.LastUpdateTime = Utils.FromUnixTime(endTime); return; } else @@ -1510,14 +1498,14 @@ private void GetWlHistoricData() { cumulus.LogMessage("GetWlHistoricData: Invalid historic message received"); cumulus.LogDataMessage("GetWlHistoricData: Received: " + responseBody); - cumulus.LastUpdateTime = FromUnixTime(endTime); + cumulus.LastUpdateTime = Utils.FromUnixTime(endTime); return; } } catch (Exception ex) { cumulus.LogMessage("GetWlHistoricData: Exception: " + ex.Message); - cumulus.LastUpdateTime = FromUnixTime(endTime); + cumulus.LastUpdateTime = Utils.FromUnixTime(endTime); return; } @@ -1532,7 +1520,7 @@ private void GetWlHistoricData() var refData = sensorWithMostRecs.data[dataIndex].FromJsv(); DecodeHistoric(sensorWithMostRecs.data_structure_type, sensorWithMostRecs.sensor_type, sensorWithMostRecs.data[dataIndex]); - var timestamp = FromUnixTime(refData.ts); + var timestamp = Utils.FromUnixTime(refData.ts); foreach (var sensor in histObj.sensors) { @@ -1695,7 +1683,7 @@ private void DecodeHistoric(int dataType, int sensorType, string json) { case 11: // ISS data var data11 = json.FromJsv(); - var recordTs = FromUnixTime(data11.ts); + var recordTs = Utils.FromUnixTime(data11.ts); //weatherLinkArchiveInterval = data.Value("arch_int"); // Temperature & Humidity @@ -1742,10 +1730,10 @@ private void DecodeHistoric(int dataType, int sensorType, string json) cumulus.LogDebugMessage($"WL.com historic: using temp/hum data from TxId {data11.tx_id}"); // do high temp - ts = FromUnixTime(data11.temp_hi_at); + ts = Utils.FromUnixTime(data11.temp_hi_at); DoOutdoorTemp(ConvertTempFToUser(data11.temp_hi), ts); // do low temp - ts = FromUnixTime(data11.temp_lo_at); + ts = Utils.FromUnixTime(data11.temp_lo_at); DoOutdoorTemp(ConvertTempFToUser(data11.temp_lo), ts); // do last temp DoOutdoorTemp(ConvertTempFToUser(data11.temp_last), recordTs); @@ -1759,10 +1747,10 @@ private void DecodeHistoric(int dataType, int sensorType, string json) try { // do high humidty - ts = FromUnixTime(data11.hum_hi_at); + ts = Utils.FromUnixTime(data11.hum_hi_at); DoOutdoorHumidity(Convert.ToInt32(data11.hum_hi), ts); // do low humidity - ts = FromUnixTime(data11.hum_lo_at); + ts = Utils.FromUnixTime(data11.hum_lo_at); DoOutdoorHumidity(Convert.ToInt32(data11.hum_lo), ts); // do current humidity DoOutdoorHumidity(Convert.ToInt32(data11.hum_last), recordTs); @@ -1775,10 +1763,10 @@ private void DecodeHistoric(int dataType, int sensorType, string json) try { // do high DP - ts = FromUnixTime(data11.dew_point_hi_at); + ts = Utils.FromUnixTime(data11.dew_point_hi_at); DoOutdoorDewpoint(ConvertTempFToUser(data11.dew_point_hi), ts); // do low DP - ts = FromUnixTime(data11.dew_point_lo_at); + ts = Utils.FromUnixTime(data11.dew_point_lo_at); DoOutdoorDewpoint(ConvertTempFToUser(data11.dew_point_lo), ts); // do last DP DoOutdoorDewpoint(ConvertTempFToUser(data11.dew_point_last), recordTs); @@ -1794,7 +1782,7 @@ private void DecodeHistoric(int dataType, int sensorType, string json) try { // do low WC - ts = FromUnixTime(data11.wind_chill_lo_at); + ts = Utils.FromUnixTime(data11.wind_chill_lo_at); DoWindChill(ConvertTempFToUser(data11.wind_chill_lo), ts); // do last WC DoWindChill(ConvertTempFToUser(data11.wind_chill_last), recordTs); @@ -2201,13 +2189,13 @@ private void DecodeHistoric(int dataType, int sensorType, string json) { var data13baro = json.FromJsv(); // check the high - var ts = FromUnixTime(data13baro.bar_hi_at); + var ts = Utils.FromUnixTime(data13baro.bar_hi_at); DoPressure(ConvertPressINHGToUser(data13baro.bar_hi), ts); // check the low - ts = FromUnixTime(data13baro.bar_lo_at); + ts = Utils.FromUnixTime(data13baro.bar_lo_at); DoPressure(ConvertPressINHGToUser(data13baro.bar_lo), ts); // leave it at current value - ts = FromUnixTime(data13baro.ts); + ts = Utils.FromUnixTime(data13baro.ts); DoPressure(ConvertPressINHGToUser(data13baro.bar_sea_level), ts); DoPressTrend("Pressure trend"); // Altimeter from absolute @@ -2336,7 +2324,7 @@ private void DecodeWlApiHealth(WlHistorySensor sensor, bool startingup) { var data15 = sensor.data.Last().FromJsv(); - var dat = FromUnixTime(data15.firmware_version); + var dat = Utils.FromUnixTime(data15.firmware_version); DavisFirmwareVersion = dat.ToUniversalTime().ToString("yyyy-MM-dd"); var battV = data15.battery_voltage / 1000.0; @@ -2484,11 +2472,11 @@ private void GetWlHistoricHealth() return; } - var unixDateTime = ToUnixTime(DateTime.Now); + var unixDateTime = Utils.ToUnixTime(DateTime.Now); var startTime = unixDateTime - weatherLinkArchiveInterval; int endTime = unixDateTime; - cumulus.LogDebugMessage($"WLL Health: Downloading the historic record from WL.com from: {FromUnixTime(startTime):s} to: {FromUnixTime(endTime):s}"); + cumulus.LogDebugMessage($"WLL Health: Downloading the historic record from WL.com from: {Utils.FromUnixTime(startTime):s} to: {Utils.FromUnixTime(endTime):s}"); SortedDictionary parameters = new SortedDictionary { @@ -2552,7 +2540,7 @@ private void GetWlHistoricHealth() if (responseBody == "{}") { cumulus.LogMessage("WLL Health: WeatherLink API: No data was returned. Check your Device Id."); - cumulus.LastUpdateTime = FromUnixTime(endTime); + cumulus.LastUpdateTime = Utils.FromUnixTime(endTime); return; } @@ -2634,7 +2622,7 @@ private void GetWlHistoricHealth() // Return true if only 1 result is found, else return false private void GetAvailableStationIds(bool logToConsole = false) { - var unixDateTime = ToUnixTime(DateTime.Now); + var unixDateTime = Utils.ToUnixTime(DateTime.Now); if (cumulus.WllApiKey == string.Empty || cumulus.WllApiSecret == string.Empty) { @@ -2740,7 +2728,7 @@ private void GetAvailableStationIds(bool logToConsole = false) private void GetAvailableSensors() { - var unixDateTime = ToUnixTime(DateTime.Now); + var unixDateTime = Utils.ToUnixTime(DateTime.Now); if (cumulus.WllApiKey == string.Empty || cumulus.WllApiSecret == string.Empty) { diff --git a/CumulusMX/GW1000Station.cs b/CumulusMX/GW1000Station.cs index 3955d8d1..c5175df0 100644 --- a/CumulusMX/GW1000Station.cs +++ b/CumulusMX/GW1000Station.cs @@ -577,6 +577,7 @@ private void GetLiveData() double windSpeedLast = -999, rainRateLast = -999, rainLast = -999, gustLast = -999, gustLastCal = -999; int windDirLast = -999; + double outdoortemp = -999; bool batteryLow = false; @@ -592,7 +593,8 @@ private void GetLiveData() break; case 0x02: //Outdoor Temperature (℃) tempInt16 = ConvertBigEndianInt16(data, idx); - DoOutdoorTemp(ConvertTempCToUser(tempInt16 / 10.0), dateTime); + // do not process temperature here as if "MX calculates DP" is enabled, we have not yet read the humidity value. Have to do it at the end. + outdoortemp = tempInt16 / 10.0; idx += 2; break; case 0x03: //Dew point (℃) @@ -869,6 +871,10 @@ private void GetLiveData() } } while (idx < size); + // Process outdoor temperature here, as GW1000 currently does not supply Dew Point so we have to calculate it in DoOutdoorTemp() + if (outdoortemp > -999) + DoOutdoorTemp(ConvertTempCToUser(outdoortemp), dateTime); + if (tenMinuteChanged) tenMinuteChanged = false; // Now do the stuff that requires more than one input parameter diff --git a/CumulusMX/InternetSettings.cs b/CumulusMX/InternetSettings.cs index 37dd7234..5e86af13 100644 --- a/CumulusMX/InternetSettings.cs +++ b/CumulusMX/InternetSettings.cs @@ -222,8 +222,8 @@ public string UpdateInternetConfig(IHttpContext context) cumulus.WCloud.SendUV = settings.weathercloud.includeuv; cumulus.WCloud.SynchronisedUpdate = (60 % cumulus.WCloud.Interval == 0); - //cumulus.WCloudTimer.Interval = cumulus.WCloudInterval * 60 * 1000; - //cumulus.WCloudTimer.Enabled = cumulus.WCloudEnabled && !cumulus.SynchronisedWCloudUpdate && !String.IsNullOrWhiteSpace(cumulus.WCloudWid) && !String.IsNullOrWhiteSpace(cumulus.WCloudKey); + 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) { @@ -302,6 +302,27 @@ public string UpdateInternetConfig(IHttpContext context) context.Response.StatusCode = 500; } + // OpenWeatherMap + try + { + cumulus.OpenWeatherMap.Enabled = settings.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 { @@ -398,6 +419,9 @@ public string UpdateInternetConfig(IHttpContext context) // Save the settings cumulus.WriteIniFile(); + // Do OpenWeatherMap setup + cumulus.EnableOpenWeatherMap(); + cumulus.SetUpHttpProxy(); //cumulus.SetFtpLogging(cumulus.FTPlogging); @@ -572,6 +596,14 @@ public string GetInternetAlpacaFormData() 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() { @@ -653,6 +685,7 @@ public string GetInternetAlpacaFormData() pwsweather = pwssettings, wow = wowsettings, cwop = cwopsettings, + openweathermap = openweathermapsettings, mqtt = mqttsettings, moonimage = moonimagesettings, proxies = proxy, @@ -788,6 +821,7 @@ public class JsonInternetSettingsData 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; } @@ -930,6 +964,16 @@ public class JsonInternetSettingsCwop 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; } diff --git a/CumulusMX/ProgramSettings.cs b/CumulusMX/ProgramSettings.cs new file mode 100644 index 00000000..8e8e66d6 --- /dev/null +++ b/CumulusMX/ProgramSettings.cs @@ -0,0 +1,118 @@ +using System; +using System.IO; +using System.Net; +using ServiceStack.Text; +using Unosquare.Labs.EmbedIO; + +namespace CumulusMX +{ + public class ProgramSettings + { + private readonly Cumulus cumulus; + private readonly string programOptionsFile; + private readonly string programSchemaFile; + + 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"; + } + + public string GetProgramAlpacaFormData() + { + // Build the settings data, convert to JSON, and return it + var options = new JsonProgramSettingsOptions() + { + startuphostping = cumulus.ProgramOptions.StartupPingHost, + startupdelay = cumulus.ProgramOptions.StartupDelaySecs, + startupdelaymaxuptime = cumulus.ProgramOptions.StartupDelayMaxUptime, + debuglogging = cumulus.ProgramOptions.DebugLogging, + datalogging = cumulus.ProgramOptions.DataLogging, + stopsecondinstance = cumulus.ProgramOptions.WarnMultiple + }; + + + //return JsonConvert.SerializeObject(data); + return JsonSerializer.SerializeToString(options); + } + + public string GetProgramAlpacaFormOptions() + { + using (StreamReader sr = new StreamReader(programOptionsFile)) + { + string json = sr.ReadToEnd(); + return json; + } + } + + public string GetProgramAlpacaFormSchema() + { + using (StreamReader sr = new StreamReader(programSchemaFile)) + { + string json = sr.ReadToEnd(); + return json; + } + } + + + public string UpdateProgramConfig(IHttpContext context) + { + var errorMsg = ""; + context.Response.StatusCode = 200; + // get the response + try + { + 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)); + + // de-serialize it to the settings structure + var settings = JsonSerializer.DeserializeFromString(json); + + // process the settings + try + { + cumulus.ProgramOptions.StartupPingHost = settings.startuphostping; + cumulus.ProgramOptions.StartupDelaySecs = settings.startupdelay; + cumulus.ProgramOptions.StartupDelayMaxUptime = settings.startupdelaymaxuptime; + cumulus.ProgramOptions.DebugLogging = settings.debuglogging; + cumulus.ProgramOptions.DataLogging = settings.datalogging; + cumulus.ProgramOptions.WarnMultiple = settings.stopsecondinstance; + } + catch (Exception ex) + { + var msg = "Error processing Program Options: " + ex.Message; + cumulus.LogMessage(msg); + errorMsg += msg + "\n\n"; + context.Response.StatusCode = 500; + } + + // Save the settings + cumulus.WriteIniFile(); + } + catch (Exception ex) + { + cumulus.LogMessage(ex.Message); + context.Response.StatusCode = 500; + return ex.Message; + } + + return context.Response.StatusCode == 200 ? "success" : errorMsg; + } + } + + public class JsonProgramSettingsOptions + { + public string startuphostping { get; set; } + public int startupdelay { get; set; } + public int startupdelaymaxuptime { get; set; } + public bool debuglogging { get; set; } + public bool datalogging { get; set; } + public bool stopsecondinstance { get; set; } + } +} diff --git a/CumulusMX/Properties/AssemblyInfo.cs b/CumulusMX/Properties/AssemblyInfo.cs index f770e80c..d06599ed 100644 --- a/CumulusMX/Properties/AssemblyInfo.cs +++ b/CumulusMX/Properties/AssemblyInfo.cs @@ -5,8 +5,8 @@ // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("CumulusMX-test1")] -[assembly: AssemblyDescription("Build 3098")] +[assembly: AssemblyTitle("CumulusMX")] +[assembly: AssemblyDescription("Build 3099")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("CumulusMX")] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("3.9.3.3098")] -[assembly: AssemblyFileVersion("3.9.3.3098")] +[assembly: AssemblyVersion("3.9.4.3099")] +[assembly: AssemblyFileVersion("3.9.4.3099")] diff --git a/CumulusMX/StationSettings.cs b/CumulusMX/StationSettings.cs index 963b1f14..9a98196c 100644 --- a/CumulusMX/StationSettings.cs +++ b/CumulusMX/StationSettings.cs @@ -8,58 +8,57 @@ namespace CumulusMX { - public class StationSettings + internal class StationSettings { private readonly Cumulus cumulus; + private readonly WeatherStation station; private readonly string stationOptionsFile; private readonly string stationSchemaFile; - public StationSettings(Cumulus cumulus) + internal StationSettings(Cumulus cumulus, WeatherStation station) { this.cumulus = cumulus; + this.station = station; stationOptionsFile = cumulus.AppDir + "interface"+Path.DirectorySeparatorChar+"json" + Path.DirectorySeparatorChar + "StationOptions.json"; stationSchemaFile = cumulus.AppDir + "interface"+Path.DirectorySeparatorChar+"json" + Path.DirectorySeparatorChar + "StationSchema.json"; } - public string GetStationAlpacaFormData() + internal string GetStationAlpacaFormData() { // Build the settings data, convert to JSON, and return it var options = new JsonStationSettingsOptions() - { - usezerobearing = cumulus.StationOptions.UseZeroBearing, - calcwindaverage = cumulus.StationOptions.UseWind10MinAve, - usespeedforavg = cumulus.StationOptions.UseSpeedForAvgCalc, - use100for98hum = cumulus.StationOptions.Humidity98Fix, - calculatedewpoint = cumulus.StationOptions.CalculatedDP, - calculatewindchill = cumulus.StationOptions.CalculatedWC, - syncstationclock = cumulus.StationOptions.SyncTime, - cumuluspresstrendnames = cumulus.StationOptions.UseCumulusPresstrendstr, - vp1minbarupdate = cumulus.StationOptions.ForceVPBarUpdate, - extrasensors = cumulus.StationOptions.LogExtraSensors, - ignorelacrosseclock = cumulus.StationOptions.WS2300IgnoreStationClock, - roundwindspeeds = cumulus.StationOptions.RoundWindSpeed, - synchroniseforeads = cumulus.StationOptions.SyncFOReads, - debuglogging = cumulus.StationOptions.DebugLogging, - datalogging = cumulus.StationOptions.DataLogging, - stopsecondinstance = cumulus.StationOptions.WarnMultiple, - readreceptionstats = cumulus.StationOptions.DavisReadReceptionStats - }; + { + usezerobearing = cumulus.StationOptions.UseZeroBearing, + calcwindaverage = cumulus.StationOptions.UseWind10MinAve, + usespeedforavg = cumulus.StationOptions.UseSpeedForAvgCalc, + use100for98hum = cumulus.StationOptions.Humidity98Fix, + calculatedewpoint = cumulus.StationOptions.CalculatedDP, + calculatewindchill = cumulus.StationOptions.CalculatedWC, + syncstationclock = cumulus.StationOptions.SyncTime, + cumuluspresstrendnames = cumulus.StationOptions.UseCumulusPresstrendstr, + vp1minbarupdate = cumulus.StationOptions.ForceVPBarUpdate, + extrasensors = cumulus.StationOptions.LogExtraSensors, + ignorelacrosseclock = cumulus.StationOptions.WS2300IgnoreStationClock, + roundwindspeeds = cumulus.StationOptions.RoundWindSpeed, + synchroniseforeads = cumulus.StationOptions.SyncFOReads, + readreceptionstats = cumulus.StationOptions.DavisReadReceptionStats + }; var units = new JsonStationSettingsUnits() - { - wind = cumulus.WindUnit, - pressure = cumulus.PressUnit, - temp = cumulus.TempUnit, - rain = cumulus.RainUnit - }; + { + wind = cumulus.WindUnit, + pressure = cumulus.PressUnit, + temp = cumulus.TempUnit, + rain = cumulus.RainUnit + }; var tcpsettings = new JsonStationSettingsTCPsettings() - { - ipaddress = cumulus.VP2IPAddr, - tcpport = cumulus.VP2TCPPort, - disconperiod = cumulus.VP2PeriodicDisconnectInterval - }; + { + ipaddress = cumulus.VP2IPAddr, + tcpport = cumulus.VP2TCPPort, + disconperiod = cumulus.VP2PeriodicDisconnectInterval + }; var davisconn = new JsonStationSettingsDavisConn() {conntype = cumulus.VP2ConnectionType, tcpsettings = tcpsettings}; @@ -84,14 +83,14 @@ public string GetStationAlpacaFormData() var longitude = new JsonStationSettingsLatLong() { degrees = deg, minutes = min, seconds = sec, hemisphere = hem }; var location = new JsonStationSettingsLocation() - { - altitude = (int) cumulus.Altitude, - altitudeunit = "metres", - description = cumulus.LocationDesc, - Latitude = latitude, - Longitude = longitude, - sitename = cumulus.LocationName - }; + { + altitude = (int) cumulus.Altitude, + altitudeunit = "metres", + description = cumulus.LocationDesc, + Latitude = latitude, + Longitude = longitude, + sitename = cumulus.LocationName + }; if (cumulus.AltitudeInFeet) { @@ -99,13 +98,13 @@ public string GetStationAlpacaFormData() } var forecast = new JsonStationSettingsForecast() - { - highpressureextreme = cumulus.FChighpress, - lowpressureextreme = cumulus.FClowpress, - pressureunit = "mb/hPa", - updatehourly = cumulus.HourlyForecast, - usecumulusforecast = cumulus.UseCumulusForecast - }; + { + highpressureextreme = cumulus.FChighpress, + lowpressureextreme = cumulus.FClowpress, + pressureunit = "mb/hPa", + updatehourly = cumulus.HourlyForecast, + usecumulusforecast = cumulus.UseCumulusForecast + }; if (!cumulus.FCpressinMB) { @@ -113,13 +112,13 @@ public string GetStationAlpacaFormData() } var solar = new JsonStationSettingsSolar() - { - solarmin = cumulus.SolarMinimum, - transfactor = cumulus.RStransfactor, - sunthreshold = cumulus.SunThreshold, - solarcalc = cumulus.SolarCalc, - turbidity = cumulus.BrasTurbidity - }; + { + solarmin = cumulus.SolarMinimum, + transfactor = cumulus.RStransfactor, + sunthreshold = cumulus.SunThreshold, + solarcalc = cumulus.SolarCalc, + turbidity = cumulus.BrasTurbidity + }; var annualrainfall = new JsonStationSettingsAnnualRainfall() {rainseasonstart = cumulus.RainSeasonStart, ytdamount = cumulus.YTDrain, ytdyear = cumulus.YTDrainyear}; @@ -223,7 +222,7 @@ public string GetStationAlpacaFormData() }; var data = new JsonStationSettingsData() - { + { stationtype = cumulus.StationType, units = units, davisconn = davisconn, @@ -244,7 +243,7 @@ public string GetStationAlpacaFormData() return JsonSerializer.SerializeToString(data); } - public string GetStationAlpacaFormOptions() + internal string GetStationAlpacaFormOptions() { using (StreamReader sr = new StreamReader(stationOptionsFile)) { @@ -253,7 +252,7 @@ public string GetStationAlpacaFormOptions() } } - public string GetStationAlpacaFormSchema() + internal string GetStationAlpacaFormSchema() { using (StreamReader sr = new StreamReader(stationSchemaFile)) { @@ -309,8 +308,7 @@ private void LatToDMS(double latitude, out int d, out int m, out int s, out stri d = secs / 60; } - //public string UpdateStationConfig(HttpListenerContext context) - public string UpdateStationConfig(IHttpContext context) + internal string UpdateStationConfig(IHttpContext context) { var errorMsg = ""; context.Response.StatusCode = 200; @@ -325,8 +323,8 @@ public string UpdateStationConfig(IHttpContext context) var json = WebUtility.UrlDecode(data.Substring(5)); // de-serialize it to the settings structure - //var settings = JsonConvert.DeserializeObject(json); var settings = JsonSerializer.DeserializeFromString(json); + // process the settings // Graph Config @@ -455,9 +453,6 @@ public string UpdateStationConfig(IHttpContext context) cumulus.StationOptions.WS2300IgnoreStationClock = settings.Options.ignorelacrosseclock; cumulus.StationOptions.RoundWindSpeed = settings.Options.roundwindspeeds; cumulus.StationOptions.SyncFOReads = settings.Options.synchroniseforeads; - cumulus.StationOptions.DebugLogging = settings.Options.debuglogging; - cumulus.StationOptions.DataLogging = settings.Options.datalogging; - cumulus.StationOptions.WarnMultiple = settings.Options.stopsecondinstance; cumulus.StationOptions.DavisReadReceptionStats = settings.Options.readreceptionstats; } catch (Exception ex) @@ -651,49 +646,80 @@ public string UpdateStationConfig(IHttpContext context) return context.Response.StatusCode == 200 ? "success" : errorMsg; } - public string FtpNow() + internal string FtpNow(IHttpContext context) { - if (string.IsNullOrEmpty(cumulus.FtpHostname)) - return "{\"result\":\"No FTP host defined\"}"; + try + { + var data = new StreamReader(context.Request.InputStream).ReadToEnd(); + var json = WebUtility.UrlDecode(data); + // Dead simple (dirty), there is only one setting at present! + var includeGraphs = json.Contains("true"); - if (cumulus.WebUpdating == 1) - { - cumulus.LogMessage("Warning, manual FTP, a previous web update is still in progress, first chance, skipping attempt"); - return "{\"result\":\"A web update is already in progress\"}"; - } + if (string.IsNullOrEmpty(cumulus.FtpHostname)) + return "{\"result\":\"No FTP host defined\"}"; + + + if (cumulus.WebUpdating == 1) + { + cumulus.LogMessage("FTP Now: Warning, a previous web update is still in progress, first chance, skipping attempt"); + return "{\"result\":\"A web update is already in progress\"}"; + } + + if (cumulus.WebUpdating >= 2) + { + cumulus.LogMessage("FTP Now: Warning, a previous web update is still in progress, second chance, aborting connection"); + if (cumulus.ftpThread.ThreadState == ThreadState.Running) + cumulus.ftpThread.Abort(); + + // If enabled (re)generate the daily graph data files, and upload + if (includeGraphs && cumulus.IncludeGraphDataFiles) + { + cumulus.LogDebugMessage("FTP Now: Generating the daily graph data files"); + station.CreateEodGraphDataFiles(); + cumulus.DailyGraphDataFilesNeedFTP = true; + } + + cumulus.LogMessage("FTP Now: Trying new web update"); + cumulus.WebUpdating = 1; + cumulus.ftpThread = new Thread(cumulus.DoHTMLFiles) { IsBackground = true }; + cumulus.ftpThread.Start(); + return "{\"result\":\"An existing FTP process was aborted, and a new FTP process invoked\"}"; + } + + // If enabled (re)generate the daily graph data files, and upload + if (includeGraphs && cumulus.IncludeGraphDataFiles) + { + cumulus.LogDebugMessage("FTP Now: Generating the daily graph data files"); + station.CreateEodGraphDataFiles(); + cumulus.DailyGraphDataFilesNeedFTP = true; + } - if (cumulus.WebUpdating >= 2) - { - cumulus.LogMessage("Warning, manual FTP, a previous web update is still in progress,second chance, aborting connection"); - if (cumulus.ftpThread.ThreadState == System.Threading.ThreadState.Running) - cumulus.ftpThread.Abort(); - cumulus.LogMessage("Trying new web update"); cumulus.WebUpdating = 1; cumulus.ftpThread = new Thread(cumulus.DoHTMLFiles) { IsBackground = true }; cumulus.ftpThread.Start(); - return "{\"result\":\"Am existing FTP process was aborted, and a new FTP process invoked\"}"; + return "{\"result\":\"FTP process invoked\"}"; + } + catch (Exception ex) + { + cumulus.LogMessage($"FTP Now: {ex.Message}"); + context.Response.StatusCode = 500; + return $"{{\"result\":\"Error: {ex.Message}\"}}"; } - - cumulus.WebUpdating = 1; - cumulus.ftpThread = new Thread(cumulus.DoHTMLFiles) { IsBackground = true }; - cumulus.ftpThread.Start(); - return "{\"result\":\"FTP process invoked\"}"; - } - public string GetWSport() + internal string GetWSport() { return "{\"wsport\":\"" + cumulus.wsPort + "\"}"; } - public string GetVersion() + internal string GetVersion() { return "{\"Version\":\"" + cumulus.Version + "\",\"Build\":\"" + cumulus.Build + "\"}"; } } - public class JsonStationSettingsData + internal class JsonStationSettingsData { public int stationtype { get; set; } public JsonStationSettingsUnits units { get; set; } @@ -711,7 +737,7 @@ public class JsonStationSettingsData public JsonStationSettingsGraphs Graphs { get; set; } } - public class JsonStationSettingsUnits + internal class JsonStationSettingsUnits { public int wind { get; set; } public int pressure { get; set; } @@ -719,7 +745,7 @@ public class JsonStationSettingsUnits public int rain { get; set; } } - public class JsonStationSettingsOptions + internal class JsonStationSettingsOptions { public bool usezerobearing { get; set; } public bool calcwindaverage { get; set; } @@ -740,32 +766,32 @@ public class JsonStationSettingsOptions public bool readreceptionstats { get; set; } } - public class JsonStationSettingsTCPsettings + internal class JsonStationSettingsTCPsettings { public string ipaddress { get; set; } public int tcpport { get; set; } public int disconperiod { get; set; } } - public class JsonStationSettingsDavisConn + internal class JsonStationSettingsDavisConn { public int conntype { get; set; } public JsonStationSettingsTCPsettings tcpsettings { get; set; } } - public class JSonStationSettingsGw1000Conn + internal class JSonStationSettingsGw1000Conn { public string ipaddress { get; set; } public bool autoDiscover { get; set; } } - public class JsonStationSettingsLogRollover + internal class JsonStationSettingsLogRollover { public string time { get; set; } public bool summer10am { get; set; } } - public class JsonStationSettingsLatLong + internal class JsonStationSettingsLatLong { public int degrees { get; set; } public int minutes { get; set; } @@ -773,7 +799,7 @@ public class JsonStationSettingsLatLong public string hemisphere { get; set; } } - public class JsonStationSettingsLocation + internal class JsonStationSettingsLocation { public JsonStationSettingsLatLong Latitude { get; set; } public JsonStationSettingsLatLong Longitude { get; set; } @@ -783,7 +809,7 @@ public class JsonStationSettingsLocation public string description { get; set; } } - public class JsonStationSettingsForecast + internal class JsonStationSettingsForecast { public bool usecumulusforecast { get; set; } public bool updatehourly { get; set; } @@ -792,7 +818,7 @@ public class JsonStationSettingsForecast public string pressureunit { get; set; } } - public class JsonStationSettingsSolar + internal class JsonStationSettingsSolar { public int sunthreshold { get; set; } public int solarmin { get; set; } @@ -802,7 +828,7 @@ public class JsonStationSettingsSolar public double turbidity { get; set; } } - public class JsonStationSettingsWLL + internal class JsonStationSettingsWLL { public JsonStationSettingsWLLNetwork network { get; set; } public JsonStationSettingsWLLApi api { get; set; } @@ -811,19 +837,19 @@ public class JsonStationSettingsWLL public JsonStationSettingsWllExtraTemp extraTemp { get; set; } } - public class JsonStationSettingsWLLNetwork + internal class JsonStationSettingsWLLNetwork { public bool autoDiscover { get; set; } } - public class JsonStationSettingsWLLApi + internal class JsonStationSettingsWLLApi { public string apiKey { get; set; } public string apiSecret { get; set; } public string apiStationId { get; set; } } - public class JsonStationSettingsWllPrimary + internal class JsonStationSettingsWllPrimary { public int wind { get; set; } public int temphum { get; set; } @@ -832,14 +858,14 @@ public class JsonStationSettingsWllPrimary public int uv { get; set; } } - public class JsonStationSettingsWllSoilLeaf + internal class JsonStationSettingsWllSoilLeaf { public JsonStationSettingsWllSoilTemp extraSoilTemp { get; set; } public JsonStationSettingsWllSoilMoist extraSoilMoist { get; set; } public JsonStationSettingsWllExtraLeaf extraLeaf { get; set; } } - public class JsonStationSettingsWllSoilTemp + internal class JsonStationSettingsWllSoilTemp { public int soilTempTx1 { get; set; } public int soilTempIdx1 { get; set; } @@ -851,7 +877,7 @@ public class JsonStationSettingsWllSoilTemp public int soilTempIdx4 { get; set; } } - public class JsonStationSettingsWllSoilMoist + internal class JsonStationSettingsWllSoilMoist { public int soilMoistTx1 { get; set; } public int soilMoistIdx1 { get; set; } @@ -863,7 +889,7 @@ public class JsonStationSettingsWllSoilMoist public int soilMoistIdx4 { get; set; } } - public class JsonStationSettingsWllExtraLeaf + internal class JsonStationSettingsWllExtraLeaf { public int leafTx1 { get; set; } public int leafIdx1 { get; set; } diff --git a/CumulusMX/Utils.cs b/CumulusMX/Utils.cs new file mode 100644 index 00000000..518b7924 --- /dev/null +++ b/CumulusMX/Utils.cs @@ -0,0 +1,20 @@ +using System; +using Unosquare.Swan; + +namespace CumulusMX +{ + internal class Utils + { + public static DateTime FromUnixTime(long unixTime) + { + // WWL uses UTC ticks, convert to local time + var utcTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(unixTime); + return utcTime.ToLocalTime(); + } + + public static int ToUnixTime(DateTime dateTime) + { + return (int)dateTime.ToUniversalTime().ToUnixEpochDate(); + } + } +} diff --git a/CumulusMX/WMR100Station.cs b/CumulusMX/WMR100Station.cs index c13b3b41..130836df 100644 --- a/CumulusMX/WMR100Station.cs +++ b/CumulusMX/WMR100Station.cs @@ -56,6 +56,8 @@ public WMR100Station(Cumulus cumulus) : base(cumulus) WMR200ExtraHumValues = new double[11]; WMR200ChannelPresent = new bool[11]; WMR200ExtraDPValues = new double[11]; + + LoadLastHoursFromDataLogs(DateTime.Now); } else { @@ -72,7 +74,6 @@ public override void portDataReceived(object sender, SerialDataReceivedEventArgs public override void Start() { DoDayResetIfNeeded(); - LoadLastHoursFromDataLogs(DateTime.Now); DoTrendValues(DateTime.Now); cumulus.StartTimersAndSensors(); diff --git a/CumulusMX/WeatherStation.cs b/CumulusMX/WeatherStation.cs index 2effb0e1..9485aaf8 100644 --- a/CumulusMX/WeatherStation.cs +++ b/CumulusMX/WeatherStation.cs @@ -307,6 +307,8 @@ public struct DailyHighLow public double SolarElevation; + public double SolarFactor = -1; // used to adjust solar transmission factor (range 0-1), disabled = -1 + public bool WindReadyToPlot = false; public bool TempReadyToPlot = false; private bool first_temp = true; @@ -1531,6 +1533,7 @@ private void HourChanged(DateTime now) if (now.Hour == 0) { ResetMidnightRain(now); + //RecalcSolarFactor(now); } int rollHour = Math.Abs(cumulus.GetHourInc()); @@ -1754,6 +1757,11 @@ private void MinuteChanged(DateTime now) cumulus.UpdateWCloud(now); } + if (cumulus.OpenWeatherMap.Enabled && (now.Minute % cumulus.OpenWeatherMap.Interval == 0) && cumulus.OpenWeatherMap.SynchronisedUpdate && !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)) { cumulus.UpdatePWSweather(now); @@ -2803,6 +2811,29 @@ public void ResetSunshineHours() // called at midnight irrespective of rollover WriteYesterdayFile(); } + /* + private void RecalcSolarFactor(DateTime now) // called at midnight irrespective of rollover time + { + if (cumulus.SolarFactorSummer > 0 && cumulus.SolarFactorWinter > 0) + { + // Calculate the solar factor from the day of the year + // Use a cosine of the difference between summer and winter values + int doy = now.DayOfYear; + // take summer solistice as June 21 or December 21 (N & S hemispheres) - ignore leap years + // sol = day 172 (North) + // sol = day 355 (South) + int sol = cumulus.Latitude >= 0 ? 172 : 355; + int daysSinceSol = (doy - sol) % 365; + double multiplier = Math.Cos((daysSinceSol / 365) * 2 * Math.PI); // range +1/-1 + SolarFactor = (multiplier + 1) / 2; // bring it into the range 0-1 + } + else + { + SolarFactor = -1; + } + } + */ + public void SwitchToNormalRunning() { cumulus.CurrentActivity = "Normal running"; @@ -6422,6 +6453,7 @@ public void DoDayResetIfNeeded() { ResetMidnightRain(DateTime.Now); ResetSunshineHours(); + //RecalcSolarFactor(DateTime.Now); } } @@ -8578,249 +8610,6 @@ private string ByteArrayToString(byte[] ba) return hex.ToString(); } - /* - public string GetAwekasURL(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.AwekasPW)); - } - - pwstring = ByteArrayToString(hashPW); - - int presstrend; - - double threeHourlyPressureChangeMb = 0; - - switch (cumulus.PressUnit) - { - case 0: - case 1: - threeHourlyPressureChangeMb = presstrendval * 3; - break; - case 2: - threeHourlyPressureChangeMb = presstrendval * 3 / 0.0295333727; - break; - } - - if (threeHourlyPressureChangeMb > 6) presstrend = 2; - else if (threeHourlyPressureChangeMb > 3.5) presstrend = 2; - else if (threeHourlyPressureChangeMb > 1.5) presstrend = 1; - else if (threeHourlyPressureChangeMb > 0.1) presstrend = 1; - else if (threeHourlyPressureChangeMb > -0.1) presstrend = 0; - else if (threeHourlyPressureChangeMb > -1.5) presstrend = -1; - else if (threeHourlyPressureChangeMb > -3.5) presstrend = -1; - else if (threeHourlyPressureChangeMb > -6) presstrend = -2; - else - presstrend = -2; - - double AvgTemp; - if (tempsamplestoday > 0) - AvgTemp = TempTotalToday / tempsamplestoday; - else - AvgTemp = 0; - - StringBuilder sb = new StringBuilder("http://data.awekas.at/eingabe_pruefung.php?val="); - sb.Append(cumulus.AwekasUser + sep); // 1 - sb.Append(pwstring + sep); //2 - sb.Append(timestamp.ToString("dd'.'MM'.'yyyy';'HH':'mm") + sep); // 3 + 4 - sb.Append(ConvertUserTempToC(OutdoorTemperature).ToString("F1", InvC) + sep); // 5 - sb.Append(OutdoorHumidity + sep); // 6 - sb.Append(ConvertUserPressToMB(Pressure).ToString("F1", InvC) + sep); // 7 - sb.Append(ConvertUserRainToMM(RainToday).ToString("F1", InvC) + sep); // 8 - sb.Append(ConvertUserWindToKPH(WindAverage).ToString("F1", InvC) + sep); // 9 - sb.Append(AvgBearing + sep); // 10 - sb.Append(sep + sep + sep); // 11/12/13 - condition and warning, snow height - sb.Append(cumulus.AwekasLang + sep); // 14 - sb.Append(presstrend + sep); // 15 - sb.Append(ConvertUserWindToKPH(RecentMaxGust).ToString("F1", InvC) + sep); // 16 - - if (cumulus.SendSolarToAwekas) - { - sb.Append(SolarRad.ToString("F1", InvC) + sep); // 17 - } - else - { - sb.Append(sep); - } - - if (cumulus.SendUVToAwekas) - { - sb.Append(UV.ToString("F1", InvC) + sep); // 18 - } - else - { - sb.Append(sep); - } - - if (cumulus.SendSolarToAwekas) - { - if (cumulus.StationType == StationTypes.FineOffsetSolar) { - sb.Append(LightValue.ToString("F0", InvC) + sep); // 19 - } - else - { - sb.Append(sep); - } - - sb.Append(SunshineHours.ToString("F0", InvC) + sep); // 20 - } - else - { - sb.Append(sep + sep); - } - - if (cumulus.SendSoilTempToAwekas) - { - sb.Append(ConvertUserTempToC(SoilTemp1).ToString("F1", InvC) + sep); // 21 - } - else - { - sb.Append(sep); - } - - sb.Append(ConvertUserRainToMM(RainRate).ToString("F1", InvC) + sep); // 22 - - sb.Append("Cum_" + cumulus.Version + sep); //23 - - sb.Append(sep + sep); // 24/25 location for mobile - - sb.Append(ConvertUserTempToC(HiLoToday.LowTemp).ToString("F1", InvC) + sep); // 26 - - sb.Append(ConvertUserTempToC(AvgTemp).ToString("F1", InvC) + sep); // 27 - - sb.Append(ConvertUserTempToC(HiLoToday.HighTemp).ToString("F1", InvC) + sep); // 28 - - sb.Append(ConvertUserTempToC(ThisMonthRecs.LowTemp.Val).ToString("F1", InvC) + sep); // 29 - - sb.Append(sep); // 30 avg temp this month - - sb.Append(ConvertUserTempToC(ThisMonthRecs.HighTemp.Val).ToString("F1", InvC) + sep); // 31 - - sb.Append(ConvertUserTempToC(ThisYear.LowTemp.Val).ToString("F1", InvC) + sep); // 32 - - sb.Append(sep); // 33 avg temp this year - - sb.Append(ConvertUserTempToC(ThisYear.HighTemp.Val).ToString("F1", InvC) + sep); // 34 - - sb.Append(HiLoToday.LowHumidity + sep); // 35 - - sb.Append(sep); // 36 avg hum today - - sb.Append(HiLoToday.HighHumidity + sep); // 37 - - sb.Append(ThisMonthRecs.LowHumidity.Val + sep); // 38 - - sb.Append(sep); // 39 avg hum this month - - sb.Append(ThisMonthRecs.HighHumidity.Val + sep); // 40 - - sb.Append(ThisYear.LowHumidity.Val + sep); // 41 - - sb.Append(sep); // 42 avg hum this year - - sb.Append(ThisYear.HighHumidity.Val + sep); // 43 - - sb.Append(ConvertUserPressToMB(HiLoToday.LowPress).ToString("F1", InvC) + sep); // 44 - - sb.Append(sep); // 45 avg press today - - sb.Append(ConvertUserPressToMB(HiLoToday.HighPress).ToString("F1", InvC) + sep); // 46 - - sb.Append(ConvertUserPressToMB(ThisMonthRecs.LowPress.Val).ToString("F1", InvC) + sep); // 47 - - sb.Append(sep); // 48 avg press this month - - sb.Append(ConvertUserPressToMB(ThisMonthRecs.HighPress.Val).ToString("F1", InvC) + sep); // 49 - - sb.Append(ConvertUserPressToMB(ThisYear.LowPress.Val).ToString("F1", InvC) + sep); // 50 - - sb.Append(sep); // 51 avg press this year - - sb.Append(ConvertUserPressToMB(ThisYear.HighPress.Val).ToString("F1", InvC) + sep); // 52 - - sb.Append(sep + sep); // 53/54 min/avg wind today - - sb.Append(ConvertUserWindToKPH(HiLoToday.HighWind).ToString("F1", InvC) + sep); // 55 - - sb.Append(sep + sep); // 56/57 min/avg wind this month - - sb.Append(ConvertUserWindToKPH(ThisMonthRecs.HighWind.Val).ToString("F1", InvC) + sep); // 58 - - sb.Append(sep + sep); // 59/60 min/avg wind this year - - sb.Append(ConvertUserWindToKPH(ThisYear.HighWind.Val).ToString("F1", InvC) + sep); // 61 - - sb.Append(sep + sep); // 62/63 min/avg gust today - - sb.Append(ConvertUserWindToKPH(HiLoToday.HighGust).ToString("F1", InvC) + sep); // 64 - - sb.Append(sep + sep); // 65/66 min/avg gust this month - - sb.Append(ConvertUserWindToKPH(ThisMonthRecs.HighGust.Val).ToString("F1", InvC) + sep); // 67 - - sb.Append(sep + sep); // 68/69 min/avg gust this year - - sb.Append(ConvertUserWindToKPH(ThisYear.HighGust.Val).ToString("F1", InvC) + sep); // 70 - - sb.Append(sep + sep + sep); // 71/72/73 avg wind bearing today/month/year - - sb.Append(ConvertUserRainToMM(RainLast24Hour).ToString("F1", InvC) + sep); // 74 - - sb.Append(ConvertUserRainToMM(RainMonth).ToString("F1", InvC) + sep); // 75 - - sb.Append(ConvertUserRainToMM(RainYear).ToString("F1", InvC) + sep); // 76 - - sb.Append(sep); // 77 avg rain rate today - - sb.Append(ConvertUserRainToMM(HiLoToday.HighRain).ToString("F1", InvC) + sep); // 78 - - sb.Append(sep); // 79 avg rain rate this month - - sb.Append(ConvertUserRainToMM(ThisMonthRecs.HighRainRate.Val).ToString("F1", InvC) + sep); // 80 - - sb.Append(sep); // 81 avg rain rate this year - - sb.Append(ConvertUserRainToMM(ThisYear.HighRainRate.Val).ToString("F1", InvC) + sep); // 82 - - sb.Append(sep); // 83 avg solar today - - sb.Append(HiLoToday.HighSolar.ToString("F1", InvC)); // 84 - - sb.Append(sep); // 85 avg solar this month - - sb.Append(sep); // 86 high solar this month - - sb.Append(sep); // 87 avg solar this year - - sb.Append(sep); // 88 high solar this year - - sb.Append(sep); // 89 avg uv today - - sb.Append(HiLoToday.HighUv.ToString("F1", InvC)); // 90 - - sb.Append(sep); // 91 avg uv this month - - sb.Append(sep); // 92 high uv this month - - sb.Append(sep); // 93 avg uv this year - - sb.Append(sep); // 94 high uv this year - - sb.Append(sep + sep + sep + sep + sep + sep); // 95/96/97/98/99/100 avg/max lux today/month/year - - sb.Append(sep + sep); // 101/102 sun hours this month/year - - sb.Append(sep + sep + sep + sep + sep + sep + sep + sep + sep); //103-111 min/avg/max Soil temp today/month/year - - return sb.ToString(); - } - */ public string GetAwekasURLv4(out string pwstring, DateTime timestamp) { @@ -9206,6 +8995,24 @@ public string GetWindyURL(out string apistring, DateTime timestamp) return URL.ToString(); } + public string GetOpenWeatherMapData(DateTime timestamp) + { + StringBuilder sb = new StringBuilder($"[{{\"station_id\":\"{cumulus.OpenWeatherMap.ID}\","); + + sb.Append($"\"dt\":{Utils.ToUnixTime(timestamp)},"); + sb.Append($"\"temperature\":{Math.Round(ConvertUserTempToC(OutdoorTemperature), 1)},"); + sb.Append($"\"wind_deg\":{AvgBearing},"); + sb.Append($"\"wind_speed\":{Math.Round(ConvertUserWindToMS(WindAverage), 1)},"); + sb.Append($"\"wind_gust\":{Math.Round(ConvertUserWindToMS(RecentMaxGust), 1)},"); + sb.Append($"\"pressure\":{Math.Round(PressureHPa(Pressure), 1)},"); + sb.Append($"\"humidity\":{OutdoorHumidity},"); + sb.Append($"\"rain_1h\":{Math.Round(ConvertUserRainToMM(RainLastHour), 1)},"); + sb.Append($"\"rain_24h\":{Math.Round(ConvertUserRainToMM(RainLast24Hour), 1)}"); + sb.Append("}]"); + + return sb.ToString(); + } + private string PressINstr(double pressure) { var pressIN = ConvertUserPressToIN(pressure); diff --git a/CumulusMX/webtags.cs b/CumulusMX/webtags.cs index fb476c8b..68224a14 100644 --- a/CumulusMX/webtags.cs +++ b/CumulusMX/webtags.cs @@ -4610,9 +4610,21 @@ private string TagSystemUpTime(Dictionary tagParams) { try { - var upTime = new PerformanceCounter("System", "System Up Time"); - upTime.NextValue(); - TimeSpan ts = TimeSpan.FromSeconds(upTime.NextValue()); + double upTime = 0; + if (cumulus.Platform.Substring(0, 3) == "Win") + { + cumulus.UpTime.NextValue(); + upTime = cumulus.UpTime.NextValue(); + } + else if (File.Exists(@"/proc/uptime")) + { + var text = File.ReadAllText(@"/proc/uptime"); + var strTime = text.Split(' ')[0]; + double.TryParse(strTime, out upTime); + } + + TimeSpan ts = TimeSpan.FromSeconds(upTime); + return string.Format($"{ts.Days} days {ts.Hours} hours"); } catch (Exception ex) diff --git a/Updates.txt b/Updates.txt index 7bb37dfe..3d9bf048 100644 --- a/Updates.txt +++ b/Updates.txt @@ -1,3 +1,29 @@ +3.9.4 - b3099 +————————————— +- Fix: WMR100 stations generated empty daily graph data files on start-up +- Fix: System uptime web tag for Linux systems - maybe! +- Fix: Historic charts wind run tooltip suffix +- Fix: High Min Temp and Low Max Temp records editors were not displaying and allowing input of the time +- Fix: This Month/This Year records editors were showing records for the entire day file rather than the specific period +- Fix: GW1000 station dewpoint calculation incorrect on first packet read at start-up + +- New: Settings page: Program Settings. Moved Debug/Data logging options and Prevent Second Instance to here from Station Settings. + Note: These settings still remain in the [Station] section of the Cumulus.ini file for backwards compatibility +- New: Two settings to delay the start-up of Cumulus MX. Both added to the new Program Settings page. + Start-up Host PING: Specify a remote hostname or IP address, Cumulus will wait for a successful PING from this address before continuing + Start-up Delay: Specify a fixed time in seconds to delay the start-up. The delay will be executed after the PING check is one is specified + Start-up Delay Max Uptime: Specify a maximum system uptime in seconds after which the delay will no longer be applied. Set to zero to always delay + New section in Cumulus.ini: + [Program] + StartupPingHost= // Default is no remote host - enter a host name or IP address + StartupDelaySecs=0 // Default is no delay = 0 + StartupDelayMaxUptime=300 // Default is 300 seconds (5 minutes) +- New: FTP Now - Adds a new option to regenerate the daily graph data files (if enabled), and include them in the manually invoked FTP session +- New: Adds support for OpenWeatherMap uploads (and catch-up). Using an existing station if the id is supplied, or your API key only has one station defined. + If no stations are defined CMX automatically creates a new one using your station details and starts uploading to it. + + + 3.9.3 - b3098 ————————————— - Fix: Records check was triggering for very small value changes in derived values such as feels like temps