基于 Rust 与 GBT32960 规范的编解码层

news2025/3/4 14:14:13

根据架构设计,实现编解码层的代码设计

Cargo.toml 加入二进制序列化支持

# 序列化支持
...
bincode = "1.3"           # 添加二进制序列化支持
bytes-utils = "0.1"       # 添加字节处理工具

开始编码

错误处理(error.rs):

定义了编解码过程中可能遇到的错误类型,使用枚举定义

use thiserror::Error;

#[derive(Error, Debug)]
pub enum CodecError {
    #[error("数据长度不足")]
    InsufficientData,

    #[error("校验和错误")]
    ChecksumMismatch,

    #[error("无效的起始符")]
    InvalidStartByte,

    #[error("无效的命令标识: {0}")] InvalidCommand(u8),

    #[error("IO错误: {0}")] Io(#[from] std::io::Error),
}

数据帧结构(frame.rs):

- 定义了符合 GBT32960 协议的数据帧结构
- 提供了创建和校验数据帧的方法

frame.rs

use bytes::{ Bytes, BytesMut, BufMut };
use chrono::NaiveDateTime;
use serde::{ Serialize, Deserialize };

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Frame {
    pub start_byte: u8, // 起始符 0x23
    pub command_flag: u8, // 命令标识
    pub response_flag: u8, // 应答标志
    pub vin: String, // 车辆识别码
    pub encrypt_method: u8, // 加密方式
    pub payload_length: u16, // 数据单元长度
    pub payload: Bytes, // 数据单元
    pub checksum: u8, // BCC校验码
}

impl Frame {
    pub fn new(command: u8, vin: String, payload: Bytes) -> Self {
        let payload_length = payload.len() as u16;
        Self {
            start_byte: 0x23,
            command_flag: command,
            response_flag: 0xfe,
            vin,
            encrypt_method: 0x01,
            payload_length,
            payload,
            checksum: 0x00, // 将在编码时计算
        }
    }

    pub fn calculate_checksum(&self) -> u8 {
        let mut bcc: u8 = 0;
        // 命令标识
        bcc ^= self.command_flag;
        // 应答标志
        bcc ^= self.response_flag;
        // VIN码(17位)
        for byte in self.vin.as_bytes() {
            bcc ^= byte;
        }
        // 加密方式
        bcc ^= self.encrypt_method;
        // 数据单元长度(2字节)
        bcc ^= ((self.payload_length >> 8) & 0xff) as u8;
        bcc ^= (self.payload_length & 0xff) as u8;
        // 数据单元
        for byte in self.payload.iter() {
            bcc ^= byte;
        }
        bcc
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_calculate_checksum() {
        let payload = Bytes::from_static(&[0x01, 0x02, 0x03]);
        let frame = Frame::new(
            0x01, // command
            "LSVNV2182E0200001".to_string(), // vin
            payload
        );

        let checksum = frame.calculate_checksum();
        assert!(checksum != 0, "校验和不应该为0");

        // 创建相同内容的帧,校验和应该相同
        let frame2 = Frame::new(
            0x01,
            "LSVNV2182E0200001".to_string(),
            Bytes::from_static(&[0x01, 0x02, 0x03])
        );
        assert_eq!(checksum, frame2.calculate_checksum(), "相同内容的帧应该有相同的校验和");
    }

    #[test]
    fn test_different_content_different_checksum() {
        let frame1 = Frame::new(0x01, "LSVNV2182E0200001".to_string(), Bytes::from_static(&[0x01]));

        let frame2 = Frame::new(0x01, "LSVNV2182E0200001".to_string(), Bytes::from_static(&[0x02]));

        assert_ne!(
            frame1.calculate_checksum(),
            frame2.calculate_checksum(),
            "不同内容的帧应该有不同的校验和"
        );
    }
}

编解码器(codec.rs):

- 实现了 tokio 的 Decoder 和 Encoder trait
- 负责数据帧的序列化和反序列化

use bytes::{ BytesMut, Buf };
use tokio_util::codec::{ Decoder, Encoder };
use super::{ Frame, CodecError };

pub struct Gbt32960Codec;

impl Decoder for Gbt32960Codec {
    type Item = Frame;
    type Error = CodecError;

    fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
        // 检查数据长度是否足够
        if src.len() < 22 {
            // 最小帧长度
            return Ok(None);
        }

        // 检查起始符
        if src[0] != 0x23 {
            return Err(CodecError::InvalidStartByte);
        }

        // TODO: 实现完整的解码逻辑
        // 1. 读取各个字段
        // 2. 验证校验和
        // 3. 解析数据单元

