Skip to content

Commit

Permalink
block[weather]: Add sunrise/sunset to nws
Browse files Browse the repository at this point in the history
  • Loading branch information
bim9262 committed Oct 21, 2024
1 parent b77f328 commit 82075f0
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 100 deletions.
58 changes: 56 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,11 @@ shellexpand = "3.0"
signal-hook = "0.3"
signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
smart-default = "0.7"
sunrise = "1.0.1"
swayipc-async = "2.0"
thiserror = "1.0"
toml = { version = "0.8", features = ["preserve_order"] }
tz-search = "0.1.1"
unicode-segmentation = "1.10.1"
wayrs-client = { version = "1.0", features = ["tokio"] }
wayrs-protocols = { version = "0.14", features = ["wlr-foreign-toplevel-management-unstable-v1"] }
Expand Down
41 changes: 22 additions & 19 deletions src/blocks/weather.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,9 @@ use std::fmt;
use std::sync::{Arc, Mutex};
use std::time::Instant;

use chrono::{DateTime, Utc};
use chrono::{DateTime, Datelike, Utc};
use chrono_tz::Tz;
use sunrise::sunrise_sunset;

use crate::formatting::Format;

Expand Down Expand Up @@ -183,7 +185,6 @@ trait WeatherProvider {
&self,
autolocated_location: Option<&Coordinates>,
need_forecast: bool,
need_sunrise_and_sunset: bool,
) -> Result<WeatherResult>;
}

Expand Down Expand Up @@ -271,8 +272,8 @@ struct WeatherResult {
location: String,
current_weather: WeatherMoment,
forecast: Option<Forecast>,
sunrise: Option<DateTime<Utc>>,
sunset: Option<DateTime<Utc>>,
sunrise: DateTime<Utc>,
sunset: DateTime<Utc>,
}

impl WeatherResult {
Expand All @@ -289,6 +290,8 @@ impl WeatherResult {
"wind" => Value::number(self.current_weather.wind),
"wind_kmh" => Value::number(self.current_weather.wind_kmh),
"direction" => Value::text(convert_wind_direction(self.current_weather.wind_direction).into()),
"sunrise" => Value::datetime(self.sunrise, None),
"sunset" => Value::datetime(self.sunset, None),
};

if let Some(forecast) = self.forecast {
Expand Down Expand Up @@ -320,13 +323,6 @@ impl WeatherResult {
}
}

if let Some(sunset) = self.sunset {
values.insert("sunset".into(), Value::datetime(sunset, None));
}

if let Some(sunrise) = self.sunrise {
values.insert("sunrise".into(), Value::datetime(sunrise, None));
}
values
}
}
Expand Down Expand Up @@ -465,7 +461,6 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {

let autolocate_interval = config.autolocate_interval.unwrap_or(config.interval);
let need_forecast = need_forecast(&format, format_alt.as_ref());
let need_sunrise_and_sunset = need_sunrise_and_sunset(&format, format_alt.as_ref());

let mut timer = config.interval.timer();

Expand All @@ -477,8 +472,7 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
None
};

let fetch =
|| provider.get_weather(location.as_ref(), need_forecast, need_sunrise_and_sunset);
let fetch = || provider.get_weather(location.as_ref(), need_forecast);
let data = fetch.retry(ExponentialBuilder::default()).await?;
let data_values = data.into_values();

Expand Down Expand Up @@ -527,11 +521,20 @@ fn need_forecast(format: &Format, format_alt: Option<&Format>) -> bool {
has_forecast_key(format) || format_alt.is_some_and(has_forecast_key)
}

