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(())
}

这个进阶示例:

  1. 定义了一个自定义的日志处理器LogProcessor
  2. 可以处理文本和二进制两种类型的日志帧
  3. 支持显示日志位置信息
  4. 提供了更完善的错误处理

要使用这个示例,你需要:

  1. 准备一个由嵌入式设备生成的defmt格式的日志文件(通常命名为*.bin)
  2. 将此代码保存为main.rs
  3. 运行cargo run来解码日志文件

1 回复

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也可以作为命令行工具使用:

  1. 安装:
cargo install defmt-decoder
  1. 基本使用:
defmt-decoder firmware.elf < input.bin > output.txt
  1. 从串口读取:
defmt-decoder firmware.elf --serial /dev/ttyUSB0 --baud 115200

注意事项

  1. 确保使用的ELF文件与生成日志的固件完全匹配
  2. 对于实时解码,建议使用适当的缓冲策略
  3. 如果日志格式不匹配,可能需要检查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),
        }
    }
}

这个完整示例展示了如何:

  1. 从ELF文件加载类型信息
  2. 配置串口连接
  3. 创建日志文件
  4. 设置日志级别过滤
  5. 实现实时解码循环,同时输出到控制台和日志文件

要使用此示例,您需要:

  1. 在Cargo.toml中添加依赖:
[dependencies]
defmt-decoder = "0.3"
serialport = "4.2"
  1. 确保有正确的ELF文件和串口设备路径
回到顶部