Implement chart rendering

This commit is contained in:
hodasemi 2023-09-21 22:20:42 +02:00
parent 0c33986628
commit 4cf0c7ff48
6 changed files with 93 additions and 12 deletions

View file

@ -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) {
@ -178,3 +189,54 @@ async function change_device_name(plug, name) {
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);
}

View file

@ -9,6 +9,8 @@
<body>
<div id="main"></div>
<div id="chart_div"></div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script type="text/javascript" src="/js/main.js"></script>
</body>

View file

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

View file

@ -14,10 +14,6 @@ impl Devices {
Ok(from_str(&fs::read_to_string(file)?)?)
}
pub fn to_json(&self) -> Result<String> {
Ok(to_string(self)?)
}
#[allow(unused)]
pub fn save(&self, file: &str) -> Result<()> {
fs::write(file, to_string_pretty(self)?)?;

View file

@ -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<u64> {
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:#?}")))?

View file

@ -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<String>,
db: Data<Arc<Mutex<DataBase>>>,
) -> Result<impl Responder, Error> {
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};