diff --git a/resources/js/main.js b/resources/js/main.js index 9c48086..7f0edd8 100644 --- a/resources/js/main.js +++ b/resources/js/main.js @@ -51,6 +51,7 @@ async function startup() { let row_device = document.createElement('tr'); + // create device name column let device_name_entry = document.createElement('td'); let device_name = document.createElement('input'); device_name.value = device_descriptor; @@ -73,6 +74,7 @@ async function startup() { device_name_entry.appendChild(device_name_edit); row_device.appendChild(device_name_entry); + // get plug status const device_status_response = await fetch( "/plug_state/" + device_id, { @@ -82,6 +84,7 @@ async function startup() { let device_state = JSON.parse(await device_status_response.json()); + // create device led state column let device_led_state_entry = document.createElement('td'); let device_led_state = document.createElement('label'); device_led_state.innerText = device_state["led"]; @@ -98,6 +101,7 @@ async function startup() { device_led_state_entry.appendChild(device_led_off); row_device.appendChild(device_led_state_entry); + // create device power state column let device_power_state_entry = document.createElement('td'); let device_power_state = document.createElement('label'); device_power_state.innerText = device_state["power"]; @@ -114,11 +118,18 @@ async function startup() { device_power_state_entry.appendChild(device_power_off); row_device.appendChild(device_power_state_entry); + // create device power draw column let device_power_draw_entry = document.createElement('td'); - let device_power_draw_ = document.createElement('label'); - device_power_draw_.innerText = device_state["power_draw"] + " W"; + let device_power_draw = document.createElement('label'); + device_power_draw.innerText = device_state["power_draw"] + " W"; + let device_power_draw_graph_button = document.createElement('button'); + device_power_draw_graph_button.onclick = async () => { await render_graph(device_id) }; + let device_power_draw_graph_button_icon = document.createElement('i'); + device_power_draw_graph_button_icon.className = "fa fa-chart-bar"; - device_power_draw_entry.appendChild(device_power_draw_); + device_power_draw_graph_button.appendChild(device_power_draw_graph_button_icon); + device_power_draw_entry.appendChild(device_power_draw); + device_power_draw_entry.appendChild(device_power_draw_graph_button); row_device.appendChild(device_power_draw_entry); table.appendChild(row_device); @@ -163,7 +174,7 @@ async function power_on(plug) { } async function power_off(plug) { - await change_plug_state(plug, "pwoer", "off") + await change_plug_state(plug, "power", "off") } async function change_device_name(plug, name) { @@ -177,4 +188,55 @@ async function change_device_name(plug, name) { if (!response.ok) { console.error(response.body); } +} + +async function render_graph(plug) { + // remove old graph, if present + let old = document.getElementById("chart"); + + if (old !== null) { + old.remove(); + } + + // create new chart div + let chart = document.createElement('canvas'); + chart.id = "chart"; + + const response = await fetch( + "/plug_data/" + plug, + { + method: "GET" + } + ); + + const j = await response.json(); + const data = JSON.parse(j); + + let y = []; + let x = []; + + for (let i = 0; i < data.length; i++) { + let [time, watts] = data[i]; + + x.push(time); + y.push(watts); + } + + const chart_data = { + labels: x, + datasets: [{ + label: plug, + data: y, + fill: false, + borderColor: 'rgb(75, 192, 192)', + tension: 0.1 + }] + }; + + new Chart(chart, { + type: 'line', + data: chart_data, + }); + + document.getElementById("chart_div").appendChild(chart); } \ No newline at end of file diff --git a/resources/static/index.html b/resources/static/index.html index 2ebb1b6..ef2c9c1 100644 --- a/resources/static/index.html +++ b/resources/static/index.html @@ -9,6 +9,8 @@
+
+ diff --git a/src/db.rs b/src/db.rs index 4f9bec8..baeaba2 100644 --- a/src/db.rs +++ b/src/db.rs @@ -153,9 +153,11 @@ impl DataBase { self.sql .prepare(&format!( " - SELECT time, watts + SELECT data.time, data.watts FROM data - WHERE device=\"{device}\" + INNER JOIN devices + ON data.device_id=devices.id + WHERE devices.device=\"{device}\" " ))? .query_map([], |row| Ok((row.get(0)?, row.get(1)?)))? diff --git a/src/devices.rs b/src/devices.rs index 9e2c071..fe5c8c0 100644 --- a/src/devices.rs +++ b/src/devices.rs @@ -14,10 +14,6 @@ impl Devices { Ok(from_str(&fs::read_to_string(file)?)?) } - pub fn to_json(&self) -> Result { - Ok(to_string(self)?) - } - #[allow(unused)] pub fn save(&self, file: &str) -> Result<()> { fs::write(file, to_string_pretty(self)?)?; diff --git a/src/main.rs b/src/main.rs index 3bfa1f7..b187072 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,7 +18,9 @@ use anyhow::Result; use devices::Devices; use futures::{future::try_join_all, try_join, Future}; use tasmota::Tasmota; -use web_server::{change_device_name, change_plug_state, device_query, index, plug_state}; +use web_server::{ + change_device_name, change_plug_state, device_query, index, plug_data, plug_state, +}; fn since_epoch() -> Result { Ok(SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs()) @@ -64,6 +66,7 @@ async fn run_web_server( .service(plug_state) .service(change_plug_state) .service(change_device_name) + .service(plug_data) }) .bind(("0.0.0.0", 8062)) .map_err(|err| anyhow::Error::msg(format!("failed binding to address: {err:#?}")))? diff --git a/src/web_server.rs b/src/web_server.rs index f19ed9f..3178ad4 100644 --- a/src/web_server.rs +++ b/src/web_server.rs @@ -2,7 +2,7 @@ use actix_files::NamedFile; use actix_web::{ get, post, web::{Data, Json, Path}, - Responder, ResponseError, + Error, Responder, ResponseError, }; use serde::Serialize; use serde_json::to_string; @@ -151,6 +151,22 @@ async fn change_plug_state( }) } +#[get("/plug_data/{plug}")] +async fn plug_data( + param: Path, + db: Data>>, +) -> Result { + let plug = param.into_inner(); + + let data = db + .lock() + .unwrap() + .read(&plug) + .map_err(|err| MyError::from(err))?; + + Ok(Json(to_string(&data)?)) +} + #[cfg(test)] mod test { use actix_web::{http::header::ContentType, test, App};