include modified weatherapi, use forcast, use worst rain detection

This commit is contained in:
Empire 2023-06-09 01:09:59 +02:00
parent 0b973f8201
commit a201193d58
20 changed files with 3175 additions and 76 deletions

View File

@ -1,13 +1,7 @@
[package]
name = "LEDboardClient"
version = "0.1.0"
edition = "2021"
[workspace]
members = [
"openweathermap",
"bin"
]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bit = "0.1.1"
embedded-graphics = "0.8.0"
openweathermap = "0.2.4"
substring = "1.4.5"
tinybmp = "0.5.0"

16
client/bin/Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
name = "LEDboardClient"
version = "0.1.0"
edition = "2021"
[dependencies]
bit = "0.1.1"
embedded-graphics = "0.8.0"
substring = "1.4.5"
tinybmp = "0.5.0"
openweathermap = { path = "../openweathermap" }
chrono = { version = "0.4.23", default-features = false , features = ["iana-time-zone"] }
chrono-tz = "0.8.0"
colored = "2.0.0"
datetime = "0.5.2"

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -1,18 +1,24 @@
use bit::BitIndex;
use chrono_tz::Europe::Berlin;
use chrono::{DateTime, NaiveDateTime, Utc, Timelike};
use openweathermap::{forecast::{Weather, List}};
use substring::Substring;
use tinybmp::Bmp;
use core::time;
use embedded_graphics::{
image::{Image, ImageRaw},
image::{Image},
mono_font::{iso_8859_1::FONT_6X10, MonoTextStyle},
pixelcolor::{BinaryColor, Rgb565},
pixelcolor::{BinaryColor},
prelude::*,
primitives::{Line, PrimitiveStyle},
primitives::{PrimitiveStyle},
text::Text,
};
use openweathermap::{self, CurrentWeather, Weather};
use std::net::UdpSocket;
use std::{env, sync::mpsc::Receiver, thread};
use std::{net::UdpSocket, time::{Instant, Duration}};
use std::{env, thread};
use openweathermap::forecast::Forecast;
const IMAGE_SIZE_BYTE: usize = (IMAGE_WIDTH_BYTE * IMAGE_HEIGHT) as usize; /* one byte contains 8 LEDs, one in each bit */
const IMAGE_WIDTH: u32 = 5 * 32;
@ -105,13 +111,8 @@ impl DrawTarget for UdpDisplay {
}
}
fn renderWeatherIcon(display: &mut UdpDisplay, icon: &[u8]){
let icon_image = Bmp::from_slice(icon).unwrap();
Image::new(&icon_image, Point::new((IMAGE_WIDTH-40) as i32, 0)).draw(display).unwrap();
}
fn renderWeather(display: &mut UdpDisplay ,data: &Option<Result<CurrentWeather, String>>){
fn renderWeather(display: &mut UdpDisplay ,data: &Option<Result<Forecast, String>>){
let text_style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On);
match data {
@ -123,47 +124,44 @@ fn renderWeather(display: &mut UdpDisplay ,data: &Option<Result<CurrentWeather,
println!("{}", &error);
}
Ok(result) => {
if(!result.weather.is_empty()){
let condition = &result.weather[0];
let short_icon_code = condition.icon.substring(0,2);
if !result.list.is_empty() {
let mut max:f64 = 0_f64;
let mut best = &result.list[0];
for forecast in &result.list {
let time_s = forecast.dt;
let local_time = NaiveDateTime::from_timestamp_millis(time_s*1000).unwrap();
let zoned_time : DateTime<Utc> = DateTime::from_utc(local_time, Utc);
let europe_time = zoned_time.with_timezone(&Berlin);
let hour = europe_time.hour();
let minute = europe_time.minute();
let cur_time = DateTime::<Utc>::default();
if(zoned_time > cur_time){
println!("Skipping old result {hour}:{minute} @{time_s}");
}
match &forecast.rain {
Some(x) => {
let rain_v = x.three_hours;
println!("Rain at {hour}:{minute} @{time_s} with {rain_v} prior best was {max}");
if rain_v > max {
best = forecast;
max = rain_v;
}
},
None => println!("No rain at {hour}:{minute}"),
}
}
let condition = best.weather[0].to_owned();
println!("Weather info: {} desc: {} icon {}", condition.main, condition.description, condition.icon);
match short_icon_code {
"01" => {
renderWeatherIcon(display, include_bytes!("sun.bmp"));
},
"02" => {
renderWeatherIcon(display, include_bytes!("few_clouds.bmp"));
},
"03" => {
renderWeatherIcon(display, include_bytes!("scattered_clouds.bmp"));
},
"04" => {
renderWeatherIcon(display, include_bytes!("broken_clouds.bmp"));
},
"09" => {
renderWeatherIcon(display, include_bytes!("shower.bmp"));
},
"10" => {
renderWeatherIcon(display, include_bytes!("rain.bmp"));
},
"11" => {
renderWeatherIcon(display, include_bytes!("thunderstorm.bmp"));
},
"13" => {
renderWeatherIcon(display, include_bytes!("snow.bmp"));
},
"50" => {
renderWeatherIcon(display, include_bytes!("mist.bmp"));
},
_ => {
println!("Missing icon for {short_icon_code}");
Text::new(&condition.description, Point::new(0, 0), text_style)
.draw(display)
.unwrap();
}
}
renderWeatherIcon(&condition, display);
}
}
},
@ -174,12 +172,52 @@ fn renderWeather(display: &mut UdpDisplay ,data: &Option<Result<CurrentWeather,
println!("{}", "no result");
}
}
}
fn send_package(ipaddress: String, data: &Option<Result<CurrentWeather, String>>) {
fn renderWeatherIcon(condition: &Weather, display: &mut UdpDisplay ){
let text_style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On);
let short_icon_code = condition.icon.substring(0,2);
let icon_image: Result<Bmp<BinaryColor>, tinybmp::ParseError> = match short_icon_code {
"01" => {
Bmp::from_slice(include_bytes!("sun.bmp"))
},
"02" => {
Bmp::from_slice(include_bytes!("few_clouds.bmp"))
},
"03" => {
Bmp::from_slice(include_bytes!("scattered_clouds.bmp"))
},
"04" => {
Bmp::from_slice(include_bytes!("broken_clouds.bmp"))
},
"09" => {
Bmp::from_slice(include_bytes!("shower.bmp"))
},
"10" => {
Bmp::from_slice(include_bytes!("rain.bmp"))
},
"11" => {
Bmp::from_slice(include_bytes!("thunderstorm.bmp"))
},
"13" => {
Bmp::from_slice(include_bytes!("snow.bmp"))
},
"50" => {
Bmp::from_slice(include_bytes!("mist.bmp"))
},
_ => {
println!("Missing icon for {short_icon_code}");
Text::new(&condition.description, Point::new(0, 0), text_style)
.draw(display)
.unwrap();
return;
}
};
Image::new(&icon_image.unwrap(), Point::new((IMAGE_WIDTH-40) as i32, 0)).draw(display).unwrap();
}
fn send_package(ipaddress: String, data: &Option<Result<Forecast, String>>) {
let mut package: [u8; PACKAGE_LENGTH] = [0; PACKAGE_LENGTH];
// Brightness
@ -228,33 +266,31 @@ fn main() {
// one argument passed
2 => {
let ip = &args[1];
let receiver = openweathermap::init(
"Mannheim",
let receiver = openweathermap::init_forecast("Mannheim",
"metric",
"de",
"978882ab9dd05e7122ff2b0aef2d3e55",
60,
);
60,1);
let mut lastData = Option::None;
let mut last_data = Option::None;
loop {
let delay = time::Duration::from_millis(10000);
thread::sleep(delay);
let answer = openweathermap::update(&receiver);
let answer = openweathermap::update_forecast(&receiver);
match answer {
Some(_) => {
lastData = answer;
last_data = answer;
}
None => {
}
}
send_package(ip.to_string(), &lastData);
send_package(ip.to_string(), &last_data);
}
}
// all the other cases

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -0,0 +1 @@
openweathermap.thats-software.com

View File

@ -0,0 +1,22 @@
[package]
authors = ["Patrick Hoffmann <pat@thats-software.com>"]
description = "easy access current weather data from OpenWeatherMap"
documentation = "https://docs.rs/openweathermap"
edition = "2018"
homepage = "https://openweathermap.thats-software.com"
keywords = ["weather", "openweathermap"]
license = "MIT OR Apache-2.0"
name = "openweathermap"
readme = "README.md"
version = "0.2.4"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
futures = {version = "0.3.1", features = ["executor"]}
http = {version = "0.2.4"}
rand = {version = "0.8.3"}
regex = {version = "1.4.6"}
reqwest = {version = "0.11.3", default-features = false, features = ["blocking"]}
serde = {version = "1.0", features = ["derive"]}
serde_json = {version = "1.0"}

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Patrick Hoffmann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,141 @@
# openweathermap [![Rust](https://github.com/fightling/openweathermap/actions/workflows/rust.yml/badge.svg)](https://github.com/fightling/openweathermap/actions/workflows/rust.yml)
...is a *rust crate* which lets you easily access current weather data from [OpenWeatherMap](https://openweathermap.org/). This is an *unofficial* extension I have made to learn *rust* a little but I hope you have fun with it.
## Contents
<!-- MDTOC maxdepth:6 firsth1:2 numbering:0 flatten:0 bullets:1 updateOnSave:1 -->
- [Contents](#contents)
- [How to use](#how-to-use)
- [Get continuous weather updates](#get-continuous-weather-updates)
- [First: Start polling](#first-start-polling)
- [Then: Get weather updates](#then-get-weather-updates)
- [Nothing New: `None`](#nothing-new-none)
- [Weather Update: `CurrentWeather`](#weather-update-currentweather)
- [Some Error: `Err`](#some-error-err)
- [Get weather just once](#get-weather-just-once)
- [Reference Documentation](#reference-documentation)
- [Links](#links)
- [Website](#website)
- [*github* repository](#github-repository)
- [on *crates.io*](#on-cratesio)
- [License](#license)
<!-- /MDTOC -->
## How to use
First add this crate to your dependencies in you `Cargo.toml` file:
```toml
[dependencies]
openweathermap = "0.2.4"
```
### Get continuous weather updates
Then use the crate in your rust source file by calling `openweathermap::init()` which returns a receiver object.
You can then use this receiver object to call `openweathermap::update()` to get weather updates like in the following example:
```rust
extern crate openweathermap;
use openweathermap::{init,update};
fn main() {
// start our observatory via OWM
let receiver = &init("Berlin,DE", "metric", "en", "<APIKEY>", 10);
loop {
match update(receiver) {
Some(response) => match response {
Ok(current) => println!(
"Today's weather in {} is {}",
current.name.as_str(),
current.weather[0].main.as_str()
),
Err(e) => println!("Could not fetch weather because: {}", e),
},
None => (),
}
}
}
```
#### First: Start polling
`init()` spawns a thread which then will periodically poll *OpenWeatherMap* for the latest current weather report.
You then can use `update()` to ask for it.
#### Then: Get weather updates
There are three possible kinds of result you get from `update()` which you will have to face:
##### Nothing New: `None`
`update()` returns `None` if there is currently no new update available.
Which means: **You wont get any update twice!**
In other words: `update()` is not caching the last weather update for you.
##### Weather Update: `CurrentWeather`
If a new update was downloaded by the polling thread `update()` returns some `CurrentWeather` object.
`CurrentWeather` is a nested `struct` with the already parsed json properties.
Those are well described [here](https://openweathermap.org/current#parameter).
##### Some Error: `Err`
On error `update()` returns some `String` object which includes a brief error description.
Errors may occur...
- initially while **there is no update yet** you will get an `Err` which includes exactly the String `"loading..."` (predefined in `openweathermap::LOADING`).
- if a **server error** response was received (e.g. `401 Unauthorized` if an **invalid API key** was used).
- on **json errors** while parsing the response from *OpenWeatherMap*.
### Get weather just once
If you need the weather just once you may use the method `weather()` which envelopes `init()` and `update()` into one single synchronous or asynchronous call.
After the first successful weather update the spawned thread will stop immediately and you get the result in return.
```rust
extern crate openweathermap;
use openweathermap::blocking::weather;
fn main() {
// start our observatory via OWM
match &weather("Berlin,DE", "metric", "en", "<APIKEY>") {
Ok(current) => println!(
"Today's weather in {} is {}",
current.name.as_str(),
current.weather[0].main.as_str()
),
Err(e) => println!("Could not fetch weather because: {}", e),
}
}
```
There is a *blocking* and a *non-blocking* variant of `weather()`:
- The above example uses the synchronous (*blocking*) variant `openweathermap::blocking::weather` which wont return until there is a new update.
- If you like to deal with the returned *future* by yourself just use `openweathermap::weather` and asynchronously await the result until there is any.
## Reference Documentation
Beside this introduction there is a reference documentation which can be found [here](https://docs.rs/openweathermap).
## Links
### Website
This README tastes better at [openweathermap.thats-software.com](https://openweathermap.thats-software.com).
### *github* repository
For the source code see [this repository](https://github.com/fightling/openweathermap) at *github.com*.
### on *crates.io*
Published at [*crates.io*](https://crates.io/crates/openweathermap).
## License
openweathermap is licensed under the *MIT license* (LICENSE-MIT or http://opensource.org/licenses/MIT)

View File

@ -0,0 +1,132 @@
use serde::Deserialize;
/// Location coordinates
#[derive(Deserialize, Debug)]
pub struct Coord {
/// geo location, longitude
pub lon: f64,
/// geo location, latitude
pub lat: f64,
}
/// Weather condition description
#[derive(Deserialize, Debug)]
pub struct Weather {
/// Weather condition id
pub id: u64,
/// Group of weather parameters (Rain, Snow, Extreme etc.)
pub main: String,
/// Weather condition
pub description: String,
/// Weather icon id
pub icon: String,
}
/// Detailed weather report
#[derive(Deserialize, Debug)]
pub struct Main {
/// Temperature. Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit.
pub temp: f64,
/// Temperature. This temperature parameter accounts for the human perception of weather.
/// Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit.
pub feels_like: f64,
/// Atmospheric pressure (on the sea level, if there is no sea_level or grnd_level data), hPa
pub pressure: f64,
/// Humidity, %
pub humidity: f64,
/// Minimum temperature at the moment.
/// This is minimal currently observed temperature (within large megalopolises and urban areas).
/// Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit.
pub temp_min: f64,
/// Maximum temperature at the moment.
/// This is maximal currently observed temperature (within large megalopolises and urban areas).
/// Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit.
pub temp_max: f64,
/// Atmospheric pressure on the sea level, hPa
pub sea_level: Option<f64>,
/// Atmospheric pressure on the ground level, hPa
pub grnd_level: Option<f64>,
}
/// Detailed wind report
#[derive(Deserialize, Debug)]
pub struct Wind {
/// Wind speed. Unit Default: meter/sec, Metric: meter/sec, Imperial: miles/hour.
pub speed: f64,
/// Wind direction, degrees (meteorological)
pub deg: f64,
/// Wind gust. Unit Default: meter/sec, Metric: meter/sec, Imperial: miles/hour
pub gust: Option<f64>,
}
/// Detailed clouds report
#[derive(Deserialize, Debug)]
pub struct Clouds {
/// Cloudiness, %
pub all: f64,
}
/// Rain or snow volume report
#[derive(Deserialize, Debug)]
pub struct Volume {
/// Volume for the last 1 hour, mm
#[serde(rename = "1h")]
pub h1: Option<f64>,
/// Volume for the last 3 hours, mm
#[serde(rename = "3h")]
pub h3: Option<f64>,
}
/// Additional information
#[derive(Deserialize, Debug)]
pub struct Sys {
/// Internal parameter
#[serde(rename = "type")]
pub type_: Option<u64>,
/// Internal parameter
pub id: Option<u64>,
/// Internal parameter
pub message: Option<f64>,
/// Country code (GB, JP etc.)
pub country: String,
/// Sunrise time, unix, UTC
pub sunrise: i64,
/// Sunset time, unix, UTC
pub sunset: i64,
}
#[derive(Deserialize, Debug)]
/// current weather report in a nested struct
pub struct CurrentWeather {
/// report origin coordinates
pub coord: Coord,
/// vector with one item of weather condition descriptions
pub weather: Vec<Weather>,
/// Internal parameter
pub base: String,
/// detailed weather report
pub main: Main,
/// Visibility, meter
pub visibility: u64,
/// detailed wind report
pub wind: Wind,
/// detailed clouds report
pub clouds: Clouds,
/// detailed rain report
pub rain: Option<Volume>,
/// detailed snow report
pub snow: Option<Volume>,
/// Time of data calculation, unix, UTC
pub dt: i64,
/// additional information
pub sys: Sys,
/// Shift in seconds from UTC
pub timezone: i64,
/// City ID
pub id: u64,
/// City name
pub name: String,
/// Internal parameter
pub cod: u64,
}

View File

@ -0,0 +1,94 @@
use serde::{Deserialize, __private::de};
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Forecast {
pub city: City,
pub cod: String,
pub message: f64,
pub cnt: f64,
pub list: Vec<List>,
}
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct City {
pub id: f64,
pub name: String,
pub coord: Coord,
pub country: String,
pub population: f64,
pub timezone: f64,
pub sunrise: i64,
pub sunset: i64,
}
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Coord {
pub lon: f64,
pub lat: f64,
}
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct List {
pub dt: i64,
pub main: Main,
pub weather: Vec<Weather>,
pub wind: Wind,
pub clouds: Clouds,
pub pop: f64,
pub rain: Option<Rain>,
}
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
pub struct Wind{
pub speed:f64,
pub deg:f64,
pub gust:f64
}
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Rain{
#[serde(rename = "3h")]
pub three_hours:f64
}
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Clouds{
all:f64
}
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
pub struct Main {
pub temp: f64,
pub feels_like: f64,
pub temp_min: f64,
pub temp_max: f64,
pub pressure: f64,
pub sea_level: f64,
pub grnd_level: f64,
pub humidity: f64,
pub temp_kf: f64,
}
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FeelsLike {
pub day: f64,
pub night: f64,
pub eve: f64,
pub morn: f64,
}
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Weather {
pub id: f64,
pub main: String,
pub description: String,
pub icon: String,
}

369
client/openweathermap/src/lib.rs Executable file
View File

@ -0,0 +1,369 @@
extern crate reqwest;
extern crate serde_json;
use forecast::Forecast;
use futures::executor;
use http::StatusCode;
use regex::Regex;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
mod api;
pub mod forecast;
pub use api::*;
#[cfg(test)]
mod tests;
/// Receiver object you get from `init()` and have top handle to `update()`.
pub type Receiver = mpsc::Receiver<Result<CurrentWeather, String>>;
/// Receiver object you get from `init_forecast()` and have top handle to `update()`.
pub type ForecastReceiver = mpsc::Receiver<Result<Forecast, String>>;
/// Loading error messaage you get at the first call of `update()`.
pub const LOADING: &str = "loading...";
/// Spawns a thread which fetches the current weather from
/// [openweathermap.org](https://openweathermap.org) periodically.
/// #### Parameters
/// - `location`: Can be a city name, a city ID or a geographical coordinate:
/// - city name: may be followed by comma separated state code and/or country code (e.g. `"Berlin,DE"`).
/// - city ID: which can be found at [this](https://openweathermap.org/find) where you will get link that includes the ID
/// - e.g. `"2950159"` for Berlin, Germany
/// - coordinates: given by comma separated latitude and longitude (e.g. `"52.5244,13.4105"`). |
/// - `units`: One of the following:
/// - `"metric"`: meters, m/s, °C, etc.
/// - `"imperial"`: miles, mi/s, °F, etc.
/// - `"standard"`: meters, m/s, K, etc.
/// - `lang`: Language code:
/// - `"en"`: for English
/// - `"de"`: for German
/// - see [this list](https://openweathermap.org/current#multi) for all available language codes
/// - `api_key`: Your API key which you can get [here](https://openweathermap.org/price)
/// - `poll_mins`: Update interval:
/// - `> 0`: duration of poll period in minutes (`10` is recommended)
/// - `= 0`: thread will terminate after the first successful update.
/// #### Return value
/// - `openweathermap::Receiver`: Handle this to `openweathermap::update()` to get the latest weather update.
///
/// The return value is a `mpsc` *channel receiver*:
/// ```rust
/// pub type Receiver = std::sync::mpsc::Receiver<Result<openweathermap::CurrentWeather, String>>;
/// ```
pub fn init(location: &str, units: &str, lang: &str, api_key: &str, poll_mins: u64) -> Receiver {
// generate correct request URL depending on city is id or name
let url = match location.parse::<u64>().is_ok() {
true => format!(
"http://api.openweathermap.org/data/2.5/weather?id={}&units={}&lang={}&appid={}",
location, units, lang, api_key
),
false => {
let re = Regex::new(r"(-?\d+\.\d+)\s*,\s*(-?\d+\.\d+)").unwrap();
match re.captures(&location) {
Some(caps) => format!("http://api.openweathermap.org/data/2.5/weather?lat={}&lon={}&units={}&lang={}&appid={}",
caps.get(1).unwrap().as_str(), caps.get(2).unwrap().as_str(), units, lang, api_key ),
None => format!(
"http://api.openweathermap.org/data/2.5/weather?q={}&units={}&lang={}&appid={}",
location, units, lang, api_key ),
}
}
};
// fork thread that continuously fetches weather updates every <poll_mins> minutes
let period = Duration::from_secs(60 * poll_mins);
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send(Err(LOADING.to_string())).unwrap_or(());
loop {
match reqwest::blocking::get(&url) {
Ok(response) => match response.status() {
StatusCode::OK => match serde_json::from_str(&response.text().unwrap()) {
Ok(w) => {
tx.send(Ok(w)).unwrap_or(());
if period == Duration::new(0, 0) {
break;
}
thread::sleep(period);
}
Err(e) => tx.send(Err(e.to_string())).unwrap_or(()),
},
_ => tx.send(Err(response.status().to_string())).unwrap_or(()),
},
Err(_e) => (),
}
}
});
// return receiver that provides the updated weather as json string
return rx;
}
/// Spawns a thread which fetches the forecast from
/// [openweathermap.org](https://openweathermap.org) periodically.
/// #### Parameters
/// - `location`: Can be a city name, a city ID or a geographical coordinate:
/// - city name: may be followed by comma separated state code and/or country code (e.g. `"Berlin,DE"`).
/// - city ID: which can be found at [this](https://openweathermap.org/find) where you will get link that includes the ID
/// - e.g. `"2950159"` for Berlin, Germany
/// - coordinates: given by comma separated latitude and longitude (e.g. `"52.5244,13.4105"`). |
/// - `units`: One of the following:
/// - `"metric"`: meters, m/s, °C, etc.
/// - `"imperial"`: miles, mi/s, °F, etc.
/// - `"standard"`: meters, m/s, K, etc.
/// - `lang`: Language code:
/// - `"en"`: for English
/// - `"de"`: for German
/// - see [this list](https://openweathermap.org/current#multi) for all available language codes
/// - `api_key`: Your API key which you can get [here](https://openweathermap.org/price)
/// - `poll_mins`: Update interval:
/// - `> 0`: duration of poll period in minutes (`10` is recommended)
/// - `= 0`: thread will terminate after the first successful update.
/// #### Return value
/// - `openweathermap::Receiver`: Handle this to `openweathermap::update()` to get the latest weather update.
///
/// The return value is a `mpsc` *channel receiver*:
/// ```rust
/// pub type Receiver = std::sync::mpsc::Receiver<Result<openweathermap::CurrentWeather, String>>;
/// ```
pub fn init_forecast(
location: &str,
units: &str,
lang: &str,
api_key: &str,
poll_mins: u64,
days: u32,
) -> ForecastReceiver {
// generate correct request URL depending on city is id or name
let url = match location.parse::<u64>().is_ok() {
true => format!(
"http://api.openweathermap.org/data/2.5/forecast?id={}&units={}&lang={}&appid={}&cnt={}",
location, units, lang, api_key, days
),
false => {
let re = Regex::new(r"(-?\d+\.\d+)\s*,\s*(-?\d+\.\d+)").unwrap();
match re.captures(&location) {
Some(caps) => format!("http://api.openweathermap.org/data/2.5/forecast?lat={}&lon={}&units={}&lang={}&appid={}&cnt={}",
caps.get(1).unwrap().as_str(), caps.get(2).unwrap().as_str(), units, lang, api_key, days ),
None => format!(
"http://api.openweathermap.org/data/2.5/forecast?q={}&units={}&lang={}&appid={}&cnt={}",
location, units, lang, api_key, days ),
}
}
};
// fork thread that continuously fetches weather updates every <poll_mins> minutes
let period = Duration::from_secs(60 * poll_mins);
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send(Err(LOADING.to_string())).unwrap_or(());
loop {
//let fake: Result<Forecast, serde_json::Error> = serde_json::from_str(include_str!("example.json"));
//tx.send(Result::Ok(fake.unwrap())).unwrap();
match reqwest::blocking::get(&url) {
Ok(response) => match response.status() {
StatusCode::OK => match serde_json::from_str(&response.text().unwrap()) {
Ok(w) => {
tx.send(Ok(w)).unwrap_or(());
if period == Duration::new(0, 0) {
break;
}
thread::sleep(period);
}
Err(e) => tx.send(Err(e.to_string())).unwrap_or(()),
},
_ => tx.send(Err(response.status().to_string())).unwrap_or(()),
},
Err(_e) => (),
}
}
});
// return receiver that provides the updated weather as json string
return rx;
}
/// Get current weather update that the spawned thread could fetched.
/// #### Parameters
/// - `receiver`: the *channel receiver* from preceded call to `openweathermap::init()`
/// #### Returng value
/// - ⇒ `None`: No update available
/// - ⇒ `Some(Result)`: Update available
/// - ⇒ `Ok(CurrentWeather)`: Weather information in a nested struct called `CurrentWeather`
/// (see also [*OpenWeatherMap* documentation](https://openweathermap.org/current#parameter) for details)
/// - ⇒ `Err(String)`: Error message about any occured http or json issue
/// - e.g. `401 Unauthorized`: if your API key is invalid
/// - some json parser error message if response from OpenWeatherMap could not be parsed
pub fn update(receiver: &Receiver) -> Option<Result<CurrentWeather, String>> {
match receiver.try_recv() {
Ok(response) => Some(response),
Err(_e) => None,
}
}
pub fn update_forecast(receiver: &ForecastReceiver) -> Option<Result<Forecast, String>> {
match receiver.try_recv() {
Ok(response) => Some(response),
Err(_e) => None,
}
}
/// Fetch current weather update once and stop thread immediately after success.
/// Returns the result in a *future*.
/// #### Parameters
/// - `location`: Can be a city name, a city ID or a geographical coordinate:
/// - city name: may be followed by comma separated state code and/or country code (e.g. `"Berlin,DE"`).
/// - city ID: which can be found at [this](https://openweathermap.org/find) where you will get link that includes the ID
/// - e.g. `"2950159"` for Berlin, Germany
/// - coordinates: given by comma separated latitude and longitude (e.g. `"52.5244,13.4105"`). |
/// - `units`: One of the following:
/// - `"metric"`: meters, m/s, °C, etc.
/// - `"imperial"`: miles, mi/s, °F, etc.
/// - `"standard"`: meters, m/s, K, etc.
/// - `lang`: Language code:
/// - `"en"`: for English
/// - `"de"`: for German
/// - see [this list](https://openweathermap.org/current#multi) for all available language codes
/// - `api_key`: Your API key which you can get [here](https://openweathermap.org/price)
/// #### Return value
/// - ⇒ `Ok(CurrentWeather)`: weather information in a nested struct called `CurrentWeather`
/// (see also [*OpenWeatherMap* documentation](https://openweathermap.org/current#parameter) for details)
/// - ⇒ `Err(String)`: Error message about any occured http or json issue
/// - e.g. `401 Unauthorized` if your API key is invalid
/// - some json parser error message if response from OpenWeatherMap could not be parsed
pub async fn weather(
location: &str,
units: &str,
lang: &str,
api_key: &str,
) -> Result<CurrentWeather, String> {
let r = init(location, units, lang, api_key, 0);
loop {
match update(&r) {
Some(response) => match response {
Ok(current) => return Ok(current),
Err(e) => {
if e != LOADING {
return Err(e);
}
}
},
None => (),
}
}
}
/// Fetch forecast weather update once and stop thread immediately after success.
/// Returns the result in a *future*.
/// #### Parameters
/// - `location`: Can be a city name, a city ID or a geographical coordinate:
/// - city name: may be followed by comma separated state code and/or country code (e.g. `"Berlin,DE"`).
/// - city ID: which can be found at [this](https://openweathermap.org/find) where you will get link that includes the ID
/// - e.g. `"2950159"` for Berlin, Germany
/// - coordinates: given by comma separated latitude and longitude (e.g. `"52.5244,13.4105"`). |
/// - `units`: One of the following:
/// - `"metric"`: meters, m/s, °C, etc.
/// - `"imperial"`: miles, mi/s, °F, etc.
/// - `"standard"`: meters, m/s, K, etc.
/// - `lang`: Language code:
/// - `"en"`: for English
/// - `"de"`: for German
/// - see [this list](https://openweathermap.org/current#multi) for all available language codes
/// - `api_key`: Your API key which you can get [here](https://openweathermap.org/price)
/// #### Return value
/// - ⇒ `Ok(Forecast)`: weather information in a nested struct called `CurrentWeather`
/// (see also [*OpenWeatherMap* documentation](https://openweathermap.org/current#parameter) for details)
/// - ⇒ `Err(String)`: Error message about any occured http or json issue
/// - e.g. `401 Unauthorized` if your API key is invalid
/// - some json parser error message if response from OpenWeatherMap could not be parsed
pub async fn forecast(
location: &str,
units: &str,
lang: &str,
api_key: &str,
days: u32,
) -> Result<Forecast, String> {
let r = init_forecast(location, units, lang, api_key, 0, days);
loop {
match update_forecast(&r) {
Some(response) => match response {
Ok(forecast) => return Ok(forecast),
Err(e) => {
if e != LOADING {
return Err(e);
}
}
},
None => (),
}
}
}
/// synchronous functions
pub mod blocking {
use super::*;
/// Fetches a weather update once and stops the thread immediately after success then returns the update.
/// #### Parameters
/// - `location`: Can be a city name, a city ID or a geographical coordinate:
/// - city name may be followed by comma separated state code and/or country code (e.g. `"Berlin,DE"`).
/// - city ID which can be found at [this](https://openweathermap.org/find) where you will get link that includes the ID
/// - e.g. `"2950159"` for Berlin, Germany
/// - coordinates given by comma separated latitude and longitude (e.g. `"52.5244,13.4105"`). |
/// - `units`: One of the following:
/// - `"metric"`: meters, m/s, °C, etc.
/// - `"imperial"`: miles, mi/s, °F, etc.
/// - `"standard"`: meters, m/s, K, etc.
/// - `lang`: Language code:
/// - `"en"`: for English
/// - `"de"`: for German
/// - see [this list](https://openweathermap.org/current#multi) for all available language codes
/// - `api_key`: Your API key which you can get [here](https://openweathermap.org/price)
/// #### Return value
/// - ⇒ `Ok(CurrentWeather)`: weather information in a nested struct called `CurrentWeather`
/// (see also [*OpenWeatherMap* documentation](https://openweathermap.org/current#parameter) for details)
/// - ⇒ `Err(String)`: Error message about any occured http or json issue
/// - e.g. `401 Unauthorized` if your API key is invalid
/// - some json parser error message if response from OpenWeatherMap could not be parsed
pub fn weather(
location: &str,
units: &str,
lang: &str,
api_key: &str,
) -> Result<CurrentWeather, String> {
// wait for result
executor::block_on(super::weather(location, units, lang, api_key))
}
/// Fetches a weather update once and stops the thread immediately after success then returns the update.
/// #### Parameters
/// - `location`: Can be a city name, a city ID or a geographical coordinate:
/// - city name may be followed by comma separated state code and/or country code (e.g. `"Berlin,DE"`).
/// - city ID which can be found at [this](https://openweathermap.org/find) where you will get link that includes the ID
/// - e.g. `"2950159"` for Berlin, Germany
/// - coordinates given by comma separated latitude and longitude (e.g. `"52.5244,13.4105"`). |
/// - `units`: One of the following:
/// - `"metric"`: meters, m/s, °C, etc.
/// - `"imperial"`: miles, mi/s, °F, etc.
/// - `"standard"`: meters, m/s, K, etc.
/// - `lang`: Language code:
/// - `"en"`: for English
/// - `"de"`: for German
/// - see [this list](https://openweathermap.org/current#multi) for all available language codes
/// - `api_key`: Your API key which you can get [here](https://openweathermap.org/price)
/// #### Return value
/// - ⇒ `Ok(CurrentWeather)`: weather information in a nested struct called `CurrentWeather`
/// (see also [*OpenWeatherMap* documentation](https://openweathermap.org/current#parameter) for details)
/// - ⇒ `Err(String)`: Error message about any occured http or json issue
/// - e.g. `401 Unauthorized` if your API key is invalid
/// - some json parser error message if response from OpenWeatherMap could not be parsed
pub fn forecast(
location: &str,
units: &str,
lang: &str,
api_key: &str,
days: u32,
) -> Result<Forecast, String> {
// wait for result
executor::block_on(super::forecast(location, units, lang, api_key, days))
}
}

File diff suppressed because it is too large Load Diff