Initial Version
This commit is contained in:
Generated
+2338
File diff suppressed because it is too large
Load Diff
+18
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "weather"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
trpl = "0.3.0"
|
||||
scraper = "0.27.0"
|
||||
tokio = { version = "1.52.3", features = ["rt", "rt-multi-thread", "macros"] }
|
||||
reqwest = { version = "0.13.4", default-features = false }
|
||||
rumqttc = "0.25.1"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_derive = "1.0.228"
|
||||
serde_json = "1.0.149"
|
||||
|
||||
# Raspberrypi
|
||||
# cross build --release --target aarch64-unknown-linux-gnu
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
cargo clean
|
||||
cross build --release --target aarch64-unknown-linux-gnu
|
||||
rsync -e "ssh" target/aarch64-unknown-linux-gnu/release/weather pi:/data/rust/weather
|
||||
cargo clean
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
mod weather_data;
|
||||
|
||||
use crate::weather_data::{DwdWeather, MucWeather};
|
||||
use rumqttc::{AsyncClient, EventLoop, MqttOptions, QoS};
|
||||
use scraper::{Html as HtmlScraper, Selector};
|
||||
use std::time::Duration;
|
||||
use tokio::{task, time};
|
||||
|
||||
const METEO_MUNICH_API_URL: &str =
|
||||
"https://www.meteorologie.lmu.de/~quicklooks/aktuelle_messwerte/messwerte_stadt.html";
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() {
|
||||
let (client, mut eventloop) = create_conn();
|
||||
|
||||
match meteo_weather(48.163142, 11.542922).await {
|
||||
Ok(weather) => {
|
||||
task::spawn(async move {
|
||||
publish(client.clone(), &weather).await;
|
||||
time::sleep(Duration::from_secs(3)).await;
|
||||
});
|
||||
|
||||
let mut count = 0;
|
||||
loop {
|
||||
let event = eventloop.poll().await;
|
||||
match &event {
|
||||
Ok(_) => {
|
||||
if count == 10 {
|
||||
break;
|
||||
}
|
||||
count = count + 1;
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error = {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn meteo_weather(latitude: f64, longitude: f64) -> Result<MucWeather, reqwest::Error> {
|
||||
let response = trpl::get(METEO_MUNICH_API_URL).await;
|
||||
let response_text = response.text().await;
|
||||
|
||||
let fragment = HtmlScraper::parse_fragment(&response_text);
|
||||
let mut temperature = 999.0;
|
||||
let mut humidity = 0.0;
|
||||
let mut pressure = 0.0;
|
||||
let mut precipitation = 0.0;
|
||||
|
||||
let profilwerte = Selector::parse("#messwerte-profilwerte").unwrap();
|
||||
let winddruck = Selector::parse("#messwerte-winddruck").unwrap();
|
||||
let niederschlag = Selector::parse("#messwerte-niederschlag").unwrap();
|
||||
let row_selector = Selector::parse("tr").unwrap();
|
||||
let cell_selector = Selector::parse("td").unwrap();
|
||||
|
||||
if let Some(table) = fragment.select(&profilwerte).next() {
|
||||
for row in table.select(&row_selector) {
|
||||
let mut row_data = Vec::new();
|
||||
for cell in row.select(&cell_selector) {
|
||||
let text = cell.text().collect::<Vec<_>>().join(" ");
|
||||
row_data.push(text.trim().to_string());
|
||||
}
|
||||
if row_data[0] == "Lufttemperatur" {
|
||||
temperature = row_data[1].replace(" °C", "").trim().parse::<f64>().unwrap();
|
||||
}
|
||||
if row_data[0] == "Relative Feuchte" {
|
||||
humidity = row_data[1].replace(" %", "").trim().parse::<f64>().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(table) = fragment.select(&winddruck).next() {
|
||||
for row in table.select(&row_selector) {
|
||||
let mut row_data = Vec::new();
|
||||
for cell in row.select(&cell_selector) {
|
||||
let text = cell.text().collect::<Vec<_>>().join(" ");
|
||||
row_data.push(text.trim().to_string());
|
||||
}
|
||||
if row_data[0] == "Luftdruck NN" {
|
||||
pressure = row_data[1].replace(" hPa", "").trim().parse::<f64>().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(table) = fragment.select(&niederschlag).next() {
|
||||
for row in table.select(&row_selector) {
|
||||
let mut row_data = Vec::new();
|
||||
for cell in row.select(&cell_selector) {
|
||||
let text = cell.text().collect::<Vec<_>>().join(" ");
|
||||
row_data.push(text.trim().to_string());
|
||||
}
|
||||
if row_data.len() > 1 && row_data[1].contains(" mm") {
|
||||
precipitation = row_data[1].replace(" mm", "").trim().parse::<f64>().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if temperature == 999.0 {
|
||||
let url = format!(
|
||||
"https://api.brightsky.dev/current_weather?lat={}&lon={}",
|
||||
latitude, longitude
|
||||
);
|
||||
println!("Fetching {}", url);
|
||||
let result = trpl::get(&url).await.text().await;
|
||||
let dwd_weather: DwdWeather = serde_json::from_str(&result).unwrap();
|
||||
temperature = dwd_weather.weather.temperature.unwrap();
|
||||
pressure = dwd_weather.weather.pressure_msl;
|
||||
humidity = dwd_weather.weather.relative_humidity as f64;
|
||||
precipitation = dwd_weather.weather.precipitation;
|
||||
}
|
||||
|
||||
let weather = MucWeather {
|
||||
temperature,
|
||||
pressure,
|
||||
humidity,
|
||||
precipitation,
|
||||
};
|
||||
println!("Zeile: {:?}", weather);
|
||||
Ok(weather)
|
||||
}
|
||||
|
||||
async fn publish(client: AsyncClient, muc: &MucWeather) {
|
||||
// Serialize it to a JSON string.
|
||||
let json = serde_json::to_string(&muc);
|
||||
client
|
||||
.publish("munich/weather", QoS::AtLeastOnce, false, json.unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
client
|
||||
.publish(
|
||||
"munich/temperature",
|
||||
QoS::AtLeastOnce,
|
||||
false,
|
||||
muc.temperature.to_string(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
client
|
||||
.publish(
|
||||
"munich/pressure",
|
||||
QoS::AtLeastOnce,
|
||||
false,
|
||||
muc.pressure.to_string(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
client
|
||||
.publish(
|
||||
"munich/humidity",
|
||||
QoS::AtLeastOnce,
|
||||
false,
|
||||
muc.humidity.to_string(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
client
|
||||
.publish(
|
||||
"munich/precipitation",
|
||||
QoS::AtLeastOnce,
|
||||
false,
|
||||
muc.precipitation.to_string(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
time::sleep(Duration::from_secs(2)).await;
|
||||
}
|
||||
|
||||
fn create_conn() -> (AsyncClient, EventLoop) {
|
||||
let mut mqttoptions = MqttOptions::new("rust-mqqt", "192.168.1.37", 1883);
|
||||
mqttoptions
|
||||
.set_keep_alive(Duration::from_secs(5))
|
||||
.set_manual_acks(false)
|
||||
.set_credentials("mqtt", "delta32#")
|
||||
.set_clean_session(false);
|
||||
AsyncClient::new(mqttoptions, 10)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
pub struct MucWeather {
|
||||
#[serde(rename = "Temperature")]
|
||||
pub(crate) temperature: f64,
|
||||
#[serde(rename = "Humidity")]
|
||||
pub(crate) humidity: f64,
|
||||
#[serde(rename = "Pressure")]
|
||||
pub(crate) pressure: f64,
|
||||
#[serde(rename = "Precipitation")]
|
||||
pub(crate) precipitation: f64,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DwdWeather {
|
||||
pub weather: Weather,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Weather {
|
||||
#[serde(rename = "source_id")]
|
||||
pub source_id: i64,
|
||||
pub timestamp: String,
|
||||
#[serde(rename = "cloud_cover")]
|
||||
pub cloud_cover: i64,
|
||||
pub condition: String,
|
||||
#[serde(rename = "dew_point")]
|
||||
pub dew_point: Option<f64>,
|
||||
#[serde(rename = "solar_60")]
|
||||
pub solar: Option<f64>,
|
||||
#[serde(rename = "precipitation_60")]
|
||||
pub precipitation: f64,
|
||||
#[serde(rename = "pressure_msl")]
|
||||
pub pressure_msl: f64,
|
||||
#[serde(rename = "relative_humidity")]
|
||||
pub relative_humidity: i64,
|
||||
pub visibility: i64,
|
||||
#[serde(rename = "wind_direction_60")]
|
||||
pub wind_direction: i64,
|
||||
#[serde(rename = "wind_speed_60")]
|
||||
pub wind_speed: Option<f64>,
|
||||
#[serde(rename = "wind_gust_direction_60")]
|
||||
pub wind_gust_direction: i64,
|
||||
#[serde(rename = "wind_gust_speed_60")]
|
||||
pub wind_gust_speed: Option<f64>,
|
||||
#[serde(rename = "sunshine_60")]
|
||||
pub sunshine: Option<f64>,
|
||||
pub temperature: Option<f64>,
|
||||
pub icon: String,
|
||||
}
|
||||
Reference in New Issue
Block a user