diff --git a/build.rs b/build.rs index 5efe9c9..1717f27 100644 --- a/build.rs +++ b/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(".", "_")); } diff --git a/src/httpd.rs b/src/httpd.rs index d77e45c..0e685c5 100644 --- a/src/httpd.rs +++ b/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 = 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()) } }) } diff --git a/src/main.rs b/src/main.rs index 50ae80f..30d877c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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:?}"); diff --git a/src/static/app.js b/src/static/app.js index 0a7e3dd..5b29082 100644 --- a/src/static/app.js +++ b/src/static/app.js @@ -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); + } +} + diff --git a/src/static/styles.css b/src/static/styles.css index 07053ac..f9da5c7 100644 --- a/src/static/styles.css +++ b/src/static/styles.css @@ -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;