258 lines
8.2 KiB
C
258 lines
8.2 KiB
C
/**
|
||
* @file test_protocol.c
|
||
* @brief CAN 协议栈功能测试程序
|
||
*
|
||
* 测试流程:
|
||
* 1. 加载 XML 配置(模拟保护装置)
|
||
* 2. 注册回调(遥控/定值/遥信遥测读取)
|
||
* 3. 绑定 CAN 接口(默认 can0)
|
||
* 4. 进入主循环:定时上送 + 接收处理
|
||
*
|
||
* 用法:
|
||
* ./can_test -c config/protection_relay.xml -i can0 -v
|
||
*
|
||
* 本测试程序可在 PC 上编译运行(使用 vcan 虚拟 CAN 接口),不依赖实际硬件。
|
||
*/
|
||
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <unistd.h>
|
||
#include <signal.h>
|
||
#include <time.h>
|
||
#include "can_protocol.h"
|
||
|
||
/* ================================================================
|
||
* 命令行参数
|
||
* ================================================================ */
|
||
static const char *g_xml_path = "config/protection_relay.xml";
|
||
static const char *g_can_if = "can0";
|
||
static int g_verbose = 0;
|
||
static int g_quit = 0;
|
||
|
||
/* ================================================================
|
||
* 模拟数据(应用层维护的实际信号值)
|
||
* ================================================================ */
|
||
|
||
/* v2.0: 遥信双位置 (2-bit DPS) 位对数据
|
||
* 每字节 = 4 个遥信点,每个点 2-bit:
|
||
* 01 = 分(OFF), 10 = 合(ON)
|
||
* 点0(断路器): 10(合), 点1(刀闸1): 10(合), 点2(刀闸2): 10(合)
|
||
* → Byte0 = 0b10101001 = 0xA5 (点顺序: 3,2,1,0 → 但 bit[1:0]=点0)
|
||
* 正确: bit[1:0]=点0=10, bit[3:2]=点1=10, bit[5:4]=点2=10, bit[7:6]=点3=00
|
||
* → 0b00101010 = 0x2A
|
||
*/
|
||
static uint8_t g_yx_bitmap[16] = {
|
||
0x2A, /* 点0=10(合) 点1=10(合) 点2=10(合) 点3=00(中间态) */
|
||
};
|
||
|
||
/* v2.0: 遥测浮点值 (IEEE 754 float, 实际工程值) */
|
||
static float g_yc_values[] = {
|
||
[0] = 125.0f, /* 0x0100: Ia = 125.0 A */
|
||
[1] = 124.5f, /* 0x0101: Ib = 124.5 A */
|
||
[2] = 125.2f, /* 0x0102: Ic = 125.2 A */
|
||
[3] = 2.5f, /* 0x0103: 3I0 = 2.5 A */
|
||
[4] = 6350.0f, /* 0x0104: Ua = 6350 V */
|
||
[5] = 6345.0f, /* 0x0105: Ub = 6345 V */
|
||
[6] = 6352.0f, /* 0x0106: Uc = 6352 V */
|
||
[7] = 50.0f, /* 0x0107: 3U0 = 50 V */
|
||
[8] = 50.0f, /* 0x0108: P = 50.00 MW */
|
||
[9] = 12.0f, /* 0x0109: Q = 12.00 Mvar */
|
||
[10] = 50.00f, /* 0x010A: f = 50.00 Hz */
|
||
[11] = 0.98f, /* 0x010B: cosφ = 0.98 */
|
||
};
|
||
|
||
/* ================================================================
|
||
* 回调函数实现
|
||
* ================================================================ */
|
||
|
||
/**
|
||
* 遥信读取回调 — 协议栈定时发送时调用
|
||
*/
|
||
static int yx_read_cb(uint16_t addr, uint8_t *bitmap, int max_bytes,
|
||
void *arg)
|
||
{
|
||
(void)arg;
|
||
|
||
/* 将模拟遥信数据复制给协议栈 */
|
||
int idx = (addr - 0x0001); /* 遥信起始地址 0x0001 */
|
||
if (idx >= 0 && idx < (int)sizeof(g_yx_bitmap)) {
|
||
int copy = (max_bytes < 16) ? max_bytes : 16;
|
||
memcpy(bitmap, &g_yx_bitmap[idx], copy);
|
||
}
|
||
|
||
if (g_verbose) {
|
||
printf("[YX_READ] addr=0x%04X, bitmap=0x%02X\n",
|
||
addr, bitmap[0]);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* 遥测读取回调 (v2.0: float 值)
|
||
*/
|
||
static int yc_read_cb(uint16_t addr, float *value, void *arg)
|
||
{
|
||
(void)arg;
|
||
int idx = (addr - 0x0100);
|
||
if (idx >= 0 && idx < (int)(sizeof(g_yc_values) / sizeof(g_yc_values[0])))
|
||
*value = g_yc_values[idx];
|
||
else
|
||
*value = 0.0f;
|
||
if (g_verbose)
|
||
printf("[YC_READ] addr=0x%04X, value=%.3f\n", addr, (double)*value);
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* 遥控回调 — 收到遥控请求时调用
|
||
*/
|
||
static int yk_cb(uint8_t point, yk_op_type_e op, void *arg)
|
||
{
|
||
(void)arg;
|
||
|
||
const char *op_name[] = { "选择", "执行", "撤销" };
|
||
printf("\n========================================\n");
|
||
printf("[YK] 收到遥控命令: 点号=%u, 操作=%s\n",
|
||
point, op_name[op]);
|
||
printf("========================================\n\n");
|
||
|
||
/* 实际应用中,这里会操作 GPIO/继电器 */
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* 定值回调 (v2.0: float 值)
|
||
*/
|
||
static int dz_cb(uint16_t addr, float value, void *arg)
|
||
{
|
||
(void)arg;
|
||
printf("\n========================================\n");
|
||
printf("[DZ] 收到定值写入: 地址=0x%04X, 值=%.3f\n", addr, (double)value);
|
||
printf("========================================\n\n");
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* 文件接收回调 — 收到文件首帧时调用
|
||
*/
|
||
static int ft_recv_cb(uint16_t file_id, const char *filename,
|
||
uint32_t file_size, void *arg)
|
||
{
|
||
(void)arg;
|
||
|
||
printf("\n========================================\n");
|
||
printf("[FT] 收到文件传输请求:\n");
|
||
printf(" 文件ID: 0x%04X\n", file_id);
|
||
printf(" 文件名: %s\n", filename);
|
||
printf(" 大小: %u bytes\n", file_size);
|
||
printf("========================================\n\n");
|
||
|
||
return 0; /* 返回 0 表示同意接收 */
|
||
}
|
||
|
||
/* ================================================================
|
||
* 信号处理
|
||
* ================================================================ */
|
||
static void sig_handler(int signo)
|
||
{
|
||
printf("\n收到信号 %d, 退出...\n", signo);
|
||
g_quit = 1;
|
||
can_protocol_stop();
|
||
}
|
||
|
||
/* ================================================================
|
||
* 使用说明
|
||
* ================================================================ */
|
||
static void print_usage(const char *prog)
|
||
{
|
||
printf("CAN 协议栈测试程序 — 电力二次设备通讯\n\n");
|
||
printf("用法: %s [选项]\n\n", prog);
|
||
printf("选项:\n");
|
||
printf(" -c <xml> XML 配置文件路径 (默认: config/protection_relay.xml)\n");
|
||
printf(" -i <if> CAN 接口名称 (默认: can0)\n");
|
||
printf(" -v 详细输出模式\n");
|
||
printf(" -h 显示本帮助\n");
|
||
printf("\n示例:\n");
|
||
printf(" # 使用虚拟 CAN 接口测试 (需要先 modprobe vcan)\n");
|
||
printf(" sudo ip link add dev vcan0 type vcan\n");
|
||
printf(" sudo ip link set up vcan0\n");
|
||
printf(" %s -c config/protection_relay.xml -i vcan0 -v\n", prog);
|
||
printf("\n # 模拟双装置互测\n");
|
||
printf(" sudo ip link add dev vcan0 type vcan\n");
|
||
printf(" sudo ip link add dev vcan1 type vcan\n");
|
||
printf(" sudo ip link set up vcan0\n");
|
||
printf(" sudo ip link set up vcan1\n");
|
||
printf(" # 终端1: %s -c config/protection_relay.xml -i vcan0 -v\n", prog);
|
||
printf(" # 终端2: %s -c config/data_center.xml -i vcan1 -v\n", prog);
|
||
}
|
||
|
||
/* ================================================================
|
||
* 主函数
|
||
* ================================================================ */
|
||
int main(int argc, char *argv[])
|
||
{
|
||
int opt;
|
||
|
||
while ((opt = getopt(argc, argv, "c:i:vh")) != -1) {
|
||
switch (opt) {
|
||
case 'c':
|
||
g_xml_path = optarg;
|
||
break;
|
||
case 'i':
|
||
g_can_if = optarg;
|
||
break;
|
||
case 'v':
|
||
g_verbose = 1;
|
||
break;
|
||
case 'h':
|
||
default:
|
||
print_usage(argv[0]);
|
||
return (opt == 'h') ? 0 : 1;
|
||
}
|
||
}
|
||
|
||
printf("===== CAN 协议栈测试程序 =====\n");
|
||
printf("配置文件: %s\n", g_xml_path);
|
||
printf("CAN 接口: %s\n", g_can_if);
|
||
printf("\n");
|
||
|
||
/* 1. 初始化协议栈 */
|
||
if (can_protocol_init(g_xml_path) < 0) {
|
||
fprintf(stderr, "协议栈初始化失败!\n");
|
||
return 1;
|
||
}
|
||
|
||
/* 2. 注册回调 */
|
||
can_protocol_register_yx_read_callback(yx_read_cb, NULL);
|
||
can_protocol_register_yc_read_callback(yc_read_cb, NULL);
|
||
can_protocol_register_yk_callback(yk_cb, NULL);
|
||
can_protocol_register_dz_callback(dz_cb, NULL);
|
||
can_protocol_register_ft_recv_callback(ft_recv_cb, NULL);
|
||
|
||
printf("回调函数注册完成\n");
|
||
|
||
/* 3. 绑定 CAN 接口 */
|
||
if (can_protocol_bind(g_can_if) < 0) {
|
||
fprintf(stderr, "绑定 CAN 接口失败! (尝试: sudo ip link set up %s)\n",
|
||
g_can_if);
|
||
can_protocol_deinit();
|
||
return 1;
|
||
}
|
||
|
||
/* 4. 注册信号处理 */
|
||
signal(SIGINT, sig_handler);
|
||
signal(SIGTERM, sig_handler);
|
||
|
||
/* 5. 进入主循环 */
|
||
printf("\n进入主循环... (按 Ctrl-C 退出)\n\n");
|
||
|
||
can_protocol_run();
|
||
|
||
/* 6. 清理 */
|
||
can_protocol_deinit();
|
||
|
||
printf("测试程序正常退出\n");
|
||
return 0;
|
||
}
|