Exemple #1
0
        /// <summary>
        /// Fetches DarkSky api, Tidal api to gather weather data, and select relevant data
        /// assigning it to this object
        /// </summary>
        /// <param name="lat">Latitude</param>
        /// <param name="lon">Longitude</param>
        /// <param name="location">Location name for provided coordinates</param>
        /// <param name="stationId">Tidal station id</param>
        /// <param name="addDays">Fetch data for future days. How many days add to today date</param>
        /// <param name="addHours">Fetch data for current hours + addHours</param>
        public void Update(double lat, double lon, string location, string stationId, int addDays = 0, int addHours = 0)
        {
            var client = new DarkSkyService(Constants.DarkSkyKey);
            // creating time object which depicts forecast time
            var time = DateTimeOffset.Now.AddDays(addDays).AddHours(addHours);

            // calling api for weather info
            DarkSkyApi.Models.Forecast f = Task.Run(() => client.GetTimeMachineWeatherAsync(lat, lon, time)).Result;
            // setting model properties with api result
            icon          = f.Currently.Icon;
            this.location = location;
            double _temp = Math.Round(UnitConverters.FahrenheitToCelsius(f.Currently.Temperature), 1);

            temperature   = _temp.ToString() + "°C";
            summary       = f.Currently.Summary;
            humidity      = $"{(int)(f.Currently.Humidity * 100)}%";
            windSpeed     = string.Format("{0:0.00}m/s", f.Currently.WindSpeed / 2.237); // convert to m/s
            windDirection = ((int)(f.Currently.WindBearing)).ToString() + "°";
            if (stationId != string.Empty)
            {
                waterLevel = TidalApi.GetInstance().GetWaterInfo(stationId, addDays, time.Hour);
            }
            else
            {
                waterLevel = "0m";
            }
            // setting forecast time in model, setting minutes and seconds to 0
            date = new DateTime(time.Year, time.Month, time.Day, time.Hour, 0, 0);
        }
        public async Task TimeMachineUnitsCanBeSpecified()
        {
            var client = new DarkSkyService(apiKey);
            var date   = DateTime.Now.Subtract(new TimeSpan(2, 0, 0, 0));

            var result = await client.GetTimeMachineWeatherAsync(AlcatrazLatitude, AlcatrazLongitude, date, Unit.CA);

            Assert.That(result, Is.Not.Null);
            Assert.That(result.Flags.Units, Is.EqualTo(Unit.CA.ToValue()));
        }
        public async Task CanRetrieveForThePast()
        {
            var client = new DarkSkyService(apiKey);
            var date   = DateTime.Now.Subtract(new TimeSpan(2, 0, 0, 0));

            var result = await client.GetTimeMachineWeatherAsync(AlcatrazLatitude, AlcatrazLongitude, date);

            Assert.That(result, Is.Not.Null);
            Assert.That(result.Currently, Is.Not.Null);
        }
        public async Task <Forecast> GetWeatherForecastAsync(double latitude, double longitude)
        {
            Forecast result = await darkSkyProxy.GetTimeMachineWeatherAsync(longitude, latitude, DateTime.Now, Unit.Auto);

            List <HourDataPoint> hourDataPoints = result.Hourly.Hours.ToList();

            DERMSCommon.WeatherForecast.WeatherForecast weatherForecast = new DERMSCommon.WeatherForecast.WeatherForecast(1001, 1, 1, 1, 1, DateTime.Now, "");

            return(result);
        }
        public async Task TimeMachineWorksWithPeriodDecimalSeperator()
        {
            var client = new DarkSkyService(apiKey);
            var date   = DateTime.Now.Subtract(new TimeSpan(2, 0, 0, 0));

            Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
            var result = await client.GetTimeMachineWeatherAsync(AlcatrazLatitude, AlcatrazLongitude, date, Unit.CA);

            Assert.That(result, Is.Not.Null);
            Assert.That(result.Currently, Is.Not.Null);
        }