        Ok(None)
    }
}

impl Encoder<Frame> for Gbt32960Codec {
    type Error = CodecError;

    fn encode(&mut self, frame: Frame, dst: &mut BytesMut) -> Result<(), Self::Error> {
        // TODO: 实现编码逻辑
        // 1. 写入各个字段
        // 2. 计算并写入校验和
        Ok(())
    }
}

实现完整的解码逻辑

use bytes::{BytesMut, Buf, BufMut};
use tokio_util::codec::{Decoder, Encoder};
use super::{Frame, CodecError};

pub struct Gbt32960Codec;

impl Decoder for Gbt32960Codec {
    type Item = Frame;
    type Error = CodecError;

    fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
        // 检查数据长度是否足够
        if src.len() < 22 {  // 最小帧长度
            return Ok(None);
        }

        // 检查起始符
        if src[0] != 0x23 {
            return Err(CodecError::InvalidStartByte);
        }

        // 读取命令标识和应答标志
        let command_flag = src[1];
        let response_flag = src[2];

        // 读取 VIN 码(17字节)
        let vin = String::from_utf8_lossy(&src[3..20]).to_string();

        // 读取加密方式
        let encrypt_method = src[20];

        // 读取数据单元长度(2字节)
        let payload_length = ((src[21] as u16) << 8) | (src[22] as u16);

        // 检查是否有足够的数据
        let total_length = 23 + payload_length as usize + 1; // 头部 + 数据单元 + 校验码
        if src.len() < total_length {
            return Ok(None);
        }

        // 读取数据单元
        let payload = src.slice(23..23 + payload_length as usize);

        // 读取校验码
        let received_checksum = src[total_length - 1];

        // 创建帧对象进行校验和计算
        let frame = Frame {
            start_byte: 0x23,
            command_flag,
            response_flag,
            vin,
            encrypt_method,
            payload_length,
            payload: payload.freeze(),
            checksum: received_checksum,
        };

        // 验证校验和
        let calculated_checksum = frame.calculate_checksum();
        if calculated_checksum != received_checksum {
            return Err(CodecError::ChecksumMismatch);
        }

        // 消费已处理的字节
        src.advance(total_length);

        Ok(Some(frame))
    }
}

impl Encoder<Frame> for Gbt32960Codec {
    type Error = CodecError;

