Purge collectors and info page

This commit is contained in:
2026-05-15 15:46:42 +02:00
parent 428e75488d
commit ba6ce926fc
15 changed files with 26 additions and 356 deletions

45
Cargo.lock generated
View File

@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "addr2line" name = "addr2line"
@@ -1304,6 +1304,12 @@ dependencies = [
"untrusted", "untrusted",
] ]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.17" version = "1.0.17"
@@ -1835,26 +1841,14 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.87" version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd"
dependencies = [
"bumpalo",
"log",
"once_cell", "once_cell",
"proc-macro2", "rustversion",
"quote", "wasm-bindgen-macro",
"syn 2.0.50",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -1872,9 +1866,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.87" version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@@ -1882,22 +1876,25 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.87" version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
dependencies = [ dependencies = [
"bumpalo",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.50", "syn 2.0.50",
"wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.87" version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
dependencies = [
"unicode-ident",
]
[[package]] [[package]]
name = "web-sys" name = "web-sys"

View File

@@ -1,10 +1,10 @@
################## ##################
### BASE STAGE ### ### BASE STAGE ###
################## ##################
FROM rust:1.76.0 as base FROM rust:1.95.0 as base
# Install build dependencies # Install build dependencies
RUN cargo install --locked trunk@^0.18.8 strip_cargo_version RUN cargo install --locked trunk@^0.21.14 strip_cargo_version
RUN rustup target add wasm32-unknown-unknown RUN rustup target add wasm32-unknown-unknown
RUN rustup target add x86_64-unknown-linux-musl RUN rustup target add x86_64-unknown-linux-musl

View File

@@ -6,17 +6,6 @@ persistence_dir = "/tmp/"
#username = "user" #username = "user"
#password = "password" #password = "password"
[collectors]
markdown_web_links = [
"https://example.org/lmao.md"
]
#weatherapi_key = ""
#weatherapi_locations = [
# "London",
#]
[[bulbs]] [[bulbs]]
id = "light/bedroom" id = "light/bedroom"

View File

@@ -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>,
}

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -1,10 +1,8 @@
mod collector;
mod persistence; mod persistence;
mod tasks; mod tasks;
mod util; mod util;
use clap::Parser; use clap::Parser;
use collector::CollectorConfig;
use common::{BulbMap, ClientMessage, ServerMessage}; use common::{BulbMap, ClientMessage, ServerMessage};
use futures_util::{SinkExt, StreamExt}; use futures_util::{SinkExt, StreamExt};
use lighter_manager::{manager::BulbsConfig, mqtt_conf::MqttConfig}; use lighter_manager::{manager::BulbsConfig, mqtt_conf::MqttConfig};
@@ -47,8 +45,6 @@ struct Opt {
pub struct Config { pub struct Config {
mqtt: MqttConfig, mqtt: MqttConfig,
collectors: CollectorConfig,
persistence_dir: Option<PathBuf>, persistence_dir: Option<PathBuf>,
#[serde(flatten)] #[serde(flatten)]
@@ -104,7 +100,6 @@ async fn main() {
let state = Box::leak(Box::new(state)); let state = Box::leak(Box::new(state));
task::spawn(tasks::lights_task(state)); task::spawn(tasks::lights_task(state));
task::spawn(tasks::info_task(state));
let ws = warp::path("ws") let ws = warp::path("ws")
// The `ws()` filter will prepare the Websocket handshake. // The `ws()` filter will prepare the Websocket handshake.

View File

@@ -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;
}
}
}

View File

@@ -1,5 +1,3 @@
pub mod info;
pub mod lights; pub mod lights;
pub use info::info_task;
pub use lights::lights_task; pub use lights::lights_task;

View File

@@ -6,10 +6,6 @@ use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
#[non_exhaustive] #[non_exhaustive]
pub enum ServerMessage { pub enum ServerMessage {
InfoPage {
html: String,
},
/// Update the state of a bulb /// Update the state of a bulb
BulbState { BulbState {
id: BulbId, id: BulbId,
@@ -23,7 +19,6 @@ pub enum ServerMessage {
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
#[non_exhaustive] #[non_exhaustive]
pub enum ClientMessage { pub enum ClientMessage {
//SubscribeToInfo,
//SubscribeToBulbs, //SubscribeToBulbs,
GetBulbs, GetBulbs,
SetBulbColor { SetBulbColor {

View File

@@ -6,7 +6,7 @@ edition = "2021"
[dependencies] [dependencies]
seed = "0.10.0" seed = "0.10.0"
wasm-bindgen = "=0.2.87" # must match Trunk.toml wasm-bindgen = "=0.2.121" # must match Trunk.toml
serde = { version = "1.0.0", features = ['derive'] } serde = { version = "1.0.0", features = ['derive'] }
ron = "0.7.1" ron = "0.7.1"
chrono = { version = "0.4.20", features = ["serde"] } chrono = { version = "0.4.20", features = ["serde"] }

View File

@@ -1,8 +1,8 @@
[serve] [serve]
address = "0.0.0.0" addresses = ["0.0.0.0"]
[tools] [tools]
wasm_bindgen = "0.2.87" wasm_bindgen = "0.2.121"
[[proxy]] [[proxy]]
# This WebSocket proxy example has a backend and ws field. This example will listen for # This WebSocket proxy example has a backend and ws field. This example will listen for

View File

@@ -42,9 +42,6 @@ pub enum Pages {
#[page("404", NotFound)] #[page("404", NotFound)]
NotFound(page::not_found::Model), NotFound(page::not_found::Model),
#[page("info", Info)]
Info(page::info::Model),
#[page("lights", Lights)] #[page("lights", Lights)]
Lights(page::lights::Model), Lights(page::lights::Model),
} }
@@ -52,7 +49,6 @@ pub enum Pages {
#[derive(Debug)] #[derive(Debug)]
pub enum PageMsg { pub enum PageMsg {
NotFound(page::not_found::Msg), NotFound(page::not_found::Msg),
Info(page::info::Msg),
Lights(page::lights::Msg), Lights(page::lights::Msg),
} }

View File

@@ -1,39 +0,0 @@
use common::ServerMessage;
use seed::prelude::*;
use seed::{div, raw};
use seed_router::Page;
#[derive(Default)]
pub struct Model {
content: String,
}
#[derive(Debug)]
pub enum Msg {
ServerMessage(ServerMessage),
}
impl Page for Model {
type Msg = Msg;
fn new(orders: &mut impl Orders<Self::Msg>) -> Self {
orders.subscribe(Msg::ServerMessage);
Model {
content: r#"<div class="penguin"></div>"#.into(),
}
}
fn update(&mut self, msg: Self::Msg, _orders: &mut impl Orders<Self::Msg>) {
match msg {
Msg::ServerMessage(ServerMessage::InfoPage { html }) => {
self.content = html;
}
Msg::ServerMessage(_) => {}
}
}
fn view(&self) -> Node<Self::Msg> {
div![raw![&self.content]]
}
}

View File

@@ -1,3 +1,2 @@
pub mod info;
pub mod lights; pub mod lights;
pub mod not_found; pub mod not_found;