commit cdd11db55ced099e2e9056353dfbca6ce5bd39b3
parent 27418eb4fef5960c2a7d6987eef8badeec8a2df5
Author: Sylvia Ivory <git@sivory.net>
Date: Thu, 19 Jun 2025 01:30:28 -0700
Add forecast information
Diffstat:
2 files changed, 125 insertions(+), 8 deletions(-)
diff --git a/crates/weather/src/model.rs b/crates/weather/src/model.rs
@@ -297,6 +297,46 @@ pub enum CurrentVariable {
Time,
/// NOTE: Not a valid variable, only found within `.current_units`
Interval,
+
+ // Duplicates of Hourly (interpolated)
+ PressureMsl,
+ SurfacePressure,
+ CloudCover,
+ CloudCoverLow,
+ CloudCoverMid,
+ CloudCoverHigh,
+ #[strum(to_string = "wind_speed_120m")]
+ WindSpeed120m,
+ #[strum(to_string = "wind_speed_180m")]
+ WindSpeed180m,
+ #[strum(to_string = "wind_direction_120m")]
+ WindDirection120m,
+ #[strum(to_string = "wind_direction_180m")]
+ WindDIrection180m,
+ VapourPressureDeficit,
+ Evapotranspiration,
+ Et0FaoEvapotranspiration,
+ PrecipitationProbability,
+ SnowDepth,
+ #[strum(to_string = "soil_temperature_0cm")]
+ SoilTemperature0cm,
+ #[strum(to_string = "soil_temperature_6cm")]
+ SoilTemperature6cm,
+ #[strum(to_string = "soil_temperature_18cm")]
+ SoilTemperature18cm,
+ #[strum(to_string = "soil_temperature_54cm")]
+ SoilTemperature54cm,
+ #[strum(to_string = "soil_moisture_0_to_1cm")]
+ SoilMoisture0To1cm,
+ #[strum(to_string = "soil_moisture_1_to_3cm")]
+ SoilMoisture1To3cm,
+ #[strum(to_string = "soil_moisture_3_to_9cm")]
+ SoilMoisture3To9cm,
+ #[strum(to_string = "soil_moisture_9_to_27cm")]
+ SoilMoisture9To27cm,
+ #[strum(to_string = "soil_moisture_27_to_81cm")]
+ SoilMoisture28To81cm,
+ IsDay,
}
#[derive(Serialize)]
diff --git a/src/main.rs b/src/main.rs
@@ -4,7 +4,10 @@ use chrono::{
DateTime, Local,
format::{Item, StrftimeItems},
};
-use fjordgard_weather::MeteoClient;
+use fjordgard_weather::{
+ MeteoClient,
+ model::{CurrentVariable, Forecast, ForecastOptions},
+};
use iced::{
Color, Element, Font, Length, Size, Subscription, Task,
font::Weight,
@@ -16,7 +19,7 @@ use iced::{
use background::BackgroundHandle;
use config::Config;
use icon::{icon, icon_button};
-use log::debug;
+use log::{debug, error};
use crate::config::BackgroundMode;
@@ -35,6 +38,10 @@ struct Fjordgard {
settings_window: Option<settings::Settings>,
main_window: window::Id,
+
+ forecast_loaded: bool,
+ forecast_text: String,
+ forecast_icon: String,
}
#[derive(Debug, Clone, Copy)]
@@ -56,6 +63,9 @@ enum Message {
Settings(settings::Message),
Background(background::Message),
+
+ RequestForecastUpdate,
+ ForecastUpdate(Result<Forecast, String>),
}
impl Fjordgard {
@@ -82,8 +92,12 @@ impl Fjordgard {
settings_window: None,
main_window: id,
+
+ forecast_loaded: false,
+ forecast_text: String::from("Weather unknown"),
+ forecast_icon: String::from("icons/weather/not-available.svg"),
},
- Task::batch(vec![
+ Task::batch([
open.map(|_| Message::MainWindowOpened),
task.map(Message::Background),
]),
@@ -142,9 +156,16 @@ impl Fjordgard {
.unwrap();
}
- self.background
+ let background_task = self
+ .background
.load_config(&config)
- .map(Message::Background)
+ .map(Message::Background);
+
+ if !self.forecast_loaded && config.location.is_some() {
+ Task::batch([background_task, Task::done(Message::RequestForecastUpdate)])
+ } else {
+ background_task
+ }
}
Message::Settings(msg) => {
if let Some(settings) = &mut self.settings_window {
@@ -162,6 +183,61 @@ impl Fjordgard {
debug!("main window opened");
Task::none()
}
+ Message::RequestForecastUpdate => {
+ let config = self.config.borrow();
+ if let Some(location) = &config.location {
+ let meteo = self.meteo.clone();
+ let (latitude, longitude) = (location.latitude, location.latitude);
+
+ Task::future(async move {
+ meteo
+ .forecast_single(
+ latitude,
+ longitude,
+ Some(ForecastOptions {
+ current: Some(vec![
+ CurrentVariable::Temperature2m,
+ CurrentVariable::Rain,
+ CurrentVariable::Snowfall,
+ CurrentVariable::IsDay,
+ CurrentVariable::CloudCover,
+ ]),
+ ..Default::default()
+ }),
+ )
+ .await
+ })
+ .map(|r| Message::ForecastUpdate(r.map_err(|e| e.to_string())))
+ } else {
+ Task::none()
+ }
+ }
+ Message::ForecastUpdate(res) => match res {
+ Err(e) => {
+ error!("failed to load forecast: {e}");
+ Task::none()
+ }
+ Ok(forecast) => {
+ let forecast_text = || -> Option<String> {
+ let current = forecast.current?;
+ let units = forecast.current_units?;
+
+ let temperature = current.data.get(&CurrentVariable::Temperature2m)?;
+ let temperature_units = units.get(&CurrentVariable::Temperature2m)?;
+
+ // TODO; calculate descriptor string
+ // TODO; calculate icon
+ Some(format!("{temperature}{temperature_units}"))
+ };
+
+ if let Some(forecast_text) = forecast_text() {
+ self.forecast_loaded = true;
+ self.forecast_text = forecast_text;
+ }
+
+ Task::none()
+ }
+ },
_ => Task::none(),
}
}
@@ -191,9 +267,9 @@ impl Fjordgard {
.center();
let weather_widget = container(row![
- icon("icons/weather/not-available.svg"),
+ icon(&self.forecast_icon),
horizontal_space().width(Length::Fixed(7.25)),
- text("Weather unknown").color(Color::WHITE)
+ text(&self.forecast_text).color(Color::WHITE)
])
.center_x(Length::Fill);
@@ -225,8 +301,9 @@ impl Fjordgard {
}
fn subscription(&self) -> Subscription<Message> {
- Subscription::batch(vec![
+ Subscription::batch([
time::every(time::Duration::from_secs(1)).map(|_| Message::Tick(Local::now())),
+ time::every(time::Duration::from_secs(60 * 15)).map(|_| Message::RequestForecastUpdate),
window::close_events().map(Message::WindowClosed),
])
}