基于Rust与ESP32的Wi-Fi红外空调遥控器开发实战
2026/7/25 2:41:45 网站建设 项目流程

你有没有遇到过这样的情况:炎热的夏天回到家,空调遥控器却找不到了?或者想要在床上调整温度,却懒得起身找遥控器?今天我要分享的,就是如何用一块小小的M5StickC开发板,结合Rust语言,打造一个可以通过Wi-Fi远程控制的空调遥控器。

这个项目看似简单,但背后涉及的技术栈却很有意思:ESP32芯片的嵌入式开发、Rust语言在嵌入式领域的应用、红外信号编解码、以及网络通信协议。更重要的是,它展示了如何用现代开发工具解决日常生活中的实际问题。

1. 这个项目真正解决了什么问题

传统的空调遥控器有几个明显的痛点:容易丢失、控制距离有限、无法远程操作。而市面上的智能空调虽然支持手机控制,但价格昂贵,且需要依赖厂商的云服务。

这个项目的核心价值在于:

  • 低成本解决方案:M5StickC开发板价格在百元以内,远低于智能空调的差价
  • 完全本地控制:不依赖任何云服务,数据完全在本地网络流转
  • 高度可定制:可以根据自己的需求定制控制逻辑和界面
  • 技术学习价值:同时学习嵌入式开发、Rust语言和网络编程

特别适合以下场景的开发者:

  • 想要入门嵌入式Rust开发的初学者
  • 需要对现有家电进行智能化改造的DIY爱好者
  • 希望了解物联网设备本地化解决方案的技术人员

2. 技术栈选择背后的考量

2.1 为什么选择M5StickC

M5StickC是一款基于ESP32芯片的微型开发板,它具备以下优势:

特性说明对本项目的价值
小巧尺寸48mm × 25mm × 13mm便于安装在任意位置
内置显示屏0.96寸彩色LCD可以显示状态和温度信息
红外发射内置IR LED直接控制空调,无需额外硬件
Wi-Fi支持基于ESP32的Wi-Fi模块实现网络远程控制
电池供电内置80mAh锂电池可以便携使用

2.2 为什么选择Rust语言

在嵌入式领域,C/C++一直是主流选择。但Rust提供了独特优势:

  • 内存安全:编译时检查避免内存错误,对于长期运行的设备至关重要
  • 现代工具链:cargo包管理器让依赖管理变得简单
  • 强大的类型系统:减少运行时错误,提高代码可靠性
  • 嵌入式生态成熟:esp-rs项目为ESP32提供了完整的支持
// Rust在嵌入式中的优势示例:强大的类型检查 #[derive(Debug)] enum AirConditionerMode { Cool, Heat, Fan, Dry, } impl AirConditionerMode { fn from_str(s: &str) -> Result<Self, &'static str> { match s { "cool" => Ok(AirConditionerMode::Cool), "heat" => Ok(AirConditionerMode::Heat), "fan" => Ok(AirConditionerMode::Fan), "dry" => Ok(AirConditionerMode::Dry), _ => Err("Invalid mode"), } } }

3. 环境准备与工具链配置

3.1 硬件准备清单

  • M5StickC开发板 × 1
  • USB Type-C数据线 × 1
  • 空调(用于测试红外信号)
  • 电脑(Windows/macOS/Linux均可)

3.2 软件环境搭建

Rust嵌入式开发环境配置相对复杂,但遵循以下步骤可以避免很多坑:

# 1. 安装Rust工具链 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source ~/.cargo/env # 2. 添加ESP32目标支持 rustup target add riscv32imc-unknown-none-elf # 针对ESP32-C3 # 或者 rustup target add xtensa-esp32-none-elf # 针对ESP32 # 3. 安装espup工具(ESP32开发工具链管理) cargo install espup # 4. 安装ESP32工具链 espup install # 5. 配置环境变量(添加到~/.bashrc或~/.zshrc) source ~/export-esp.sh

3.3 项目依赖配置

创建新的Rust项目并配置依赖:

cargo new ac-wifi-remote cd ac-wifi-remote

Cargo.toml中添加依赖:

[package] name = "ac-wifi-remote" version = "0.1.0" edition = "2021" [dependencies] esp-idf-hal = "0.42" esp-idf-svc = "0.42" embedded-svc = "0.25" anyhow = "1.0" log = "0.4" [dependencies.esp-idf-sys] version = "0.33" features = ["binstart"]

4. 红外信号编解码原理

4.1 空调红外协议基础

空调红外信号比电视遥控器复杂得多,因为它需要传输更多信息:

  • 开关状态
  • 运行模式(制冷/制热/除湿/送风)
  • 温度设定(通常16-30℃)
  • 风速设定
  • 扫风模式

