Updates all crates....

Terrifying and time consuming - have not run on ESP32C3 yet...
This commit is contained in:
2025-02-09 14:02:42 +01:00
parent efe2539ceb
commit a5cbb1e759
11 changed files with 512 additions and 204 deletions

View File

@@ -1,6 +1,6 @@
use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex, RawMutex};
use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, RawMutex};
use embassy_sync::blocking_mutex::Mutex;
use embassy_time::Instant;
use alloc::vec::Vec;
@@ -9,8 +9,8 @@ use crate::serial;
pub struct DataPoint {
pub instant: Instant,
t1: i8,
t2: i8,
pub t1: i8,
pub t2: i8,
pub target: i8,
pub current: i8,
}
@@ -63,7 +63,6 @@ impl<T: Copy, const CAP: usize> RingBuffer<T, CAP> {
/**
* Get oldest item in the ringbuffer
*/
pub fn get_first(&self) -> Option<T> {
if self.has_wrapped {
Some(self.buf[self.index])
@@ -73,6 +72,7 @@ impl<T: Copy, const CAP: usize> RingBuffer<T, CAP> {
None
}
}
*/
/**
* Get all items in the buffer, oldest at index 0,

View File

@@ -1,19 +1,23 @@
use picoserve::routing::{get, get_service};
use picoserve::routing::{get, get_service, parse_path_segment};
use picoserve::response::{IntoResponse, File, Json};
use embassy_time::{Duration, Instant};
use picoserve::response::status::StatusCode;
use embassy_time::{Duration, Instant, with_timeout};
use embassy_net::Stack;
use maud::{DOCTYPE, html, Markup};
use alloc::vec::Vec;
use crate::sntp_client::get_now;
use crate::sntp_client::get_instant;
use serde::{Serialize, Serializer};
use serde::ser::SerializeSeq;
use include_file_compress::include_file_compress_deflate;
use crate::serial;
use crate::database::DATAPOINT_BUFFER;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::mutex::Mutex;
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");
const APP_JS_FILENAME: &str = concat!("/app-", env!("CARGO_PKG_VERSION_SAFE"), ".js");
static PICO_CONFIG : picoserve::Config<Duration> = picoserve::Config::new(
picoserve::Timeouts {
@@ -43,8 +47,16 @@ async fn web_task(
) -> ! {
let api_router = picoserve::Router::new()
.route("/latest", get(|| async { latest() }))
.route("/allreadings", get(|| async { allreadings() }));
.route("/allreadings", get(|| async { allreadings() }))
.route("/metrics", get(|| async { metrics () }))
.route("/cmd/start", get(|| { cmd_start() }))
.route("/cmd/stop", get(|| { cmd_stop() }))
.route("/cmd/ventilate", get(|| { cmd_ventilate() }))
.route(("/cmd/temperature", parse_path_segment::<i8>()), get(|t| { cmd_temperature(t) }));
let image_router = picoserve::Router::new()
.route("/favicon.png", get_service(File::with_content_type_and_headers(&PNG_CONTENT_TYPE,
include_bytes!("static/favicon.png"), CACHEABLE_IMAGE_HEADERS)))
.route("/risc-v-logo.png", get_service(File::with_content_type_and_headers(&PNG_CONTENT_TYPE,
include_bytes!("static/risc-v-logo.png"), CACHEABLE_IMAGE_HEADERS)))
.route("/espressif-logo.png", get_service(File::with_content_type_and_headers(&PNG_CONTENT_TYPE,
@@ -85,6 +97,7 @@ fn page(heading: &str, content: Markup) -> Markup {
html {
head {
link rel="stylesheet" type="text/css" href=(STYLES_CSS_FILENAME);
link rel="icon" type="image/png" href="/images/facicon.png"
script src=(APP_JS_FILENAME) {};
title { (heading) }
}
@@ -184,6 +197,15 @@ impl LatestResponse {
}
}
fn metrics() -> impl IntoResponse {
match DATAPOINT_BUFFER.get_latest() {
None => "".into(),
Some(datapoint) => {
alloc::format!(include_str!("static/prometheus-template.txt"), target = datapoint.target, temperature = datapoint.current, command = datapoint.t1, state = datapoint.t2)
}
}
}
fn latest() -> impl IntoResponse {
match DATAPOINT_BUFFER.get_latest() {
None => Json(LatestResponse::none()),
@@ -192,9 +214,54 @@ 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 ..." }
page("Wallas 22GB Wifi Extension", html! {
div .opaque { p .intro { "Beta version of ESP32C3 based Wifi extension to the Wallas 361062 Control Panel for the DT/GB Heaters" } }
div .opaque { p #graph { "Waiting for data ..." } }
div .opaque { p #latest { "Waiting for latest reading ..." } }
button #"button-start" .button { "Start heater" }
button #"button-stop" .button { "Stop heater" }
button #"button-ventilate" .button { "Start ventilator" }
button #"button-target" .button { "Set target temperature" }
})
}
async fn wait_for_ok () -> () {
let mut subscriber = serial::DOMAIN_MESSAGE_CHANNEL.subscriber().unwrap();
loop {
let data = subscriber.next_message_pure().await;
if let serial::DomainMessage::AtOk = data {
return ();
}
}
}
static CMDLOCK: Mutex<CriticalSectionRawMutex, ()> = Mutex::new(());
async fn cmd(c: serial::DomainCommand) -> impl IntoResponse {
match CMDLOCK.try_lock() {
Err(_) => (StatusCode::LOCKED, ""),
_ => {
serial::DOMAIN_COMMAND_CHANNEL.publisher().unwrap().publish_immediate(c);
match with_timeout(Duration::from_secs(5), wait_for_ok()).await {
Ok(()) => (StatusCode::NO_CONTENT, ""),
Err(_) => (StatusCode::GATEWAY_TIMEOUT, "")
}
}
}
}
async fn cmd_start() -> impl IntoResponse {
cmd(serial::DomainCommand::Start).await
}
async fn cmd_stop() -> impl IntoResponse {
cmd(serial::DomainCommand::Stop).await
}
async fn cmd_ventilate() -> impl IntoResponse {
cmd(serial::DomainCommand::Ventilate).await
}
async fn cmd_temperature(t: i8) -> impl IntoResponse {
cmd(serial::DomainCommand::Temperature(t)).await
}

View File

@@ -2,23 +2,21 @@
#![no_main]
use esp_backtrace as _;
use esp_hal::prelude::*;
use log::{info, error};
extern crate alloc;
use heapless::String;
use esp_hal::rng::Rng;
use esp_hal::timer::systimer::SystemTimer;
use esp_hal::timer::timg::TimerGroup;
use esp_hal::clock::CpuClock;
/**
* Embassy includes
*/
use esp_hal_embassy::init as initialize_embassy;
use esp_hal::timer::systimer::Target;
//use esp_hal_embassy::init as initialize_embassy;
use esp_hal_embassy::main;
use embassy_executor::Spawner;
// use embassy_time::{Duration, Timer};
use esp_hal::timer::timg::TimerGroup;
/**
* Rng functionality
@@ -104,8 +102,9 @@ async fn main_fallible(
esp_println::logger::init_logger_from_env();
let systimer = SystemTimer::new(peripherals.SYSTIMER).split::<Target>();
initialize_embassy(systimer.alarm0);
// let systimer = SystemTimer::new(peripherals.SYSTIMER).split::<Target>();
let tmg0 = TimerGroup::new(peripherals.TIMG0);
// initialize_embassy(tmg0.timer0);
let rng = Rng::new(peripherals.RNG);
@@ -113,13 +112,13 @@ async fn main_fallible(
let password =
String::<64>::try_from(WIFI_PASSWORD).map_err(|()| Error::ParseCredentials)?;
let stack = connect_to_wifi(spawner, TimerGroup::new(peripherals.TIMG0), rng, peripherals.WIFI, peripherals.RADIO_CLK, (ssid, password)).await.unwrap();
let stack = connect_to_wifi(spawner, tmg0, rng, peripherals.WIFI, peripherals.RADIO_CLK, (ssid, password)).await.unwrap();
let _ = httpd_spawn(spawner, HTTPD_SOCKETS, stack);
let _ = sntp_client_spawn(spawner, stack);
serial_spawn(spawner, peripherals.UART0.into(), peripherals.GPIO20.into(), peripherals.GPIO21.into());
serial_spawn(spawner, peripherals.UART0.into());
let _ = database_spawn(spawner);

View File

@@ -7,7 +7,6 @@
//! Random numbers generator
use rand_core::CryptoRng;
use rand_core::Error;
use rand_core::RngCore;
use esp_hal::rng::Rng;
@@ -39,10 +38,12 @@ impl RngCore for RngWrapper {
}
}
/*
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
self.fill_bytes(dest);
Ok(())
}
*/
}
impl CryptoRng for RngWrapper {}

View File

@@ -2,7 +2,6 @@ use esp_hal::{
// clock::ClockControl,
// peripherals::{Peripherals},
uart::{AnyUart, Uart, UartRx, UartTx, Config},
gpio::AnyPin,
Async,
};
use log::{info, error, debug};
@@ -13,6 +12,7 @@ use embassy_sync::pubsub::{PubSubChannel, Subscriber};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_time::{Instant, Timer};
use heapless::Vec;
use nom::Parser;
const BUFFER_SIZE: usize = 256;
type BaseMessage = heapless::String<BUFFER_SIZE>;
@@ -31,19 +31,20 @@ pub enum DomainCommand {
Temperature(i8),
}
type DomainMessageChannel = PubSubChannel<CriticalSectionRawMutex, DomainMessage, 3, 1, 1>;
pub type DomainMessageSubscriber<'a> = Subscriber<'a, CriticalSectionRawMutex, DomainMessage, 3, 1, 1>;
type DomainMessageChannel = PubSubChannel<CriticalSectionRawMutex, DomainMessage, 3, 2, 1>;
pub type DomainMessageSubscriber<'a> = Subscriber<'a, CriticalSectionRawMutex, DomainMessage, 3, 2, 1>;
pub static DOMAIN_MESSAGE_CHANNEL: DomainMessageChannel = DomainMessageChannel::new();
type DomainCommandChannel = PubSubChannel<CriticalSectionRawMutex, DomainCommand, 3, 1, 1>;
static DOMAIN_COMMAND_CHANNEL: DomainCommandChannel = DomainCommandChannel::new();
pub static DOMAIN_COMMAND_CHANNEL: DomainCommandChannel = DomainCommandChannel::new();
pub fn serial_spawn(spawner: embassy_executor::Spawner, peri_uart: AnyUart, rx_pin: AnyPin, tx_pin: AnyPin) {
pub fn serial_spawn(spawner: embassy_executor::Spawner, peri_uart: AnyUart) {
// Initialize and configure UART0
let config = Config::default().rx_fifo_full_threshold(BUFFER_SIZE as u16);
let my_uart = Uart::new_with_config(peri_uart, config, rx_pin, tx_pin).unwrap().into_async();
let config = Config::default().with_rx_fifo_full_threshold(BUFFER_SIZE as u16);
// removed rx_pin, tx_pin
let my_uart = Uart::new(peri_uart, config).unwrap().into_async();
// Split UART0 to create seperate Tx and Rx handles
let (rx, tx) = my_uart.split();
@@ -75,10 +76,10 @@ async fn fakedata() {
fn at_ok_parser (i: &str) -> Option<()> {
match nom::sequence::tuple((
match (
nom::bytes::complete::tag("AT+OK"),
nom::character::complete::crlf::<&str, nom::error::Error<&str>>
))(i) {
).parse(i) {
Ok((_residual, (_, _))) => {
Some(())
}
@@ -89,7 +90,7 @@ fn at_ok_parser (i: &str) -> Option<()> {
}
fn at_wallas_parser (i: &str) -> Option<(i8, i8, i8, i8)> {
match nom::sequence::tuple((
match (
nom::bytes::complete::tag("AT+WALLAS="),
nom::character::complete::i8,
nom::character::complete::char(','),
@@ -99,7 +100,7 @@ fn at_wallas_parser (i: &str) -> Option<(i8, i8, i8, i8)> {
nom::character::complete::char(','),
nom::character::complete::i8,
nom::character::complete::crlf::<&str, nom::error::Error<&str>>
))(i) {
).parse(i) {
Ok((_residual, (_, t1, _, t2, _, t3, _, t4, _))) => {
Some((t1, t2, t3, t4))
}
@@ -128,6 +129,7 @@ async fn writer(mut tx: UartTx<'static, Async>) {
let mut domain_subscriber = DOMAIN_COMMAND_CHANNEL.subscriber().unwrap();
loop {
let cmd = domain_subscriber.next_message_pure().await;
info!("Writing message {:?} to serial port", &cmd);
let _ = embedded_io_async::Write::write(&mut tx,
match &cmd {
DomainCommand::Start => b"START\r\n",
@@ -135,7 +137,7 @@ async fn writer(mut tx: UartTx<'static, Async>) {
DomainCommand::Ventilate => b"VENT\r\n",
DomainCommand::Temperature(temp) => {
let msg = alloc::format!("TEMP={}\r\n", temp);
wbuf.copy_from_slice(msg.as_bytes());
wbuf[0..msg.len()].copy_from_slice(msg.as_bytes());
&wbuf[0..msg.len()]
}
}

View File

@@ -4,10 +4,39 @@ function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
document.addEventListener("DOMContentLoaded", () => LoadCurrent(), false);
document.addEventListener("DOMContentLoaded", () => LoadGraph(), false);
document.addEventListener("DOMContentLoaded", () => main(), false);
async function LoadCurrent() {
async function main () {
var pgraph = document.querySelector("#graph");
if (pgraph) LoadGraph(pgraph);
var platest = document.querySelector("#latest");
if (platest) LoadLatest(platest);
var bstart = document.querySelector("#button-start");
if (bstart) bstart.addEventListener("click",
() => wallas_command("start", "Wallas Heater did not acknowledge the start command", "Wallas heater started"));
var bstop = document.querySelector("#button-stop");
if (bstop) bstop.addEventListener("click",
() => wallas_command("stop", "Wallas Heater did not acknowledge the stop command", "Wallas heater stopped"));
var bventilate = document.querySelector("#button-ventilate");
if (bventilate) bventilate.addEventListener("click",
() => wallas_command("ventilate", "Wallas Heater did not acknowledge the ventilate command", "Wallas heater is ventilating"));
var btarget = document.querySelector("#button-target");
if (btarget) {
btarget.addEventListener("click", set_temperature_dialog);
var target_label = document.querySelector("label#target-label");
document.querySelector("input#target").addEventListener("input", (i) => { target_label.innerHTML = `Target temperature is ${i.target.value}°C`; });
}
}
async function LoadLatest(platest) {
await sleep(500);
while (true) {
fetch("/api/v1/latest").then((response) => response.json()).then((json) => {
@@ -15,7 +44,7 @@ async function LoadCurrent() {
time = json.time;
temperature = json.temperature;
target = json.target;
document.getElementById("latest").innerHTML = `Temperature was ${temperature}&deg;C at ${time} UTC, target temperature was ${target}&deg;C`;
platest.innerHTML = `Temperature was ${temperature}&deg;C at ${time} UTC, target temperature was ${target}&deg;C`;
}
})
await sleep(10000);
@@ -23,50 +52,136 @@ async function LoadCurrent() {
};
const svg_xmlns = "http://www.w3.org/2000/svg";
function appendSVGElement(parent, name, attributes) {
function appendSVGElement(parent, name, attributes, text = null) {
var elem = document.createElementNS(svg_xmlns, name);
for (var aname in attributes) {
elem.setAttributeNS(null, aname, attributes[aname]);
}
parent.appendChild(elem);
return elem;
if (text != null) {
textnode = document.createTextNode(text);
elem.appendChild(textnode);
}
return parent.appendChild(elem);
}
async function LoadGraph() {
function appendElement(parent, name, attributes, text = null) {
var elem = document.createElement(name);
for (var aname in attributes) {
elem.setAttribute(aname, attributes[aname]);
}
if (text != null) {
textnode = document.createTextNode(text);
elem.appendChild(textnode);
}
return parent.appendChild(elem);
}
async function LoadGraph(pgraph) {
await sleep(1000);
while (true) {
fetch("/api/v1/allreadings").then((response) => response.json()).then((json) => {
if (json instanceof Array && json.length > 0) {
var width = pgraph.offsetWidth;
var height = pgraph.offsetHeight;
var temp_y = y => (25 - y) * height/35;
var temp_i = i => (99 - i) * (width - 48)/100;
var graph = document.createDocumentFragment();
var svg = appendSVGElement(graph, "svg", {"width": "100%", "height": "100%", "viewBox": "0 0 100 35"});
var x = 99;
var svg = appendSVGElement(graph, "svg", {"width": "100%", "height": "100%", "viewBox": `0 0 ${width} ${height}`});
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;
for (var i = 0; i < json.length && i < 99; i++) {
var r = json[json.length - i - 1];
var x_coord = temp_i(i);
var temperature_y = temp_y(r.temperature);
temperature_points.push(`${x_coord},${temperature_y}`);
var target_y = temp_y(r.target);
target_points.push(`${x_coord},${target_y}`);
}
temperature_points_str = temperature_points.join(" ");
target_points_str = target_points.join(" ");
// Horizontal lines
// 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"});
appendSVGElement(svg, "polyline", {"class": "grid grid-zero", "points": ""+temp_i(100)+","+temp_y(0)+" "+temp_i(0)+","+temp_y(0)});
// 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"});
appendSVGElement(svg, "polyline", {"class": "grid", "points": ""+temp_i(100)+","+temp_y(10)+" "+temp_i(0)+","+temp_y(10)});
// 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"});
appendSVGElement(svg, "polyline", {"class": "grid", "points": ""+temp_i(100)+","+temp_y(20)+" "+temp_i(0)+","+temp_y(20)});
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});
// Vertical lines
appendSVGElement(svg, "polyline", {"class": "grid", "points": ""+temp_i(10)+","+temp_y(24)+" "+temp_i(10)+","+temp_y(-9)});
appendSVGElement(svg, "polyline", {"class": "grid", "points": ""+temp_i(30)+","+temp_y(24)+" "+temp_i(30)+","+temp_y(-9)});
appendSVGElement(svg, "polyline", {"class": "grid", "points": ""+temp_i(50)+","+temp_y(24)+" "+temp_i(50)+","+temp_y(-9)});
appendSVGElement(svg, "polyline", {"class": "grid", "points": ""+temp_i(70)+","+temp_y(24)+" "+temp_i(70)+","+temp_y(-9)});
appendSVGElement(svg, "polyline", {"class": "grid", "points": ""+temp_i(90)+","+temp_y(24)+" "+temp_i(90)+","+temp_y(-9)});
// text
appendSVGElement(svg, "text", {"x": temp_i(0), "y": temp_y(0), "dx": 6, "dy": 5, "fill": "#000000"}, "0°C");
appendSVGElement(svg, "text", {"x": temp_i(0), "y": temp_y(10), "dx": 6, "dy": 5, "fill": "#000000"}, "10°C");
appendSVGElement(svg, "text", {"x": temp_i(0), "y": temp_y(20), "dx": 6, "dy": 5, "fill": "#000000"}, "20°C");
document.getElementById("graph").replaceChildren(graph);
target_polyline = appendSVGElement(svg, "polyline", {"class": "data target", "points": target_points_str});
temperature_polyline = appendSVGElement(svg, "polyline", {"class": "data temperature", "points": temperature_points_str});
//temperature_polyline.onmouseover = () => { console.log("yup"); };
pgraph.replaceChildren(graph);
}
})
await sleep(10000);
}
}
async function set_temperature_dialog() {
var target = null;
await fetch("/api/v1/latest").then((response) => response.json()).then((json) => {
console.log(json);
if (json.target) {
if (json.target < 5) {
target = 5;
} else if (json.target > 25) {
target = 25;
} else {
target = json.target;
}
}
});
var dialog = document.querySelector("dialog#set-target");
dialog.showModal();
var target_label = document.querySelector("label#target-label");
var target_input = document.querySelector("input#target");
console.log(target);
target_input.value = target;
target_label.innerHTML = `Target temperature is ${target}°C`;
target_input.addEventListener("input", (i) => { target_label.innerHTML = `Target temperature is ${i.target.value}°C`; });
document.querySelector("span.cancel-x").addEventListener("click", () => { dialog.close(); });
document.querySelector("button#set-target").addEventListener("click", (e) => {
e.preventDefault();
dialog.close();
var temperature = target_input.value;
wallas_command(`temperature/${temperature}`, "Wallas Heater did not accept the command", "Temperature target set to ${temperature}°C");
});
}
async function wallas_command(cmd, no_atok, success) {
var dialog = document.querySelector("dialog#message");
dialog.addEventListener('cancel', (event) => { event.preventDefault(); });
dialog.innerHTML = "Sending command....";
dialog.showModal();
fetch(`/api/v1/cmd/${cmd}`).then((response) => {
if (response.status == 423) {
dialog.innerHTML = "Busy executing other command, try again later";
} else if (response.status == 504) {
dialog.innerHTML = no_atok;
} else if (response.status == 204) {
dialog.innerHTML = success;
} else {
dialog.innerHTML = "Trouble communicating with Wallas Heater, try again later";
}
setTimeout(() => {
dialog.close();
}, 5000);
});
}

View File

@@ -20,11 +20,14 @@ p.intro {
padding: 0.2em;
margin: 0.3em 10vw 0.3em 10vw;
}
div.opaque {
background: rgba(213, 228, 246, 0.8);
}
p#graph {
border: 1px solid #B15C1B;
padding: 0.2em;
margin: 0.3em 15vw 0.3em 15vw;
height: 40vw;
height: 30vw;
line-height: 100%;
text-align: center;
}
@@ -32,6 +35,18 @@ p#latest {
border: 1px solid #B15C1B;
padding: 0.2em;
margin: 0.3em 10vw 0.3em 10vw;
opacity: 1;
}
span.cancel-x {
font-weight: bold;
position: absolute;
top: 0.5em;
right: 0.5em;
cursor: default;
color: #9f0000;
}
span.cancel-x:hover {
color: #ff0000;
}
div#footer {
position: absolute;
@@ -41,8 +56,60 @@ div#footer {
font-size: 70%;
width: 100%;
padding-bottom: 0.3em;
z-index: -1;
}
dialog#set-target {
padding: 1em;
width: 25em;
}
dialog#set-target form {
margin-top: 2em;
}
dialog#set-target button {
margin-top: 2em;
}
dialog.dialog-center {
position: fixed;
background: rgb(213, 228, 246);
z-index: 3;
width: 25em;
border: 2px solid #ef7606;
}
div#footer > a > img {
padding-left: 3vw;
padding-right: 3vw;
}
button {
background-color: rgb(160, 171, 185);
border: 2px solid rgb(160, 171, 185);
padding: 15px;
border-radius: 5px;
}
button:hover {
border: 2px solid black;
background-color: #ef7606;
}
polyline.data {
stroke-width: 2px;
stroke-linecap: round;
stroke-linejoin: round;
fill: none;
}
polyline.temperature {
stroke: #0079d9;
}
polyline.target {
stroke: #B15C1B;
stroke-dasharray: 0.5,1;
}
polyline.grid {
stroke-width: 1;
fill: none;
stroke: rgb(160, 171, 185);
stroke-linecap: round;
stroke-dasharray: 5,7;
}
polyline.grid-zero {
stroke: rgb(107, 114, 123);
stroke-dasharray: 5,3;
}

View File

@@ -45,7 +45,7 @@ use esp_hal::peripherals::TIMG0;
use esp_hal::peripherals::WIFI;
use esp_hal::rng::Rng;
use esp_hal::timer::timg::TimerGroup;
use esp_hal::Blocking;
// use esp_hal::Blocking;
use heapless::String;
@@ -70,7 +70,7 @@ pub static STOP_WIFI_SIGNAL: Signal<CriticalSectionRawMutex, ()> = Signal::new()
/// Connect to WiFi
pub async fn connect(
spawner: Spawner,
timg0: TimerGroup<'static, TIMG0, Blocking>,
timg0: TimerGroup<TIMG0>,
rng: Rng,
wifi: WIFI,
radio_clock_control: RADIO_CLK,