mirror of
https://github.com/kittywitch/esp32-c3-meepy.git
synced 2026-02-09 07:59:18 -08:00
feat: various attempts
This commit is contained in:
parent
065c6dfae9
commit
81700d30b6
6 changed files with 854 additions and 25 deletions
185
src/http.rs
Normal file
185
src/http.rs
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
// Copyright Claudio Mattera 2024-2025.
|
||||
//
|
||||
// Distributed under the MIT License or the Apache 2.0 License at your option.
|
||||
// See the accompanying files LICENSE-MIT.txt and LICENSE-APACHE-2.0.txt, or
|
||||
// online at
|
||||
// https://opensource.org/licenses/MIT
|
||||
// https://opensource.org/licenses/Apache-2.0
|
||||
|
||||
//! HTTP client
|
||||
|
||||
use alloc::format;
|
||||
use alloc::string::FromUtf8Error;
|
||||
use alloc::string::String;
|
||||
use embassy_net::dns::DnsSocket;
|
||||
use alloc::vec::Vec;
|
||||
use embassy_net::dns::Error as DnsError;
|
||||
use embassy_net::tcp::client::TcpClient;
|
||||
use embassy_net::tcp::client::TcpClientState;
|
||||
use embassy_net::tcp::ConnectError as TcpConnectError;
|
||||
use embassy_net::tcp::Error as TcpError;
|
||||
use embassy_net::Stack;
|
||||
use log::debug;
|
||||
|
||||
use reqwless::client::HttpClient;
|
||||
use reqwless::client::TlsConfig;
|
||||
use reqwless::client::TlsVerify;
|
||||
use reqwless::headers::ContentType;
|
||||
use reqwless::request::Method;
|
||||
use reqwless::request::RequestBuilder;
|
||||
use reqwless::Error as ReqlessError;
|
||||
|
||||
use rand_core::RngCore as _;
|
||||
|
||||
use crate::RngWrapper;
|
||||
use crate::NTFY_TOKEN;
|
||||
|
||||
/// Response size
|
||||
const RESPONSE_SIZE: usize = 4096;
|
||||
|
||||
/// HTTP client
|
||||
///
|
||||
/// This trait exists to be extended with requests to specific sites, like in
|
||||
/// [`WorldTimeApiClient`][crate::worldtimeapi::WorldTimeApiClient].
|
||||
pub trait ClientTrait {
|
||||
/// Send an HTTP request
|
||||
async fn send_request(&mut self, url: &str) -> Result<String, Error>;
|
||||
}
|
||||
|
||||
/// HTTP client
|
||||
pub struct Client {
|
||||
/// Wifi stack
|
||||
stack: Stack<'static>,
|
||||
|
||||
/// Random numbers generator
|
||||
rng: RngWrapper,
|
||||
|
||||
/// TCP client state
|
||||
tcp_client_state: TcpClientState<1, 4096, 4096>,
|
||||
|
||||
/// Buffer for received TLS data
|
||||
read_record_buffer: [u8; 16640],
|
||||
|
||||
/// Buffer for transmitted TLS data
|
||||
write_record_buffer: [u8; 16640],
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Create a new client
|
||||
pub fn new(stack: Stack<'static>, rng: RngWrapper) -> Self {
|
||||
debug!("Create TCP client state");
|
||||
let tcp_client_state = TcpClientState::<1, 4096, 4096>::new();
|
||||
|
||||
Self {
|
||||
stack,
|
||||
rng,
|
||||
|
||||
tcp_client_state,
|
||||
|
||||
read_record_buffer: [0_u8; 16640],
|
||||
write_record_buffer: [0_u8; 16640],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientTrait for Client {
|
||||
async fn send_request(&mut self, url: &str) -> Result<String, Error> {
|
||||
debug!("Send HTTPs request to {url}");
|
||||
|
||||
debug!("Create DNS socket");
|
||||
let dns_socket = DnsSocket::new(self.stack);
|
||||
|
||||
let seed = self.rng.next_u64();
|
||||
let tls_config = TlsConfig::new(
|
||||
seed,
|
||||
&mut self.read_record_buffer,
|
||||
&mut self.write_record_buffer,
|
||||
TlsVerify::None,
|
||||
);
|
||||
|
||||
debug!("Create TCP client");
|
||||
let tcp_client = TcpClient::new(self.stack, &self.tcp_client_state);
|
||||
|
||||
debug!("Create HTTP client");
|
||||
let mut client = HttpClient::new_with_tls(&tcp_client, &dns_socket, tls_config);
|
||||
|
||||
debug!("Create HTTP request");
|
||||
let mut buffer = [0_u8; 4096];
|
||||
let mut request = client.request(Method::GET, url).await?;
|
||||
|
||||
debug!("Send HTTP request");
|
||||
let auth = format!("Bearer {}", NTFY_TOKEN);
|
||||
let auth_str = auth.as_str();
|
||||
let headers = [("Authorization", auth_str)];
|
||||
// -H "Authorization: Bearer ''${SSH_NOTIFY_TOKEN}" \=
|
||||
let mut response = request
|
||||
//.host("ntfy.kittywit.ch")
|
||||
.content_type(ContentType::TextPlain)
|
||||
.headers(&headers);
|
||||
let mut response = response
|
||||
.send(&mut buffer).await?;
|
||||
|
||||
debug!("Response status: {:?}", response.status);
|
||||
|
||||
let buffer = response.body().read_to_end().await?;
|
||||
|
||||
debug!("Read {} bytes", buffer.len());
|
||||
|
||||
let mut vecy = Vec::new();
|
||||
vecy.extend_from_slice(buffer);
|
||||
let output = String::from_utf8(vecy)?;
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
|
||||
/// An error within an HTTP request
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
// Error turning it into a utf-8 string
|
||||
FromUtf8Error(FromUtf8Error),
|
||||
|
||||
/// Response was too large
|
||||
ResponseTooLarge,
|
||||
|
||||
/// Error within TCP streams
|
||||
Tcp(TcpError),
|
||||
|
||||
/// Error within TCP connection
|
||||
TcpConnect(#[expect(unused, reason = "Never read directly")] TcpConnectError),
|
||||
|
||||
/// Error within DNS system
|
||||
Dns(#[expect(unused, reason = "Never read directly")] DnsError),
|
||||
|
||||
/// Error in HTTP client
|
||||
Reqless(#[expect(unused, reason = "Never read directly")] ReqlessError),
|
||||
}
|
||||
|
||||
impl From<FromUtf8Error> for Error {
|
||||
fn from(error: FromUtf8Error) -> Self {
|
||||
Self::FromUtf8Error(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TcpError> for Error {
|
||||
fn from(error: TcpError) -> Self {
|
||||
Self::Tcp(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TcpConnectError> for Error {
|
||||
fn from(error: TcpConnectError) -> Self {
|
||||
Self::TcpConnect(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DnsError> for Error {
|
||||
fn from(error: DnsError) -> Self {
|
||||
Self::Dns(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ReqlessError> for Error {
|
||||
fn from(error: ReqlessError) -> Self {
|
||||
Self::Reqless(error)
|
||||
}
|
||||
}
|
||||
71
src/main.rs
71
src/main.rs
|
|
@ -5,13 +5,20 @@
|
|||
extern crate alloc;
|
||||
|
||||
|
||||
use alloc::format;
|
||||
use alloc::{format, string::{String, ToString}, vec::Vec};
|
||||
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, once_lock::OnceLock, rwlock::RwLock};
|
||||
use esp_alloc as _;
|
||||
use esp_backtrace as _;
|
||||
use esp_hal::{peripherals::RSA, rsa::Rsa};
|
||||
|
||||
use crate::http::ClientTrait;
|
||||
|
||||
use {
|
||||
embassy_net::Runner,
|
||||
core::error::Error,
|
||||
embassy_net::{
|
||||
Runner,
|
||||
|
||||
},
|
||||
embassy_time::{Duration, Timer},
|
||||
display_interface_spi::SPIInterface, embassy_executor::Spawner, embedded_graphics::{
|
||||
mono_font::{ascii::FONT_6X10, MonoTextStyle},
|
||||
|
|
@ -36,7 +43,14 @@ use {
|
|||
esp_println::println
|
||||
};
|
||||
|
||||
use embassy_net::{Stack, StackResources};
|
||||
mod rng;
|
||||
mod http;
|
||||
|
||||
use {
|
||||
rng::RngWrapper,
|
||||
};
|
||||
|
||||
use embassy_net::{dns::DnsSocket, tcp::client::{TcpClient, TcpClientState}, IpAddress, Stack, StackResources};
|
||||
#[cfg(target_arch = "riscv32")]
|
||||
use esp_hal::interrupt::software::SoftwareInterruptControl;
|
||||
use esp_radio::{
|
||||
|
|
@ -47,6 +61,10 @@ use esp_radio::{
|
|||
|
||||
const SSID: &str = env!("WIFI_SSID");
|
||||
const PASSWORD: &str = env!("WIFI_PASSWORD");
|
||||
const NTFY_TOKEN: &str = env!("NTFY_TOKEN");
|
||||
const NTFY_SCHEME: &str = env!("NTFY_SCHEME");
|
||||
const NTFY_UPSTREAM: &str = env!("NTFY_UPSTREAM");
|
||||
const NTFY_SUBPATH: &str = env!("NTFY_SUBPATH");
|
||||
|
||||
esp_bootloader_esp_idf::esp_app_desc!();
|
||||
|
||||
|
|
@ -320,30 +338,44 @@ struct Controller<'tft> {
|
|||
}
|
||||
|
||||
impl <'tft>Controller<'tft> {
|
||||
async fn init(spawner: Spawner, mut display: TFT<'tft>, stack: Stack<'static>) -> Self {
|
||||
let wifi = ControllerWifi::init_wifi(spawner, &mut display, stack).await;
|
||||
async fn init(mut display: TFT<'tft>, stack: Stack<'static>) -> Self {
|
||||
let mut wifi = ControllerWifi::init_wifi(&mut display, stack).await;
|
||||
let mut controller = Self {
|
||||
display,
|
||||
wifi,
|
||||
};
|
||||
if let Some(config) = controller.wifi.stack.config_v4() {
|
||||
controller.display.fullscreen_alert(&format!("Controller initialized!\nCurrent IP address: {}", config.address), true);
|
||||
let dns_servers = config.dns_servers
|
||||
.iter()
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
controller.display.fullscreen_alert(&format!("Controller initialized!\nCurrent IP address: {}\nDNS: {}", config.address, dns_servers), true);
|
||||
}
|
||||
controller
|
||||
}
|
||||
async fn req(&mut self) {
|
||||
//let url = format!("{}://{}/{}/raw", NTFY_SCHEME, NTFY_UPSTREAM, NTFY_SUBPATH);
|
||||
let url = "https://ntfy.kittywit.ch/alerts/raw";
|
||||
match self.wifi.client.send_request(&url).await {
|
||||
Ok(dat) => self.display.fullscreen_alert(&dat, true),
|
||||
Err(err) => {
|
||||
let out = format!("Error in request: {:?}", err);
|
||||
log::error!("{}", out);
|
||||
self.display.fullscreen_alert(&out, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ControllerWifi {
|
||||
stack: Stack<'static>,
|
||||
client: http::Client,
|
||||
|
||||
}
|
||||
|
||||
impl ControllerWifi {
|
||||
async fn init_wifi(spawner: Spawner, display: &mut TFT<'_>, stack: Stack<'static>) -> Self {
|
||||
|
||||
|
||||
let mut rx_buffer = [0; 4096];
|
||||
let mut tx_buffer = [0; 4096];
|
||||
|
||||
async fn init_wifi(display: &mut TFT<'_>, stack: Stack<'static>) -> Self {
|
||||
println!("Waiting to get IP address...");
|
||||
display.fullscreen_alert("Waiting to obtain an IP address", true);
|
||||
loop {
|
||||
|
|
@ -354,11 +386,14 @@ impl ControllerWifi {
|
|||
}
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
let rng = Rng::new();
|
||||
let client = http::Client::new(stack, RngWrapper::from(rng));
|
||||
Self {
|
||||
stack
|
||||
stack,
|
||||
client,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
|
|
@ -420,7 +455,7 @@ async fn main(spawner: Spawner) {
|
|||
#[cfg(feature = "log")]
|
||||
// The default log level can be specified here.
|
||||
// You can see the esp-println documentation: https://docs.rs/esp-println
|
||||
esp_println::logger::init_logger(log::LevelFilter::Info);
|
||||
esp_println::logger::init_logger(log::LevelFilter::Debug);
|
||||
|
||||
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||
let peripherals: Peripherals = init(config);
|
||||
|
|
@ -467,7 +502,11 @@ async fn main(spawner: Spawner) {
|
|||
spawner.spawn(connection(wifi_controller)).ok();
|
||||
spawner.spawn(net_task(runner)).ok();
|
||||
|
||||
let controller = Controller::init(spawner, display, stack).await;
|
||||
|
||||
let mut controller = Controller::init(display, stack).await;
|
||||
|
||||
Timer::after(Duration::from_millis(5000)).await;
|
||||
controller.req().await;
|
||||
|
||||
loop {
|
||||
// your business logic
|
||||
|
|
|
|||
56
src/rng.rs
Normal file
56
src/rng.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright Claudio Mattera 2024-2025.
|
||||
//
|
||||
// Distributed under the MIT License or the Apache 2.0 License at your option.
|
||||
// See the accompanying files LICENSE-MIT.txt and LICENSE-APACHE-2.0.txt, or
|
||||
// online at
|
||||
// https://opensource.org/licenses/MIT
|
||||
// https://opensource.org/licenses/Apache-2.0
|
||||
|
||||
//! Random numbers generator
|
||||
|
||||
use rand_core::CryptoRng;
|
||||
use rand_core::RngCore;
|
||||
|
||||
use esp_hal::rng::Rng;
|
||||
|
||||
/// A wrapper for ESP random number generator that implement traits form
|
||||
/// `rand_core`
|
||||
#[derive(Clone)]
|
||||
pub struct RngWrapper(Rng);
|
||||
|
||||
impl From<Rng> for RngWrapper {
|
||||
fn from(rng: Rng) -> Self {
|
||||
Self(rng)
|
||||
}
|
||||
}
|
||||
|
||||
impl RngCore for RngWrapper {
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
self.0.random()
|
||||
}
|
||||
|
||||
fn next_u64(&mut self) -> u64 {
|
||||
u32_pair_to_u64(self.next_u32(), self.next_u32())
|
||||
}
|
||||
|
||||
fn fill_bytes(&mut self, dest: &mut [u8]) {
|
||||
for value in dest.iter_mut() {
|
||||
let [random_value, _, _, _] = self.next_u32().to_ne_bytes();
|
||||
*value = random_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CryptoRng for RngWrapper {}
|
||||
|
||||
/// Join a pair of `u32` into a `u64`
|
||||
#[allow(
|
||||
clippy::many_single_char_names,
|
||||
clippy::min_ident_chars,
|
||||
reason = "This is still readable"
|
||||
)]
|
||||
fn u32_pair_to_u64(first: u32, second: u32) -> u64 {
|
||||
let [a, b, c, d] = first.to_ne_bytes();
|
||||
let [e, f, g, h] = second.to_ne_bytes();
|
||||
u64::from_ne_bytes([a, b, c, d, e, f, g, h])
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue