Purge collectors and info page
This commit is contained in:
@@ -6,17 +6,6 @@ persistence_dir = "/tmp/"
|
||||
#username = "user"
|
||||
#password = "password"
|
||||
|
||||
[collectors]
|
||||
markdown_web_links = [
|
||||
"https://example.org/lmao.md"
|
||||
]
|
||||
|
||||
#weatherapi_key = ""
|
||||
#weatherapi_locations = [
|
||||
# "London",
|
||||
#]
|
||||
|
||||
|
||||
[[bulbs]]
|
||||
id = "light/bedroom"
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
mod markdown_web;
|
||||
mod weatherapi;
|
||||
|
||||
pub use markdown_web::MarkdownWeb;
|
||||
pub use weatherapi::WeatherApi;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait Collector {
|
||||
async fn collect(&mut self) -> anyhow::Result<String>;
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CollectorConfig {
|
||||
pub markdown_web_links: Vec<String>,
|
||||
|
||||
pub weatherapi_key: Option<String>,
|
||||
pub weatherapi_locations: Vec<String>,
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
use crate::collector::Collector;
|
||||
use reqwest::get;
|
||||
|
||||
pub struct MarkdownWeb {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Collector for MarkdownWeb {
|
||||
async fn collect(&mut self) -> anyhow::Result<String> {
|
||||
let text = get(&self.url).await?.text().await?;
|
||||
let html = markdown::to_html(&text);
|
||||
|
||||
Ok(html)
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
use crate::collector::Collector;
|
||||
use chrono::{serde::ts_seconds, DateTime, Utc};
|
||||
use reqwest::get;
|
||||
use serde::Deserialize;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Response {
|
||||
current: CurrentBody,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
struct Condition {
|
||||
text: String,
|
||||
icon: String,
|
||||
code: i64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
struct CurrentBody {
|
||||
#[serde(with = "ts_seconds")]
|
||||
last_updated_epoch: DateTime<Utc>,
|
||||
last_updated: String,
|
||||
temp_c: f64,
|
||||
is_day: i8,
|
||||
condition: Condition,
|
||||
wind_kph: f64,
|
||||
wind_degree: f64,
|
||||
wind_dir: String,
|
||||
pressure_mb: f64,
|
||||
precip_mm: f64,
|
||||
humidity: f64,
|
||||
cloud: f64,
|
||||
feelslike_c: f64,
|
||||
vis_km: f64,
|
||||
uv: f64,
|
||||
gust_kph: f64,
|
||||
}
|
||||
|
||||
pub struct WeatherApi {
|
||||
pub api_key: String,
|
||||
pub location: String,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Collector for WeatherApi {
|
||||
async fn collect(&mut self) -> anyhow::Result<String> {
|
||||
let path = "https://api.weatherapi.com/v1/current.json";
|
||||
let api_key = &self.api_key;
|
||||
let location = &self.location;
|
||||
let url = format!("{path}?key={api_key}&q={location}&aqi=no");
|
||||
let Response {
|
||||
current:
|
||||
CurrentBody {
|
||||
last_updated_epoch,
|
||||
temp_c,
|
||||
feelslike_c,
|
||||
wind_kph,
|
||||
precip_mm,
|
||||
humidity,
|
||||
cloud,
|
||||
..
|
||||
},
|
||||
..
|
||||
} = get(&url).await?.json().await?;
|
||||
|
||||
let wind_mps = (wind_kph / 3.6).round();
|
||||
let wind = wind_speed_to_beaufort(wind_mps);
|
||||
|
||||
let temp = if feelslike_c != temp_c {
|
||||
format!("**{temp_c}°** (känns som {feelslike_c}°)")
|
||||
} else {
|
||||
format!("**{temp_c}°**")
|
||||
};
|
||||
|
||||
let time_of_day = last_updated_epoch.naive_local().format("%H:%M");
|
||||
|
||||
let markdown = format!(
|
||||
r#"
|
||||
# Väder
|
||||
|
||||
## {location} kl {time_of_day}
|
||||
|
||||
{temp}. **{wind}** ({wind_mps} m/s).
|
||||
|
||||
{cloud}% molntäcke, {precip_mm} mm regn, {humidity}% luftfuktighet.
|
||||
|
||||
"#
|
||||
);
|
||||
|
||||
let html = markdown::to_html(markdown.trim());
|
||||
|
||||
Ok(html)
|
||||
}
|
||||
}
|
||||
|
||||
/// Wind speed expressed on the beaufort scale
|
||||
struct BeaufortScale(u8);
|
||||
|
||||
impl Display for BeaufortScale {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let name = match self {
|
||||
BeaufortScale(0) => "Lugnt",
|
||||
BeaufortScale(1 | 2) => "Svag vind",
|
||||
BeaufortScale(3 | 4) => "Måttlig vind",
|
||||
BeaufortScale(5 | 6) => "Frisk vind",
|
||||
BeaufortScale(7 | 8) => "Hård vind",
|
||||
BeaufortScale(9) => "Mycket hård vind",
|
||||
BeaufortScale(10) => "Storm",
|
||||
BeaufortScale(11) => "Svår storm",
|
||||
BeaufortScale(12..) => "Orkan",
|
||||
};
|
||||
|
||||
write!(f, "{name}")
|
||||
}
|
||||
}
|
||||
|
||||
fn wind_speed_to_beaufort(mps: f64) -> BeaufortScale {
|
||||
let beaufort_wind_speeds = [
|
||||
0.0..0.3,
|
||||
0.3..1.6,
|
||||
1.6..3.4,
|
||||
3.4..5.5,
|
||||
5.5..8.8,
|
||||
8.0..10.8,
|
||||
10.8..13.9,
|
||||
13.9..17.2,
|
||||
17.2..20.8,
|
||||
20.8..24.5,
|
||||
24.5..28.5,
|
||||
28.5..32.7,
|
||||
32.7..37.0,
|
||||
37.0..41.5,
|
||||
41.5..46.2,
|
||||
46.2..51.0,
|
||||
51.0..56.1,
|
||||
56.1..61.3,
|
||||
];
|
||||
|
||||
let mps = mps.max(0.0);
|
||||
|
||||
let index = beaufort_wind_speeds
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(i, range)| range.contains(&mps).then_some(i))
|
||||
.unwrap_or(beaufort_wind_speeds.len());
|
||||
|
||||
BeaufortScale(index as u8)
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
mod collector;
|
||||
mod persistence;
|
||||
mod tasks;
|
||||
mod util;
|
||||
|
||||
use clap::Parser;
|
||||
use collector::CollectorConfig;
|
||||
use common::{BulbMap, ClientMessage, ServerMessage};
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use lighter_manager::{manager::BulbsConfig, mqtt_conf::MqttConfig};
|
||||
@@ -47,8 +45,6 @@ struct Opt {
|
||||
pub struct Config {
|
||||
mqtt: MqttConfig,
|
||||
|
||||
collectors: CollectorConfig,
|
||||
|
||||
persistence_dir: Option<PathBuf>,
|
||||
|
||||
#[serde(flatten)]
|
||||
@@ -104,7 +100,6 @@ async fn main() {
|
||||
let state = Box::leak(Box::new(state));
|
||||
|
||||
task::spawn(tasks::lights_task(state));
|
||||
task::spawn(tasks::info_task(state));
|
||||
|
||||
let ws = warp::path("ws")
|
||||
// The `ws()` filter will prepare the Websocket handshake.
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use common::ServerMessage;
|
||||
use tokio::time::sleep;
|
||||
|
||||
use crate::{
|
||||
collector::{Collector, MarkdownWeb, WeatherApi},
|
||||
State,
|
||||
};
|
||||
|
||||
pub async fn info_task(state: &State) {
|
||||
let mut collectors: Vec<Box<dyn Collector + Send>> = vec![];
|
||||
|
||||
for url in &state.config.collectors.markdown_web_links {
|
||||
collectors.push(Box::new(MarkdownWeb {
|
||||
url: url.to_string(),
|
||||
}));
|
||||
}
|
||||
|
||||
if !state.config.collectors.weatherapi_locations.is_empty() {
|
||||
let api_key = state
|
||||
.config
|
||||
.collectors
|
||||
.weatherapi_key
|
||||
.as_deref()
|
||||
.expect("Missing weatherapi_key");
|
||||
|
||||
for location in state.config.collectors.weatherapi_locations.iter().cloned() {
|
||||
collectors.push(Box::new(WeatherApi {
|
||||
api_key: api_key.to_string(),
|
||||
location,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
let mut collectors = collectors.into_boxed_slice();
|
||||
|
||||
if collectors.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let server_message = &state.server_message;
|
||||
|
||||
let collectors_len = collectors.len();
|
||||
let next = move |i: usize| (i + 1) % collectors_len;
|
||||
let mut i = 0;
|
||||
|
||||
loop {
|
||||
sleep(Duration::from_secs(30)).await;
|
||||
|
||||
// don't bother collecting if no clients are connected
|
||||
// there is always 1 receiver held by main process
|
||||
if server_message.receiver_count() <= 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
i = next(i);
|
||||
let collector = &mut collectors[i];
|
||||
|
||||
let msg = match collector.collect().await {
|
||||
Ok(html) => ServerMessage::InfoPage { html },
|
||||
Err(e) => {
|
||||
warn!("collector error: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = server_message.send(msg) {
|
||||
error!("broadcast channel error: {e}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
pub mod info;
|
||||
pub mod lights;
|
||||
|
||||
pub use info::info_task;
|
||||
pub use lights::lights_task;
|
||||
|
||||
Reference in New Issue
Block a user