2020-10-09 19:47:43 +02:00
|
|
|
use std::path::Path;
|
2020-10-09 23:06:56 +02:00
|
|
|
use std::process::Stdio;
|
2020-10-09 17:07:18 +02:00
|
|
|
use std::sync::Mutex;
|
2020-10-09 13:30:37 +02:00
|
|
|
use std::thread;
|
|
|
|
use std::time::{Duration, SystemTime};
|
2021-06-13 20:51:18 +02:00
|
|
|
|
|
|
|
use color_eyre::eyre::eyre;
|
|
|
|
use color_eyre::Result;
|
|
|
|
use lazy_static::lazy_static;
|
|
|
|
use rocket::serde::json::Json;
|
|
|
|
use rocket::tokio::fs::File;
|
|
|
|
use rocket::tokio::io::AsyncReadExt;
|
|
|
|
use rocket::tokio::select;
|
|
|
|
use rocket::tokio::sync::oneshot::Receiver;
|
|
|
|
use rocket::tokio::time::sleep;
|
|
|
|
use rocket::{get, routes};
|
|
|
|
use serde::{Deserialize, Serialize};
|
2020-10-09 17:40:48 +02:00
|
|
|
use thirtyfour::prelude::*;
|
2020-10-09 23:06:56 +02:00
|
|
|
use tokio::process::{Child, Command};
|
2020-10-09 13:30:37 +02:00
|
|
|
|
2020-10-09 19:47:43 +02:00
|
|
|
/// The port used by the Gecko Driver
|
2020-10-10 21:13:23 +02:00
|
|
|
const GECKO_DRIVER_PORT: u16 = 4444;
|
2020-10-09 19:47:43 +02:00
|
|
|
|
|
|
|
/// The interval between data polls
|
|
|
|
///
|
|
|
|
/// This depends on with which interval Autaurco processes new information from the convertor.
|
2020-10-09 17:07:18 +02:00
|
|
|
const POLL_INTERVAL: u64 = 300;
|
2020-10-09 13:30:37 +02:00
|
|
|
|
2020-10-09 19:47:43 +02:00
|
|
|
/// The URL to the My Autarco site
|
|
|
|
const URL: &'static str = "https://my.autarco.com/";
|
|
|
|
|
2021-06-13 20:51:18 +02:00
|
|
|
/// The login configuration
|
2020-10-09 19:47:43 +02:00
|
|
|
#[derive(Debug, Deserialize)]
|
|
|
|
struct Config {
|
|
|
|
username: String,
|
|
|
|
password: String,
|
|
|
|
}
|
2020-10-09 13:30:37 +02:00
|
|
|
|
2021-06-13 20:51:18 +02:00
|
|
|
/// Spawns the gecko driver
|
|
|
|
///
|
|
|
|
/// Note that the function blocks and delays at least a second to ensure everything is up and
|
|
|
|
/// running.
|
2020-10-17 00:39:42 +02:00
|
|
|
fn spawn_driver(port: u16) -> Result<Child> {
|
|
|
|
// This is taken from the webdriver-client crate.
|
|
|
|
let child = Command::new("geckodriver")
|
|
|
|
.arg("--port")
|
|
|
|
.arg(format!("{}", port))
|
|
|
|
.stdin(Stdio::null())
|
|
|
|
.stderr(Stdio::null())
|
|
|
|
.stdout(Stdio::null())
|
|
|
|
.kill_on_drop(true)
|
|
|
|
.spawn()?;
|
|
|
|
|
|
|
|
thread::sleep(Duration::new(1, 500));
|
|
|
|
|
|
|
|
Ok(child)
|
2020-10-09 16:31:21 +02:00
|
|
|
}
|
|
|
|
|
2020-10-09 17:30:41 +02:00
|
|
|
#[derive(Clone, Copy, Debug, Serialize)]
|
2020-10-09 16:50:11 +02:00
|
|
|
struct Status {
|
|
|
|
current_w: u32,
|
|
|
|
total_kwh: u32,
|
|
|
|
last_updated: u64,
|
|
|
|
}
|
2020-10-09 16:31:21 +02:00
|
|
|
|
2020-10-09 19:47:43 +02:00
|
|
|
async fn load_config() -> Result<Config> {
|
|
|
|
let config_file_name = Path::new(env!("CARGO_MANIFEST_DIR")).join("autarco.toml");
|
|
|
|
let mut file = File::open(config_file_name).await?;
|
|
|
|
|
|
|
|
let mut contents = String::new();
|
|
|
|
file.read_to_string(&mut contents).await?;
|
|
|
|
let config = toml::from_str(&contents)?;
|
|
|
|
|
|
|
|
Ok(config)
|
|
|
|
}
|
|
|
|
|
2020-10-09 17:40:48 +02:00
|
|
|
async fn login(driver: &WebDriver) -> Result<()> {
|
2020-10-09 19:47:43 +02:00
|
|
|
let config = load_config().await?;
|
|
|
|
|
2020-10-09 17:40:48 +02:00
|
|
|
driver.get(URL).await?;
|
2020-10-09 13:30:37 +02:00
|
|
|
|
2020-10-09 17:40:48 +02:00
|
|
|
let input = driver.find_element(By::Id("username")).await?;
|
2020-10-09 19:47:43 +02:00
|
|
|
input.send_keys(&config.username).await?;
|
2020-10-09 17:40:48 +02:00
|
|
|
let input = driver.find_element(By::Id("password")).await?;
|
2020-10-09 19:47:43 +02:00
|
|
|
input.send_keys(&config.password).await?;
|
2020-10-09 17:40:48 +02:00
|
|
|
let input = driver.find_element(By::Css("button[type=submit]")).await?;
|
|
|
|
input.click().await?;
|
2020-10-09 13:30:37 +02:00
|
|
|
|
2020-10-09 16:50:11 +02:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-10-09 17:40:48 +02:00
|
|
|
async fn element_value(driver: &WebDriver, by: By<'_>) -> Result<u32> {
|
|
|
|
let element = driver.find_element(by).await?;
|
|
|
|
let text = element.text().await?;
|
2020-10-09 16:50:11 +02:00
|
|
|
let value = text.parse()?;
|
|
|
|
|
|
|
|
Ok(value)
|
|
|
|
}
|
2020-10-09 13:30:37 +02:00
|
|
|
|
2020-10-09 17:07:18 +02:00
|
|
|
lazy_static! {
|
|
|
|
static ref STATUS: Mutex<Option<Status>> = Mutex::new(None);
|
|
|
|
}
|
|
|
|
|
2020-10-09 23:06:56 +02:00
|
|
|
async fn update_loop(mut rx: Receiver<()>) -> Result<()> {
|
2020-10-09 16:50:11 +02:00
|
|
|
let mut caps = DesiredCapabilities::firefox();
|
|
|
|
caps.set_headless()?;
|
2020-10-09 17:40:48 +02:00
|
|
|
let driver = WebDriver::new(&format!("http://localhost:{}", GECKO_DRIVER_PORT), &caps).await?;
|
2020-10-09 16:50:11 +02:00
|
|
|
|
|
|
|
// Go to the My Autarco site and login
|
2020-10-10 21:14:10 +02:00
|
|
|
println!("⚡ Logging in...");
|
2020-10-17 00:40:25 +02:00
|
|
|
// FIXME: Just dropping the driver hangs the process?
|
|
|
|
if let Err(e) = login(&driver).await {
|
|
|
|
driver.quit().await?;
|
|
|
|
return Err(e);
|
|
|
|
}
|
2020-10-09 16:50:11 +02:00
|
|
|
|
2020-10-09 23:06:56 +02:00
|
|
|
let mut last_updated = 0;
|
2020-10-09 16:50:11 +02:00
|
|
|
loop {
|
2020-10-09 23:06:56 +02:00
|
|
|
// Wait the poll interval to check again!
|
2021-06-13 20:51:18 +02:00
|
|
|
sleep(Duration::from_secs(1)).await;
|
2020-10-09 23:06:56 +02:00
|
|
|
|
|
|
|
// Shut down if there is a signal
|
|
|
|
if let Ok(()) = rx.try_recv() {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
let timestamp = SystemTime::now()
|
|
|
|
.duration_since(SystemTime::UNIX_EPOCH)
|
|
|
|
.unwrap()
|
|
|
|
.as_secs();
|
|
|
|
if timestamp - last_updated < POLL_INTERVAL {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-10-09 13:30:37 +02:00
|
|
|
// Retrieve the data from the elements
|
2020-10-09 17:40:48 +02:00
|
|
|
let current_w = match element_value(&driver, By::Css("h2#pv-now b")).await {
|
2020-10-09 16:54:02 +02:00
|
|
|
Ok(value) => value,
|
|
|
|
Err(error) => {
|
|
|
|
eprintln!("Failed to retrieve current power: {}", error);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
};
|
2020-10-09 17:40:48 +02:00
|
|
|
let total_kwh = match element_value(&driver, By::Css("h2#pv-to-date b")).await {
|
2020-10-09 16:54:02 +02:00
|
|
|
Ok(value) => value,
|
|
|
|
Err(error) => {
|
|
|
|
eprintln!("Failed to retrieve total energy production: {}", error);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
};
|
2020-10-09 23:06:56 +02:00
|
|
|
last_updated = timestamp;
|
2020-10-09 13:30:37 +02:00
|
|
|
|
2020-10-09 17:07:18 +02:00
|
|
|
// Update the status
|
|
|
|
let mut status_guard = STATUS.lock().expect("Status mutex was poisoned");
|
2020-10-09 16:50:11 +02:00
|
|
|
let status = Status {
|
|
|
|
current_w,
|
|
|
|
total_kwh,
|
|
|
|
last_updated,
|
|
|
|
};
|
2020-10-10 21:14:10 +02:00
|
|
|
println!("⚡ Updated status to: {:#?}", status);
|
2020-10-09 17:07:18 +02:00
|
|
|
status_guard.replace(status);
|
2020-10-09 13:30:37 +02:00
|
|
|
}
|
2020-10-09 23:06:56 +02:00
|
|
|
|
|
|
|
Ok(())
|
2020-10-09 13:30:37 +02:00
|
|
|
}
|
2020-10-09 17:30:41 +02:00
|
|
|
|
|
|
|
#[get("/", format = "application/json")]
|
2020-10-09 19:47:43 +02:00
|
|
|
async fn status() -> Option<Json<Status>> {
|
2020-10-09 23:06:56 +02:00
|
|
|
let status_guard = STATUS.lock().expect("Status mutex was poisoined");
|
2020-10-09 17:30:41 +02:00
|
|
|
status_guard.map(|status| Json(status))
|
|
|
|
}
|
|
|
|
|
2020-10-09 23:06:56 +02:00
|
|
|
#[rocket::main]
|
2020-10-17 00:41:55 +02:00
|
|
|
async fn main() -> Result<()> {
|
2020-10-17 00:41:19 +02:00
|
|
|
color_eyre::install()?;
|
|
|
|
|
2021-06-13 20:51:18 +02:00
|
|
|
let mut driver_proc =
|
2020-10-17 00:39:42 +02:00
|
|
|
spawn_driver(GECKO_DRIVER_PORT).expect("Could not find/start the Gecko Driver");
|
|
|
|
|
2021-06-13 20:51:18 +02:00
|
|
|
let (tx, rx) = rocket::tokio::sync::oneshot::channel();
|
|
|
|
let updater = rocket::tokio::spawn(update_loop(rx));
|
2020-10-09 23:06:56 +02:00
|
|
|
|
2021-06-13 20:51:18 +02:00
|
|
|
let rocket = rocket::build().mount("/", routes![status]).ignite().await?;
|
|
|
|
let shutdown = rocket.shutdown();
|
2020-10-17 00:41:55 +02:00
|
|
|
|
2021-06-13 20:51:18 +02:00
|
|
|
select! {
|
|
|
|
result = driver_proc.wait() => {
|
|
|
|
shutdown.notify();
|
2020-10-17 00:41:55 +02:00
|
|
|
tx.send(()).map_err(|_| eyre!("Could not send shutdown signal"))?;
|
|
|
|
result?;
|
|
|
|
},
|
|
|
|
result = rocket.launch() => {
|
|
|
|
tx.send(()).map_err(|_| eyre!("Could not send shutdown signal"))?;
|
|
|
|
result?;
|
|
|
|
},
|
|
|
|
result = updater => {
|
2021-06-13 20:51:18 +02:00
|
|
|
shutdown.notify();
|
2020-10-17 00:41:55 +02:00
|
|
|
result??;
|
|
|
|
}
|
|
|
|
}
|
2020-10-09 23:06:56 +02:00
|
|
|
|
2020-10-17 00:41:55 +02:00
|
|
|
Ok(())
|
2020-10-09 23:06:56 +02:00
|
|
|
}
|