/** * @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 #include #include #include #include #include #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 配置文件路径 (默认: config/protection_relay.xml)\n"); printf(" -i 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; }