    fn encode(&mut self, frame: Frame, dst: &mut BytesMut) -> Result<(), Self::Error> {
        // TODO: 实现编码逻辑
        // 1. 写入各个字段
        // 2. 计算并写入校验和
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use bytes::Bytes;

    #[test]
    fn test_decode_valid_frame() {
        let mut codec = Gbt32960Codec;
        let mut buffer = BytesMut::new();

        // 构造测试数据
        buffer.put_u8(0x23);                          // 起始符
        buffer.put_u8(0x01);                          // 命令标识
        buffer.put_u8(0xFE);                          // 应答标志
        buffer.extend_from_slice(b"LSVNV2182E0200001"); // VIN码
        buffer.put_u8(0x01);                          // 加密方式
        buffer.put_u16(2);                            // 数据长度
        buffer.extend_from_slice(&[0x01, 0x02]);      // 数据单元
        
        // 计算并添加校验和
        let checksum = buffer[1..buffer.len()].iter().fold(0u8, |acc, &x| acc ^ x);
        buffer.put_u8(checksum);

        // 解码
        let result = codec.decode(&mut buffer).unwrap().unwrap();

        // 验证解码结果
        assert_eq!(result.command_flag, 0x01);
        assert_eq!(result.vin, "LSVNV2182E0200001");
        assert_eq!(result.payload.len(), 2);
        assert_eq!(buffer.len(), 0); // 确保所有数据都被消费
    }

    #[test]
    fn test_decode_invalid_checksum() {
        let mut codec = Gbt32960Codec;
        let mut buffer = BytesMut::new();

        // 构造测试数据(使用错误的校验和)
        buffer.put_u8(0x23);
        buffer.put_u8(0x01);
        buffer.put_u8(0xFE);
        buffer.extend_from_slice(b"LSVNV2182E0200001");
        buffer.put_u8(0x01);
        buffer.put_u16(0);
        buffer.put_u8(0xFF); // 错误的校验和

        // 验证解码失败
        assert!(matches!(
            codec.decode(&mut buffer),
            Err(CodecError::ChecksumMismatch)
        ));
    }
}

代码地址

阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台

总结

1. 完整的帧解析逻辑:
   - 起始符验证,根据接口协议验证是否0x23开头
   - 命令标识和应答标志解析
   - VIN码解析,vin码17个字节长度
   - 加密方式解析,读取加密方式,测试的时候可以先不使用,上生产环境后要打开
   - 数据单元长度解析,表示数据payload的总长度
   - 数据单元提取
   - 校验和验证
2. 数据完整性检查:
   - 最小帧长度检查
   - 完整数据长度检查
   - 校验和验证
3. 添加了单元测试:
   - 测试有效帧的解码
   - 测试校验和错误的情况

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2309527.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

conda安装及超详细避坑实战

1. Anaconda介绍。 Anaconda是一站式数据科学与机器学习平台&#xff0c;专为开发者、数据分析师设计,并带有python中超过180个科学包及其依赖项。通过 Anaconda&#xff0c;您可以轻松管理数据环境、安装依赖包&#xff0c;快速启动数据分析、机器学习项目。 Anaconda集成了…

LM studio 加载ollama的模型

1.LM 下载&#xff1a; https://lmstudio.ai/ 2.ollama下载&#xff1a; https://ollama.com/download 3.打开ollama&#xff0c;下载deepseek-r1。 本机设备资源有限&#xff0c;选择7B的&#xff0c;执行ollama run deepseek-r1:7b 4.windows chocolatey下载&#xff1a; P…

深入探索像ChatGPT这样的大语言模型-02-POST training supervised finetuning

参考 【必看珍藏】2月6日&#xff0c;安德烈卡帕西最新AI普及课&#xff1a;深入探索像ChatGPT这样的大语言模型&#xff5c;Andrej Karpathy fineweb知乎翻译介绍 fineweb-v1原始连接 fineweb中文翻译版本 Chinese Fineweb Edu数据集 查看网络的内部结果&#xff0c;可以参…

Kaldi环境配置与Aishell训练

一、项目来源 代码来源&#xff1a;kaldi-asr/kaldi: kaldi-asr/kaldi is the official location of the Kaldi project. (github.com) 官网文档&#xff1a;Kaldi: The build process (how Kaldi is compiled) (kaldi-asr.org) 踩着我的同门李思成-CSDN博客填上的坑kaldi环境…

数据集/API 笔记:新加坡PSI(空气污染指数)API

data.gov.sg 数据范围&#xff1a;2016年2月 - 2025年3月 1 获取API方式 curl --request GET \--url https://api-open.data.gov.sg/v2/real-time/api/psi 2 返回数据 API 的数据结构可以分为 3 大部分&#xff1a; 区域元数据&#xff08;regionMetadata&#xff09; →…

【GPU使用】如何在物理机和Docker中指定GPU进行推理和训练

我的机器上有4张H100卡&#xff0c;我现在只想用某一张卡跑程序&#xff0c;该如何设置。 代码里面设置 import os # 记住要写在impot torch前 os.environ[CUDA_VISIBLE_DEVICES] "0, 1"命令行设置 export CUDA_VISIBLE_DEVICES0,2 # Linux 环境 python test.py …

【Java项目】基于SpringBoot的CSGO赛事管理系统

【Java项目】基于SpringBoot的CSGO赛事管理系统 技术简介&#xff1a;采用SpringBoot框架、Java语言、MySQL数据库等技术实现。 系统简介&#xff1a;CSGO赛事管理系统是一个基于B/S架构的管理系统&#xff0c;主要功能包括前台和后台管理模块。前台系统功能模块分为&#xf…

MIPI接口:(4)MIPI CSI-2协议详解(上)

1. 什么是CSI&#xff1f; CSI&#xff08;Camera Serial Interface&#xff09;是MIPI联盟早期制定的摄像头接口标准&#xff0c;主要用于连接摄像头和处理器。 CSI-2是CSI的第二代版本&#xff0c;在原有基础上进行了全面优化&#xff1a; &#xff08;1&#xff09;分层架…

防火墙旁挂组网双机热备负载均衡

一&#xff0c;二层交换网络&#xff1a; 使用MSTPVRRP组网形式 VLAN 2--->SW3为主,SW4 作为备份 VLAN 3--->SW4为主,SW3 作为备份 MSTP 设计 --->SW3 、 4 、 5 运行 实例 1 &#xff1a; VLAN 2 实例 2 &#xff1a; VLAN 3 SW3 是实例 1 的主根&#xff0c;实…

视频教育网站开源系统的部署安装 (roncoo-education)服务器为ubuntu22.04.05

一、说明 前端技术体系&#xff1a;Vue3 Nuxt3 Vite5 Vue-Router Element-Plus Pinia Axios 后端技术体系&#xff1a;Spring Cloud Alibaba2021 MySQL8 Nacos Seata Mybatis Druid redis 后端系统&#xff1a;roncoo-education&#xff08;核心框架&#xff1a;S…

CF 886A.ACM ICPC(Java实现)

题目分析 输入6个值&#xff0c;判断某三个值的和能够等于另外三个值的和 思路分析 首先判断总和是不是一个偶数&#xff0c;如果不是就“NO”。由于小何同学算法不好&#xff0c;只能使用三层for循环强行判断某三个值是否能等于总和的一半&#xff0c;可以就“YES”。 代码 …

Spring Boot 自动装配深度解析与实践指南

目录 引言&#xff1a;自动装配如何重塑Java应用开发&#xff1f; 一、自动装配核心机制 1.1 自动装配三大要素 1.2 自动装配流程 二、自定义自动配置实现 2.1 创建自动配置类 2.2 配置属性绑定 2.3 注册自动配置 三、条件注解深度应用 3.1 常用条件注解对比 3.2 自定…

【windows driver】 开发环境简明安装教程

一、下载路径 https://learn.microsoft.com/en-us/windows-hardware/drivers/other-wdk-downloads 二、安装步骤&#xff1a; 1、安装Visual Studio IDE 笔者建议安装最新版本&#xff0c;可以向下兼容。发文截止到目前&#xff0c;VS2022是首选&#xff0c;当前笔者由于项…

探秘基带算法:从原理到5G时代的通信变革【八】QAM 调制 / 解调

文章目录 2.7 QAM 调制 / 解调2.7.1 概述2.7.2 星座图星座图的结构与性能发射端的信息编码与接收端的解码差分编码的分类与实现差分编码的模4格雷加法器公式16QAM星座图与映射关系 2.7.3 信号表达式正交振幅调制的基本原理与系统分析相位误差对QAM性能的影响多电平正交振幅调制…

Flink性能指标详解MetricsAnalysis

文章目录 Flink 组成1.JobManager2.TaskManager3.ResourceManager4.Dispatcher5.Client6. Env JobManager MetricsTaskManager Metrics Flink 组成 1.JobManager 管理任务 作业调度&#xff1a;负责接收和调度作业&#xff0c;分配任务到 TaskManager。资源管理&#xff1a;…

Halcon 车牌识别-超精细教程

车牌示例 流程: 读取图片转灰度图阈值分割,找车牌内容将车牌位置设置变换区域形状找到中心点和弧度利用仿射变换,斜切车牌旋转转正,把车牌抠出来利用形态学操作拼接车牌号数字训练ocr开始识别中文车牌 本文章用到的算子(解析) Halcon 算子-承接车牌识别-CSDN博客 rgb1_to_gray…

Redis实战篇《黑马点评》8 附近商铺

8.附近商户 8.1GEO数据结构的基本用法 GEO就是Geolocation的简写形式&#xff0c;代表地理坐标。Redis在3.2版本中加入了对GEO的支持&#xff0c;允许存储地理坐标信息&#xff0c;帮助我们根据经纬度来检索数据&#xff0c;常见的命令有 GEOADD&#xff1a;添加一个地理空间…

【02】Cocos游戏开发引擎从0开发一款游戏-cocos项目目录结构熟悉-调试运行项目-最重要的assets资源文件认识-场景sense了解-优雅草卓伊凡

【02】Cocos游戏开发引擎从0开发一款游戏-cocos项目目录结构熟悉-调试运行项目-最重要的assets资源文件认识-场景sense了解-优雅草卓伊凡 开发背景 接下来我们直接打开我们的项目开始进一步操作&#xff0c; 实战开发 导入项目 我把得到的项目解压到本地&#xff0c;我们开…

kafka-web管理工具cmak

一. 背景&#xff1a; 日常运维工作中&#xff0c;采用cli的方式进行kafka集群的管理&#xff0c;还是比较繁琐的(指令复杂&#xff1f;)。为方便管理&#xff0c;可以选择一些开源的webui工具。 推荐使用cmak。 二. 关于cmak&#xff1a; cmak是 Yahoo 贡献的一款强大的 Apac…

Unity中动态切换光照贴图LightProbe的方法

关键代码&#xff1a;LightmapSettings.lightmaps lightmapDatas; LightmapData中操作三张图&#xff1a;lightmapColor,lightmapDir,以及一张ShadowMap 这里只操作前两张&#xff1a; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI;public cl…