• Rust 通过 DBC 解析传感器数据



    本篇给出一个 Rust 通过FFI(Foreign Function Interface)调用 DBC生成的C代码 解析传感器数据的例子

    传感器 DBC

    以一个简单的开放DBC的倾角传感器MTLT305D为例, DBC可以到官网下载

    下载后重命名为 MTLT305D_dbc_19.1.51_20190621.dbc, 注意:

    • 可以不需要CAN分析仪, PC上直接用vcan或vxcan模拟即可
    • 可以不需要真的去买一个传感器, 下面会写一个传感器的模拟器
    • 这可能是一个非标准的DBC或者里面存在一些错误, 用 rust 开源的 dbccdbc-codegen 生成报错, python cantools 可能有一定的纠错能力, 是可以顺利生成C代码的, 这也是本篇的由来.

    VXCAN

    首先是PC没有can口, 用VXCAN来模拟一对 can0-vxcan0, 解析程序用can0, 模拟器vxcan0, 脚本vxcan.sh如下

    #!/bin/sh
    sudo modprobe can_raw
    sudo modprobe vxcan
    
    if ip link show can0 > /dev/null 2>&1; then
        sudo ip link set dev can0 down
        sudo ip link set dev vxcan0 down
        sudo ip link delete dev can0 type vxcan
    fi
    
    sudo ip link add dev can0 type vxcan
    sudo ip link set up can0
    sudo ip link set dev vxcan0 up
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    Python Cantools 写传感器模拟器

    用Python配合cantools来写传感器模拟器极为简单, 先安装必要的库

    python3 -m pip install python-can
    python3 -m pip install cantools
    
    • 1
    • 2

    倾角传感器主要用到 3轴acc, 3轴gyro, pitch, roll, 主要涉及3帧报文, 所以模拟器里也只实现这3帧报文, 100Hz, 造假数据循环播发. 加载DBC, 填充消息即可, fake_mtlt305d.py如下

    #!/usr/bin/python3
    
    import can
    import cantools
    import time
    from datetime import datetime
    from threading import Timer
    
    can_bus = can.interface.Bus(bustype='socketcan', channel='vxcan0', bitrate=500000)
    sensor_dbc = cantools.database.load_file('MTLT305D_dbc_19.1.51_20190621.dbc')
    
    def x8F02D80_send(acc_x, acc_y, acc_z):
        msg = sensor_dbc.get_message_by_name("Aceinna_Accel")
        data = msg.encode({
            "Aceinna_AccX": acc_x,
            "Aceinna_AccY": acc_y,
            "Aceinna_AccZ": acc_z,
            "Aceinna_LateralAcc_FigureOfMerit": 0,
            "Aceinna_LongiAcc_FigureOfMerit": 0,
            "Aceinna_VerticAcc_FigureOfMerit": 0,
            "Aceinna_Support_Rate_Acc": 0
        })
        message = can.Message(
            arbitration_id = msg.frame_id,
            data = data,
            is_extended_id = True
        )
        can_bus.send(message)
    
    def xCF02A80_send(gyro_x, gyro_y, gyro_z):
        msg = sensor_dbc.get_message_by_name("Aceinna_AngleRate")
        data = msg.encode({
            "Aceinna_GyroX": gyro_x,
            "Aceinna_GyroY": gyro_y,
            "Aceinna_GyroZ": gyro_z,
            "Aceinna_PitchRate_Figure_OfMerit": 0,
            "Aceinna_RollRate_Figure_OfMerit": 0,
            "Aceinna_YawRate_Figure_OfMerit": 0,
            "Aceinna_AngleRate_Latency": 0
        })
        message = can.Message(
            arbitration_id = msg.frame_id,
            data = data,
            is_extended_id = True
        )
        can_bus.send(message)
    
    def xCF02980_send(pitch, roll):
        msg = sensor_dbc.get_message_by_name("Aceinna_Angles")
        data = msg.encode({
            "Aceinna_Pitch": pitch,
            "Aceinna_Roll": roll,
            "Aceinna_Pitch_Compensation": 0,
            "Aceinna_Pitch_Figure_OfMerit": 0,
            "Aceinna_Roll_Compensation": 0,
            "Aceinna_Roll_Figure_OfMerit": 0,
            "Aceinna_PitchRoll_Latency": 0
        })
        message = can.Message(
            arbitration_id = msg.frame_id,
            data = data,
            is_extended_id = True
        )
        can_bus.send(message)
    
    if __name__ == '__main__':
        cnt = 0
        while True:
            t = time.time()
            xCF02980_send(7+cnt,8+cnt)
            x8F02D80_send(1+cnt,2+cnt,3+cnt)
            xCF02A80_send(4+cnt,5+cnt,6+cnt)
            cnt = (cnt+1)%10
            dt = time.time() - t
            if dt < 0.01:
                time.sleep(0.01 - dt)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    运行程序, 检验

    $ python3 fake_mtlt305d.py
    
    $ candump -td -x can0
     ...
     (000.010054)  can0  TX - -  0CF02980   [8]  00 80 81 00 00 82 00 00
     (000.000059)  can0  TX - -  08F02D80   [8]  2C 7E 90 7E F4 7E 00 00
     (000.000027)  can0  TX - -  0CF02A80   [8]  00 80 80 80 00 81 00 00
     (000.010002)  can0  TX - -  0CF02980   [8]  00 00 82 00 80 82 00 00
     (000.000060)  can0  TX - -  08F02D80   [8]  90 7E F4 7E 58 7F 00 00
     (000.000026)  can0  TX - -  0CF02A80   [8]  80 80 00 81 80 81 00 00
     (000.010072)  can0  TX - -  0CF02980   [8]  00 80 82 00 00 83 00 00
     (000.000062)  can0  TX - -  08F02D80   [8]  F4 7E 58 7F BC 7F 00 00
     (000.000027)  can0  TX - -  0CF02A80   [8]  00 81 80 81 00 82 00 00
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    DBC 生成C代码

    python3 -m cantools generate_c_source --database-name mtlt305d -e UTF-8  MTLT305D_dbc_19.1.51_20190621.dbc
    
    • 1

    把生成的 mtlt305d.cmtlt305d.h 移到src文件夹.

    Rust bindgen

    先来看下最终的工程目录

    $ tree play_ffi_cantools/
    play_ffi_cantools/
    ├── build.rs
    ├── Cargo.toml
    ├── fake_mtlt305d.py
    ├── MTLT305D_dbc_19.1.51_20190621.dbc
    ├── src
    │   ├── can.rs
    │   ├── main.rs
    │   ├── mtlt305d.c
    │   └── mtlt305d.h
    ├── vxcan.sh
    └── wrapper.h
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    Library Usage with build.rs - The bindgen User Guide (rust-lang.github.io)

    bindgen添加到Cargo.toml, 事实上还需要 rust-lang/cc-rs: Rust library for build scripts to compile C/C++ code into a Rust library (github.com) 把 C代码编译成库, dependencies里的三个库可参考上篇, 是给CAN用的:

    $ cat Cargo.toml
    [package]
    name = "play_ffi_cantools"
    version = "0.1.0"
    edition = "2021"
    
    [dependencies]
    libc = "0.2.132"
    ifstructs = "0.1.1"
    iptool = "0.1.0"
    
    [build-dependencies]
    bindgen = "0.60.1"
    cc = "1.0.73"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    写一个wrapper.h, (不一定非要叫这个名字, 和build.rs对应上就行?)

    #include "src/mtlt305d.h"
    
    • 1

    编写build.rs: 首先是用cc把dbc生成的c代码编译成库, 然后在编译时生成 Rust FFI 绑定

    extern crate bindgen;
    extern crate cc;
    
    use std::env;
    use std::path::PathBuf;
    
    fn main() {
        cc::Build::new()
            .file("src/mtlt305d.c")
            .compile("mtlt305d");
    
        let bindings = bindgen::Builder::default()
            .header("wrapper.h")
            .parse_callbacks(Box::new(bindgen::CargoCallbacks))
            .generate()
            .expect("Unable to generate bindings");
    
        let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
        bindings
            .write_to_file(out_path.join("bindings.rs"))
            .expect("Couldn't write bindings!");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    给库写rust接口或者直接填充src/main.rs解析:

    #![allow(non_upper_case_globals)]
    #![allow(non_camel_case_types)]
    #![allow(non_snake_case)]
    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
    
    mod can;
    
    #[derive(Debug)]
    struct mtlt305d_t {
        acc_x: f64,
        acc_y: f64,
        acc_z: f64,
        gyro_x: f64,
        gyro_y: f64,
        gyro_z: f64,
        pitch: f64,
        roll: f64,
    }
    
    impl mtlt305d_t {
        fn new() -> mtlt305d_t {
            mtlt305d_t {
                acc_x: 0.0,
                acc_y: 0.0,
                acc_z: 0.0,
                gyro_x: 0.0,
                gyro_y: 0.0,
                gyro_z: 0.0,
                pitch: 0.0,
                roll: 0.0,
            }
        }
    }
    
    fn mtlt305d_parser(frame: &can::CanFrame, mtlt305d: &mut mtlt305d_t) -> i32 {
        let id = frame.can_id & 0x1FFFFFFF;
        let ret = match id {
            MTLT305D_ACEINNA_ANGLES_FRAME_ID => unsafe {
                let mut msg = std::mem::zeroed();
                mtlt305d_aceinna_angles_unpack(
                    &mut msg,
                    &frame.data[0],
                    MTLT305D_ACEINNA_ANGLES_LENGTH.into(),
                );
                mtlt305d.pitch = mtlt305d_aceinna_angles_aceinna_pitch_decode(msg.aceinna_pitch);
                mtlt305d.roll = mtlt305d_aceinna_angles_aceinna_roll_decode(msg.aceinna_roll);
                1
            },
            MTLT305D_ACEINNA_ACCEL_FRAME_ID => unsafe {
                let mut msg = std::mem::zeroed();
                mtlt305d_aceinna_accel_unpack(
                    &mut msg,
                    &frame.data[0],
                    MTLT305D_ACEINNA_ACCEL_LENGTH.into(),
                );
                mtlt305d.acc_x = mtlt305d_aceinna_accel_aceinna_acc_x_decode(msg.aceinna_acc_x);
                mtlt305d.acc_y = mtlt305d_aceinna_accel_aceinna_acc_y_decode(msg.aceinna_acc_y);
                mtlt305d.acc_z = mtlt305d_aceinna_accel_aceinna_acc_z_decode(msg.aceinna_acc_z);
                2
            },
            MTLT305D_ACEINNA_ANGLE_RATE_FRAME_ID => unsafe {
                let mut msg = std::mem::zeroed();
                mtlt305d_aceinna_angle_rate_unpack(
                    &mut msg,
                    &frame.data[0],
                    MTLT305D_ACEINNA_ANGLE_RATE_LENGTH.into(),
                );
                mtlt305d.gyro_x = mtlt305d_aceinna_angle_rate_aceinna_gyro_x_decode(msg.aceinna_gyro_x);
                mtlt305d.gyro_y = mtlt305d_aceinna_angle_rate_aceinna_gyro_y_decode(msg.aceinna_gyro_y);
                mtlt305d.gyro_z = mtlt305d_aceinna_angle_rate_aceinna_gyro_z_decode(msg.aceinna_gyro_z);
                3
            },
            _ => {
                println!("{}", id);
                0
            },
        }
        .into();
        ret
    }
    
    fn main() {
        let fd = can::can_open("can0");
        if fd < 0 {
            println!("can_open failed");
            return;
        }
        let mut mtlt305d = mtlt305d_t::new();
        let mut frame = can::CanFrame::new();
        loop {
            can::can_read(fd, &mut frame);
            let ret = mtlt305d_parser(&frame, &mut mtlt305d);
            if ret == 3 {
                println!("{:?}", mtlt305d);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97

    编译运行

    $ cargo build
    
    # cargo run -p play_ffi_cantools
    # cargo run --bin play_ffi_cantools
    # ./target/debug/play_ffi_cantools
    $ cargo run -p play_ffi_cantools
        Finished dev [unoptimized + debuginfo] target(s) in 0.02s
         Running `target/debug/play_ffi_cantools`
    mtlt305d_t { acc_x: 1.0, acc_y: 2.0, acc_z: 3.0, gyro_x: 4.0, gyro_y: 5.0, gyro_z: 6.0, pitch: 7.0, roll: 8.0 }
    mtlt305d_t { acc_x: 2.0, acc_y: 3.0, acc_z: 4.0, gyro_x: 5.0, gyro_y: 6.0, gyro_z: 7.0, pitch: 8.0, roll: 9.0 }
    mtlt305d_t { acc_x: 3.0, acc_y: 4.0, acc_z: 5.0, gyro_x: 6.0, gyro_y: 7.0, gyro_z: 8.0, pitch: 9.0, roll: 10.0 }
    mtlt305d_t { acc_x: 4.0, acc_y: 5.0, acc_z: 6.0, gyro_x: 7.0, gyro_y: 8.0, gyro_z: 9.0, pitch: 10.0, roll: 11.0 }
    mtlt305d_t { acc_x: 5.0, acc_y: 6.0, acc_z: 7.0, gyro_x: 8.0, gyro_y: 9.0, gyro_z: 10.0, pitch: 11.0, roll: 12.0 }
    mtlt305d_t { acc_x: 6.0, acc_y: 7.0, acc_z: 8.0, gyro_x: 9.0, gyro_y: 10.0, gyro_z: 11.0, pitch: 12.0, roll: 13.0 }
    mtlt305d_t { acc_x: 7.0, acc_y: 8.0, acc_z: 9.0, gyro_x: 10.0, gyro_y: 11.0, gyro_z: 12.0, pitch: 13.0, roll: 14.0 }
    mtlt305d_t { acc_x: 8.0, acc_y: 9.0, acc_z: 10.0, gyro_x: 11.0, gyro_y: 12.0, gyro_z: 13.0, pitch: 14.0, roll: 15.0 }
    mtlt305d_t { acc_x: 9.0, acc_y: 10.0, acc_z: 11.0, gyro_x: 12.0, gyro_y: 13.0, gyro_z: 14.0, pitch: 15.0, roll: 16.0 }
    mtlt305d_t { acc_x: 10.0, acc_y: 11.0, acc_z: 12.0, gyro_x: 13.0, gyro_y: 14.0, gyro_z: 15.0, pitch: 16.0, roll: 17.0 }
    mtlt305d_t { acc_x: 1.0, acc_y: 2.0, acc_z: 3.0, gyro_x: 4.0, gyro_y: 5.0, gyro_z: 6.0, pitch: 7.0, roll: 8.0 }
    mtlt305d_t { acc_x: 2.0, acc_y: 3.0, acc_z: 4.0, gyro_x: 5.0, gyro_y: 6.0, gyro_z: 7.0, pitch: 8.0, roll: 9.0 }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    可以看到解析结果正如模拟器所发…

    Github

    rust_note/playground/play_ffi_cantools at main · weifengdq/rust_note (github.com)

  • 相关阅读:
    RPC与微服务
    Python3 - Linux 下安装 LibreOffice 以及使用
    单位换算表大全
    三、博客首页完成《iVX低代码仿CSDN个人博客制作》
    Django:五、登录界面实现动态图片验证码
    .bashrc中配置环境变量不生效
    【元宇宙欧米说】从个人创作者的角度聊聊NFT
    【linux驱动开发】-驱动入门之LED
    JUC源码学习笔记8——ConcurrentHashMap源码分析1 如何实现低粒度锁的插入,如何实现统计元素个数,如何实现并发扩容迁移
    【软考系统架构设计师】2023年系统架构师冲刺模拟习题之《数据库系统》
  • 原文地址:https://blog.csdn.net/weifengdq/article/details/126490089