OK! We have a workign thing! (Sorta). This now presents a web page which automatically updates the CO2, temp, and humitity values via JavaScript fetching from the Pico. The code diffs contain some gnarly stuff... I've thrown more debug prints and some sleeps in as I was hitting a sort of concurrently issue I think. I need to dig deeper into how these locks are working. Also I turned off keepalives as with the JS in the mix we were basically self-dossing... and adding more HTTP workers is bad solution as it just locks up the Pi (so those concurrently issues again?!) Anyway... it's definitely a milestone. Next up a spot of cleaning and debugging.
This commit is contained in:
parent
8ec0ccbbad
commit
54930ec630
4 changed files with 101 additions and 10 deletions
|
|
@ -3,10 +3,19 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Meat-Pi</title>
|
||||
<title>SCD40 CO2 PPM Sensor</title>
|
||||
<link rel="stylesheet" href="main.css">
|
||||
<script src="main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Meat-Pi 🍖🌡️</h1>
|
||||
<h1>SCD40 CO2 PPM Sensor</h1>
|
||||
<dl>
|
||||
<dt>CO<sub>2</sub></dt>
|
||||
<dd id="co2ppm">enable JavaScript!</dd>
|
||||
<dt>Temperature</dt>
|
||||
<dd id="temperature">enable JavaScript!</dd>
|
||||
<dt>Humidity</dt>
|
||||
<dd id="humidity">enable JavaScript!</dd>
|
||||
</dl>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -17,3 +17,16 @@ body {
|
|||
align-items: center;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
dl {
|
||||
display: grid;
|
||||
grid-template-columns: max-content auto;
|
||||
}
|
||||
|
||||
dt {
|
||||
grid-column-start: 1;
|
||||
}
|
||||
|
||||
dd {
|
||||
grid-column-start: 2;
|
||||
}
|
||||
|
|
|
|||
28
src/html/main.js
Normal file
28
src/html/main.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// data field names
|
||||
var data = [ "co2ppm", "temperature", "humidity" ];
|
||||
|
||||
// when the DOM is ready use JS to remove the "enable JS" text
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
data.forEach(( datum, i ) => document.getElementById(datum).innerHTML = "…");
|
||||
});
|
||||
|
||||
var i1 = setInterval(
|
||||
function() {
|
||||
fetch("data/humidity")
|
||||
.then(function(response) { return response.json(); })
|
||||
.then(function(json) {
|
||||
document.getElementById("humidity").innerHTML = parseFloat(json).toFixed(0) + "%";
|
||||
return fetch("data/temperature");
|
||||
})
|
||||
.then(function(response) { return response.json(); })
|
||||
.then(function(json) {
|
||||
document.getElementById("temperature").innerHTML = parseFloat(json).toFixed(1) + "°C";
|
||||
return fetch("data/co2ppm")
|
||||
})
|
||||
.then(function(response) { return response.json(); })
|
||||
.then(function(json) {
|
||||
document.getElementById("co2ppm").innerHTML = json + " PPM";
|
||||
});
|
||||
},
|
||||
2000
|
||||
);
|
||||
57
src/main.rs
57
src/main.rs
|
|
@ -31,10 +31,13 @@ use {defmt_rtt as _, panic_probe as _};
|
|||
|
||||
// ensure the network/password files have no trailing newline
|
||||
// i.e. generate like: echo -n "password" > src/secrets/wifi-password
|
||||
const WIFI_NETWORK: &str = include_str!("secrets/wifi-network");
|
||||
const WIFI_PASSWORD: &str = include_str!("secrets/wifi-password");
|
||||
// see README in src/secrets
|
||||
const WIFI_NETWORK: &str = "bendybogalow"; // include_str!("secrets/wifi-network");
|
||||
const WIFI_PASSWORD: &str = "parsnipcabbageonion"; // include_str!("secrets/wifi-password");
|
||||
// web content
|
||||
const INDEX: &str = include_str!("html/index.html");
|
||||
const CSS: &str = include_str!("html/main.css");
|
||||
const JS: &str = include_str!("html/main.js");
|
||||
|
||||
// TODO: I think these calls can be combined?
|
||||
bind_interrupts!(struct Irqs {
|
||||
|
|
@ -109,7 +112,12 @@ impl AppWithStateBuilder for AppProps {
|
|||
get_service(File::css(CSS)),
|
||||
)
|
||||
.route(
|
||||
"/data",
|
||||
"/main.js",
|
||||
get_service(File::javascript(JS)),
|
||||
)
|
||||
/*.route( // FIXME: for some reason this causes the whole pi to lock up...
|
||||
// mutex shenanigans....
|
||||
"/data.json",
|
||||
get(
|
||||
|State(SharedSCD40Data(scd40data)): State<SharedSCD40Data>| //newbie note: | delimits a closure
|
||||
async move { picoserve::response::Json(
|
||||
|
|
@ -123,6 +131,27 @@ impl AppWithStateBuilder for AppProps {
|
|||
)
|
||||
}
|
||||
),
|
||||
)*/
|
||||
.route(
|
||||
"/data/co2ppm",
|
||||
get(
|
||||
|State(SharedSCD40Data(scd40data)): State<SharedSCD40Data>| //newbie note: | delimits a closure
|
||||
async move { picoserve::response::Json( scd40data.lock().await.co2ppm ) }
|
||||
),
|
||||
)
|
||||
.route(
|
||||
"/data/temperature",
|
||||
get(
|
||||
|State(SharedSCD40Data(scd40data)): State<SharedSCD40Data>| //newbie note: | delimits a closure
|
||||
async move { picoserve::response::Json( scd40data.lock().await.temperature ) }
|
||||
),
|
||||
)
|
||||
.route(
|
||||
"/data/humidity",
|
||||
get(
|
||||
|State(SharedSCD40Data(scd40data)): State<SharedSCD40Data>| //newbie note: | delimits a closure
|
||||
async move { picoserve::response::Json( scd40data.lock().await.humidity ) }
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -167,9 +196,12 @@ async fn read_co2(
|
|||
shared_scd40data: SharedSCD40Data
|
||||
) {
|
||||
log::info!("Enter sensor read loop");
|
||||
Timer::after_millis(1000).await;
|
||||
loop {
|
||||
if scd.data_ready().unwrap() {
|
||||
let m = scd.read_measurement().unwrap();
|
||||
log::info!("Read a measurement");
|
||||
Timer::after_millis(1000).await;
|
||||
// TODO: is there a way to write this in one block/struct rather than three locks?
|
||||
shared_scd40data.0.lock().await.co2ppm = m.co2;
|
||||
shared_scd40data.0.lock().await.temperature = m.temperature;
|
||||
|
|
@ -178,7 +210,7 @@ async fn read_co2(
|
|||
"CO2: {}\nHumidity: {}\nTemperature: {}", m.co2, m.humidity, m.temperature
|
||||
)
|
||||
}
|
||||
Timer::after_secs(1).await;
|
||||
Timer::after_secs(5).await;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -250,7 +282,7 @@ async fn main(spawner: Spawner) {
|
|||
// if static IP then use this code:
|
||||
log::info!("main: configure static IP");
|
||||
let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 {
|
||||
address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 1, 113), 24),
|
||||
address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 1, 39), 24),
|
||||
dns_servers: Vec::new(),
|
||||
gateway: Some(Ipv4Address::new(192, 168, 1, 254)),
|
||||
});
|
||||
|
|
@ -267,6 +299,7 @@ async fn main(spawner: Spawner) {
|
|||
defmt::unwrap!(spawner.spawn(net_task(runner)));
|
||||
|
||||
log::info!("main: await network join");
|
||||
Timer::after_millis(1000).await;
|
||||
loop {
|
||||
match control
|
||||
.join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes()))
|
||||
|
|
@ -279,6 +312,8 @@ async fn main(spawner: Spawner) {
|
|||
}
|
||||
Timer::after_millis(100).await;
|
||||
}
|
||||
log::info!("main: network up");
|
||||
Timer::after_millis(1000).await;
|
||||
|
||||
// Wait for DHCP, not necessary when using static IP
|
||||
/*log::info!("waiting for DHCP...");
|
||||
|
|
@ -301,31 +336,37 @@ async fn main(spawner: Spawner) {
|
|||
// Set up the SCD40 I2C sensor
|
||||
|
||||
log::info!("Starting I2C Comms with SCD40");
|
||||
Timer::after_millis(1000).await;
|
||||
// this code derived from: https://github.com/SvetlinZarev/libscd/blob/main/examples/embassy-scd4x/src/main.rs
|
||||
// TODO: how to make pins configurable?
|
||||
let sda = p.PIN_26;
|
||||
let scl = p.PIN_27;
|
||||
let i2c = i2c::I2c::new_blocking(p.I2C1, scl, sda, Config::default());
|
||||
log::info!("Initialise Scd4x");
|
||||
Timer::after_millis(1000).await;
|
||||
let mut scd = Scd4x::new(i2c, Delay);
|
||||
|
||||
// When re-programming, the controller will be restarted, but not the sensor. We try to stop it
|
||||
// in order to prevent the rest of the commands failing.
|
||||
log::info!("Stop periodic measurements");
|
||||
Timer::after_millis(1000).await;
|
||||
_ = scd.stop_periodic_measurement();
|
||||
|
||||
log::info!("Sensor serial number: {:?}", scd.serial_number());
|
||||
Timer::after_millis(1000).await;
|
||||
if let Err(e) = scd.start_periodic_measurement() {
|
||||
log::error!("Failed to start periodic measurement: {:?}", e );
|
||||
|
||||
}
|
||||
|
||||
log::info!("Spawn Sensor worker");
|
||||
Timer::after_millis(1000).await;
|
||||
spawner.must_spawn(read_co2(scd, shared_scd40data));
|
||||
|
||||
////////////////////////////////////////////
|
||||
// Set up the HTTP service
|
||||
|
||||
|
||||
log::info!("Commence HTTP service");
|
||||
Timer::after_millis(5000).await;
|
||||
|
||||
let app = make_static!(AppRouter<AppProps>, AppProps.build_app());
|
||||
|
||||
|
|
@ -336,7 +377,6 @@ async fn main(spawner: Spawner) {
|
|||
read_request: Some(Duration::from_secs(1)),
|
||||
write: Some(Duration::from_secs(1)),
|
||||
})
|
||||
.keep_connection_alive()
|
||||
);
|
||||
|
||||
for id in 0..WEB_TASK_POOL_SIZE {
|
||||
|
|
@ -348,5 +388,6 @@ async fn main(spawner: Spawner) {
|
|||
AppState{ shared_scd40data },
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue