esp32笔记[11]-rust的网页服务器

摘要

使用rust(no-std)环境和esp-hal库实现一个简易的http网页服务器,可以GET和POST数据;使用embassy实现异步操作;使用build.rs生成字节数组嵌入网页数据到rust代码中.

平台信息

  • esp32(模组:ESP32-WROOM-32D)
    • (xtensa lx6)(xtensa-esp32-none-elf)
  • rust

超链接

esp32笔记[7]-使用rust+zig开发入门

开源地址

完整代码请移步:
[https://gitcode.net/QS2002/sugarheart]

原理简介

rust的build.rs进行编译预处理

[https://juejin.cn/post/7255230505409544247]
[https://doc.rust-lang.org/cargo/reference/build-scripts.html]
在Rust中,build.rs文件是一个特殊的脚本,它会在编译过程开始之前被执行,可以用于实现一些项目的预处理操作,比如代码生成、调用cmake等.

build.rs文件是在Cargo.toml文件中通过build-dependencies部分引入的,它需要在项目的根目录下创建一个名为 build.rs 的文件。然后,在Cargo.toml文件中的[build-dependencies]部分引入相应的依赖,如build-dependencies = "build.rs".

rust的embassy简介

[https://embassy.dev/book/dev/getting_started.html]
[https://zhuanlan.zhihu.com/p/604927675]
Rust + async ❤️ embedded

The Rust programming language is blazingly fast and memory-efficient, with no runtime, garbage collector or OS. It catches a wide variety of bugs at compile time, thanks to its full memory- and thread-safety, and expressive type system.

Rust's async/await allows for unprecedently easy and efficient multitasking in embedded systems. Tasks get transformed at compile time into state machines that get run cooperatively. It requires no dynamic memory allocation, and runs on a single stack, so no per-task stack size tuning is required. It obsoletes the need for a traditional RTOS with kernel context switching, and is faster and smaller than one!
The embassy-net network stack implements extensive networking functionality, including Ethernet, IP, TCP, UDP, ICMP and DHCP. Async drastically simplifies managing timeouts and serving multiple connections concurrently.
Rust编程语言以其极速和高效的内存管理而闻名,它没有运行时、垃圾回收器或操作系统的负担。由于其完整的内存和线程安全性以及表达力强大的类型系统,它在编译时能够捕获各种类型的错误。

Rust的async/await功能使得在嵌入式系统中进行多任务处理变得前所未有的简单和高效。任务在编译时被转换为状态机,并以协作方式运行。它不需要动态内存分配,并且在单个堆栈上运行,因此不需要针对每个任务进行堆栈大小的调整。它取代了传统的具有内核上下文切换的实时操作系统(RTOS),并且比传统RTOS更快、更小巧!

Embassy-net网络堆栈实现了广泛的网络功能,包括以太网、IP、TCP、UDP、ICMP和DHCP。异步编程极大地简化了管理超时和同时处理多个连接的操作。

综上所述,Rust的Embassy框架在嵌入式系统中提供了强大而易于使用的异步编程能力,帮助开发者编写安全、正确和高效的嵌入式代码。

Rust的Embassy是一个异步框架,主要用于嵌入式环境,使得异步/await成为嵌入式开发的首选。

Embassy框架的主要工作原理是通过创建任务(Task)和消息队列(Message Queue),实现任务间的异步通信。任务可以通过消息队列进行事件的发送和接收,从而实现任务间的通信。此外,Embassy还支持定时器和事件循环,可以帮助用户更好地控制任务的执行。

总的来说,Embassy是一个功能强大,易于使用的嵌入式异步框架,可以帮助开发者更好地进行嵌入式开发。

示例程序:

//! embassy hello world
//!
//! This is an example of running the embassy executor with multiple tasks
//! concurrently.

#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]

use embassy_executor::Spawner;
use embassy_time::{Duration, Timer};
use esp32_hal::{
    clock::ClockControl,
    embassy::{self},
    peripherals::Peripherals,
    prelude::*,
    timer::TimerGroup,
};
use esp_backtrace as _;

#[embassy_executor::task]
async fn run() {
    loop {
        esp_println::println!("Hello world from embassy using esp-hal-async!");
        Timer::after(Duration::from_millis(1_000)).await;
    }
}

#[main]
async fn main(spawner: Spawner) -> ! {
    esp_println::println!("Init!");
    let peripherals = Peripherals::take();
    let system = peripherals.SYSTEM.split();
    let clocks = ClockControl::boot_defaults(system.clock_control).freeze();

    let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
    embassy::init(&clocks, timer_group0.timer0);

    spawner.spawn(run()).ok();

    loop {
        esp_println::println!("Bing!");
        Timer::after(Duration::from_millis(5_000)).await;
    }
}

http1.0报文简介

HTTP报文是在HTTP应用程序之间传输的数据块。它由一些以文本形式的元信息开头,用于HTTP协议的交互。HTTP报文分为请求报文和响应报文两种类型。

请求报文是客户端发送给服务器的报文,用于请求执行某个动作。请求报文的格式包括起始行、头部和主体三个部分。起始行包括请求方法、请求URL和HTTP协议版本。头部包含了更详细地描述报文的键值对形式的信息。主体部分是可选的,用于传输请求的数据。

响应报文是服务器发送给客户端的报文,作为对请求的响应。响应报文的格式包括起始行、头部和主体三个部分。起始行包括状态码和状态消息,用于表示请求的处理结果。头部包含了更详细地描述报文的键值对形式的信息。主体部分是可选的,用于传输响应的数据。

HTTP报文是以ASCII编码的文本信息组成的,可以跨越多行。在HTTP/1.1及之前的版本中,这些报文是明文发送的。而在HTTP/2中,报文被分割成多个HTTP帧进行传输,以提供优化和性能改进。

总结一下,HTTP报文是在HTTP应用程序之间传输的数据块,分为请求报文和响应报文两种类型。它们由起始行、头部和主体组成,以ASCII编码的文本形式进行传输。

HTTP(Hypertext Transfer Protocol)是一种用于在网络上传输超文本的协议。它使用客户端-服务器模型,客户端发送HTTP请求,服务器返回HTTP响应。HTTP请求和响应都由报文组成,报文是由多行数据构成的字符串文本。

HTTP1.0的GET请求报文示例:

GET /path/to/resource HTTP/1.0
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36
Accept: text/html,application/xhtml+xml

HTTP1.0的POST请求报文示例:

POST /path/to/resource HTTP/1.0
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Content-Length: 15

username=admin&password=123456

以上是HTTP1.0版本的GET和POST请求报文示例。GET请求通过URL传递参数,请求行中包含请求方法(GET)、资源路径和HTTP协议版本。请求头部包含主机名(Host)、用户代理(User-Agent)和接受的内容类型(Accept)等信息。

POST请求与GET请求的区别在于,POST请求将数据放在请求体中,请求头部还包含了内容类型(Content-Type)和内容长度(Content-Length)等信息。

实际获取的http报文1:

POST / HTTP/1.1
Cookie: 
Content-Type: application/x-www-form-urlencoded
User-Agent: Dalvik/2.1.0 (Linux; U; Android 13; NX712J Build/TKQ1.221220.001)
Host: 192.168.2.1:8080
Connection: Keep-Alive
Accept-Encoding: gzip
Content-Length: 6

hihhhh

实际获取的报文2:

POST /post_gcode HTTP/1.1
Host: 192.168.2.1:8080
Connection: keep-alive
Content-Length: 14
User-Agent: Mozilla/5.0 (Linux; Android 13; NX712J Build/TKQ1.221220.001) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.5249.126 Mobile Safari/537.36
Content-Type: text/plain
Accept: */*
Origin: http://192.168.2.1:8080
X-Requested-With: mark.via
Referer: http://192.168.2.1:8080/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7

/post_gcode M0

使用\x转义以在字节数组中表示utf-8中文内容

[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Character_escape]
[https://www.cnblogs.com/yiyide266/p/10282708.html]
[https://blog.csdn.net/YungGuo/article/details/110197818]
A character escape represents a character that may not be able to be conveniently represented in its literal form.

\f, \n, \r, \t, \v
\cA, \cB, …, \cz
\0
\^, \$, \\, \., \*, \+, \?, \(, \), \[, \], {, }, \|, \/

\xHH
\uHHHH
\u{HHH}

转换代码:

// 转义特殊字符
let mut escaped_str = str_content.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r");

let mut non_ascii_chars = Vec::new();
for ch in escaped_str.chars() {
    if !ch.is_ascii() {
        let utf8_bytes = ch.to_string().into_bytes();
        let temp_str = utf8_bytes.iter().map(|byte| format!("\\x{:02X}", byte)).collect::<String>();
        non_ascii_chars.push((ch, temp_str));
    }
}

for (ch, temp_str) in non_ascii_chars {
    escaped_str = escaped_str.replace(&ch.to_string(), &temp_str);
}

实现

核心代码

build.rs

/*
Copyright (c) 2023 qsbye
This file is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan PSL v2. 
You may obtain a copy of Mulan PSL v2 at:
        http://license.coscl.org.cn/MulanPSL2 
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.  
See the Mulan PSL v2 for more details.  

说明:
构建脚本,在宿主机构建时运行,有std库
预处理数据并写入mydata.rs
*/
use std::path::Path;
use std::fs::{File,OpenOptions};
use std::io::{self, Write,Seek,Read,Error};
use core::time::Duration;
use std::thread;

fn main() {
    // 清空文件内容
    let mydata_path = Path::new("./src/mydata_temp.rs");
    let mut mydata_file = match File::create(&mydata_path) {
        Err(why) => panic!("couldn't create/open {}: {}", mydata_path.display(), why),
        Ok(file) => file,
    };
    match mydata_file.write_all(b"") {
        Err(why) => {
            println!("clear failed: {:?}", why);
        },
        Ok(()) => {
            println!("clear ok!");
        }
    }

    // 延时1秒  
    thread::sleep(Duration::from_secs(1));  

    // 附加新数据
    // let _ = append_file_to_mydata("./src/data/index.html","webpage_index");
    let _ = append_file_to_mydata("./src/data/test.html","webpage_test");

}

fn append_file_to_mydata(filepath: &str, tag: &str) -> Result<(), io::Error> {
    let mut file_content = Vec::new();
    File::open(filepath)?.read_to_end(&mut file_content)?;

    let str_content = String::from_utf8_lossy(&file_content);
    // 转义特殊字符
    let mut escaped_str = str_content.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r");

    let mut non_ascii_chars = Vec::new();
    // 1个中文字符用3个16进制表示
    for ch in escaped_str.chars() {
        if !ch.is_ascii() {
            let utf8_bytes = ch.to_string().into_bytes();
            let temp_str = utf8_bytes.iter().map(|byte| format!("\\x{:02X}", byte)).collect::<String>();
            non_ascii_chars.push((ch, temp_str));
        }
    }

    for (ch, temp_str) in non_ascii_chars {
        escaped_str = escaped_str.replace(&ch.to_string(), &temp_str);
    }

    let rust_code = format!("pub static {} : &[u8] = b\"HTTP/1.0 200 OK\\r\\n\\r\\n{}\\r\\n\";", tag, escaped_str);

    let mydata_path = Path::new("./src/mydata_temp.rs");
    let mut mydata_file = File::options().append(true).open(&mydata_path)?; 
    writeln!(mydata_file, "{}", rust_code)?; 

    Ok(())
}

test.html

<!DOCTYPE html>
<html lang='en'>
<head>
    <meta charset='UTF-8'>
    <meta name='viewport' content='width=device-width, initial-scale=1.0'>
    <title>POST Data Example</title>
</head>
<body>
    <button id='myButton1' style='width: 100vw; height: 20vh;'>Click Me</button>
    <button id='myButton2' style='width: 100vw; height: 20vh;'>POST OK</button>
    <button id='myButton3' style='width: 100vw; height: 20vh;'>GET DATA</button>
    <span id="dashboardData">仪表盘数据</span>

    <script>
        document.getElementById('myButton1').addEventListener('click', function() {
            // 使用 Fetch API 发送 POST 请求
            fetch('/post_gcode', {
                method: 'POST',
                headers: {
                    'Content-Type': 'text/plain'
                },
                body: '/post_gcode M0'
            })
            .then(response => {
                // 检查响应状态,如果是 200-299 之间的状态码,说明请求成功
                if (response.ok) {
                    console.log('Data posted successfully!');
                } else {
                    console.error('Error posting data:', response.statusText);
                }
            })
            .catch(error => {
                console.error('An error occurred:', error);
            });
        });
        document.getElementById('myButton2').addEventListener('click', function() {
            // 使用 Fetch API 发送 POST 请求
            fetch('/post_ok', {
                method: 'POST',
                headers: {
                    'Content-Type': 'text/plain'
                },
                body: '/post_ok x'
            })
            .then(response => {
                // 检查响应状态,如果是 200-299 之间的状态码,说明请求成功
                if (response.ok) {
                    console.log('Data posted successfully!');
                    console.log(response);
                    document.getElementById('myButton2').style.backgroundColor = 'red';
                    // 打印响应的body内容并判断是否包含"ok"文本
                    response.text().then(bodyText => {
                        console.log('Response body:', bodyText);
                        if (bodyText.includes('ok')) {
                            console.log('Body contains "ok" text!');
                            document.getElementById('myButton2').style.backgroundColor = 'green';
                        } else {
                            console.log('Body does not contain "ok" text.');
                        }
            });
                } else {
                    console.error('Error posting data:', response.statusText);
                    console.log(response);
                    document.getElementById('myButton2').style.backgroundColor = 'blue';
                }
            })
            .catch(error => {
                console.error('An error occurred:', error);
                document.getElementById('myButton2').style.backgroundColor = 'yellow';
            });
        });
        document.getElementById('myButton3').addEventListener('click', function() {
            // 使用 Fetch API 发送 POST 请求
            fetch('/get_data', {
                method: 'POST',
                headers: {
                    'Content-Type': 'text/plain'
                },
                body: '/get_data x'
            })
            .then(response => {
                // 检查响应状态,如果是 200-299 之间的状态码,说明请求成功
                if (response.ok) {
                    console.log('Data posted successfully!');
                    console.log(response);
                    document.getElementById('myButton3').style.backgroundColor = 'red';
                    // 打印响应的body内容并判断是否包含"ok"文本
                    response.text().then(bodyText => {
                        console.log('Response body:', bodyText);
                        console.log('Body contains "ok" text!');
                        document.getElementById('myButton3').style.backgroundColor = 'green';
                        // 更新数据
                        updataDashboard(bodyText);
                        
            });
                } else {
                    console.error('Error posting data:', response.statusText);
                    console.log(response);
                    document.getElementById('myButton3').style.backgroundColor = 'blue';
                }
            })
            .catch(error => {
                console.error('An error occurred:', error);
                document.getElementById('myButton3').style.backgroundColor = 'yellow';
            });
        });

        /*
        更新页面仪表盘数据
        输入:http的body数据
        输出:无
        */
        function updataDashboard(http_body_data){
            let mapping = {
                1: "heatedbed",
                2: "jet",
                3: "hot_jet",
                4: "hot_heatedbed",
                5: "jet_on"
            };
            var dashboardDataElement = document.getElementById('dashboardData');
            // 分割数据字符串
            var pairs = http_body_data.split(';');
            
            // 遍历数据对,解析并显示数据
            var output = '';
            pairs.forEach(function(pair) {
                if (pair !== '') {
                    var keyValue = pair.split('=');
                    var key = keyValue[0];
                    var value = keyValue[1];
                    output += mapping[key] + ': ' + value + '<br>';
                }
            });
            
            // 将解析后的数据显示到页面上
            dashboardDataElement.innerHTML = output;
        }
    </script>
</body>
</html>

main.rs

/*
备注:
- 使用no-std,没有常规的main函数
- 串口波特率115200
- 不要占用SPI FLASH的GPIO(6,7,8,9,10,11)
目标平台:
- esp32s1(xtensa lx6)(xtensa-esp32-none-elf)
依赖:
- esp32-hal(0.16.0)
- esp-backtrace(0.9.0)
- esp-println(0.7.0)
- critical_section(1.1.2)
- esp-wifi(0.1.0)
- smoltcp(0.10.0)
- embedded-svc(0.26.1)
- embedded-io(0.6.1)
- ssd1306(0.8.4)
- embedded_graphics(0.8.1)
- embassy-executor(0.3.2)
- embassy-time(0.1.5)
- embassy-sync(0.4.0)
- embedded-io-async(0.6.0)
- static_cell(2.0.0)
- embassy-net(0.2.1)
- tinybmp(0.5.0)
多任务:
- 核心0:WIFI,http服务器,SD卡
- 核心1:电机,传感器,串口,IIC显示屏
编译及烧录命令:
- cargo-offline run
- (优先使用)cargo build --release
- cargo-offline build --release
- cargo-offline build --example main --release
*/
#![no_std]
#![no_main]
#![allow(unused_imports)]
#![allow(unused_parens)]
#![allow(unused_variables)]
#![allow(unused_unsafe)]
#![allow(dead_code)]
#![allow(unused_mut)]
#![feature(c_variadic)]
#![feature(const_mut_refs)]
#![feature(type_alias_impl_trait)]

/* start 导入库 */
use esp_println::println;//串口打印
use esp_println::print;//串口打印
use core::{
    cell::RefCell, //内部可变性类型RefCell
    fmt::Write, //文本写入接口
    borrow::BorrowMut,//引用借用
};
use critical_section::Mutex;//no-std库专用的Mutex

#[path = "../vendor/esp-wifi/examples-util/util.rs"]
mod examples_util;

use esp32_hal::{
    clock::ClockControl, //时钟控制
    peripherals::{self, Peripherals, TIMG0, TIMG1,UART0,I2C0},//外设控制器
    prelude::*, 
    timer::{Timer, Timer0, TimerGroup},//定时器
    Rtc,//rtc时钟
    Delay,//延时
    gpio::{AnyPin,Input, Output, PullDown, PushPull, IO},//gpio相关
    interrupt,//中断
    interrupt::Priority,//中断优先级
    xtensa_lx,//xtensa_lx架构相关
    uart::config::AtCmdConfig,//串口解析at命令
    Cpu,//cpu相关
    Uart,//串口相关
    macros::ram,//ram宏支持
    Rng,//Rng相关
    i2c::I2C,//i2c相关
    embassy::{self,executor::Executor as EmbassyExecutor},//多任务相关
    cpu_control::{CpuControl, Stack},//多核心相关
};

use embedded_io::*; // wifi相关
use embedded_svc::ipv4::Interface; // wifi相关
use embedded_svc::wifi::{
    AccessPointConfiguration, 
    Configuration, 
    Wifi,
}; // wifi相关
use smoltcp::iface::SocketStorage;// wifi相关
use esp_wifi::{
    initialize as WifiInitialize,
    wifi::utils::create_network_interface,
    wifi::WifiMode,// wifi模式
    wifi::WifiController,// wifi控制器
    wifi::WifiDevice, // wifi设备
    wifi::WifiApDevice,// wifi ap设备
    wifi::WifiEvent, // wifi事件
    wifi::WifiState, // wifi状态
    wifi_interface::WifiStack,
    current_millis, 
    EspWifiInitFor,
};// wifi相关

use esp_backtrace as _;// 获取调用堆栈信息
use nb::block;//在异步上下文中阻塞执行代码
mod mydata;// 导入变量
use crate::mydata::webpage_404;// 导入404页面
use crate::mydata::webpage_index;// 导入index页面
use crate::mydata::webpage_test;// 导入test页面
mod myclass;// 导入自定义类
use crate::myclass::MySTD;// 导入自定义MySTD类
use crate::myclass::MyString;// 导入自定义MyString类
use crate::myclass::KeyValuePair;// 导入自定义KeyValuePair<'a>类
use crate::myclass::MyStringDictionary;// 导入自定义MyStringDictionary<'a>类
use crate::myclass::MyGCODE;// 导入自定义MyGCODE类
use crate::myclass::MyHttp;// 导入自定义MyHttp类

use ssd1306::{
    prelude::*, 
    I2CDisplayInterface, 
    Ssd1306,
    mode::BufferedGraphicsMode,
    mode::BasicMode,
};//ssd1306相关
use embedded_graphics::{
    mono_font::{
        ascii::{FONT_6X10, FONT_9X18_BOLD},
        MonoTextStyleBuilder,
    },
    pixelcolor::BinaryColor,
    prelude::*,
    text::{Alignment, Text},
    pixelcolor::Rgb565,
    pixelcolor::Gray2,
    pixelcolor::Rgb888,
    image::Image,
};// ssd1306相关
use tinybmp::Bmp;// ssd1306相关

use embassy_executor::{Spawner as EmbassySpawner};// 多任务相关
use embassy_time::{
    Duration as EmbassyDuration, 
    Timer as EmbassyTimer,
};// 多任务相关
use embassy_sync::{
    blocking_mutex::raw::CriticalSectionRawMutex as EmbassyMutex, 
    signal::Signal as EmbassySignal,
};//多任务相关
use static_cell::make_static;//多任务相关
use embassy_net::tcp::TcpSocket;//多任务网络相关
use embassy_net::{
    Config as EmbassyNetConfig, 
    IpListenEndpoint, 
    Ipv4Address, 
    Ipv4Cidr, 
    Stack as EmbassyNetStack, 
    StackResources, 
    StaticConfigV4,
};//多任务网络相关
/* end 导入库 */

/* start 全局变量 */
static TIMER0: Mutex<RefCell<Option<Timer<Timer0<TIMG0>>>>> = Mutex::new(RefCell::new(None));
static TIMER1: Mutex<RefCell<Option<Timer<Timer0<TIMG1>>>>> = Mutex::new(RefCell::new(None));
static mut LED_D4: Option<esp32_hal::gpio::Gpio2<Output<PushPull>>> = None;
static mut LED_D5: Option<esp32_hal::gpio::Gpio3<Output<PushPull>>> = None;
static SERIAL0: Mutex<RefCell<Option<Uart<UART0>>>> = Mutex::new(RefCell::new(None));
// 创建一个静态数组作为字符缓冲区
const UART0_RX_BUFFER_SIZE: usize = 32;
static mut UART0_RX_BUFFER: [u8; UART0_RX_BUFFER_SIZE] = [0; UART0_RX_BUFFER_SIZE];
static mut UART0_RX_BUF_INDEX: usize = 0; // 缓冲区当前索引
// 调试参数
const DEBUG_LEVEL_0:bool = true;
const DEBUG_LEVEL_1:bool = true;
// ssd1306全局变量
static DISPLAY: Mutex<RefCell<Option<Ssd1306<I2CInterface<I2C<I2C0>>, DisplaySize128x64, BufferedGraphicsMode<DisplaySize128x64>>>>> = Mutex::new(RefCell::new(None));
/* end 全局变量 */

/* start embassy多任务:任务3 */
#[embassy_executor::task]
async fn task3_connection(mut _controller: WifiController<'static>) {
    println!("start connection task");
    println!("Device capabilities: {:?}", _controller.get_capabilities());
    loop {
        match esp_wifi::wifi::get_wifi_state() {
            WifiState::ApStarted => {
                // wait until we're no longer connected
                _controller.wait_for_event(WifiEvent::ApStop).await;
                EmbassyTimer::after(EmbassyDuration::from_millis(5000)).await
            }
            _ => {}
        }
        if !matches!(_controller.is_started(), Ok(true)) {
            let client_config = Configuration::AccessPoint(AccessPointConfiguration {
                ssid: "sugardraw".into(),
                ..Default::default()
            });
            _controller.set_configuration(&client_config).unwrap();
            println!("Starting wifi");
            _controller.start().await.unwrap();
            println!("Wifi started!");
        }
    }
}
/* end embassy多任务:任务3 */

/* start embassy多任务:任务4 */
#[embassy_executor::task]
async fn task4_net(_stack: &'static EmbassyNetStack<WifiDevice<'static, WifiApDevice>>) {
    _stack.run().await;
}
/* end embassy多任务:任务4 */

/* start embassy多任务:任务5 */
#[embassy_executor::task]
async fn task5_http(_stack: &'static EmbassyNetStack<WifiDevice<'static, WifiApDevice>>,_spawner:EmbassySpawner) {
  let mut wifi_rx_buffer = [0; 1536];
  let mut wifi_tx_buffer = [0; 1536];

  println!("Connect to the AP `sugardraw` and point your browser to http://192.168.2.1:80");
  println!("Use a static IP in the range 192.168.2.2 .. 192.168.2.255, use gateway 192.168.2.1");

  let mut socket = TcpSocket::new(&_stack, &mut wifi_rx_buffer, &mut wifi_tx_buffer);
  socket.set_timeout(Some(EmbassyDuration::from_secs(10)));
  loop {
      println!("Wait for connection...");
      let r = socket
          .accept(IpListenEndpoint {
              addr: None,
              port: 80,
          })
          .await;
      println!("Connected...");

      if let Err(e) = r {
          println!("connect error: {:?}", e);
          continue;
      }

      use embedded_io_async::Write;

      let mut buffer = [0u8; 1024];
      let mut pos = 0;
      loop {
          match socket.read(&mut buffer).await {
              Ok(0) => {
                  println!("read EOF");
                  break;
              }
              Ok(len) => {
                  let to_print =
                      unsafe { core::str::from_utf8_unchecked(&buffer[..(pos + len)]) };

                  if to_print.contains("\r\n\r\n") {
                      print!("{}", to_print);
                      println!();
               
                    if to_print.contains("/post_ok") || to_print.contains("/get_status") || to_print.contains("/get_data"){
                        // 优先处理需要进行回复的http请求:/post_ok,/get_status,/get_data,/index.html
                        if let Some(payload) = MyHttp::extract_data_from_http_response(to_print) {
                            println!("Extracted Important data: {}", payload);
                            // 判断meta_command并进行不同操作
                            if let Some((meta_command,_)) = MyHttp::split_payload(payload){
                                println!("meta_command:{}",meta_command);
                                if(meta_command == "/post_ok"){
                                    println!("rec: /post_ok");
                                    let r = socket.write_all(b"HTTP/1.0 200 OK\r\n\r\nok\r\n").await;
                                    if let Err(e) = r {
                                        println!("write error: {:?}", e);
                                    }
                                    let r = socket.flush().await;
                                    if let Err(e) = r {
                                        println!("flush error: {:?}", e);
                                    }
                                }// end /post_ok
                                else if(meta_command == "/get_status"){
                                    // TODO 获取系统状态
                                    
                                }// end /get_status
                                else if(meta_command == "/get_data"){
                                    // TODO 获取各个数据
                                    let _data = b"HTTP/1.0 200 OK\r\n\r\n1=20;2=25;3=0;4=0;5=0;\r\n";
                                    let r = socket.write_all(_data).await;

                                    if let Err(e) = r {
                                        println!("write error: {:?}", e);
                                    }
                                    let r = socket.flush().await;
                                    if let Err(e) = r {
                                        println!("flush error: {:?}", e);
                                    }
                                }// end /get_data
                            }else{
                                // pass

                                }// end let Some((meta_command,_))
                        }else {
                            println!("Failed to extract important data from the HTTP response");
                        }// end let Some(payload)

                    }// end to_print.contains("\r\n\r\n")
                    else{
                        // 返回index.html
                        if to_print.contains("index.html"){
                            // 传递index.html页面
                            let r = socket.write_all(webpage_test).await;
                            //let r = socket.write_all(webpage_index).await;
                            if let Err(e) = r {
                                println!("write error: {:?}", e);
                            }
                            let r = socket.flush().await;
                            if let Err(e) = r {
                                println!("flush error: {:?}", e);
                          }
                        }// end if to_print.contains("index.html")

                    }// end to_print.contains("/post_ok")等

                    // 自动进行剩余http请求处理
                    MyHttp::auto_http_handler(&to_print,&mut socket,_spawner);
                    break;
                  }
                  pos += len;
              }
              Err(e) => {
                  println!("read error: {:?}", e);
                  break;
              }
          };
        }

      // 关闭本次http连接
      EmbassyTimer::after(EmbassyDuration::from_millis(1000)).await;
      socket.close();
      EmbassyTimer::after(EmbassyDuration::from_millis(1000)).await;
      socket.abort();
  }
}
/* end embassy多任务:任务5 */

/* start 程序入口点 */
#[main]
async fn main(spawner: EmbassySpawner) -> ! {
    // 省略代码
    /* start wifi配置 */
    let wifi_init = WifiInitialize(
        EspWifiInitFor::Wifi,
        timer1,
        Rng::new(peripherals.RNG),
        system.radio_clock_control,
        &clocks,
    );
    let wifi = peripherals.WIFI;
    let (wifi_interface, wifi_controller) = 
            esp_wifi::wifi::new_with_mode(&wifi_init.unwrap(), wifi, WifiApDevice).unwrap();
    let wifi_config = EmbassyNetConfig::ipv4_static(StaticConfigV4 {
        address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 2, 1), 24),
        gateway: Some(Ipv4Address::from_bytes(&[192, 168, 2, 1])),
        dns_servers: Default::default(),
    });
    let wifi_seed = 1234;
    // 初始化网络栈
    let wifi_stack = &*make_static!(EmbassyNetStack::new(
        wifi_interface,
        wifi_config,
        make_static!(StackResources::<3>::new()),
        wifi_seed
    ));
    spawner.spawn(task3_connection(wifi_controller)).ok();
    spawner.spawn(task4_net(&wifi_stack)).ok();
    // 等待stack挂载成功
    loop {
        if wifi_stack.is_link_up() {
            break;
        }
        EmbassyTimer::after(EmbassyDuration::from_millis(500)).await;
    }
    // 开启http服务
    spawner.spawn(task5_http(&wifi_stack,spawner)).ok();
    /* end wifi配置 */

    // 省略代码
}

mydata.rs

/* start 网页test页面 */
pub static webpage_test : &[u8] = b"HTTP/1.0 200 OK\r\n\r\n<!DOCTYPE html>\n<html lang='en'>\n<head>\n    <meta charset='UTF-8'>\n    <meta name='viewport' content='width=device-width, initial-scale=1.0'>\n    <title>POST Data Example</title>\n</head>\n<body>\n    <button id='myButton1' style='width: 100vw; height: 20vh;'>Click Me</button>\n    <button id='myButton2' style='width: 100vw; height: 20vh;'>POST OK</button>\n    <button id='myButton3' style='width: 100vw; height: 20vh;'>GET DATA</button>\n    <span id=\"dashboardData\">\xE4\xBB\xAA\xE8\xA1\xA8\xE7\x9B\x98\xE6\x95\xB0\xE6\x8D\xAE</span>\n\n    <script>\n        document.getElementById('myButton1').addEventListener('click', function() {\n            // \xE4\xBD\xBF\xE7\x94\xA8 Fetch API \xE5\x8F\x91\xE9\x80\x81 POST \xE8\xAF\xB7\xE6\xB1\x82\n            fetch('/post_gcode', {\n                method: 'POST',\n                headers: {\n                    'Content-Type': 'text/plain'\n                },\n                body: '/post_gcode M0'\n            })\n            .then(response => {\n                // \xE6\xA3\x80\xE6\x9F\xA5\xE5\x93\x8D\xE5\xBA\x94\xE7\x8A\xB6\xE6\x80\x81\xEF\xBC\x8C\xE5\xA6\x82\xE6\x9E\x9C\xE6\x98\xAF 200-299 \xE4\xB9\x8B\xE9\x97\xB4\xE7\x9A\x84\xE7\x8A\xB6\xE6\x80\x81\xE7\xA0\x81\xEF\xBC\x8C\xE8\xAF\xB4\xE6\x98\x8E\xE8\xAF\xB7\xE6\xB1\x82\xE6\x88\x90\xE5\x8A\x9F\n                if (response.ok) {\n                    console.log('Data posted successfully!');\n                } else {\n                    console.error('Error posting data:', response.statusText);\n                }\n            })\n            .catch(error => {\n                console.error('An error occurred:', error);\n            });\n        });\n        document.getElementById('myButton2').addEventListener('click', function() {\n            // \xE4\xBD\xBF\xE7\x94\xA8 Fetch API \xE5\x8F\x91\xE9\x80\x81 POST \xE8\xAF\xB7\xE6\xB1\x82\n            fetch('/post_ok', {\n                method: 'POST',\n                headers: {\n                    'Content-Type': 'text/plain'\n                },\n                body: '/post_ok x'\n            })\n            .then(response => {\n                // \xE6\xA3\x80\xE6\x9F\xA5\xE5\x93\x8D\xE5\xBA\x94\xE7\x8A\xB6\xE6\x80\x81\xEF\xBC\x8C\xE5\xA6\x82\xE6\x9E\x9C\xE6\x98\xAF 200-299 \xE4\xB9\x8B\xE9\x97\xB4\xE7\x9A\x84\xE7\x8A\xB6\xE6\x80\x81\xE7\xA0\x81\xEF\xBC\x8C\xE8\xAF\xB4\xE6\x98\x8E\xE8\xAF\xB7\xE6\xB1\x82\xE6\x88\x90\xE5\x8A\x9F\n                if (response.ok) {\n                    console.log('Data posted successfully!');\n                    console.log(response);\n                    document.getElementById('myButton2').style.backgroundColor = 'red';\n                    // \xE6\x89\x93\xE5\x8D\xB0\xE5\x93\x8D\xE5\xBA\x94\xE7\x9A\x84body\xE5\x86\x85\xE5\xAE\xB9\xE5\xB9\xB6\xE5\x88\xA4\xE6\x96\xAD\xE6\x98\xAF\xE5\x90\xA6\xE5\x8C\x85\xE5\x90\xAB\"ok\"\xE6\x96\x87\xE6\x9C\xAC\n                    response.text().then(bodyText => {\n                        console.log('Response body:', bodyText);\n                        if (bodyText.includes('ok')) {\n                            console.log('Body contains \"ok\" text!');\n                            document.getElementById('myButton2').style.backgroundColor = 'green';\n                        } else {\n                            console.log('Body does not contain \"ok\" text.');\n                        }\n            });\n                } else {\n                    console.error('Error posting data:', response.statusText);\n                    console.log(response);\n                    document.getElementById('myButton2').style.backgroundColor = 'blue';\n                }\n            })\n            .catch(error => {\n                console.error('An error occurred:', error);\n                document.getElementById('myButton2').style.backgroundColor = 'yellow';\n            });\n        });\n        document.getElementById('myButton3').addEventListener('click', function() {\n            // \xE4\xBD\xBF\xE7\x94\xA8 Fetch API \xE5\x8F\x91\xE9\x80\x81 POST \xE8\xAF\xB7\xE6\xB1\x82\n            fetch('/get_data', {\n                method: 'POST',\n                headers: {\n                    'Content-Type': 'text/plain'\n                },\n                body: '/get_data x'\n            })\n            .then(response => {\n                // \xE6\xA3\x80\xE6\x9F\xA5\xE5\x93\x8D\xE5\xBA\x94\xE7\x8A\xB6\xE6\x80\x81\xEF\xBC\x8C\xE5\xA6\x82\xE6\x9E\x9C\xE6\x98\xAF 200-299 \xE4\xB9\x8B\xE9\x97\xB4\xE7\x9A\x84\xE7\x8A\xB6\xE6\x80\x81\xE7\xA0\x81\xEF\xBC\x8C\xE8\xAF\xB4\xE6\x98\x8E\xE8\xAF\xB7\xE6\xB1\x82\xE6\x88\x90\xE5\x8A\x9F\n                if (response.ok) {\n                    console.log('Data posted successfully!');\n                    console.log(response);\n                    document.getElementById('myButton3').style.backgroundColor = 'red';\n                    // \xE6\x89\x93\xE5\x8D\xB0\xE5\x93\x8D\xE5\xBA\x94\xE7\x9A\x84body\xE5\x86\x85\xE5\xAE\xB9\xE5\xB9\xB6\xE5\x88\xA4\xE6\x96\xAD\xE6\x98\xAF\xE5\x90\xA6\xE5\x8C\x85\xE5\x90\xAB\"ok\"\xE6\x96\x87\xE6\x9C\xAC\n                    response.text().then(bodyText => {\n                        console.log('Response body:', bodyText);\n                        console.log('Body contains \"ok\" text!');\n                        document.getElementById('myButton3').style.backgroundColor = 'green';\n                        // \xE6\x9B\xB4\xE6\x96\xB0\xE6\x95\xB0\xE6\x8D\xAE\n                        updataDashboard(bodyText);\n                        \n            });\n                } else {\n                    console.error('Error posting data:', response.statusText);\n                    console.log(response);\n                    document.getElementById('myButton3').style.backgroundColor = 'blue';\n                }\n            })\n            .catch(error => {\n                console.error('An error occurred:', error);\n                document.getElementById('myButton3').style.backgroundColor = 'yellow';\n            });\n        });\n\n        /*\n        \xE6\x9B\xB4\xE6\x96\xB0\xE9\xA1\xB5\xE9\x9D\xA2\xE4\xBB\xAA\xE8\xA1\xA8\xE7\x9B\x98\xE6\x95\xB0\xE6\x8D\xAE\n        \xE8\xBE\x93\xE5\x85\xA5:http\xE7\x9A\x84body\xE6\x95\xB0\xE6\x8D\xAE\n        \xE8\xBE\x93\xE5\x87\xBA:\xE6\x97\xA0\n        */\n        function updataDashboard(http_body_data){\n            let mapping = {\n                1: \"heatedbed\",\n                2: \"jet\",\n                3: \"hot_jet\",\n                4: \"hot_heatedbed\",\n                5: \"jet_on\"\n            };\n            var dashboardDataElement = document.getElementById('dashboardData');\n            // \xE5\x88\x86\xE5\x89\xB2\xE6\x95\xB0\xE6\x8D\xAE\xE5\xAD\x97\xE7\xAC\xA6\xE4\xB8\xB2\n            var pairs = http_body_data.split(';');\n            \n            // \xE9\x81\x8D\xE5\x8E\x86\xE6\x95\xB0\xE6\x8D\xAE\xE5\xAF\xB9\xEF\xBC\x8C\xE8\xA7\xA3\xE6\x9E\x90\xE5\xB9\xB6\xE6\x98\xBE\xE7\xA4\xBA\xE6\x95\xB0\xE6\x8D\xAE\n            var output = '';\n            pairs.forEach(function(pair) {\n                if (pair !== '') {\n                    var keyValue = pair.split('=');\n                    var key = keyValue[0];\n                    var value = keyValue[1];\n                    output += mapping[key] + ': ' + value + '<br>';\n                }\n            });\n            \n            // \xE5\xB0\x86\xE8\xA7\xA3\xE6\x9E\x90\xE5\x90\x8E\xE7\x9A\x84\xE6\x95\xB0\xE6\x8D\xAE\xE6\x98\xBE\xE7\xA4\xBA\xE5\x88\xB0\xE9\xA1\xB5\xE9\x9D\xA2\xE4\xB8\x8A\n            dashboardDataElement.innerHTML = output;\n        }\n    </script>\n</body>\n</html>\r\n";
/* end 网页test页面 */

Cargo.toml

[package]
name = 'esp32s_zig'
edition = '2021'
version = '0.1.1'
authors = [
    'Juraj Michálek <juraj.michalek@espressif.com>',
    'qsbye',
]
license = 'MIT OR Apache-2.0'
# 构建脚本
build = "./src/build.rs" 

[package.metadata]
last-modified-system-time = 1699201364

[dependencies.tinybmp]
version = "0.5.0"

[dependencies.embedded-io-async]
version = "0.6.0"

[dependencies.embassy-net]
version = "0.2.1"
features = ["nightly","proto-ipv4","tcp","udp","medium-ethernet", "dhcpv4"]

[dependencies.static_cell]
version = "2.0.0"
features = ["nightly"]

[dependencies.embassy-sync]
version = "0.4.0"

[dependencies.embassy-executor]
version = "0.3.2"
features = ['nightly']

[dependencies.embassy-time]
version = "0.1.5"

[dependencies.ssd1306]
version = "0.8.4"

[dependencies.embedded-graphics]
version = "0.8.1"

[dependencies.critical-section]
version = '1.1.2'
features = []

[dependencies.esp-backtrace]
version = '0.9.0'
features = [
    'esp32',
    'panic-handler',
    'print-uart',
    'exception-handler',
]

[dependencies.esp-println]
version = '0.7.0'
features = ['esp32']

[dependencies.esp32-hal]
version = '0.16.0'
features = ['embassy','embassy-executor-thread','embassy-time-timg0','async','embassy-executor-interrupt']

[dependencies.esp-wifi]
version = '0.1.0' # 0.1.0
path = './vendor/esp-wifi/esp-wifi'
features = ["esp32","wifi","log","utils","async","embassy-net"]
[profile.dev.package.esp-wifi]
opt-level = 3 # esp-wifi特性

[dependencies.smoltcp]
version = "0.10.0"
default-features=false
features = ["proto-igmp", "proto-ipv4", "proto-dns", "socket-tcp", "socket-icmp", "socket-udp", "socket-dns", "medium-ethernet", "proto-dhcpv4", "socket-raw", "socket-dhcpv4"]

[dependencies.embedded-svc]
version = "0.26.1"
default-features = false
features = []

[dependencies.embedded-io]
version = "0.6.1"

[profile.dev]
lto = "off" # esp-wifi特性

[profile.release]
lto = "off" # esp-wifi特性

.cargo/config.toml

[target.xtensa-esp32-none-elf]
runner = "espflash flash --monitor"

[build]
rustflags = [
  "-C", "link-arg=-nostartfiles",
  "-C", "link-arg=-Wl,-Tlinkall.x",
  "-C", "link-arg=-Trom_functions.x",#esp-wifi特有
]
target = "xtensa-esp32-none-elf"

[unstable]
build-std = ["core","alloc"]

效果

网页 后台 调试
posted @ 2023-11-16 22:32  qsBye  阅读(88)  评论(0编辑  收藏  举报