基于 Rust 与 GBT32960 规范的编解码层
根据架构设计,实现编解码层的代码设计
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, // 起始符 0x23pub 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(), // vinpayload);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. 添加了单元测试:
- 测试有效帧的解码
- 测试校验和错误的情况
相关文章:
基于 Rust 与 GBT32960 规范的编解码层
根据架构设计,实现编解码层的代码设计 Cargo.toml 加入二进制序列化支持 # 序列化支持 ... bincode "1.3" # 添加二进制序列化支持 bytes-utils "0.1" # 添加字节处理工具 开始编码 错误处理(error.rs&#x…...
conda安装及超详细避坑实战
1. Anaconda介绍。 Anaconda是一站式数据科学与机器学习平台,专为开发者、数据分析师设计,并带有python中超过180个科学包及其依赖项。通过 Anaconda,您可以轻松管理数据环境、安装依赖包,快速启动数据分析、机器学习项目。 Anaconda集成了…...
LM studio 加载ollama的模型
1.LM 下载: https://lmstudio.ai/ 2.ollama下载: https://ollama.com/download 3.打开ollama,下载deepseek-r1。 本机设备资源有限,选择7B的,执行ollama run deepseek-r1:7b 4.windows chocolatey下载: P…...
【图论】判断图中有环的两种方法及实现
判断图中有环的两种方法及实现 在图论中,检测有向图是否存在环是常见问题。本文将介绍两种主流方法:DFS三色标记法和拓扑排序(Kahn算法),并提供对应的C代码实现。 方法一:DFS三色标记法 核心思想 通过深…...
深入探索像ChatGPT这样的大语言模型-02-POST training supervised finetuning
参考 【必看珍藏】2月6日,安德烈卡帕西最新AI普及课:深入探索像ChatGPT这样的大语言模型|Andrej Karpathy fineweb知乎翻译介绍 fineweb-v1原始连接 fineweb中文翻译版本 Chinese Fineweb Edu数据集 查看网络的内部结果,可以参…...
Kaldi环境配置与Aishell训练
一、项目来源 代码来源:kaldi-asr/kaldi: kaldi-asr/kaldi is the official location of the Kaldi project. (github.com) 官网文档:Kaldi: The build process (how Kaldi is compiled) (kaldi-asr.org) 踩着我的同门李思成-CSDN博客填上的坑kaldi环境…...
数据集/API 笔记:新加坡PSI(空气污染指数)API
data.gov.sg 数据范围:2016年2月 - 2025年3月 1 获取API方式 curl --request GET \--url https://api-open.data.gov.sg/v2/real-time/api/psi 2 返回数据 API 的数据结构可以分为 3 大部分: 区域元数据(regionMetadata) →…...
【GPU使用】如何在物理机和Docker中指定GPU进行推理和训练
我的机器上有4张H100卡,我现在只想用某一张卡跑程序,该如何设置。 代码里面设置 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赛事管理系统 技术简介:采用SpringBoot框架、Java语言、MySQL数据库等技术实现。 系统简介:CSGO赛事管理系统是一个基于B/S架构的管理系统,主要功能包括前台和后台管理模块。前台系统功能模块分为…...
MIPI接口:(4)MIPI CSI-2协议详解(上)
1. 什么是CSI? CSI(Camera Serial Interface)是MIPI联盟早期制定的摄像头接口标准,主要用于连接摄像头和处理器。 CSI-2是CSI的第二代版本,在原有基础上进行了全面优化: (1)分层架…...
防火墙旁挂组网双机热备负载均衡
一,二层交换网络: 使用MSTPVRRP组网形式 VLAN 2--->SW3为主,SW4 作为备份 VLAN 3--->SW4为主,SW3 作为备份 MSTP 设计 --->SW3 、 4 、 5 运行 实例 1 : VLAN 2 实例 2 : VLAN 3 SW3 是实例 1 的主根,实…...
JMeter 实战项目脚本录制最佳实践(含 BadBoy 录制方式)
JMeter 实战项目脚本录制最佳实践(含 BadBoy 录制方式) 一、项目背景 在软件测试过程中,使用 JMeter 进行性能测试和功能测试是常见的操作。本实战项目将详细介绍如何使用 JMeter 自带工具以及 BadBoy 进行脚本录制,并完善脚本以…...
硅基流动nodejs流式输出
使用JavaScript的api直接在前端问答速度虽然快但是有token直接暴露的风险。 现在使用nodejs也可以快速进行流式输出并且可以隐藏用户敏感信息。 const express require(express); const axios require(axios); const app express(); const port 3000;//启动服务node index…...
mysql深度分页优化方案
mysql深度分页优化方案 在MySQL中,深度分页(即查询大量数据中的靠后部分)通常会导致性能问题,尤其是在使用 LIMIT offset, count 时。随着 offset 的增大,MySQL需要扫描更多的行,导致查询变慢。以下是一些优…...
视频教育网站开源系统的部署安装 (roncoo-education)服务器为ubuntu22.04.05
一、说明 前端技术体系:Vue3 Nuxt3 Vite5 Vue-Router Element-Plus Pinia Axios 后端技术体系:Spring Cloud Alibaba2021 MySQL8 Nacos Seata Mybatis Druid redis 后端系统:roncoo-education(核心框架:S…...
中间件专栏之MySQL篇——MySQL缓存策略
本文所说的MySQL缓存策略与前文提到的buffer pool不同,那是MySQL内部自己实现的,本问所讲的缓存策略是使用另一个中间件redis来缓存MySQL中的热点数据。 一、为什么需要MySQL缓存方案 缓存用户定义的热点数据,用户可以直接从缓存中获取热点…...
CSS 日常开发常用属性总结
文章目录 CSS 日常开发常用属性总结一、 常用 CSS 属性1、布局相关(1)display:(2)position:(3)float:(4)clear: 2、尺寸与溢出&#x…...
CF 886A.ACM ICPC(Java实现)
题目分析 输入6个值,判断某三个值的和能够等于另外三个值的和 思路分析 首先判断总和是不是一个偶数,如果不是就“NO”。由于小何同学算法不好,只能使用三层for循环强行判断某三个值是否能等于总和的一半,可以就“YES”。 代码 …...
Spring Boot 自动装配深度解析与实践指南
目录 引言:自动装配如何重塑Java应用开发? 一、自动装配核心机制 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 二、安装步骤: 1、安装Visual Studio IDE 笔者建议安装最新版本,可以向下兼容。发文截止到目前,VS2022是首选,当前笔者由于项…...
探秘基带算法:从原理到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 管理任务 作业调度:负责接收和调度作业,分配任务到 TaskManager。资源管理:…...
Git强制覆盖分支:将任意分支完全恢复为main分支内容
Git强制覆盖分支:将任意分支完全恢复为main分支内容 场景背景完整操作步骤一、前置准备二、操作流程步骤 1:更新本地 main 分支步骤 2:强制重置目标分支步骤 3:强制推送至远程仓库 三、操作示意图 关键风险提示(必读&a…...
WPF 如何使文本显示控件支持显示内容滚动显示
WPF中如何使文本显示控件支持显示内容滚动显示 在WPF中,TextBlock 控件本身并不直接支持滚动功能,因为它的设计初衷是用于静态文本展示。但是,你可以通过一些技巧和自定义控件来实现 TextBlock 的滚动效果。以下是几种常见的方法:…...
Halcon 车牌识别-超精细教程
车牌示例 流程: 读取图片转灰度图阈值分割,找车牌内容将车牌位置设置变换区域形状找到中心点和弧度利用仿射变换,斜切车牌旋转转正,把车牌抠出来利用形态学操作拼接车牌号数字训练ocr开始识别中文车牌 本文章用到的算子(解析) Halcon 算子-承接车牌识别-CSDN博客 rgb1_to_gray…...
HTTP/1.1 和 HTTP/2 的区别,HTTP/2 有哪些新特性?
HTTP/1.1 和 HTTP/2 的区别及新特性详解 一、核心区别:连接管理与多路复用 HTTP/1.1 使用「短连接」或「持久连接」,但每个 TCP 连接在同一时刻只能处理一个请求(HOL Blocking)。浏览器通常通过开启多个 TCP 连接(…...
Redis实战篇《黑马点评》8 附近商铺
8.附近商户 8.1GEO数据结构的基本用法 GEO就是Geolocation的简写形式,代表地理坐标。Redis在3.2版本中加入了对GEO的支持,允许存储地理坐标信息,帮助我们根据经纬度来检索数据,常见的命令有 GEOADD:添加一个地理空间…...
【02】Cocos游戏开发引擎从0开发一款游戏-cocos项目目录结构熟悉-调试运行项目-最重要的assets资源文件认识-场景sense了解-优雅草卓伊凡
【02】Cocos游戏开发引擎从0开发一款游戏-cocos项目目录结构熟悉-调试运行项目-最重要的assets资源文件认识-场景sense了解-优雅草卓伊凡 开发背景 接下来我们直接打开我们的项目开始进一步操作, 实战开发 导入项目 我把得到的项目解压到本地,我们开…...
通过ollama本地化部署deepseek后,通过API方式请求特别的慢
通过ollama本地化部署deepseek后,通过API方式请求特别的慢 一、现象二、原因分析 一、现象 deepseek火了之后,本地私有化部署大模型的门槛大大降低,即使是在家里的windows电脑,也非常简单就可以安装大模型并且使用,最…...
CSS3中布局方式说明
CSS3 提供了多种灵活的布局方式,适用于不同的场景和需求。以下是主要的布局方式及其特点: 1. Flexbox 布局(弹性盒子) 用途:一维布局(水平或垂直方向排列元素)。特点: 通过 display…...
