Rust嵌入式日志解码库defmt-decoder的使用,高效解析和转换defmt格式的二进制日志数据
Rust嵌入式日志解码库defmt-decoder的使用,高效解析和转换defmt格式的二进制日志数据
defmt-decoder
defmt
(“de format”,是"deferred formatting"的缩写)是一个针对资源受限设备(如微控制器)的高效日志框架。
这个库用于将defmt
框架的二进制日志解码为格式化字符串。它被defmt-print
和其他工具使用。
MSRV
最低支持的Rust版本是1.76(或Ferrocene 24.05)。defmt
会在最新的稳定Rust版本和MSRV上进行测试。
支持
defmt
是Knurling项目的一部分,这是Ferrous Systems为改善嵌入式系统开发工具所做的努力。
许可证
可选择以下任一种许可证:
- Apache License, Version 2.0
- MIT license
贡献
除非您明确声明,否则您有意提交的任何贡献都将按上述许可证授权,不附加任何额外条款或条件。
安装
在项目目录中运行以下Cargo命令:
cargo add defmt-decoder
或在Cargo.toml中添加:
defmt-decoder = "1.0.0"
完整示例代码
use defmt_decoder::Frame;
use std::io::Read;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 假设我们有一个包含defmt二进制日志的文件
let mut file = std::fs::File::open("log.bin")?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
// 创建解码器实例
let decoder = defmt_decoder::Decoder::new(buffer.as_slice())?;
// 解码所有帧
for frame in decoder {
match frame {
Ok(Frame::Text { text, .. }) => {
// 打印解码后的文本
println!("{}", text);
}
Ok(Frame::Binary { payload, .. }) => {
// 处理二进制数据
println!("Binary data: {:?}", payload);
}
Err(e) => {
eprintln!("Decoding error: {}", e);
}
}
}
Ok(())
}
这个示例展示了如何使用defmt-decoder
库来解码defmt格式的二进制日志文件。首先读取日志文件,然后创建解码器实例,最后遍历并解码所有帧,打印出文本内容或二进制数据。
进阶使用示例
以下是一个更完整的示例,展示了如何处理不同类型的defmt日志帧:
use defmt_decoder::{Decoder, Frame, Location};
use std::{fs::File, io::Read, path::Path};
// 自定义日志处理器
struct LogProcessor;
impl LogProcessor {
fn process(&self, frame: Frame) {
match frame {
Frame::Text { text, location } => {
println!("[LOG] {}", text);
if let Some(loc) = location {
println!(" Location: {}", loc);
}
}
Frame::Binary { payload, location } => {
println!("[BINARY DATA] {} bytes", payload.len());
if let Some(loc) = location {
println!(" Location: {}", loc);
}
// 这里可以添加自定义的二进制数据处理逻辑
}
}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 日志文件路径
let log_path = Path::new("embedded_log.bin");
// 读取日志文件内容
let mut file = File::open(log_path)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
// 创建解码器
let decoder = Decoder::new(&buffer)?;
// 创建日志处理器
let processor = LogProcessor;
// 处理每一条日志
for frame_result in decoder {
match frame_result {
Ok(frame) => processor.process(frame),
Err(e) => eprintln!("Error decoding frame: {}", e),
}
}
Ok(())
}
这个进阶示例:
- 定义了一个自定义的日志处理器
LogProcessor
- 可以处理文本和二进制两种类型的日志帧
- 支持显示日志位置信息
- 提供了更完善的错误处理
要使用这个示例,你需要:
- 准备一个由嵌入式设备生成的defmt格式的日志文件(通常命名为*.bin)
- 将此代码保存为main.rs
- 运行
cargo run
来解码日志文件
Rust嵌入式日志解码库defmt-decoder使用指南
简介
defmt-decoder是一个用于解析和转换defmt格式二进制日志数据的Rust库。defmt("deferred formatting"的缩写)是专为嵌入式系统设计的高效日志框架,而defmt-decoder则用于在主机端解码这些日志数据。
主要特性
- 高效解析defmt格式的二进制日志
- 支持从ELF文件中提取类型信息
- 提供多种输出格式选项
- 与defmt日志框架无缝集成
安装方法
在Cargo.toml中添加依赖:
[dependencies]
defmt-decoder = "0.3"
基本使用方法
1. 从ELF文件创建表
use defmt_decoder::Table;
// 读取ELF文件并解析为表
let elf = std::fs::read("path/to/your/firmware.elf")?;
let table = Table::parse(&elf)?;
2. 解码日志数据
use defmt_decoder::{log::LevelFilter, DefmtStream, StreamDecoder};
let bytes = /* 从串口或文件读取的defmt二进制数据 */;
let mut stream = DefmtStream::new(bytes, &table);
let mut decoder = StreamDecoder::new(&mut stream);
// 解码并打印每一帧日志
while let Some(frame) = decoder.decode()? {
println!("{}", frame.display(false));
}
3. 过滤日志级别
use defmt_decoder::log::LevelFilter;
// 只解码Info及以上级别的日志
let mut decoder = StreamDecoder::new(&mut stream)
.with_max_level(LevelFilter::Info);
高级用法
从串口实时解码
use serialport::SerialPort;
// 打开串口
let port = serialport::new("/dev/ttyUSB0", 115_200)
.open()
.expect("Failed to open port");
// 创建流和解码器
let mut stream = DefmtStream::new(port, &table);
let mut decoder = StreamDecoder::new(&mut stream);
// 实时解码循环
loop {
if let Some(frame) = decoder.decode().expect("Decoding failed") {
println!("{}", frame.display(false));
}
}
保存解码日志到文件
use std::fs::File;
use std::io::Write;
// 创建输出文件
let mut output = File::create("decoded.log")?;
// 解码并写入文件
while let Some(frame) = decoder.decode()? {
writeln!(output, "{}", frame.display(false))?;
}
命令行工具
defmt-decoder也可以作为命令行工具使用:
- 安装:
cargo install defmt-decoder
- 基本使用:
defmt-decoder firmware.elf < input.bin > output.txt
- 从串口读取:
defmt-decoder firmware.elf --serial /dev/ttyUSB0 --baud 115200
注意事项
- 确保使用的ELF文件与生成日志的固件完全匹配
- 对于实时解码,建议使用适当的缓冲策略
- 如果日志格式不匹配,可能需要检查defmt版本兼容性
示例项目结构
典型的嵌入式项目使用defmt和defmt-decoder的结构:
my-embedded-project/
├── firmware/
│ ├── src/
│ │ └── main.rs # 使用defmt日志宏
│ └── Cargo.toml # 依赖defmt
└── host-tools/
├── src/
│ └── main.rs # 使用defmt-decoder
└── Cargo.toml # 依赖defmt-decoder
完整示例demo
以下是使用defmt-decoder的完整示例代码:
use defmt_decoder::{log::LevelFilter, DefmtStream, StreamDecoder, Table};
use serialport::SerialPort;
use std::{fs::File, io::Write, error::Error};
fn main() -> Result<(), Box<dyn Error>> {
// 1. 从ELF文件创建表
let elf = std::fs::read("target/thumbv7m-none-eabi/debug/firmware")?;
let table = Table::parse(&elf)?;
// 2. 打开串口
let port = serialport::new("/dev/ttyACM0", 115_200)
.timeout(std::time::Duration::from_millis(1000))
.open()?;
// 3. 创建日志文件
let mut log_file = File::create("device_logs.txt")?;
// 4. 创建流和解码器
let mut stream = DefmtStream::new(port, &table);
let mut decoder = StreamDecoder::new(&mut stream)
.with_max_level(LevelFilter::Debug);
// 5. 主解码循环
loop {
match decoder.decode() {
Ok(Some(frame)) => {
let log_entry = frame.display(false);
println!("{}", log_entry); // 打印到控制台
writeln!(log_file, "{}", log_entry)?; // 写入文件
}
Ok(None) => continue,
Err(e) => eprintln!("解码错误: {}", e),
}
}
}
这个完整示例展示了如何:
- 从ELF文件加载类型信息
- 配置串口连接
- 创建日志文件
- 设置日志级别过滤
- 实现实时解码循环,同时输出到控制台和日志文件
要使用此示例,您需要:
- 在Cargo.toml中添加依赖:
[dependencies]
defmt-decoder = "0.3"
serialport = "4.2"
- 确保有正确的ELF文件和串口设备路径