diff --git a/sensors/main.py b/sensors/main.py index 2553ff4..5479267 100644 --- a/sensors/main.py +++ b/sensors/main.py @@ -311,7 +311,7 @@ def read_ltr(c, sensor, temp=None, dew_point=None, rh=None, aqi=None, lat=0.0, l if temp is not None and dew_point is not None: cloud, sun_elev = estimate_cloud_cover(c, lux, temp, dew_point, lat, lon) - fields['cloud_cover'] = cloud + fields['clouds'] = cloud fields['sun_elevation'] = sun_elev if rh is not None: diff --git a/server/src/forecast.mjs b/server/src/forecast.mjs index 15fbf03..4d0d91b 100644 --- a/server/src/forecast.mjs +++ b/server/src/forecast.mjs @@ -5,6 +5,14 @@ import {localDateStr} from './config.mjs'; export let lastForecast = { ts: 0, slots: [], summary: null } export const forecastTTL = 5 * 60 * 60_000 +function estimatePrecipitation(precipChance, clouds) { + if (precipChance < 30) return 0; + const intensity = (precipChance / 100) * clouds; + if (intensity > 0.7) return Math.round(intensity * 15 * 10) / 10; + if (intensity > 0.4) return Math.round(intensity * 6 * 10) / 10; + return Math.round(intensity * 2 * 10) / 10; +} + function estimateUV(sunElevation, cloudFraction) { if (sunElevation <= 0) return 0; const clearSkyUV = Math.sin((sunElevation * Math.PI) / 180) * 12; @@ -115,14 +123,17 @@ export async function get24HourForecast(currentSensors) { const time = new Date(now.getTime() + (i + 1) * 3600 * 1000); time.setMinutes(0, 0, 0); - const pressureDamping = Math.exp(-i * 0.08); - const pressure = seedPressure + pressureTrend * (i + 1) * pressureDamping; - const pressure_slp = Math.round((pressure + (coords.altitude ?? 0) / 8.5) * 100) / 100; - const clouds = estimateClouds(seedHumidity + humidityTrend * i, pressureTrend); - const bgTemp = seedTemp + tempTrend * (i + 1); - const temperature = Math.round(bgTemp * 10) / 10; - const humidity = forecastHumidity(seedAbsHum, temperature); - const wind_speed = forecastWind(seedWind, pressureTrend); + const pressureDamping = Math.exp(-i * 0.08); + const pressure = seedPressure + pressureTrend * (i + 1) * pressureDamping; + const pressure_slp = Math.round((pressure + (coords.altitude ?? 0) / 8.5) * 100) / 100; + const clouds = estimateClouds(seedHumidity + humidityTrend * i, pressureTrend); + const bgTemp = seedTemp + tempTrend * (i + 1); + const temperature = Math.round(bgTemp * 10) / 10; + const humidity = forecastHumidity(seedAbsHum, temperature); + const wind_speed = forecastWind(seedWind, pressureTrend); + const precipitation_chance = Math.round(weather.precipitation_chance * (1 - i * 0.02) * 100); + const raining = precipitation_chance > 60 ? 'True' : 'False'; + const precipitation = estimatePrecipitation(precipitation_chance, clouds); const celestial = getCelestialCurrent(coords.latitude, coords.longitude, time); const uv_index = estimateUV(celestial.sun_elevation ?? 0, clouds); @@ -143,14 +154,16 @@ export async function get24HourForecast(currentSensors) { wind_gusts: Math.round(wind_speed * 1.4 * 10) / 10, wind_direction: currentSensors.wind_direction ?? 0, clouds, - precipitation_chance: Math.round(weather.precipitation_chance * (1 - i * 0.02) * 100), + raining, + precipitation, + precipitation_chance, accumulation: 0, uv_index, uv_dose: 0, solar_wm2: 0, daily_light_integral: 0, lux: 0, - visibility: 50, + visibility: 50, ...celestial, }; }); @@ -174,12 +187,12 @@ export async function get24HourForecast(currentSensors) { slot.vapor_pressure_deficit = vaporPressureDeficit(slot.temperature, slot.humidity); slot.humidity_abs = absoluteHumidity(slot.temperature, slot.humidity); - slot.solar_wm2 = slot.sun_elevation > 0 + slot.solar_wm2 = slot.sun_elevation > 0 ? Math.round(Math.sin((slot.sun_elevation * Math.PI) / 180) * 1000 * (1 - slot.clouds * 0.75) * 100) / 100 : 0; slot.daily_light_integral = Math.round(slot.solar_wm2 * 3600 / 1_000_000 * 4.57 * 100) / 100; slot.lux = slot.solar_wm2 > 0 ? Math.round(slot.solar_wm2 * 120) : 0; - slot.visibility = slot.clouds > 0.85 ? 10 : 50; + slot.visibility = slot.clouds > 0.85 ? 10 : 50; Object.assign(slot, getWeatherCondition(slot)); } @@ -216,6 +229,7 @@ function summarise(slots) { return { time: slots[0].time, label: dominant.label, + code: dominant.code ?? null, icon: dominant.icon, temperature: peak(fin('temperature')), temperature_max: peak(finAll('temperature')), @@ -233,6 +247,8 @@ function summarise(slots) { wind_gusts: peak(fin('wind_gusts')), wind_direction: avg(fin('wind_direction')), clouds: avg(fin('clouds')), + raining: peak(fin('precipitation_chance')) > 60 ? 'True' : 'False', + precipitation: avg(fin('precipitation')), precipitation_chance: Math.round(peak(fin('precipitation_chance'))), accumulation: 0, uv_index: peak(fin('uv_index')), @@ -240,18 +256,18 @@ function summarise(slots) { solar_wm2: avg(fin('solar_wm2')), daily_light_integral: Math.round(finAll('daily_light_integral').reduce((a, b) => a + b, 0) * 100) / 100, lux: peak(fin('lux')), - visibility: avg(fin('visibility')), - sunrise: slots.find(s => s.sunrise)?.sunrise ?? null, - sunset: slots.find(s => s.sunset)?.sunset ?? null, - daylight: slots.find(s => s.daylight)?.daylight ?? null, - moon_phase: dominant.moon_phase ?? null, - moon_illumination: dominant.moon_illumination ?? null, - moon_elevation: dominant.moon_elevation ?? null, - moon_azimuth: dominant.moon_azimuth ?? null, - moon_new: dominant.moon_new ?? null, - moon_full: dominant.moon_full ?? null, - moonrise: slots.find(s => s.moonrise)?.moonrise ?? null, - moonset: slots.find(s => s.moonset)?.moonset ?? null, + visibility: avg(fin('visibility')), + sunrise: slots.find(s => s.sunrise)?.sunrise ?? null, + sunset: slots.find(s => s.sunset)?.sunset ?? null, + daylight: slots.find(s => s.daylight)?.daylight ?? null, + moon_phase: dominant.moon_phase ?? null, + moon_illumination: dominant.moon_illumination ?? null, + moon_elevation: dominant.moon_elevation ?? null, + moon_azimuth: dominant.moon_azimuth ?? null, + moon_new: dominant.moon_new ?? null, + moon_full: dominant.moon_full ?? null, + moonrise: slots.find(s => s.moonrise)?.moonrise ?? null, + moonset: slots.find(s => s.moonset)?.moonset ?? null, } } @@ -271,21 +287,20 @@ export async function getForecast() { export function getWeatherCondition(data) { if(data.lightning_rate > 0 && data.raining === 'True') { - return {label: 'Thunderstorm', icon: '11d'}; + return {label: 'Thunderstorm', icon: '11d', code: 211}; } - if(data.raining === 'True') { - if(data.precipitation > 7.6) return {label: 'Heavy Rain', icon: '10d'}; - if(data.precipitation > 2.5) return {label: 'Moderate Rain', icon: '10d'}; - return {label: 'Light Rain', icon: '09d'}; + if(data.precipitation > 7.6) return {label: 'Heavy Rain', icon: '10d', code: 502}; + if(data.precipitation > 2.5) return {label: 'Moderate Rain', icon: '10d', code: 501}; + return {label: 'Light Rain', icon: '09d', code: 500}; } - - if(data.visibility < 10) return {label: 'Mist', icon: '50d'}; + if(data.visibility < 10) return {label: 'Mist', icon: '50d', code: 701}; const dayNight = data.daytime ? 'd' : 'n'; - if(data.clouds > 0.85) return {label: 'Overcast Clouds', icon: `04${dayNight}`}; - if(data.clouds > 0.50) return {label: 'Broken Clouds', icon: `04${dayNight}`}; - if(data.clouds > 0.25) return {label: 'Scattered Clouds', icon: `03${dayNight}`}; - if(data.clouds > 0.10) return {label: 'Few Clouds', icon: `02${dayNight}`}; - return {label: 'Clear Sky', icon: `01${dayNight}`}; + const c = data.clouds > 1 ? data.clouds / 100 : data.clouds; + if(c > 0.85) return {label: 'Overcast Clouds', icon: `04${dayNight}`, code: 804}; + if(c > 0.50) return {label: 'Broken Clouds', icon: `04${dayNight}`, code: 803}; + if(c > 0.25) return {label: 'Scattered Clouds', icon: `03${dayNight}`, code: 802}; + if(c > 0.10) return {label: 'Few Clouds', icon: `02${dayNight}`, code: 801}; + return {label: 'Clear Sky', icon: `01${dayNight}`, code: 800}; }