The Pico W now serves a basic HTML page.

This has not been so simple. The only way I could find to make picoserve
and embassy compatible was to get a local copy of picoserve and change
its embassy dependencies to use git as their source. Otherwise there are
conflicts about embassy-timer-driver versions. Using picoserve also
required changing my Rust "channel" to "nightly". A bunch of stuff I
am not keen on, but necessary to progress rather than get bogged down in
build system meta.

Meanwhile also I've changed this to use a static IP at 192.168.3.14 for
now. For currently unknownr reasons the loop waiting for the DHCP lease
to work isn't exiting. I have rebooted the DHCP server and that didn't
help and given the WiFi on the Pico W is working I don't really know
whats up.
This commit is contained in:
Yvan 2025-03-14 17:42:07 +00:00
parent f5ce5e7958
commit bd9389cfd5
7 changed files with 1039 additions and 323 deletions

1210
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -7,6 +7,8 @@ version = "0.1.0"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
[dependencies] [dependencies]
# web server
picoserve = { version = "0.15.0", path = "../picoserve/picoserve/", features = ["embassy"] }
embassy-embedded-hal = { version = "0.3.0", git = "https://github.com/embassy-rs/embassy.git", features = ["defmt"] } embassy-embedded-hal = { version = "0.3.0", git = "https://github.com/embassy-rs/embassy.git", features = ["defmt"] }
embassy-sync = { version = "0.6.2", git = "https://github.com/embassy-rs/embassy.git", features = ["defmt"] } embassy-sync = { version = "0.6.2", git = "https://github.com/embassy-rs/embassy.git", features = ["defmt"] }
embassy-executor = { version = "0.7.0", git = "https://github.com/embassy-rs/embassy.git", features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } embassy-executor = { version = "0.7.0", git = "https://github.com/embassy-rs/embassy.git", features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] }
@ -25,13 +27,14 @@ defmt-rtt = "0.4"
fixed = "1.23.1" fixed = "1.23.1"
fixed-macro = "1.2" fixed-macro = "1.2"
# for web request example # for web request example
#reqwless = { version = "0.13.0", features = ["defmt"] } #reqwless = { version = "0.13.0", features = ["defmt"] }
#serde = { version = "1.0.203", default-features = false, features = ["derive"] } #serde = { version = "1.0.203", default-features = false, features = ["derive"] }
#serde-json-core = "0.5.1" #serde-json-core = "0.5.1"
# for assign resources example # for assign resources example
#assign-resources = { git = "https://github.com/adamgreig/assign-resources", rev = "94ad10e2729afdf0fd5a77cd12e68409a982f58a" } #assign-resources = { rev = "94ad10e2729afdf0fd5a77cd12e68409a982f58a" }
#cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } #cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] }
cortex-m = { version = "0.7.6", features = ["inline-asm"] } cortex-m = { version = "0.7.6", features = ["inline-asm"] }

View file

@ -1,17 +1,5 @@
MEMORY { MEMORY {
BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100
FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100
RAM : ORIGIN = 0x20000000, LENGTH = 264K
/* Pick one of the two options for RAM layout */
/* OPTION A: Use all RAM banks as one big block */
/* Reasonable, unless you are doing something */
/* really particular with DMA or other concurrent */
/* access that would benefit from striping */
RAM : ORIGIN = 0x20000000, LENGTH = 264K
/* OPTION B: Keep the unstriped sections separate */
/* RAM: ORIGIN = 0x20000000, LENGTH = 256K */
/* SCRATCH_A: ORIGIN = 0x20040000, LENGTH = 4K */
/* SCRATCH_B: ORIGIN = 0x20041000, LENGTH = 4K */
} }

View file

