added caching, reporting to pushover
This commit is contained in:
parent
7d298eb48b
commit
8316bc297a
4 changed files with 131 additions and 52 deletions
17
Cargo.lock
generated
17
Cargo.lock
generated
|
|
@ -954,6 +954,7 @@ dependencies = [
|
|||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
|
|
@ -965,6 +966,7 @@ dependencies = [
|
|||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"native-tls",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
|
|
@ -1225,7 +1227,6 @@ dependencies = [
|
|||
"tokio",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tower-livereload",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
|
@ -1440,20 +1441,6 @@ version = "0.3.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
|
||||
|
||||
[[package]]
|
||||
name = "tower-livereload"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aa6b29b17d4540f2bd9ec304ad39d280c4bdf291d0ea6c4123eeba10939af84"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
"http-body",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tower",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.3"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ edition = "2024"
|
|||
axum = "0.8.6"
|
||||
chrono = "0.4.42"
|
||||
dotenv = "0.15.0"
|
||||
reqwest = { version = "0.12", features = ["json"] }
|
||||
reqwest = { version = "0.12", features = ["json", "multipart"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
tokio = { version = "1.47", features = ["full"] }
|
||||
tower-http = { version = "0.6", features = ["fs"] }
|
||||
|
|
@ -15,4 +15,4 @@ tower = "0.5.0"
|
|||
tracing = "0.1.41"
|
||||
tracing-subscriber = "0.3.20"
|
||||
anyhow = "1.0"
|
||||
tower-livereload = "0.9.6"
|
||||
#tower-livereload = "0.9.6"
|
||||
|
|
|
|||
161
src/main.rs
161
src/main.rs
|
|
@ -1,27 +1,51 @@
|
|||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use axum::{
|
||||
Extension, Json, Router,
|
||||
Json, Router,
|
||||
extract::State,
|
||||
response::{ErrorResponse, Html},
|
||||
routing::get,
|
||||
};
|
||||
use chrono::prelude::*;
|
||||
use chrono::{DateTime, Days, Timelike, Utc};
|
||||
use dotenv::dotenv;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tower::ServiceBuilder;
|
||||
use tower_http::services::ServeDir;
|
||||
use tower_livereload::LiveReloadLayer;
|
||||
// use tower_livereload::LiveReloadLayer;
|
||||
|
||||
type CachedAppState = Arc<RwLock<AppState>>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct AppState {
|
||||
day_checked: bool,
|
||||
cache_reset: DateTime<Utc>,
|
||||
values: EnergyResponse,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
dotenv().ok();
|
||||
let app_state: CachedAppState = Arc::new(RwLock::new(AppState {
|
||||
values: EnergyResponse {
|
||||
energy: Energy {
|
||||
timeUnit: "".to_string(),
|
||||
unit: "".to_string(),
|
||||
values: vec![],
|
||||
},
|
||||
},
|
||||
day_checked: false,
|
||||
cache_reset: Utc::now() - Days::new(1),
|
||||
}));
|
||||
|
||||
let app = Router::new()
|
||||
.route("/api/energy", get(energy))
|
||||
.with_state(app_state)
|
||||
.route("/", get(index))
|
||||
.nest_service(
|
||||
"/static",
|
||||
ServiceBuilder::new().service(ServeDir::new("static")),
|
||||
)
|
||||
.layer(LiveReloadLayer::new());
|
||||
);
|
||||
// .layer(LiveReloadLayer::new());
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||||
println!("server on {}", listener.local_addr().unwrap());
|
||||
|
|
@ -43,50 +67,117 @@ async fn index() -> Html<String> {
|
|||
Html(html)
|
||||
}
|
||||
|
||||
async fn energy() -> axum::response::Result<Json<EnergyResponse>, ErrorResponse> {
|
||||
let site_id = std::env::var("SITE_ID").unwrap();
|
||||
let api_key = std::env::var("API_KEY").unwrap();
|
||||
let utc_now = Utc::now().date_naive();
|
||||
async fn energy(
|
||||
State(state): State<CachedAppState>,
|
||||
) -> axum::response::Result<Json<EnergyResponse>, ErrorResponse> {
|
||||
let energy_response = fetch_energy_response(state.clone()).await?;
|
||||
check_energy(state, &energy_response).await?;
|
||||
|
||||
let url = format!(
|
||||
"https://monitoringapi.solaredge.com/site/{}/energy?timeUnit=QUARTER_OF_AN_HOUR&endDate={}&startDate={}&api_key={}",
|
||||
site_id, utc_now, utc_now, api_key,
|
||||
);
|
||||
|
||||
let mut energy_response = reqwest::get(url)
|
||||
.await
|
||||
.map_err(|e| ErrorResponse::from(e.to_string()))?
|
||||
.json::<EnergyResponse>()
|
||||
.await
|
||||
.map_err(|e| ErrorResponse::from(e.to_string()))?;
|
||||
|
||||
let values: Vec<EnergyValue> = energy_response
|
||||
.energy
|
||||
.values
|
||||
.iter()
|
||||
.map(|v| EnergyValue {
|
||||
date: format!("{}+02:00", v.date.replace(' ', "T")).to_string(),
|
||||
value: v.value,
|
||||
})
|
||||
.collect();
|
||||
|
||||
energy_response.energy.values = values;
|
||||
Ok(Json(energy_response))
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
async fn check_energy(
|
||||
state: CachedAppState,
|
||||
energy_response: &EnergyResponse,
|
||||
) -> axum::response::Result<(), ErrorResponse> {
|
||||
let now = Utc::now();
|
||||
let hour = now.hour();
|
||||
let is_checked_today = state.read().unwrap().day_checked;
|
||||
if hour == 12 && !is_checked_today {
|
||||
let energy_at_1200 = energy_response
|
||||
.energy
|
||||
.values
|
||||
.iter()
|
||||
.find(|v| v.date.ends_with("12:00:00+02:00"))
|
||||
.map(|v| v.value)
|
||||
.flatten();
|
||||
if let Some(energy_at_1200) = energy_at_1200 {
|
||||
if energy_at_1200 == 0.0 {
|
||||
report().await?;
|
||||
}
|
||||
}
|
||||
state.write().unwrap().day_checked = true;
|
||||
}
|
||||
//reset at 00:00
|
||||
if hour == 0 && is_checked_today {
|
||||
state.write().unwrap().day_checked = false;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn report() -> axum::response::Result<(), ErrorResponse> {
|
||||
let user_id = std::env::var("PUSHOVER_USER_ID").unwrap();
|
||||
let api_key = std::env::var("PUSHOVER_API_KEY").unwrap();
|
||||
|
||||
let url = "https://api.pushover.net/1/messages.json";
|
||||
let form = reqwest::multipart::Form::new()
|
||||
.text("token", api_key)
|
||||
.text("user", user_id)
|
||||
.text("message", "No energy measured on the solar panels");
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let _ = client
|
||||
.post(url)
|
||||
.multipart(form)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| ErrorResponse::from(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn fetch_energy_response(state: CachedAppState) -> axum::response::Result<EnergyResponse> {
|
||||
let reset_ts = state.read().unwrap().cache_reset;
|
||||
let now = Utc::now();
|
||||
if now.signed_duration_since(reset_ts).as_seconds_f32() > 300.0 {
|
||||
state.write().unwrap().cache_reset = now;
|
||||
let site_id = std::env::var("SOLAREDGE_SITE_ID").unwrap();
|
||||
let api_key = std::env::var("SOLAREDGE_API_KEY").unwrap();
|
||||
|
||||
let url = format!(
|
||||
"https://monitoringapi.solaredge.com/site/{}/energy?timeUnit=QUARTER_OF_AN_HOUR&endDate={}&startDate={}&api_key={}",
|
||||
site_id,
|
||||
now.date_naive(),
|
||||
now.date_naive(),
|
||||
api_key,
|
||||
);
|
||||
|
||||
let mut energy_response = reqwest::get(url)
|
||||
.await
|
||||
.map_err(|e| ErrorResponse::from(e.to_string()))?
|
||||
.json::<EnergyResponse>()
|
||||
.await
|
||||
.map_err(|e| ErrorResponse::from(e.to_string()))?;
|
||||
|
||||
let values: Vec<EnergyValue> = energy_response
|
||||
.energy
|
||||
.values
|
||||
.iter()
|
||||
.map(|v| EnergyValue {
|
||||
date: format!("{}+02:00", v.date.replace(' ', "T")).to_string(),
|
||||
value: v.value,
|
||||
})
|
||||
.collect();
|
||||
|
||||
energy_response.energy.values = values;
|
||||
state.write().unwrap().values = energy_response.clone();
|
||||
Ok(energy_response)
|
||||
} else {
|
||||
Ok(state.read().unwrap().values.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
struct EnergyResponse {
|
||||
energy: Energy,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
struct Energy {
|
||||
timeUnit: String,
|
||||
unit: String,
|
||||
values: Vec<EnergyValue>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
struct EnergyValue {
|
||||
date: String,
|
||||
value: Option<f32>,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="/static/style.css" />
|
||||
<meta http-equiv="refresh" content="300" />
|
||||
</head>
|
||||
<body>
|
||||
<script src="/static/d3.v7.min.js"></script>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue