include modified weatherapi, use forcast, use worst rain detection
@ -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
@ -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"
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
@ -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
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
1
client/openweathermap/CNAME
Normal file
@ -0,0 +1 @@
|
||||
openweathermap.thats-software.com
|
22
client/openweathermap/Cargo.toml
Normal 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"}
|
21
client/openweathermap/LICENSE
Normal 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.
|
141
client/openweathermap/README.md
Normal 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)
|
132
client/openweathermap/src/api.rs
Normal 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,
|
||||
}
|
||||
|
94
client/openweathermap/src/forecast.rs
Normal 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
@ -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))
|
||||
}
|
||||
}
|