@ -1,5 +1,5 @@
[toolchain] [toolchain]
channel = "1.84" channel = "nightly"
components = [ "rust-src", "rustfmt", "llvm-tools" ] components = [ "rust-src", "rustfmt", "llvm-tools" ]
targets = [ targets = [
"thumbv6m-none-eabi", "thumbv6m-none-eabi",

12
src/html/index.html Normal file
View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Meat-Pi</title>
<link rel="stylesheet" href="main.css">
</head>
<body>
<h1>Meat-Pi 🍖🌡️</h1>
</body>
</html>

19
src/html/main.css Normal file
View file

@ -0,0 +1,19 @@
:root {
color-scheme: light dark;
--light-bg: #ddffdd;
--light-color: #002200;
--dark-bg: #002200;
--dark-color: #ddffdd;
}
* {
background-color: light-dark(var(--light-bg), var(--dark-bg));
color: light-dark(var(--light-color), var(--dark-color));
}
body {
display: flex;
flex-flow: column nowrap;
align-items: center;
font-family: Arial, sans-serif;
}

View file

@ -1,17 +1,14 @@
//! This example test the RP Pico W on board LED. //! Raspberry Pi Pico W meat thermometer
//!
//! It does not work with the RP Pico board. See blinky.rs.
#![no_std] #![no_std]
#![no_main] #![no_main]
// required for impl in AppProps code for picoserve
use core::str::from_utf8; #![feature(impl_trait_in_assoc_type)]
use cyw43::JoinOptions; use cyw43::JoinOptions;
use cyw43_pio::{PioSpi, DEFAULT_CLOCK_DIVIDER}; use cyw43_pio::{PioSpi, DEFAULT_CLOCK_DIVIDER};
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_net::{Config, StackResources}; use embassy_net::{Config, StackResources, Ipv4Cidr, Ipv4Address};
use embassy_net::tcp::TcpSocket;
use embassy_rp::bind_interrupts; use embassy_rp::bind_interrupts;
use embassy_rp::clocks::RoscRng; use embassy_rp::clocks::RoscRng;
use embassy_rp::gpio::{Level, Output}; use embassy_rp::gpio::{Level, Output};
@ -20,7 +17,13 @@ use embassy_rp::peripherals::USB;
use embassy_rp::pio::{InterruptHandler, Pio}; use embassy_rp::pio::{InterruptHandler, Pio};
use embassy_rp::usb::{Driver, InterruptHandler as USBInterruptHandler}; use embassy_rp::usb::{Driver, InterruptHandler as USBInterruptHandler};
use embassy_time::{Duration, Timer}; use embassy_time::{Duration, Timer};
use embedded_io_async::Write; use heapless::Vec;
use picoserve::{
make_static,
routing::{get_service, PathRouter},
AppBuilder, AppRouter
};
use picoserve::response::File;
use rand::RngCore; use rand::RngCore;
use static_cell::StaticCell; use static_cell::StaticCell;
use {defmt_rtt as _, panic_probe as _}; use {defmt_rtt as _, panic_probe as _};
@ -51,6 +54,54 @@ async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'sta
runner.run().await runner.run().await
} }
// picoserve HTTP code kicked off using: https://github.com/sammhicks/picoserve/blob/main/examples/embassy/hello_world/src/main.rs
struct AppProps;
impl AppBuilder for AppProps {
type PathRouter = impl PathRouter;
fn build_app(self) -> picoserve::Router<Self::PathRouter> {
picoserve::Router::new()
.route(
"/",
get_service(File::html(include_str!("html/index.html")))
)
.route(
"/main.css",
get_service(File::css(include_str!("html/main.css")))
)
}
}
// 2 is plenty of a little IoT thermometer, right?
const WEB_TASK_POOL_SIZE: usize = 2;
#[embassy_executor::task(pool_size = WEB_TASK_POOL_SIZE)]
async fn web_task(
id: usize,
stack: embassy_net::Stack<'static>,
app: &'static AppRouter<AppProps>,
config: &'static picoserve::Config<Duration>,
) -> ! {
let port = 80;
let mut tcp_rx_buffer = [0; 1024];
let mut tcp_tx_buffer = [0; 1024];
let mut http_buffer = [0; 2048];
picoserve::listen_and_serve(
id,
app,
config,
stack,
port,
&mut tcp_rx_buffer,
&mut tcp_tx_buffer,
&mut http_buffer,
)
.await
}
#[embassy_executor::main] #[embassy_executor::main]
async fn main(spawner: Spawner) { async fn main(spawner: Spawner) {
let p = embassy_rp::init(Default::default()); let p = embassy_rp::init(Default::default());
@ -102,8 +153,13 @@ async fn main(spawner: Spawner) {
.set_power_management(cyw43::PowerManagementMode::PowerSave) .set_power_management(cyw43::PowerManagementMode::PowerSave)
.await; .await;
let config = Config::dhcpv4(Default::default()); //let config = Config::dhcpv4(Default::default());
let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 {
address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 3, 14), 24),
dns_servers: Vec::new(),
gateway: Some(Ipv4Address::new(192, 168, 3, 1)),
});
// Generate random seed // Generate random seed
let seed = rng.next_u64(); let seed = rng.next_u64();
@ -126,18 +182,35 @@ async fn main(spawner: Spawner) {
} }
// Wait for DHCP, not necessary when using static IP // Wait for DHCP, not necessary when using static IP
log::info!("waiting for DHCP..."); /*log::info!("waiting for DHCP...");
while !stack.is_config_up() { while !stack.is_config_up() {
Timer::after_millis(100).await; Timer::after_millis(100).await;
} }
log::info!("DHCP is now up!"); log::info!("DHCP is now up!");*/
// And now we can use it! // And now we can use it!
let app = make_static!(AppRouter<AppProps>, AppProps.build_app());
let config = make_static!(
picoserve::Config<Duration>,
picoserve::Config::new(picoserve::Timeouts {
start_read_request: Some(Duration::from_secs(5)),
read_request: Some(Duration::from_secs(1)),
write: Some(Duration::from_secs(1)),
})
.keep_connection_alive()
);
for id in 0..WEB_TASK_POOL_SIZE {
spawner.must_spawn(web_task(id, stack, app, config));
}
/*
let mut rx_buffer = [0; 4096]; let mut rx_buffer = [0; 4096];
let mut tx_buffer = [0; 4096]; let mut tx_buffer = [0; 4096];
let mut buf = [0; 4096]; let mut buf = [0; 4096];
let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
//let delay = Duration::from_secs(1); //let delay = Duration::from_secs(1);
log::info!("main: pre-loop"); log::info!("main: pre-loop");
loop { loop {
@ -145,7 +218,7 @@ async fn main(spawner: Spawner) {
socket.set_timeout(Some(Duration::from_secs(10))); socket.set_timeout(Some(Duration::from_secs(10)));
control.gpio_set(0, false).await; control.gpio_set(0, false).await;
log::info!("Listening on TCP:1234..."); log::info!("Listening on TCP:00...");
if let Err(e) = socket.accept(1234).await { if let Err(e) = socket.accept(1234).await {
log::warn!("accept error: {:?}", e); log::warn!("accept error: {:?}", e);
continue; continue;
@ -183,4 +256,5 @@ async fn main(spawner: Spawner) {
log::info!("LED off!"); log::info!("LED off!");
control.gpio_set(0, false).await; control.gpio_set(0, false).await;
} }
*/
} }