include modified weatherapi, use forcast, use worst rain detection
@ -1,13 +1,7 @@
|
|||||||
[package]
|
[workspace]
|
||||||
name = "LEDboardClient"
|
members = [
|
||||||
version = "0.1.0"
|
"openweathermap",
|
||||||
edition = "2021"
|
"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 bit::BitIndex;
|
||||||
|
use chrono_tz::Europe::Berlin;
|
||||||
|
use chrono::{DateTime, NaiveDateTime, Utc, Timelike};
|
||||||
|
use openweathermap::{forecast::{Weather, List}};
|
||||||
use substring::Substring;
|
use substring::Substring;
|
||||||
use tinybmp::Bmp;
|
use tinybmp::Bmp;
|
||||||
use core::time;
|
use core::time;
|
||||||
use embedded_graphics::{
|
use embedded_graphics::{
|
||||||
image::{Image, ImageRaw},
|
image::{Image},
|
||||||
mono_font::{iso_8859_1::FONT_6X10, MonoTextStyle},
|
mono_font::{iso_8859_1::FONT_6X10, MonoTextStyle},
|
||||||
pixelcolor::{BinaryColor, Rgb565},
|
pixelcolor::{BinaryColor},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
primitives::{Line, PrimitiveStyle},
|
primitives::{PrimitiveStyle},
|
||||||
text::Text,
|
text::Text,
|
||||||
};
|
};
|
||||||
use openweathermap::{self, CurrentWeather, Weather};
|
|
||||||
use std::net::UdpSocket;
|
use std::{net::UdpSocket, time::{Instant, Duration}};
|
||||||
use std::{env, sync::mpsc::Receiver, thread};
|
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_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;
|
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<Forecast, String>>){
|
||||||
|
|
||||||
fn renderWeather(display: &mut UdpDisplay ,data: &Option<Result<CurrentWeather, String>>){
|
|
||||||
let text_style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On);
|
let text_style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On);
|
||||||
|
|
||||||
match data {
|
match data {
|
||||||
@ -123,47 +124,44 @@ fn renderWeather(display: &mut UdpDisplay ,data: &Option<Result<CurrentWeather,
|
|||||||
println!("{}", &error);
|
println!("{}", &error);
|
||||||
}
|
}
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
if(!result.weather.is_empty()){
|
if !result.list.is_empty() {
|
||||||
let condition = &result.weather[0];
|
let mut max:f64 = 0_f64;
|
||||||
let short_icon_code = condition.icon.substring(0,2);
|
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);
|
println!("Weather info: {} desc: {} icon {}", condition.main, condition.description, condition.icon);
|
||||||
|
|
||||||
match short_icon_code {
|
renderWeatherIcon(&condition, display);
|
||||||
"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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -174,12 +172,52 @@ fn renderWeather(display: &mut UdpDisplay ,data: &Option<Result<CurrentWeather,
|
|||||||
println!("{}", "no result");
|
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];
|
let mut package: [u8; PACKAGE_LENGTH] = [0; PACKAGE_LENGTH];
|
||||||
|
|
||||||
// Brightness
|
// Brightness
|
||||||
@ -228,33 +266,31 @@ fn main() {
|
|||||||
// one argument passed
|
// one argument passed
|
||||||
2 => {
|
2 => {
|
||||||
let ip = &args[1];
|
let ip = &args[1];
|
||||||
let receiver = openweathermap::init(
|
let receiver = openweathermap::init_forecast("Mannheim",
|
||||||
"Mannheim",
|
|
||||||
"metric",
|
"metric",
|
||||||
"de",
|
"de",
|
||||||
"978882ab9dd05e7122ff2b0aef2d3e55",
|
"978882ab9dd05e7122ff2b0aef2d3e55",
|
||||||
60,
|
60,1);
|
||||||
);
|
|
||||||
|
|
||||||
let mut lastData = Option::None;
|
let mut last_data = Option::None;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let delay = time::Duration::from_millis(10000);
|
let delay = time::Duration::from_millis(10000);
|
||||||
thread::sleep(delay);
|
thread::sleep(delay);
|
||||||
let answer = openweathermap::update(&receiver);
|
let answer = openweathermap::update_forecast(&receiver);
|
||||||
|
|
||||||
match answer {
|
match answer {
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
lastData = answer;
|
last_data = answer;
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
send_package(ip.to_string(), &lastData);
|
send_package(ip.to_string(), &last_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// all the other cases
|
// 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))
|
||||||
|
}
|
||||||
|
}
|