SVG Graph, compressed statics ... and much more
This commit is contained in:
1
build.rs
1
build.rs
@@ -1,3 +1,4 @@
|
||||
fn main() {
|
||||
println!("cargo:rustc-link-arg-bins=-Tlinkall.x");
|
||||
println!("cargo:rustc-env=CARGO_PKG_VERSION_SAFE={}", env!("CARGO_PKG_VERSION").replace(".", "_"));
|
||||
}
|
||||
|
||||
13
src/httpd.rs
13
src/httpd.rs
@@ -12,6 +12,9 @@ use include_file_compress::include_file_compress_deflate;
|
||||
|
||||
use crate::database::DATAPOINT_BUFFER;
|
||||
|
||||
const STYLES_CSS_FILENAME: &str = concat!("/styles-", env!("CARGO_PKG_VERSION_SAFE"), ".css");
|
||||
const APP_JS_FILENAME: &str = concat!("/app-", env!("CARGO_PKG_VERSION_SAFE"), ".css");
|
||||
|
||||
static PICO_CONFIG : picoserve::Config<Duration> = picoserve::Config::new(
|
||||
picoserve::Timeouts {
|
||||
start_read_request: Some(Duration::from_secs(5)),
|
||||
@@ -52,9 +55,9 @@ async fn web_task(
|
||||
picoserve::Router::new()
|
||||
.nest("/api/v1", api_router)
|
||||
.route("/", get(|| async { index() }))
|
||||
.route("/styles.css", get_service(File::with_content_type_and_headers(&CSS_CONTENT_TYPE,
|
||||
.route(STYLES_CSS_FILENAME, get_service(File::with_content_type_and_headers(&CSS_CONTENT_TYPE,
|
||||
include_file_compress_deflate!("src/static/styles.css", 5), DEFLATE_CACHEABLE_CONTENT_HEADERS)))
|
||||
.route("/app.js", get_service(File::with_content_type_and_headers(&JS_CONTENT_TYPE,
|
||||
.route(APP_JS_FILENAME, get_service(File::with_content_type_and_headers(&JS_CONTENT_TYPE,
|
||||
include_file_compress_deflate!("src/static/app.js", 5), DEFLATE_CACHEABLE_CONTENT_HEADERS)))
|
||||
.nest("/images", image_router);
|
||||
|
||||
@@ -81,8 +84,8 @@ fn page(heading: &str, content: Markup) -> Markup {
|
||||
(DOCTYPE)
|
||||
html {
|
||||
head {
|
||||
link rel="stylesheet" type="text/css" href="/styles.css";
|
||||
script src="app.js" {};
|
||||
link rel="stylesheet" type="text/css" href=(STYLES_CSS_FILENAME);
|
||||
script src=(APP_JS_FILENAME) {};
|
||||
title { (heading) }
|
||||
}
|
||||
body {
|
||||
@@ -191,7 +194,7 @@ fn latest() -> impl IntoResponse {
|
||||
fn index() -> impl IntoResponse {
|
||||
page("Wallas 22GB Wifi Extension", html! {
|
||||
p .intro { "Beta version of ESP32C3 based Wifi extension to the Wallas 361062 Control Panel for the DT/GB Heaters" }
|
||||
p #graph { "Waiting for data ..." }
|
||||
p #latest { "Waiting for latest reading ..." }
|
||||
// p { "Time is now " (get_now()) }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -59,6 +59,10 @@ mod database;
|
||||
use database::database_spawn;
|
||||
|
||||
|
||||
/// CARGO_PKG_VERSION
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
pub const VERSION_SAFE: &str = env!("CARGO_PKG_VERSION_SAFE");
|
||||
|
||||
/// SSID for WiFi network
|
||||
const WIFI_SSID: &str = env!("WIFI_SSID");
|
||||
|
||||
@@ -78,7 +82,7 @@ const MAX_CONCURRENT_SOCKETS: usize = HTTPD_SOCKETS + DHCP_SOCKETS + DNS_SOCKETS
|
||||
#[main]
|
||||
async fn main(spawner: Spawner) {
|
||||
|
||||
info!("booting firmware");
|
||||
info!("booting firmware version {}", VERSION);
|
||||
|
||||
if let Err(error) = main_fallible(spawner).await {
|
||||
error!("Error while running firmware: {error:?}");
|
||||
|
||||
@@ -4,20 +4,69 @@ function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => DOMLoaded(), false);
|
||||
document.addEventListener("DOMContentLoaded", () => LoadCurrent(), false);
|
||||
document.addEventListener("DOMContentLoaded", () => LoadGraph(), false);
|
||||
|
||||
async function DOMLoaded() {
|
||||
await sleep(1000);
|
||||
async function LoadCurrent() {
|
||||
await sleep(500);
|
||||
while (true) {
|
||||
fetch("/api/v1/latest").then((response) => response.json()).then((json) => {
|
||||
if (json.time) {
|
||||
time = json.time;
|
||||
temperature = json.temperature;
|
||||
target = json.target;
|
||||
document.getElementById("latest").innerHTML = `Temperature was ${temperature}°C at ${time} UTC, target temperature was ${target}°C`;
|
||||
}
|
||||
})
|
||||
fetch("/api/v1/latest").then((response) => response.json()).then((json) => {
|
||||
if (json.time) {
|
||||
time = json.time;
|
||||
temperature = json.temperature;
|
||||
target = json.target;
|
||||
document.getElementById("latest").innerHTML = `Temperature was ${temperature}°C at ${time} UTC, target temperature was ${target}°C`;
|
||||
}
|
||||
})
|
||||
await sleep(10000);
|
||||
}
|
||||
};
|
||||
|
||||
const svg_xmlns = "http://www.w3.org/2000/svg";
|
||||
function appendSVGElement(parent, name, attributes) {
|
||||
var elem = document.createElementNS(svg_xmlns, name);
|
||||
for (var aname in attributes) {
|
||||
elem.setAttributeNS(null, aname, attributes[aname]);
|
||||
}
|
||||
parent.appendChild(elem);
|
||||
return elem;
|
||||
}
|
||||
|
||||
async function LoadGraph() {
|
||||
await sleep(1000);
|
||||
while (true) {
|
||||
fetch("/api/v1/allreadings").then((response) => response.json()).then((json) => {
|
||||
if (json instanceof Array && json.length > 0) {
|
||||
var graph = document.createDocumentFragment();
|
||||
var svg = appendSVGElement(graph, "svg", {"width": "100%", "height": "100%", "viewBox": "0 0 100 35"});
|
||||
var x = 99;
|
||||
var target_points = [];
|
||||
var temperature_points = [];
|
||||
for (var i = json.length - 1; i > 0; i--) {
|
||||
var r = json[i];
|
||||
var temperature_y = 25 - r.temperature;
|
||||
temperature_points.push(`${x},${temperature_y}`);
|
||||
var target_y = 25 - r.target;
|
||||
target_points.push(`${x},${target_y}`);
|
||||
x = x - 1;
|
||||
}
|
||||
temperature_points_str = temperature_points.join(" ");
|
||||
target_points_str = target_points.join(" ");
|
||||
|
||||
// zero line
|
||||
appendSVGElement(svg, "polyline", {"stroke-width": "0.1", "fill": "none", "stroke":"#000080", "stroke-linecap": "round", "stroke-dasharray": "5,2", "points": "1,25 99,25"});
|
||||
// ten line
|
||||
appendSVGElement(svg, "polyline", {"stroke-width": "0.05", "fill": "none", "stroke":"#000070", "stroke-linecap": "round", "stroke-dasharray": "5,2", "points": "1,15 99,15"});
|
||||
// twenty line
|
||||
appendSVGElement(svg, "polyline", {"stroke-width": "0.05", "fill": "none", "stroke":"#000080", "stroke-linecap": "round", "stroke-dasharray": "5,2", "points": "1,5 99,5"});
|
||||
|
||||
target_polyline = appendSVGElement(svg, "polyline", {"stroke-width": "1", "fill": "none", "stroke": "#007900", "points": target_points_str});
|
||||
temperature_polyline = appendSVGElement(svg, "polyline", {"stroke-width": "1", "fill": "none", "stroke": "#0079d9", "points": temperature_points_str});
|
||||
|
||||
document.getElementById("graph").replaceChildren(graph);
|
||||
}
|
||||
})
|
||||
await sleep(10000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,14 @@ p.intro {
|
||||
padding: 0.2em;
|
||||
margin: 0.3em 10vw 0.3em 10vw;
|
||||
}
|
||||
p#graph {
|
||||
border: 1px solid #B15C1B;
|
||||
padding: 0.2em;
|
||||
margin: 0.3em 15vw 0.3em 15vw;
|
||||
height: 40vw;
|
||||
line-height: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
p#latest {
|
||||
border: 1px solid #B15C1B;
|
||||
padding: 0.2em;
|
||||
|
||||
Reference in New Issue
Block a user