From 6221e3071ba128b3580ce78f097df7efe6453ae5 Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Tue, 4 Nov 2025 17:38:41 +0500 Subject: [PATCH 1/5] refactor(weather): centralize weather data cache and improve update control - Extract shared weather data into a static WeatherDataCache class accessible by all measures - Replace instance fields with static fields for centralized weather state storage - Implement rate limiting to prevent excessive API calls with a minimum interval between requests - Use locking to handle concurrent updates and track active measure instances correctly - Refactor asynchronous update logic to run in background tasks with proper status tracking - Improve logging for update scheduling, API calls, and rate limit events - Remove deprecated HTTP fallback logic for API calls on rate limit exceedance - Update parsing methods to use centralized static data cache fields - Add debug variables to expose next update time and update interval information - Refactor value retrieval methods to reference centralized weather data cache fields --- WeatherX/WeatherX.cs | 532 ++++++++++++++++++++++--------------------- 1 file changed, 278 insertions(+), 254 deletions(-) diff --git a/WeatherX/WeatherX.cs b/WeatherX/WeatherX.cs index 859da55..047949d 100644 --- a/WeatherX/WeatherX.cs +++ b/WeatherX/WeatherX.cs @@ -18,85 +18,60 @@ protected override WebRequest GetWebRequest(Uri address) } } -internal class Measure +// Shared weather data cache that all measures can access +internal static class WeatherDataCache { - private double latitude, longitude; - private string dataType; - private int forecastDay; - private int hourOffset; - private int updateInterval; - private DateTime lastUpdate; - private API api; - private string units; - private string timezone; - - private double currentTemp = 0.0; - private string currentCondition = "Unknown"; - private double currentHumidity = 0.0; - private double currentWindSpeed = 0.0; - private double currentPressure = 0.0; - private double currentApparentTemp = 0.0; - private double currentDewPoint = 0.0; - private double currentCloudCover = 0.0; - private double currentWindDirection = 0.0; - private double currentWindGusts = 0.0; - - private double currentSolarRadiation = 0.0; - private double currentDirectRadiation = 0.0; - private double currentDiffuseRadiation = 0.0; - private double currentIsDay = 0.0; - private double currentWeatherCode = 0.0; - - private double todayUvIndex = 0.0; - - private double[] forecastTempMax = new double[7]; - private double[] forecastTempMin = new double[7]; - private double[] forecastApparentTempMax = new double[7]; - private double[] forecastApparentTempMin = new double[7]; - private string[] forecastConditions = new string[7]; - private double[] forecastWeatherCode = new double[7]; - private double[] forecastWindSpeedMax = new double[7]; - private double[] forecastUvIndexMax = new double[7]; - private string[] forecastSunrise = new string[7]; - private string[] forecastSunset = new string[7]; - - private double[] hourlyTemp = new double[48]; - private double[] hourlyHumidity = new double[48]; - private double[] hourlyWindSpeed = new double[48]; - private double[] hourlyApparentTemp = new double[48]; - private double[] hourlyCloudCover = new double[48]; - private double[] hourlyVisibility = new double[48]; - private int[] hourlyWeatherCode = new int[48]; - private string[] hourlyTime = new string[48]; - - private double[] hourlySolarRadiation = new double[48]; - private double[] hourlyDirectRadiation = new double[48]; - private double[] hourlyDiffuseRadiation = new double[48]; - - private string lastError = ""; - private string lastApiUrl = ""; - private volatile bool isUpdating = false; - private bool initialUpdateScheduled = false; - private Timer updateTimer; - - internal Measure() + public static double currentTemp = 0.0; + public static string currentCondition = "Unknown"; + public static double currentHumidity = 0.0; + public static double currentWindSpeed = 0.0; + public static double currentPressure = 0.0; + public static double currentApparentTemp = 0.0; + public static double currentDewPoint = 0.0; + public static double currentCloudCover = 0.0; + public static double currentWindDirection = 0.0; + public static double currentWindGusts = 0.0; + public static double currentSolarRadiation = 0.0; + public static double currentDirectRadiation = 0.0; + public static double currentDiffuseRadiation = 0.0; + public static double currentIsDay = 0.0; + public static double currentWeatherCode = 0.0; + public static double todayUvIndex = 0.0; + + public static double[] forecastTempMax = new double[7]; + public static double[] forecastTempMin = new double[7]; + public static double[] forecastApparentTempMax = new double[7]; + public static double[] forecastApparentTempMin = new double[7]; + public static string[] forecastConditions = new string[7]; + public static double[] forecastWeatherCode = new double[7]; + public static double[] forecastWindSpeedMax = new double[7]; + public static double[] forecastUvIndexMax = new double[7]; + public static string[] forecastSunrise = new string[7]; + public static string[] forecastSunset = new string[7]; + + public static double[] hourlyTemp = new double[48]; + public static double[] hourlyHumidity = new double[48]; + public static double[] hourlyWindSpeed = new double[48]; + public static double[] hourlyApparentTemp = new double[48]; + public static double[] hourlyCloudCover = new double[48]; + public static double[] hourlyVisibility = new double[48]; + public static int[] hourlyWeatherCode = new int[48]; + public static string[] hourlyTime = new string[48]; + public static double[] hourlySolarRadiation = new double[48]; + public static double[] hourlyDirectRadiation = new double[48]; + public static double[] hourlyDiffuseRadiation = new double[48]; + + public static DateTime lastUpdate = DateTime.MinValue; + public static string lastError = ""; + public static string lastApiUrl = ""; + + static WeatherDataCache() { - latitude = 0.0; - longitude = 0.0; - dataType = "CurrentTemp"; - forecastDay = 0; - hourOffset = 0; - updateInterval = 600; - lastUpdate = DateTime.MinValue; - units = "metric"; - timezone = "auto"; - InitializeArrays(); } - private void InitializeArrays() + public static void InitializeArrays() { - for (int i = 0; i < 7; i++) { forecastTempMax[i] = 0.0; @@ -126,6 +101,49 @@ private void InitializeArrays() hourlyDiffuseRadiation[i] = 0.0; } } +} + +internal class Measure +{ + private double latitude, longitude; + private string dataType; + private int forecastDay; + private int hourOffset; + private int updateInterval; + private API api; + private string units; + private string timezone; + + private static DateTime lastGlobalApiCall = DateTime.MinValue; + private static readonly object apiCallLock = new object(); + private static volatile bool isUpdating = false; + private const int MIN_API_CALL_INTERVAL = 5; + private static int activeMeasures = 0; + + internal Measure() + { + latitude = 0.0; + longitude = 0.0; + dataType = "CurrentTemp"; + forecastDay = 0; + hourOffset = 0; + updateInterval = 600; + units = "metric"; + timezone = "auto"; + + lock (apiCallLock) + { + activeMeasures++; + } + } + + ~Measure() + { + lock (apiCallLock) + { + activeMeasures--; + } + } internal void Reload(Rainmeter.API api, ref double maxValue) { @@ -142,12 +160,12 @@ internal void Reload(Rainmeter.API api, ref double maxValue) if (latitude < -90 || latitude > 90 || longitude < -180 || longitude > 180) { - lastError = "Invalid coordinates"; + WeatherDataCache.lastError = "Invalid coordinates"; api.Log(API.LogType.Error, $"WeatherX: Invalid coordinates Lat={latitude}, Lon={longitude}"); } else { - lastError = ""; + WeatherDataCache.lastError = ""; } if (forecastDay < 0 || forecastDay > 6) @@ -165,25 +183,62 @@ internal void Reload(Rainmeter.API api, ref double maxValue) units = "metric"; } - lastUpdate = DateTime.MinValue; - initialUpdateScheduled = false; - isUpdating = false; - - updateTimer?.Dispose(); - api.Log(API.LogType.Debug, $"WeatherX: Initialized with Lat={latitude}, Lon={longitude}, DataType={dataType}, Units={units}, HourOffset={hourOffset}"); + + // Always force update on reload/refresh + lock (apiCallLock) + { + // Reset the last update time to trigger immediate update + WeatherDataCache.lastUpdate = DateTime.MinValue; + } + api.Log(API.LogType.Debug, "WeatherX: Reload detected - forcing data refresh"); } internal double Update() { - if (!initialUpdateScheduled && string.IsNullOrEmpty(lastError)) + // Check if an update is needed based on updateInterval + double timeSinceLastUpdate = DateTime.Now.Subtract(WeatherDataCache.lastUpdate).TotalSeconds; + bool needsUpdate = (WeatherDataCache.lastUpdate == DateTime.MinValue) || (timeSinceLastUpdate >= updateInterval); + + if (needsUpdate && !isUpdating) { - initialUpdateScheduled = true; - updateTimer = new Timer(async _ => await UpdateWeatherDataAsync(), null, 2000, Timeout.Infinite); + lock (apiCallLock) + { + // Double-check inside lock to prevent race conditions + timeSinceLastUpdate = DateTime.Now.Subtract(WeatherDataCache.lastUpdate).TotalSeconds; + needsUpdate = (WeatherDataCache.lastUpdate == DateTime.MinValue) || (timeSinceLastUpdate >= updateInterval); + + if (needsUpdate && !isUpdating) + { + double timeSinceLastGlobalCall = DateTime.Now.Subtract(lastGlobalApiCall).TotalSeconds; + + // Ensure minimum interval between API calls to prevent rate limiting + if (timeSinceLastGlobalCall >= MIN_API_CALL_INTERVAL) + { + isUpdating = true; + lastGlobalApiCall = DateTime.Now; + api?.Log(API.LogType.Debug, + $"WeatherX: Triggering update - Time since last: {timeSinceLastUpdate:F1}s, Interval: {updateInterval}s"); + Task.Run(async () => await UpdateWeatherDataAsync()); + } + else + { + double waitTime = MIN_API_CALL_INTERVAL - timeSinceLastGlobalCall; + api?.Log(API.LogType.Debug, + $"WeatherX: Rate limit protection - wait {waitTime:F1}s more (last call: {timeSinceLastGlobalCall:F1}s ago, min: {MIN_API_CALL_INTERVAL}s)"); + } + } + } } - else if (DateTime.Now.Subtract(lastUpdate).TotalSeconds >= updateInterval && !isUpdating) + else if (!needsUpdate && WeatherDataCache.lastUpdate != DateTime.MinValue) { - Task.Run(async () => await UpdateWeatherDataAsync()); + // Log occasionally to show interval is working (every 60 seconds) + if (DateTime.Now.Second == 0) + { + double timeRemaining = updateInterval - timeSinceLastUpdate; + api?.Log(API.LogType.Debug, + $"WeatherX: Next update in {timeRemaining:F0}s (Interval: {updateInterval}s, Last update: {timeSinceLastUpdate:F0}s ago)"); + } } return GetNumericValue(); @@ -191,14 +246,10 @@ internal double Update() private async Task UpdateWeatherDataAsync() { - if (isUpdating) return; - - isUpdating = true; - try { - lastApiUrl = BuildApiUrl(); - api?.Log(API.LogType.Debug, $"WeatherX: Making API call to: {lastApiUrl}"); + WeatherDataCache.lastApiUrl = BuildApiUrl(); + api?.Log(API.LogType.Debug, $"WeatherX: Making API call to: {WeatherDataCache.lastApiUrl}"); System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12 | @@ -212,60 +263,38 @@ private async Task UpdateWeatherDataAsync() client.Encoding = Encoding.UTF8; client.Timeout = 15000; - string response = await client.DownloadStringTaskAsync(lastApiUrl); + string response = await client.DownloadStringTaskAsync(WeatherDataCache.lastApiUrl); ParseWeatherData(response); - lastError = ""; - lastUpdate = DateTime.Now; + WeatherDataCache.lastError = ""; + WeatherDataCache.lastUpdate = DateTime.Now; + + api?.Log(API.LogType.Debug, + $"WeatherX: ✓ Data updated successfully at {WeatherDataCache.lastUpdate:HH:mm:ss} | Next update in {updateInterval}s"); } } - catch (WebException ex) + catch (WebException wex) { - if (lastApiUrl.StartsWith("https://")) + if (wex.Response is HttpWebResponse response && response.StatusCode == (HttpStatusCode)429) { - try - { - api?.Log(API.LogType.Warning, $"WeatherX: HTTPS failed, trying HTTP fallback"); - string httpUrl = lastApiUrl.Replace("https://", "http://"); - - using (TimeoutWebClient client = new TimeoutWebClient()) - { - client.Headers.Add("User-Agent", "WeatherX-Rainmeter-Plugin/1.0"); - client.Headers.Add("Accept", "application/json"); - client.Encoding = Encoding.UTF8; - client.Timeout = 15000; - - string response = await client.DownloadStringTaskAsync(httpUrl); - ParseWeatherData(response); - - lastError = ""; - lastUpdate = DateTime.Now; - api?.Log(API.LogType.Debug, $"WeatherX: HTTP fallback successful. Temp: {currentTemp}°"); - return; - } - } - catch (Exception fallbackEx) - { - api?.Log(API.LogType.Error, $"WeatherX: HTTP fallback also failed: {fallbackEx.Message}"); - } + WeatherDataCache.lastError = "Rate limit exceeded - please wait"; + api?.Log(API.LogType.Warning, $"WeatherX: API rate limit hit (429). Waiting before next attempt."); + WeatherDataCache.lastUpdate = DateTime.Now; + } + else + { + WeatherDataCache.lastError = $"Error: {wex.Message}"; + api?.Log(API.LogType.Error, $"WeatherX: {WeatherDataCache.lastError}"); } - - lastError = $"Network Error: {ex.Message}"; - api?.Log(API.LogType.Error, $"WeatherX: {lastError}"); } catch (Exception ex) { - lastError = $"Error: {ex.Message}"; - api?.Log(API.LogType.Error, $"WeatherX: {lastError}"); + WeatherDataCache.lastError = $"Error: {ex.Message}"; + api?.Log(API.LogType.Error, $"WeatherX: {WeatherDataCache.lastError}"); } finally { isUpdating = false; - - if (updateTimer != null) - { - updateTimer.Change(updateInterval * 1000, Timeout.Infinite); - } } } @@ -277,14 +306,11 @@ private string BuildApiUrl() return $"https://api.open-meteo.com/v1/forecast?" + $"latitude={latitude.ToString(CultureInfo.InvariantCulture)}&" + $"longitude={longitude.ToString(CultureInfo.InvariantCulture)}&" + - $"current=temperature_2m,relative_humidity_2m,weather_code,surface_pressure,wind_speed_10m," + $"apparent_temperature,dew_point_2m,wind_direction_10m,wind_gusts_10m,is_day&" + - $"hourly=temperature_2m,relative_humidity_2m,wind_speed_10m," + $"apparent_temperature,cloud_cover,visibility,weather_code," + $"shortwave_radiation,direct_radiation,diffuse_radiation&" + - $"daily=weather_code,temperature_2m_max,temperature_2m_min,apparent_temperature_max," + $"apparent_temperature_min,wind_speed_10m_max,uv_index_max," + $"sunrise,sunset,shortwave_radiation_sum&" + @@ -302,10 +328,9 @@ public void ParseWeatherData(string jsonResponse) ParseCurrentWeather(jsonResponse); ParseDailyForecasts(jsonResponse); ParseHourlyData(jsonResponse); - UpdateCurrentFromHourly(); - api?.Log(API.LogType.Debug, $"WeatherX: Comprehensive parsing complete - Temp: {currentTemp}°, Condition: {currentCondition}"); + api?.Log(API.LogType.Debug, $"WeatherX: Parsing complete - Temp: {WeatherDataCache.currentTemp}°, Condition: {WeatherDataCache.currentCondition}"); } catch (Exception ex) { @@ -316,34 +341,32 @@ public void ParseWeatherData(string jsonResponse) private void ParseCurrentWeather(string json) { - - currentTemp = ParseHelper.ParseJsonValue(json, "\"current\"", "\"temperature_2m\""); - currentHumidity = ParseHelper.ParseJsonValue(json, "\"current\"", "\"relative_humidity_2m\""); - currentWindSpeed = ParseHelper.ParseJsonValue(json, "\"current\"", "\"wind_speed_10m\""); - currentPressure = ParseHelper.ParseJsonValue(json, "\"current\"", "\"surface_pressure\""); - - currentApparentTemp = ParseHelper.ParseJsonValue(json, "\"current\"", "\"apparent_temperature\""); - currentDewPoint = ParseHelper.ParseJsonValue(json, "\"current\"", "\"dew_point_2m\""); - currentWindDirection = ParseHelper.ParseJsonValue(json, "\"current\"", "\"wind_direction_10m\""); - currentWindGusts = ParseHelper.ParseJsonValue(json, "\"current\"", "\"wind_gusts_10m\""); - - currentWeatherCode = ParseHelper.ParseJsonValue(json, "\"current\"", "\"weather_code\""); - currentIsDay = ParseHelper.ParseJsonValue(json, "\"current\"", "\"is_day\""); - - int weatherCode = (int)currentWeatherCode; - currentCondition = CommonHelper.GetWeatherDescription(weatherCode); + WeatherDataCache.currentTemp = ParseHelper.ParseJsonValue(json, "\"current\"", "\"temperature_2m\""); + WeatherDataCache.currentHumidity = ParseHelper.ParseJsonValue(json, "\"current\"", "\"relative_humidity_2m\""); + WeatherDataCache.currentWindSpeed = ParseHelper.ParseJsonValue(json, "\"current\"", "\"wind_speed_10m\""); + WeatherDataCache.currentPressure = ParseHelper.ParseJsonValue(json, "\"current\"", "\"surface_pressure\""); + WeatherDataCache.currentApparentTemp = ParseHelper.ParseJsonValue(json, "\"current\"", "\"apparent_temperature\""); + WeatherDataCache.currentDewPoint = ParseHelper.ParseJsonValue(json, "\"current\"", "\"dew_point_2m\""); + WeatherDataCache.currentWindDirection = ParseHelper.ParseJsonValue(json, "\"current\"", "\"wind_direction_10m\""); + WeatherDataCache.currentWindGusts = ParseHelper.ParseJsonValue(json, "\"current\"", "\"wind_gusts_10m\""); + WeatherDataCache.currentWeatherCode = ParseHelper.ParseJsonValue(json, "\"current\"", "\"weather_code\""); + WeatherDataCache.currentIsDay = ParseHelper.ParseJsonValue(json, "\"current\"", "\"is_day\""); + + int weatherCode = (int)WeatherDataCache.currentWeatherCode; + WeatherDataCache.currentCondition = CommonHelper.GetWeatherDescription(weatherCode); + + api?.Log(API.LogType.Debug, $"WeatherX: Current parsed - Temp={WeatherDataCache.currentTemp}, Humidity={WeatherDataCache.currentHumidity}"); } private void UpdateCurrentFromHourly() { - int currentHourIndex = DateTime.Now.Hour; if (currentHourIndex < 48) { - currentCloudCover = hourlyCloudCover[currentHourIndex]; - currentSolarRadiation = hourlySolarRadiation[currentHourIndex]; - currentDirectRadiation = hourlyDirectRadiation[currentHourIndex]; - currentDiffuseRadiation = hourlyDiffuseRadiation[currentHourIndex]; + WeatherDataCache.currentCloudCover = WeatherDataCache.hourlyCloudCover[currentHourIndex]; + WeatherDataCache.currentSolarRadiation = WeatherDataCache.hourlySolarRadiation[currentHourIndex]; + WeatherDataCache.currentDirectRadiation = WeatherDataCache.hourlyDirectRadiation[currentHourIndex]; + WeatherDataCache.currentDiffuseRadiation = WeatherDataCache.hourlyDiffuseRadiation[currentHourIndex]; } } @@ -360,29 +383,29 @@ private void ParseDailyForecasts(string json) string dailySection = ParseHelper.ExtractDailySection(json, dailyStart); - ParseHelper.ParseArrayValuesInSection(dailySection, "\"temperature_2m_max\"", forecastTempMax); - ParseHelper.ParseArrayValuesInSection(dailySection, "\"temperature_2m_min\"", forecastTempMin); - ParseHelper.ParseArrayValuesInSection(dailySection, "\"apparent_temperature_max\"", forecastApparentTempMax); - ParseHelper.ParseArrayValuesInSection(dailySection, "\"apparent_temperature_min\"", forecastApparentTempMin); - ParseHelper.ParseArrayValuesInSection(dailySection, "\"wind_speed_10m_max\"", forecastWindSpeedMax); - ParseHelper.ParseArrayValuesInSection(dailySection, "\"uv_index_max\"", forecastUvIndexMax); + ParseHelper.ParseArrayValuesInSection(dailySection, "\"temperature_2m_max\"", WeatherDataCache.forecastTempMax); + ParseHelper.ParseArrayValuesInSection(dailySection, "\"temperature_2m_min\"", WeatherDataCache.forecastTempMin); + ParseHelper.ParseArrayValuesInSection(dailySection, "\"apparent_temperature_max\"", WeatherDataCache.forecastApparentTempMax); + ParseHelper.ParseArrayValuesInSection(dailySection, "\"apparent_temperature_min\"", WeatherDataCache.forecastApparentTempMin); + ParseHelper.ParseArrayValuesInSection(dailySection, "\"wind_speed_10m_max\"", WeatherDataCache.forecastWindSpeedMax); + ParseHelper.ParseArrayValuesInSection(dailySection, "\"uv_index_max\"", WeatherDataCache.forecastUvIndexMax); - ParseHelper.ParseStringArrayInSection(dailySection, "\"sunrise\"", forecastSunrise); - ParseHelper.ParseStringArrayInSection(dailySection, "\"sunset\"", forecastSunset); + ParseHelper.ParseStringArrayInSection(dailySection, "\"sunrise\"", WeatherDataCache.forecastSunrise); + ParseHelper.ParseStringArrayInSection(dailySection, "\"sunset\"", WeatherDataCache.forecastSunset); - ParseHelper.ParseArrayValuesInSection(dailySection, "\"weather_code\"", forecastWeatherCode); + ParseHelper.ParseArrayValuesInSection(dailySection, "\"weather_code\"", WeatherDataCache.forecastWeatherCode); for (int i = 0; i < 7; i++) { - forecastConditions[i] = CommonHelper.GetWeatherDescription((int)forecastWeatherCode[i]); + WeatherDataCache.forecastConditions[i] = CommonHelper.GetWeatherDescription((int)WeatherDataCache.forecastWeatherCode[i]); } - if (forecastUvIndexMax.Length > 0) + if (WeatherDataCache.forecastUvIndexMax.Length > 0) { - todayUvIndex = forecastUvIndexMax[0]; + WeatherDataCache.todayUvIndex = WeatherDataCache.forecastUvIndexMax[0]; } - api?.Log(API.LogType.Debug, $"WeatherX: Daily forecasts parsed - Day 0: Max={forecastTempMax[0]}°, UV={forecastUvIndexMax[0]}"); + api?.Log(API.LogType.Debug, $"WeatherX: Daily forecasts parsed - Day 0: Max={WeatherDataCache.forecastTempMax[0]}°"); } catch (Exception ex) { @@ -399,28 +422,26 @@ private void ParseHourlyData(string json) string hourlySection = ParseHelper.ExtractHourlySection(json, hourlyStart); - ParseHelper.ParseArrayValuesInSection(hourlySection, "\"temperature_2m\"", hourlyTemp, 48); - ParseHelper.ParseArrayValuesInSection(hourlySection, "\"relative_humidity_2m\"", hourlyHumidity, 48); - ParseHelper.ParseArrayValuesInSection(hourlySection, "\"wind_speed_10m\"", hourlyWindSpeed, 48); - ParseHelper.ParseArrayValuesInSection(hourlySection, "\"apparent_temperature\"", hourlyApparentTemp, 48); - ParseHelper.ParseArrayValuesInSection(hourlySection, "\"cloud_cover\"", hourlyCloudCover, 48); - ParseHelper.ParseArrayValuesInSection(hourlySection, "\"visibility\"", hourlyVisibility, 48); - - ParseHelper.ParseArrayValuesInSection(hourlySection, "\"shortwave_radiation\"", hourlySolarRadiation, 48); - ParseHelper.ParseArrayValuesInSection(hourlySection, "\"direct_radiation\"", hourlyDirectRadiation, 48); - ParseHelper.ParseArrayValuesInSection(hourlySection, "\"diffuse_radiation\"", hourlyDiffuseRadiation, 48); + ParseHelper.ParseArrayValuesInSection(hourlySection, "\"temperature_2m\"", WeatherDataCache.hourlyTemp, 48); + ParseHelper.ParseArrayValuesInSection(hourlySection, "\"relative_humidity_2m\"", WeatherDataCache.hourlyHumidity, 48); + ParseHelper.ParseArrayValuesInSection(hourlySection, "\"wind_speed_10m\"", WeatherDataCache.hourlyWindSpeed, 48); + ParseHelper.ParseArrayValuesInSection(hourlySection, "\"apparent_temperature\"", WeatherDataCache.hourlyApparentTemp, 48); + ParseHelper.ParseArrayValuesInSection(hourlySection, "\"cloud_cover\"", WeatherDataCache.hourlyCloudCover, 48); + ParseHelper.ParseArrayValuesInSection(hourlySection, "\"visibility\"", WeatherDataCache.hourlyVisibility, 48); + ParseHelper.ParseArrayValuesInSection(hourlySection, "\"shortwave_radiation\"", WeatherDataCache.hourlySolarRadiation, 48); + ParseHelper.ParseArrayValuesInSection(hourlySection, "\"direct_radiation\"", WeatherDataCache.hourlyDirectRadiation, 48); + ParseHelper.ParseArrayValuesInSection(hourlySection, "\"diffuse_radiation\"", WeatherDataCache.hourlyDiffuseRadiation, 48); double[] weatherCodes = new double[48]; ParseHelper.ParseArrayValuesInSection(hourlySection, "\"weather_code\"", weatherCodes, 48); for (int i = 0; i < 48; i++) { - hourlyWeatherCode[i] = (int)weatherCodes[i]; + WeatherDataCache.hourlyWeatherCode[i] = (int)weatherCodes[i]; } - ParseHelper.ParseTimeArray(hourlySection, hourlyTime); - - api?.Log(API.LogType.Debug, $"WeatherX: Hourly data parsed successfully (no precipitation)"); + ParseHelper.ParseTimeArray(hourlySection, WeatherDataCache.hourlyTime); + api?.Log(API.LogType.Debug, $"WeatherX: Hourly data parsed successfully"); } catch (Exception ex) { @@ -434,87 +455,86 @@ private double GetNumericValue() switch (dataType.ToLower()) { - case "currenttemp": - return currentTemp; + return WeatherDataCache.currentTemp; case "currenthumidity": - return currentHumidity; + return WeatherDataCache.currentHumidity; case "currentwindspeed": - return currentWindSpeed; + return WeatherDataCache.currentWindSpeed; case "currentpressure": - return currentPressure; + return WeatherDataCache.currentPressure; case "currentapparenttemp": - return currentApparentTemp; + return WeatherDataCache.currentApparentTemp; case "currentdewpoint": - return currentDewPoint; + return WeatherDataCache.currentDewPoint; case "currentcloudcover": - return currentCloudCover; + return WeatherDataCache.currentCloudCover; case "currentwinddirection": - return currentWindDirection; + return WeatherDataCache.currentWindDirection; case "currentwindgusts": - return currentWindGusts; + return WeatherDataCache.currentWindGusts; case "currentuvindex": - return todayUvIndex; + return WeatherDataCache.todayUvIndex; case "currentisday": - return currentIsDay; + return WeatherDataCache.currentIsDay; case "currentweathercode": - return currentWeatherCode; + return WeatherDataCache.currentWeatherCode; case "currentsolarradiation": - return currentSolarRadiation; + return WeatherDataCache.currentSolarRadiation; case "currentdirectradiation": - return currentDirectRadiation; + return WeatherDataCache.currentDirectRadiation; case "currentdiffuseradiation": - return currentDiffuseRadiation; + return WeatherDataCache.currentDiffuseRadiation; case "forecasttemp": case "forecasttempmax": - return forecastDay < forecastTempMax.Length ? forecastTempMax[forecastDay] : 0.0; + return forecastDay < WeatherDataCache.forecastTempMax.Length ? WeatherDataCache.forecastTempMax[forecastDay] : 0.0; case "forecasttempmin": - return forecastDay < forecastTempMin.Length ? forecastTempMin[forecastDay] : 0.0; + return forecastDay < WeatherDataCache.forecastTempMin.Length ? WeatherDataCache.forecastTempMin[forecastDay] : 0.0; case "forecastapparenttempmax": - return forecastDay < forecastApparentTempMax.Length ? forecastApparentTempMax[forecastDay] : 0.0; + return forecastDay < WeatherDataCache.forecastApparentTempMax.Length ? WeatherDataCache.forecastApparentTempMax[forecastDay] : 0.0; case "forecastapparenttempmin": - return forecastDay < forecastApparentTempMin.Length ? forecastApparentTempMin[forecastDay] : 0.0; + return forecastDay < WeatherDataCache.forecastApparentTempMin.Length ? WeatherDataCache.forecastApparentTempMin[forecastDay] : 0.0; case "forecastwindspeed": - return forecastDay < forecastWindSpeedMax.Length ? forecastWindSpeedMax[forecastDay] : 0.0; + return forecastDay < WeatherDataCache.forecastWindSpeedMax.Length ? WeatherDataCache.forecastWindSpeedMax[forecastDay] : 0.0; case "forecastuvindex": - return forecastDay < forecastUvIndexMax.Length ? forecastUvIndexMax[forecastDay] : 0.0; + return forecastDay < WeatherDataCache.forecastUvIndexMax.Length ? WeatherDataCache.forecastUvIndexMax[forecastDay] : 0.0; case "forecastweathercode": - return forecastDay < forecastWeatherCode.Length ? forecastWeatherCode[forecastDay] : 0.0; + return forecastDay < WeatherDataCache.forecastWeatherCode.Length ? WeatherDataCache.forecastWeatherCode[forecastDay] : 0.0; case "forecastsunrise": - return forecastDay < forecastSunrise.Length ? CommonHelper.ConvertIso8601ToHour(forecastSunrise[forecastDay]) : 0.0; + return forecastDay < WeatherDataCache.forecastSunrise.Length ? CommonHelper.ConvertIso8601ToHour(WeatherDataCache.forecastSunrise[forecastDay]) : 0.0; case "forecastsunset": - return forecastDay < forecastSunset.Length ? CommonHelper.ConvertIso8601ToHour(forecastSunset[forecastDay]) : 0.0; + return forecastDay < WeatherDataCache.forecastSunset.Length ? CommonHelper.ConvertIso8601ToHour(WeatherDataCache.forecastSunset[forecastDay]) : 0.0; case "hourlytemp": - return targetHour < hourlyTemp.Length ? hourlyTemp[targetHour] : 0.0; + return targetHour < WeatherDataCache.hourlyTemp.Length ? WeatherDataCache.hourlyTemp[targetHour] : 0.0; case "hourlyhumidity": - return targetHour < hourlyHumidity.Length ? hourlyHumidity[targetHour] : 0.0; + return targetHour < WeatherDataCache.hourlyHumidity.Length ? WeatherDataCache.hourlyHumidity[targetHour] : 0.0; case "hourlywindspeed": - return targetHour < hourlyWindSpeed.Length ? hourlyWindSpeed[targetHour] : 0.0; + return targetHour < WeatherDataCache.hourlyWindSpeed.Length ? WeatherDataCache.hourlyWindSpeed[targetHour] : 0.0; case "hourlyapparenttemp": - return targetHour < hourlyApparentTemp.Length ? hourlyApparentTemp[targetHour] : 0.0; + return targetHour < WeatherDataCache.hourlyApparentTemp.Length ? WeatherDataCache.hourlyApparentTemp[targetHour] : 0.0; case "hourlycloudcover": - return targetHour < hourlyCloudCover.Length ? hourlyCloudCover[targetHour] : 0.0; + return targetHour < WeatherDataCache.hourlyCloudCover.Length ? WeatherDataCache.hourlyCloudCover[targetHour] : 0.0; case "hourlyvisibility": - return targetHour < hourlyVisibility.Length ? hourlyVisibility[targetHour] : 0.0; + return targetHour < WeatherDataCache.hourlyVisibility.Length ? WeatherDataCache.hourlyVisibility[targetHour] : 0.0; case "hourlyweathercode": - return targetHour < hourlyWeatherCode.Length ? hourlyWeatherCode[targetHour] : 0.0; + return targetHour < WeatherDataCache.hourlyWeatherCode.Length ? WeatherDataCache.hourlyWeatherCode[targetHour] : 0.0; case "hourlysolarradiation": - return targetHour < hourlySolarRadiation.Length ? hourlySolarRadiation[targetHour] : 0.0; + return targetHour < WeatherDataCache.hourlySolarRadiation.Length ? WeatherDataCache.hourlySolarRadiation[targetHour] : 0.0; case "hourlydirectradiation": - return targetHour < hourlyDirectRadiation.Length ? hourlyDirectRadiation[targetHour] : 0.0; + return targetHour < WeatherDataCache.hourlyDirectRadiation.Length ? WeatherDataCache.hourlyDirectRadiation[targetHour] : 0.0; case "hourlydiffuseradiation": - return targetHour < hourlyDiffuseRadiation.Length ? hourlyDiffuseRadiation[targetHour] : 0.0; + return targetHour < WeatherDataCache.hourlyDiffuseRadiation.Length ? WeatherDataCache.hourlyDiffuseRadiation[targetHour] : 0.0; case "currenthourtemp": - return hourlyTemp[DateTime.Now.Hour % 48]; + return WeatherDataCache.hourlyTemp[DateTime.Now.Hour % 48]; case "currenthourhumidity": - return hourlyHumidity[DateTime.Now.Hour % 48]; + return WeatherDataCache.hourlyHumidity[DateTime.Now.Hour % 48]; case "currenthourwindspeed": - return hourlyWindSpeed[DateTime.Now.Hour % 48]; + return WeatherDataCache.hourlyWindSpeed[DateTime.Now.Hour % 48]; default: return 0.0; @@ -528,51 +548,60 @@ internal string GetStringValue() switch (dataType.ToLower()) { case "currentcondition": - return currentCondition; + return WeatherDataCache.currentCondition; case "currentweathercodetext": - return currentWeatherCode.ToString("F0"); + return WeatherDataCache.currentWeatherCode.ToString("F0"); case "currentisdaytext": - return currentIsDay > 0 ? "Day" : "Night"; + return WeatherDataCache.currentIsDay > 0 ? "Day" : "Night"; case "forecastcondition": - return forecastDay < forecastConditions.Length ? forecastConditions[forecastDay] : "Unknown"; + return forecastDay < WeatherDataCache.forecastConditions.Length ? WeatherDataCache.forecastConditions[forecastDay] : "Unknown"; case "forecastweathercodetext": - return forecastDay < forecastWeatherCode.Length ? forecastWeatherCode[forecastDay].ToString("F0") : "0"; + return forecastDay < WeatherDataCache.forecastWeatherCode.Length ? WeatherDataCache.forecastWeatherCode[forecastDay].ToString("F0") : "0"; case "hourlycondition": - return targetHour < hourlyWeatherCode.Length ? CommonHelper.GetWeatherDescription(hourlyWeatherCode[targetHour]) : "Unknown"; + return targetHour < WeatherDataCache.hourlyWeatherCode.Length ? CommonHelper.GetWeatherDescription(WeatherDataCache.hourlyWeatherCode[targetHour]) : "Unknown"; case "hourlyweathercodetext": - return targetHour < hourlyWeatherCode.Length ? hourlyWeatherCode[targetHour].ToString("F0") : "0"; + return targetHour < WeatherDataCache.hourlyWeatherCode.Length ? WeatherDataCache.hourlyWeatherCode[targetHour].ToString("F0") : "0"; case "currentwinddirectiontext": - return CommonHelper.GetWindDirectionText(currentWindDirection); + return CommonHelper.GetWindDirectionText(WeatherDataCache.currentWindDirection); case "debugerror": - return string.IsNullOrEmpty(lastError) ? "No Error" : lastError; + return string.IsNullOrEmpty(WeatherDataCache.lastError) ? "No Error" : WeatherDataCache.lastError; case "debugurl": - return lastApiUrl; + return WeatherDataCache.lastApiUrl; case "debugdaily": - return $"Day{forecastDay}: Max={forecastTempMax[forecastDay]:F1}°, Min={forecastTempMin[forecastDay]:F1}°, {forecastConditions[forecastDay]}"; + return $"Day{forecastDay}: Max={WeatherDataCache.forecastTempMax[forecastDay]:F1}°, Min={WeatherDataCache.forecastTempMin[forecastDay]:F1}°, {WeatherDataCache.forecastConditions[forecastDay]}"; case "debughourly": - return $"Hour+{hourOffset}: Temp={hourlyTemp[targetHour]:F1}°, {CommonHelper.GetWeatherDescription(hourlyWeatherCode[targetHour])}"; + return $"Hour+{hourOffset}: Temp={WeatherDataCache.hourlyTemp[targetHour]:F1}°, {CommonHelper.GetWeatherDescription(WeatherDataCache.hourlyWeatherCode[targetHour])}"; + case "debuglastupdate": + return WeatherDataCache.lastUpdate == DateTime.MinValue ? "Never" : WeatherDataCache.lastUpdate.ToString("HH:mm:ss"); + case "debugnextupdate": + if (WeatherDataCache.lastUpdate == DateTime.MinValue) + return "On next cycle"; + double timeRemaining = updateInterval - DateTime.Now.Subtract(WeatherDataCache.lastUpdate).TotalSeconds; + return timeRemaining > 0 ? $"In {timeRemaining:F0}s" : "Now"; + case "debugupdateinterval": + return $"{updateInterval}s"; case "status": - return isUpdating ? "Updating..." : (lastError.Length > 0 ? "Error" : "Ready"); + return isUpdating ? "Updating..." : (WeatherDataCache.lastError.Length > 0 ? "Error" : "Ready"); case "forecastsunrisetext": - return forecastDay < forecastSunrise.Length ? CommonHelper.ConvertIso8601ToTime(forecastSunrise[forecastDay]) : "N/A"; + return forecastDay < WeatherDataCache.forecastSunrise.Length ? CommonHelper.ConvertIso8601ToTime(WeatherDataCache.forecastSunrise[forecastDay]) : "N/A"; case "forecastsunsettext": - return forecastDay < forecastSunset.Length ? CommonHelper.ConvertIso8601ToTime(forecastSunset[forecastDay]) : "N/A"; + return forecastDay < WeatherDataCache.forecastSunset.Length ? CommonHelper.ConvertIso8601ToTime(WeatherDataCache.forecastSunset[forecastDay]) : "N/A"; case "hourlytime": - return targetHour < hourlyTime.Length ? hourlyTime[targetHour] : ""; + return targetHour < WeatherDataCache.hourlyTime.Length ? WeatherDataCache.hourlyTime[targetHour] : ""; case "currenthourlytime": { int currentHourIndex = DateTime.Now.Hour % 48; - return currentHourIndex < hourlyTime.Length ? hourlyTime[currentHourIndex] : ""; + return currentHourIndex < WeatherDataCache.hourlyTime.Length ? WeatherDataCache.hourlyTime[currentHourIndex] : ""; } case "uvindextext": - return CommonHelper.GetUvIndexDescription(todayUvIndex); + return CommonHelper.GetUvIndexDescription(WeatherDataCache.todayUvIndex); case "nexthourssummary": return GetNextHoursSummary(); case "debugsolarradiation": - return $"Solar: {currentSolarRadiation:F1} W/m² | Direct: {currentDirectRadiation:F1} | Diffuse: {currentDiffuseRadiation:F1}"; + return $"Solar: {WeatherDataCache.currentSolarRadiation:F1} W/m² | Direct: {WeatherDataCache.currentDirectRadiation:F1} | Diffuse: {WeatherDataCache.currentDiffuseRadiation:F1}"; case "debugcloudcover": - return $"Current Clouds: {currentCloudCover:F0}% | Next hour: {(targetHour < hourlyCloudCover.Length ? hourlyCloudCover[targetHour].ToString("F0") + "%" : "N/A")}"; + return $"Current Clouds: {WeatherDataCache.currentCloudCover:F0}% | Next hour: {(targetHour < WeatherDataCache.hourlyCloudCover.Length ? WeatherDataCache.hourlyCloudCover[targetHour].ToString("F0") + "%" : "N/A")}"; default: double value = GetNumericValue(); @@ -588,17 +617,17 @@ private string GetNextHoursSummary() for (int i = 1; i <= Math.Min(6, 47 - currentHour); i++) { int hourIndex = currentHour + i; - if (hourIndex < hourlyTemp.Length) + if (hourIndex < WeatherDataCache.hourlyTemp.Length) { - string time = hourIndex < hourlyTime.Length ? - CommonHelper.ConvertIso8601ToTime(hourlyTime[hourIndex]) : + string time = hourIndex < WeatherDataCache.hourlyTime.Length ? + CommonHelper.ConvertIso8601ToTime(WeatherDataCache.hourlyTime[hourIndex]) : DateTime.Now.AddHours(i).ToString("HH:mm"); - summary.Append($"{time}: {hourlyTemp[hourIndex]:F0}°"); + summary.Append($"{time}: {WeatherDataCache.hourlyTemp[hourIndex]:F0}°"); - if (hourIndex < hourlyWeatherCode.Length) + if (hourIndex < WeatherDataCache.hourlyWeatherCode.Length) { - string condition = CommonHelper.GetWeatherDescription(hourlyWeatherCode[hourIndex]); + string condition = CommonHelper.GetWeatherDescription(WeatherDataCache.hourlyWeatherCode[hourIndex]); if (!condition.Equals("Clear Sky", StringComparison.OrdinalIgnoreCase)) { summary.Append($" ({condition})"); @@ -614,9 +643,4 @@ private string GetNextHoursSummary() return summary.ToString(); } - - ~Measure() - { - updateTimer?.Dispose(); - } } \ No newline at end of file From dbd02832a4163aee71fb7fdaaa7ac782e7622580 Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Tue, 4 Nov 2025 17:56:54 +0500 Subject: [PATCH 2/5] feat(weather): implement parent and child measure system for WeatherX plugin - Introduce ParentMeasure and ChildMeasure classes inheriting from Measure - ParentMeasure manages data fetching and API interaction - ChildMeasure retrieves weather data from its associated ParentMeasure - Modify Initialize to create ParentMeasure or ChildMeasure based on ParentName presence - Update Dispose method to properly clean up measures from tracking list - Add logic to find and link child measures with their parent using skin and name - Change Measure class fields to protected to support inheritance - Refactor Update, Reload, and GetStringValue methods to be virtual for overrides - Add debugging logs for initialization and parent-child linking events - Include sample configuration in ParentMeasure.ini for WeatherX skin usage --- Resources/Skins/WeatherX/ParentMeasure.ini | 40 +++++ WeatherX/Plugin.cs | 22 ++- WeatherX/WeatherX.cs | 191 +++++++++++++++++++-- 3 files changed, 233 insertions(+), 20 deletions(-) create mode 100644 Resources/Skins/WeatherX/ParentMeasure.ini diff --git a/Resources/Skins/WeatherX/ParentMeasure.ini b/Resources/Skins/WeatherX/ParentMeasure.ini new file mode 100644 index 0000000..f797af3 --- /dev/null +++ b/Resources/Skins/WeatherX/ParentMeasure.ini @@ -0,0 +1,40 @@ +[Rainmeter] +Update=1000 + +[mWeatherParent] +Measure=Plugin +Plugin=WeatherX.dll +Latitude=29.696489 +Longitude=72.549843 +Units=metric +UpdateInterval=600 +DataType=CurrentTemp + +[mTemp] +Measure=Plugin +Plugin=WeatherX.dll +ParentName=mWeatherParent +DataType=CurrentTemp + +[mHumidity] +Measure=Plugin +Plugin=WeatherX.dll +ParentName=mWeatherParent +DataType=CurrentHumidity + +[mCondition] +Measure=Plugin +Plugin=WeatherX.dll +ParentName=mWeatherParent +DataType=CurrentCondition + +[mWeather] +Meter=STRING +MeasureName=mWeatherParent +X=5 +Y=5 +W=200 +H=55 +FontColor=FFFFFF +Text=[mWeatherParent] +DynamicVariables=1 diff --git a/WeatherX/Plugin.cs b/WeatherX/Plugin.cs index 41d76af..5319bbe 100644 --- a/WeatherX/Plugin.cs +++ b/WeatherX/Plugin.cs @@ -9,12 +9,32 @@ public static class Plugin [DllExport] public static void Initialize(ref IntPtr data, IntPtr rm) { - data = GCHandle.ToIntPtr(GCHandle.Alloc(new Measure())); + Rainmeter.API api = new Rainmeter.API(rm); + + string parentName = api.ReadString("ParentName", ""); + Measure measure; + + if (String.IsNullOrEmpty(parentName)) + { + // This is a parent measure + measure = new ParentMeasure(); + api.Log(API.LogType.Debug, "WeatherX: Initialized as Parent measure"); + } + else + { + // This is a child measure + measure = new ChildMeasure(); + api.Log(API.LogType.Debug, $"WeatherX: Initialized as Child measure with ParentName={parentName}"); + } + + data = GCHandle.ToIntPtr(GCHandle.Alloc(measure)); } [DllExport] public static void Finalize(IntPtr data) { + Measure measure = (Measure)GCHandle.FromIntPtr(data).Target; + measure.Dispose(); GCHandle.FromIntPtr(data).Free(); if (StringBuffer != IntPtr.Zero) diff --git a/WeatherX/WeatherX.cs b/WeatherX/WeatherX.cs index 047949d..2d7404d 100644 --- a/WeatherX/WeatherX.cs +++ b/WeatherX/WeatherX.cs @@ -105,20 +105,20 @@ public static void InitializeArrays() internal class Measure { - private double latitude, longitude; - private string dataType; - private int forecastDay; - private int hourOffset; - private int updateInterval; - private API api; - private string units; - private string timezone; - - private static DateTime lastGlobalApiCall = DateTime.MinValue; - private static readonly object apiCallLock = new object(); - private static volatile bool isUpdating = false; - private const int MIN_API_CALL_INTERVAL = 5; - private static int activeMeasures = 0; + protected double latitude, longitude; + protected string dataType; + protected int forecastDay; + protected int hourOffset; + protected int updateInterval; + protected API api; + protected string units; + protected string timezone; + + protected static DateTime lastGlobalApiCall = DateTime.MinValue; + protected static readonly object apiCallLock = new object(); + protected static volatile bool isUpdating = false; + protected const int MIN_API_CALL_INTERVAL = 5; + protected static int activeMeasures = 0; internal Measure() { @@ -145,7 +145,11 @@ internal Measure() } } - internal void Reload(Rainmeter.API api, ref double maxValue) + internal virtual void Dispose() + { + } + + internal virtual void Reload(Rainmeter.API api, ref double maxValue) { this.api = api; @@ -194,7 +198,7 @@ internal void Reload(Rainmeter.API api, ref double maxValue) api.Log(API.LogType.Debug, "WeatherX: Reload detected - forcing data refresh"); } - internal double Update() + internal virtual double Update() { // Check if an update is needed based on updateInterval double timeSinceLastUpdate = DateTime.Now.Subtract(WeatherDataCache.lastUpdate).TotalSeconds; @@ -449,7 +453,7 @@ private void ParseHourlyData(string json) } } - private double GetNumericValue() + protected double GetNumericValue() { int targetHour = CommonHelper.GetTargetHourIndex(hourOffset); @@ -541,7 +545,7 @@ private double GetNumericValue() } } - internal string GetStringValue() + internal virtual string GetStringValue() { int targetHour = CommonHelper.GetTargetHourIndex(hourOffset); @@ -609,7 +613,7 @@ internal string GetStringValue() } } - private string GetNextHoursSummary() + protected string GetNextHoursSummary() { var summary = new StringBuilder(); int currentHour = DateTime.Now.Hour; @@ -643,4 +647,153 @@ private string GetNextHoursSummary() return summary.ToString(); } +} + +// Parent Measure - handles API calls and data fetching +internal class ParentMeasure : Measure +{ + // Static list to track all parent measures + internal static System.Collections.Generic.List ParentMeasures = new System.Collections.Generic.List(); + + internal string Name; + internal IntPtr Skin; + + internal ParentMeasure() + { + ParentMeasures.Add(this); + } + + internal override void Dispose() + { + ParentMeasures.Remove(this); + } + + internal override void Reload(Rainmeter.API api, ref double maxValue) + { + base.Reload(api, ref maxValue); + + Name = api.GetMeasureName(); + Skin = api.GetSkin(); + + api.Log(API.LogType.Debug, $"WeatherX Parent: '{Name}' initialized in skin {Skin}"); + } + + internal override double Update() + { + return base.Update(); + } + + internal override string GetStringValue() + { + return base.GetStringValue(); + } + + // Method for child measures to get values + internal double GetValue(string type, int day, int hour) + { + string oldDataType = dataType; + int oldForecastDay = forecastDay; + int oldHourOffset = hourOffset; + + dataType = type; + forecastDay = day; + hourOffset = hour; + + double result = GetNumericValue(); + + dataType = oldDataType; + forecastDay = oldForecastDay; + hourOffset = oldHourOffset; + + return result; + } + + internal string GetStringValueFor(string type, int day, int hour) + { + string oldDataType = dataType; + int oldForecastDay = forecastDay; + int oldHourOffset = hourOffset; + + dataType = type; + forecastDay = day; + hourOffset = hour; + + string result = base.GetStringValue(); + + dataType = oldDataType; + forecastDay = oldForecastDay; + hourOffset = oldHourOffset; + + return result; + } +} + +// Child Measure - retrieves data from parent +internal class ChildMeasure : Measure +{ + private ParentMeasure parentMeasure = null; + + internal override void Dispose() + { + base.Dispose(); + } + + internal override void Reload(Rainmeter.API api, ref double maxValue) + { + this.api = api; + + string parentName = api.ReadString("ParentName", ""); + IntPtr skin = api.GetSkin(); + + dataType = api.ReadString("DataType", "CurrentTemp"); + forecastDay = api.ReadInt("ForecastDay", 0); + hourOffset = api.ReadInt("HourOffset", 0); + + if (forecastDay < 0 || forecastDay > 6) + { + forecastDay = 0; + } + + if (hourOffset < 0 || hourOffset > 47) + { + hourOffset = 0; + } + + // Find parent using name AND skin handle + parentMeasure = null; + foreach (ParentMeasure parent in ParentMeasure.ParentMeasures) + { + if (parent.Skin.Equals(skin) && parent.Name.Equals(parentName)) + { + parentMeasure = parent; + api.Log(API.LogType.Debug, $"WeatherX Child: Found parent '{parentName}' for DataType='{dataType}'"); + break; + } + } + + if (parentMeasure == null) + { + api.Log(API.LogType.Error, $"WeatherX Child: ParentName='{parentName}' not found"); + } + } + + internal override double Update() + { + if (parentMeasure != null) + { + return parentMeasure.GetValue(dataType, forecastDay, hourOffset); + } + + return 0.0; + } + + internal override string GetStringValue() + { + if (parentMeasure != null) + { + return parentMeasure.GetStringValueFor(dataType, forecastDay, hourOffset); + } + + return "No Parent"; + } } \ No newline at end of file From e70afce212ae4f91433567df5ab4b2c5e32caab3 Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Tue, 4 Nov 2025 19:12:43 +0500 Subject: [PATCH 3/5] feat(weatherx): add reverse geocoding support using BigDataCloud API - Introduce reverse geocode measures with city, country, continent, and subdivision data - Add language and EnableReverseGeocode options for localization and toggle - Implement asynchronous fetching and parsing of reverse geocode JSON data - Cache last geocode coordinates and refresh on coordinate changes - Extend skin configurations for displaying detailed location information - Enhance text meters to show temperature, humidity, condition, and location data - Add helper method to parse string values from JSON responses safely - Log relevant debug and error information for reverse geocode processes --- Resources/Skins/WeatherX/ParentMeasure.ini | 16 ++- Resources/Skins/WeatherX/ReverseGeocode.ini | 86 ++++++++++++ WeatherX/ParseHelper.cs | 54 ++++++++ WeatherX/WeatherX.cs | 143 +++++++++++++++++++- 4 files changed, 292 insertions(+), 7 deletions(-) create mode 100644 Resources/Skins/WeatherX/ReverseGeocode.ini diff --git a/Resources/Skins/WeatherX/ParentMeasure.ini b/Resources/Skins/WeatherX/ParentMeasure.ini index f797af3..a7fafe0 100644 --- a/Resources/Skins/WeatherX/ParentMeasure.ini +++ b/Resources/Skins/WeatherX/ParentMeasure.ini @@ -28,13 +28,17 @@ Plugin=WeatherX.dll ParentName=mWeatherParent DataType=CurrentCondition +[Background_Shape] +Meter=Shape +Shape=Rectangle 0,0,220,80,8| StrokeWidth 0 | FillColor 10,10,10,200 + [mWeather] Meter=STRING MeasureName=mWeatherParent -X=5 -Y=5 -W=200 -H=55 +MeasureName2=mHumidity +MeasureName3=mCondition +X=20 +Y=20 FontColor=FFFFFF -Text=[mWeatherParent] -DynamicVariables=1 +Antialias=1 +Text=Current Temperature:%1 #CRLF#Current Humidity : %2#CRLF#Current Condition : %3 diff --git a/Resources/Skins/WeatherX/ReverseGeocode.ini b/Resources/Skins/WeatherX/ReverseGeocode.ini new file mode 100644 index 0000000..a084581 --- /dev/null +++ b/Resources/Skins/WeatherX/ReverseGeocode.ini @@ -0,0 +1,86 @@ +[Rainmeter] +Update=1000 +BackgroundMode=2 +SolidColor=0,0,0,1 + +[mWeatherParent] +Measure=Plugin +Plugin=WeatherX.dll +Latitude=29.696489 +Longitude=72.549843 +Language=en +EnableReverseGeocode=1 +UpdateInterval=600 + +[mCity] +Measure=Plugin +Plugin=WeatherX.dll +ParentName=mWeatherParent +DataType=LocationCity + +[mCountry] +Measure=Plugin +Plugin=WeatherX.dll +ParentName=mWeatherParent +DataType=LocationCountry + +[mLocationFull] +Measure=Plugin +Plugin=WeatherX.dll +ParentName=mWeatherParent +DataType=LocationFull + +[mContinent] +Measure=Plugin +Plugin=WeatherX.dll +ParentName=mWeatherParent +DataType=LocationContinent + +[mContinentCode] +Measure=Plugin +Plugin=WeatherX.dll +ParentName=mWeatherParent +DataType=LocationContinentCode + +[mCountryCode] +Measure=Plugin +Plugin=WeatherX.dll +ParentName=mWeatherParent +DataType=LocationCountryCode + +[mState] +Measure=Plugin +Plugin=WeatherX.dll +ParentName=mWeatherParent +DataType=LocationPrincipalSubdivision + +[mStateCode] +Measure=Plugin +Plugin=WeatherX.dll +ParentName=mWeatherParent +DataType=LocationPrincipalSubdivisionCode + +[Background_Shape] +Meter=Shape +Shape=Rectangle 0,0,400,120,8| StrokeWidth 0 | FillColor 10,10,10,200 + +[Location_Text] +Meter=String +MeasureName=mCity +MeasureName2=mCountry +MeasureName3=mLocationFull +MeasureName4=mContinent +MeasureName5=mContinentCode +MeasureName6=mCountryCode +MeasureName7=mState +MeasureName8=mStateCode +Text=City: %1#CRLF#Country: %2#CRLF#Full: %3#CRLF#Continent: %4 (%5)#CRLF#Country Code: %6#CRLF#State: %7 (%8) +X=10 +Y=10 +W=380 +H=80 +FontSize=9 +FontColor=FFFFFF +AntiAlias=1 + + diff --git a/WeatherX/ParseHelper.cs b/WeatherX/ParseHelper.cs index 71eb03d..22aade3 100644 --- a/WeatherX/ParseHelper.cs +++ b/WeatherX/ParseHelper.cs @@ -44,6 +44,60 @@ public static double ParseJsonValue(string json, string section, string key) } } + public static string ParseJsonStringValue(string json, string key) + { + try + { + // Find the key in the JSON + string searchKey = $"\"{key}\""; + int keyStart = json.IndexOf(searchKey); + if (keyStart == -1) return ""; + + // Find the colon after the key + int colonPos = json.IndexOf(":", keyStart); + if (colonPos == -1) return ""; + + // Find the start of the value (skip whitespace) + int valueStart = colonPos + 1; + while (valueStart < json.Length && char.IsWhiteSpace(json[valueStart])) + { + valueStart++; + } + + // Check if the value is a string (starts with quote) + if (valueStart >= json.Length || json[valueStart] != '"') + return ""; + + // Find the end quote (handle escaped quotes) + int valueEnd = valueStart + 1; + while (valueEnd < json.Length) + { + if (json[valueEnd] == '"' && json[valueEnd - 1] != '\\') + { + break; + } + valueEnd++; + } + + if (valueEnd >= json.Length) return ""; + + // Extract the string value (without quotes) + string value = json.Substring(valueStart + 1, valueEnd - valueStart - 1); + + // Handle null or empty values + if (string.IsNullOrEmpty(value) || value.Equals("null", StringComparison.OrdinalIgnoreCase)) + { + return ""; + } + + return value; + } + catch + { + return ""; + } + } + public static string ExtractHourlySection(string json, int hourlyStart) { int braceStart = json.IndexOf("{", hourlyStart); diff --git a/WeatherX/WeatherX.cs b/WeatherX/WeatherX.cs index 2d7404d..e1d117e 100644 --- a/WeatherX/WeatherX.cs +++ b/WeatherX/WeatherX.cs @@ -65,6 +65,19 @@ internal static class WeatherDataCache public static string lastError = ""; public static string lastApiUrl = ""; + // Reverse geocoding data from BigDataCloud API + public static string locationContinent = ""; + public static string locationContinentCode = ""; + public static string locationCountryName = ""; + public static string locationCountryCode = ""; + public static string locationPrincipalSubdivision = ""; + public static string locationPrincipalSubdivisionCode = ""; + public static string locationCity = ""; + public static DateTime lastGeocodeUpdate = DateTime.MinValue; + public static double lastGeocodedLatitude = 0.0; + public static double lastGeocodedLongitude = 0.0; + public static bool hasGeocodedBefore = false; + static WeatherDataCache() { InitializeArrays(); @@ -113,6 +126,8 @@ internal class Measure protected API api; protected string units; protected string timezone; + protected string language; + protected bool enableReverseGeocode; protected static DateTime lastGlobalApiCall = DateTime.MinValue; protected static readonly object apiCallLock = new object(); @@ -130,6 +145,8 @@ internal Measure() updateInterval = 600; units = "metric"; timezone = "auto"; + language = "en"; + enableReverseGeocode = false; lock (apiCallLock) { @@ -161,6 +178,8 @@ internal virtual void Reload(Rainmeter.API api, ref double maxValue) updateInterval = api.ReadInt("UpdateInterval", 600); units = api.ReadString("Units", "metric").ToLower(); timezone = api.ReadString("Timezone", "auto"); + language = api.ReadString("Language", "en").ToLower(); + enableReverseGeocode = api.ReadInt("EnableReverseGeocode", 0) == 1; if (latitude < -90 || latitude > 90 || longitude < -180 || longitude > 180) { @@ -194,6 +213,20 @@ internal virtual void Reload(Rainmeter.API api, ref double maxValue) { // Reset the last update time to trigger immediate update WeatherDataCache.lastUpdate = DateTime.MinValue; + + // Reset geocode cache if coordinates changed or on first load + if (enableReverseGeocode) + { + bool coordsChanged = !WeatherDataCache.hasGeocodedBefore || + Math.Abs(WeatherDataCache.lastGeocodedLatitude - latitude) > 0.0001 || + Math.Abs(WeatherDataCache.lastGeocodedLongitude - longitude) > 0.0001; + + if (coordsChanged) + { + WeatherDataCache.lastGeocodeUpdate = DateTime.MinValue; + api.Log(API.LogType.Debug, $"WeatherX: Coordinates changed - will refresh reverse geocode data"); + } + } } api.Log(API.LogType.Debug, "WeatherX: Reload detected - forcing data refresh"); } @@ -224,6 +257,12 @@ internal virtual double Update() api?.Log(API.LogType.Debug, $"WeatherX: Triggering update - Time since last: {timeSinceLastUpdate:F1}s, Interval: {updateInterval}s"); Task.Run(async () => await UpdateWeatherDataAsync()); + + // Update reverse geocode if enabled and not updated yet + if (enableReverseGeocode && WeatherDataCache.lastGeocodeUpdate == DateTime.MinValue) + { + Task.Run(async () => await UpdateReverseGeocodeAsync()); + } } else { @@ -274,7 +313,7 @@ private async Task UpdateWeatherDataAsync() WeatherDataCache.lastUpdate = DateTime.Now; api?.Log(API.LogType.Debug, - $"WeatherX: ✓ Data updated successfully at {WeatherDataCache.lastUpdate:HH:mm:ss} | Next update in {updateInterval}s"); + $"WeatherX: Data updated successfully at {WeatherDataCache.lastUpdate:HH:mm:ss} | Next update in {updateInterval}s"); } } catch (WebException wex) @@ -302,6 +341,71 @@ private async Task UpdateWeatherDataAsync() } } + private async Task UpdateReverseGeocodeAsync() + { + try + { + string geocodeUrl = $"https://api.bigdatacloud.net/data/reverse-geocode-client?" + + $"latitude={latitude.ToString(CultureInfo.InvariantCulture)}&" + + $"longitude={longitude.ToString(CultureInfo.InvariantCulture)}&" + + $"localityLanguage={language}"; + + api?.Log(API.LogType.Debug, $"WeatherX: Fetching reverse geocode from: {geocodeUrl}"); + + using (TimeoutWebClient client = new TimeoutWebClient()) + { + client.Headers.Add("User-Agent", "WeatherX-Rainmeter-Plugin/1.0"); + client.Headers.Add("Accept", "application/json"); + client.Encoding = Encoding.UTF8; + client.Timeout = 10000; + + string response = await client.DownloadStringTaskAsync(geocodeUrl); + ParseReverseGeocodeData(response); + + // Store the coordinates we just geocoded + WeatherDataCache.lastGeocodedLatitude = latitude; + WeatherDataCache.lastGeocodedLongitude = longitude; + WeatherDataCache.hasGeocodedBefore = true; + WeatherDataCache.lastGeocodeUpdate = DateTime.Now; + api?.Log(API.LogType.Debug, + $"WeatherX: Reverse geocode updated - City: {WeatherDataCache.locationCity}, Country: {WeatherDataCache.locationCountryName}"); + } + } + catch (Exception ex) + { + api?.Log(API.LogType.Error, $"WeatherX: Reverse geocode error: {ex.Message}"); + } + } + + private void ParseReverseGeocodeData(string jsonResponse) + { + try + { + // Parse continent + WeatherDataCache.locationContinent = ParseHelper.ParseJsonStringValue(jsonResponse, "continent"); + WeatherDataCache.locationContinentCode = ParseHelper.ParseJsonStringValue(jsonResponse, "continentCode"); + + // Parse country + WeatherDataCache.locationCountryName = ParseHelper.ParseJsonStringValue(jsonResponse, "countryName"); + WeatherDataCache.locationCountryCode = ParseHelper.ParseJsonStringValue(jsonResponse, "countryCode"); + + // Parse subdivision (state/province) + WeatherDataCache.locationPrincipalSubdivision = ParseHelper.ParseJsonStringValue(jsonResponse, "principalSubdivision"); + WeatherDataCache.locationPrincipalSubdivisionCode = ParseHelper.ParseJsonStringValue(jsonResponse, "principalSubdivisionCode"); + + // Parse city + WeatherDataCache.locationCity = ParseHelper.ParseJsonStringValue(jsonResponse, "city"); + + api?.Log(API.LogType.Debug, + $"WeatherX: Parsed location - {WeatherDataCache.locationCity}, {WeatherDataCache.locationPrincipalSubdivision}, {WeatherDataCache.locationCountryName}"); + } + catch (Exception ex) + { + api?.Log(API.LogType.Error, $"WeatherX: Reverse geocode parsing error: {ex.Message}"); + throw; + } + } + private string BuildApiUrl() { string tempUnit = units == "imperial" ? "fahrenheit" : "celsius"; @@ -607,6 +711,27 @@ internal virtual string GetStringValue() case "debugcloudcover": return $"Current Clouds: {WeatherDataCache.currentCloudCover:F0}% | Next hour: {(targetHour < WeatherDataCache.hourlyCloudCover.Length ? WeatherDataCache.hourlyCloudCover[targetHour].ToString("F0") + "%" : "N/A")}"; + // Reverse geocoding data types + case "locationcontinent": + return WeatherDataCache.locationContinent; + case "locationcontinentcode": + return WeatherDataCache.locationContinentCode; + case "locationcountryname": + case "locationcountry": + return WeatherDataCache.locationCountryName; + case "locationcountrycode": + return WeatherDataCache.locationCountryCode; + case "locationprincipalsubdivision": + case "locationstate": + return WeatherDataCache.locationPrincipalSubdivision; + case "locationprincipalsubdivisioncode": + case "locationstatecode": + return WeatherDataCache.locationPrincipalSubdivisionCode; + case "locationcity": + return WeatherDataCache.locationCity; + case "locationfull": + return BuildFullLocationString(); + default: double value = GetNumericValue(); return value.ToString("F1", CultureInfo.InvariantCulture); @@ -647,6 +772,22 @@ protected string GetNextHoursSummary() return summary.ToString(); } + + private string BuildFullLocationString() + { + var parts = new System.Collections.Generic.List(); + + if (!string.IsNullOrEmpty(WeatherDataCache.locationCity)) + parts.Add(WeatherDataCache.locationCity); + + if (!string.IsNullOrEmpty(WeatherDataCache.locationPrincipalSubdivision)) + parts.Add(WeatherDataCache.locationPrincipalSubdivision); + + if (!string.IsNullOrEmpty(WeatherDataCache.locationCountryName)) + parts.Add(WeatherDataCache.locationCountryName); + + return parts.Count > 0 ? string.Join(", ", parts) : "Unknown Location"; + } } // Parent Measure - handles API calls and data fetching From 34e07fc033f785db565bb724613179c59618dedb Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Wed, 5 Nov 2025 07:06:52 +0500 Subject: [PATCH 4/5] docs(readme): revamp documentation with detailed usage and examples - Updated README.md with comprehensive plugin overview and feature list - Added detailed installation instructions including quick and manual methods - Provided step-by-step configuration guide with parent and child measures - Included extensive tables of available DataTypes for current, daily, hourly weather - Added examples for common use cases: temperature display, forecasts, dynamic theming - Documented location data support with reverse geocoding instructions - Included weather codes reference table for easy lookup - Added troubleshooting tips and advanced usage scenarios - Introduced a new README.txt user guide with summarized essentials - Removed deprecated ParentMeasure.ini example to simplify usage - Provided enhanced formatting and clearer explanations for better user experience --- README.md | 728 +++++++++++--------- Resources/Skins/WeatherX/FullExample.ini | Bin 0 -> 18478 bytes Resources/Skins/WeatherX/Main.ini | Bin 41878 -> 10722 bytes Resources/Skins/WeatherX/Old-Main.ini | Bin 0 -> 41878 bytes Resources/Skins/WeatherX/ParentMeasure.ini | 44 -- Resources/Skins/WeatherX/README.txt | 208 ++++++ Resources/Skins/WeatherX/ReverseGeocode.ini | Bin 1535 -> 4966 bytes Resources/Skins/WeatherX/Simple.ini | Bin 0 -> 3092 bytes 8 files changed, 594 insertions(+), 386 deletions(-) create mode 100644 Resources/Skins/WeatherX/FullExample.ini create mode 100644 Resources/Skins/WeatherX/Old-Main.ini delete mode 100644 Resources/Skins/WeatherX/ParentMeasure.ini create mode 100644 Resources/Skins/WeatherX/README.txt create mode 100644 Resources/Skins/WeatherX/Simple.ini diff --git a/README.md b/README.md index 9ccdeb7..acb1061 100644 --- a/README.md +++ b/README.md @@ -1,433 +1,477 @@ # WeatherX Plugin for Rainmeter + [![License: MIT](https://img.shields.io/badge/License-MIT-lightgrey.svg)]() -[![Version](https://img.shields.io/badge/version-1.0.1-blue.svg)]() +[![Version](https://img.shields.io/badge/version-1.1.0-blue.svg)]() [![Platform](https://img.shields.io/badge/platform-Windows-lightblue.svg)]() -A comprehensive weather plugin for Rainmeter that provides current weather conditions, forecasts, and detailed meteorological data using the Open-Meteo API. - -## Preview +A powerful Rainmeter plugin that provides real-time weather data, 7-day forecasts, hourly predictions, and location information using the free Open-Meteo API. -![WeatherX Skin Preview](https://github.com/NSTechBytes/WeatherX/blob/main/.github/preview.png) +## ✨ Features -## Features +- 🌡️ **Current Weather** - Temperature, humidity, wind, pressure, UV index +- 📅 **7-Day Forecast** - Daily high/low temperatures and conditions +- ⏰ **48-Hour Forecast** - Detailed hourly predictions +- 🌍 **Reverse Geocoding** - Automatic city/state/country detection +- ☀️ **Solar Data** - Radiation, sunrise/sunset times +- 🌙 **Day/Night Detection** - Perfect for dynamic themes +- 📊 **Multiple Units** - Metric or Imperial +- 🚀 **No API Key Required** - Free and unlimited -- **Real-time Weather Data**: Current temperature, humidity, wind conditions, and more -- **7-Day Forecasts**: Daily temperature ranges and weather conditions -- **48-Hour Forecasts**: Detailed hourly weather predictions -- **Solar Radiation Data**: UV index, solar radiation, direct and diffuse radiation -- **Comprehensive Metrics**: Apparent temperature, dew point, visibility, cloud cover -- **Day/Night Detection**: Automatic day/night status for dynamic theming -- **Numeric Weather Codes**: Both descriptive text and numeric codes for weather conditions -- **Multiple Units**: Support for metric and imperial units -- **Automatic Updates**: Configurable update intervals -- **Error Handling**: Built-in debugging and status reporting +## 📦 Installation -## Installation +### Quick Install (Recommended) +1. Download `WeatherX_v*.rmskin` from [Releases](https://github.com/NSTechBytes/WeatherX/releases) +2. Double-click to install +3. Update your coordinates in the skin +4. Done! 🎉 -### Option 1: RMSKIN Package (Recommended) -1. Download the latest `WeatherX_v*.rmskin` file from [Releases](https://github.com/NSTechBytes/WeatherX/releases) -2. Double-click the `.rmskin` file to automatically install in Rainmeter -3. Load the WeatherX skin from Rainmeter's skin browser -4. Configure your coordinates in the skin variables +### Manual Install +1. Download the plugin ZIP from [Releases](https://github.com/NSTechBytes/WeatherX/releases) +2. Copy `WeatherX.dll` to `%AppData%\Rainmeter\Plugins\` + - Use `x64` folder for 64-bit or `x32` for 32-bit +3. Refresh Rainmeter -### Option 2: Manual Installation -1. Download the plugin ZIP file from [Releases](https://github.com/NSTechBytes/WeatherX/releases) -2. Extract the appropriate DLL file (`x64` or `x32`) to your Rainmeter plugins folder -3. Place the skin files in your Rainmeter skins directory -4. Configure your coordinates and refresh Rainmeter +## 🚀 Quick Start Guide -## Configuration +### Step 1: Basic Setup -### Required Variables +Create a parent measure that handles all weather data: ```ini -[Variables] -Latitude=29.696489 ; Your location's latitude -Longitude=72.549843 ; Your location's longitude -Units=metric ; "metric" or "imperial" -UpdateInterval=300 ; Update frequency in seconds -``` +[Rainmeter] +Update=1000 -## Measure Options - -### DataType Options - -The plugin supports numerous `DataType` options for different weather measurements: - -#### Current Weather Data - -| DataType | Description | Return Type | Units (Metric/Imperial) | -|----------|-------------|-------------|-------------------------| -| `CurrentTemp` | Current temperature | Number | °C / °F | -| `CurrentCondition` | Current weather description | String | Text description | -| `CurrentHumidity` | Relative humidity | Number | % | -| `CurrentWindSpeed` | Wind speed | Number | km/h / mph | -| `CurrentWindDirection` | Wind direction in degrees | Number | 0-360° | -| `CurrentWindDirectionText` | Wind direction as text | String | N, NE, E, etc. | -| `CurrentPressure` | Atmospheric pressure | Number | hPa | -| `CurrentApparentTemp` | Feels-like temperature | Number | °C / °F | -| `CurrentDewPoint` | Dew point temperature | Number | °C / °F | -| `CurrentCloudCover` | Cloud coverage percentage | Number | % | -| `CurrentWindGusts` | Wind gust speed | Number | km/h / mph | -| `CurrentUvIndex` | UV index value | Number | 0-11+ | -| `CurrentSolarRadiation` | Solar radiation | Number | W/m² | -| `CurrentDirectRadiation` | Direct solar radiation | Number | W/m² | -| `CurrentDiffuseRadiation` | Diffuse solar radiation | Number | W/m² | -| `CurrentIsDay` | Day/night status | Number | 1.0 = Day, 0.0 = Night | -| `CurrentIsDayText` | Day/night status as text | String | "Day" or "Night" | -| `CurrentWeatherCode` | Weather code as number | Number | 0, 1, 2, 45, 61, etc. | -| `CurrentWeatherCodeText` | Weather code as text | String | "0", "1", "2", "45", "61", etc. | - -#### Daily Forecast Data - -| DataType | Description | Return Type | Additional Parameter | -|----------|-------------|-------------|----------------------| -| `ForecastTempMax` | Daily maximum temperature | Number | `ForecastDay=0-6` | -| `ForecastTempMin` | Daily minimum temperature | Number | `ForecastDay=0-6` | -| `ForecastCondition` | Daily weather condition | String | `ForecastDay=0-6` | -| `ForecastApparentTempMax` | Daily max apparent temperature | Number | `ForecastDay=0-6` | -| `ForecastApparentTempMin` | Daily min apparent temperature | Number | `ForecastDay=0-6` | -| `ForecastWindSpeed` | Daily max wind speed | Number | `ForecastDay=0-6` | -| `ForecastUvIndex` | Daily max UV index | Number | `ForecastDay=0-6` | -| `ForecastSunrise` | Sunrise time (hour value) | Number | `ForecastDay=0-6` | -| `ForecastSunset` | Sunset time (hour value) | Number | `ForecastDay=0-6` | -| `ForecastSunriseText` | Sunrise time as text | String | `ForecastDay=0-6` | -| `ForecastSunsetText` | Sunset time as text | String | `ForecastDay=0-6` | -| `ForecastWeatherCode` | Weather code as number | Number | `ForecastDay=0-6` | -| `ForecastWeatherCodeText` | Weather code as text | String | `ForecastDay=0-6` | - -#### Hourly Forecast Data - -| DataType | Description | Return Type | Additional Parameter | -|----------|-------------|-------------|----------------------| -| `HourlyTemp` | Hourly temperature | Number | `HourOffset=0-47` | -| `HourlyCondition` | Hourly weather condition | String | `HourOffset=0-47` | -| `HourlyHumidity` | Hourly humidity | Number | `HourOffset=0-47` | -| `HourlyWindSpeed` | Hourly wind speed | Number | `HourOffset=0-47` | -| `HourlyApparentTemp` | Hourly apparent temperature | Number | `HourOffset=0-47` | -| `HourlyCloudCover` | Hourly cloud coverage | Number | `HourOffset=0-47` | -| `HourlyVisibility` | Hourly visibility | Number | `HourOffset=0-47` | -| `HourlySolarRadiation` | Hourly solar radiation | Number | `HourOffset=0-47` | -| `HourlyDirectRadiation` | Hourly direct radiation | Number | `HourOffset=0-47` | -| `HourlyDiffuseRadiation` | Hourly diffuse radiation | Number | `HourOffset=0-47` | -| `HourlyTime` | Hourly timestamp | String | `HourOffset=0-47` | -| `HourlyWeatherCode` | Weather code as number | Number | `HourOffset=0-47` | -| `HourlyWeatherCodeText` | Weather code as text | String | `HourOffset=0-47` | - -#### Special Data Types - -| DataType | Description | Return Type | Purpose | -|----------|-------------|-------------|---------| -| `UvIndexText` | UV index description | String | "Low", "Moderate", "High", etc. | -| `NextHoursSummary` | Summary of next 6 hours | String | Quick forecast overview | -| `Status` | Plugin status | String | "Ready", "Updating...", "Error" | -| `DebugError` | Last error message | String | Troubleshooting | -| `DebugUrl` | API URL being used | String | Debugging | -| `DebugSolarRadiation` | Solar radiation debug info | String | Detailed solar data | -| `DebugCloudCover` | Cloud cover debug info | String | Cloud coverage details | - -## Usage Examples - -### Basic Temperature Display +[Variables] +Latitude=40.7128 ; New York coordinates +Longitude=-74.0060 +Units=metric ; "metric" or "imperial" +UpdateInterval=600 ; Update every 10 minutes -```ini -[MeasureCurrentTemp] +[mWeatherParent] Measure=Plugin Plugin=WeatherX.dll -DataType=CurrentTemp Latitude=#Latitude# Longitude=#Longitude# Units=#Units# UpdateInterval=#UpdateInterval# - -[MeterTemp] -Meter=String -MeasureName=MeasureCurrentTemp -Text=%1° -FontSize=24 +DataType=CurrentTemp ``` -### Daily Forecast +### Step 2: Add Child Measures + +Use child measures to get specific weather data: ```ini -[MeasureTomorrowMax] +[mTemperature] Measure=Plugin Plugin=WeatherX.dll -DataType=ForecastTempMax -ForecastDay=1 -Latitude=#Latitude# -Longitude=#Longitude# -Units=#Units# -UpdateInterval=#UpdateInterval# +ParentName=mWeatherParent +DataType=CurrentTemp -[MeasureTomorrowMin] +[mCondition] Measure=Plugin Plugin=WeatherX.dll -DataType=ForecastTempMin -ForecastDay=1 -Latitude=#Latitude# -Longitude=#Longitude# -Units=#Units# -UpdateInterval=#UpdateInterval# +ParentName=mWeatherParent +DataType=CurrentCondition + +[mHumidity] +Measure=Plugin +Plugin=WeatherX.dll +ParentName=mWeatherParent +DataType=CurrentHumidity ``` -### Hourly Forecast +### Step 3: Display the Data + +```ini +[MeterWeather] +Meter=String +MeasureName=mTemperature +MeasureName2=mCondition +MeasureName3=mHumidity +X=10 +Y=10 +FontSize=12 +FontColor=255,255,255 +Text="Temp: %1°#CRLF#Condition: %2#CRLF#Humidity: %3%" +``` +### How to Get Your Coordinates + +1. Go to [Google Maps](https://maps.google.com) +2. Right-click your location +3. Click the coordinates to copy them +4. Paste into your `Latitude` and `Longitude` variables + +## 📊 Available Data Types + +### Current Weather + +| DataType | Description | Example Value | +|----------|-------------|---------------| +| `CurrentTemp` | Current temperature | 22.5 | +| `CurrentCondition` | Weather description | "Clear Sky" | +| `CurrentHumidity` | Relative humidity | 65 | +| `CurrentWindSpeed` | Wind speed | 15.2 | +| `CurrentWindDirection` | Wind direction degrees | 180 | +| `CurrentWindDirectionText` | Wind direction | "S" | +| `CurrentPressure` | Atmospheric pressure | 1013.2 | +| `CurrentApparentTemp` | Feels-like temperature | 24.1 | +| `CurrentDewPoint` | Dew point | 16.3 | +| `CurrentCloudCover` | Cloud coverage % | 25 | +| `CurrentWindGusts` | Wind gusts speed | 22.5 | +| `CurrentUvIndex` | UV index | 5.2 | +| `CurrentIsDay` | Day=1, Night=0 | 1.0 | +| `CurrentIsDayText` | Day/Night as text | "Day" | +| `CurrentWeatherCode` | Weather code number | 0 | +| `CurrentWeatherCodeText` | Weather code as text | "0" | +| `CurrentSolarRadiation` | Solar radiation W/m² | 450.5 | + +### Daily Forecast (0-6 days) + +Add `ForecastDay=0` (today) to `ForecastDay=6` (6 days ahead) + +| DataType | Description | +|----------|-------------| +| `ForecastTempMax` | Daily maximum temperature | +| `ForecastTempMin` | Daily minimum temperature | +| `ForecastCondition` | Daily weather condition | +| `ForecastApparentTempMax` | Max feels-like temperature | +| `ForecastApparentTempMin` | Min feels-like temperature | +| `ForecastWindSpeed` | Max wind speed | +| `ForecastUvIndex` | Max UV index | +| `ForecastSunrise` | Sunrise hour (numeric) | +| `ForecastSunset` | Sunset hour (numeric) | +| `ForecastSunriseText` | Sunrise time "HH:mm" | +| `ForecastSunsetText` | Sunset time "HH:mm" | +| `ForecastWeatherCode` | Weather code number | +| `ForecastWeatherCodeText` | Weather code as text | + +**Example:** ```ini -[MeasureNext3Hours] +[mTomorrowMax] Measure=Plugin Plugin=WeatherX.dll +ParentName=mWeatherParent +DataType=ForecastTempMax +ForecastDay=1 +``` + +### Hourly Forecast (0-47 hours) + +Add `HourOffset=0` (current hour) to `HourOffset=47` (47 hours ahead) + +| DataType | Description | +|----------|-------------| +| `HourlyTemp` | Hourly temperature | +| `HourlyCondition` | Hourly weather condition | +| `HourlyHumidity` | Hourly humidity | +| `HourlyWindSpeed` | Hourly wind speed | +| `HourlyApparentTemp` | Hourly feels-like temp | +| `HourlyCloudCover` | Hourly cloud coverage | +| `HourlyVisibility` | Hourly visibility | +| `HourlyWeatherCode` | Weather code number | +| `HourlyWeatherCodeText` | Weather code as text | +| `HourlyTime` | Hour timestamp | +| `HourlySolarRadiation` | Solar radiation W/m² | + +**Example:** +```ini +[mNext3Hours] +Measure=Plugin +Plugin=WeatherX.dll +ParentName=mWeatherParent DataType=HourlyTemp HourOffset=3 -Latitude=#Latitude# -Longitude=#Longitude# -Units=#Units# -UpdateInterval=#UpdateInterval# ``` -### Wind Information +### Location Data (Reverse Geocoding) +Enable with `EnableReverseGeocode=1` in parent measure: + +| DataType | Description | Example | +|----------|-------------|----------| +| `LocationCity` | City name | "New York" | +| `LocationState` | State/Province | "New York" | +| `LocationStateCode` | State code | "NY" | +| `LocationCountry` | Country name | "United States" | +| `LocationCountryCode` | Country code | "US" | +| `LocationContinent` | Continent name | "North America" | +| `LocationContinentCode` | Continent code | "NA" | +| `LocationFull` | Full location string | "New York, NY, United States" | + +**Example:** ```ini -[MeasureWindSpeed] +[mWeatherParent] Measure=Plugin Plugin=WeatherX.dll -DataType=CurrentWindSpeed Latitude=#Latitude# Longitude=#Longitude# -Units=#Units# -UpdateInterval=#UpdateInterval# +EnableReverseGeocode=1 +DataType=CurrentTemp -[MeasureWindDirection] +[mCity] Measure=Plugin Plugin=WeatherX.dll -DataType=CurrentWindDirectionText -Latitude=#Latitude# -Longitude=#Longitude# -Units=#Units# -UpdateInterval=#UpdateInterval# - -[MeterWind] -Meter=String -MeasureName=MeasureWindSpeed -MeasureName2=MeasureWindDirection -Text=Wind: %1 %2 +ParentName=mWeatherParent +DataType=LocationCity ``` -### Day/Night Detection +### Special Data Types + +| DataType | Description | +|----------|-------------| +| `UvIndexText` | UV description: "Low", "Moderate", "High", etc. | +| `NextHoursSummary` | Summary of next 6 hours | +| `Status` | Plugin status: "Ready", "Updating...", "Error" | +| `DebugError` | Last error message | +| `DebugUrl` | API URL being used | +| `DebugLastUpdate` | Last update time | +| `DebugNextUpdate` | Time until next update | + + +## 🌦️ Weather Codes Reference + +| Code | Description | +|------|-------------| +| 0 | Clear Sky | +| 1 | Mainly Clear | +| 2 | Partly Cloudy | +| 3 | Overcast | +| 45 | Fog | +| 48 | Depositing Rime Fog | +| 51 | Light Drizzle | +| 53 | Moderate Drizzle | +| 55 | Dense Drizzle | +| 61 | Slight Rain | +| 63 | Moderate Rain | +| 65 | Heavy Rain | +| 71 | Slight Snow | +| 73 | Moderate Snow | +| 75 | Heavy Snow | +| 80 | Slight Rain Showers | +| 81 | Moderate Rain Showers | +| 82 | Violent Rain Showers | +| 95 | Thunderstorm | +| 96 | Thunderstorm with Hail | + +## 🚀 Advanced Examples + +### Complete Weather Widget ```ini -[MeasureDayNight] +[Rainmeter] +Update=1000 + +[Variables] +Latitude=40.7128 +Longitude=-74.0060 +Units=imperial +UpdateInterval=600 + +; Parent measure +[mWeatherParent] Measure=Plugin Plugin=WeatherX.dll -DataType=CurrentIsDay Latitude=#Latitude# Longitude=#Longitude# Units=#Units# UpdateInterval=#UpdateInterval# +EnableReverseGeocode=1 +DataType=CurrentTemp -[MeasureDayNightText] +; Current weather +[mTemp] Measure=Plugin Plugin=WeatherX.dll -DataType=CurrentIsDayText -Latitude=#Latitude# -Longitude=#Longitude# -Units=#Units# -UpdateInterval=#UpdateInterval# +ParentName=mWeatherParent +DataType=CurrentTemp + +[mCondition] +Measure=Plugin +Plugin=WeatherX.dll +ParentName=mWeatherParent +DataType=CurrentCondition + +[mHumidity] +Measure=Plugin +Plugin=WeatherX.dll +ParentName=mWeatherParent +DataType=CurrentHumidity + +[mWindSpeed] +Measure=Plugin +Plugin=WeatherX.dll +ParentName=mWeatherParent +DataType=CurrentWindSpeed + +[mWindDir] +Measure=Plugin +Plugin=WeatherX.dll +ParentName=mWeatherParent +DataType=CurrentWindDirectionText + +; Location +[mCity] +Measure=Plugin +Plugin=WeatherX.dll +ParentName=mWeatherParent +DataType=LocationCity + +; Display meters +[MeterLocation] +Meter=String +MeasureName=mCity +X=10 +Y=10 +FontSize=14 +FontWeight=700 +FontColor=255,255,255 +Text="%1" -[MeterDayNight] +[MeterTemp] +Meter=String +MeasureName=mTemp +X=10 +Y=35 +FontSize=48 +FontColor=255,255,255 +Text="%1°" + +[MeterCondition] Meter=String -MeasureName=MeasureDayNightText -Text=Time: %1 +MeasureName=mCondition +X=10 +Y=90 +FontSize=16 +FontColor=200,200,200 +Text="%1" + +[MeterDetails] +Meter=String +MeasureName=mHumidity +MeasureName2=mWindSpeed +MeasureName3=mWindDir +X=10 +Y=115 +FontSize=12 +FontColor=180,180,180 +Text="Humidity: %1% | Wind: %2 %3" ``` -### Weather Code Usage +### 7-Day Forecast ```ini -[MeasureWeatherCode] +; Tomorrow's forecast +[mTomorrowHigh] Measure=Plugin Plugin=WeatherX.dll -DataType=CurrentWeatherCode -Latitude=#Latitude# -Longitude=#Longitude# -Units=#Units# -UpdateInterval=#UpdateInterval# +ParentName=mWeatherParent +DataType=ForecastTempMax +ForecastDay=1 -[MeasureWeatherCodeText] +[mTomorrowLow] Measure=Plugin Plugin=WeatherX.dll -DataType=CurrentWeatherCodeText -Latitude=#Latitude# -Longitude=#Longitude# -Units=#Units# -UpdateInterval=#UpdateInterval# +ParentName=mWeatherParent +DataType=ForecastTempMin +ForecastDay=1 -[MeterWeatherCode] +[mTomorrowCondition] +Measure=Plugin +Plugin=WeatherX.dll +ParentName=mWeatherParent +DataType=ForecastCondition +ForecastDay=1 + +[MeterTomorrow] Meter=String -MeasureName=MeasureWeatherCode -MeasureName2=MeasureWeatherCodeText -Text=Code: %1 (%2) +MeasureName=mTomorrowHigh +MeasureName2=mTomorrowLow +MeasureName3=mTomorrowCondition +X=10 +Y=150 +FontSize=11 +Text="Tomorrow: %1°/%2° - %3" ``` -### Dynamic Theming Example +### Dynamic Day/Night Backgrounds ```ini -[MeasureDayNight] +[mIsDay] Measure=Plugin Plugin=WeatherX.dll +ParentName=mWeatherParent DataType=CurrentIsDay -Latitude=#Latitude# -Longitude=#Longitude# -Units=#Units# -UpdateInterval=#UpdateInterval# -[MeasureWeatherCode] +[mWeatherCode] Measure=Plugin Plugin=WeatherX.dll +ParentName=mWeatherParent DataType=CurrentWeatherCode -Latitude=#Latitude# -Longitude=#Longitude# -Units=#Units# -UpdateInterval=#UpdateInterval# -[MeterBackground] +[MeterBG] Meter=Image -; Dynamic background based on day/night and weather -ImageName=#@#Images\Weather\%1_%2.png -MeasureName=MeasureDayNight -MeasureName2=MeasureWeatherCode -; This would use images like: 1_0.png (day_clear), 0_61.png (night_rain), etc. +; Use different images based on time and weather +; Image naming: day_0.jpg (day, clear), night_61.jpg (night, rain) +ImageName=#@#Backgrounds\[mIsDay]_[mWeatherCode].jpg +W=400 +H=300 ``` -## Parameters - -### Required Parameters - -- **`Latitude`**: Your location's latitude (-90 to 90) -- **`Longitude`**: Your location's longitude (-180 to 180) -- **`DataType`**: The type of weather data to retrieve (see table above) - -### Optional Parameters - -- **`Units`**: Unit system ("metric" or "imperial", default: "metric") -- **`UpdateInterval`**: Update frequency in seconds (default: 600) -- **`ForecastDay`**: Day offset for forecast data (0-6, default: 0) -- **`HourOffset`**: Hour offset for hourly data (0-47, default: 0) -- **`Timezone`**: Timezone identifier (default: "auto") - -## Data Source - -This plugin uses the [Open-Meteo API](https://open-meteo.com/), which provides: -- Free weather data -- No API key required -- Global coverage -- High accuracy -- Multiple forecast models - -## Error Handling - -The plugin includes comprehensive error handling: -- Network timeout protection -- HTTPS/HTTP fallback -- Invalid coordinate detection -- JSON parsing error recovery -- Status reporting via `Status` DataType - -## Debugging - -Use these DataTypes for troubleshooting: -- `Status`: Current plugin status -- `DebugError`: Last error message -- `DebugUrl`: API URL being called -- `DebugSolarRadiation`: Solar data details -- `DebugCloudCover`: Cloud cover information - -## Units - -### Metric System (default) -- Temperature: Celsius (°C) -- Wind Speed: km/h -- Pressure: hPa -- Visibility: km -- Radiation: W/m² - -### Imperial System -- Temperature: Fahrenheit (°F) -- Wind Speed: mph -- Pressure: hPa (unchanged) -- Visibility: km (unchanged) -- Radiation: W/m² (unchanged) - -## Weather Codes - -The plugin translates numeric weather codes into descriptive text: - -| Code | Description | -| ---- | ----------------------------- | -| 0 | Clear Sky | -| 1 | Mainly Clear | -| 2 | Partly Cloudy | -| 3 | Overcast | -| 45 | Fog | -| 48 | Depositing Rime Fog | -| 51 | Light Drizzle | -| 53 | Moderate Drizzle | -| 55 | Dense Drizzle | -| 56 | Light Freezing Drizzle | -| 57 | Dense Freezing Drizzle | -| 61 | Slight Rain | -| 63 | Moderate Rain | -| 65 | Heavy Rain | -| 66 | Light Freezing Rain | -| 67 | Heavy Freezing Rain | -| 71 | Slight Snow | -| 73 | Moderate Snow | -| 75 | Heavy Snow | -| 77 | Snow Grains | -| 80 | Slight Rain Showers | -| 81 | Moderate Rain Showers | -| 82 | Violent Rain Showers | -| 85 | Slight Snow Showers | -| 86 | Heavy Snow Showers | -| 95 | Thunderstorm | -| 96 | Thunderstorm with Slight Hail | -| 99 | Thunderstorm with Heavy Hail | - -## Performance - -- Automatic HTTP fallback for network issues -- Efficient JSON parsing without external dependencies -- Configurable update intervals to minimize API calls -- Memory-efficient data storage -- Non-blocking asynchronous updates - -## Building from Source - -### Prerequisites -- Visual Studio 2022 with C++ development tools -- .NET Framework 4.7.2 or later -- PowerShell (for build script) - -### Build Instructions -1. Clone the repository -2. Open PowerShell in the project directory -3. Run the build script: - ```powershell - powershell -ExecutionPolicy Bypass -Command ". .\Build.ps1; Dist -major 1 -minor 0 -patch 0" - ``` -4. Find the built files in the `dist/` folder: - - `WeatherX_v*.rmskin` - Complete skin package - - `WeatherX_v*_x64_x86_dll.zip` - Plugin DLLs only - -## Requirements - -- Rainmeter 4.5 or later -- .NET Framework 4.7.2 or later -- Internet connection - -## License - -MIT - -## Contributing - -Contributions are welcome! Please feel free to submit issues and enhancement requests. +## 🔧 Troubleshooting + +### Plugin Not Loading +- Ensure you're using the correct DLL version (x64 vs x32) +- Check Rainmeter log for error messages +- Verify .NET Framework 4.8 is installed + +### No Data Showing +- Check your coordinates are correct (use Google Maps) +- Verify internet connection +- Check `DebugError` DataType for error messages +- Ensure `UpdateInterval` is not too short (minimum 60 seconds recommended) + +### Location Not Updating +- Make sure `EnableReverseGeocode=1` is set in parent measure +- Location updates when coordinates change +- Check Rainmeter log for geocoding errors + +### Debug Example + +```ini +[mDebugStatus] +Measure=Plugin +Plugin=WeatherX.dll +ParentName=mWeatherParent +DataType=Status + +[mDebugError] +Measure=Plugin +Plugin=WeatherX.dll +ParentName=mWeatherParent +DataType=DebugError + +[MeterDebug] +Meter=String +MeasureName=mDebugStatus +MeasureName2=mDebugError +Text="Status: %1#CRLF#Error: %2" +``` +## 📝 License + +MIT License - Free to use and modify + +## 👏 Credits + +- Weather data: [Open-Meteo API](https://open-meteo.com/) +- Reverse geocoding: [BigDataCloud API](https://www.bigdatacloud.com/) +- Plugin framework: [Rainmeter](https://www.rainmeter.net/) + +## 🐛 Issues & Support + +Found a bug or have a feature request? [Open an issue](https://github.com/NSTechBytes/WeatherX/issues) + +--- + +Made with ❤️ for the Rainmeter community diff --git a/Resources/Skins/WeatherX/FullExample.ini b/Resources/Skins/WeatherX/FullExample.ini new file mode 100644 index 0000000000000000000000000000000000000000..1455a1fcef43246b0f67b0b68d7277400d3c74db GIT binary patch literal 18478 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 RKBq_47t#}IHmltj{{w1t1C{^) literal 0 HcmV?d00001 diff --git a/Resources/Skins/WeatherX/Main.ini b/Resources/Skins/WeatherX/Main.ini index e4247cd3428e65dc0a7fb9e3381ad5e3ad0f4541..4e27787c96f70ffb2ab95df3cbb9232d42e3b745 100644 GIT binary patch literal 10722 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 diff --git a/Resources/Skins/WeatherX/Old-Main.ini b/Resources/Skins/WeatherX/Old-Main.ini new file mode 100644 index 0000000000000000000000000000000000000000..4a91584d48e047244a776cfc33c09e6c9cb4c535 GIT binary patch literal 41878 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 literal 0 HcmV?d00001 diff --git a/Resources/Skins/WeatherX/ParentMeasure.ini b/Resources/Skins/WeatherX/ParentMeasure.ini deleted file mode 100644 index a7fafe0..0000000 --- a/Resources/Skins/WeatherX/ParentMeasure.ini +++ /dev/null @@ -1,44 +0,0 @@ -[Rainmeter] -Update=1000 - -[mWeatherParent] -Measure=Plugin -Plugin=WeatherX.dll -Latitude=29.696489 -Longitude=72.549843 -Units=metric -UpdateInterval=600 -DataType=CurrentTemp - -[mTemp] -Measure=Plugin -Plugin=WeatherX.dll -ParentName=mWeatherParent -DataType=CurrentTemp - -[mHumidity] -Measure=Plugin -Plugin=WeatherX.dll -ParentName=mWeatherParent -DataType=CurrentHumidity - -[mCondition] -Measure=Plugin -Plugin=WeatherX.dll -ParentName=mWeatherParent -DataType=CurrentCondition - -[Background_Shape] -Meter=Shape -Shape=Rectangle 0,0,220,80,8| StrokeWidth 0 | FillColor 10,10,10,200 - -[mWeather] -Meter=STRING -MeasureName=mWeatherParent -MeasureName2=mHumidity -MeasureName3=mCondition -X=20 -Y=20 -FontColor=FFFFFF -Antialias=1 -Text=Current Temperature:%1 #CRLF#Current Humidity : %2#CRLF#Current Condition : %3 diff --git a/Resources/Skins/WeatherX/README.txt b/Resources/Skins/WeatherX/README.txt new file mode 100644 index 0000000..d15dfaf --- /dev/null +++ b/Resources/Skins/WeatherX/README.txt @@ -0,0 +1,208 @@ +================================================================================ + WEATHERX PLUGIN FOR RAINMETER - USER GUIDE +================================================================================ + +VERSION: 1.1.0 +AUTHOR: NSTechBytes +LICENSE: MIT +GITHUB: https://github.com/NSTechBytes/WeatherX + +================================================================================ + GETTING STARTED +================================================================================ + +1. FIND YOUR COORDINATES + ------------------------ + - Go to https://maps.google.com + - Right-click your location + - Click the coordinates to copy + - Example: 40.7128, -74.0060 + (First = Latitude, Second = Longitude) + +2. EDIT THE SKIN + -------------- + - Open any .ini file in this folder + - Find the [Variables] section + - Update Latitude and Longitude + - Choose Units (metric or imperial) + - Save and refresh the skin + +3. EXAMPLE SKINS INCLUDED + ----------------------- + Simple.ini - Minimal weather display (RECOMMENDED FOR BEGINNERS) + Main.ini - Full-featured weather widget with location + ReverseGeocode.ini - Demonstrates location detection + FullExample.ini - Shows all available features + +================================================================================ + BASIC STRUCTURE +================================================================================ + +PARENT MEASURE (Required - One per location) +--------------------------------------------- +Makes the API call and fetches weather data: + +[mWeatherParent] +Measure=Plugin +Plugin=WeatherX.dll +Latitude=40.7128 +Longitude=-74.0060 +Units=metric +UpdateInterval=600 +DataType=CurrentTemp + +CHILD MEASURES (Get specific data) +----------------------------------- +Don't make API calls - retrieve data from parent: + +[mTemp] +Measure=Plugin +Plugin=WeatherX.dll +ParentName=mWeatherParent ; Links to parent +DataType=CurrentTemp ; What data to get + +DISPLAY METER +------------- +[MeterTemp] +Meter=String +MeasureName=mTemp +Text="%1°" + +================================================================================ + POPULAR DATA TYPES +================================================================================ + +CURRENT WEATHER +--------------- +CurrentTemp - Current temperature +CurrentCondition - Weather description +CurrentHumidity - Humidity percentage +CurrentWindSpeed - Wind speed +CurrentWindDirectionText - Wind direction (N, NE, E, etc.) +CurrentApparentTemp - Feels-like temperature +CurrentUvIndex - UV index +CurrentIsDay - 1.0 = day, 0.0 = night + +DAILY FORECAST (Add ForecastDay=0 to 6) +---------------------------------------- +ForecastTempMax - High temperature +ForecastTempMin - Low temperature +ForecastCondition - Weather condition +ForecastSunriseText - Sunrise time +ForecastSunsetText - Sunset time + +Example: +DataType=ForecastTempMax +ForecastDay=1 ; Tomorrow + +HOURLY FORECAST (Add HourOffset=0 to 47) +----------------------------------------- +HourlyTemp - Temperature +HourlyCondition - Weather condition +HourlyHumidity - Humidity + +Example: +DataType=HourlyTemp +HourOffset=3 ; 3 hours from now + +LOCATION (Add EnableReverseGeocode=1 to parent) +------------------------------------------------ +LocationCity - City name +LocationState - State/Province +LocationCountry - Country name +LocationFull - Full location string + +================================================================================ + UNITS +================================================================================ + +METRIC (default) +---------------- +Temperature: Celsius (°C) +Wind Speed: km/h +Pressure: hPa + +IMPERIAL +-------- +Temperature: Fahrenheit (°F) +Wind Speed: mph +Pressure: hPa + +Set in parent measure: +Units=metric or Units=imperial + +================================================================================ + TROUBLESHOOTING +================================================================================ + +NO DATA SHOWING? +---------------- +✓ Check coordinates are correct +✓ Verify internet connection +✓ Check Rainmeter log (Ctrl+G) +✓ Use DataType=DebugError to see errors + +PLUGIN NOT LOADING? +------------------- +✓ Using correct DLL version (x64 vs x32)? +✓ .NET Framework 4.8 installed? +✓ Try restarting Rainmeter + +LOCATION NOT SHOWING? +--------------------- +✓ Add EnableReverseGeocode=1 to parent measure +✓ Wait for first update (default 10 minutes) + +================================================================================ + TIPS & BEST PRACTICES +================================================================================ + +1. Use ONE parent measure per location + Create child measures for each data point + +2. Update Interval + Don't set below 60 seconds (respect the free API) + Default 600 seconds (10 minutes) is recommended + +3. Testing + Use DataType=Status to check if plugin is working + Use DebugError to see any error messages + +4. Multiple Locations + Create separate parent measures with different names + +5. Dynamic Themes + Use CurrentIsDay to change colors/images for day/night + Use CurrentWeatherCode for weather-specific themes + +================================================================================ + WEATHER CODES +================================================================================ + +0 = Clear Sky 61 = Slight Rain +1 = Mainly Clear 63 = Moderate Rain +2 = Partly Cloudy 65 = Heavy Rain +3 = Overcast 71 = Slight Snow +45 = Fog 73 = Moderate Snow +51 = Light Drizzle 75 = Heavy Snow +53 = Moderate Drizzle 80 = Rain Showers +55 = Dense Drizzle 95 = Thunderstorm + +================================================================================ + COMPLETE DOCUMENTATION +================================================================================ + +For complete documentation, examples, and troubleshooting: +- README.md - Full documentation +- QUICKSTART.md - Step-by-step beginner guide +- GitHub: https://github.com/NSTechBytes/WeatherX + +================================================================================ + SUPPORT +================================================================================ + +Found a bug? Have a question? +Open an issue: https://github.com/NSTechBytes/WeatherX/issues + +Made with ❤️ for the Rainmeter community +================================================================================ diff --git a/Resources/Skins/WeatherX/ReverseGeocode.ini b/Resources/Skins/WeatherX/ReverseGeocode.ini index a084581ce6d83d5a25c35a7cbd3a5b062e6c58cb..d34595803a9c30049805f40e8dbdd89818a71838 100644 GIT binary patch literal 4966 zcmdT|T~As;5S`~H{SP-9->d=rs7=EIMXXIw44`O8A1F{Eeg?$Ura!8`ufGA=llzJ2N|T_U!CjfB$MqRa(-QE4h@e+~OI?XL&8JIafVM* z_Eo+W%d}#x18C4vUK;pI?H<~JoTyeoMmlnhkv!HMs`WNxzR11yhbnihIz#w%4Jod^ zCOJGAoh@U$4GHRVrG5D%b)ETyv9Hh>zz42v`bD{bWDB#=m|u|`Vs<2Fib4dN=i2j; z9z%10u@=0fonaj7dsr*Vo)qLjK1vC1b-b0N7WXR{Ih6{KtfE&&uL#*Xysbck_RF!C zw^+ZUv1}m5HF=AObFvMs^v`J#75eM--auzV5z5F`jMEXMC^G~`Zg&|)TUQQum#Dd& zzk@#)kfsHPHP>UD`8og0?jaDV0HpWe*=j;_Lt}pKjKT~l` zY$nowyEBZSsI2Q~#&LP~WyCV>_jB+qeH%PK^ zG=K2!`d_)0j>3B!#z78_H0KSF3tvhsNw33rb`cG4vvl+?B{K8hVp?Pc#CoQN>Y$9u z=X9a>45MkHUTWc7ouhsn#H{NCjB|Bh9v<}|$6J|N{wuOH;{Qcc<-sEo8QG48N-VD8ZB%12>P39jem36&!KgdsrM;| z%3&I>He=tMhvxHa9-3G5khQz>b5l_xa%4{W6h!z+V6DhnXrPkzHN|(!bN;OOJt2nt zRy$LzWR(!!?q-7?oB$o-$o{GJbK2hp#;mexu=pLGP--hN!-}_$wR(^WzX?p!?^IrQ z$9BoLT(h97^wD0x8Y^V3#2MB+-_-V!tYB!MX;WK73-^_xKx5?EW$C0dZRgKTg zluqNh9%E~$cs$=F&gmy11+!LwyGPB{vEf&Dgxuo3h|{PNd)C&o zplCKcD(sPCTN5$afX9?Fw2~ZKf55@!L)3WIJh#G=Ha%)T1H3|(ah)riNPaJ;tJi-y ze`#xmk!3G;675`Cw4OUzSx?}U#DQ#K{MMS)>2XN1#_qevauw@2=4wU|B4#@2>^2Ld zCfPLf3@?Ws^DSo@U>v#O-o~-qo`@v>%i?YWYt80lB>BIjK2DCi(OfouI-~so&Qf^(-`(72h&ZwhuMm^BoqQmF2+RL)5m z(C@F{mm3@o!r(nL{S@)7>3Xl5E7lUzP2Tm(z&fS}tuqXf*cbZ39K13a*q!vj+4K*{ z>ysJvNf{!foz3}LJ=Bu^7@S5Y`Ep*IbbQeedxPyn+>QI=jvbnk;KS+Uthc+u3v5_C zntH1%3Ea-mGd&|WIIhW0yR7Iq4aql(OM!Yz{tPs8fBn=h+=pBs*oEM1hZB^L-&A%R AI{*Lx diff --git a/Resources/Skins/WeatherX/Simple.ini b/Resources/Skins/WeatherX/Simple.ini new file mode 100644 index 0000000000000000000000000000000000000000..28569c0b45099637d93f44e51295035b3219d9ad GIT binary patch literal 3092 zcmdUxZ%-3J5XR@ViQnNO;T;l+phR;ouoNLFe?rT@5MP`Hfl#Pv3lZao@)N4RXZALC z?G=NOzA%@&-P@U+o#&a^+5PqN#P%$+OS`daJF_8g-#*%NduIFg#ZHlFEVY^~_#=tl z6WenAEZ52MS=-EzrPL1jYW4+f-;R9Ns@?iXkCBu$2X4KC&L_L~{=oGPe9i#BdT43& zd0XUNaA}kA4jP)Tm9}it+Ai5->@#!v_@K4p{JM3~%#bD(zY^O;dTZy-LW0dJ?-fW7 znAvA6!%Nv2B zpE{N$FwWZ>5HH$Ft|JcQU7__7jV0$}!CvRA?4u=}0gQxL%IA*TD92J9g*e}_dKYb3 z2+YbSp-P|liFS9vzXv`VY&4g*DH{jM&_x zD-P<;hWHRqZQ5%j!WbhutxVCr+JuRGGnSb48u}Q)Y_gzm19z z)IEbvy&i-b_t|pudI;T3!BA*AGDgGw~V*C^CrdcvB;~|v|ivKAQuZlq49{dV4=qCrYsp{ zLAR9<$D){O~Stdjxx$J{*Wt?nKJ$wyFj={+sES{_v|e@Lzyv~ GXU1>6hSTZ* literal 0 HcmV?d00001 From 1800204a8c0c3349033c6843abf710a37c263e21 Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Wed, 5 Nov 2025 07:14:10 +0500 Subject: [PATCH 5/5] build(core): bump version to 1.2.0 and update related files - Updated Build.ps1 usage comment with new minor version 1.2.0 - Modified Resources/skin_definition.json to reflect version 1.2.0 - Changed output filename in skin_definition.json to corresponding 1.2.0 version - Updated AssemblyInfo.cs AssemblyVersion attribute to 1.2.0.0 - Included binary changes in WeatherX skins reflecting the version update --- Build.ps1 | 2 +- Resources/Skins/WeatherX/FullExample.ini | Bin 18478 -> 18336 bytes Resources/Skins/WeatherX/Main.ini | Bin 10722 -> 10728 bytes Resources/Skins/WeatherX/ReverseGeocode.ini | Bin 4966 -> 4954 bytes Resources/Skins/WeatherX/Simple.ini | Bin 3092 -> 3098 bytes Resources/skin_definition.json | 4 ++-- WeatherX/AssemblyInfo.cs | 2 +- 7 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Build.ps1 b/Build.ps1 index 62dd068..c03fc62 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -1,4 +1,4 @@ -#usage -----> powershell -ExecutionPolicy Bypass -Command "& {. .\Build.ps1; Dist -major 1 -minor 1 -patch 0}" +#usage -----> powershell -ExecutionPolicy Bypass -Command "& {. .\Build.ps1; Dist -major 1 -minor 2 -patch 0}" $msbuild = "C:\Program Files\Microsoft Visual Studio\2022\Community\Msbuild\Current\Bin\MSBuild.exe" function Add-RMSkinFooter { diff --git a/Resources/Skins/WeatherX/FullExample.ini b/Resources/Skins/WeatherX/FullExample.ini index 1455a1fcef43246b0f67b0b68d7277400d3c74db..dba8061c2d85fefdca49fea3a90e7264ed0098ce 100644 GIT binary patch delta 66 zcmZ2CfpI}U;|2#NK_doB20aEdAZf;6!eGH*Ia!faQqY{i2qdR!vV<-j77f;p~N!{$n9OME3 DiLE2S diff --git a/Resources/Skins/WeatherX/Main.ini b/Resources/Skins/WeatherX/Main.ini index 4e27787c96f70ffb2ab95df3cbb9232d42e3b745..8467e5819c5254358eefa4927834a5ef5d5f7718 100644 GIT binary patch delta 52 zcmaD9{33Y697aJS21^D#1~VXO#$du=!C*OgEwiMcIfD^U)D$RU2^2G7Fy4HTQBVT_ DMp+8A delta 46 ycmaD6{3v+C97bLf1_K5?26F~O1|tTG$!i%U`E`Lj6QGy@5SlRD?+NGt diff --git a/Resources/Skins/WeatherX/ReverseGeocode.ini b/Resources/Skins/WeatherX/ReverseGeocode.ini index d34595803a9c30049805f40e8dbdd89818a71838..7663ed36943cdb870c0a60530acd63d74e0e30b3 100644 GIT binary patch delta 52 zcmaE+c1vx;Bt}6a21^D#1~VXO#$du=!C*OgBcr6CIfD^U)D$RU2^2G7Fy4HVu~!fP DGCB&j delta 64 zcmcbm_DpTVBu04?1_K5?26F~O1|tRw1_g9Dc|Efvzb;VT1gOdY2+bG_HlJne6$Ai$ C^a~3B diff --git a/Resources/Skins/WeatherX/Simple.ini b/Resources/Skins/WeatherX/Simple.ini index 28569c0b45099637d93f44e51295035b3219d9ad..94efd617adf0f0808d7c1d23ae126376108c7755 100644 GIT binary patch delta 52 zcmbOtF-u~@Bt}6a21^D#1~VXO#$du=!C*OgKBJ_dIfD^U)D$RU2^2G7Fy4He(U%JV D4EG70 delta 46 ycmbOwF-2m-Bt~8n1_K5?26F~O1|tTG$&JjC{JKD%2~f-c2+bG_HlJnm