常见的空调红外协议有:

  • NEC协议:最常用,但空调通常使用扩展版本
  • RC-5协议:飞利浦系列
  • 自定义协议:各品牌厂商的自定义格式

4.2 信号捕获与分析

首先需要捕获原装遥控器的红外信号:

use esp_idf_hal::prelude::*; use esp_idf_hal::gpio; use esp_idf_hal::delay::FreeRtos; struct IrReceiver { pin: gpio::PinDriver<'static, gpio::Gpio4, gpio::Input>, } impl IrReceiver { fn new() -> anyhow::Result<Self> { let peripherals = Peripherals::take().unwrap(); let pin = gpio::PinDriver::input(peripherals.pins.gpio4)?; Ok(IrReceiver { pin }) } fn capture_signal(&mut self) -> anyhow::Result<Vec<u32>> { let mut timings = Vec::new(); let mut last_state = self.pin.is_high(); let mut start_time = FreeRtos::get_tick_count(); // 捕获持续时间的信号 for _ in 0..1000 { let current_state = self.pin.is_high(); if current_state != last_state { let duration = FreeRtos::get_tick_count() - start_time; timings.push(duration); start_time = FreeRtos::get_tick_count(); last_state = current_state; } FreeRtos::delay_ms(1); } Ok(timings) } }

4.3 信号解析与生成

解析捕获到的红外信号:

struct IrSignal { header_mark: u32, header_space: u32, data_bits: Vec<bool>, bit_duration_mark: u32, bit_duration_space_0: u32, bit_duration_space_1: u32, } impl IrSignal { fn decode(timings: &[u32]) -> anyhow::Result<Self> { // 分析信号时序,识别协议格式 // 这里以NEC协议为例 if timings.len() < 4 { return Err(anyhow::anyhow!("Signal too short")); } let header_mark = timings[0]; let header_space = timings[1]; // 解析数据位 let mut data_bits = Vec::new(); for chunk in timings[2..].chunks(2) { if chunk.len() == 2 { let mark_duration = chunk[0]; let space_duration = chunk[1]; // 根据空间持续时间判断是0还是1 if space_duration > mark_duration * 2 { data_bits.push(true); } else { data_bits.push(false); } } } Ok(IrSignal { header_mark, header_space, data_bits, bit_duration_mark: 560, // 典型NEC协议值 bit_duration_space_0: 560, bit_duration_space_1: 1690, }) } fn encode(&self) -> Vec<u32> { let mut signal = Vec::new(); // 添加头部 signal.push(self.header_mark); signal.push(self.header_space); // 添加数据位 for &bit in &self.data_bits { signal.push(self.bit_duration_mark); if bit { signal.push(self.bit_duration_space_1); } else { signal.push(self.bit_duration_space_0); } } signal } }

5. Wi-Fi网络通信实现

5.1 网络连接配置

实现Wi-Fi连接功能:

use esp_idf_svc::wifi::{BlockingWifi, EspWifi}; use esp_idf_svc::eventloop::EspSystemEventLoop; use embedded_svc::wifi::{AuthMethod, ClientConfiguration, Configuration}; struct WiFiManager { wifi: BlockingWifi<EspWifi<'static>>, } impl WiFiManager { fn new() -> anyhow::Result<Self> { let peripherals = Peripherals::take().unwrap(); let sys_loop = EspSystemEventLoop::take()?; let wifi = EspWifi::new(peripherals.modem, sys_loop.clone(), None)?; let wifi = BlockingWifi::wrap(wifi, sys_loop)?; Ok(WiFiManager { wifi }) } fn connect(&mut self, ssid: &str, password: &str) -> anyhow::Result<()> { // 配置Wi-Fi客户端 let wifi_config = Configuration::Client(ClientConfiguration { ssid: ssid.try_into().unwrap(), bssid: None, auth_method: AuthMethod::WPA2Personal, password: password.try_into().unwrap(), channel: None, }); self.wifi.set_configuration(&wifi_config)?; // 启动Wi-Fi self.wifi.start()?; // 连接网络 self.wifi.connect()?; // 等待连接成功 self.wifi.wait_netif_up()?; log::info!("Wi-Fi connected successfully"); Ok(()) } }

5.2 HTTP服务器实现

创建一个简单的HTTP服务器来处理控制请求:

use embedded_svc::http::Method; use embedded_svc::http::server::Registry; use embedded_svc::io::Write; use std::collections::HashMap; struct ControlServer { ir_emitter: IrEmitter, } impl ControlServer { fn handle_request(&mut self, method: Method, path: &str, body: &str) -> String { match (method, path) { (Method::Get, "/status") => { serde_json::json!({ "status": "online", "device": "AC Remote" }).to_string() } (Method::Post, "/control") => { let params: HashMap<String, String> = serde_json::from_str(body).unwrap(); self.handle_control_command(params) } _ => { serde_json::json!({ "error": "Not found" }).to_string() } } } fn handle_control_command(&mut self, params: HashMap<String, String>) -> String { if let (Some(mode), Some(temp)) = (params.get("mode"), params.get("temperature")) { match mode.as_str() { "cool" => { self.ir_emitter.send_cool_command(temp.parse().unwrap()); serde_json::json!({"status": "cooling", "temperature": temp}).to_string() } "heat" => { self.ir_emitter.send_heat_command(temp.parse().unwrap()); serde_json::json!({"status": "heating", "temperature": temp}).to_string() } _ => serde_json::json!({"error": "Invalid mode"}).to_string() } } else { serde_json::json!({"error": "Missing parameters"}).to_string() } } }

6. 完整的系统集成

6.1 主程序逻辑

将各个模块整合到主程序中:

use anyhow::Result; use log::info; fn main() -> Result<()> { // 初始化日志系统 esp_idf_svc::log::EspLogger::initialize_default(); info!("Starting AC Wi-Fi Remote..."); // 初始化Wi-Fi let mut wifi_manager = WiFiManager::new()?; wifi_manager.connect("YourWiFiSSID", "YourWiFiPassword")?; // 初始化红外发射器 let mut ir_emitter = IrEmitter::new()?; // 初始化控制服务器 let mut control_server = ControlServer::new(ir_emitter); // 启动HTTP服务器 start_http_server(control_server)?; info!("AC Wi-Fi Remote is ready!"); // 主循环 loop { // 处理网络请求 handle_network_requests()?; // 其他后台任务 FreeRtos::delay_ms(100); } } fn start_http_server(control_server: ControlServer) -> Result<()> { // 实现HTTP服务器启动逻辑 // 这里简化实现,实际需要使用esp-idf-svc中的http服务器组件 info!("HTTP server started on port 80"); Ok(()) }

6.2 用户界面设计

利用M5StickC的显示屏提供本地交互:

struct DisplayManager { display: DisplayDriver, } impl DisplayManager { fn show_status(&mut self, status: &str, temperature: i32) -> Result<()> { self.display.clear(); // 显示状态信息 self.display.draw_text(10, 10, status, Color::White, Color::Black); // 显示温度 let temp_text = format!("{}°C", temperature); self.display.draw_text(10, 30, &temp_text, Color::Red, Color::Black); // 显示连接状态 self.display.draw_text(10, 50, "Wi-Fi: Connected", Color::Green, Color::Black); self.display.flush(); Ok(()) } fn show_error(&mut self, error: &str) -> Result<()> { self.display.clear(); self.display.draw_text(10, 10, "Error:", Color::Red, Color::Black); self.display.draw_text(10, 30, error, Color::White, Color::Black); self.display.flush(); Ok(()) } }

7. 实际部署与测试

7.1 硬件连接与配置

M5StickC的引脚配置:

struct HardwarePins { ir_led: gpio::Gpio26, // 红外发射LED button_a: gpio::Gpio37, // 按钮A(用于本地控制) button_b: gpio::Gpio39, // 按钮B(用于模式切换) } impl HardwarePins { fn configure() -> Result<Self> { let peripherals = Peripherals::take().unwrap(); Ok(HardwarePins { ir_led: peripherals.pins.gpio26, button_a: peripherals.pins.gpio37, button_b: peripherals.pins.gpio39, }) } }

7.2 功能测试流程

完整的测试步骤:

  1. 编译和烧录
cargo build --release espflash /dev/ttyUSB0 target/riscv32imc-unknown-none-elf/release/ac-wifi-remote
  1. Wi-Fi连接测试

    • 观察M5StickC显示屏是否显示"Wi-Fi Connected"
    • 检查路由器客户端列表确认设备在线
  2. 红外信号测试

    • 用手机摄像头观察IR LED是否闪烁(红外光在摄像头下可见)
    • 测试空调是否响应控制命令
  3. 网络控制测试

# 测试HTTP API curl -X GET http://192.168.1.100/status curl -X POST http://192.168.1.100/control \ -H "Content-Type: application/json" \ -d '{"mode":"cool","temperature":25}'

7.3 预期运行结果

成功运行后,你应该看到:

  • M5StickC显示屏显示连接状态和当前温度设置
  • 通过HTTP请求可以控制空调开关和模式切换
  • 红外信号正常发射,空调响应控制命令

8. 常见问题与解决方案

问题现象可能原因排查方法解决方案
编译失败,提示找不到esp-idf环境变量未正确设置检查export-esp.sh是否已source手动设置环境变量:source ~/export-esp.sh
Wi-Fi连接失败密码错误或信号弱检查SSID和密码是否正确确保设备在路由器信号范围内
红外控制无响应红外频率不匹配用手机摄像头检查IR LED是否工作调整红外载波频率(通常38kHz)
HTTP请求超时IP地址错误或防火墙阻挡检查设备获取的IP地址在路由器中为设备设置静态IP
设备频繁重启内存不足或代码错误查看串口日志输出优化内存使用,检查数组越界

8.1 红外信号调试技巧

红外信号调试是项目中最容易出问题的环节:

// 添加调试功能,通过串口输出信号时序 fn debug_ir_signal(timings: &[u32]) { log::info!("IR Signal timings:"); for (i, &duration) in timings.iter().enumerate() { log::info!("{}: {}us", i, duration); } } // 测试不同的载波频率 fn test_carrier_frequencies() { let frequencies = [36000, 38000, 40000]; // 常见红外载波频率 for freq in frequencies { log::info!("Testing frequency: {}Hz", freq); // 发送测试信号并检查空调响应 } }

8.2 网络稳定性优化

对于需要长期运行的设备,网络稳定性很重要:

impl WiFiManager { fn check_connection(&mut self) -> bool { // 定期检查网络连接状态 self.wifi.is_up().unwrap_or(false) } fn reconnect_if_needed(&mut self) -> Result<()> { if !self.check_connection() { log::warn!("Wi-Fi connection lost, attempting reconnect..."); self.wifi.disconnect()?; FreeRtos::delay_ms(5000); // 等待5秒 self.connect("YourWiFiSSID", "YourWiFiPassword")?; } Ok(()) } }

9. 进阶功能与扩展思路

9.1 温度自动化控制

实现基于环境温度的智能控制:

struct TemperatureAutomation { target_temp: i32, hysteresis: i32, // 迟滞范围,避免频繁开关 sensor: TemperatureSensor, } impl TemperatureAutomation { fn should_turn_on(&self, current_temp: i32) -> bool { current_temp > self.target_temp + self.hysteresis } fn should_turn_off(&self, current_temp: i32) -> bool { current_temp < self.target_temp - self.hysteresis } fn auto_control(&mut self) { let current_temp = self.sensor.read_temperature(); if self.should_turn_on(current_temp) { self.send_cool_command(self.target_temp); } else if self.should_turn_off(current_temp) { self.send_power_off(); } } }

9.2 多设备支持与场景模式

扩展支持控制多个空调设备:

#[derive(Clone)] struct AirConditioner { name: String, ir_code: IrSignal, location: String, } struct MultiACController { ac_units: HashMap<String, AirConditioner>, current_scene: SceneMode, } impl MultiACController { fn create_scene(&mut self, name: &str, settings: HashMap<String, ACSetting>) { // 创建场景模式,如"回家模式"、"睡眠模式" } fn execute_scene(&mut self, scene_name: &str) { if let Some(scene) = self.scenes.get(scene_name) { for (ac_name, setting) in scene { if let Some(ac) = self.ac_units.get(ac_name) { self.send_command(ac, setting); } } } } }

9.3 能耗统计与优化

添加用电量统计功能:

struct EnergyMonitor { power_consumption: f32, // 当前功率 total_energy: f32, // 总耗电量 start_time: u64, // 开始时间 } impl EnergyMonitor { fn calculate_energy_saving(&self, manual_mode: f32, auto_mode: f32) -> f32 { // 计算自动化控制相比手动控制的节能效果 ((manual_mode - auto_mode) / manual_mode) * 100.0 } fn generate_report(&self) -> EnergyReport { EnergyReport { total_energy: self.total_energy, average_power: self.power_consumption, cost: self.total_energy * 0.6, // 假设电费0.6元/度 carbon_footprint: self.total_energy * 0.785, // 碳排放系数 } } }

这个项目展示了如何用现代技术栈解决实际问题,从硬件控制到网络通信,从信号处理到用户交互,涵盖了嵌入式开发的多个重要方面。最重要的是,它证明了Rust在嵌入式领域的可行性,为想要尝试嵌入式Rust开发的开发者提供了一个很好的起点。

在实际部署时,建议先在小范围内测试所有功能,确保稳定性后再正式使用。对于生产环境,还需要考虑电源管理、故障恢复、安全认证等更多工程化问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询