Initial commit: CCU621_M firmware project with BLE debug link support.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-08 17:28:36 +08:00
commit 9ceb218f80
1597 changed files with 724159 additions and 0 deletions
+730
View File
@@ -0,0 +1,730 @@
# TCP调试服务器通信协议说明
## 协议概述
本协议用于CCU601E充电桩TCP调试服务器与客户端之间的通信。协议基于JSON格式,包含心跳、系统信息、充电枪数据、充电控制、参数查询与修改等功能。
## 基本协议格式
所有消息遵循以下基本格式:
```json
{
"command": "命令类型",
"data": { ... },
"timestamp": "YYYY-MM-DD HH:MM:SS.000",
"version": "2.0"
}
```
字段说明:
- `command`: 命令类型(字符串)
- `data`: 命令数据(对象)
- `timestamp`: 时间戳,格式为"YYYY-MM-DD HH:MM:SS.000"
- `version`: 协议版本,固定为"2.0"
## 命令类型列表
| 命令 | 值 | 说明 |
|------|-----|------|
| 心跳 | Heartbeat | 心跳消息,用于保持连接 |
| 系统信息 | SystemInfo | 系统信息查询/上报 |
| 枪信息 | GunInfo | 充电枪数据查询/上报 |
| 充电控制 | ChargeControl | 充电启停控制 |
| 参数查询 | ParamQuery | 参数查询 |
| 参数修改 | ParamModify | 参数修改 |
| 文件操作 | FileOperations | 文件系统操作(查询、删除、下载、上传) |
## 1. 心跳消息 (Heartbeat)
### 1.1 服务器发送心跳
```json
{
"command": "Heartbeat",
"data": {
"sequence": 123,
"source": "device"
},
"timestamp": "2025-12-27 14:48:27.000",
"version": "2.0"
}
```
字段说明:
- `sequence`: 序列号,每次递增
- `source`: 来源,固定为"device"
### 1.2 客户端回复心跳
```json
{
"command": "Heartbeat",
"data": {
"sequence": 123,
"source": "client"
},
"timestamp": "2025-12-27 14:48:27.100",
"version": "2.0"
}
```
## 2. 系统信息 (SystemInfo)
### 2.1 服务器发送系统信息
```json
{
"command": "SystemInfo",
"data": {
"systemModel": "IES-2000",
"gunCount": 2,
"devicePower": 120.0,
"networkStatus": 1,
"faultCount": 0,
"version": "V1.2.3"
},
"timestamp": "2025-12-27 14:48:28.000",
"version": "2.0"
}
```
字段说明:
- `systemModel`: 系统型号
- `gunCount`: 枪数量
- `devicePower`: 设备功率(kW)
- `networkStatus`: 网络状态(0:异常, 1:正常)
- `faultCount`: 故障数量
- `version`: 软件版本
## 3. 充电枪信息 (GunInfo)
### 3.1 服务器发送枪信息
```json
{
"command": "GunInfo",
"data": {
"gunNumber": 1,
"chargingStatus": 2,
"gunConnectionStatus": 1,
"chargingTime": 125,
"demandVoltage": 400.0,
"demandCurrent": 32.0,
"actualVoltage": 398.5,
"actualCurrent": 30.2,
"energy": 15.7,
"cost": 31.4,
"batterySOC": 65.0,
"userId": "user001",
"orderNumber": "ORD20231227001"
},
"timestamp": "2025-12-27 14:48:29.000",
"version": "2.0"
}
```
字段说明:
- `gunNumber`: 枪编号(1-2)
- `chargingStatus`: 充电状态(0:空闲, 1:已连接, 2:充电中, 3:充电完成, 4:故障)
- `gunConnectionStatus`: 枪连接状态(0:未连接, 1:已连接)
- `chargingTime`: 充电时间(分钟)
- `demandVoltage`: 需求电压(V)
- `demandCurrent`: 需求电流(A)
- `actualVoltage`: 实际电压(V)
- `actualCurrent`: 实际电流(A)
- `energy`: 充电能量(kWh)
- `cost`: 充电费用(元)
- `batterySOC`: 电池SOC(%)
- `userId`: 用户ID
- `orderNumber`: 订单号
## 4. 充电控制 (ChargeControl)
### 4.1 客户端发送充电控制
```json
{
"command": "ChargeControl",
"data": {
"gunNumber": 1,
"action": 1
},
"timestamp": "2025-12-27 14:48:30.000",
"version": "2.0"
}
```
字段说明:
- `gunNumber`: 枪编号(1-2)
- `action`: 动作(0:停止充电, 1:启动充电)
### 4.2 服务器响应充电控制
```json
{
"command": "ChargeControl",
"data": {
"gunNumber": 1,
"action": 1,
"result": 0,
"message": "充电启动成功"
},
"timestamp": "2025-12-27 14:48:30.500",
"version": "2.0"
}
```
字段说明:
- `result`: 结果(0:成功, 1:失败)
- `message`: 结果消息
## 5. 参数查询与修改
### 5.1 参数分类(type字段)
| 类型 | 值 | 说明 |
|------|-----|------|
| 系统基本参数 | BasicParameters | 桩编号、功率、电压电流等 |
| 网络参数 | NetworkParameters | IP、域名、平台配置等 |
| 电表参数 | MeterParameters | 电表数量、归属、地址等 |
| 使能参数 | EnableParameters | 门禁、枪锁、国标等使能标志 |
| 费率参数 | FeeParameters | 计费模型、费率类型等 |
| SDK参数 | SDKParameters | 产品秘钥、设备密钥等 |
| 二维码参数 | QRCodeParameters | 各枪二维码内容 |
### 5.2 参数查询请求
```json
{
"command": "ParamQuery",
"data": {
"type": "BasicParameters"
},
"timestamp": "2025-12-27 14:48:31.000",
"version": "2.0"
}
```
### 5.3 参数查询响应
```json
{
"command": "ParamQuery",
"data": {
"type": "BasicParameters",
"count": 18,
"params": [
{
"name": "pile_serial_num",
"type": "string",
"value": "CCU601E-001"
},
{
"name": "u8_gunCnt",
"type": "uint8",
"value": 2
}
]
},
"timestamp": "2025-12-27 14:48:31.500",
"version": "2.0"
}
```
字段说明:
- `count`: 参数总数
- `params`: 参数数组
- `name`: 参数名称
- `type`: 参数类型
- `value`: 参数值
### 5.4 参数修改请求
```json
{
"command": "ParamModify",
"data": {
"type": "BasicParameters",
"params": [
{
"name": "pile_serial_num",
"type": "string",
"value": "CCU601E-002"
},
{
"name": "u8_threshold_gunTemp",
"type": "int",
"value": 85
}
]
},
"timestamp": "2025-12-27 14:48:32.000",
"version": "2.0"
}
```
### 5.5 参数修改响应
```json
{
"command": "ParamModify",
"data": {
"type": "BasicParameters",
"received": 2,
"processed": 2,
"success": 2,
"failed": 0
},
"timestamp": "2025-12-27 14:48:32.500",
"version": "2.0"
}
```
字段说明:
- `received`: 接收到的参数数量
- `processed`: 已处理的参数数量
- `success`: 成功的参数数量
- `failed`: 失败的参数数量
## 6. 参数详细列表
### 6.1 BasicParameters(系统基本参数)
| 参数名 | 类型 | 说明 | 示例值 |
|--------|------|------|--------|
| pile_serial_num | string | 桩编号 | "CCU601E-001" |
| u8_pwd | string | 维护密码 | "123456" |
| u8_gunCnt | uint8 | 枪数量 | 2 |
| u8_pile_type | uint8 | 桩类型 | 1 |
| u8_terminal | uint8_array | 终端地址 | [0, 0] |
| e_ledType | uint8 | 灯板类型 | 0 |
| u8_threshold_gunTemp | uint8 | 枪温阈值(°C) | 90 |
| u8_soc_limit | uint8 | SOC阈值(%) | 100 |
| u16_ratePow | uint32 | 额定功率(W) | 120000 |
| u16_max_volt | uint16 | 最大电压(0.1V) | 10000 |
| u16_min_volt | uint16 | 最小电压(0.1V) | 2000 |
| u16_trickle_limit | uint16 | 涓流充电停机阈值(0.1A) | 100 |
| u16_max_cur | uint16 | 最大电流(0.1A) | 2500 |
| u8_mdu_num | uint8 | 充电模块数量 | 4 |
| u8_MduType | uint8 | 模块类型 | 1 |
| u8_mduTemp_flag | uint8 | 模块温度获取使能 | 1 |
| u8_mdu_rated_pow | uint8 | 模块额定功率(kW) | 30 |
| u16_mdu_max_vol | uint16 | 模块最大电压(0.1V) | 10000 |
| u16_mdu_max_cur | uint16 | 模块最大电流(0.1A) | 3000 |
### 6.2 NetworkParameters(网络参数)
| 参数名 | 类型 | 说明 | 示例值 |
|--------|------|------|--------|
| ethIp | array | IP地址(4字节数组) | [192,168,1,140] |
| ethGate | array | 网关(4字节数组) | [192,168,1,254] |
| ethMask | array | 子网掩码(4字节数组) | [255,255,255,0] |
| ethMac | array | MAC地址(6字节数组) | [0,14,234,3,165,1] |
| serverADomain | string | 服务器A域名 | "39.104.96.26,8897" |
| serverBDomain | string | 服务器B域名 | "" |
| platAPileNum | string | 平台1桩编号 | "" |
| platBPileNum | string | 平台2桩编号 | "" |
| programType | bit | 程序类型(2位) 0:社会,1:sdk,2:ocpp | 0b00 |
| netAType | bit | 平台1联网方式(2位) 0:以太网,1:GPRS | 0b00 |
| netBType | bit | 平台2联网方式(2位) 0:以太网,1:GPRS | 0b00 |
| platAEn | bit | 平台1使能(2位) 0:不使能,1:使能 | 0b01 |
| platBEn | bit | 平台2使能(2位) 0:不使能,1:使能 | 0b01 |
| platAType | bit | 平台1类型(4位) 0:云快充,1:... | 0b0000 |
| platBType | bit | 平台2类型(4位) 默认积成平台 | 0b0000 |
| simAnpType | bit | SIM卡APN类型(2位) 0:公网卡,1:国网移动,2:国网联通,3:国网电信 | 0b00 |
### 6.3 MeterParameters(电表参数)
| 参数名 | 类型 | 说明 | 示例值 |
|--------|------|------|--------|
| u8_meter_num | uint8 | 电表数量(0-4) | 2 |
| meter_0_inGun | uint8 | 电表0归属枪号(0:无效,1:A枪,2:B枪) | 1 |
| meter_0_highPrec | uint8 | 电表0高精度支持(0:不支持,1:支持) | 1 |
| meter_0_isAC | uint8 | 电表0是否为交流表(0:直流表,1:交流表) | 0 |
| meter_0_addr | hex_array | 电表0地址(6字节) | "00 00 00 00 00 00" |
| meter_1_inGun | uint8 | 电表1归属枪号 | 2 |
| meter_1_highPrec | uint8 | 电表1高精度支持 | 1 |
| meter_1_isAC | uint8 | 电表1是否为交流表 | 0 |
| meter_1_addr | hex_array | 电表1地址 | "00 00 00 00 00 00" |
| meter_2_inGun | uint8 | 电表2归属枪号 | 1 |
| meter_2_highPrec | uint8 | 电表2高精度支持 | 0 |
| meter_2_isAC | uint8 | 电表2是否为交流表 | 1 |
| meter_2_addr | hex_array | 电表2地址 | "00 00 00 00 00 00" |
| meter_3_inGun | uint8 | 电表3归属枪号 | 2 |
| meter_3_highPrec | uint8 | 电表3高精度支持 | 0 |
| meter_3_isAC | uint8 | 电表3是否为交流表 | 1 |
| meter_3_addr | hex_array | 电表3地址 | "00 00 00 00 00 00" |
### 6.4 EnableParameters(使能参数)
| 参数名 | 类型 | 说明 | 示例值 |
|--------|------|------|--------|
| bit_doorEnable | uint8 | 门禁使能 | 0 |
| bit_gunLockEna | uint8 | 枪锁使能 | 1 |
| bit_gunLockNeg | uint8 | 枪锁信号取反 | 0 |
| bit_AcContactEna | uint8 | 交流接触器使能 | 1 |
| bit_gunHoming | uint8 | 枪归位信号有无 | 1 |
| bit_GBEnable | uint8 | 国标使能 | 1 |
| bit_LcdEnable | uint8 | 液晶使能 | 1 |
| bit_auxpower | uint8 | 辅助电压返回状态使能 | 0 |
| bit_reliefen | uint8 | 泄放使能 | 1 |
| bit_YeLenEn | uint8 | 液冷通讯使能 | 0 |
| bit_ABcommEn | uint8 | 主副班通讯使能 | 0 |
| bit_ScreenSaverEn | uint8 | UI屏保使能 | 1 |
| bit_PlugEn | uint8 | 即插即充使能 | 0 |
| bit_uiEnglishEn | uint8 | UI界面英语使能 | 0 |
### 6.5 FeeParameters(费率参数)
| 参数名 | 类型 | 说明 | 示例值 |
|--------|------|------|--------|
| u8_model_no | string | 计费模型编号(17字节) | "FEE20231227001" |
| u8_feeCnt | uint8 | 费率条数(0-30) | 5 |
| fee_type_0_rate | uint32 | 费率类型0费率(小数点后5位) | 15000 |
| fee_type_0_sevice | uint32 | 费率类型0服务费(小数点后5位) | 500 |
| fee_type_1_rate | uint32 | 费率类型1费率 | 18000 |
| fee_type_1_sevice | uint32 | 费率类型1服务费 | 600 |
| fee_type_2_rate | uint32 | 费率类型2费率 | 20000 |
| fee_type_2_sevice | uint32 | 费率类型2服务费 | 700 |
| ... | ... | ... | ... |
| fee_type_29_rate | uint32 | 费率类型29费率 | 0 |
| fee_type_29_sevice | uint32 | 费率类型29服务费 | 0 |
| u8_fee_type_0 | uint8 | 时段0费率类型(1-30) | 1 |
| u8_fee_type_1 | uint8 | 时段1费率类型 | 1 |
| u8_fee_type_2 | uint8 | 时段2费率类型 | 1 |
| ... | ... | ... | ... |
| u8_fee_type_95 | uint8 | 时段95费率类型 | 3 |
### 6.6 SDKParametersSDK参数)
| 参数名 | 类型 | 说明 | 示例值 |
|--------|------|------|--------|
| product_key | string | 产品秘钥(20字节) | "" |
| device_name | string | 资产码(32字节) | "" |
| device_secret | string | 设备密钥(64字节) | "" |
| device_reg_code | string | 注册码(64字节) | "" |
| device_uid | string | 出厂编码(64字节) | "" |
| firmware_version | string | 固件版本信息(64字节) | "V1.0.0" |
### 6.7 QRCodeParameters(二维码参数)
| 参数名 | 类型 | 说明 | 示例值 |
|--------|------|------|--------|
| qrcode_gun1 | string | 枪1二维码 | "https://example.com/qr/001" |
| qrcode_gun2 | string | 枪2二维码 | "https://example.com/qr/002" |
## 7. 文件操作 (FileOperations)
文件操作用于上位机对单片机文件系统进行操作,包含查询、删除、下载、上传四种操作。
### 7.1 文件操作子命令
| 子命令 | 值 | 说明 |
|--------|-----|------|
| 文件查询 | Query | 查询指定目录下的文件列表 |
| 文件删除 | Delete | 删除指定文件 |
| 文件下载 | Download | 下载文件内容 |
| 文件上传 | Upload | 上传文件到设备 |
### 7.2 文件查询请求
```json
{
"command": "FileOperations",
"data": {
"subCommand": "Query",
"path": "/",
"recursive": 0
},
"timestamp": "2025-12-29 14:00:00.000",
"version": "2.0"
}
```
字段说明:
- `subCommand`: 子命令,固定为"Query"
- `path`: 查询路径,如"/"表示根目录
- `recursive`: 是否递归查询(0:否, 1:是)
### 7.3 文件查询响应
```json
{
"command": "FileOperations",
"data": {
"subCommand": "Query",
"path": "/",
"fileCount": 3,
"dirCount": 2,
"totalSize": 1050624,
"files": [
{
"name": "config.txt",
"type": 0,
"size": 1024,
"modifiedTime": "2025-12-29 10:00:00",
"createdTime": "2025-12-29 10:00:00"
},
{
"name": "log.txt",
"type": 0,
"size": 2048,
"modifiedTime": "2025-12-29 11:30:00",
"createdTime": "2025-12-29 11:30:00"
},
{
"name": "firmware.bin",
"type": 0,
"size": 1048576,
"modifiedTime": "2025-12-29 12:00:00",
"createdTime": "2025-12-29 12:00:00"
}
]
},
"timestamp": "2025-12-29 14:00:00.500",
"version": "2.0"
}
```
字段说明:
- `fileCount`: 文件数量
- `dirCount`: 目录数量
- `totalSize`: 总文件大小(字节)
- `files`: 文件信息数组
- `name`: 文件名
- `type`: 文件类型(0:普通文件, 1:目录, 2:系统文件)
- `size`: 文件大小(字节)
- `modifiedTime`: 修改时间
- `createdTime`: 创建时间
### 7.4 文件删除请求
文件删除请求支持两种格式:
#### 格式1:直接指定完整路径
```json
{
"command": "FileOperations",
"data": {
"subCommand": "Delete",
"path": "/config.txt",
"force": 0
},
"timestamp": "2025-12-29 14:01:00.000",
"version": "2.0"
}
```
#### 格式2:指定路径和文件名(推荐格式)
```json
{
"command": "FileOperations",
"data": {
"subCommand": "Delete",
"fileName": "20260104_143116.log",
"path": "/log",
"force": 0
},
"timestamp": "2026-01-04 15:06:04.630",
"version": "2.0"
}
```
字段说明:
- `subCommand`: 子命令,固定为"Delete"
- `path`: 文件路径(格式1)或目录路径(格式2)
- `fileName`: 文件名(仅格式2需要)
- `force`: 强制删除(0:否, 1:是),可选字段,默认为0
**注意**
1. 格式1中,`path`字段包含完整的文件路径(如`/config.txt`
2. 格式2中,`path`字段为目录路径,`fileName`为文件名,系统会自动组合为完整路径:`path + "/" + fileName`
3. 如果同时提供`path``fileName`字段,优先使用格式2(组合路径)
4. `force`字段为可选字段,默认为0(不强制删除)。当设置为1时,允许删除系统文件
### 7.5 文件删除响应
```json
{
"command": "FileOperations",
"data": {
"subCommand": "Delete",
"path": "/config.txt",
"status": 0,
"message": "File deleted successfully"
},
"timestamp": "2025-12-29 14:01:00.500",
"version": "2.0"
}
```
字段说明:
- `status`: 删除状态(0:成功, 1:失败, 3:文件未找到, 5:权限不足)
- `message`: 结果消息
### 7.6 文件下载请求
```json
{
"command": "FileOperations",
"data": {
"subCommand": "Download",
"path": "/log.txt",
"offset": 0,
"chunkSize": 1024
},
"timestamp": "2025-12-29 14:02:00.000",
"version": "2.0"
}
```
字段说明:
- `subCommand`: 子命令,固定为"Download"
- `path`: 文件路径
- `offset`: 起始偏移(字节),用于断点续传。首次下载设置为0
- `chunkSize`: 分块大小(字节),最大1024字节,超过1024会自动限制为1024
**下载流程说明**
1. 客户端首次请求时,设置`offset=0``chunkSize`建议设置为1024(最大支持值)
2. 服务器返回文件的第一块数据,包含`fileSize`(文件总大小)和`isLastChunk`标志
3. 客户端根据返回的`dataLen`计算下一次请求的偏移:`offset = offset + dataLen`
4. 重复步骤1-3,直到`isLastChunk=1`表示下载完成
5. 如果下载中断,可以从上次的偏移位置继续下载(断点续传)
### 7.7 文件下载响应
```json
{
"command": "FileOperations",
"data": {
"subCommand": "Download",
"path": "/log.txt",
"fileSize": 2048,
"offset": 0,
"chunkSize": 1024,
"dataLen": 11,
"isLastChunk": 0,
"data": "SGVsbG8gV29ybGQ="
},
"timestamp": "2025-12-29 14:02:00.500",
"version": "2.0"
}
```
字段说明:
- `fileSize`: 文件总大小(字节)
- `offset`: 当前数据块的起始偏移(字节)
- `chunkSize`: 实际使用的分块大小(字节),可能与请求的chunkSize不同(受1024字节限制)
- `dataLen`: Base64编码后的数据长度(字节)
- `isLastChunk`: 是否为最后一块(0:否, 1:是)。当`offset + dataLen >= fileSize`时为1
- `data`: 数据内容(Base64编码)。实际二进制数据大小为`dataLen`字节,Base64编码后长度约为`dataLen * 4/3`
**技术细节**
1. **分块限制**:每块数据最大1024字节。如果请求的`chunkSize`超过1024,会自动限制为1024
2. **Base64编码**:所有文件数据都使用Base64编码传输,确保二进制数据的安全传输
3. **进度计算**:下载进度可通过公式计算:`进度 = (offset + dataLen) / fileSize * 100%`
4. **错误处理**
- 如果文件不存在,返回错误
- 如果`offset`超过文件大小,返回错误
- 如果读取文件失败,返回错误
5. **内存管理**:客户端需要及时释放接收到的数据内存,避免内存泄漏
### 7.8 文件上传请求
```json
{
"command": "FileOperations",
"data": {
"subCommand": "Upload",
"path": "/upload/newfile.txt",
"fileSize": 4096,
"chunkSize": 1024,
"offset": 0,
"data": "SGVsbG8gV29ybGQ=",
"dataLen": 11,
"isLastChunk": 0
},
"timestamp": "2025-12-29 14:03:00.000",
"version": "2.0"
}
```
字段说明:
- `subCommand`: 子命令,固定为"Upload"
- `path`: 文件路径
- `fileSize`: 文件总大小
- `chunkSize`: 分块大小
- `offset`: 当前偏移
- `data`: 数据内容(Base64编码)
- `dataLen`: 数据长度
- `isLastChunk`: 是否为最后一块(0:否, 1:是)
### 7.9 文件上传响应
```json
{
"command": "FileOperations",
"data": {
"subCommand": "Upload",
"path": "/upload/newfile.txt",
"receivedSize": 1024,
"totalSize": 4096,
"status": 2,
"message": "Upload in progress"
},
"timestamp": "2025-12-29 14:03:00.500",
"version": "2.0"
}
```
字段说明:
- `receivedSize`: 已接收大小
- `totalSize`: 文件总大小
- `status`: 上传状态(0:成功, 1:失败, 2:进行中, 4:空间不足)
- `message`: 结果消息
### 7.10 文件操作状态码
| 状态码 | 值 | 说明 |
|--------|-----|------|
| 成功 | 0 | 操作成功 |
| 失败 | 1 | 操作失败 |
| 进行中 | 2 | 操作进行中(适用于上传/下载) |
| 文件未找到 | 3 | 文件不存在 |
| 空间不足 | 4 | 磁盘空间不足 |
| 权限不足 | 5 | 权限不足 |
## 8. 数据类型说明
| 类型 | 说明 | 示例 |
|------|------|------|
| string | 字符串 | "CCU601E-001" |
| int | 整数 | 85 |
| uint8 | 无符号8位整数 | 2 |
| uint16 | 无符号16位整数 | 2500 |
| uint32 | 无符号32位整数 | 120000 |
| ip_address | IP地址字符串 | "192.168.1.140" |
| mac_address | MAC地址字符串 | "00:0E:EA:03:A5:01" |
| hex_array | 十六进制数组字符串 | "00 00 00 00 00 00" |
| uint8_array | 无符号8位整数数组 | [1,1,1,2,2,2,3,3,3] |
## 8. 通信流程
### 8.1 连接建立
1. 客户端连接TCP服务器(端口9699)
2. 服务器接受连接(仅允许一个客户端)
3. 开始周期性通信
### 8.2 周期性消息
- 心跳:每10秒发送一次
- 系统信息:每10秒发送一次
- 充电枪数据:每1秒发送一次(两把枪分别发送)
### 8.3 心跳超时
- 客户端超过60秒未发送心跳,服务器主动断开连接
### 8.4 命令交互
1. 客户端发送命令请求
2. 服务器处理命令并返回响应
3. 错误时返回相应的错误信息
## 9. 错误处理
### 9.1 通用错误格式
```json
{
"command": "原命令",
"data": {
"error": {
"code": 1001,
"message": "错误描述"
}
},
"timestamp": "2025-12-27 14:48:33.000",
"version": "2.0"
}
```
### 9.2 错误码定义
| 错误码 | 说明 |
|--------|------|
| 1000 | 参数解析错误 |
| 1001 | 命令不支持 |
| 1002 | 参数缺失 |
| 1003 | 参数值无效
File diff suppressed because it is too large Load Diff
+124
View File
@@ -0,0 +1,124 @@
/**
* @file fc41d_ble.h
* @brief FC41D 蓝牙调试:配置宏 + 对外接口(仅 BLE_DEBUG_EN=1 有效)
*
* 分层:
* - 本头文件:编译配置、DIP 选路、comm_link 后端 API
* - fc41d_ble.cAT 命令、模组电源、BLE 状态机、QBLETRANMODE 透传收发(内部 static
*/
#ifndef FC41D_BLE_H
#define FC41D_BLE_H
#include <stdint.h>
#include "publicdata/public_define.h"
#if BLE_DEBUG_EN
#include "usart.h"
/* ---------- 硬件 / 串口 ---------- */
#ifndef FC41D_USART_ID
#define FC41D_USART_ID E_USART_7 /* UART7 PE1/PE0,接 FC41D */
#endif
#ifndef FC41D_UART_HW_NUM
#define FC41D_UART_HW_NUM 7U /* UART7 PE1/PE0 */
#endif
#ifndef FC41D_RST_PULSE_MS
#define FC41D_RST_PULSE_MS 200U /* 复位脉冲高电平保持时间 */
#endif
#ifndef FC41D_POWER_ON_DELAY_MS
#define FC41D_POWER_ON_DELAY_MS 5000U /* 复位释放后等待模组启动 */
#endif
#ifndef FC41D_UART_BAUD
#define FC41D_UART_BAUD 115200U /* UART7 固定波特率,不再做双波特率探测 */
#endif
#ifndef FC41D_AT_CMD_WAIT_MS
#define FC41D_AT_CMD_WAIT_MS 1500U /* 常规 AT 阻塞等待 */
#endif
#ifndef FC41D_GATT_AT_WAIT_MS
#define FC41D_GATT_AT_WAIT_MS 1200U /* QBLEGATTSNTFY 阻塞等待 */
#endif
#ifndef FC41D_AT_INIT_WAIT_MS
#define FC41D_AT_INIT_WAIT_MS 8000U /* QBLEINIT 栈初始化较慢,单独加长 */
#endif
#ifndef FC41D_STAT_POLL_MS
#define FC41D_STAT_POLL_MS 10000U /* 等待连接时 QBLESTAT 兜底查询间隔 */
#endif
#ifndef FC41D_CONNECT_WAIT_MS
#define FC41D_CONNECT_WAIT_MS (120000U) /* 等待手机连接最长时间 */
#endif
/* ---------- BLE GATT / 广播名(Quectel 手册 §4.2.1 外设示例) ---------- */
#ifndef FC41D_BLE_NAME
#define FC41D_BLE_NAME "CCU621_Debug"
#endif
#ifndef FC41D_SRV_UUID
#define FC41D_SRV_UUID "fff1"
#endif
#ifndef FC41D_CHAR_WRITE_UUID
#define FC41D_CHAR_WRITE_UUID "fff2" /* 手机 → 设备 Write */
#endif
#ifndef FC41D_CHAR_NOTIFY_UUID
#define FC41D_CHAR_NOTIFY_UUID "fff3" /* 设备 → 手机 Notify */
#endif
/** QBLETRANMODE 优先尝试的 UUID(部分固件需 fff2 而非 fff3 */
#ifndef FC41D_TRAN_UUID
#define FC41D_TRAN_UUID FC41D_CHAR_WRITE_UUID
#endif
/** 1=连接后 AT+QBLETRANMODE 透传(手册);0=GATT 非透传 +QBLERECV */
#ifndef FC41D_BLE_USE_TRANSPARENT
#define FC41D_BLE_USE_TRANSPARENT 1
#endif
#ifndef BLE_DEBUG_PRINT
#define BLE_DEBUG_PRINT 1
#endif
#if BLE_DEBUG_PRINT
#include <stdio.h>
#define BT_PRINT(fmt, ...) printf("[BT] " fmt, ##__VA_ARGS__)
#else
#define BT_PRINT(fmt, ...) do {} while (0)
#endif
/* ---------- DIP1 拨码选路(副板 sub_comm 上报) ---------- */
/** @brief 等待副板 DI 帧就绪,便于读取 DIP1 */
int ble_link_wait_dip_ready(uint32_t timeout_ms);
/** @brief DIP1=1 时为蓝牙调试上位机模式 */
uint8_t ble_link_is_bt_mode(void);
/* ---------- comm_link 后端(与 tcp_server 共用 JSON 协议) ---------- */
int comm_link_ble_start(void);
void comm_link_ble_stop(void);
void comm_link_ble_process(void);
int comm_link_ble_is_client_connected(void);
void comm_link_ble_close_client(void);
int comm_link_ble_receive_data(uint8_t *buffer, uint32_t buffer_size);
int comm_link_ble_rx_upgrade_begin(void);
int comm_link_ble_rx_upgrade_end(void);
void comm_link_ble_rx_flush_pending(void);
int comm_link_ble_diag_trace(void);
int comm_link_ble_send_data(const uint8_t *data, uint32_t len);
int comm_link_ble_get_peer_info(char *buf, uint32_t buf_size);
#endif /* BLE_DEBUG_EN */
#endif /* FC41D_BLE_H */
+75
View File
@@ -0,0 +1,75 @@
/**
* @file fc41d_ble_priv.h
* @brief FC41D BLE 后端私有上下文(与 tcp_link_ctx_t 在 comm_link 中 union 复用)
*/
#ifndef FC41D_BLE_PRIV_H
#define FC41D_BLE_PRIV_H
#include <stdint.h>
#include "fc41d_ble.h"
#define FC41D_AT_RX_ACC_SIZE 768U
#define FC41D_AT_LINE_MAX 128U
#define FC41D_AT_CMD_MAX 1200U
/** link 解析缓冲(AT 行与 +QBLERECV */
#define FC41D_LINK_RX_NORMAL 512U
/** 固件升级:单包 1024B Base64 JSON + UART 残包 */
#define FC41D_LINK_RX_UPGRADE 2048U
/** 应用 RX 环(心跳/参数查询等) */
#define FC41D_APP_RX_NORMAL 1024U
/** 固件升级 Data JSON 拼包(1024B 载荷约 1.6KB JSON */
#define FC41D_APP_RX_UPGRADE 2048U
typedef enum {
FC41D_BLE_ST_IDLE = 0,
FC41D_BLE_ST_POWER_ON,
FC41D_BLE_ST_WAIT_CONNECT,
FC41D_BLE_ST_CONNECTED,
FC41D_BLE_ST_ERROR,
} fc41d_ble_state_t;
typedef enum {
FC41D_BLE_URC_NONE = 0,
FC41D_BLE_URC_CONNECTED,
FC41D_BLE_URC_DISCONNECTED,
} fc41d_ble_urc_t;
typedef struct {
char rx_acc[FC41D_AT_RX_ACC_SIZE];
uint16_t rx_acc_len;
fc41d_ble_urc_t pending_urc;
char at_cmd_buf[FC41D_AT_LINE_MAX];
char ble_cmd_buf[64];
char gatt_at_cmd[FC41D_AT_CMD_MAX];
} fc41d_ble_at_t;
typedef struct {
uint8_t *link_rx_acc;
uint16_t link_rx_acc_cap;
uint16_t link_rx_acc_len;
uint16_t qble_payload_need;
uint8_t *app_rx;
uint16_t app_rx_cap;
uint16_t app_rx_wr;
uint16_t app_rx_rd;
} fc41d_ble_io_t;
typedef struct {
fc41d_ble_state_t state;
uint64_t connect_deadline_ms;
uint64_t power_on_deadline_ms;
uint64_t last_stat_poll_ms;
uint8_t transparent; /* 1=QBLETRANMODE 已生效,UART 直传 JSON */
uint8_t gatt_fallback; /* 1=透传 AT 失败,回退 +QBLERECV/QBLEGATTSNTFY */
} fc41d_ble_sm_t;
typedef struct {
fc41d_ble_at_t at;
fc41d_ble_io_t io;
fc41d_ble_sm_t sm;
} fc41d_ble_ctx_t;
#endif /* FC41D_BLE_PRIV_H */
File diff suppressed because it is too large Load Diff
+176
View File
@@ -0,0 +1,176 @@
/**
* @file debug_files.h
* @brief 文件类业务:FileOperations (FATFS) + FirmwareUpgrade (Flash IAP) — 阶段2合并
*
* 合并自:system_filectrl.h、system_filetransfer.h
*/
#ifndef DEBUG_FILES_H
#define DEBUG_FILES_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ========== FileOperations (FATFS) ========== */
#define FILE_OP_SUBCMD_QUERY "Query"
#define FILE_OP_SUBCMD_DELETE "Delete"
#define FILE_OP_SUBCMD_DOWNLOAD "Download"
#define FILE_OP_SUBCMD_UPLOAD "Upload"
#define FILE_OP_STATUS_SUCCESS 0
#define FILE_OP_STATUS_FAILED 1
#define FILE_OP_STATUS_IN_PROGRESS 2
#define FILE_OP_STATUS_NOT_FOUND 3
#define FILE_OP_STATUS_NO_SPACE 4
#define FILE_OP_STATUS_PERMISSION_DENIED 5
#define FILE_TYPE_REGULAR 0
#define FILE_TYPE_DIRECTORY 1
#define FILE_TYPE_SYSTEM 2
typedef struct {
char name[128];
uint8_t type;
uint32_t size;
char modified_time[32];
char created_time[32];
} file_info_t;
typedef struct {
char path[256];
uint8_t recursive;
} file_query_request_t;
typedef struct {
char path[256];
uint16_t file_count;
uint16_t dir_count;
uint32_t total_size;
file_info_t *files;
} file_query_response_t;
typedef struct {
char path[256];
uint8_t force;
} file_delete_request_t;
typedef struct {
char path[256];
uint8_t status;
char message[128];
} file_delete_response_t;
typedef struct {
char path[256];
uint32_t offset;
uint32_t chunk_size;
} file_download_request_t;
typedef struct {
char path[256];
uint32_t file_size;
uint32_t offset;
uint32_t chunk_size;
uint32_t data_len;
uint8_t *data;
uint8_t is_last_chunk;
} file_download_response_t;
typedef struct {
char path[256];
uint32_t file_size;
uint32_t chunk_size;
uint32_t offset;
uint32_t data_len;
uint8_t *data;
uint8_t is_last_chunk;
} file_upload_request_t;
typedef struct {
char path[256];
uint32_t received_size;
uint32_t total_size;
uint8_t status;
char message[128];
} file_upload_response_t;
typedef struct {
char subCommand[32];
union {
file_query_request_t query;
file_delete_request_t delete;
file_download_request_t download;
file_upload_request_t upload;
} request;
union {
file_query_response_t query;
file_delete_response_t delete;
file_download_response_t download;
file_upload_response_t upload;
} response;
} file_operation_data_t;
int system_filectrl_init(void);
int system_filectrl_process_query(const file_query_request_t *request, file_query_response_t *response);
int system_filectrl_process_delete(const file_delete_request_t *request, file_delete_response_t *response);
int system_filectrl_process_download(const file_download_request_t *request, file_download_response_t *response);
int system_filectrl_process_upload(const file_upload_request_t *request, file_upload_response_t *response);
void system_filectrl_free_query_response(file_query_response_t *response);
void system_filectrl_free_download_response(file_download_response_t *response);
void system_filectrl_free_upload_request(file_upload_request_t *request);
int system_filectrl_get_filesystem_info(uint32_t *total_space, uint32_t *free_space);
int system_filectrl_file_exists(const char *path);
int system_filectrl_get_file_info(const char *path, file_info_t *info);
#ifndef TCP_DEBUG_FILE_AVAILABLE
#define TCP_DEBUG_FILE_AVAILABLE 0
#endif
/* ========== FirmwareUpgrade (Flash IAP) ========== */
typedef enum {
FILE_TRANSFER_IDLE = 0,
FILE_TRANSFER_STARTED,
FILE_TRANSFER_RECEIVING,
FILE_TRANSFER_COMPLETED,
FILE_TRANSFER_FAILED
} file_transfer_state_t;
typedef struct {
file_transfer_state_t state;
uint32_t file_size;
uint32_t total_packets;
uint32_t packet_size;
uint32_t received_packets;
uint32_t received_bytes;
char firmware_version[32];
uint32_t crc32;
uint8_t *buffer;
} firmware_upgrade_context_t;
int system_filetransfer_init(void);
int system_filetransfer_start_firmware_upgrade(uint32_t file_size, uint32_t total_packets,
uint32_t packet_size, const char *firmware_version);
/** 应答 Start 后执行:擦除升级区并写升级头(可能阻塞数秒) */
int system_filetransfer_commit_firmware_start(void);
int system_filetransfer_receive_firmware_packet(uint32_t packet_index, const uint8_t *data,
uint32_t data_len, uint32_t crc);
int system_filetransfer_end_firmware_upgrade(uint8_t status, const char *error_message);
int system_filetransfer_is_reboot_pending(void);
void system_filetransfer_clear_reboot_pending(void);
int system_filetransfer_get_firmware_status(firmware_upgrade_context_t *context);
int system_filetransfer_is_active(void);
int system_filetransfer_get_expected_packet_bytes(uint32_t packet_index, uint32_t *out_len);
void system_filetransfer_touch_activity_on_rx(void);
void system_filetransfer_poll_idle_timeout(void);
void system_filetransfer_reset(void);
#ifdef __cplusplus
}
#endif
#endif /* DEBUG_FILES_H */
+621
View File
@@ -0,0 +1,621 @@
/**
* @file debug_link.c
* @brief 调试链路层实现(comm_link_ctx + TCP Socket + comm_link 分发)
*/
#include "debug_link.h"
#include "tcp_protocol.h"
#include "publicdata/public_define.h"
#include "lwip/sockets.h"
#include "lwip/netdb.h"
#include <string.h>
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "app_fatfs/fatfs_init.h"
#if BLE_DEBUG_EN
#include "ble_link/fc41d_ble.h"
#endif
#if TCP_DEBUG_EN
/* =============================================================================
* 会话上下文(原 comm_link_ctx.c
* ============================================================================= */
static comm_link_ctx_t s_comm_link_ctx;
comm_link_ctx_t *comm_link_ctx_get(void)
{
return &s_comm_link_ctx;
}
comm_link_session_t *comm_link_session(void)
{
return &s_comm_link_ctx.session;
}
void comm_link_ctx_reset_session(void)
{
comm_link_type_t saved_type = s_comm_link_ctx.session.type;
memset(&s_comm_link_ctx.session, 0, sizeof(s_comm_link_ctx.session));
s_comm_link_ctx.session.type = saved_type;
}
void comm_link_ctx_reset_all(void)
{
memset(&s_comm_link_ctx, 0, sizeof(s_comm_link_ctx));
}
/* =============================================================================
* TCP Socket 后端(原 tcp_server_socket.c,本文件内 static
* ============================================================================= */
#ifndef AF_INET
#define AF_INET 2
#endif
#ifndef SOCK_STREAM
#define SOCK_STREAM 1
#endif
#ifndef SOL_SOCKET
#define SOL_SOCKET 0xFFF
#endif
#ifndef SO_REUSEADDR
#define SO_REUSEADDR 0x0004
#endif
#ifndef INADDR_ANY
#define INADDR_ANY 0x00000000
#endif
#ifndef F_GETFL
#define F_GETFL 3
#endif
#ifndef F_SETFL
#define F_SETFL 4
#endif
#ifndef O_NONBLOCK
#define O_NONBLOCK 0x0004
#endif
#ifndef EWOULDBLOCK
#define EWOULDBLOCK 11
#endif
#ifndef EAGAIN
#define EAGAIN 11
#endif
#ifndef ECONNRESET
#define ECONNRESET 104
#endif
#ifndef EPIPE
#define EPIPE 32
#endif
#ifndef ECONNABORTED
#define ECONNABORTED 103
#endif
#ifndef ETIMEDOUT
#define ETIMEDOUT 110
#endif
#ifndef ENETDOWN
#define ENETDOWN 100
#endif
#ifndef ENETUNREACH
#define ENETUNREACH 101
#endif
#ifndef EHOSTUNREACH
#define EHOSTUNREACH 113
#endif
#ifndef errno
extern int errno;
#endif
#define TCP_LOCAL_PORT TCP_DEBUG_PORT
#define MAX_CLIENTS TCP_DEBUG_MAX_CLIENTS
typedef struct {
int server_socket;
int client_socket;
uint8_t is_client_connected;
struct sockaddr_in client_addr;
} tcp_socket_server_t;
static tcp_socket_server_t socket_server = {
.server_socket = -1,
.client_socket = -1,
.is_client_connected = 0,
};
static SemaphoreHandle_t socket_mutex = NULL;
static int tcp_socket_create_server(void)
{
int sock;
struct sockaddr_in server_addr;
int opt = 1;
sock = lwip_socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
TCP_PRINT("Failed to create socket: %d\r\n", sock);
return -1;
}
lwip_setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(TCP_LOCAL_PORT);
if (lwip_bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
TCP_PRINT("Failed to bind socket to port %d\r\n", TCP_LOCAL_PORT);
lwip_close(sock);
return -1;
}
if (lwip_listen(sock, MAX_CLIENTS) < 0) {
TCP_PRINT("Failed to listen on socket\r\n");
lwip_close(sock);
return -1;
}
{
int flags = lwip_fcntl(sock, F_GETFL, 0);
lwip_fcntl(sock, F_SETFL, flags | O_NONBLOCK);
}
TCP_PRINT("TCP server socket created on port %d\r\n", TCP_LOCAL_PORT);
return sock;
}
static int tcp_socket_init(void)
{
socket_mutex = xSemaphoreCreateMutex();
if (socket_mutex == NULL) {
TCP_PRINT("Failed to create socket mutex\r\n");
return -1;
}
socket_server.server_socket = tcp_socket_create_server();
if (socket_server.server_socket < 0) {
vSemaphoreDelete(socket_mutex);
socket_mutex = NULL;
return -1;
}
socket_server.client_socket = -1;
socket_server.is_client_connected = 0;
TCP_PRINT("Socket server initialized successfully\r\n");
return 0;
}
static int tcp_socket_accept_client(void)
{
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
int new_sock;
if (socket_server.server_socket < 0) {
return -1;
}
if (socket_mutex != NULL) {
xSemaphoreTake(socket_mutex, portMAX_DELAY);
}
new_sock = lwip_accept(socket_server.server_socket,
(struct sockaddr *)&client_addr, &addr_len);
if (new_sock >= 0) {
if (socket_server.is_client_connected) {
TCP_PRINT("Rejecting new connection - already have a client\r\n");
lwip_close(new_sock);
if (socket_mutex != NULL) {
xSemaphoreGive(socket_mutex);
}
return 0;
}
{
int flags = lwip_fcntl(new_sock, F_GETFL, 0);
lwip_fcntl(new_sock, F_SETFL, flags | O_NONBLOCK);
}
socket_server.client_socket = new_sock;
socket_server.is_client_connected = 1;
memcpy(&socket_server.client_addr, &client_addr, sizeof(client_addr));
TCP_PRINT("Client connected from %s:%d\r\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
}
if (socket_mutex != NULL) {
xSemaphoreGive(socket_mutex);
}
return (new_sock >= 0) ? 1 : 0;
}
static int tcp_socket_receive_data(uint8_t *buffer, uint32_t buffer_size)
{
int received;
if (buffer == NULL || buffer_size == 0U) {
return -1;
}
if (socket_mutex != NULL) {
xSemaphoreTake(socket_mutex, portMAX_DELAY);
}
if (!socket_server.is_client_connected || socket_server.client_socket < 0) {
if (socket_mutex != NULL) {
xSemaphoreGive(socket_mutex);
}
return -1;
}
received = lwip_recv(socket_server.client_socket, buffer, buffer_size, 0);
if (received > 0) {
TCP_PRINT("Received %d bytes from client\r\n", received);
} else if (received == 0) {
TCP_PRINT("Client disconnected\r\n");
lwip_close(socket_server.client_socket);
socket_server.client_socket = -1;
socket_server.is_client_connected = 0;
received = -1;
#if FATFS_EN
fatfs_set_write_disable_flag(FATFS_WRITE_DISABLE_TCP_DEBUG, 0);
#endif
} else {
int err = errno;
if (err != EWOULDBLOCK && err != EAGAIN) {
TCP_PRINT("Socket receive error: %d\r\n", err);
if (err == ECONNRESET || err == EPIPE || err == ECONNABORTED ||
err == ETIMEDOUT || err == ENETDOWN || err == ENETUNREACH ||
err == EHOSTUNREACH) {
TCP_PRINT("Connection error detected, closing socket\r\n");
lwip_close(socket_server.client_socket);
socket_server.client_socket = -1;
socket_server.is_client_connected = 0;
#if FATFS_EN
fatfs_set_write_disable_flag(FATFS_WRITE_DISABLE_TCP_DEBUG, 0);
#endif
}
received = -1;
} else {
received = 0;
}
}
if (socket_mutex != NULL) {
xSemaphoreGive(socket_mutex);
}
return received;
}
static int tcp_socket_send_data(const uint8_t *data, uint32_t len)
{
int sent;
if (data == NULL || len == 0U) {
return -1;
}
if (socket_mutex != NULL) {
xSemaphoreTake(socket_mutex, portMAX_DELAY);
}
if (!socket_server.is_client_connected || socket_server.client_socket < 0) {
if (socket_mutex != NULL) {
xSemaphoreGive(socket_mutex);
}
return -1;
}
sent = lwip_send(socket_server.client_socket, data, len, 0);
if (sent > 0) {
TCP_PRINT("Sent %d bytes to client\r\n", sent);
} else {
TCP_PRINT("Failed to send data: %d\r\n", sent);
if (sent < 0) {
int err = errno;
if (err == ECONNRESET || err == EPIPE) {
TCP_PRINT("Connection broken, closing socket\r\n");
lwip_close(socket_server.client_socket);
socket_server.client_socket = -1;
socket_server.is_client_connected = 0;
}
}
}
if (socket_mutex != NULL) {
xSemaphoreGive(socket_mutex);
}
return sent;
}
static int tcp_socket_is_client_connected(void)
{
int connected;
if (socket_mutex != NULL) {
xSemaphoreTake(socket_mutex, portMAX_DELAY);
}
connected = socket_server.is_client_connected;
if (socket_mutex != NULL) {
xSemaphoreGive(socket_mutex);
}
return connected;
}
static void tcp_socket_close_client(void)
{
if (socket_mutex != NULL) {
xSemaphoreTake(socket_mutex, portMAX_DELAY);
}
if (socket_server.client_socket >= 0) {
lwip_close(socket_server.client_socket);
socket_server.client_socket = -1;
}
socket_server.is_client_connected = 0;
#if FATFS_EN
fatfs_set_write_disable_flag(FATFS_WRITE_DISABLE_TCP_DEBUG, 0);
#endif
TCP_PRINT("Client connection closed\r\n");
if (socket_mutex != NULL) {
xSemaphoreGive(socket_mutex);
}
}
static void tcp_socket_close_server(void)
{
if (socket_mutex != NULL) {
xSemaphoreTake(socket_mutex, portMAX_DELAY);
}
if (socket_server.client_socket >= 0) {
lwip_close(socket_server.client_socket);
socket_server.client_socket = -1;
}
if (socket_server.server_socket >= 0) {
lwip_close(socket_server.server_socket);
socket_server.server_socket = -1;
}
socket_server.is_client_connected = 0;
#if FATFS_EN
fatfs_set_write_disable_flag(FATFS_WRITE_DISABLE_TCP_DEBUG, 0);
#endif
if (socket_mutex != NULL) {
xSemaphoreGive(socket_mutex);
vSemaphoreDelete(socket_mutex);
socket_mutex = NULL;
}
TCP_PRINT("Socket server closed\r\n");
}
static int tcp_socket_get_client_ip(char *ip_buffer, uint32_t buffer_size)
{
const char *ip_str;
if (ip_buffer == NULL || buffer_size == 0U) {
return -1;
}
if (socket_mutex != NULL) {
xSemaphoreTake(socket_mutex, portMAX_DELAY);
}
if (!socket_server.is_client_connected) {
if (socket_mutex != NULL) {
xSemaphoreGive(socket_mutex);
}
return -1;
}
ip_str = inet_ntoa(socket_server.client_addr.sin_addr);
if (ip_str != NULL) {
strncpy(ip_buffer, ip_str, buffer_size - 1U);
ip_buffer[buffer_size - 1U] = '\0';
} else {
if (socket_mutex != NULL) {
xSemaphoreGive(socket_mutex);
}
return -1;
}
if (socket_mutex != NULL) {
xSemaphoreGive(socket_mutex);
}
return 0;
}
/* =============================================================================
* comm_link 分发(原 comm_link.c
* ============================================================================= */
#define SESS (comm_link_session())
static uint8_t comm_link_using_ble(void)
{
#if BLE_DEBUG_EN
return (SESS->type == COMM_LINK_BLE) ? 1U : 0U;
#else
return 0U;
#endif
}
void comm_link_set_type(comm_link_type_t type)
{
SESS->type = type;
}
comm_link_type_t comm_link_get_type(void)
{
return SESS->type;
}
comm_link_type_t comm_link_type_from_dip(void)
{
#if BLE_DEBUG_EN
return ble_link_is_bt_mode() ? COMM_LINK_BLE : COMM_LINK_TCP;
#else
return COMM_LINK_TCP;
#endif
}
int comm_link_start(void)
{
if (comm_link_using_ble() != 0U) {
#if BLE_DEBUG_EN
return comm_link_ble_start();
#endif
}
return tcp_socket_init();
}
void comm_link_stop(void)
{
if (comm_link_using_ble() != 0U) {
#if BLE_DEBUG_EN
comm_link_ble_stop();
#endif
return;
}
tcp_socket_close_server();
}
void comm_link_process(void)
{
#if BLE_DEBUG_EN
if (comm_link_using_ble() != 0U) {
comm_link_ble_process();
}
#endif
}
int comm_link_accept_client(void)
{
if (comm_link_using_ble() != 0U) {
return 0;
}
return tcp_socket_accept_client();
}
int comm_link_is_client_connected(void)
{
if (comm_link_using_ble() != 0U) {
#if BLE_DEBUG_EN
return comm_link_ble_is_client_connected();
#endif
return 0;
}
return tcp_socket_is_client_connected();
}
void comm_link_close_client(void)
{
if (comm_link_using_ble() != 0U) {
#if BLE_DEBUG_EN
comm_link_ble_close_client();
#endif
return;
}
tcp_socket_close_client();
}
int comm_link_receive_data(uint8_t *buffer, uint32_t buffer_size)
{
if (comm_link_using_ble() != 0U) {
#if BLE_DEBUG_EN
return comm_link_ble_receive_data(buffer, buffer_size);
#endif
return 0;
}
return tcp_socket_receive_data(buffer, buffer_size);
}
int comm_link_send_data(const uint8_t *data, uint32_t len)
{
if (comm_link_using_ble() != 0U) {
#if BLE_DEBUG_EN
return comm_link_ble_send_data(data, len);
#endif
return -1;
}
return tcp_socket_send_data(data, len);
}
int comm_link_get_peer_info(char *buf, uint32_t buf_size)
{
if ((buf == NULL) || (buf_size == 0U)) {
return -1;
}
if (SESS->peer_info[0] != '\0') {
(void)snprintf(buf, buf_size, "%s", SESS->peer_info);
return 0;
}
if (comm_link_using_ble() != 0U) {
#if BLE_DEBUG_EN
return comm_link_ble_get_peer_info(buf, buf_size);
#endif
}
return tcp_socket_get_client_ip(buf, buf_size);
}
#else /* TCP_DEBUG_EN */
comm_link_ctx_t *comm_link_ctx_get(void) { return NULL; }
comm_link_session_t *comm_link_session(void) { return NULL; }
void comm_link_ctx_reset_session(void) { }
void comm_link_ctx_reset_all(void) { }
void comm_link_set_type(comm_link_type_t type) { (void)type; }
comm_link_type_t comm_link_get_type(void) { return COMM_LINK_TCP; }
comm_link_type_t comm_link_type_from_dip(void) { return COMM_LINK_TCP; }
int comm_link_start(void) { return -1; }
void comm_link_stop(void) { }
void comm_link_process(void) { }
int comm_link_accept_client(void) { return -1; }
int comm_link_is_client_connected(void) { return 0; }
void comm_link_close_client(void) { }
int comm_link_receive_data(uint8_t *buffer, uint32_t buffer_size)
{
(void)buffer;
(void)buffer_size;
return -1;
}
int comm_link_send_data(const uint8_t *data, uint32_t len)
{
(void)data;
(void)len;
return -1;
}
int comm_link_get_peer_info(char *buf, uint32_t buf_size)
{
(void)buf;
(void)buf_size;
return -1;
}
#endif /* TCP_DEBUG_EN */
+95
View File
@@ -0,0 +1,95 @@
/**
* @file debug_link.h
* @brief 调试链路层:TCP Socket 后端 + comm_link 适配 + 会话上下文(阶段1合并)
*
* 合并自:comm_link.h、comm_link_ctx.h、tcp/tcp_link_priv.h、tcp_server_socket 对外 API
*/
#ifndef DEBUG_LINK_H
#define DEBUG_LINK_H
#include <stdint.h>
#include "publicdata/public_define.h"
#ifdef __cplusplus
extern "C" {
#endif
/* ---------- comm_link 类型与 API ---------- */
typedef enum {
COMM_LINK_TCP = 0,
#if BLE_DEBUG_EN
COMM_LINK_BLE = 1,
#endif
} comm_link_type_t;
#if BLE_DEBUG_EN
#define COMM_LINK_IS_BLE(t) ((t) == COMM_LINK_BLE)
#else
#define COMM_LINK_IS_BLE(t) (0)
#endif
void comm_link_set_type(comm_link_type_t type);
comm_link_type_t comm_link_get_type(void);
comm_link_type_t comm_link_type_from_dip(void);
int comm_link_start(void);
void comm_link_stop(void);
void comm_link_process(void);
int comm_link_accept_client(void);
int comm_link_is_client_connected(void);
void comm_link_close_client(void);
int comm_link_receive_data(uint8_t *buffer, uint32_t buffer_size);
int comm_link_send_data(const uint8_t *data, uint32_t len);
int comm_link_get_peer_info(char *buf, uint32_t buf_size);
/* ---------- comm_link_ctxTCP/BLE backend union ---------- */
#define COMM_LINK_PEER_INFO_SIZE 48U
typedef struct {
comm_link_type_t type;
uint8_t client_connected;
char peer_info[COMM_LINK_PEER_INFO_SIZE];
} comm_link_session_t;
#if BLE_DEBUG_EN
#include "ble_link/fc41d_ble_priv.h"
#endif
#include "lwip/sockets.h"
#include "FreeRTOS.h"
#include "semphr.h"
typedef struct {
int server_socket;
int client_socket;
struct sockaddr_in client_addr;
SemaphoreHandle_t mutex;
} tcp_link_ctx_t;
typedef union {
#if BLE_DEBUG_EN
fc41d_ble_ctx_t ble;
#endif
tcp_link_ctx_t tcp;
} comm_link_backend_u;
typedef struct {
comm_link_session_t session;
comm_link_backend_u backend;
} comm_link_ctx_t;
comm_link_ctx_t *comm_link_ctx_get(void);
comm_link_session_t *comm_link_session(void);
void comm_link_ctx_reset_session(void);
void comm_link_ctx_reset_all(void);
#ifdef __cplusplus
}
#endif
#endif /* DEBUG_LINK_H */
+853
View File
@@ -0,0 +1,853 @@
/**
******************************************************************************
* @file system_data.c
* @brief 系统数据交互模块实现(简化版)
*
* 该模块负责系统信息、充电信息获取、充电控制等功能
* 为参数配置、文件获取等功能预留接口
******************************************************************************
*/
#include "system_data.h"
#include <string.h>
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "publicdata/type.h"
#include "publicdata/public_define.h"
#if TCP_DEBUG_EN
#include "tcp_ser/tcp_server.h"
#include "tcp_ser/tcp_protocol.h"
#include "flash_file_mgr/fault_flash_impl.h"
#include "plat_comm/impl/bs_public_impl.h"
#include "publicdata/publicdata.h"
#include "flowctrl/flowctrl_task.h"
#include "collect_ctrl/collect_task.h"
#include "bms/bms_interface.h"
#include "meter_calculate/meter_calculate_impl.h"
#include "flash_file_mgr/meter_calculate_flash_impl.h"
#include "fault_cheak/fault_interface.h"
#include "app_fatfs/fatfs_card.h"
/**
* @brief 获取默认系统信息(从system_data模块获取)
* @param systemInfo 输出系统信息结构体
*/
void tcp_get_default_system_info(tcp_system_info_data_t *systemInfo)
{
REAL_FAULT_DATA_T rl_data;
if (systemInfo == NULL) {
return;
}
v_get_fault_data(&rl_data);
memset(systemInfo, 0, sizeof(tcp_system_info_data_t));
/* 从system_data模块获取系统信息 */
strcpy(systemInfo->systemModel, (char *)Device_Model); //设备型号
systemInfo->gunCount = GUN_MAX_CNT; //最大支持枪数量
systemInfo->devicePower = g_t_share_data.t_sys_fix_cfg.u16_ratePow; //设备功率 kW
systemInfo->networkStatus = u8_bs_get_log_on_flag();
systemInfo->faultCount = rl_data.u8_fault_cnt;
strcpy(systemInfo->version, (char *)Software_Version);
}
static void tcp_copy_log_field(char *dst, size_t dst_sz, const U8_T *src, size_t src_sz)
{
size_t i;
if ((dst == NULL) || (dst_sz == 0U)) {
return;
}
dst[0] = '\0';
if (src == NULL) {
return;
}
for (i = 0U; i + 1U < dst_sz && i < src_sz; i++) {
U8_T b = src[i];
if (b == 0U) {
break;
}
if (b < 0x20U || b > 0x7EU) {
break;
}
dst[i] = (char)b;
}
dst[i] = '\0';
}
/**
* @brief 获取默认充电枪数据(从system_data模块获取)
* @param gunData 输出充电枪数据结构体
* @param gunNumber 枪编号 (1-2)
*/
void tcp_get_default_gun_data(tcp_gun_data_t *gunData, uint8_t gunNumber)
{
U8_T u8_gunNo = gunNumber -1;
U8_T u8_gunState = u16_flow_put_data(u8_gunNo , E_FLOW_PUT_DATA_GUN_REAL_STATE,0); //0空闲 1充电启动 2绝缘启动 3绝缘计算完成 4绝缘检测模块停止 5开始预充 6预充完成 7充电中 8BMS通讯超时 9充电停止 10充电停止完成
U8_T u8_gunLink = u8_get_gun_yx_data(YX_GUN_LINK,u8_gunNo);
U16_T u16_needVol = u16_bms_put_data(u8_gunNo,E_BMS_PUT_DATA_BMS_NEED_VOL,0);
U16_T u16_needCur = u16_bms_put_data(u8_gunNo,E_BMS_PUT_DATA_BMS_NEED_CUR,0);
U16_T u16_outVol = (U16_T)(u64_get_meter_data(u8_gunNo,0,E_METER_DATA_VOL)/1000); //充电电压 精度 0.1V
U16_T u16_outCur = (U16_T)(u64_get_meter_data(u8_gunNo,0,E_METER_DATA_CUR)/1000); //充电电流 精度 0.1A
S_LOG_DATA s_log;
char user_field[32];
memset(&s_log , 0 ,sizeof(S_LOG_DATA));
v_meterlog_put_char_log_data(u8_gunNo,&s_log); //获取订单信息
if (gunData == NULL) {
return;
}
memset(gunData, 0, sizeof(tcp_gun_data_t));
/* 设置枪编号 */
gunData->gunNumber = gunNumber;
/* 从system_data模块获取枪信息 */
if(u8_gunState == 0 && u8_gunLink == 0)
gunData->chargingStatus = CHARGING_STATUS_IDLE; /* 空闲 */
else if(u8_gunState == 0 && u8_gunLink == 1)
gunData->chargingStatus = CHARGING_STATUS_CONNECTED; /* 已连接 */
else if(u8_gunState > 0 && u8_gunState < 7)
gunData->chargingStatus = CHARGING_STATUS_STARTING; /* 启动中 */
else if(u8_gunState >= 7 && u8_gunState <= 8)
gunData->chargingStatus = CHARGING_STATUS_CHARGING; /* 充电中 */
else if(u8_gunState > 8)
gunData->chargingStatus = CHARGING_STATUS_FINISHED; /* 充电完成 */
gunData->gunConnectionStatus = u8_gunLink;
gunData->demandVoltage = u16_needVol / 10.0f;
gunData->demandCurrent = u16_needCur / 10.0f;
gunData->actualVoltage = u16_outVol / 10.0f;
gunData->actualCurrent = u16_outCur / 10.0f;
/* 空闲/仅插枪:不上送无效订单残留,避免 JSON 过长导致 BLE 分片异常 */
if (gunData->chargingStatus <= CHARGING_STATUS_CONNECTED) {
gunData->energy = 0.0f;
gunData->cost = 0.0f;
gunData->batterySOC = 0.0f;
gunData->charge_time = 0U;
gunData->userId[0] = '\0';
gunData->orderNumber[0] = '\0';
return;
}
gunData->energy = (float)s_log.u32_used_energy * 0.0001f;
gunData->cost = (float)s_log.u32_used_money * 0.0001f;
gunData->batterySOC = (s_log.u8_endSoc <= 100U) ? (float)s_log.u8_endSoc : 100.0f;
gunData->charge_time = s_log.u16_ChargedTime;
tcp_copy_log_field(user_field, sizeof(user_field), s_log.u8_user_id, sizeof(s_log.u8_user_id));
if (user_field[0] != '\0') {
(void)snprintf(gunData->userId, sizeof(gunData->userId), "USER_%s", user_field);
}
tcp_copy_log_field(gunData->orderNumber, sizeof(gunData->orderNumber),
s_log.u8_preTradeNo, sizeof(s_log.u8_preTradeNo));
//测试
// if(u8_gunNo == 0) gunData->chargingStatus = CHARGING_STATUS_CONNECTED;
// else gunData->chargingStatus = CHARGING_STATUS_CHARGING;
// gunData->gunConnectionStatus = 1;
}
void v_mydebug_char_ctrl(U8_T u8_gunNo, U8_T u8_flag, U16_T u16_res)
{
S_FLOW_CHARGE_CTRL_INFO e_charge_ctrl_info;
memset(&e_charge_ctrl_info, 0, sizeof(S_FLOW_CHARGE_CTRL_INFO));
e_charge_ctrl_info.e_cmd = u8_flag;
if (u8_flag == CMD_ON) {
e_charge_ctrl_info.e_start_res = CTR_CMD_DEBUG;
e_charge_ctrl_info.e_sdk_chargMode = E_SDK_CHGMOD_NONE;
} else {
e_charge_ctrl_info.e_stop_res = E_NORMAL_1001;
}
v_flow_start_stop_ctrl(u8_gunNo, &e_charge_ctrl_info);
TCP_PRINT("[%d]gun charge ctrl[%d],[%d]\n", u8_gunNo, u8_flag, u16_res);
if (u8_flag == CMD_OFF) {
v_meterlog_set_stop_char_log_info(u8_gunNo, E_NORMAL_1001);
} else {
S_CHAR_LOG_START_DATA s_log_start_data;
memset(&s_log_start_data, 0, sizeof(S_CHAR_LOG_START_DATA));
s_log_start_data.e_start_reason = e_charge_ctrl_info.e_start_res;
strncpy((char *)s_log_start_data.u8_user_id, "DEBUG_001", EVS_MAX_TRADE_LEN);
v_meterlog_set_start_char_log_info(u8_gunNo, &s_log_start_data);
}
}
/**
* @brief 停止充电(模拟实现)
* @param gunNumber 枪编号 (1-2)
* @param message 输出消息
* @retval 0成功,-1失败
*/
int system_data_stop_charge(uint8_t gunNumber, char *message)
{
if (gunNumber < 1 || gunNumber > GUN_MAX_CNT) {
if (message) strcpy(message, "参数错误:枪编号无效");
return -1;
}
v_mydebug_char_ctrl(gunNumber-1 , CMD_OFF ,0);
if (message) strcpy(message, "充电停止成功(模拟)");
return 0;
}
/**
* @brief 启动充电(模拟实现)
* @param gunNumber 枪编号 (1-2)
* @param userId 用户ID
* @param orderNumber 订单号
* @param message 输出消息
* @retval 0成功,-1失败
*/
int system_data_start_charge(uint8_t gunNumber, const char *userId,
const char *orderNumber, char *message)
{
if (gunNumber < 1 || gunNumber > GUN_MAX_CNT) {
if (message) strcpy(message, "参数错误:枪编号无效");
return -1;
}
v_mydebug_char_ctrl(gunNumber-1 , CMD_ON ,0);
if (message) strcpy(message, "充电启动成功(模拟)");
return 0;
}
/**
* @brief 获取系统故障数据
* @param response 输出故障信息响应结构体
* @retval 无
*/
void tcp_get_system_fault_data(tcp_fault_info_response_t *response)
{
REAL_FAULT_DATA_T rl_data;
U8_T valid_fault_count = 0;
int i, j;
if (response == NULL) {
return;
}
memset(response, 0, sizeof(tcp_fault_info_response_t));
v_get_fault_data(&rl_data); //获取故障信息
/* 首先统计有效故障数量(e_mag != 0) */
for (i = 0; i < CURR_FAULT_MAX_CNT; i++) {
if (rl_data.fault_data[i].e_mag != 0) {
valid_fault_count++;
}
}
/* 填充响应结构体 */
response->faultCount = valid_fault_count;
/* 分配故障项数组内存 */
if (valid_fault_count > 0) {
response->faults = (tcp_fault_item_t *)pvPortMalloc(valid_fault_count * sizeof(tcp_fault_item_t));
if (response->faults == NULL) {
TCP_PRINT("Failed to allocate memory for fault items\r\r\n");
response->faultCount = 0;
return;
}
memset(response->faults, 0, valid_fault_count * sizeof(tcp_fault_item_t));
/* 转换故障数据格式 */
for (i = 0, j = 0; i < CURR_FAULT_MAX_CNT && j < valid_fault_count; i++) {
Fault_data *fault = &rl_data.fault_data[i];
/* 跳过无效故障(e_mag == 0 */
if (fault->e_mag == 0) {
continue;
}
/* 转换故障位置 */
/*
E_WHOLE_PILE = 0, //整桩-终端
E_WHOLE_HOST = 500, //整桩-主机-功率分配
E_GUN = 1000, //枪地址
E_CHAR_MDU = 2000, //充电模块地址
E_SWH_MDU = 3000, //开关模块地址
E_OTHER = 4000, //其他位置
*/
/* 根据E_FAULT_SITE枚举值判断故障位置 */
if (fault->e_ingun >= E_OTHER) {
snprintf(response->faults[j].location,sizeof(response->faults[j].location) -1,
"其他故障");
} else if (fault->e_ingun >= E_SWH_MDU) {
snprintf(response->faults[j].location,sizeof(response->faults[j].location) -1,
"开关模块 %d",fault->e_ingun - E_SWH_MDU);
}
else if (fault->e_ingun >= E_CHAR_MDU) {
snprintf(response->faults[j].location,sizeof(response->faults[j].location) -1,
"充电模块 %d",fault->e_ingun - E_CHAR_MDU);
}
else if (fault->e_ingun >= E_GUN) {
snprintf(response->faults[j].location,sizeof(response->faults[j].location) -1,
"充电枪 %d",fault->e_ingun - E_GUN);
}
else if (fault->e_ingun >= E_WHOLE_HOST) {
snprintf(response->faults[j].location,sizeof(response->faults[j].location) -1,
"整桩主机 %d",fault->e_ingun - E_WHOLE_HOST);
}
else {
snprintf(response->faults[j].location,sizeof(response->faults[j].location) -1,
"整桩终端 %d",fault->e_ingun);
}
snprintf(response->faults[j].code,sizeof(response->faults[j].code) -1,
"%d",fault->e_mag);
char mag[FAULT_GET_MAG_MAX_LEN]= {0};
v_get_fault_info(fault->e_mag , mag ,FAULT_GET_MAG_MAX_LEN);
snprintf(response->faults[j].description,sizeof(response->faults[j].description) -1,
"%s",mag);
/* 转换时间格式 */
snprintf(response->faults[j].time, sizeof(response->faults[j].time),
"%04d-%02d-%02d %02d:%02d:%02d",
fault->time.iYear, fault->time.ucMonth, fault->time.ucDay,
fault->time.ucHour, fault->time.ucMin, fault->time.ucSec);
j++; /* 移动到下一个有效故障项 */
}
} else {
response->faults = NULL;
}
TCP_PRINT("Got %u valid faults from 0 total faults\r\r\n", valid_fault_count);
}
/**
* @brief 处理自定义数据
* @param request 自定义数据请求
* @param response 自定义数据响应
* @retval 0成功,-1失败
*/
int tcp_process_custom_data(const tcp_custom_data_t *request, tcp_custom_data_t *response)
{
if (request == NULL || response == NULL) {
return -1;
}
TCP_PRINT("Processing custom data:\r\r\n");
TCP_PRINT(" functionContent: %s\r\r\n", request->functionContent);
TCP_PRINT(" functionField: %s\r\r\n", request->functionField);
TCP_PRINT(" functionParams: %s\r\r\n", request->functionParams);
/* 复制请求数据到响应(根据用户要求,字段内容可以自定义) */
memset(response, 0, sizeof(tcp_custom_data_t));
/* 这里可以根据实际需求处理自定义数据 */
/* 示例:如果functionContent是"哈哈哈",则回复"呵呵呵" */
if(request->functionField[0] == '\0') //无数据
{
TCP_PRINT("functionField is null\r\n");
//request->functionField
//response->functionContent
//request->functionParams
}
else if(strcmp(request->functionField, "reboot") == 0)
{
v_sys_reboot();
}
else if(strcmp(request->functionField, "paramInit") == 0)
{
v_public_param_init();
strncpy(response->functionField , request->functionField , sizeof(response->functionField)-1);
strncpy(response->functionContent , "success" , sizeof(response->functionContent)-1);
strncpy(response->functionParams , "success" , sizeof(response->functionParams)-1);
}
#if BS_OCPP_EN
else if(strcmp(request->functionField, "ocpp_cfg") == 0) //
{
strncpy(response->functionField , request->functionField , sizeof(response->functionField)-1);
strncpy(response->functionContent , request->functionContent , sizeof(response->functionContent)-1);
if(strcmp(request->functionContent, "getInfo") == 0) //获取信息
{
v_get_ocpp_cfg_info(response->functionParams ,sizeof(response->functionParams)-1);
}
else if(request->functionContent[0] != '\0') //不为空 则是设置命令
{
if(u8_set_ocpp_cfg_info(request->functionContent,request->functionParams) == 1)
{
strncpy(response->functionParams ,success , sizeof(response->functionContent)-1);
}
else
{
strncpy(response->functionParams ,failure , sizeof(response->functionContent)-1);
}
}
}
#endif
else
{
strncpy(response->functionField , request->functionField , sizeof(response->functionField)-1);
strncpy(response->functionContent , "unknown" , sizeof(response->functionContent)-1);
}
TCP_PRINT("Custom data processed:\r\r\n");
TCP_PRINT(" functionContent: %s\r\r\n", response->functionContent);
TCP_PRINT(" functionField: %s\r\r\n", response->functionField);
TCP_PRINT(" functionParams: %s\r\r\n", response->functionParams);
return 0;
}
uint16_t tcp_history_get_total_count(const char *historyType)
{
if (historyType == NULL) {
return 0u;
}
if (strcmp(historyType, "fault") == 0) {
return u16_fault_get_his_count();
}
if (strcmp(historyType, "charging") == 0) {
v_chg_order_flash_init();
return v_get_chg_order_count();
}
if (strcmp(historyType, "card") == 0) {
uint32_t cnt = 0u;
card_init();
if (card_get_count(&cnt) == CARD_STATUS_OK) {
if (cnt > 0xFFFFu) {
return 0xFFFFu;
}
return (uint16_t)cnt;
}
return 0u;
}
return 0u;
}
static void tcp_history_sanitize_commas(char *s)
{
/* 协议content字段内不得出现英文逗号,否则上位机会split(',')解析出错 */
if (s == NULL) {
return;
}
while (*s != '\0') {
if (*s == ',') {
*s = ';';
}
s++;
}
}
static void tcp_history_format_comm_time(const Comm_Time *t, char *buf, size_t bufSize)
{
if (t == NULL || buf == NULL || bufSize == 0u) {
return;
}
(void)snprintf(buf, bufSize,
"%04d-%02d-%02d %02d:%02d:%02d",
(int)t->iYear,
(int)t->ucMonth,
(int)t->ucDay,
(int)t->ucHour,
(int)t->ucMin,
(int)t->ucSec);
buf[bufSize - 1u] = '\0';
}
/* 将订单中的停止原因转为可读文本:仅取 e_stop_reason[] 中第一个非空项(与多条 | 拼接相比更短,利于协议长度) */
static void tcp_history_build_stop_reason_text(const S_LOG_DATA *order, char *dst, size_t dstSize)
{
if (order == NULL || dst == NULL || dstSize == 0u) {
return;
}
dst[0] = '\0';
for (int i = 0; i < NUM_1; i++) {
E_FAULT_INFO code = order->e_stop_reason[i];
if (code == E_FAULT_NULL) {
continue;
}
char frag[128] = {0};
v_get_fault_info(code, frag, (U16_T)sizeof(frag));
if (frag[0] == '\0') {
(void)snprintf(frag, sizeof(frag), "%d", (int)code);
}
tcp_history_sanitize_commas(frag);
(void)strncpy(dst, frag, dstSize - 1u);
dst[dstSize - 1u] = '\0';
return;
}
(void)strncpy(dst, "-", dstSize - 1u);
dst[dstSize - 1u] = '\0';
}
uint16_t tcp_history_build_records(const char *historyType,
uint16_t startIndex,
uint16_t fetchCount,
char (*time_buf)[HISTORY_TIME_STR_SIZE],
char (*content_buf)[HISTORY_CONTENT_STR_SIZE],
uint8_t *outIncludeTime,
uint16_t *outTotalCount)
{
uint16_t totalCount = 0u;
uint16_t wantedCount = 0u;
uint16_t filledCount = 0u;
uint8_t includeTime = 0u;
if (historyType == NULL || time_buf == NULL || content_buf == NULL || outIncludeTime == NULL || outTotalCount == NULL) {
return 0u;
}
memset(time_buf, 0, (size_t)HISTORY_QUERY_MAX_RECORDS * (size_t)HISTORY_TIME_STR_SIZE);
memset(content_buf, 0, (size_t)HISTORY_QUERY_MAX_RECORDS * (size_t)HISTORY_CONTENT_STR_SIZE);
if (strcmp(historyType, "fault") == 0) {
includeTime = 1u;
totalCount = u16_fault_get_his_count();
} else if (strcmp(historyType, "charging") == 0) {
includeTime = 1u;
v_chg_order_flash_init();
totalCount = v_get_chg_order_count();
} else if (strcmp(historyType, "card") == 0) {
includeTime = 0u;
uint32_t cnt = 0u;
card_init();
if (card_get_count(&cnt) == CARD_STATUS_OK) {
totalCount = (cnt > 0xFFFFu) ? 0xFFFFu : (uint16_t)cnt;
} else {
totalCount = 0u;
}
} else {
*outIncludeTime = 0u;
*outTotalCount = 0u;
return 0u;
}
*outIncludeTime = includeTime;
*outTotalCount = totalCount;
if (fetchCount == 0u || totalCount == 0u) {
return 0u;
}
wantedCount = fetchCount;
if (wantedCount > HISTORY_QUERY_MAX_RECORDS) {
wantedCount = HISTORY_QUERY_MAX_RECORDS;
}
if (startIndex >= totalCount) {
return 0u;
}
if (wantedCount > (uint16_t)(totalCount - startIndex)) {
wantedCount = (uint16_t)(totalCount - startIndex);
}
if (strcmp(historyType, "fault") == 0) {
HIS_FAULT_DATA_T hisFault;
memset(&hisFault, 0, sizeof(hisFault));
for (uint16_t i = 0u; i < wantedCount; i++) {
uint16_t newest_pos = (uint16_t)(startIndex + i);
if (u8_fault_read_his_by_newest(newest_pos, &hisFault) == 0u) {
break;
}
snprintf(time_buf[i], HISTORY_TIME_STR_SIZE,
"%04d-%02d-%02d %02d:%02d:%02d",
hisFault.s_occur_time.iYear,
hisFault.s_occur_time.ucMonth,
hisFault.s_occur_time.ucDay,
hisFault.s_occur_time.ucHour,
hisFault.s_occur_time.ucMin,
hisFault.s_occur_time.ucSec);
char location[64] = {0};
char code[16] = {0};
char desc[128] = {0};
char actionTypeStr[16] = {0};
char value1Str[32] = {0};
char value2Str[32] = {0};
char field_content[HISTORY_CONTENT_STR_SIZE] = {0};
if (hisFault.e_site >= E_OTHER) {
snprintf(location, sizeof(location), "其他故障");
} else if (hisFault.e_site >= E_SWH_MDU) {
snprintf(location, sizeof(location), "开关模块 %d", hisFault.e_site - E_SWH_MDU);
} else if (hisFault.e_site >= E_CHAR_MDU) {
snprintf(location, sizeof(location), "充电模块 %d", hisFault.e_site - E_CHAR_MDU);
} else if (hisFault.e_site >= E_GUN) {
snprintf(location, sizeof(location), "充电枪 %d", hisFault.e_site - E_GUN);
} else if (hisFault.e_site >= E_WHOLE_HOST) {
snprintf(location, sizeof(location), "整桩主机 %d", hisFault.e_site - E_WHOLE_HOST);
} else {
snprintf(location, sizeof(location), "整桩终端 %d", hisFault.e_site);
}
snprintf(code, sizeof(code), "%d", (int)hisFault.e_fault_info);
v_get_fault_info(hisFault.e_fault_info, desc, (U16_T)sizeof(desc));
snprintf(actionTypeStr, sizeof(actionTypeStr), "%d", (int)hisFault.ActType);
snprintf(value1Str, sizeof(value1Str), "%d", (int)hisFault.s16_value1);
snprintf(value2Str, sizeof(value2Str), "%d", (int)hisFault.s16_value2);
tcp_history_sanitize_commas(location);
tcp_history_sanitize_commas(desc);
snprintf(field_content, sizeof(field_content),
"%s,%s,%s,%s,%s,%s",
location,
code,
desc,
actionTypeStr,
value1Str,
value2Str);
strncpy(content_buf[i], field_content, HISTORY_CONTENT_STR_SIZE - 1);
content_buf[i][HISTORY_CONTENT_STR_SIZE - 1] = '\0';
filledCount++;
}
} else if (strcmp(historyType, "charging") == 0) {
S_LOG_DATA order;
memset(&order, 0, sizeof(order));
U32_T lastIndex = v_get_chg_order_last_index();
if (lastIndex == 0u) {
return 0u;
}
for (uint16_t i = 0u; i < wantedCount; i++) {
U32_T offset = (U32_T)(startIndex + i);
if (lastIndex <= offset) {
break;
}
U32_T index = lastIndex - offset;
if (v_read_chg_order_record(&order, index) != 0u) {
break;
}
snprintf(time_buf[i], HISTORY_TIME_STR_SIZE,
"%04d-%02d-%02d %02d:%02d:%02d",
order.EndChargeTime.iYear,
order.EndChargeTime.ucMonth,
order.EndChargeTime.ucDay,
order.EndChargeTime.ucHour,
order.EndChargeTime.ucMin,
order.EndChargeTime.ucSec);
char userId[40] = {0};
char stopCmd[16] = {0};
char tradeNoStr[EVS_MAX_TRADE_LEN + 4] = {0};
char preTradeStr[EVS_MAX_TRADE_LEN + 4] = {0};
char startTimeStr[HISTORY_TIME_STR_SIZE] = {0};
char stopTimeStr[HISTORY_TIME_STR_SIZE] = {0};
char stopReasonText[256] = {0};
const char *uid_src = (const char *)order.u8_user_id;
uint16_t n = 0u;
while (n < 32u && n < (uint16_t)(sizeof(userId) - 1u) && uid_src[n] != '\0') {
userId[n] = uid_src[n];
n++;
}
userId[n] = '\0';
/* 停机码为 4 字节原始码,可能含 0x00,不能按 C 字符串拷贝,否则上位机看到为空 */
(void)snprintf(stopCmd, sizeof(stopCmd), "%d",
(unsigned int)order.e_stop_reason[0]);
{
const char *ts = (const char *)order.u8_tradeNo;
n = 0u;
while (n < (uint16_t)(EVS_MAX_TRADE_LEN - 1u) && n < (uint16_t)(sizeof(tradeNoStr) - 1u) && ts[n] != '\0') {
tradeNoStr[n] = ts[n];
n++;
}
tradeNoStr[n] = '\0';
}
{
const char *ps = (const char *)order.u8_preTradeNo;
n = 0u;
while (n < (uint16_t)(EVS_MAX_TRADE_LEN - 1u) && n < (uint16_t)(sizeof(preTradeStr) - 1u) && ps[n] != '\0') {
preTradeStr[n] = ps[n];
n++;
}
preTradeStr[n] = '\0';
}
tcp_history_format_comm_time(&order.StartChargeTime, startTimeStr, sizeof(startTimeStr));
tcp_history_format_comm_time(&order.EndChargeTime, stopTimeStr, sizeof(stopTimeStr));
tcp_history_sanitize_commas(userId);
tcp_history_sanitize_commas(stopCmd);
tcp_history_sanitize_commas(tradeNoStr);
tcp_history_sanitize_commas(preTradeStr);
tcp_history_build_stop_reason_text(&order, stopReasonText, sizeof(stopReasonText));
tcp_history_sanitize_commas(stopReasonText);
char usedMoneyStr[64] = {0};
char usedEnergyStr[64] = {0};
snprintf(usedMoneyStr, sizeof(usedMoneyStr), "%u.%04u",
(unsigned int)(order.u32_used_money / 10000u),
(unsigned int)(order.u32_used_money % 10000u));
snprintf(usedEnergyStr, sizeof(usedEnergyStr), "%u.%04u",
(unsigned int)(order.u32_used_energy / 10000u),
(unsigned int)(order.u32_used_energy % 10000u));
/*
* content 逗号分隔字段顺序(上位机表格不单独展示“停止时间”,与 JSON time 同源):
* 订单索引,枪号,开始时间,停止时间,充电时长(分钟),开始SOC,停止SOC,用电量,金额,
* 用户ID,设备流水号,平台流水号,停机原因(仅e_stop_reason首条),停机码(u8_stop_cmd四字节HEX共8位)
*/
char field_content[HISTORY_CONTENT_STR_SIZE] = {0};
snprintf(field_content, sizeof(field_content),
"%u,%u,%s,%s,%u,%u,%u,%s,%s,%s,%s,%s,%s,%s",
(unsigned int)order.u32_index,
(unsigned int)order.u8_gunNo,
startTimeStr,
stopTimeStr,
(unsigned int)order.u16_ChargedTime,
(unsigned int)order.u8_startSoc,
(unsigned int)order.u8_endSoc,
usedEnergyStr,
usedMoneyStr,
userId,
tradeNoStr,
preTradeStr,
stopReasonText,
stopCmd);
strncpy(content_buf[i], field_content, HISTORY_CONTENT_STR_SIZE - 1);
content_buf[i][HISTORY_CONTENT_STR_SIZE - 1] = '\0';
filledCount++;
}
} else if (strcmp(historyType, "card") == 0) {
uint32_t cnt = 0u;
CardInfo *card_list = NULL;
if (totalCount == 0u) {
return 0u;
}
card_list = (CardInfo *)pvPortMalloc(sizeof(CardInfo) * (size_t)totalCount);
if (card_list == NULL) {
return 0u;
}
memset(card_list, 0, sizeof(CardInfo) * (size_t)totalCount);
if (card_get_all(card_list, &cnt, totalCount) != CARD_STATUS_OK) {
vPortFree(card_list);
return 0u;
}
uint16_t u16_cnt = (uint16_t)cnt;
if (u16_cnt < totalCount) {
totalCount = u16_cnt;
*outTotalCount = totalCount;
}
for (uint16_t i = 0u; i < wantedCount; i++) {
uint16_t ascPos = (uint16_t)(totalCount - 1u - (uint16_t)(startIndex + i));
if (ascPos >= u16_cnt) {
break;
}
char cardNum[40] = {0};
strncpy(cardNum, card_list[ascPos].card_num, 31);
cardNum[31] = '\0';
tcp_history_sanitize_commas(cardNum);
char field_content[HISTORY_CONTENT_STR_SIZE] = {0};
snprintf(field_content, sizeof(field_content),
"%s,%u,%u",
cardNum,
(unsigned int)card_list[ascPos].balance,
(unsigned int)card_list[ascPos].status);
strncpy(content_buf[i], field_content, HISTORY_CONTENT_STR_SIZE - 1);
content_buf[i][HISTORY_CONTENT_STR_SIZE - 1] = '\0';
filledCount++;
}
vPortFree(card_list);
card_list = NULL;
}
return filledCount;
}
int tcp_history_clear_records(const char *historyType, char *outMessage, uint32_t outMessageSize)
{
if (historyType == NULL) {
if (outMessage && outMessageSize > 0u) {
strncpy(outMessage, "未知historyType", outMessageSize - 1u);
outMessage[outMessageSize - 1u] = '\0';
}
return -1;
}
if (strcmp(historyType, "fault") == 0) {
v_fault_clear_his_fault();
if (outMessage && outMessageSize > 0u) {
strncpy(outMessage, "Fault records cleared", outMessageSize - 1u);
outMessage[outMessageSize - 1u] = '\0';
}
return 0;
}
if (strcmp(historyType, "charging") == 0) {
v_chg_order_clear_all();
if (outMessage && outMessageSize > 0u) {
strncpy(outMessage, "Charge records cleared", outMessageSize - 1u);
outMessage[outMessageSize - 1u] = '\0';
}
return 0;
}
if (strcmp(historyType, "card") == 0) {
CardStatus st = card_clear_all();
if (st == CARD_STATUS_OK) {
if (outMessage && outMessageSize > 0u) {
strncpy(outMessage, "卡信息已清除", outMessageSize - 1u);
outMessage[outMessageSize - 1u] = '\0';
}
return 0;
}
if (outMessage && outMessageSize > 0u) {
snprintf(outMessage, outMessageSize, "Card info clear failed: %d", (int)st);
}
return -1;
}
if (outMessage && outMessageSize > 0u) {
strncpy(outMessage, "未知historyType", outMessageSize - 1u);
outMessage[outMessageSize - 1u] = '\0';
}
return -1;
}
#endif
+105
View File
@@ -0,0 +1,105 @@
/**
******************************************************************************
* @file system_data.h
* @brief 系统数据交互模块头文件
*
* 该模块负责系统信息、充电信息获取、充电控制等功能
* 为参数配置、文件获取等功能预留接口
******************************************************************************
*/
#ifndef __SYSTEM_DATA_H
#define __SYSTEM_DATA_H
#include <stdint.h>
#include "publicdata/public_define.h"
#include "tcp_ser/system_paracfg.h"
#include "tcp_ser/tcp_protocol.h"
#ifdef __cplusplus
extern "C" {
#endif
/* 函数声明 */
const char* param_type_to_string(param_data_type_t type);
int param_get_value_as_string(const param_map_item_t *item, char *buffer, uint32_t size);
uint32_t param_get_enable_bit_value(uint16_t param_id, uint8_t op_flag, uint32_t value);
uint32_t param_get_netcomm_bit_value(uint16_t param_id, uint8_t op_flag, uint32_t value);
int param_set_value_from_string(const param_map_item_t *item, const char *value_str);
/* 参数映射表获取函数 */
const param_map_item_t* param_get_basic_map(void);
uint16_t param_get_basic_map_size(void);
const param_map_item_t* param_get_network_map(void);
uint16_t param_get_network_map_size(void);
const param_map_item_t* param_get_meter_map(void);
uint16_t param_get_meter_map_size(void);
const param_map_item_t* param_get_enable_map(void);
uint16_t param_get_enable_map_size(void);
const param_map_item_t* param_get_fee_map(void);
uint16_t param_get_fee_map_size(void);
const param_map_item_t* param_get_sdk_map(void);
uint16_t param_get_sdk_map_size(void);
const param_map_item_t* param_get_qrcode_map(void);
uint16_t param_get_qrcode_map_size(void);
/* 故障信息获取函数 */
void tcp_get_system_fault_data(tcp_fault_info_response_t *response);
int tcp_process_custom_data(const tcp_custom_data_t *request, tcp_custom_data_t *response);
/* ================= 历史记录(HistoryOperations ================= */
#define HISTORY_TIME_STR_SIZE 32
#define HISTORY_CONTENT_STR_SIZE 640
#define HISTORY_QUERY_MAX_RECORDS 20
/**
* @brief 历史查询一包记录的传输缓冲区(体积大,勿在栈上分配;用 pvPortMalloc 申请并在用后 vPortFree
*/
typedef struct tcp_history_query_xfer {
char time_buf[HISTORY_QUERY_MAX_RECORDS][HISTORY_TIME_STR_SIZE];
char content_buf[HISTORY_QUERY_MAX_RECORDS][HISTORY_CONTENT_STR_SIZE];
} tcp_history_query_xfer_t;
/**
* @brief 获取历史记录总数
* @param historyType "fault"|"charging"|"card"
* @return 总条数
*/
uint16_t tcp_history_get_total_count(const char *historyType);
/**
* @brief 根据 startIndex(0=最新) 构建单包记录内容
*
* @param historyType "fault"|"charging"|"card"
* @param startIndex 距离最新的偏移
* @param fetchCount 请求条数(实际会受 HISTORY_QUERY_MAX_RECORDS 与设备实际数量约束)
* @param time_buf [HISTORY_QUERY_MAX_RECORDS][HISTORY_TIME_STR_SIZE] fault/charging填充
* @param content_buf [HISTORY_QUERY_MAX_RECORDS][HISTORY_CONTENT_STR_SIZE] 三种类型均填充
* @param outIncludeTime 输出:1=返回time字段,0=不返回time字段
* @param outTotalCount 输出:设备该类型总条数
* @return 返回实际填充的记录条数(<=fetchCount
*/
uint16_t tcp_history_build_records(const char *historyType,
uint16_t startIndex,
uint16_t fetchCount,
char (*time_buf)[HISTORY_TIME_STR_SIZE],
char (*content_buf)[HISTORY_CONTENT_STR_SIZE],
uint8_t *outIncludeTime,
uint16_t *outTotalCount);
/**
* @brief 清除历史记录(全部)
* @param historyType "fault"|"charging"|"card"
* @param outMessage 输出提示信息(可选)
* @param outMessageSize outMessage大小(可选)
* @return 0=成功,非0=失败
*/
int tcp_history_clear_records(const char *historyType, char *outMessage, uint32_t outMessageSize);
#ifdef __cplusplus
}
#endif
#endif /* __SYSTEM_DATA_H */
File diff suppressed because it is too large Load Diff
+263
View File
@@ -0,0 +1,263 @@
#ifndef SYSTEM_PARACFG_H
#define SYSTEM_PARACFG_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include <stdint.h>
#include "publicdata/public_define.h"
#include "publicdata/publicdata.h"
/*------ Exported macro(宏定义) -------------------------*/
/*------ Exported struct(结构体定义) --------------------*/
/* 参数类型枚举(节省内存) */
typedef enum {
PARAM_TYPE_STRING = 0, /* 字符串类型 */
PARAM_TYPE_UINT8 = 1, /* uint8类型 */
PARAM_TYPE_UINT16 = 2, /* uint16类型 */
PARAM_TYPE_UINT32 = 3, /* uint32类型 */
PARAM_TYPE_FLOAT = 4, /* 浮点类型 */
PARAM_TYPE_INT = 5, /* 整数类型 */
PARAM_TYPE_ARRAY = 6, /* 数组类型(字节数组) */
PARAM_TYPE_BIT = 7, /* 位字段类型 */
} param_data_type_t;
/* 基本参数枚举 */
typedef enum {
PARAM_BASIC_GUN_COUNT, /* 枪数量 */
PARAM_BASIC_RATE_POW, /* 额定功率 */
PARAM_BASIC_MAX_VOLT, /* 最大电压 */
PARAM_BASIC_MAX_CUR, /* 最大电流 */
PARAM_BASIC_PWD, /* 维护密码 */
PARAM_BASIC_PILE_TYPE, /* 桩类型 */
PARAM_BASIC_TERMINAL_0, /* 终端地址0 */
PARAM_BASIC_TERMINAL_1, /* 终端地址1 */
PARAM_BASIC_LED_TYPE, /* 灯板类型 */
PARAM_BASIC_TIME_ZONE, /* 时区设置*/
PARAM_BASIC_THRESHOLD_GUN_TEMP, /* 枪温阈值 */
PARAM_BASIC_SOC_LIMIT, /* SOC阈值 */
PARAM_BASIC_MIN_VOLT, /* 最小电压 */
PARAM_BASIC_TRICKLE_LIMIT, /* 涓流充电停机阈值 */
PARAM_BASIC_MDU_NUM, /* 充电模块数量 */
PARAM_BASIC_MDU_TYPE, /* 模块类型 */
PARAM_BASIC_MDU_TEMP_FLAG, /* 模块温度获取使能 */
PARAM_BASIC_MDU_RATED_POW, /* 模块额定功率 */
PARAM_BASIC_MDU_MAX_VOL, /* 模块最大电压 */
PARAM_BASIC_MDU_MAX_CUR, /* 模块最大电流 */
PARAM_BASIC_GUN_TYPE_0, /* 枪1类型 0欧标枪 1国标枪 */
PARAM_BASIC_GUN_TYPE_1, /* 枪2类型 0欧标枪 1国标枪 */
PARAM_BASIC_GUN_POWER_LIMIT_0, /* 枪1充电功率限制 0表示不限制 单位kW */
PARAM_BASIC_GUN_POWER_LIMIT_1, /* 枪2充电功率限制 0表示不限制 单位kW */
PARAM_BASIC_PILE_POWER_LIMIT, /* 整桩功率限制 0表示不限制 单位kW */
PARAM_BASIC_COUNT /* 基本参数数量 */
} param_basic_enum_t;
/* 网络参数枚举 */
typedef enum {
PARAM_NETWORK_ETH_IP = 0, /* IP地址 */
PARAM_NETWORK_ETH_GATE, /* 网关 */
PARAM_NETWORK_ETH_MASK, /* 子网掩码 */
PARAM_NETWORK_ETH_MAC, /* MAC地址 */
PARAM_NETWORK_SERVER_A_DOMAIN, /* 服务器A域名 */
PARAM_NETWORK_SERVER_B_DOMAIN, /* 服务器B域名 */
PARAM_NETWORK_PLAT_A_PILENUM, /* 平台1桩编号 */
PARAM_NETWORK_PLAT_B_PILENUM, /* 平台2桩编号 */
PARAM_NETWORK_NET_COMM_PARA, /* 网络连接参数(整体)- 保留用于兼容性 */
/* U_NET_COMM_PARA 位字段参数 */
PARAM_NETWORK_PROGRAM_TYPE, /* 程序类型(2位) */
PARAM_NETWORK_NET_A_TYPE, /* 平台1联网方式(2位) */
PARAM_NETWORK_NET_B_TYPE, /* 平台2联网方式(2位) */
PARAM_NETWORK_PLAT_A_EN, /* 平台1使能(2位) */
PARAM_NETWORK_PLAT_B_EN, /* 平台2使能(2位) */
PARAM_NETWORK_PLAT_A_TYPE, /* 平台1类型(4位) */
PARAM_NETWORK_PLAT_B_TYPE, /* 平台2类型(4位) */
PARAM_NETWORK_SIM_ANP_TYPE, /* SIM卡APN类型(2位) */
PARAM_NETWORK_COUNT /* 网络参数数量 */
} param_network_enum_t;
/* 电表参数枚举 */
typedef enum {
PARAM_METER_METER_NUM = 0, /* 电表数量 */
PARAM_METER_0_IN_GUN, /* 电表0归属枪号 */
PARAM_METER_0_HIGH_PREC, /* 电表0高精度支持 */
PARAM_METER_0_IS_AC, /* 电表0是否为交流表 */
PARAM_METER_0_ADDR, /* 电表0地址 */
PARAM_METER_1_IN_GUN, /* 电表1归属枪号 */
PARAM_METER_1_HIGH_PREC, /* 电表1高精度支持 */
PARAM_METER_1_IS_AC, /* 电表1是否为交流表 */
PARAM_METER_1_ADDR, /* 电表1地址 */
PARAM_METER_2_IN_GUN, /* 电表2归属枪号 */
PARAM_METER_2_HIGH_PREC, /* 电表2高精度支持 */
PARAM_METER_2_IS_AC, /* 电表2是否为交流表 */
PARAM_METER_2_ADDR, /* 电表2地址 */
PARAM_METER_3_IN_GUN, /* 电表3归属枪号 */
PARAM_METER_3_HIGH_PREC, /* 电表3高精度支持 */
PARAM_METER_3_IS_AC, /* 电表3是否为交流表 */
PARAM_METER_3_ADDR, /* 电表3地址 */
PARAM_METER_COUNT /* 电表参数数量 */
} param_meter_enum_t;
/* 使能参数枚举 */
typedef enum {
PARAM_ENABLE_DOOR_ENABLE = 0, /* 门禁使能(2位) */
PARAM_ENABLE_GUN_LOCK_ENA, /* 枪锁使能(2位) */
PARAM_ENABLE_GUN_LOCK_NEG, /* 枪锁信号取反(2位) */
PARAM_ENABLE_AC_CONTACT_ENA, /* 交流接触器使能(2位) */
PARAM_ENABLE_GUN_HOMING, /* 枪归位信号有无(2位) */
PARAM_ENABLE_GB_ENABLE, /* 国标使能(2位) */
PARAM_ENABLE_LCD_ENABLE, /* 液晶使能(2位) */
PARAM_ENABLE_AUX_POWER, /* 辅助电压返回状态使能(2位) */
PARAM_ENABLE_RELIEF_EN, /* 泄放使能(2位) */
PARAM_ENABLE_YE_LEN_EN, /* 液冷通讯使能(2位) */
PARAM_ENABLE_AB_COMM_EN, /* 主副班通讯使能(2位) */
PARAM_ENABLE_SCREEN_SAVER_EN, /* UI屏保显示使能(2位) */
PARAM_ENABLE_PLUG_EN, /* 即插即充使能(2位) */
PARAM_ENABLE_UI_ENGLISH_EN, /* UI界面英语使能(2位) */
PARAM_ENABLE_DC_CONTACT_EN, /* 直流接触器使能(2位) */
PARAM_ENABLE_GUN_FAN_EN, /* 枪风扇使能(2位) */
PARAM_ENABLE_MDU_ZERO_POW_EN, /* 零功控制使能(2位) */
PARAM_ENABLE_LED_GB_EN, /* LED国标使能(2位) */
PARAM_ENABLE_COUNT /* 使能参数数量 */
} param_enable_enum_t;
/* 费率参数枚举 */
typedef enum {
PARAM_FEE_MODEL_NO = 0, /* 计费模型编号 */
PARAM_FEE_FEE_CNT, /* 费率条数 */
PARAM_FEE_TYPE_0_RATE, /* 费率类型0费率 */
PARAM_FEE_TYPE_0_SERVICE, /* 费率类型0服务费 */
PARAM_FEE_TYPE_1_RATE, /* 费率类型1费率 */
PARAM_FEE_TYPE_1_SERVICE, /* 费率类型1服务费 */
PARAM_FEE_TYPE_2_RATE, /* 费率类型2费率 */
PARAM_FEE_TYPE_2_SERVICE, /* 费率类型2服务费 */
PARAM_FEE_TYPE_3_RATE, /* 费率类型3费率 */
PARAM_FEE_TYPE_3_SERVICE, /* 费率类型3服务费 */
PARAM_FEE_TYPE_4_RATE, /* 费率类型4费率 */
PARAM_FEE_TYPE_4_SERVICE, /* 费率类型4服务费 */
PARAM_FEE_TYPE_5_RATE, /* 费率类型5费率 */
PARAM_FEE_TYPE_5_SERVICE, /* 费率类型5服务费 */
PARAM_FEE_TYPE_6_RATE, /* 费率类型6费率 */
PARAM_FEE_TYPE_6_SERVICE, /* 费率类型6服务费 */
PARAM_FEE_TYPE_7_RATE, /* 费率类型7费率 */
PARAM_FEE_TYPE_7_SERVICE, /* 费率类型7服务费 */
PARAM_FEE_TYPE_8_RATE, /* 费率类型8费率 */
PARAM_FEE_TYPE_8_SERVICE, /* 费率类型8服务费 */
PARAM_FEE_TYPE_9_RATE, /* 费率类型9费率 */
PARAM_FEE_TYPE_9_SERVICE, /* 费率类型9服务费 */
PARAM_FEE_TYPE_10_RATE, /* 费率类型10费率 */
PARAM_FEE_TYPE_10_SERVICE, /* 费率类型10服务费 */
PARAM_FEE_TYPE_11_RATE, /* 费率类型11费率 */
PARAM_FEE_TYPE_11_SERVICE, /* 费率类型11服务费 */
PARAM_FEE_TYPE_12_RATE, /* 费率类型12费率 */
PARAM_FEE_TYPE_12_SERVICE, /* 费率类型12服务费 */
PARAM_FEE_TYPE_13_RATE, /* 费率类型13费率 */
PARAM_FEE_TYPE_13_SERVICE, /* 费率类型13服务费 */
PARAM_FEE_TYPE_14_RATE, /* 费率类型14费率 */
PARAM_FEE_TYPE_14_SERVICE, /* 费率类型14服务费 */
PARAM_FEE_TYPE_15_RATE, /* 费率类型15费率 */
PARAM_FEE_TYPE_15_SERVICE, /* 费率类型15服务费 */
PARAM_FEE_TYPE_16_RATE, /* 费率类型16费率 */
PARAM_FEE_TYPE_16_SERVICE, /* 费率类型16服务费 */
PARAM_FEE_TYPE_17_RATE, /* 费率类型17费率 */
PARAM_FEE_TYPE_17_SERVICE, /* 费率类型17服务费 */
PARAM_FEE_TYPE_18_RATE, /* 费率类型18费率 */
PARAM_FEE_TYPE_18_SERVICE, /* 费率类型18服务费 */
PARAM_FEE_TYPE_19_RATE, /* 费率类型19费率 */
PARAM_FEE_TYPE_19_SERVICE, /* 费率类型19服务费 */
PARAM_FEE_TYPE_20_RATE, /* 费率类型20费率 */
PARAM_FEE_TYPE_20_SERVICE, /* 费率类型20服务费 */
PARAM_FEE_TYPE_21_RATE, /* 费率类型21费率 */
PARAM_FEE_TYPE_21_SERVICE, /* 费率类型21服务费 */
PARAM_FEE_TYPE_22_RATE, /* 费率类型22费率 */
PARAM_FEE_TYPE_22_SERVICE, /* 费率类型22服务费 */
PARAM_FEE_TYPE_23_RATE, /* 费率类型23费率 */
PARAM_FEE_TYPE_23_SERVICE, /* 费率类型23服务费 */
PARAM_FEE_TYPE_24_RATE, /* 费率类型24费率 */
PARAM_FEE_TYPE_24_SERVICE, /* 费率类型24服务费 */
PARAM_FEE_TYPE_25_RATE, /* 费率类型25费率 */
PARAM_FEE_TYPE_25_SERVICE, /* 费率类型25服务费 */
PARAM_FEE_TYPE_26_RATE, /* 费率类型26费率 */
PARAM_FEE_TYPE_26_SERVICE, /* 费率类型26服务费 */
PARAM_FEE_TYPE_27_RATE, /* 费率类型27费率 */
PARAM_FEE_TYPE_27_SERVICE, /* 费率类型27服务费 */
PARAM_FEE_TYPE_28_RATE, /* 费率类型28费率 */
PARAM_FEE_TYPE_28_SERVICE, /* 费率类型28服务费 */
PARAM_FEE_TYPE_29_RATE, /* 费率类型29费率 */
PARAM_FEE_TYPE_29_SERVICE, /* 费率类型29服务费 */
PARAM_FEE_TYPE_1_24, /* 时段0费率类型 */
PARAM_FEE_TYPE_25_48, /* 时段0费率类型 */
PARAM_FEE_TYPE_49_72, /* 时段0费率类型 */
PARAM_FEE_TYPE_73_96, /* 时段0费率类型 */
PARAM_FEE_COUNT /* 费率参数数量 */
} param_fee_enum_t;
/* SDK参数枚举 */
typedef enum {
PARAM_SDK_PRODUCT_KEY = 0, /* 产品秘钥 */
PARAM_SDK_DEVICE_NAME, /* 资产码 */
PARAM_SDK_DEVICE_SECRET, /* 设备密钥 */
PARAM_SDK_DEVICE_REG_CODE, /* 注册码 */
PARAM_SDK_DEVICE_UID, /* 出厂编码 */
PARAM_SDK_FIRMWARE_VERSION, /* 固件版本信息 */
PARAM_SDK_COUNT /* SDK参数数量 */
} param_sdk_enum_t;
/* 二维码参数枚举 */
typedef enum {
PARAM_QRCODE_GUN1 = 0, /* 枪1二维码 */
PARAM_QRCODE_GUN2, /* 枪2二维码 */
PARAM_QRCODE_CUSTOM_VER, /* 自定义版本号 */
PARAM_QRCODE_APN_STR, /* 4G apn */
PARAM_QRCODE_OCPP_AUTH_KEY, /* OCPP鉴权密钥 */
PARAM_QRCODE_COUNT /* 二维码参数数量 */
} param_qrcode_enum_t;
/* 参数映射表项结构 */
typedef struct {
uint16_t param_id; /* 参数ID(枚举值) */
const char *param_name; /* 参数名称 */
param_data_type_t data_type; /* 参数数据类型 */
void *data_addr; /* 参数数据地址 */
uint16_t data_size; /* 参数数据大小 */
const char *decs; /* 参数描述(可为NULL */
} param_map_item_t;
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
#if TCP_DEBUG_EN
const param_map_item_t *param_get_basic_map(void);
uint16_t param_get_basic_map_size(void);
const param_map_item_t *param_get_network_map(void);
uint16_t param_get_network_map_size(void);
const param_map_item_t *param_get_meter_map(void);
uint16_t param_get_meter_map_size(void);
const param_map_item_t *param_get_enable_map(void);
uint16_t param_get_enable_map_size(void);
const param_map_item_t *param_get_fee_map(void);
uint16_t param_get_fee_map_size(void);
const param_map_item_t *param_get_sdk_map(void);
uint16_t param_get_sdk_map_size(void);
const param_map_item_t *param_get_qrcode_map(void);
uint16_t param_get_qrcode_map_size(void);
const char *param_type_to_string(param_data_type_t type);
uint32_t param_get_enable_bit_value(uint16_t param_id, uint8_t op_flag, uint32_t value);
uint32_t param_get_netcomm_bit_value(uint16_t param_id, uint8_t op_flag, uint32_t value);
int param_get_value_as_string(const param_map_item_t *item, char *buffer, uint32_t size);
int param_set_value_from_string(const param_map_item_t *item, const char *value_str);
const param_map_item_t *param_get_map_item_by_id(uint16_t param_id);
#endif /* TCP_DEBUG_EN */
#ifdef __cplusplus
}
#endif
#endif /* SYSTEM_PARACFG_H */
File diff suppressed because it is too large Load Diff
+489
View File
@@ -0,0 +1,489 @@
/**
******************************************************************************
* @file tcp_protocol.h
* @brief TCP通信协议解析头文件
******************************************************************************
*/
#ifndef __TCP_PROTOCOL_H
#define __TCP_PROTOCOL_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* 命令类型定义(字符串类型) */
#define CMD_HEARTBEAT "Heartbeat"
#define CMD_SYSTEM_INFO "SystemInfo"
#define CMD_GUN_INFO "GunInfo"
#define CMD_CHARGE_CONTROL "ChargeControl"
#define CMD_ALARM_NOTIFY "AlarmNotify"
#define CMD_STATUS_RESPONSE "StatusResponse"
#define CMD_PARAM_QUERY "ParamQuery"
#define CMD_PARAM_MODIFY "ParamModify"
#define CMD_FIRMWARE_UPGRADE "FirmwareUpgrade"
#define CMD_FAULT_INFO "FaultInfo"
#define CMD_CUSTOM_DATA "CustomData"
/* 网络状态定义 */
#define NETWORK_STATUS_OK 1 /* 网络正常 */
#define NETWORK_STATUS_ERROR 0 /* 网络异常 */
/* 充电状态定义 */
#define CHARGING_STATUS_IDLE 0 /* 空闲 */
#define CHARGING_STATUS_CONNECTED 1 /* 已连接 */
#define CHARGING_STATUS_STARTING 2 /* 启动中 */
#define CHARGING_STATUS_CHARGING 3 /* 充电中 */
#define CHARGING_STATUS_FINISHED 4 /* 充电完成 */
/* 枪连接状态定义 */
#define GUN_CONNECTION_DISCONNECTED 0 /* 未连接 */
#define GUN_CONNECTION_CONNECTED 1 /* 已连接 */
/* 充电控制动作定义 */
#define CHARGE_ACTION_STOP 0 /* 停止充电 */
#define CHARGE_ACTION_START 1 /* 启动充电 */
/* 固件升级子命令定义 */
#define FIRMWARE_SUBCMD_START "Start"
#define FIRMWARE_SUBCMD_DATA "Data"
#define FIRMWARE_SUBCMD_END "End"
#define FIRMWARE_SUBCMD_RESPONSE "Response"
/* 固件升级状态定义 */
#define FIRMWARE_STATUS_SUCCESS 0 /* 成功 */
#define FIRMWARE_STATUS_FAILED 1 /* 失败 */
#define FIRMWARE_STATUS_IN_PROGRESS 2 /* 进行中 */
#define FIRMWARE_STATUS_COMPLETED 3 /* 完成 */
/* 文件操作命令定义 */
#define CMD_FILE_OPERATIONS "FileOperations"
/* 文件操作子命令定义 */
#define FILE_OP_SUBCMD_QUERY "Query"
#define FILE_OP_SUBCMD_DELETE "Delete"
#define FILE_OP_SUBCMD_DOWNLOAD "Download"
#define FILE_OP_SUBCMD_UPLOAD "Upload"
/* 历史记录查询/清除命令定义 */
#define CMD_HISTORY_OPERATIONS "HistoryOperations"
#define HISTORY_OP_SUBCMD_QUERY "Query"
#define HISTORY_OP_SUBCMD_CLEAR "Clear"
/* 文件操作状态定义 */
#define FILE_OP_STATUS_SUCCESS 0 /* 成功 */
#define FILE_OP_STATUS_FAILED 1 /* 失败 */
#define FILE_OP_STATUS_IN_PROGRESS 2 /* 进行中 */
#define FILE_OP_STATUS_NOT_FOUND 3 /* 文件未找到 */
#define FILE_OP_STATUS_NO_SPACE 4 /* 空间不足 */
#define FILE_OP_STATUS_PERMISSION_DENIED 5 /* 权限不足 */
/* 文件类型定义 */
#define FILE_TYPE_REGULAR 0 /* 普通文件 */
#define FILE_TYPE_DIRECTORY 1 /* 目录 */
#define FILE_TYPE_SYSTEM 2 /* 系统文件 */
/* 文件信息结构 */
typedef struct {
char name[128]; /* 文件名 */
uint8_t type; /* 文件类型 */
uint32_t size; /* 文件大小(字节) */
char modified_time[32]; /* 修改时间 */
char created_time[32]; /* 创建时间 */
} tcp_file_info_t;
/* 文件查询请求结构 */
typedef struct {
char path[256]; /* 查询路径 */
uint8_t recursive; /* 是否递归查询(0:否, 1:是) */
} tcp_file_query_request_t;
/* 文件查询响应结构 */
typedef struct {
char path[256]; /* 查询路径 */
uint16_t file_count; /* 文件数量 */
uint16_t dir_count; /* 目录数量 */
uint32_t total_size; /* 总大小(字节) */
tcp_file_info_t *files; /* 文件信息数组(动态分配) */
} tcp_file_query_response_t;
/* 文件删除请求结构 */
typedef struct {
char path[256]; /* 文件路径 */
uint8_t force; /* 强制删除(0:否, 1:是) */
} tcp_file_delete_request_t;
/* 文件删除响应结构 */
typedef struct {
char path[256]; /* 文件路径 */
uint8_t status; /* 删除状态 */
char message[128]; /* 结果消息 */
} tcp_file_delete_response_t;
/* 文件下载请求结构 */
typedef struct {
char path[256]; /* 文件路径 */
uint32_t offset; /* 起始偏移(字节) */
uint32_t chunk_size; /* 分块大小(字节) */
} tcp_file_download_request_t;
/* 文件下载响应结构 */
typedef struct {
char path[256]; /* 文件路径 */
uint32_t file_size; /* 文件总大小 */
uint32_t offset; /* 当前偏移 */
uint32_t chunk_size; /* 分块大小 */
uint32_t data_len; /* 数据长度 */
uint8_t *data; /* 数据内容(Base64编码) */
uint8_t is_last_chunk; /* 是否为最后一块(0:否, 1:是) */
} tcp_file_download_response_t;
/* 文件上传请求结构 */
typedef struct {
char path[256]; /* 文件路径 */
uint32_t file_size; /* 文件总大小 */
uint32_t chunk_size; /* 分块大小 */
uint32_t offset; /* 当前偏移 */
uint32_t data_len; /* 数据长度 */
uint8_t *data; /* 数据内容(Base64编码) */
uint8_t is_last_chunk; /* 是否为最后一块(0:否, 1:是) */
} tcp_file_upload_request_t;
/* 文件上传响应结构 */
typedef struct {
char path[256]; /* 文件路径 */
uint32_t received_size; /* 已接收大小 */
uint32_t total_size; /* 文件总大小 */
uint8_t status; /* 上传状态 */
char message[128]; /* 结果消息 */
} tcp_file_upload_response_t;
/* 文件操作通用数据结构 */
typedef struct {
char subCommand[32]; /* 子命令: Query, Delete, Download, Upload */
union {
tcp_file_query_request_t query; /* 查询请求 */
tcp_file_delete_request_t delete; /* 删除请求 */
tcp_file_download_request_t download; /* 下载请求 */
tcp_file_upload_request_t upload; /* 上传请求 */
} request;
union {
tcp_file_query_response_t query; /* 查询响应 */
tcp_file_delete_response_t delete; /* 删除响应 */
tcp_file_download_response_t download; /* 下载响应 */
tcp_file_upload_response_t upload; /* 上传响应 */
} response;
} tcp_file_operation_data_t;
/* 心跳消息结构 */
typedef struct {
uint32_t sequence; /* 序列号 */
char source[32]; /* 来源 */
} tcp_heartbeat_data_t;
/* 系统信息结构 */
typedef struct {
char systemModel[32]; /* 系统型号 */
uint8_t gunCount; /* 枪数量 */
float devicePower; /* 设备功率 */
uint8_t networkStatus; /* 网络状态 */
uint8_t faultCount; /* 故障数量 */
char version[32]; /* 版本号 */
} tcp_system_info_data_t;
/* 充电枪数据结构 */
typedef struct {
uint8_t gunNumber; /* 枪编号 (1-2) */
uint8_t chargingStatus; /* 充电状态 */
uint8_t gunConnectionStatus; /* 枪连接状态 */
uint16_t charge_time; /* 充电时间 min*/
float demandVoltage; /* 需求电压 */
float demandCurrent; /* 需求电流 */
float actualVoltage; /* 实际电压 */
float actualCurrent; /* 实际电流 */
float energy; /* 充电能量 (kWh) */
float cost; /* 充电费用 */
float batterySOC; /* 电池SOC (%) */
char userId[32]; /* 用户ID */
char orderNumber[32]; /* 订单号 */
} tcp_gun_data_t;
/* 充电控制结构 */
typedef struct {
uint8_t gunNumber; /* 枪编号 (1-2) */
uint8_t action; /* 动作 (0:停止, 1:启动) */
} tcp_charge_control_t;
/* 参数类型定义 */
#define PARAM_TYPE_BASIC "BasicParameters"
#define PARAM_TYPE_NETWORK "NetworkParameters"
#define PARAM_TYPE_METER "MeterParameters"
#define PARAM_TYPE_ENABLE "EnableParameters"
#define PARAM_TYPE_FEE "FeeParameters"
#define PARAM_TYPE_SDK "SDKParameters"
#define PARAM_TYPE_QRCODE "QRCodeParameters"
/* 参数项结构 */
typedef struct {
char name[32]; /* 参数名称 */
char type[16]; /* 参数类型 */
char value[64]; /* 参数值(字符串形式) */
char decs[64]; /* 参数描述(可为空) */
} tcp_param_item_t;
/* 参数查询请求结构 */
typedef struct {
char type[32]; /* 参数类型 */
} tcp_param_query_request_t;
/* 参数查询响应结构 */
typedef struct {
char type[32]; /* 参数类型 */
uint16_t count; /* 参数数量 */
tcp_param_item_t *params; /* 参数数组(动态分配) */
} tcp_param_query_response_t;
/* 参数修改请求结构 */
typedef struct {
char type[32]; /* 参数类型 */
uint16_t count; /* 参数数量 */
tcp_param_item_t *params; /* 参数数组(动态分配) */
} tcp_param_modify_request_t;
/* 参数修改响应结构 */
typedef struct {
char type[32]; /* 参数类型 */
uint16_t received; /* 接收到的参数数量 */
uint16_t processed; /* 已处理的参数数量 */
uint16_t success; /* 成功的参数数量 */
uint16_t failed; /* 失败的参数数量 */
} tcp_param_modify_response_t;
/* 固件升级数据结构 */
typedef struct {
char subCommand[32]; /* 子命令: Start, Data, End, Response */
union {
struct {
uint32_t fileSize; /* 文件大小(字节) */
uint32_t totalPackets; /* 总包数 */
uint32_t packetSize; /* 包大小(字节) */
char firmwareVersion[32]; /* 固件版本 */
} start;
struct {
uint32_t packetIndex; /* 包索引 */
uint32_t totalPackets; /* 总包数 */
char *data; /* 数据(Base64编码),动态分配 */
uint32_t data_len; /* 数据长度 */
uint32_t crc; /* CRC校验值 */
} data;
struct {
uint8_t status; /* 状态 */
char errorMessage[128]; /* 错误信息 */
} end;
struct {
uint8_t response; /* 响应码 */
int packetIndex; /* 包索引 */
char message[64]; /* 消息 */
} response;
} u;
} tcp_firmware_upgrade_data_t;
/* CustomData数据结构 */
typedef struct {
char functionField[32]; /* 功能字段 */
char functionContent[32]; /* 功能内容 */
char functionParams[128]; /* 功能参数 */
} tcp_custom_data_t;
/* 故障信息数据结构 */
typedef struct {
char faultCode[16]; /* 故障代码(可选),如"F001",为空表示查询所有故障 */
} tcp_fault_info_request_t;
/* 历史记录操作请求结构 */
typedef struct {
char historyType[16]; /* fault|charging|card */
char subCommand[32]; /* Query|Clear */
uint32_t queryId; /* 一次查询会话标识 */
uint16_t startIndex; /* 距离最新的偏移:0=最新 */
uint16_t fetchCount; /* 希望获取的条数(服务端会裁剪/按字节预算修正) */
} tcp_history_operations_data_t;
/* 单个故障信息结构 */
typedef struct {
char location[32]; /* 故障位置,如"充电枪1" */
char code[16]; /* 故障代码,如"F001" */
char time[32]; /* 故障时间,格式"YYYY-MM-DD HH:MM:SS" */
char description[128]; /* 故障描述,如"过流保护触发" */
} tcp_fault_item_t;
/* 故障信息响应结构 */
typedef struct {
uint8_t status; /* 状态码,0表示成功 */
uint16_t faultCount; /* 故障数量 */
tcp_fault_item_t *faults; /* 故障信息数组(动态分配) */
} tcp_fault_info_response_t;
/* 通用消息结构 */
typedef struct {
char version[16]; /* 协议版本 */
char timestamp[32]; /* 时间戳 */
char command[32]; /* 命令类型(字符串) */
union {
tcp_heartbeat_data_t heartbeat; /* 心跳数据 */
tcp_system_info_data_t systemInfo; /* 系统信息数据 */
tcp_gun_data_t gunData; /* 充电枪数据 */
tcp_charge_control_t chargeControl; /* 充电控制数据 */
tcp_param_query_request_t paramQuery; /* 参数查询请求 */
tcp_param_modify_request_t paramModify; /* 参数修改请求 */
tcp_firmware_upgrade_data_t firmwareUpgrade; /* 固件升级数据 */
tcp_file_operation_data_t fileOperation; /* 文件操作数据 */
tcp_fault_info_request_t faultInfo; /* 故障信息请求 */
tcp_history_operations_data_t historyOperations; /* 历史记录查询/清除 */
tcp_custom_data_t customData; /* CustomData数据 */
} data;
} tcp_message_t;
/* 函数声明 */
/**
* @brief 生成当前时间戳字符串
* @param buffer 缓冲区
* @param size 缓冲区大小
* @retval 0成功,-1失败
*/
int tcp_generate_timestamp(char *buffer, uint32_t size);
/**
* @brief 生成心跳消息JSON
* @param sequence 序列号
* @param source 来源字符串
* @param buffer 输出缓冲区
* @param size 缓冲区大小
* @retval JSON字符串长度,-1表示失败
*/
int tcp_generate_heartbeat_json(uint32_t sequence, const char *source, char *buffer, uint32_t size);
/**
* @brief 生成系统信息消息JSON
* @param systemInfo 系统信息结构体指针
* @param buffer 输出缓冲区
* @param size 缓冲区大小
* @retval JSON字符串长度,-1表示失败
*/
int tcp_generate_system_info_json(const tcp_system_info_data_t *systemInfo, char *buffer, uint32_t size);
/**
* @brief 生成充电枪数据消息JSON
* @param gunData 充电枪数据结构体指针
* @param buffer 输出缓冲区
* @param size 缓冲区大小
* @retval JSON字符串长度,-1表示失败
*/
int tcp_generate_gun_data_json(const tcp_gun_data_t *gunData, char *buffer, uint32_t size);
/**
* @brief 解析接收到的JSON消息
* @param json JSON字符串
* @param message 输出消息结构体
* @retval 0成功,-1失败
*/
int tcp_parse_json_message(const char *json, tcp_message_t *message);
/**
* @brief 释放消息结构体中分配的内存
* @param message 消息结构体指针
*/
void tcp_free_message(tcp_message_t *message);
/**
* @brief 处理接收到的消息
* @param message 消息结构体指针
* @retval 0成功,-1失败
*/
int tcp_process_received_message(const tcp_message_t *message);
/**
* @brief 获取默认系统信息
* @param systemInfo 输出系统信息结构体
*/
void tcp_get_default_system_info(tcp_system_info_data_t *systemInfo);
/**
* @brief 获取默认充电枪数据
* @param gunData 输出充电枪数据结构体
* @param gunNumber 枪编号 (1-2)
*/
void tcp_get_default_gun_data(tcp_gun_data_t *gunData, uint8_t gunNumber);
int system_data_stop_charge(uint8_t gunNumber, char *message);
int system_data_start_charge(uint8_t gunNumber, const char *userId,
const char *orderNumber, char *message);
/* 参数查询相关函数 */
int tcp_generate_param_query_response(const tcp_param_query_response_t *response, char *buffer, uint32_t size);
int tcp_generate_param_modify_response(const tcp_param_modify_response_t *response, char *buffer, uint32_t size);
int tcp_process_param_query(const tcp_param_query_request_t *request, tcp_param_query_response_t *response);
int tcp_process_param_modify(const tcp_param_modify_request_t *request, tcp_param_modify_response_t *response);
/* 固件升级相关函数 */
void tcp_firmware_upgrade_log_io(const char *dir, const char *data, uint32_t len);
void tcp_firmware_upgrade_log_buf(const char *dir, const uint8_t *data, uint32_t len);
void tcp_firmware_upgrade_log_meta(const char *tag, uint32_t a, uint32_t b, uint32_t c, uint32_t d);
int tcp_generate_firmware_upgrade_json(const tcp_firmware_upgrade_data_t *firmwareUpgrade, const char *subCommand, char *buffer, uint32_t size);
int tcp_process_firmware_upgrade(const tcp_firmware_upgrade_data_t *firmwareUpgrade);
/* 文件操作相关函数 */
int tcp_generate_file_query_response(const tcp_file_query_response_t *response, char *buffer, uint32_t size);
int tcp_generate_file_delete_response(const tcp_file_delete_response_t *response, char *buffer, uint32_t size);
int tcp_generate_file_download_response(const tcp_file_download_response_t *response, char *buffer, uint32_t size);
int tcp_generate_file_upload_response(const tcp_file_upload_response_t *response, char *buffer, uint32_t size);
int tcp_process_file_operation(const tcp_file_operation_data_t *fileOperation);
/* CustomData相关函数 */
int tcp_generate_custom_data_json(const tcp_custom_data_t *customData, char *buffer, uint32_t size);
/* TCP调试打印宏定义 */
#ifndef TCP_DEBUG_PRINT
#define TCP_DEBUG_PRINT 0 /* 默认使能调试打印 */
#endif
#if TCP_DEBUG_PRINT
#define TCP_PRINT(fmt, ...) printf("[TCP] " fmt, ##__VA_ARGS__)
#define TCP_PRINT_RAW(fmt, ...) printf(fmt, ##__VA_ARGS__)
#define TCP_PRINT_RAW_DATA(data, len) do { \
if ((data) != NULL && (len) > 0) { \
char *buf = (char *)pvPortMalloc((len) + 1); \
if (buf != NULL) { \
memcpy(buf, (data), (len)); \
buf[(len)] = '\0'; \
TCP_PRINT("RECV RAW DATA: %s\r\n", buf); \
vPortFree(buf); \
} \
} \
} while(0)
#define TCP_PRINT_RAW_SEND(data, len) do { \
if ((data) != NULL && (len) > 0) { \
char *buf = (char *)pvPortMalloc((len) + 1); \
if (buf != NULL) { \
memcpy(buf, (data), (len)); \
buf[(len)] = '\0'; \
TCP_PRINT("SEND RAW DATA: %s\r\n", buf); \
vPortFree(buf); \
} \
} \
} while(0)
#else
#define TCP_PRINT(fmt, ...) do {} while(0)
#define TCP_PRINT_RAW(fmt, ...) do {} while(0)
#define TCP_PRINT_RAW_DATA(data, len) do {} while(0)
#define TCP_PRINT_RAW_SEND(data, len) do {} while(0)
#endif
#ifdef __cplusplus
}
#endif
#endif /* __TCP_PROTOCOL_H */
+105
View File
@@ -0,0 +1,105 @@
#include "tcp_ser/tcp_ser_task.h"
#include "app_init/app_init.h"
#include "app_init/task_lock.h"
#include "mylog/mylog.h"
#include "publicdata/publicdata.h"
#include "tcp_ser/tcp_server.h"
#include "debug_link.h"
#include "wdt_task/wdt_task.h"
#if TCP_DEBUG_EN
#define TCPSER_TASK_ID (TASK_ID_TcpSer)
#define TCPSER_SLEEP_TIME_MS() (MY_GET_SLEEP_TIME(TCPSER_TASK_ID))
#define TCPSER_LOCK_INIT() (u8_task_lock_ctrl(TCPSER_TASK_ID, TASK_LOCK_OP_INIT))
#define TCPSER_LOCK_TAKE() (u8_task_lock_ctrl(TCPSER_TASK_ID, TASK_LOCK_OP_TAKE))
#define TCPSER_LOCK_GIVE() (u8_task_lock_ctrl(TCPSER_TASK_ID, TASK_LOCK_OP_GIVE))
#define TCPSER_FEED_DOG() (v_wdt_setFlag(TCPSER_TASK_ID))
#endif
#define TCPSER_LOG(fmt, ...) MYLOG_MSG(TCPSER_TASK_ID, (fmt), ##__VA_ARGS__)
#if TCP_DEBUG_EN && BLE_DEBUG_EN
#include "ble_link/fc41d_ble.h"
#else
#include "sub_comm/sub_comm_task.h"
#endif
static const char *tcp_ser_link_name(void)
{
#if BLE_DEBUG_EN
return COMM_LINK_IS_BLE(comm_link_get_type()) ? "BLE" : "TCP";
#else
return "TCP";
#endif
}
/**
* @brief tcpSer task:协议/业务共用 tcp_serverDIP1 选 TCP 或 BLE 物理链路。
*/
void v_tcp_ser_task(void *argument)
{
#if TCP_DEBUG_EN
uint16_t sleep_time_ms;
uint32_t last_send_time;
uint32_t current_time;
uint32_t last_start_try_time;
uint8_t server_started;
(void)argument;
(void)TCPSER_LOCK_INIT();
sleep_time_ms = TCPSER_SLEEP_TIME_MS();
last_send_time = get_current_seconds();
last_start_try_time = 0U;
server_started = 0U;
#if BLE_DEBUG_EN
(void)ble_link_wait_dip_ready(3000U);
comm_link_set_type(comm_link_type_from_dip());
#else
comm_link_set_type(COMM_LINK_TCP);
if (u8_sub_comm_yx_get(E_SUB_DIP_1) != 0U) {
TCPSER_LOG("DIP1=1 but BLE_DEBUG_EN=0, force TCP link");
}
#endif
TCPSER_LOG("debug link=%s (BLE_DEBUG_EN=%d)", tcp_ser_link_name(), BLE_DEBUG_EN);
for (;;) {
TCPSER_FEED_DOG();
current_time = get_current_seconds();
if (server_started == 0U) {
if ((last_start_try_time == 0U) ||
(u32_safe_seconds_since(last_start_try_time) >= 2U)) {
if (tcp_server_start() == 0) {
server_started = 1U;
TCPSER_LOG("debug server start ok, link=%s", tcp_ser_link_name());
} else {
TCPSER_LOG("debug server start fail, retry in 2s");
}
last_start_try_time = current_time;
}
} else {
tcp_server_process();
}
if ((TASK_RUN_PRINT_TIME != 0U) &&
(u32_safe_seconds_since(last_send_time) >= TASK_RUN_PRINT_TIME)) {
if (TCPSER_LOCK_TAKE() == 1U) {
TCPSER_LOG("tcp ser running, link=%s sleep=%ums",
tcp_ser_link_name(), sleep_time_ms);
(void)TCPSER_LOCK_GIVE();
}
last_send_time = current_time;
}
{
mSleep(sleep_time_ms);
}
}
#else
(void)argument;
for (;;) {
mSleep(1000U);
}
#endif
}
+7
View File
@@ -0,0 +1,7 @@
#ifndef TCP_SER_TASK_H
#define TCP_SER_TASK_H
void v_tcp_ser_task(void *argument);
#endif
File diff suppressed because it is too large Load Diff
+77
View File
@@ -0,0 +1,77 @@
/**
******************************************************************************
* @file tcp_server.h
* @brief TCP调试服务器头文件
******************************************************************************
*/
#ifndef __TCP_SERVER_H
#define __TCP_SERVER_H
#include "debug_link.h"
#include "lwip/err.h"
#include "lwip/tcp.h"
#ifdef __cplusplus
extern "C" {
#endif
/* 消息类型枚举 */
typedef enum {
TCP_MSG_HEARTBEAT = 1, /* 心跳消息 */
TCP_MSG_SYSTEM_INFO = 2, /* 系统信息 */
TCP_MSG_GUN_DATA = 3, /* 充电枪数据 */
/* 可以在此添加其他消息类型 */
} tcp_msg_type_t;
/* 命令类型枚举(用于接收解析) */
typedef enum {
TCP_CMD_HEARTBEAT = 1, /* 心跳 */
TCP_CMD_SYSTEM_INFO = 2, /* 系统信息 */
TCP_CMD_GUN_DATA = 3, /* 充电枪数据(服务器发送) */
TCP_CMD_CHARGE_CONTROL = 4, /* 充电启停控制(客户端发送) */
} tcp_cmd_type_t;
/* 服务器状态结构体 */
typedef struct {
struct tcp_pcb *server_pcb; /* 服务器PCB */
struct tcp_pcb *client_pcb; /* 客户端PCB(仅支持一个) */
uint8_t is_client_connected; /* 客户端连接状态 */
/* 协议相关状态 */
uint32_t heartbeat_sequence; /* 心跳序列号 */
uint8_t system_info_sent; /* 系统信息是否已发送 */
/* 定时计数变量 */
uint32_t tick_counter; /* 全局tick计数器,每秒递增 */
/* 消息发送间隔(秒) */
uint32_t heartbeat_last_tick; /* 上次心跳发送时的tick */
uint32_t system_info_last_tick; /* 上次系统信息发送时的tick */
uint32_t gun_data_last_tick; /* 上次充电枪数据发送时的tick */
uint32_t last_heartbeat_received_tick; /* 上次收到心跳时的tick */
} tcp_server_state_t;
/* 函数声明 */
err_t tcp_server_start(void);
void tcp_server_process(void);
void tcp_server_stop(void);
void tcp_server_task(void *arg);
/* 协议相关函数 */
void tcp_server_send_message(tcp_msg_type_t msg_type);
void tcp_server_process_received_data(const char *data, uint16_t len);
/* 通用发送函数 */
err_t tcp_server_send_json(const char *json_data, uint16_t len);
int tcp_server_rx_upgrade_begin(void);
int tcp_server_rx_upgrade_end(void);
void tcp_server_rx_clear_pending(void);
void tcp_server_link_rx_drain(void);
#ifdef __cplusplus
}
#endif
#endif /* __TCP_SERVER_H */
+371
View File
@@ -0,0 +1,371 @@
# tcp_ser 模块说明
> 本文档描述 `CCU621_M` 工程 `app/tcp_ser` 目录的职责划分、**完整文件清单**、各源文件功能、TCP/BLE 双链路与命令处理流程。
> 返回主说明:[README](../../README.md)
**文档版本**:与当前工程树同步(含 BLE 调试、`debug_link` 链路层;**阶段1** 已合并 `comm_link`+Socket**阶段2** 已合并文件类业务;**不含** `fwbin_protocol` / `fwup_protocol`)。
## 快速跳转
- [1. 模块总览](#1-模块总览)
- [2. 目录与文件清单(完整)](#2-目录与文件清单完整)
- [3. Keil 工程源文件](#3-keil-工程源文件)
- [4. 各文件功能说明](#4-各文件功能说明)
- [5. 分层架构与调用关系](#5-分层架构与调用关系)
- [6. TCP / BLE 双链路](#6-tcp--ble-双链路)
- [7. 已支持命令与处理路径](#7-已支持命令与处理路径)
- [8. 关键流程](#8-关键流程)
- [9. 固件升级(FirmwareUpgrade](#9-固件升级firmwareupgrade)
- [10. GunInfo 数据规则](#10-guninfo-数据规则)
- [11. CustomData / HistoryOperations](#11-customdata--historyoperations)
- [12. 编译开关与运行条件](#12-编译开关与运行条件)
- [13. 维护注意事项](#13-维护注意事项)
- [14. 已移除 / 不在树内的文件](#14-已移除--不在树内的文件)
- [关联文档](#关联文档)
---
## 1. 模块总览
`tcp_ser` 是 CCU621 的 **上位机调试服务**模块:协议与业务在 `tcp_server` / `tcp_protocol` 中**共用**,物理链路通过 `comm_link`**以太网 TCP****蓝牙 BLEFC41D** 之间切换。
| 职责 | 说明 |
|---|---|
| 链路适配 | `comm_link`DIP1 选 TCP 或 BLE,对上统一 `receive/send/process` |
| TCP 服务 | 监听端口(默认 **9699**),单客户端 |
| BLE 服务 | FC41D GATT 非透传(fff1/fff2/fff3),广播名默认 `CCU621_Debug` |
| 周期上送 | 每 **2s** 推送 `SystemInfo` + 双枪 `GunInfo`(客户端发 `Heartbeat` 保活) |
| 命令处理 | JSON v2.0 解析、分发、组响应 |
| 业务解耦 | 协议层与充电控制、参数、文件、升级等分离 |
```
上位机 / 小程序
│ JSON(同一套 v2.0 协议)
tcp_server.c ← RX 缓冲、JSON 抽帧、周期上送、连接管理
├── debug_link.c ← 链路层(comm_link 分发 + TCP Socket + 会话上下文)
│ └── ble_link/fc41d_ble.c UART7 AT + GATTBLE 后端)
└── tcp_protocol.c ← JSON 解析 / 组包 / 命令路由
├── debug_files.c ← FileOperations + FirmwareUpgrade
├── system_data.c
└── system_paracfg.c
```
---
## 2. 目录与文件清单(完整)
当前 `app/tcp_ser/`**19 个文件**(含 `ble_link/` 子目录;`.c` 源文件 7 个):
```
app/tcp_ser/
├── tcp_ser_task.c # FreeRTOS 任务入口
├── tcp_ser_task.h
├── tcp_server.c # 调试服务主逻辑(经 comm_link 收发)
├── tcp_server.h
├── debug_link.c # 链路层:comm_link 分发 + lwIP Socket + 会话上下文
├── debug_link.h # 链路层对外 API 与类型
├── debug_files.c # 文件类业务:FileOperations + FirmwareUpgrade
├── debug_files.h # 文件类业务对外 API 与类型
├── tcp_protocol.c # JSON v2.0 协议
├── tcp_protocol.h
├── system_data.c # 运行时数据、充电控制、历史、CustomData
├── system_data.h
├── system_paracfg.c # 参数映射表
├── system_paracfg.h
├── ble_link/ # ── BLE 子目录(BLE_DEBUG_EN=1 时编入)
│ ├── fc41d_ble.c # FC41D 完整实现 + comm_link 后端
│ ├── fc41d_ble.h # 配置宏、对外 API
│ └── fc41d_ble_priv.h # BLE 私有上下文(与 tcp_link_ctx union
├── TCP_DEBUG_PROTOCOL.md # JSON 报文字段说明
└── tcp_ser模块说明.md # 本文档
```
| 子目录 | 说明 |
|---|---|
| `ble_link/` | 仅 FC41D 蓝牙;UART7 AT 辅助函数为 **本目录 static**,不扩展公共 `BSP/com/usart.c` |
---
## 3. Keil 工程源文件
`Project/GD32H759.uvprojx``tcp_ser` 相关 **.c** 源文件(按工程登记):
| 源文件 | 路径 |
|---|---|
| `tcp_ser_task.c` | `app/tcp_ser/` |
| `tcp_server.c` | `app/tcp_ser/` |
| `debug_link.c` | `app/tcp_ser/` |
| `fc41d_ble.c` | `app/tcp_ser/ble_link/` |
| `tcp_protocol.c` | `app/tcp_ser/` |
| `system_data.c` | `app/tcp_ser/` |
| `system_paracfg.c` | `app/tcp_ser/` |
| `debug_files.c` | `app/tcp_ser/` |
头文件通过 Include Path `..\app\tcp_ser` 及子目录引用,无需单独加入工程。
---
## 4. 各文件功能说明
### 4.1 任务与调度
| 文件 | 功能 |
|---|---|
| **`tcp_ser_task.c`** | `v_tcp_ser_task`:任务锁、看门狗;`BLE_DEBUG_EN``ble_link_wait_dip_ready()``comm_link_set_type(comm_link_type_from_dip())`;每 2s 重试 `tcp_server_start()`;循环 `tcp_server_process()`。 |
| **`tcp_ser_task.h`** | 声明 `v_tcp_ser_task`。 |
### 4.2 链路层(`debug_link`
| 文件 | 功能 |
|---|---|
| **`debug_link.c`** | 阶段1 合并实现,分三段:<br>• **会话上下文**:单例 `comm_link_ctx_t``session` + `backend` union<br>• **TCP Socket 后端**(本文件内 `static`):lwIP `init/accept/receive/send/close` 等<br>• **comm_link 分发**`comm_link_start/stop/process``comm_link_receive_data``comm_link_send_data` 等;按 `session.type` 转发到 TCP 或 `comm_link_ble_*` |
| **`debug_link.h`** | 链路层统一头:`COMM_LINK_TCP/BLE``comm_link_*` API、`comm_link_ctx_*``tcp_link_ctx_t``comm_link_backend_u` |
### 4.3 传输后端(BLE
| **`ble_link/fc41d_ble.c`** | FC41DAT 层、PE6 复位、BLE 状态机、GATT Notify 发送、`+QBLERECV` 解析;实现 `comm_link_ble_*`UART7 **固定 115200**`FC41D_UART_BAUD`),阻塞收包在 `fc41d_ble.c` 内 static 实现。 |
| **`ble_link/fc41d_ble.h`** | 硬件/AT 超时宏、`FC41D_BLE_NAME`、GATT UUID、`ble_link_is_bt_mode()``comm_link_ble_*` API。 |
| **`ble_link/fc41d_ble_priv.h`** | AT 缓冲、link/app RX 环、状态机枚举与 `fc41d_ble_ctx_t`。 |
### 4.4 服务主控层
| 文件 | 功能 |
|---|---|
| **`tcp_server.c`** | 核心状态机:<br>• 动态 RX 缓冲:常态 **1024B**,固件升级 **8192B**`tcp_server_rx_upgrade_begin/end`<br>• `comm_link_receive_data` 收包 → JSON 括号抽帧<br>• 周期上送:每 **2s** `SystemInfo` + 双枪 `GunInfo`BLE 枪间加延时<br>• 升级进行中暂停周期上送;跳过前导垃圾字节(BLE 分片)<br>• 心跳超时 30s(升级中不判超时)<br>• TCP 回包可追加 `\r\n`BLE 纯 JSON 无 CRLF |
| **`tcp_server.h`** | `tcp_server_state_t``tcp_server_start/process/stop``tcp_server_rx_upgrade_*`。 |
### 4.5 协议层
| 文件 | 功能 |
|---|---|
| **`tcp_protocol.c`** | JSON v2.0 组包/解析/分发;**BLE 链路拒绝 `FirmwareUpgrade`**;其余命令与 TCP 相同。 |
| **`tcp_protocol.h`** | `CMD_*`、结构体、状态枚举。 |
### 4.6 业务层
| 文件 | 功能 |
|---|---|
| **`system_data.c`** | `tcp_get_default_system_info/gun_data`、充电启停、`tcp_process_custom_data`、故障与历史记录;GunInfo 见 [§10](#10-guninfo-数据规则)。 |
| **`system_data.h`** | 业务接口与历史查询结构。 |
| **`system_paracfg.c/h`** | 七类参数映射表与读写。 |
| **`debug_files.c`** | 阶段2 合并实现,分两段:<br>• **FileOperations**`FATFS_EN=1`):Query/Delete/Download/Upload<br>• **FirmwareUpgrade**TCP JSON+Base64 写外置 Flash`system_filetransfer_is_active`、空闲超时等 |
| **`debug_files.h`** | 文件类业务统一头:`file_operation_data_t``firmware_upgrade_context_t``system_filectrl_*``system_filetransfer_*` |
### 4.7 协议文档
| 文件 | 功能 |
|---|---|
| **`TCP_DEBUG_PROTOCOL.md`** | 上位机 JSON 字段与示例(TCP/BLE 共用)。 |
---
## 5. 分层架构与调用关系
| 层次 | 文件 | 职责 |
|---|---|---|
| **任务** | `tcp_ser_task.c` | 选链路、启服务、周期 `tcp_server_process` |
| **服务** | `tcp_server.c` | 缓冲、抽帧、定时上送、连接/超时 |
| **链路** | `debug_link.c` | TCP/BLE 字节流统一接口 + Socket 后端 |
| **后端** | `debug_link.c`TCP/ `fc41d_ble.c`BLE | 物理收发 |
| **协议** | `tcp_protocol.c` | JSON ↔ 结构体、路由 |
| **业务** | `system_data.c` / `system_paracfg.c` / `debug_files.c` | 桩数据、参数、升级、文件 |
扩展新命令:改 `tcp_protocol.h/c` → 对应 `system_*.c``debug_files.c` → 更新 `TCP_DEBUG_PROTOCOL.md`
---
## 6. TCP / BLE 双链路
### 6.1 选路
| 条件 | 物理链路 |
|---|---|
| `BLE_DEBUG_EN=0` | 恒为 TCPDIP1=1 仅打日志,仍走 TCP |
| `BLE_DEBUG_EN=1` 且 DIP1=0 | 以太网 TCP,端口 **9699** |
| `BLE_DEBUG_EN=1` 且 DIP1=1 | FC41D BLE GATT |
DIP 来源:副板 `sub_comm``u8_sub_comm_yx_get(E_SUB_DIP_1)`
### 6.2 协议差异
| 项目 | TCP | BLE |
|---|---|---|
| 报文格式 | JSON v2.0 | 同左 |
| 下行结束符 | 可选 `\r\n` | 无 CRLF |
| 周期上报 | 2s SystemInfo + GunInfo×2 | 同左(枪间 ~80ms 间隔) |
| `FirmwareUpgrade` | 支持 | **拒绝**`tcp_protocol.c` |
| 固件相关 RX 缓冲放大 | 8192B | 同左(若走 TCP 升级) |
### 6.3 硬件(BLE
| 项目 | 说明 |
|---|---|
| 模组 | Quectel FC41D |
| UART | UART7PE1/PE0 |
| 复位 | PE6`FC41D_RST` 高有效 |
| 广播名 | `CCU621_Debug`(可宏改) |
| 服务/特征 | fff1 / fff2(Write) / fff3(Notify) |
---
## 7. 已支持命令与处理路径
| command | 方向 | TCP | BLE | 业务入口 |
|---|---|:---:|:---:|---|
| `Heartbeat` | 双向 | ✓ | ✓ | 更新 `last_heartbeat_received_tick` |
| `SystemInfo` | 设备→上位机 | ✓ | ✓ | `tcp_get_default_system_info` |
| `GunInfo` | 设备→上位机 | ✓ | ✓ | `tcp_get_default_gun_data` |
| `ChargeControl` | 上位机→设备 | ✓ | ✓ | `system_data_start/stop_charge` |
| `ParamQuery` / `ParamModify` | 上位机→设备 | ✓ | ✓ | `system_paracfg` |
| `FileOperations` | 上位机→设备 | ✓ | ✓ | `system_filectrl_process_*` |
| `HistoryOperations` | 上位机→设备 | ✓ | ✓ | `tcp_history_build/clear_records` |
| `FaultInfo` | 上位机→设备 | ✓ | ✓ | `tcp_get_system_fault_data` |
| `CustomData` | 上位机→设备 | ✓ | ✓ | `tcp_process_custom_data` |
| `FirmwareUpgrade` | 上位机→设备 | ✓ | **✗** | `system_filetransfer_*`(仅 TCP |
---
## 8. 关键流程
### 8.1 下行(上位机 → 设备)
```
comm_link_receive_data()
→ tcp_rx_buffer_append()
→ tcp_rx_buffer_extract_json() // 括号匹配;升级期可 skip 前导垃圾
→ tcp_server_process_received_data()
→ tcp_parse_json_message()
→ tcp_process_received_message()
→ system_* 业务
→ tcp_generate_*_json()
→ comm_link_send_data()
```
### 8.2 上行(周期,每 2s
`tcp_server_send_ctrl()`
1. 发送 `SystemInfo`
2. BLE 延时 ~120ms / TCP ~20ms
3. 依次发送枪 1、枪 2 的 `GunInfo`BLE 枪间 ~80ms
固件升级进行中:**不**做周期上送。
### 8.3 任务主循环
```
v_tcp_ser_task
├─ [BLE] ble_link_wait_dip_ready + comm_link_set_type(DIP1)
├─ tcp_server_start() → comm_link_start()
└─ tcp_server_process()
├─ comm_link_process() // BLE 状态机
├─ comm_link_accept_client() // 仅 TCP
├─ comm_link_receive_data()
├─ tcp_server_process_rx_buffer()
├─ system_filetransfer_poll_idle_timeout()
└─ tcp_server_send_ctrl()
```
---
## 9. 固件升级(FirmwareUpgrade
**仅 TCP 链路**。JSON + Base64 分包,写外部 Flash 升级区。
| subCommand | 关键字段 |
|---|---|
| `Start` | `fileSize`, `totalPackets`, `packetSize`, `firmwareVersion` |
| `Data` | `packetIndex`, `data`(Base64), `crc` |
| `End` | `status`, `errorMessage` |
| `Response` | `response`(ACK/NAK), `packetIndex` |
实现:`tcp_protocol.c` + `debug_files.c`(原 `system_filetransfer`)。升级时 RX 缓冲扩至 8192B,并可用 `system_filetransfer_get_expected_packet_bytes` 判断 Data JSON 是否收齐。
---
## 10. GunInfo 数据规则
`tcp_get_default_gun_data()``system_data.c`):
1. **空闲 / 仅插枪**`chargingStatus <= CONNECTED`):`energy``cost``batterySOC``chargingTime`**0**`userId``orderNumber`**空串**(避免 Flash 残留导致 JSON 过长、BLE 分片异常)。
2. **充电中**:从 `v_meterlog_put_char_log_data` 填实时量;`tcp_copy_log_field()` 安全拷贝用户/订单字段(遇非可打印 ASCII 或 `'\0'` 停止)。
3. **SOC**:限制 0100。
---
## 11. CustomData / HistoryOperations
### CustomData
| functionField | 行为 |
|---|---|
| `paramInit` | `v_public_param_init()` |
| `reboot` | 应答后 `v_sys_reboot()` |
| `faultInfo` | 当前故障摘要 |
### HistoryOperations
`subCommand=Query``historyType=fault`:分页 `queryId` / `startIndex` / `fetchCount``content` 格式:
`location,code,desc,actType,value1,value2`
---
## 12. 编译开关与运行条件
| 宏 | 默认值 | 说明 |
|---|---|---|
| `TCP_DEBUG_EN` | `1` | 总开关;为 0 时整模块空桩 |
| `BLE_DEBUG_EN` | `1` | 为 0 时不编入 `fc41d_ble.c` 有效代码,恒 TCP |
| `TCP_DEBUG_PORT` | `9699` | TCP 监听端口 |
| `TCP_DEBUG_MAX_CLIENTS` | `1` | 单客户端 |
约束:`BLE_DEBUG_EN` 要求 `TCP_DEBUG_EN=1``#error` 保护)。
运行前提:
- **TCP**lwIP 已初始化、网口可用
- **BLE**FC41D 硬件 + UART7`BSP/GPIO``FC41D_RST_PORT/PIN`PE6
- **文件操作**FatFS 已挂载
- **固件升级**:外部 Flash + IAP
---
## 13. 维护注意事项
1. **协议与链路分离**JSON 逻辑只在 `tcp_server` / `tcp_protocol`;换链路只动 `comm_link` 与后端。
2. **勿改公共 usart**FC41D 的 UART7 固定 **115200**`FC41D_UART_BAUD`),阻塞收包在 `fc41d_ble.c` 内 static 实现。
3. **GunInfo 字符串**:订单/用户字段必须用 `tcp_copy_log_field`,禁止对定长 `U8_T[]` 直接 `%s` / `strcpy`
4. **BLE 包长**:单条 GunInfo JSON 宜控制在 GATT 分片友好范围;空闲态勿带长 `orderNumber`
5. **Keil**:勿再加入已删除的 `fwbin_protocol.c``fwup_protocol.c``comm_link*.c``tcp_server_socket.c``system_filectrl.c``system_filetransfer.c`;须包含 `debug_link.c``debug_files.c``ble_link/fc41d_ble.c`
6. **DIP 调试**:无模组时设 `BLE_DEBUG_EN=0` 或 DIP1=0 走 TCP。
---
## 14. 已移除 / 不在树内的文件
以下文件**不在**当前 `app/tcp_ser` 目录,亦**不应**加入 Keil 工程:
| 文件 | 说明 |
|---|---|
| `fwbin_protocol.c/h` | FWBIN 二进制升级(`F0 F9`),已废弃 |
| `fwup_protocol.c` | FWUP 行协议升级,已废弃 |
| `tcp/tcp_socket.c` | 旧路径;Socket 已合并进 `debug_link.c` |
| `comm_link.c` / `comm_link_ctx.c` / `tcp_server_socket.c` | 阶段1 已合并为 `debug_link.c` |
| `system_filectrl.c` / `system_filetransfer.c` | 阶段2 已合并为 `debug_files.c` |
| `comm_link.h` / `comm_link_ctx.h` / `system_filectrl.h` / `system_filetransfer.h` / `tcp/` | 兼容头已删除,统一 `#include "debug_link.h"` / `"debug_files.h"` |
| `_backup_phase1/` / `_backup_phase2/` | 合并过程临时备份,已删除 |
历史方案可参考备份目录 `CCU621_M-蓝牙功能升级失败-退回前保存``docs/CCU621_M_固件升级十六进制协议_FWUP.md`**已过时**)。
---
## 关联文档
| 文档 | 说明 |
|---|---|
| [`CCU621_M/README.md`](../../README.md) | 工程主索引 |
| [`TCP_DEBUG_PROTOCOL.md`](TCP_DEBUG_PROTOCOL.md) | JSON 报文字段与示例 |
| [`docs/CCU621_M_蓝牙调试通信方案.md`](../../docs/CCU621_M_蓝牙调试通信方案.md) | 蓝牙联调方案 |
| 本文 | `app/tcp_ser/tcp_ser模块说明.md` |