fn need_sunrise_and_sunset(format: &Format, format_alt: Option<&Format>) -> bool {
fn has_sunrise_or_sunset_keys(format: &Format) -> bool {
format.contains_key("sunrise") || format.contains_key("sunset")
}
has_sunrise_or_sunset_keys(format) || format_alt.is_some_and(has_sunrise_or_sunset_keys)
fn calculate_sunrise_sunset(lat: f64, lon: f64) -> Result<(DateTime<Utc>, DateTime<Utc>)> {
let tz: Tz = tz_search::lookup(lat, lon)
.unwrap()
.parse()
.error("Unable to find tz for lat/long")?;
let date = Utc::now().with_timezone(&tz);
let (sunrise, sunset) = sunrise_sunset(lat, lon, date.year(), date.month(), date.day());

Ok((
DateTime::<Utc>::from_timestamp(sunrise, 0)
.error("Unable to convert timestamp to DateTime")?,
DateTime::<Utc>::from_timestamp(sunset, 0)
.error("Unable to convert timestamp to DateTime")?,
))
}

#[derive(Debug, Deserialize, Clone, Copy, PartialEq, Eq, SmartDefault)]
Expand Down
67 changes: 9 additions & 58 deletions src/blocks/weather/met_no.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ type LegendsStore = HashMap<String, LegendsResult>;
#[derive(Deserialize, Debug, SmartDefault)]
#[serde(tag = "name", rename_all = "lowercase", deny_unknown_fields, default)]
pub struct Config {
coordinates: Option<(String, String)>,
coordinates: Option<(f64, f64)>,
altitude: Option<String>,
#[serde(default)]
lang: ApiLanguage,
Expand Down Expand Up @@ -171,45 +171,29 @@ struct ForecastTimeInstant {
relative_humidity: Option<f64>,
}

#[derive(Debug, Deserialize)]
struct SunResponse {
properties: Properties,
}

#[derive(Debug, Deserialize)]
struct Properties {
sunrise: TimeData,
sunset: TimeData,
}

#[derive(Debug, Deserialize)]
struct TimeData {
time: String,
}

static LEGENDS: LazyLock<Option<LegendsStore>> =
LazyLock::new(|| serde_json::from_str(include_str!("met_no_legends.json")).ok());

const FORECAST_URL: &str = "https://api.met.no/weatherapi/locationforecast/2.0/compact";
const SUN_URL: &str = "https://api.met.no/weatherapi/sunrise/3.0/sun";

#[async_trait]
impl WeatherProvider for Service<'_> {
async fn get_weather(
&self,
location: Option<&Coordinates>,
need_forecast: bool,
need_sunrise_and_sunset: bool,
) -> Result<WeatherResult> {
let (lat, lon) = location
.as_ref()
.map(|loc| (loc.latitude.to_string(), loc.longitude.to_string()))
.or_else(|| self.config.coordinates.clone())
.map(|loc| (loc.latitude, loc.longitude))
.or(self.config.coordinates)
.error("No location given")?;

let (sunrise, sunset) = calculate_sunrise_sunset(lat, lon)?;

let querystr: HashMap<&str, String> = map! {
"lat" => &lat,
"lon" => &lon,
"lat" => lat.to_string(),
"lon" => lon.to_string(),
[if let Some(alt) = &self.config.altitude] "altitude" => alt,
};

Expand All @@ -234,6 +218,8 @@ impl WeatherProvider for Service<'_> {
location: location_name,
current_weather,
forecast: None,
sunrise,
sunset,
});
}

Expand All @@ -255,41 +241,6 @@ impl WeatherProvider for Service<'_> {

let forecast = Some(Forecast::new(&data_agg, fin));

let (sunset, sunrise) = match need_sunrise_and_sunset {
true => {
let sun_query_string: HashMap<&str, String> = map! {
"lat" => &lat,
"lon" => &lon,
};

let sun_data: SunResponse = REQWEST_CLIENT
.get(SUN_URL)
.query(&sun_query_string)
.header(reqwest::header::CONTENT_TYPE, "application/json")
.send()
.await
.error("Forecast request failed")?
.json()
.await
.error("Forecast request failed")?;

let sunset = Some(
DateTime::parse_from_str(&sun_data.properties.sunset.time, "%Y-%m-%dT%H:%M%z")
.error("failed to parse sunset timestring")?
.to_utc(),
);

let sunrise = Some(
DateTime::parse_from_str(&sun_data.properties.sunrise.time, "%Y-%m-%dT%H:%M%z")
.error("failed to parse sunrise timestring")?
.to_utc(),
);

(sunset, sunrise)
}
false => (None, None),
};

Ok(WeatherResult {
location: location_name,
current_weather,
Expand Down
Loading

0 comments on commit 82075f0

Please sign in to comment.