首页
苏兮影视
随笔记
壁纸
更多
直播
时光轴
友联
关于
统计
Search
1
v2ray节点搭建
1,074 阅读
2
软件添加id功能按钮
1,029 阅读
3
QQ扫码无法登录的解决方案
990 阅读
4
网易云音乐歌单ID获取教程
922 阅读
5
js逆向xhs
792 阅读
谈天说地
建站源码
经验教程
资源分享
动漫美图
登录
Search
标签搜索
java
rust
flutter
esp32c3
springboot
安卓
linux
vue
dart
设计模式
docker
ssh
joe
快捷键
git
fish shell
maven
redis
netty
groovy
尽意
累计撰写
116
篇文章
累计收到
41
条评论
首页
栏目
谈天说地
建站源码
经验教程
资源分享
动漫美图
页面
苏兮影视
随笔记
壁纸
直播
时光轴
友联
关于
统计
搜索到
116
篇与
的结果
ESP32C3引入Slint: 解决依赖冲突
为什么要折腾这个?想在 ESP32-C3 开发板上实现一个酷炫的 UI 界面,选了 Rust 生态的 Slint 框架。结果刚加上依赖就报错:error: 不能同时启用 critical-section 和 portable_atomic_unsafe_assume_single_core这两个特性不能同时存在解决问题过程第一步:创建最小测试项目# 生成纯净的 ESP32-C3 项目骨架 esp-generate --chip=esp32c3 slint_demo此时项目仅包含最基础的硬件驱动,编译一次成功:cargo build Compiling slint_demo v0.1.0 Finished dev [optimized] target(s)第二步:引入 Slint 引爆问题按官方文档添加依赖:cargo add slint@1.10.0 --no-default-features --features "compat-1-2 unsafe-single-threaded libm renderer-software"再次编译立刻报错:error: critical-section 与 portable_atomic_unsafe_assume_single_core 冲突第三步:定位问题注释 Slint 保留 ESP-HAL → 编译成功注释 ESP-HAL 保留 Slint → 依然报错Slint 的依赖树里藏了冲突的组件第四步:解决问题搜索 slint critical-section site:github.com锁定关键讨论:Issue #5057发现修复补丁:Commit 29168bc核心修改点:# Slint 内部配置调整 - portable-atomic = { features = ["critical-section"] } + portable-atomic = {} # 解除强制依赖最终配置方案关键依赖声明 (Cargo.toml)[dependencies] # 硬件抽象层 esp-hal = { version = "1.0.0-beta.0", default-features = false, features = ["esp32c3"] # 注意关闭默认特性 } # UI 框架 [dependencies.slint] git = "https://github.com/slint-ui/slint" rev = "29168bc89270798f6075a0a729c14a3f011ceb4f" # 锁定修复版本 default-features = false features = [ "compat-1-2", # 兼容旧版 API "unsafe-single-threaded", # 单核模式 "libm", # 数学库支持 "renderer-software" # 软件渲染 ] # 内存分配器 esp-alloc = "0.7.0"内存管理配置 (main.rs)// 初始化堆内存(72KB 用于 UI) #[main] fn main() -> ! { let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); let _peripherals = esp_hal::init(config); esp_alloc::heap_allocator!(size: 72 * 1024); }经验总结依赖管理铁律用 cargo tree -e features 查看依赖关系通过 default-features = false 精准控制特性版本锁定技巧使用 rev = "commit_hash" 锁定特定修复版本定期检查 GitHub Issues 跟踪问题进展
2025年05月03日
205 阅读
0 评论
0 点赞
2025-05-01
ESP32C3使用BLE
初始化项目参考 项目创建 新建一个基于std的新项目在Cargo.toml添加必要的依赖[package] name = "esp32c3-ble-demo" version = "0.1.0" authors = ["suxii <m@suxii.cn>"] edition = "2021" resolver = "2" rust-version = "1.77" [[bin]] name = "esp32c3-ble-demo" harness = false # do not use the built in cargo test harness -> resolve rust-analyzer errors [features] default = [] experimental = ["esp-idf-svc/experimental"] [dependencies] log = "0.4" esp-idf-svc = { version = "0.51", features = ["critical-section", "embassy-time-driver", "embassy-sync","experimental","std"] } futures = "0.3.31" anyhow = "1.0.98" enumset = "1.1.5" heapless = "0.8.0" esp-idf-sys = "0.36.1" esp-idf-hal = "0.45.2" # 处理序列化,反序列化 serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" [build-dependencies] embuild = "0.33" 开启BLE功能将sdkconfig.defaults 文件替换成# Rust often needs a bit of an extra main task stack size compared to C (the default is 3K) CONFIG_ESP_MAIN_TASK_STACK_SIZE=7000 CONFIG_BT_BLUEDROID_ENABLED=y CONFIG_BT_ENABLED=y CONFIG_BT_BLE_ENABLED=y CONFIG_BT_GATTS_ENABLE=y CONFIG_BT_BLE_SMP_ENABLE=y CONFIG_BT_GATTS_SEND_SERVICE_CHANGE_MODE=y CONFIG_BT_BTC_TASK_STACK_SIZE=7000 CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y CONFIG_BT_CTRL_BLE_MAX_ACT=10 CONFIG_BT_CTRL_BLE_MAX_ACT_EFF=10 CONFIG_BT_CTRL_BLE_STATIC_ACL_TX_BUF_NB=0 CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_SUPP=y CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_NUM=100 CONFIG_BT_CTRL_BLE_ADV_REPORT_DISCARD_THRSHOLD=20 CONFIG_BT_CTRL_BLE_SCAN_DUPL=y # Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default). # This allows to use 1 ms granuality for thread sleeps (10 ms by default). #CONFIG_FREERTOS_HZ=1000 # Workaround for https://github.com/espressif/esp-idf/issues/7631 #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n并且在同级目录新建一个 sdkconfig.esp32c3文件# Rust often needs a bit of an extra main task stack size compared to C (the default is 3K) CONFIG_ESP_MAIN_TASK_STACK_SIZE=7000 CONFIG_BT_ENABLED=y CONFIG_BT_BLE_ENABLED=y CONFIG_BT_GATTS_ENABLE=y CONFIG_BT_GATTS_SEND_SERVICE_CHANGE_MODE=y CONFIG_BT_BTC_TASK_STACK_SIZE=7000 CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n CONFIG_BTDM_CTRL_MODE_BTDM=n # Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default). # This allows to use 1 ms granuality for thread sleeps (10 ms by default). #CONFIG_FREERTOS_HZ=1000 # Workaround for https://github.com/espressif/esp-idf/issues/7631 #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n代码编写新建一个ble_server.rs文件,用来管理BLE use std::sync::{mpsc, Arc, Condvar, Mutex}; use std::thread; use enumset::enum_set; use esp_idf_hal::ledc::{LedcDriver, LedcTimerDriver, Resolution}; use esp_idf_hal::modem; use esp_idf_svc::bt::ble::gap::{AdvConfiguration, BleGapEvent, EspBleGap}; use esp_idf_svc::bt::ble::gatt::server::{ConnectionId, EspGatts, GattsEvent, TransferId}; use esp_idf_svc::bt::ble::gatt::{ AutoResponse, GattCharacteristic, GattDescriptor, GattId, GattInterface, GattResponse, GattServiceId, GattStatus, Handle, Permission, Property, }; use esp_idf_svc::bt::{BdAddr, Ble, BtDriver, BtStatus, BtUuid}; use esp_idf_svc::hal::delay::FreeRtos; use esp_idf_svc::nvs::{ EspNvsPartition, NvsDefault}; use esp_idf_svc::sys::{EspError, ESP_FAIL}; use log::{error, info, warn}; use crate::ble_recv_message; use crate::message::Message; pub fn start( modem: modem::Modem, nvs_clone:EspNvsPartition<NvsDefault> ) -> anyhow::Result<()> { // nvs(Non-Volatile Storage,非易失性存储)是持久化存储,保证断电数据不丢失 // 创建ble蓝牙驱动 let bt = Arc::new(BtDriver::new(modem, Some(nvs_clone))?); // 自定义一个蓝牙集合服务,创建gap,跟gatts let server = ExampleServer::new( Arc::new(EspBleGap::new(bt.clone())?), Arc::new(EspGatts::new(bt.clone())?), ); info!("BLE Gap and Gatts 初始化完成"); let gap_server = server.clone(); // 处理gap事件回调吗,gap在连接阶段参与,不参与内容传输 server.gap.subscribe(move |event| { gap_server.check_esp_status(gap_server.on_gap_event(event)); })?; let gatts_server = server.clone(); // gap处理完成,说明连接已经建立了,现在开始处理gatt,也就是数据通信的部分 server.gatts.subscribe(move |(gatt_if, event)| { gatts_server.check_esp_status(gatts_server.on_gatts_event(gatt_if, event)) })?; info!("BLE Gap and Gatts 订阅事件初始化完成"); server.gatts.register_app(APP_ID)?; info!("Gatts BTP app 完成注册"); // 新线程处理发送消息 let _ = thread::Builder::new() .stack_size(2000) .spawn(move||{ let mut ind_data = 0_u16; loop { server.indicate(&ind_data.to_le_bytes()).unwrap(); info!("广播给客户端的信息为: {ind_data}"); ind_data = ind_data.wrapping_add(1); FreeRtos::delay_ms(10000); } }); anyhow::Ok(()) } const APP_ID: u16 = 0; const MAX_CONNECTIONS: usize = 2; // Our service UUID pub const SERVICE_UUID: u128 = 0xad91b201734740479e173bed82d75f9d; /// Our "recv" characteristic - i.e. where clients can send data. pub const RECV_CHARACTERISTIC_UUID: u128 = 0xb6fccb5087be44f3ae22f85485ea42c4; /// Our "indicate" characteristic - i.e. where clients can receive data if they subscribe to it pub const IND_CHARACTERISTIC_UUID: u128 = 0x503de214868246c4828fd59144da41be; type ExBtDriver = BtDriver<'static, Ble>; type ExEspBleGap = Arc<EspBleGap<'static, Ble, Arc<ExBtDriver>>>; type ExEspGatts = Arc<EspGatts<'static, Ble, Arc<ExBtDriver>>>; #[derive(Debug, Clone)] struct Connection { peer: BdAddr, conn_id: Handle, subscribed: bool, mtu: Option<u16>, } #[derive(Default)] struct State { gatt_if: Option<GattInterface>, service_handle: Option<Handle>, recv_handle: Option<Handle>, ind_handle: Option<Handle>, ind_cccd_handle: Option<Handle>, connections: heapless::Vec<Connection, MAX_CONNECTIONS>, response: GattResponse, ind_confirmed: Option<BdAddr>, } #[derive(Clone)] pub struct ExampleServer { gap: ExEspBleGap, gatts: ExEspGatts, state: Arc<Mutex<State>>, condvar: Arc<Condvar>, } impl ExampleServer { pub fn new(gap: ExEspBleGap, gatts: ExEspGatts) -> Self { Self { gap, gatts, state: Arc::new(Mutex::new(Default::default())), condvar: Arc::new(Condvar::new()), } } } impl ExampleServer { // 发送indicate消息 fn indicate(&self, data: &[u8]) -> Result<(), EspError> { for peer_index in 0..MAX_CONNECTIONS { // 发送数据给全部已经订阅的服务 let mut state = self.state.lock().unwrap(); loop { if state.connections.len() <= peer_index { // 表示已经向全部订阅的客户发送了消息 break; } let Some(gatt_if) = state.gatt_if else { // 丢失了gatt的接口连接 break; }; let Some(ind_handle) = state.ind_handle else { // 丢失了handle break; }; // confirmed翻译为 证实 // 检查状态:确保上次的指示数据已被客户端确认接收。 // 发送数据:如果状态允许,则向指定客户端发送新的指示数据。 // 更新状态:记录当前客户端已接收到指示数据。 // 等待条件:如果状态不允许发送数据,则等待其他线程的通知。 if state.ind_confirmed.is_none() { let conn = &state.connections[peer_index]; self.gatts .indicate(gatt_if, conn.conn_id, ind_handle, data)?; state.ind_confirmed = Some(conn.peer); let conn = &state.connections[peer_index]; info!("Indicated数据发送给客户端 {}", conn.peer); break; } else { state = self.condvar.wait(state).unwrap(); } } } Ok(()) } /// 处理客户端订阅 fn on_subscribed(&self, addr: BdAddr) { // todo 客户端订阅 warn!("客户端 {addr} 订阅了"); } /// 处理客户端取消订阅 fn on_unsubscribed(&self, addr: BdAddr) { // todo 客户肯定取消订阅 warn!("客户端 {addr} 取消订阅"); } /// 接收消息回调 fn on_recv(&self, addr: BdAddr, data: &[u8], offset: u16, mtu: Option<u16>) { // todo 这里写接收消息的逻辑 // 按大端解析 // let res = u16::from_be_bytes([data[0],data[1]]); let res = String::from_utf8_lossy(data); if let Err(e) = ble_recv_message::handle_message(&res){ error!("处理消息错误:{e}") } warn!("接收消息来自客户端 {addr}: {data:?}, offset: {offset}, mtu: {mtu:?} text:{res}"); } /// GAP 事件的主事件处理程序 fn on_gap_event(&self, event: BleGapEvent) -> Result<(), EspError> { info!("得到gap事件: {event:?}"); // ble广播配置 Advertising:广告,广播 if let BleGapEvent::AdvertisingConfigured(status) = event { // 从gap事件中,结构出BtStatus状态,然后检查bt的状态 // 如果bt连接状态检查不通过,就直接往上级抛异常 self.check_bt_status(status)?; // gap处理完成,开启广播 self.gap.start_advertising()?; } Ok(()) } /// GATTS 事件的主事件处理程序 fn on_gatts_event( &self, gatt_if: GattInterface, event: GattsEvent, ) -> Result<(), EspError> { info!("得到gatts事件消息: {event:?}"); match event { // 处理服务注册,注册成功后,创建服务 GattsEvent::ServiceRegistered { status, app_id } => { self.check_gatt_status(status)?; if APP_ID == app_id { self.create_service(gatt_if)?; } } // 处理服务创建完成后的事件,配置gatts的服务 GattsEvent::ServiceCreated { status, service_handle, .. } => { self.check_gatt_status(status)?; self.configure_and_start_service(service_handle)?; } // 处理特征添加 GattsEvent::CharacteristicAdded { status, attr_handle, service_handle, char_uuid, } => { self.check_gatt_status(status)?; self.register_characteristic(service_handle, attr_handle, char_uuid)?; } // 添加描述符 GattsEvent::DescriptorAdded { status, attr_handle, service_handle, descr_uuid, } => { self.check_gatt_status(status)?; self.register_cccd_descriptor(service_handle, attr_handle, descr_uuid)?; } // 删除服务 GattsEvent::ServiceDeleted { status, service_handle, } => { self.check_gatt_status(status)?; self.delete_service(service_handle)?; } // 注销服务 GattsEvent::ServiceUnregistered { status, service_handle, .. } => { self.check_gatt_status(status)?; self.unregister_service(service_handle)?; } // 注册或更新其 MTU(Maximum Transmission Unit 最大传输单元)值 GattsEvent::Mtu { conn_id, mtu } => { self.register_conn_mtu(conn_id, mtu)?; } // 处理连接的客户端 GattsEvent::PeerConnected { conn_id, addr, .. } => { self.create_conn(conn_id, addr)?; } // 处理客户端断开连接 GattsEvent::PeerDisconnected { addr, .. } => { self.delete_conn(addr)?; // todo 开启广播 self.gap.start_advertising()?; } GattsEvent::Write { conn_id, trans_id, addr, handle, offset, need_rsp, is_prep, value, } => { let handled = self.recv( gatt_if, conn_id, trans_id, addr, handle, offset, need_rsp, is_prep, value, )?; // 值为true,表示是客户端的操作,例如客户端注册,注销,发送消息 if handled { self.send_write_response( gatt_if, conn_id, trans_id, handle, offset, need_rsp, is_prep, value, )?; } } GattsEvent::Confirm { status, .. } => { self.check_gatt_status(status)?; self.confirm_indication()?; } _ => (), } Ok(()) } /// 创建服务并开始广播, /// 在通知我们 GATTS 应用程序已注册后,从事件回调中调 fn create_service(&self, gatt_if: GattInterface) -> Result<(), EspError> { // 初始化结构体的 GattInterface self.state.lock().unwrap().gatt_if = Some(gatt_if); // 设置esp32设备的名字 self.gap.set_device_name("ESP32")?; // flag 标志位是一个位掩码,具体含义可以参考 Bluetooth SIG 定义的标准。 // 常见的标志位组合: // 0x01:LE Limited Discoverable Mode(有限可发现模式)。 // 0x02:LE General Discoverable Mode(通用可发现模式)。 // 0x04:BR/EDR Not Supported(不支持经典蓝牙)。 // 0x18:LE and BR/EDR Controller(同时支持 LE 和经典蓝牙)。 // 在代码中,flag: 2 表示设备处于通用可发现模式(LE General Discoverable Mode)。 self.gap.set_adv_conf(&AdvConfiguration { include_name: true, // 指示是否在广播数据中包含设备名称 include_txpower: true, // 指示是否在广播数据中包含设备的发射功率 flag: 2, service_uuid: Some(BtUuid::uuid128(SERVICE_UUID)), // 指定设备提供的服务 UUID // service_data: todo!(), // 用于广播与特定服务相关的数据 // manufacturer_data: todo!(), // 用于广播厂商自定义的数据 ..Default::default() })?; self.gatts.create_service( gatt_if, &GattServiceId { id: GattId { uuid: BtUuid::uuid128(SERVICE_UUID), inst_id: 0, // 指定服务的实例Id 如果设备支持多个相同 UUID 的服务实例,inst_id 用于区分它们 }, is_primary: true, // 指示服务是否为主服务 }, 8, )?; Ok(()) } /// 删除服务 fn delete_service(&self, service_handle: Handle) -> Result<(), EspError> { let mut state = self.state.lock().unwrap(); if state.service_handle == Some(service_handle) { state.recv_handle = None; state.ind_handle = None; state.ind_cccd_handle = None; } Ok(()) } /// 取消注册服务 fn unregister_service(&self, service_handle: Handle) -> Result<(), EspError> { let mut state = self.state.lock().unwrap(); if state.service_handle == Some(service_handle) { state.gatt_if = None; state.service_handle = None; } Ok(()) } /// 配置并且启动服务 /// 在通知我们创建服务后从事件回调中调用 fn configure_and_start_service(&self, service_handle: Handle) -> Result<(), EspError> { // 初始化service_handle服务 self.state.lock().unwrap().service_handle = Some(service_handle); // 开启gatts服务 // pub type Handle = u16; 由于u16是基本类型,实现了copy,所以下面两个函数都能使用,本质是copy了一份数据 self.gatts.start_service(service_handle)?; // 添加特性 self.add_characteristics(service_handle)?; Ok(()) } /// 添加注册的两个uuid特性到服务中 /// 在通知我们创建服务后从事件回调中调用 fn add_characteristics(&self, service_handle: Handle) -> Result<(), EspError> { // permissions 定义了客户端对该特征的操作权限。 // 在这里,Permission::Write 表示客户端可以写入该特征的值。 // 其他常见的权限包括: // Permission::Read:允许读取特征值。 // Permission::Notify:允许服务器主动通知客户端特征值的变化。 // Permission::Indicate:类似于通知,但需要客户端确认。 // properties 定义了特征的属性(Properties),描述了该特征支持的操作类型。 // 在这里,Property::Write 表示客户端可以通过写操作更新该特征的值。 // 其他常见的属性包括: // Property::Read:允许客户端读取特征值。 // Property::Notify:允许服务器发送通知。 // Property::Indicate:允许服务器发送指示 self.gatts.add_characteristic( service_handle, // 服务的唯一标识 &GattCharacteristic { uuid: BtUuid::uuid128(RECV_CHARACTERISTIC_UUID), permissions: enum_set!(Permission::Write), properties: enum_set!(Property::Write), max_len: 200, // 定义了特征值的最大长度(以字节为单位) auto_rsp: AutoResponse::ByApp, // 表示应用程序需要手动响应客户端的请求 }, &[], // 表示该特征的描述符(Descriptors)列表,&[] 表示没有附加任何描述符 )?; self.gatts.add_characteristic( service_handle, &GattCharacteristic { uuid: BtUuid::uuid128(IND_CHARACTERISTIC_UUID), permissions: enum_set!(Permission::Write | Permission::Read), properties: enum_set!(Property::Indicate), max_len: 200, // Mac iondicate data auto_rsp: AutoResponse::ByApp, }, &[], )?; Ok(()) } /// 添加 CCCD 描述 /// 只添加特征indicate fn register_characteristic( &self, service_handle: Handle, attr_handle: Handle, char_uuid: BtUuid, ) -> Result<(), EspError> { // 在找esp主动推送消息给客户端的uuid服务 let indicate_char = { let mut state = self.state.lock().unwrap(); if state.service_handle != Some(service_handle) { false } else if char_uuid == BtUuid::uuid128(RECV_CHARACTERISTIC_UUID) { state.recv_handle = Some(attr_handle); false } else if char_uuid == BtUuid::uuid128(IND_CHARACTERISTIC_UUID) { state.ind_handle = Some(attr_handle); true } else { false } }; if indicate_char { self.gatts.add_descriptor( service_handle, &GattDescriptor { uuid: BtUuid::uuid16(0x2902), // CCCD 通常用于控制特征的通知(Notify)和指示(Indicate)功能,允许客户端订阅或取消订阅特征值的变化 permissions: enum_set!(Permission::Read | Permission::Write), }, )?; } Ok(()) } /// 注册 CCCD 描述符 /// 在通知我们添加了描述符后,从事件回调中调用 /// 这段代码的作用是注册一个 CCCD(Client Characteristic Configuration Descriptor)描述符,并将其相关信息存储到内部状态中。具体功能如下: /// 验证描述符的 UUID 是否为标准的 CCCD UUID(0x2902)。 /// 确认描述符所属的服务句柄是否与内部记录的服务句柄一致。 /// 如果条件满足,则将描述符的属性句柄(attr_handle)存储到内部状态中,以便后续用于处理通知或指示功能。 /// 这种设计通常用于 BLE(蓝牙低功耗)设备的 GATT 服务实现中,确保 CCCD 的句柄被正确记录,从而支持客户端订阅或取消订阅特征值的变化 fn register_cccd_descriptor( &self, service_handle: Handle, attr_handle: Handle, descr_uuid: BtUuid, ) -> Result<(), EspError> { let mut state = self.state.lock().unwrap(); if descr_uuid == BtUuid::uuid16(0x2902) // CCCD UUID && state.service_handle == Some(service_handle) { state.ind_cccd_handle = Some(attr_handle); } Ok(()) } /// 从客户端接收消息 fn register_conn_mtu(&self, conn_id: ConnectionId, mtu: u16) -> Result<(), EspError> { let mut state = self.state.lock().unwrap(); // 找到目标客户端 if let Some(conn) = state .connections .iter_mut() .find(|conn| conn.conn_id == conn_id) { // 并且设置最大传输单元的值,字节 conn.mtu = Some(mtu); } Ok(()) } /// 创建一个新的连接 fn create_conn(&self, conn_id: ConnectionId, addr: BdAddr) -> Result<(), EspError> { // 创建指定大小的连接,如果超过,新的连接将不再处理 let added = { let mut state = self.state.lock().unwrap(); if state.connections.len() < MAX_CONNECTIONS { state .connections .push(Connection { peer: addr, conn_id, subscribed: false, mtu: None, }) .map_err(|_| ()) .unwrap(); true } else { false } }; if added { // 如果已经添加,则配置gap参数 // addr:表示目标设备的蓝牙地址(BdAddr),用于标识要配置连接参数的远程设备。 // min_int_ms:表示连接间隔的最小值(单位为毫秒)。 // max_int_ms:表示连接间隔的最大值(单位为毫秒)。 // latency_ms:表示连接延迟(单位为毫秒)。 // timeout_ms:表示超时时间(单位为毫秒)。 self.gap.set_conn_params_conf(addr, 10, 20, 0, 400)?; } Ok(()) } /// 删除一个连接 fn delete_conn(&self, addr: BdAddr) -> Result<(), EspError> { let mut state = self.state.lock().unwrap(); // Connection { peer, .. }:这是结构体解构语法,用于从 Connection 结构体中提取字段 peer if let Some(index) = state .connections .iter() .position(|Connection { peer, .. }| *peer == addr) { // remove: // 从向量中移除指定索引位置的元素。 // 将该索引之后的所有元素向前移动一位,以保持顺序。 // 时间复杂度为 O(n),因为可能需要移动多个元素。 // swap_remove: // 从向量中移除指定索引位置的元素。 // 使用最后一个元素填补空缺,不保证顺序。 // 时间复杂度为 O(1),因为只涉及少量操作。 state.connections.swap_remove(index); } Ok(()) } /// 一个辅助方法,用于处理向我们发送数据到 “recv” 特征的客户端 #[allow(clippy::too_many_arguments)] fn recv( &self, _gatt_if: GattInterface, conn_id: ConnectionId, _trans_id: TransferId, addr: BdAddr, handle: Handle, offset: u16, _need_rsp: bool, _is_prep: bool, value: &[u8], ) -> Result<bool, EspError> { let mut state = self.state.lock().unwrap(); let recv_handle = state.recv_handle; let ind_cccd_handle = state.ind_cccd_handle; let Some(conn) = state .connections .iter_mut() .find(|conn| conn.conn_id == conn_id) else { return Ok(false); }; if Some(handle) == ind_cccd_handle { // 订阅或取消订阅我们的(indication characteristic)指示特性 if offset == 0 && value.len() == 2 { let value = u16::from_le_bytes([value[0], value[1]]); if value == 0x02 { if !conn.subscribed { conn.subscribed = true; self.on_subscribed(conn.peer); } } else if conn.subscribed { conn.subscribed = false; self.on_unsubscribed(conn.peer); } } } else if Some(handle) == recv_handle { // Receive data on the recv characteristic self.on_recv(addr, value, offset, conn.mtu); } else { return Ok(false); } Ok(true) } /// 辅助方法,向Peer客户端发送响应节点,该 Peer 节点刚刚向我们发送了 “recv” 上的一些数据 /// characteristic. (特征) /// 这只是必要的,因为我们支持写确认(与未确认的写入相比,这是更复杂的情况) #[allow(clippy::too_many_arguments)] fn send_write_response( &self, gatt_if: GattInterface, conn_id: ConnectionId, trans_id: TransferId, handle: Handle, offset: u16, need_rsp: bool, is_prep: bool, value: &[u8], ) -> Result<(), EspError> { // 是否需要处理 写确认 if !need_rsp { return Ok(()); } // 是否准备好了数据 if is_prep { let mut state = self.state.lock().unwrap(); state .response .attr_handle(handle) .auth_req(0) .offset(offset) .value(value) .map_err(|_| EspError::from_infallible::<ESP_FAIL>())?; self.gatts.send_response( gatt_if, conn_id, trans_id, GattStatus::Ok, Some(&state.response), )?; } else { self.gatts .send_response(gatt_if, conn_id, trans_id, GattStatus::Ok, None)?; } Ok(()) } /// 处理下一个 indication fn confirm_indication(&self) -> Result<(), EspError> { let mut state = self.state.lock().unwrap(); if state.ind_confirmed.is_none() { // Should not happen:表示我们已收到 // 我们没有发送一个指示(indication) // unreachable!() 是 Rust 提供的一个宏,用于标记代码中“不应该到达”的位置,到这里会恐慌 unreachable!(); } // 通知主循环可以发送下一个indication state.ind_confirmed = None; // So that the main loop can send the next indication self.condvar.notify_all(); Ok(()) } fn check_esp_status(&self, status: Result<(), EspError>) { if let Err(e) = status { warn!("检查esp的状态错误为: {:?}", e); } } fn check_bt_status(&self, status: BtStatus) -> Result<(), EspError> { // matches! 是匹配模式,!取反 // bt状态不匹配就抛连接失败异常 if !matches!(status, BtStatus::Success) { warn!("Got status: {:?}", status); Err(EspError::from_infallible::<ESP_FAIL>()) } else { Ok(()) } } fn check_gatt_status(&self, status: GattStatus) -> Result<(), EspError> { if !matches!(status, GattStatus::Ok) { warn!("gatt的状态错误: {:?}", status); Err(EspError::from_infallible::<ESP_FAIL>()) } else { Ok(()) } } } 新建一个ble_recv_message.rs文件,处理BLE的消息接收use log::{error, info}; use crate::{driver_manage::{GLOBAL_LED}, message::{LedMessage, Message}}; pub fn handle_message(src:&str) -> anyhow::Result<()>{ let message = Message::from_str(src); if let Ok(message) = message{ match message { Message::Led(led_message) => { handle_led_message(led_message)? }, Message::Unknown => { error!("收到错误的类型") }, } } anyhow::Ok(()) } fn handle_led_message(led_message:LedMessage)->anyhow::Result<()>{ info!("{:?}",led_message); if led_message.value == 1{ let mut guard = GLOBAL_LED.lock().unwrap(); guard.as_mut().unwrap().set_high()?; }else { let mut guard = GLOBAL_LED.lock().unwrap(); guard.as_mut().unwrap().set_low()?; } anyhow::Ok(()) } 新建message.rs,处理消息传输,序列化跟反序列化 use serde::{Deserialize, Serialize}; #[derive(Debug,Serialize,Deserialize)] #[serde(tag="type")] pub enum Message{ #[serde(rename = "led")] Led(LedMessage), #[serde(other)] Unknown } #[derive(Debug,Serialize,Deserialize)] #[serde(rename_all = "camelCase")] pub struct LedMessage{ // 0关,1开 pub value:u8 } impl Message{ pub fn to_json(&self) -> serde_json::Result<String>{ serde_json::to_string(self) } pub fn from_str(src:&str) -> serde_json::Result<Self>{ serde_json::from_str(src) } } 新建driver_manage.rs文件,用来管理注册的外设use std::sync::Mutex; use esp_idf_hal::{gpio::{AnyIOPin, AnyOutputPin, Gpio9, Output, OutputPin, PinDriver}, peripheral::Peripheral, prelude::Peripherals}; pub static GLOBAL_LED :Mutex<Option<PinDriver<'static,AnyOutputPin,Output>>> = Mutex::new(None); pub fn init_global(led_gpio:AnyOutputPin) -> anyhow::Result<()>{ { let led = PinDriver::output(led_gpio)?; let mut guard = GLOBAL_LED.lock().unwrap(); *guard = Some(led); } anyhow::Ok(()) } 编辑main.rs文件 use esp_idf_hal::{ gpio::OutputPin, prelude::Peripherals}; use esp_idf_svc::{log::EspLogger, nvs::EspDefaultNvsPartition}; mod ble_server; mod driver_manage; mod message; mod ble_recv_message; fn main() -> anyhow::Result<()> { esp_idf_svc::sys::link_patches(); EspLogger::initialize_default(); let peripherals = Peripherals::take()?; let nvs = EspDefaultNvsPartition::take()?; driver_manage::init_global( peripherals.pins.gpio12.downgrade_output())?; ble_server::start(peripherals.modem,nvs.clone())?; anyhow::Ok(()) } 效果展示成功接收到客户端消息
2025年05月01日
235 阅读
0 评论
1 点赞
2025-04-30
rust裸机开发esp32c3
安装esp-generate# 安装新版生成工具 cargo install esp-generate # 安装依赖工具链 cargo install espup espup install激活环境变量source $HOME/export-esp.sh # linuxWindows用户默认在C:\Users\HOME目录下有一个 export-esp.ps1文件,双击执行激活环境变量使用esp-genertate 创建新项目esp-generate --chip=esp32c3 demo这个项目是个hello world项目,不需要额外的配置,接着按s创建项目添加依赖在Cargo.toml的[dependencies]添加println依赖esp-println = { version = "0.13.1",features = ["esp32c3","log"] }点亮LED修改mian.rs点亮一个led#![no_std] #![no_main] use esp_hal::clock::CpuClock; use esp_hal::gpio::{Level, Output, OutputConfig}; use esp_hal::main; use esp_hal::time::{Duration, Instant}; use esp_println::println; #[panic_handler] fn panic(_: &core::panic::PanicInfo) -> ! { loop {} } #[main] fn main() -> ! { // generator version: 0.3.1 let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); let peripherals = esp_hal::init(config); let mut led = Output::new(peripherals.GPIO9, Level::Low, OutputConfig::default()); let mut count = 0; loop { let delay_start = Instant::now(); led.toggle(); println!("第{count}次数改变led状态"); count+=1; while delay_start.elapsed() < Duration::from_millis(1500) {} } // for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.0.0-beta.0/examples/src/bin } 成功打印日志
2025年04月30日
179 阅读
0 评论
0 点赞
2025-04-27
rust开发esp32c3驱动oled屏幕
使用的是 I2C SSD1306 0.96寸的OLED屏幕1.在Cargo.toml中添加图形库依赖# 处理ssd1306屏幕 ssd1306 = {version = "0.10.0",features = ["graphics"]} embedded-graphics = "0.8.1"完整配置 Cargo.toml[package] name = "esp32c3-oled-demo" version = "0.1.0" authors = ["suxii <m@suxii.cn>"] edition = "2021" resolver = "2" rust-version = "1.77" [[bin]] name = "esp32c3-oled-demo" harness = false # do not use the built in cargo test harness -> resolve rust-analyzer errors [features] default = [] experimental = ["esp-idf-svc/experimental"] [dependencies] log = "0.4" esp-idf-svc = { version = "0.51", features = ["critical-section", "embassy-time-driver", "embassy-sync","experimental","std"] } futures = "0.3.31" anyhow = "1.0.98" esp-idf-hal = "0.45.2" # 处理ssd1306屏幕 ssd1306 = {version = "0.10.0",features = ["graphics"]} embedded-graphics = "0.8.1" [build-dependencies] embuild = "0.33" 2.在main.rs中编写rust代码use esp_idf_hal::{ delay::FreeRtos, i2c::*, peripherals::Peripherals, prelude::*, }; use embedded_graphics::{ mono_font::{ascii::FONT_6X10, MonoTextStyle}, pixelcolor::BinaryColor, prelude::*, primitives::{Circle, Line, PrimitiveStyle, Rectangle}, text::Text }; use ssd1306::{I2CDisplayInterface, Ssd1306, prelude::*, size::DisplaySize128x64}; fn main() -> anyhow::Result<()> { esp_idf_hal::sys::link_patches(); // 初始化外设 let peripherals = Peripherals::take()?; // 配置I2C let i2c = peripherals.i2c0; let sda = peripherals.pins.gpio5; let scl = peripherals.pins.gpio6; let config = I2cConfig::new().baudrate(400.kHz().into()); let i2c = I2cDriver::new(i2c, sda, scl, &config)?; // 创建SSD1306接口 let interface = I2CDisplayInterface::new(i2c); let mut display = Ssd1306::new( interface, DisplaySize128x64, DisplayRotation::Rotate0 ).into_buffered_graphics_mode(); // 初始化显示屏 display.init().expect("SSD1306 init failed"); display.flush().expect("Failed to flush display"); // 创建图形样式 let text_style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On); let line_style = PrimitiveStyle::with_stroke(BinaryColor::On, 1); let mut run_x = 64; loop { // 清空屏幕 display.clear(BinaryColor::Off).unwrap(); // 绘制边框 Rectangle::new(Point::new(0, 0), Size::new(128, 64)) .into_styled(line_style) .draw(&mut display).unwrap(); // 绘制文本 Text::new("Hello Rust!", Point::new(10, 15), text_style) .draw(&mut display).unwrap(); // 绘制直线 Line::new(Point::new(10, 25), Point::new(118, 25)) .into_styled(line_style) .draw(&mut display).unwrap(); // 绘制圆形 if run_x > 128 { run_x = 0 } run_x+=10; Circle::new(Point::new(run_x, 40), 20) .into_styled(line_style) .draw(&mut display).unwrap(); // 更新显示 display.flush().expect("Failed to flush display"); // 间隔2秒 FreeRtos::delay_ms(2000); } }3.接线 SSD1306SDA -> io05SCL -> io06VCC -> 5vGND -> GND4.效果展示
2025年04月27日
148 阅读
0 评论
1 点赞
Rust 与 Kotlin:优雅的数据传输与多态序列化实践
在现代软件开发中,尤其是在系统编程和移动应用领域,实现不同语言之间的数据交换至关重要。Rust 和 Kotlin 都以其高性能和安全性著称,提供了强大的多态序列化机制,能够确保数据结构在不同语言间的一致性和可维护性。本文将通过一个示例,展示如何在 Rust 和 Kotlin 中定义和处理多态消息,确保它们之间的无缝互操作。定义消息结构我们希望构建一个多态消息系统,包含两种类型的消息:LedMessage:控制 LED 的整数值。TextMessage:发送一条文本消息。Rust 实现Rust 使用 serde 库进行序列化和反序列化。通过使用 #[serde(tag = "type")] 属性,我们可以根据 type 字段区分不同的消息类型。use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] #[serde(tag = "type")] enum Message { #[serde(rename = "led")] Led(LedMessage), #[serde(rename = "text")] Text(TextMessage), #[serde(other)] Unknown, } #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct LedMessage { value: i32, } #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct TextMessage { text: String, } impl Message { fn to_json(&self) -> serde_json::Result<String> { serde_json::to_string(self) } fn from_str(src: &str) -> serde_json::Result<Self> { serde_json::from_str(src) } } fn test() { let led_message = Message::Led(LedMessage { value: 33 }); let encoded = led_message.to_json().unwrap(); println!("{}", encoded); match Message::from_str("{\"text\":\"hello text\",\"type\":\"text\"}").unwrap() { Message::Led(led_message) => { println!("收到 LedMessage:{}", led_message.value) } Message::Text(text_message) => { println!("收到 TextMessage:{}", text_message.text) } Message::Unknown => { println!("未知的消息类型") } } }在这个实现中,type 字段用于区分不同的消息类型,serde 会根据该字段自动选择合适的枚举变体进行反序列化。Kotlin 实现Kotlin 使用 kotlinx.serialization 库处理多态序列化。通过自定义序列化器,我们可以根据 type 字段动态选择合适的反序列化器。import kotlinx.serialization.* import kotlinx.serialization.json.* object MessageSerializer : JsonContentPolymorphicSerializer<Message>(Message::class) { override fun selectDeserializer(element: JsonElement): DeserializationStrategy<Message> { val type = element.jsonObject["type"]!!.jsonPrimitive.content return when (type) { "led" -> Message.LedMessage.serializer() "text" -> Message.TextMessage.serializer() else -> throw SerializationException("未知类型:$type") } } } @Serializable(with = MessageSerializer::class) sealed class Message { abstract val type: String fun toJson(): String { return json.encodeToString(Message.serializer(), this) } companion object { private val json = Json { ignoreUnknownKeys = false encodeDefaults = true } fun fromStr(src: String): Message { return json.decodeFromString<Message>(src) } } @Serializable data class LedMessage(val value: Int, override val type: String = "led") : Message() @Serializable data class TextMessage(val text: String, override val type: String = "text") : Message() }在这个实现中,MessageSerializer 会根据 JSON 中的 type 字段动态选择合适的反序列化器,从而实现多态反序列化。Rust 与 Kotlin 的互操作性为了实现 Rust 与 Kotlin 之间的无缝通信,我们可以在 Rust 中将 Message 枚举序列化为 JSON,然后在 Kotlin 中反序列化,或者反向操作。Rust 到 JSONlet led_message = Message::Led(LedMessage { value: 33 }); let json = led_message.to_json().unwrap(); println!("{}", json);输出:{"type":"led","value":33}Kotlin 从 JSONval json = """{"type":"led","value":33}""" val message = Message.fromStr(json) println(message)输出:LedMessage(value=33, type=led)通过这种方式,Rust 和 Kotlin 可以共享相同的数据结构,确保数据的一致性和类型安全。总结通过利用 Rust 的 serde 和 Kotlin 的 kotlinx.serialization,我们可以实现优雅且高效的多态序列化。这种方式不仅确保了类型安全,还促进了不同语言之间的平滑数据交换。采用这些序列化技术,可以增强应用程序的互操作性,使其更加健壮和可维护。
2025年04月26日
143 阅读
0 评论
1 点赞
1
2
3
4
...
24