Exemple #6
0
        public async void DarkSky()
        {
            var      client = new DarkSkyService(Constants.DarkSkyApi);
            Forecast result = await client.GetTimeMachineWeatherAsync(
                Convert.ToDouble(locationLatitude.Text),
                Convert.ToDouble(locationLongitude.Text),
                DateTimeOffset.Now
                );

            tempature.Text  = Convert.ToString(result.Currently.Temperature);
            tempTitle.Text  = result.Currently.Icon.ToUpper().Replace("-", " ");
            tempIcon.Source = result.Currently.Icon.Replace("-", "") + ".png";
        }
        public async Task TimeMachineExclusionWorksCorrectly()
        {
            var client        = new DarkSkyService(apiKey);
            var date          = DateTime.Now.Subtract(new TimeSpan(2, 0, 0, 0));
            var exclusionList = new List <Exclude> {
                Exclude.Hourly
            };

            var result = await client.GetTimeMachineWeatherAsync(AlcatrazLatitude, AlcatrazLongitude, date, Unit.US, exclusionList);

            Assert.That(result, Is.Not.Null);
            Assert.That(result.Currently, Is.Not.Null);
            Assert.That(result.Hourly, Is.Null);
        }
        public static NeedToSrpinkle GetForecastStatic()
        {
            NeedToSrpinkle needToSrpinkle = new NeedToSrpinkle();

            try
            {
                // Get the forecast
                var client = new DarkSkyService(_settings.ApiKey);
                var tsk    = client.GetWeatherDataAsync(_settings.Latitude, _settings.Longitude, _settings.Unit, _settings.Language);
                tsk.Wait();
                var forecast = tsk.Result;
                needToSrpinkle.Forecast = forecast;
                // Get Forcast max temperature for the next 24h and same for precipitations
                float ForecastMaxTemp                  = 0;
                float ForecastTotalPrecipitation       = 0;
                float ForecastProbabilityPrecipitation = 0;
                if (forecast.Daily.Days != null)
                {
                    if (forecast.Daily.Days.Count > 0)
                    {
                        if (forecast.Daily.Days[0].HighTemperature >= ForecastMaxTemp)
                        {
                            ForecastMaxTemp = forecast.Daily.Days[0].HighTemperature;
                        }
                        if ((forecast.Daily.Days[0].PrecipitationIntensity * 24) >= ForecastTotalPrecipitation)
                        {
                            ForecastTotalPrecipitation = forecast.Daily.Days[0].PrecipitationIntensity * 24;
                        }
                        if (forecast.Daily.Days[0].PrecipitationProbability >= ForecastProbabilityPrecipitation)
                        {
                            ForecastProbabilityPrecipitation = forecast.Daily.Days[0].PrecipitationProbability * 100;
                        }
                    }
                }
                // Get historical temperature of the day before
                tsk = client.GetTimeMachineWeatherAsync(_settings.Latitude, _settings.Longitude, DateTime.Now.AddDays(-1), _settings.Unit, _settings.Language);
                tsk.Wait();
                var history = tsk.Result;
                // find the al up precipitation and max temperature
                float HistMaxTemp            = 0;
                float HistTotalPrecipitation = 0;
                if (history.Daily.Days != null)
                {
                    if (history.Daily.Days.Count > 0)
                    {
                        if (history.Daily.Days[0].HighTemperature >= HistMaxTemp)
                        {
                            HistMaxTemp = history.Daily.Days[0].HighTemperature;
                        }
                        if (history.Daily.Days[0].PrecipitationAccumulation >= HistTotalPrecipitation)
                        {
                            HistTotalPrecipitation = history.Daily.Days[0].PrecipitationAccumulation;
                        }
                    }
                }


                needToSrpinkle.NeedTo = false;
                //Do all math with fuzzy logic
                foreach (var objective in _fuzzySprinklers)
                {
                    //Found the righ range
                    if ((ForecastMaxTemp >= objective.TempMin) && (ForecastMaxTemp < objective.TempMax))
                    {
                        // How much it rained ?
                        if (HistTotalPrecipitation >= objective.RainMax)
                        {
                            needToSrpinkle.NeedTo = false;
                        }
                        // Will it rain for sure? and will it rain enough?
                        else if ((ForecastProbabilityPrecipitation >= _settings.PrecipitationPercentForecast) && (ForecastTotalPrecipitation >= objective.RainMax))
                        {
                            needToSrpinkle.NeedTo = false;
                        }
                        else
                        {   // so we need to sprinkler. Make the math how long with the correction factor
                            // first calculate proportion of time vs the theoritical maximum
                            needToSrpinkle.PercentageCorrection = (float)(((objective.RainMax - HistTotalPrecipitation) / objective.RainMax) * objective.SprinklingMax / 100.0);
                            needToSrpinkle.NeedTo = true;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                return(null);
            }
            return(needToSrpinkle);
        }
        public async Task <bool> GetWeatherForecastAsyncSimulate()
        {
            //MOCK_Start
            bool isFileFull = true;
            Dictionary <long, List <HourDataPoint> > keyValuePairs = new Dictionary <long, List <HourDataPoint> >();

            //Dictionary<long, List<HourDataPoint>> keyValuePairs = cache.ReadFromFileDataPoint();

            if (keyValuePairs.Count == 0)
            {
                isFileFull = false;
            }
            //MOCK_End

            foreach (KeyValuePair <long, IdentifiedObject> kvp in analogniStari)
            {
                List <HourDataPoint> hourDataPoints;
                //MOCK_Start
                if (!isFileFull)
                {
                    //MOCK_End
                    Forecast result = await darkSkyProxy.GetTimeMachineWeatherAsync(((Analog)kvp.Value).Longitude, ((Analog)kvp.Value).Latitude, DateTime.Now, Unit.Auto);

                    hourDataPoints = result.Hourly.Hours.ToList();
                    //MOCK_Start
                    keyValuePairs.Add(((Analog)kvp.Value).GlobalId, hourDataPoints);
                }
                else
                {
                    hourDataPoints = keyValuePairs[((Analog)kvp.Value).GlobalId];

                    DateTimeOffset timeOffset = DateTimeOffset.Parse("00:00 AM");
                    foreach (HourDataPoint dataPoint in hourDataPoints)
                    {
                        dataPoint.Time = timeOffset;
                        timeOffset     = timeOffset.AddHours(1);
                    }
                }
                //MOCK_End

                DERMSCommon.WeatherForecast.WeatherForecast weatherForecast = new DERMSCommon.WeatherForecast.WeatherForecast(1001, 1, 1, 1, 1, DateTime.Now, "");
                foreach (HourDataPoint hdr in hourDataPoints)
                {
                    if (hdr.Time.Hour.Equals(DateTime.Now.Hour))
                    {
                        hourDataPoint = hdr;
                    }
                }
                float vrednost = 0;

                vrednost = CalculateHourAhead(((Analog)kvp.Value).Name, ((Analog)kvp.Value).NormalValue, ((Analog)kvp.Value).Latitude, ((Analog)kvp.Value).Longitude);
                foreach (KeyValuePair <List <long>, ushort> gidoviNaAdresu in GidoviNaAdresu)
                {
                    if (gidoviNaAdresu.Key[1] == (((Analog)kvp.Value).GlobalId) && ((Analog)kvp.Value).Description == "Simulation")
                    {
                        try
                        {
                            ModbusWriteCommandParameters p  = new ModbusWriteCommandParameters(6, (byte)ModbusFunctionCode.WRITE_SINGLE_REGISTER, gidoviNaAdresu.Value, (ushort)vrednost, configuration);
                            Common.IModbusFunction       fn = FunctionFactory.CreateModbusFunction(p);
                            commandExecutor.EnqueueCommand(fn);
                            ((Analog)kvp.Value).NormalValue = vrednost;
                            ModbusWriteCommandParameters p1  = new ModbusWriteCommandParameters(6, (byte)ModbusFunctionCode.WRITE_SINGLE_REGISTER, (ushort)(gidoviNaAdresu.Value - 1), (ushort)vrednost, configuration);
                            Common.IModbusFunction       fn1 = FunctionFactory.CreateModbusFunction(p1);
                            commandExecutor.EnqueueCommand(fn1);

                            ModbusWriteCommandParameters p12  = new ModbusWriteCommandParameters(6, (byte)ModbusFunctionCode.WRITE_SINGLE_REGISTER, (ushort)(gidoviNaAdresu.Value - 2), 0, configuration);
                            Common.IModbusFunction       fn12 = FunctionFactory.CreateModbusFunction(p12);
                            commandExecutor.EnqueueCommand(fn12);
                        }
                        catch (Exception ex)
                        {
                            string message = $"{ex.TargetSite.ReflectedType.Name}.{ex.TargetSite.Name}: {ex.Message}";
                            return(false);
                        }
                    }
                }
            }

            //MOCK_Start
            if (!isFileFull)
            {
                cache.WriteToFile(keyValuePairs);
            }
            //MOCK_End

            //foreach (KeyValuePair<long, IdentifiedObject> kvp in digitalniStari)
            //{
            //    float vrednost = 0;
            //    List<Forecast> fo = new List<Forecast>();
            //    GetWeatherForecastAsync(((Discrete)kvp.Value).Latitude, ((Discrete)kvp.Value).Longitude);
            //    vrednost = CalculateHourAhead(((Discrete)kvp.Value).Name, ((Discrete)kvp.Value).NormalValue, ((Discrete)kvp.Value).Latitude, ((Discrete)kvp.Value).Latitude);
            //    foreach (KeyValuePair<List<long>, ushort> gidoviNaAdresu in GidoviNaAdresu)
            //    {
            //        if (gidoviNaAdresu.Key.Contains(((Discrete)kvp.Value).GlobalId))
            //        {
            //            ushort raw = 0;
            //            raw = EGUConverter.ConvertToRaw(2, 5, vrednost);
            //            ModbusWriteCommandParameters p = new ModbusWriteCommandParameters(6, (byte)ModbusFunctionCode.WRITE_SINGLE_COIL, gidoviNaAdresu.Value, raw, configuration);
            //            IModbusFunction fn = FunctionFactory.CreateModbusFunction(p);
            //            commandExecutor.EnqueueCommand(fn);
            //        }
            //    }
            //}
            if (analogniStari.Count.Equals(0))
            {
                return(false);
            }
            return(true);
        }
Exemple #10
0
        public async Task XAsync()
        {
            var client = new DarkSkyService("9a9c19a4d9a62d812bf17136c99cc6cf");

            Forecast result = await client.GetTimeMachineWeatherAsync(47.650479, -122.3091841, DateTimeOffset.Now);
        }
Exemple #11
0
    public async Task GetWeather()
    {
        string slat = txtlat.Text;
        string slon = txtlong.Text;

        //slat = "40.909615";
        //slon = "-73.113439";

        if (slat != "" & slon != "")
        {
            var client = new DarkSkyService("9a9c19a4d9a62d812bf17136c99cc6cf");

            //Forecast result = await client.GetTimeMachineWeatherAsync(lat, lon, date);
            var numcalls = client.ApiCallsMade;

            //Don't go above 1000 for now to keep it all free
            if (numcalls > 950)
            {
                lblResults.Text = "You've made more than 950 requests today.";
            }
            else
            {
                DateTime d1 = Convert.ToDateTime(date1.Value);
                d1 = DateTime.SpecifyKind(d1, DateTimeKind.Local);

                DateTimeOffset date = d1;

                double lat = Convert.ToDouble(slat);
                double lon = Convert.ToDouble(slon);

                try
                {
                    Forecast result = await client.GetTimeMachineWeatherAsync(lat, lon, date);

                    DataTable dt = HourlyInfo(result);

                    if (dt.Rows.Count > 0)
                    {
                        grid.DataSource = dt;
                        grid.DataBind();
                        grid.Visible = true;

                        SQL_utils sql = new SQL_utils("data");

                        string bulkinsertresult = sql.BulkInsert(dt, "ALL_WeatherInfo");
                        sql.Close();

                        lblResults.Text = bulkinsertresult;
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine("** ERROR in TimeMachineWeatherAsync");
                    Debug.WriteLine(ex.Message);
                    lblResults.Text += ex.Message;

                    string param = String.Format("<br/>Lat:{0}  Long:{1}  Date:{2}", lat, lon, date.ToString());

                    //lblResults.Text += param;
                }
            }
        }
    }
Exemple #12
0
        static private string GetForecast(string param)
        {
            List <Param> Params = Param.decryptParam(param);

            if (Params != null)
            {
                //Check if there is an automation setting
                if (Params.Where(m => m.Name.ToLower() == paramAutomateAll).Any())
                {
                    WunderSettings.AutomateAll = Param.CheckConvertBool(Params, paramAutomateAll);
                }
            }

            StringBuilder sb = new StringBuilder();

            try
            {
                // Get the forecast
                var client = new DarkSkyService(ForecastSettings.ApiKey);
                var tsk    = client.GetWeatherDataAsync(ForecastSettings.Latitude, ForecastSettings.Longitude, ForecastSettings.Unit, ForecastSettings.Language);
                tsk.Wait();
                var forecast = tsk.Result;
                // Get Forcast max temperature for the next 24h and same for precipitations
                float ForecastMaxTemp                  = 0;
                float ForecastTotalPrecipitation       = 0;
                float ForecastProbabilityPrecipitation = 0;
                if (forecast.Daily.Days != null)
                {
                    if (forecast.Daily.Days.Count > 0)
                    {
                        if (forecast.Daily.Days[0].HighTemperature >= ForecastMaxTemp)
                        {
                            ForecastMaxTemp = forecast.Daily.Days[0].HighTemperature;
                        }
                        if ((forecast.Daily.Days[0].PrecipitationIntensity * 24) >= ForecastTotalPrecipitation)
                        {
                            ForecastTotalPrecipitation = forecast.Daily.Days[0].PrecipitationIntensity * 24;
                        }
                        if (forecast.Daily.Days[0].PrecipitationProbability >= ForecastProbabilityPrecipitation)
                        {
                            ForecastProbabilityPrecipitation = forecast.Daily.Days[0].PrecipitationProbability * 100;
                        }
                    }
                }
                // Get historical temperature of the day before
                tsk = client.GetTimeMachineWeatherAsync(ForecastSettings.Latitude, ForecastSettings.Longitude, DateTime.Now.AddDays(-1), ForecastSettings.Unit, ForecastSettings.Language);
                tsk.Wait();
                var history = tsk.Result;
                // find the al up precipitation and max temperature
                float HistMaxTemp            = 0;
                float HistTotalPrecipitation = 0;
                if (history.Daily.Days != null)
                {
                    if (history.Daily.Days.Count > 0)
                    {
                        if (history.Daily.Days[0].HighTemperature >= HistMaxTemp)
                        {
                            HistMaxTemp = history.Daily.Days[0].HighTemperature;
                        }
                        if (history.Daily.Days[0].PrecipitationAccumulation >= HistTotalPrecipitation)
                        {
                            HistTotalPrecipitation = history.Daily.Days[0].PrecipitationAccumulation;
                        }
                    }
                }

                // Creating the header
                sb.Append(BuildHeader());
                sb.Append("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\"><meta name=\"Generator\" content=\"Meteo forecast\"></head><body>");
                sb.Append("Meteo forecast for <b>" + ForecastSettings.City + "</b></br>" + forecast.Daily.Summary + "<img src=\"https://www.ellerbach.net/public/meteo/" + forecast.Daily.Icon + ".png\"><br>");

                WunderSettings.NeedToSprinkle = false;
                // Do all the math without fuzzy logic
                if (Fuzzy == null)
                {
                    // Did it rained anough?
                    if (HistTotalPrecipitation > WunderSettings.PrecipitationThresholdActuals)
                    {
                        sb.Append($"<b>No need to sprinkle</b>: yesterday rain was {HistTotalPrecipitation} mm more than the threshold {WunderSettings.PrecipitationThresholdActuals} mm.<br>");
                        WunderSettings.NeedToSprinkle = false;
                    }
                    //Is it warm enough?
                    else if (ForecastMaxTemp > WunderSettings.MinTemp)
                    {
                        WunderSettings.PercentageCorrection = (float)(((WunderSettings.PrecipitationThresholdActuals - ForecastTotalPrecipitation) / WunderSettings.PrecipitationThresholdActuals));
                        //Will it rain?
                        if (ForecastTotalPrecipitation > WunderSettings.PrecipitationThresholdForecast)
                        {
                            var introstr = $"Forecast is for rain with {ForecastTotalPrecipitation} mm, more than {WunderSettings.PrecipitationThresholdForecast} mm.<br>";
                            //Enough rain?
                            if (ForecastProbabilityPrecipitation > WunderSettings.PrecipitationPercentForecast)
                            {
                                sb.Append("<b>No need to sprinkle</b><ol>");
                                sb.Append($"<li>{introstr}</li>");
                                sb.Append($"<li>Considence index is {ForecastTotalPrecipitation} % more than the threshold {WunderSettings.PrecipitationPercentForecast} %.</ol><br>");
                                WunderSettings.NeedToSprinkle = false;
                            }
                            //Not enough rain, so need to sprinkler
                            else
                            {
                                sb.Append($"<b>I plan to sprinkle</b>:<ol>");
                                sb.Append($"<li>Confidence index is {ForecastTotalPrecipitation} % less than threshold {WunderSettings.PrecipitationPercentForecast} %</li>");
                                sb.Append($"<li>sprinkling will be adjusted by {(WunderSettings.PercentageCorrection * 100).ToString("0")} %</li></ol><br>");
                                WunderSettings.NeedToSprinkle = true;
                            }
                        }
                    }
                    //Not warm enough to srpinkler
                    else
                    {
                        sb.Append($"<b>No need to sprinkle</b>: Temperature will be {ForecastMaxTemp}°C lower than threshold {WunderSettings.MinTemp}. Please use manual program if you still want to sprinkler.");
                        WunderSettings.NeedToSprinkle = false;
                    }
                }
                //Do all math with fuzzy logic
                else
                {
                    foreach (var objective in Fuzzy)
                    {
                        //Found the righ range
                        if ((ForecastMaxTemp >= objective.TempMin) && (ForecastMaxTemp < objective.TempMax))
                        {
                            // How much it rained ?
                            if (HistTotalPrecipitation >= objective.RainMax)
                            {
                                sb.Append($"<b>No need to sprinkle</b>: It rained {HistTotalPrecipitation} mm more than the threshold {objective.RainMax} mm.<br>");
                                WunderSettings.NeedToSprinkle = false;
                            }
                            // Will it rain for sure? and will it rain enough?
                            else if ((ForecastProbabilityPrecipitation >= WunderSettings.PrecipitationPercentForecast) && (ForecastTotalPrecipitation >= objective.RainMax))
                            {
                                sb.Append($"<b>No need to sprinkle</b>:<ol><li>Confidence index is {ForecastProbabilityPrecipitation} % more than the threshold {WunderSettings.PrecipitationPercentForecast} %</li>");
                                sb.Append($"<li>With {ForecastTotalPrecipitation} mm more than the threshold {objective.RainMax} mm.</li></ol><br>");
                                WunderSettings.NeedToSprinkle = false;
                            }
                            else
                            {   // so we need to sprinkler. Make the math how long with the correction factor
                                // first calculate proportion of time vs the theoritical maximum
                                WunderSettings.PercentageCorrection = (float)(((objective.RainMax - HistTotalPrecipitation) / objective.RainMax) * objective.SprinklingMax / 100.0);
                                sb.Append($"<b>I plan to sprinkle</b>: <ol><li>Yesterday rain was {HistTotalPrecipitation} mm and {HistMaxTemp}°C</li>");
                                sb.Append($"<li>Today forecast is {ForecastTotalPrecipitation} mm at {ForecastProbabilityPrecipitation}% and max temperature {ForecastMaxTemp}°C</li>");
                                sb.Append($"<li>Percentage adjustment is {(WunderSettings.PercentageCorrection * 100).ToString("0")}%</li></ol><br>");
                                WunderSettings.NeedToSprinkle = true;
                            }
                        }
                    }
                }

                // display the forecast per day
                sb.Append("<table><tr><th>Date</th><th>Summary</th><th></th><th>Temp min</th><th>Temp max</th><th>Wind speed</th><th>Precipitation</th><tr>");
                for (int i = 0; i < forecast.Daily.Days.Count; i++)
                {
                    sb.Append("<tr><td>" + forecast.Daily.Days[i].Time.AddDays(1).ToString("yyyy-MM-dd ddd") + "</td><td>");
                    sb.Append(forecast.Daily.Days[i].Summary + "</td><td>");
                    sb.Append("<img src=\"https://www.ellerbach.net/public/meteo/" + forecast.Daily.Days[i].Icon + ".png\"></td><td>");
                    sb.Append(forecast.Daily.Days[i].LowTemperature.ToString("0.0") + "°C</td><td>");
                    sb.Append(forecast.Daily.Days[i].HighTemperature.ToString("0.0") + "°C</td><td>");
                    sb.Append((forecast.Daily.Days[i].WindSpeed * 3.6).ToString("0.0") + "km/h</td><td>");
                    sb.Append(forecast.Daily.Days[i].PrecipitationType + " " + forecast.Daily.Days[i].PrecipitationProbability * 100 + "% chance with " + (forecast.Daily.Days[i].PrecipitationIntensity * 24).ToString("0.0") + " mm</td>");
                }
                //hourly forecast
                sb.Append("</table><br />Prévisions par heure:<br />");
                sb.Append("<table><tr><th>Date</th><th>Summary</th><th></th><th>Temp</th><th>Temp app</th><th>Wind speed</th><th>Precipitation</th><tr>");
                for (int i = 0; i < forecast.Hourly.Hours.Count; i++)
                {
                    sb.Append("<tr><td>" + forecast.Hourly.Hours[i].Time.ToString("yyyy-MM-dd ddd hh:mm") + "</td><td>");
                    sb.Append(forecast.Hourly.Hours[i].Summary + "</td><td>");
                    sb.Append("<img src=\"https://www.ellerbach.net/public/meteo/" + forecast.Hourly.Hours[i].Icon + ".png\"></td><td>");
                    sb.Append(forecast.Hourly.Hours[i].Temperature.ToString("0.0") + "°C</td><td>");
                    sb.Append(forecast.Hourly.Hours[i].ApparentTemperature.ToString("0.0") + "°C</td><td>");
                    sb.Append((forecast.Hourly.Hours[i].WindSpeed * 3.6).ToString("0.0") + "km/h</td><td>");
                    sb.Append(forecast.Hourly.Hours[i].PrecipitationType + " " + forecast.Hourly.Hours[i].PrecipitationProbability * 100 + "% chance with " + (forecast.Hourly.Hours[i].PrecipitationIntensity).ToString("0.0") + " mm</td>");
                }

                //End of the page
                sb.Append("</table></body></html>");
            }
            catch (Exception ex)
            {
                sb.Append($"<p>ERROR:<br>{ex.Message}");
            }
            sb.Append("<p><a href='/typic.aspx" + Param.ParamStart + securityKey + Param.ParamSeparator + paramClk
                      + Param.ParamEqual + "1'>Create typical program</a><br>");
            sb.Append("<a href='/" + paramPageSprinkler + Param.ParamStart + securityKey + "'>Return to main page</a>");
            sb.Append("</p></BODY></HTML>");
            return(sb.ToString());
        }