/******************************************************************** 版权所有: 文件版本: V1.00 文件名称: IEC101.c 生成日期: 2010年04月26日 作 者:hhw 使用范围: 功 能: IEC101规约处理 更新信息: 更新日志1 修改者: 修改日期: 修改内容: 修改原因: *********************************************************************/ #include "head.h" u32 IEC101_SendEvent(IEC101_DEF *pt, int is_send_cos); void IEC101_Info_Printf_One(IEC101_DEF *p); extern int gprs_vs_write(const unsigned char *buf, int count); GPRS_INF_DEF tGprsInf; static void IEC101_State(IEC101_DEF *pt101, int state) { if (pt101->st101 != state) { char buf[64]; char log_info[128]; rt_printf_time("IEC101(%d) state: %d => %d!\r\n", pt101->chnl, pt101->st101, state); pt101->st101 = state; if (state == IEC101_ST_DATA) { sprintf(buf, "101主站连接(%s)!\r\n", get_comm_name(pt101->chnl + 1)); log_str_time(LOG_OPERATE, buf, 0, 1); sprintf(log_info, "IEC101(%d):101 main station connection", pt101->chnl + 1); load_hs_log_rcd(TYPE_CHNL_LINK, true, NULL, log_info, 1); if (log_flag.com_err[pt101->chnl] == true) { memset(log_info, 0, sizeof(log_info)); sprintf(log_info, "IEC101(%d):Communication process anomaly return", pt101->chnl + 1); load_hs_log_rcd(TYPE_COMM_ERR, true, NULL, log_info, 0); log_flag.com_err[pt101->chnl] = false; } // SOE发送缓冲区复位,发送指针回到ACK的位置 if (pRunSet->bTT_SoeResumeComm) { #ifdef SOE_RELINK_DISCOS if (!g_soe_queue.bSoeRelink[pt101->chnl]) { // g_soe_queue.relink_tail_bak[pt101->chnl].n = g_soe_queue.tail_send[pt101->chnl].n; g_soe_queue.relink_tail_bak[pt101->chnl].n = g_soe_queue.tail_eep.n; } g_soe_queue.bSoeRelink[pt101->chnl] = true; #endif // pt101->bSoeRelink = true; g_soe_queue.tail_send[pt101->chnl].n = g_soe_queue.tail_ack[pt101->chnl].n; } } else if (state == IEC101_ST_NONE) { sprintf(buf, "101主站断开(%s)!\r\n", get_comm_name(pt101->chnl + 1)); log_str_time(LOG_OPERATE, buf, 0, 0); sprintf(log_info, "IEC101(%d):101 main station disconnection", pt101->chnl + 1); load_hs_log_rcd(TYPE_CHNL_LINK, true, NULL, log_info, 0); } } return; } static void IEC101_ResetTxBuf(IEC101_DEF *pt101) { pt101->btx_buf_m = false; pt101->btx_buf_s = false; memset(&pt101->tx_buf, 0, sizeof(pt101->tx_buf)); } static void IEC101_Reset(IEC101_DEF *pt101) { // 通道属性、连接计数不允许复位 // 规约状态复位 g_comm_link_status &= ~(1 << pt101->chnl); IEC101_State(pt101, IEC101_ST_NONE); pt101->bSendChange = false; // 计时 pt101->us0_sec = ustimer_get_origin(); pt101->secs = 0; pt101->us0_sendpiece = ustimer_get_origin(); // 对时 pt101->us0_frame = 0; pt101->ms_delay = 0; // 1级、2级数据发送 pt101->bFirstFCV = 1; pt101->tb_index = -1; pt101->FCBSave = 0; // 遥测发送处理 pt101->bycsend = FALSE; pt101->ycno = 0; pt101->us0_yc = ustimer_get_origin(); memset(pt101->yc_save, 0, sizeof(pt101->yc_save)); // 其它状态复位 pt101->bRmtSM2 = false; pt101->dValidTime = 0; pt101->bMSend = false; pt101->bDIR = true; pt101->bResetProcess = false; pt101->tm.us0_callall = dTCounter; // 不能用微妙定时器,最多支持13分钟 pt101->tm.bAppEcho = false; // pt101->bSoeRelink = false; pt101->bTimeSyn = false; #if 0 // SOE发送缓冲区复位,发送指针回到ACK的位置 if(pRunSet->bTT_SoeResumeComm) { #ifdef SOE_RELINK_DISCOS if(!g_soe_queue.bSoeRelink[pt101->chnl]) { //g_soe_queue.relink_tail_bak[pt101->chnl].n = g_soe_queue.tail_send[pt101->chnl].n; g_soe_queue.relink_tail_bak[pt101->chnl].n = g_soe_queue.tail_eep.n; } g_soe_queue.bSoeRelink[pt101->chnl] = true; #endif //pt101->bSoeRelink = true; g_soe_queue.tail_send[pt101->chnl].n=g_soe_queue.tail_ack[pt101->chnl].n; } #endif // 遥控信息复位 ResetRctrl(MASTER_101 + pt101->chnl); // 发送缓冲区复位 IEC101_ResetTxBuf(pt101); RS_Recv_Enable(pt101->chnl); #ifdef RCD_STRAN_S rt_printf_time("-> Link Reset, Clear File Cache\r\n"); iec_freefile(&pt101->tf); pt101->tf.bTransing = false; pt101->tf.bdatTraned = false; #endif } void IEC101_Init(IEC101_DEF *pt, u8 chnl) { // 接收帧间隔为重发间隔时间的一半 g_tRsComm[chnl].us_recv_timeout = pRunSet->dT101Resend * USTIMER_SEC / 2; memset(pt, 0, sizeof(IEC101_DEF)); pt->type = IEC101_CHN; pt->chnl = chnl; pt->bMaintainComm = false; if (tRunPara.tUartPara[chnl].wProtocol == PROTOCOL_MAINTAIN) { pt->bMaintainComm = true; } // 初始化接收buf结构 pt->rx_buf.chn = pt->chnl; pt->rx_buf.buf = pt->recvbuf; rt_fifo_init(&pt->recv_fifo, pt->recv_fifo_buf, IEC101_FIFO_SIZE); #ifdef RCD_STRAN_S queue_init(&pt->rcd_qt.queue, NEWRCD_COUNT, &pt->rcd_qt.data, NEWRCD_SIZE); #endif IEC101_Reset(pt); } /************************************************************************** 函数名称:IEC101_Send 函数版本:1.00 作者: 创建日期:2008.9.1 函数功能说明:启动485口的数据发送 输入参数: 输出参数: 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ void IEC101_SSend(IEC101_DEF *pt) { u8 *sb = g_tRsComm[pt->chnl].sendbuf; // 如果链路层没有数据,直接返回 if (pt->btx_buf_s == false) { return; } // 有数据,复制到真正的发送BUF并清标志 memcpy(sb, pt->tx_buf_s, pt->tx_buf_s[0] + 1); pt->btx_buf_s = false; // 置发送标志 RS_Send_Enable(pt->chnl); g_tRsComm[pt->chnl].nSendCnt = 1; // 发送缓冲区的第一个字节为发送长度 pt->bSend = true; if (!g_bCommLed) g_bCommLed = true; if (UART_CHANNEL[pt->chnl] == CFG_UART_GPRS_IN) { // gprs_vs_write(&sb[1], sb[0]); pt->bSend = false; s_stat_tx((int)pt->chnl, sb[0]); } #ifdef FUN_FUXI_ESAM if (UART_CHANNEL[pt->chnl] == CFG_UART_CHIP_S1) { write_chip_s1(&sb[1], sb[0]); pt->bSend = false; } #endif if (g_print_101 && (g_print_port & (1 << pt->chnl))) { rt_printf("TX_S101_%d:", pt->chnl); print_msg("", &sb[1], sb[0]); } // 统计发送帧数 s_stat_tx_frame((int)pt->chnl); } /************************************************************************** 函数名称:IEC101_MSend 函数版本:1.00 作者: 创建日期:2008.9.1 函数功能说明:平衡101,作为主站发送 输入参数: 输出参数: 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ static void IEC101_MSend(IEC101_DEF *pt) { u8 *sb = g_tRsComm[pt->chnl].sendbuf; // 如果链路层没有数据,直接返回 if (pt->btx_buf_m == false) { return; } // 有数据,复制到真正的发送BUF并清标志 memcpy(sb, pt->tx_buf_m, pt->tx_buf_m[0] + 1); pt->btx_buf_m = false; // 置发送标志 RS_Send_Enable(pt->chnl); g_tRsComm[pt->chnl].nSendCnt = 1; pt->bSend = true; if (!g_bCommLed) g_bCommLed = true; if (UART_CHANNEL[pt->chnl] == CFG_UART_GPRS_IN) { // gprs_vs_write(&sb[1], sb[0]); pt->bSend = false; s_stat_tx((int)pt->chnl, sb[0]); } #ifdef FUN_FUXI_ESAM if (UART_CHANNEL[pt->chnl] == CFG_UART_CHIP_S1) { write_chip_s1(&sb[1], sb[0]); pt->bSend = false; } #endif if (g_print_101 && (g_print_port & (1 << pt->chnl))) { rt_printf("TX_M101_%d:", pt->chnl); print_msg("", &sb[1], sb[0]); } // 统计发送帧数 s_stat_tx_frame((int)pt->chnl); } static void IEC101_ext_send(IEC101_DEF *pt, u8 *buf, u16 len) { #ifdef FUNC_ENCRY_IN_ONE_SERIAL if ((tRunPara.tUartPara[0].wProtocol == PROTOCOL_WED_ENC) && pt->chnl == 1) { // 投入纬德规约时,串口通道1/3/4的待发数据统一由串口通道0发送处理 u8 *sb = g_tRsComm[0].extsendbuf_1; memcpy(sb, buf, len); g_tRsComm[0].extsendcnt_1 = 0; g_tRsComm[0].extsendlen_1 = len; g_tRsComm[0].bextsend_1 = true; } else if ((tRunPara.tUartPara[0].wProtocol == PROTOCOL_WED_ENC) && pt->chnl == 3) { // 投入纬德规约时,串口通道1/3/4的待发数据统一由串口通道0发送处理 u8 *sb = g_tRsComm[0].extsendbuf_2; memcpy(sb, buf, len); g_tRsComm[0].extsendcnt_2 = 0; g_tRsComm[0].extsendlen_2 = len; g_tRsComm[0].bextsend_2 = true; } else if ((tRunPara.tUartPara[0].wProtocol == PROTOCOL_WED_ENC) && pt->chnl == 4) { // 投入纬德规约时,串口通道1/3/4的待发数据统一由串口通道0发送处理 u8 *sb = g_tRsComm[0].extsendbuf_3; memcpy(sb, buf, len); g_tRsComm[0].extsendcnt_3 = 0; g_tRsComm[0].extsendlen_3 = len; g_tRsComm[0].bextsend_3 = true; } else #endif { u8 *sb = g_tRsComm[pt->chnl].extsendbuf; memcpy(sb, buf, len); g_tRsComm[pt->chnl].extsendcnt = 0; g_tRsComm[pt->chnl].extsendlen = len; g_tRsComm[pt->chnl].bextsend = true; } } static void IEC101_Send_Polling(IEC101_DEF *pt) { #ifdef FUNC_ENCRY_IN_ONE_SERIAL int i = 0, j = 0, crc_sum = 0; #endif u8 *buf; #ifdef FUNC_ENCRY_IN_ONE_SERIAL memset(&g_tRsComm[pt->chnl].enc_sendbuf, 0, sizeof(&g_tRsComm[pt->chnl].enc_sendbuf)); #endif // 如果串口忙,不能发送数据 if (g_tRsComm[pt->chnl].bextsend #ifdef FUNC_ENCRY_IN_ONE_SERIAL || g_tRsComm[0].bextsend_1 || g_tRsComm[0].bextsend_2 || g_tRsComm[0].bextsend_3 #endif ) { pt->us0_sendpiece = ustimer_get_origin(); return; } if (pt->bSend) // 有数据要发送了 { int len = g_tRsComm[pt->chnl].sendbuf[0]; #ifdef FUNC_ENCRY_IN_ONE_SERIAL if (tRunPara.tUartPara[0].wProtocol == PROTOCOL_WED_ENC) { // 纬德规约内容组包 g_tRsComm[pt->chnl].enc_sendbuf[i++] = 0xAA; g_tRsComm[pt->chnl].enc_sendbuf[i++] = (u8)(len); // 数据报文长度 g_tRsComm[pt->chnl].enc_sendbuf[i++] = (u8)((len) >> 8); g_tRsComm[pt->chnl].enc_sendbuf[i++] = pt->enc_tunnel; memcpy(&g_tRsComm[pt->chnl].enc_sendbuf[i], &g_tRsComm[pt->chnl].sendbuf[1], len); i = i + len; for (j = 1; j < i; j++) { crc_sum = crc_sum + g_tRsComm[pt->chnl].enc_sendbuf[j]; } g_tRsComm[pt->chnl].enc_sendbuf[i++] = (u8)crc_sum; g_tRsComm[pt->chnl].enc_sendbuf[i++] = 0x55; buf = &g_tRsComm[pt->chnl].enc_sendbuf[0]; len = i; } else #endif { buf = &g_tRsComm[pt->chnl].sendbuf[1]; } if (pRunSet->bTT_ESAM && (!pt->bMaintainComm)) { u8 ti, cot, se; u8 *out; iec_GetMsgInfo(buf, len, &ti, &cot, &se, true); len = sec_encrpt_iec(ti, cot, se, buf, len, &out); if (len > 0) { IEC101_ext_send(pt, out, len); } } else { IEC101_ext_send(pt, buf, len); } pt->bSend = false; return; } if (ustimer_get_duration(pt->us0_sendpiece) < USTIMER_MS * 5) // 发送帧之间间隔5ms { return; } // 如果有从动站数据,发送 if (pt->btx_buf_s) { IEC101_SSend(pt); pt->us0_sendpiece = ustimer_get_origin(); } // 如果有启动站数据,发送 else if (pt->btx_buf_m) { IEC101_MSend(pt); pt->us0_sendpiece = ustimer_get_origin(); } else if (pt->bEchosecmd5) { u8 md5buf[24]; u16 len; len = sec_md5_echo(md5buf); IEC101_ext_send(pt, md5buf, len); pt->bEchosecmd5 = false; } } /************************************************************************** 函数名称:IEC101_GetOneBuf 函数版本:1.00 作者: 创建日期:2008.9.1 函数功能说明:将数据添加到一级数据缓冲区 输入参数:pt 各通道101结构定义首地址,ps源数据地址 输出参数: 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ static int IEC101_BufFreeNum(iec_tbuf_t *pt) { return (pt->tb_tail_ack - pt->tb_head - 1) & (IEC_TBUF_NUM - 1); } static u16 IEC101_GetOneBuf(iec_tbuf_t *pt) { u16 head; head = pt->tb_head; pt->tb_head++; // 系统设计时,保证缓冲不会满,以下情况不应出现 if (pt->tb_head == pt->tb_tail_ack || pt->tb_head == pt->tb_tail_send) { rt_printf_time("IEC101: IEC101_GetOneBuf error:head=%d,send=%d,ack=%d.\r\n", pt->tb_head, pt->tb_tail_send, pt->tb_tail_ack); } return head; } /************************************************************************** 函数名称:IEC101_FrameEcho 函数版本:1.00 作者: 创建日期:2008.9.1 函数功能说明:应答短帧报文 输入参数:pt 各通道101结构定义首地址,ZoneByte短政控制域 输出参数: 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ static void IEC101_FrameEcho(IEC101_DEF *pt, unsigned char ZoneByte) { u8 *pd = pt->tx_buf_s; // 如果此时正在发送数据,说明逻辑不对 if (pt->btx_buf_s) { rt_printf("IEC101_FrameEcho(%d) err:链路层BUF已有数据\r\n", pt->chnl); } // 更新ACD或DIR位 if (tRunPara.tUartPara[pt->chnl].b101PH) { // 平衡101,DIR置1 if (!pt->bDIR) { IEC101_SETDIR(ZoneByte); } } else { // 目前所有数据都当成一级数据,等新标准正式颁布后再分一、二级数据。 // 非平衡101, 如果还有一级数据,设置ACD位 if ((pt->tx_buf[0].tb_head != pt->tx_buf[0].tb_tail_send) || (pt->tx_buf[1].tb_head != pt->tx_buf[1].tb_tail_send) || (pt->tx_buf[2].tb_head != pt->tx_buf[2].tb_tail_send)) { IEC101_SETACD(ZoneByte); } } // 形成报文 if (!tRunPara.b101Addr2Byte) { pd[0] = 5; pd[1] = 0x10; pd[2] = ZoneByte; pd[3] = (u8)tRunPara.byAddr; pd[4] = pd[2] + pd[3]; pd[5] = 0x16; } else { pd[0] = 6; pd[1] = 0x10; pd[2] = ZoneByte; pd[3] = (u8)tRunPara.byAddr; pd[4] = (u8)(tRunPara.byAddr >> 8); pd[5] = pd[2] + pd[3] + pd[4]; pd[6] = 0x16; } } /************************************************************************** 函数名称:IEC101_GetClassOne 函数版本:1.00 作者: 创建日期:2008.9.1 函数功能说明:从一级数据缓冲区里读取数据 输入参数: 输出参数: 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ static void IEC101_UpdateCOS(u8 *pd) { u8 sq, num, i, ti; u16 cp; u8 addr = 0; // 传送原因双字节 u8 SIQ; if (tRunPara.b101Cot2Byte) addr++; if (tRunPara.b101App2Byte) addr++; ti = pd[0]; sq = pd[1] & 0x80; num = pd[1] & 0x7f; cp = pd[4 + addr] | (pd[5 + addr] << 8); pd += (6 + addr); for (i = 0; i < num; i++) { SIQ = tbl_get_yx(tbl_cp2index(cp), (ti == 3 ? 1 : 0)); #if !defined FUNC_JX_YX_BEFORE if (g_run_stu.bjx) SIQ |= 0x80; #endif *pd++ = SIQ; if (sq == 0) { cp = pd[0] | (pd[1] << 8); pd += 2; } else { cp++; } } } // 101可变帧长中信息体相对于帧头的地址。 u32 IEC101_InfoAddr(void) { u32 addr; // 10 = 68 L L 68 CTRL LADDR TYPE VSQ COT PADDR addr = 10; if (tRunPara.b101Addr2Byte) addr++; if (tRunPara.b101App2Byte) addr++; if (tRunPara.b101Cot2Byte) addr++; return addr; } static bool IEC101_SendOneBuf(IEC101_DEF *pt101) { u16 i, len, frm_attr, cnt; u8 *pd, cs, tb_send; iec_tbuf_t *pt; // 还有上帧数据没有发送完,直接返回。逻辑上不应该有这种现象出现 if (tRunPara.tUartPara[pt101->chnl].b101PH ? pt101->btx_buf_m : pt101->btx_buf_s) { RT_PRINTF_POSITION(); return false; } // 运行到这,如果有数据就无条件发送 for (i = 0; i < 3; i++) { pt = &pt101->tx_buf[i]; if (pt->tb_head == pt->tb_tail_send) { continue; } // 更新发送尾指针,保留老指针备用。 // 由于系统设计时保证发送BUF队列不会溢出,所以指针先加也是没有问题的。 tb_send = pt->tb_tail_send; pt->tb_tail_send++; // 拷贝数据并设置控制域 len = pt->tb_data[tb_send][0]; if (tRunPara.tUartPara[pt101->chnl].b101PH) { u8 zone; // 获取数据 pd = pt101->tx_buf_m; memcpy(pd, pt->tb_data[tb_send], len + 1); // 平衡101,将控制域修改,原来一级数据缓冲区中为8,现在需要变为3 zone = IEC101_CALL_DATA; // 合纵FTU现场,测试长帧,功能码需回2,不能是3 (原来为3,不合理) if (pd[7 + tRunPara.b101Addr2Byte] == 104) // 长帧测试帧 { zone = IEC101_CALL_TESTLINK; pt101->bTestFrameL = true; } // 梅州FTU,101主站总召前,通讯续传不回确认帧 if ((pd[7 + tRunPara.b101Addr2Byte] == 0x1e) && (pt101->bSendChange == 0)) // SOE { pt101->bSoeResume = true; } IEC101_SETFCV(zone); // FCV位有效 IEC101_SETPRM(zone); if (!pt101->bDIR) IEC101_SETDIR(zone); if (pt101->bMFCB) { IEC101_SETFCB(zone); } else { IEC101_CLRFCB(zone); } pd[5] = zone; } else { // 获取数据 pd = pt101->tx_buf_s; memcpy(pd, pt->tb_data[tb_send], len + 1); // 目前所有数据都当成一级数据,等新标准正式颁布后再分一、二级数据。 // 非平衡101,如果还有一级数据,设置ACD位 if ((pt101->tx_buf[0].tb_head != pt101->tx_buf[0].tb_tail_send) || (pt101->tx_buf[1].tb_head != pt101->tx_buf[1].tb_tail_send) || (pt101->tx_buf[2].tb_head != pt101->tx_buf[2].tb_tail_send)) { IEC101_SETACD(pd[5]); } } // 更新低优先级的COS frm_attr = pt->tb_info[tb_send].frm_attr; if ((frm_attr & (FRAME_I_PRIOR_LOW | FRAME_I_COS)) == (FRAME_I_PRIOR_LOW | FRAME_I_COS)) { IEC101_UpdateCOS(pd + 7 + tRunPara.b101Addr2Byte); } // 如果是延时获取帧,校验和计算前计算延时 if (frm_attr & FRAME_I_106) { u16 ms; u8 *info_addr; info_addr = pd + 1 + IEC101_InfoAddr(); ms = info_addr[2] + (info_addr[3] << 8); ms += ustimer_get_duration(pt101->us0_frame) / USTIMER_MS; info_addr[0] = 0; info_addr[1] = 0; info_addr[2] = ms; info_addr[3] = ms >> 8; } // 计算校验和 cs = 0; len = pd[2]; pd = &pd[5]; for (cnt = 0; cnt < len; cnt++) // 标示拷贝 { cs += *pd++; } *pd++ = cs; // 保留发送的索引,供确认的时候用 pt101->tb_index = i; // 设置安全报文发送标志 if (frm_attr & FRAME_I_ENCRYPT) { pt101->bMSend_encrypt = true; } return true; } return false; } static void IEC101_AckOneBuf(IEC101_DEF *pt101) // 调整一级数据缓冲区,已发送数据清除 { iec_tbuf_t *pt; char log_info[128] = {0}; // 如果最后发送的遥测帧,确认遥测帧 if (pt101->bycsend) { pt101->bycsend = FALSE; return; } // 如果没有发送报文,不需要确认 if (pt101->tb_index < 0) { return; } // 如果没有需要确认的帧,返回,但这不是正常流程 pt = &pt101->tx_buf[pt101->tb_index]; pt101->tb_index = -1; if (pt->tb_tail_ack == pt->tb_tail_send) { rt_printf_time("IEC101_AckOneBuf error!\r\n"); IEC101_Info_Printf_One(pt101); sprintf(log_info, "IEC101(%d):IEC101_AckOneBuf error!", pt101->chnl + 1); load_hs_log_rcd(TYPE_COMM_ERR, true, NULL, log_info, 1); log_flag.com_err[pt101->chnl] = true; return; } // 如果是SOE报文,需确认SOE缓冲池中SOE. if (pt->tb_info[pt->tb_tail_ack].frm_attr & FRAME_I_SOE) { soe_ack(pt101->chnl, pt->tb_info[pt->tb_tail_ack].soe_tail_send); } pt->tb_tail_ack++; } /************************************************************************** 函数名称: IEC101_Asdu_Yc 函数版本:1.00 作者: 创建日期:2008.9.1 函数功能说明:通用分类数据的链路直接响应 输入参数:pt 缓冲区地址,pdat报文数据,len报文长度 vsq,inf,cot ,rii,IEC101规约要求的内容 输出参数: 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ static void IEC101_Asdu_Yc(IEC101_DEF *pt, u8 *pdat, u8 len, u8 vsq, u8 cot) { u8 *pd, *pdata; u8 cnt, checksum, sum; u8 AddByte = 0; sum = 0; if (tRunPara.tUartPara[pt->chnl].b101PH) { pdata = pt->tx_buf_m; } else { pdata = pt->tx_buf_s; } if (tRunPara.b101Addr2Byte) AddByte = 1; pd = &pdata[7 + AddByte]; if (tRunPara.bYcBDH) // 标度化值 { pd[sum++] = 11; // 类型标示 } else if (tRunPara.bYcFloat) // 浮点值 { pd[sum++] = 13; // 类型标示浮点数 } else { pd[sum++] = 9; // 类型标示 } pd[sum++] = vsq; // VSQ pd[sum++] = cot; // 传送原因 if (tRunPara.b101Cot2Byte) pd[sum++] = 0; // 传送原因 pd[sum++] = (u8)tRunPara.byAddr; // 应用服务单元地址 if (tRunPara.b101App2Byte) pd[sum++] = (u8)(tRunPara.byAddr >> 8); // 应用服务单元地址 for (cnt = 0; cnt < len; cnt++) // 标示拷贝 { pd[sum++] = *pdat++; } pdata[0] = (sum + 2 + 6 + AddByte); // 报文长度 +2=控制域、地址 +6=报文头(4字节)校验和、结束符 pdata[1] = 0x68; // 报文头 pdata[2] = sum + 2 + AddByte; // 数据长度 pdata[3] = sum + 2 + AddByte; pdata[4] = 0x68; if (tRunPara.tUartPara[pt->chnl].b101PH) { pdata[5] = IEC101_CALL_DATA; IEC101_SETFCV(pdata[5]); // FCV位有效 IEC101_SETPRM(pdata[5]); if (!pt->bDIR) IEC101_SETDIR(pdata[5]); if (pt->bMFCB) { IEC101_SETFCB(pdata[5]); } else { IEC101_CLRFCB(pdata[5]); } } else { pdata[5] = IEC101_ECHO_DATA; // 类型标示 } pdata[6] = (u8)tRunPara.byAddr; // 链路地址 if (tRunPara.b101Addr2Byte) pdata[7] = (u8)(tRunPara.byAddr >> 8); // 链路地址 checksum = 0; pd = &pdata[5]; for (cnt = 0; cnt < pdata[2]; cnt++) // 标示拷贝 { checksum += *pd++; } *pd++ = checksum; *pd++ = 0x16; } /************************************************************************** 函数名称:IEC101_GetYc 函数版本:1.00 作者: 创建日期:2008.9.1 函数功能说明:响应遥测帧 输入参数: 输出参数: 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ static bool IEC101_GetYc(IEC101_DEF *pt) { bool bContinue = false; // 是否保存当前判断的遥测位置 u8 *pd; u8 vsq; int i, num; WORD cp = 0; int ycvalue; union { float ff; u8 tt[4]; } ff; // 主动上送遥测条件: // 1、总查询结束后可送 // 2、没有在处理加密遥控报文 // 3、不是复位进程状态 // 4、主动上送遥测投入 if ((!pt->bSendChange) || pt->bRmtSM2 || pt->bResetProcess || (!tRunPara.bAutoSendYc)) { return false; } // 平衡101,主动发送不能小于发送间隔时间 if (tRunPara.tUartPara[pt->chnl].b101PH) { if ((ustimer_get_duration(pt->us0_yc) < (DWORD)rt_round(tRunPara.fYcTime * USTIMER_SEC)) || byc_filter) // { return false; } } #if defined IEC_JXYB_DEAL if (g_run_stu.bjx) return false; #endif // 得到一遥测帧 pd = g_arrIECBuf; vsq = 0; num = 0; for (i = pt->ycno; i < g_table_head->ac_num; i++) { u8 QDS = 0; // 品质描述, 默认为零 u8 no = g_ac_table[i].indexno; long lmeaval; bool bsend = false; if (g_ac_table[i].dead_value == 0 || g_ac_table[i].normal == 0) continue; // 死区或额定为零,不主动上送 if (no > 0) { DWORD dwMax = 0; DWORD dwDeadVal = 0; float rate; float f_65536 = 65536.0; u8 link_qds = 0; no -= 1; rate = (float)g_ac_table[i].rate / 65536.0; // dwMax=(DWORD)g_ac_table[i].normal; dwMax = (tRunPara.bDeadType == true) ? (DWORD)g_ac_table[i].normal : _AbsL(pt->yc_save[i]); if (dwMax == 0) // 防止额定或者前一次保存值为0 dwMax = 1; dwDeadVal = _Mul_Div_U(g_ac_table[i].dead_value, dwMax, 6553600); if (dwDeadVal == 0) dwDeadVal = 1; if (dwMax == 0) f_65536 = 1.0; lmeaval = GetRmtMeaVal(g_ac_table[i].owner, no, &link_qds); ff.ff = (float)lmeaval / f_65536 * rate; // 转为浮点数 #ifdef VOLT_ADAPTIVE_FACTOR if (pRunSet->bTT_AdaptiveFactor) { if (0 == g_ac_table[i].owner) { if ((no <= PUB_AC_UCA1) && (no >= PUB_AC_UA1)) { ff.ff = ((float)lmeaval) / f_65536 * 10 / pRunSet->a_pt_two; } else if ((no <= PUB_AC_UCA2) && (no >= PUB_AC_UA2)) { ff.ff = ((float)lmeaval) / f_65536 * 10 / pRunSet->b_pt_two; } } } #endif ycvalue = (int)rt_round(ff.ff); // 溢出处理 if (tRunPara.bYcFloat) { if (_AbsL(lmeaval) > (dwMax * 10)) // 大于额定值的10倍*65536 { if (dwMax > 0) QDS |= 0x01; // 遥测溢出 } } else { if (ycvalue >= 0) { if (ycvalue > 32767) { ycvalue = 32767; QDS |= 0x01; // 遥测溢出 } } else { if (ycvalue < (-32768)) { ycvalue = -32768; QDS |= 0x01; // 遥测溢出 } } } #if (0) /* 会一直突发上送 */ if (((no == PUB_AC_YC9) || (no == PUB_AC_YC10) || (no == PUB_AC_YC11) || (no == PUB_AC_YC12))) { bsend = true; } #endif // 如果大于死区值,上送 #ifndef IEC_JXYB_DEAL if (bsend || ((i < MAX_YC_NUMBER) && (_AbsL(lmeaval - pt->yc_save[i]) >= dwDeadVal))) #else if (bsend || ((i < MAX_YC_NUMBER) && (_AbsL(lmeaval - pt->yc_save[i]) >= dwDeadVal) && (link_qds != 0x80))) #endif { if (g_run_stu.bToolRmtTest || g_run_stu.bHmiRmtTest) // 在测试模式下,置无效状态 { if (pRunSet->bTT_RmtTest) QDS |= 0x80; } cp = g_ac_table[i].cp; if (i < MAX_YC_NUMBER) pt->yc_save[i] = lmeaval; pd[num++] = (u8)cp; // 信息体地址L pd[num++] = (u8)(cp >> 8); // 信息体地址H if (tRunPara.bYcFloat) // 浮点值上送 { num += CopySwap(&pd[num], ff.tt, sizeof(ff.tt), true); } else { pd[num++] = (u8)(ycvalue); // 无符号整数 pd[num++] = (u8)(ycvalue >> 8); // 无符号整数 } if (g_run_stu.bjx) { QDS |= 0x80; // 带品质描述,有效 <1>:=无效 } #ifdef IEC_QDS_HAVE QDS |= link_qds; #endif pd[num++] = QDS; vsq++; } if (num > MAX_101_FRAME_LENTH) { bContinue = true; break; } } } // 更新下一帧的遥测序号 pt->ycno = bContinue ? (i + 1) : 0; // 发送遥测帧 if (num > 0) { IEC101_Asdu_Yc(pt, g_arrIECBuf, num, vsq, 3); pt->us0_yc = ustimer_get_origin(); return true; } return false; } static void IEC101_LinkInf(IEC101_DEF *pt) { // 68 bc bc 68 28 01 f0 08 14 01 01 40 00 00 00 00 00 00 00 00 00 00 00 00 00 遥测帧 // vsq cot appaddr u8 *pd, *pdata; u8 checksum, sum; int i; WORD cp; u8 num; WORD count = 0; pdata = pt->tx_buf_s; sum = 0; pd = &pdata[5]; pd[sum++] = IEC101_ECHO_DATA; // 类型标示 pd[sum++] = (u8)tRunPara.byAddr; // 链路地址 pd[sum++] = 250; // 类型标示 pd[sum++] = 8; // 长度 pd[sum++] = 14; // 传送原因 pd[sum++] = 1; // KOD pd[sum++] = 0; // 统计遥测遥信个数 cp = g_di_table[0].cp; num = 0; for (i = 0; i < g_table_head->di_num; i++) // 遥信个数统计 { if (g_di_table[i].cp == (cp + num)) { num++; } else { pd[sum++] = 1; // 遥信类型 pd[sum++] = num; pd[sum++] = (u8)cp; // 遥信个数 pd[sum++] = (u8)(cp >> 8); // 遥信个数 count++; cp = g_di_table[i].cp; num = 1; } } if (num) { pd[sum++] = 1; // 遥信类型 pd[sum++] = num; pd[sum++] = (u8)cp; // 遥信点号 pd[sum++] = (u8)(cp >> 8); // 遥信点号 count++; } cp = g_ac_table[0].cp; num = 0; for (i = 0; i < g_table_head->ac_num; i++) // 遥测个数统计 { if (g_ac_table[i].cp == (cp + num)) { num++; } else { pd[sum++] = 2; // 遥测类型 pd[sum++] = num; pd[sum++] = (u8)cp; // 遥测个数 pd[sum++] = (u8)(cp >> 8); // 遥测个数 count++; cp = g_ac_table[i].cp; num = 1; } } if (num) { pd[sum++] = 2; // 遥测类型 pd[sum++] = num; pd[sum++] = (u8)cp; // 遥测个数 pd[sum++] = (u8)(cp >> 8); // 遥测个数 count++; } pdata[0] = sum + 6; // 报文长度 +2=控制域、地址 +6=报文头(4字节)校验和、结束符 pdata[1] = 0x68; // 报文头 pdata[2] = sum; // 数据长度 pdata[3] = sum; pdata[4] = 0x68; pdata[11] = count; checksum = 0; pd = &pdata[5]; for (i = 0; i < sum; i++) // 标示拷贝 { checksum += *pd++; } *pd++ = checksum; *pd++ = 0x16; } /************************************************************************** 函数名称:IEC101_Asdu_Add 函数版本:1.00 作者: 创建日期:2008.9.1 函数功能说明:将通用分类数据添加到一级数据缓冲区中 输入参数:pt 缓冲区地址,pdat报文数据,len报文长度 vsq,inf,cot ,rii,IEC101规约要求的内容 输出参数: 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ static void IEC101_Asdu_Add_sts(IEC101_DEF *pt, u8 *pdat, u8 len, u8 vsq, u8 cot, u8 type, u16 attr, u16 soe_tail_send) { u8 *pd; u8 cnt, L; u16 head; iec_tbuf_t *ptb; // 获取BUF ptb = &pt->tx_buf[1]; if (attr & FRAME_I_PRIOR_HIGH) ptb = &pt->tx_buf[0]; else if (attr & FRAME_I_PRIOR_LOW) ptb = &pt->tx_buf[2]; head = IEC101_GetOneBuf(ptb); // 保存数据 ptb->tb_info[head].frm_attr = attr; ptb->tb_info[head].soe_tail_send = g_soe_queue.tail_send[pt->chnl].n; L = IEC101_InfoAddr() + len - 4; // 报文生成 pd = ptb->tb_data[head]; *pd++ = L + 6; // 报文长度 +2=控制域、地址 +6=报文头(4字节)校验和、结束符 *pd++ = 0x68; // 报文头 *pd++ = L; // 数据长度 *pd++ = L; *pd++ = 0x68; *pd++ = IEC101_ECHO_DATA; // 非平衡功能码,平衡通道发送时再修改 *pd++ = (u8)tRunPara.byAddr; // 链路地址1 if (tRunPara.b101Addr2Byte) { *pd++ = (u8)(tRunPara.byAddr >> 8); // 链路地址2 } *pd++ = type; // 类型标示 *pd++ = vsq; // VSQ *pd++ = cot; // 传送原因1 if (tRunPara.b101Cot2Byte) { *pd++ = 0; // 传送原因2 } *pd++ = (u8)tRunPara.byAddr; // 应用服务单元地址1 if (tRunPara.b101App2Byte) { *pd++ = (u8)(tRunPara.byAddr >> 8); // 应用服务单元地址2 } for (cnt = 0; cnt < len; cnt++) { *pd++ = *pdat++; // 信息体 } *pd++ = 0; // 校验和,推迟计算 *pd++ = 0x16; // 结束字符 return; } void IEC101_Asdu_Add(IEC101_DEF *pt, u8 *pdat, u8 len, u8 vsq, u8 cot, u8 type, u16 attr) { IEC101_Asdu_Add_sts(pt, pdat, len, vsq, cot, type, attr, 0); } /************************************************************************** 函数名称:IEC101_TimeSet 函数版本:1.00 作者: 创建日期:2008.9.1 函数功能说明:时间同步,并应答 输入参数: 输出参数: 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ static void IEC101_TimeSetEcho(IEC101_DEF *pt101, u8 *ps, u8 scot) // 确认帧 { u8 *pd; int num; int check_time = false; u8 cot; TIME ct; struct timespec ts; u8 week; // 对时 if (scot == IEC_COT_ACT) { ct.ms = ps[0] + ((WORD)ps[1] << 8); ct.min = (ps[2] & 0x3F); ct.hour = (ps[3] & 0x1F); ct.day = (ps[4] & 0x1F); ct.month = (ps[5] & 0x0F); ct.year = (ps[6] & 0x7F); // 检查时间,如果合法,重新设置系统时间 check_time = check_rtc(&ct); if (check_time) { u32 ms; // 计算延时,传输延时T2和发送该报文的传输耗时T3 ms = ustimer_get_duration(pt101->us0_frame) / USTIMER_MS; // rt_printf("时间设置:T2=%dms,T3=%dms.\r\n",pt101->ms_delay,ms); ms += pt101->ms_delay; // 修正时间 ct.ms += ms; if (ct.ms >= 60000) { ct.ms -= 60000; sys_time_change(&ct); } // 设置时间 sys_time_set_rmt(&ct); } } clk_time_get(&ts); timespec_to_rtc(ts, &ct, 1); // 得到星期 rtc_to_timespec(&ct, &ts); week = WEEK_DAY(ts.tv_sec); // 组织报文,以最新时间返回 pd = g_arrIECBuf; num = 0; #if defined GD_AREA_ZHONGSHAN || defined GD_AREA_ZHONGSHAN_2020 pd[num++] = 0; // 信息序号=0时钟同步 pd[num++] = 0; // 信息序号=0时钟同步 if (scot == IEC_COT_ACT) { pd[num++] = ps[0]; // MsL pd[num++] = ps[1]; // MsH pd[num++] = ps[2]; // Min pd[num++] = ps[3]; // hour pd[num++] = ps[4]; // Day pd[num++] = ps[5]; // month pd[num++] = ps[6]; // year } else { pd[num++] = (u8)ct.ms; // MsL pd[num++] = (u8)(ct.ms >> 8); // MsH pd[num++] = ct.min; // Min pd[num++] = ct.hour; // hour pd[num++] = ct.day; // Day pd[num++] = ct.month; // month pd[num++] = ct.year; // year } #else pd[num++] = 0; // 信息序号=0时钟同步 pd[num++] = 0; // 信息序号=0时钟同步 pd[num++] = (u8)ct.ms; // MsL pd[num++] = (u8)(ct.ms >> 8); // MsH pd[num++] = ct.min; // Min pd[num++] = ct.hour; // hour pd[num++] = ((week << 5) | ct.day); // Day pd[num++] = ct.month; // month pd[num++] = ct.year; // year #endif if (scot == IEC_COT_REQ) { cot = 0x05; } else { cot = check_time ? 0x07 : 0x47; // 对时内容非法,即将传送原因的P/N位置为1 } IEC101_Asdu_Add(pt101, g_arrIECBuf, num, 1, cot, 103, FRAME_I); // } static void IEC101_TestFrame(IEC101_DEF *pt101, u8 *ps) // 测试确认帧 { u8 *pd; int num; // 组织报文 pd = g_arrIECBuf; num = 0; pd[num++] = 0; // 信息体地址 pd[num++] = 0; // pd[num++] = ps[0]; // pd[num++]=0xAA; //测试码 pd[num++] = ps[1]; // pd[num++]=0x55; //测试码 IEC101_Asdu_Add(pt101, g_arrIECBuf, num, 1, 7, 104, FRAME_I | FRAME_I_PRIOR_LOW); // } /************************************************************************** 函数名称:IEC101_InitOK 函数版本:1.00 作者: 创建日期:2007.9.1 函数功能说明:子站初始化完毕 输入参数: 输出参数: 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ static void IEC101_InitOK(IEC101_DEF *pt101) { u8 *pd; int num; u8 cot; // 组织报文 pd = g_arrIECBuf; num = 0; pd[num++] = 0; pd[num++] = 0; if (pt101->bInitCot) { pd[num++] = 2; // 远方复位 } else { pd[num++] = 0; // 当地电源合上 } pt101->bInitCot = true; cot = pRunSet->bTT_101Cot03 ? 3 : 4; IEC101_Asdu_Add(pt101, g_arrIECBuf, num, 1, cot, 70, FRAME_I); // 70 初始化结束帧 } // 复位进程 static void IEC101_ResetProcess(IEC101_DEF *pt101, u8 qrp) { u8 *pd; int num; // 组织报文 pd = g_arrIECBuf; num = 0; pd[num++] = 0; pd[num++] = 0; pd[num++] = qrp; IEC101_Asdu_Add(pt101, g_arrIECBuf, num, 1, 7, 105, FRAME_I); } /************************************************************************** 函数名称:IEC101_CallAll 函数版本:1.00 作者: 创建日期:2008.9.1 函数功能说明:通用范围总查询响应 输入参数: 输出参数: 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ static void IEC101_CallAll(IEC101_DEF *pt, u8 QOI) // 确认帧 { u8 *pd, *pdata; u8 vsq; // 通用分类数据集数目 u16 cp = 0, attr; int i, num, begin, end; pdata = g_arrIECBuf; pt->ycno = 0; attr = pt->bSendChange ? FRAME_I_PRIOR_LOW : FRAME_I; //| FRAME_I_PRIOR_HIGH // 总召唤确认帧 num = 0; pd = pdata; pd[num++] = 0; pd[num++] = 0; pd[num++] = QOI; IEC101_Asdu_Add(pt, pdata, num, 1, 7, 100, FRAME_I); // 遥测帧 if ((QOI == IEC_QOI_CALL_ALL_GROUP) | ((QOI >= IEC_QOI_CALL_YC_BEGIN && QOI <= IEC_QOI_CALL_YC_END))) { int ycvalue; u8 ti; union { float ff; u8 tt[4]; } ff; long lmeaval; // 类型标识 if (tRunPara.bYcBDH) { ti = 11; // 标度化值 } else if (tRunPara.bYcFloat) { ti = 13; // 浮点值 } else { ti = 9; // 归一化值 } // 确认起止点号 if (QOI == IEC_QOI_CALL_ALL_GROUP || pRunSet->dGroupYcSize == 0) { begin = 0; end = g_table_head->ac_num; } else if (pRunSet->dGroupYcSize) { begin = (QOI - IEC_QOI_CALL_YC_BEGIN) * pRunSet->dGroupYcSize; end = begin + pRunSet->dGroupYcSize; if (end > g_table_head->ac_num) { end = g_table_head->ac_num; } } // 得到第一点的通讯点号,必须检查g_ac_table,否则有可能系统崩溃 if (g_ac_table) { cp = g_ac_table[begin].cp; } // 初始数据 pd = pdata; vsq = 0; num = 0; // 如果点号连续,保存第一点点号 if (pRunSet->bDIContinue) { pd[num++] = (u8)cp; pd[num++] = (u8)(cp >> 8); } // 发送begin到end之间的所有遥测值 for (i = begin; i < end; i++) { u8 QDS = 0; // 品质描述, 默认为零 u8 link_qds = 0; u8 no = g_ac_table[i].indexno; // 得到遥测值 if (no > 0) { DWORD dwMax = 0; float rate; float f_65536 = 65536.0; no -= 1; rate = (float)g_ac_table[i].rate / 65536.0; // dwMax=(DWORD)g_ac_table[i].normal; dwMax = (tRunPara.bDeadType == true) ? (DWORD)g_ac_table[i].normal : _AbsL(pt->yc_save[i]); if (dwMax == 0) // 防止额定或者前一次保存值为0 dwMax = 1; lmeaval = GetRmtMeaVal(g_ac_table[i].owner, no, &link_qds); if (dwMax == 0) f_65536 = 1.0; ff.ff = ((float)lmeaval) / f_65536 * rate; // 转为浮点数 #ifdef VOLT_ADAPTIVE_FACTOR if (pRunSet->bTT_AdaptiveFactor) { if (0 == g_ac_table[i].owner) { if ((no <= PUB_AC_UCA1) && (no >= PUB_AC_UA1)) { ff.ff = ((float)lmeaval) / f_65536 * 10 / pRunSet->a_pt_two; } else if ((no <= PUB_AC_UCA2) && (no >= PUB_AC_UA2)) { ff.ff = ((float)lmeaval) / f_65536 * 10 / pRunSet->b_pt_two; } } } #endif ycvalue = (int)rt_round(ff.ff); if (tRunPara.bYcFloat) { if (_AbsL(lmeaval) > (dwMax * 10)) // 大于额定值的10倍*65536 { if (dwMax > 0) QDS |= 0x01; // 遥测溢出 } } else { if (ycvalue >= 0) { if (ycvalue > 32767) { ycvalue = 32767; QDS |= 0x01; // 遥测溢出 } } else { if (ycvalue < (-32768)) { ycvalue = -32768; QDS |= 0x01; // 遥测溢出 } } } } else // 备用点号,数值上送0 { lmeaval = 0; ycvalue = 0; ff.ff = 0; if (!pRunSet->bDIContinue) continue; // 点号不连续上送,可不送备用点 } // 如果在测试模式下,置无效状态 if (g_run_stu.bToolRmtTest || g_run_stu.bHmiRmtTest) { if (pRunSet->bTT_RmtTest) QDS |= 0x80; // 带品质描述,有效 <1>:=无效 } #if !defined FUNC_JX_YC_SIQ00 if (g_run_stu.bjx) { QDS |= 0x80; // 带品质描述,有效 <1>:=无效 } #endif #ifdef IEC_QDS_HAVE QDS |= link_qds; #endif // 保存遥测值,供主动遥测上送比较用 if (i < MAX_YC_NUMBER) pt->yc_save[i] = lmeaval; // 如果点号不连续,上送每个点的点号 if (!pRunSet->bDIContinue) { cp = g_ac_table[i].cp; pd[num++] = (u8)cp; pd[num++] = (u8)(cp >> 8); } // 上送值 if (tRunPara.bYcFloat) { // 浮点值上送 num += CopySwap(&pd[num], ff.tt, sizeof(ff.tt), true); } else { // 整型 pd[num++] = (u8)(ycvalue); pd[num++] = (u8)(ycvalue >> 8); } pd[num++] = QDS; vsq++; // 发送一帧 if (num > MAX_101_FRAME_LENTH && (i < g_table_head->ac_num - 1)) { u8 vsq1 = vsq; if (pRunSet->bDIContinue) { vsq1 |= 0x80; } IEC101_Asdu_Add(pt, pdata, num, (vsq1), QOI, ti, attr); num = 0; pd = pdata; if (pRunSet->bDIContinue) { pd[num++] = (u8)(cp + vsq); pd[num++] = (u8)((cp + vsq) >> 8); cp += vsq; } vsq = 0; } } if (num > 2) { u8 vsq1 = vsq; if (pRunSet->bDIContinue) { vsq1 |= 0x80; } IEC101_Asdu_Add(pt, pdata, num, (vsq1), QOI, ti, attr); } pt->us0_yc = ustimer_get_origin(); } // 事件帧 if ((QOI == IEC_QOI_CALL_ALL_GROUP) || (QOI >= IEC_QOI_CALL_YX_BEGIN && QOI <= IEC_QOI_CALL_YX_END)) { u8 ti; u8 yxtype = 0; // 确定类型标识,单点(1)或双点(3) ti = tRunPara.bDPI ? 3 : 1; // 确定起止点号 if (QOI == IEC_QOI_CALL_ALL_GROUP || pRunSet->dGroupYxSize == 0) { begin = 0; end = g_table_head->di_num; } else { begin = (QOI - IEC_QOI_CALL_YX_BEGIN) * pRunSet->dGroupYxSize; end = begin + pRunSet->dGroupYxSize; if (end > g_table_head->di_num) { end = g_table_head->di_num; } } // 初始数据 pd = pdata; vsq = 0; num = 0; // 得到第一点的通讯点号,必须检查g_di_table,否则有可能系统崩溃 if (g_di_table) { cp = g_di_table[begin].cp; yxtype = tbl_get_dpi(begin); } // 如果点号连续,保存第一点点号 if (pRunSet->bDIContinue) { pd[num++] = (u8)cp; pd[num++] = (u8)(cp >> 8); } // 发送begin到end之间的所有遥信值 for (i = begin; i < end; i++) { u8 SIQ; // 得到遥信值 if (tRunPara.bSDPI) // 单双点分别上送 { u8 type = tbl_get_dpi(i); if (yxtype != type && num > 2) { u8 vsq2 = vsq; u8 ti2 = yxtype ? 3 : 1; yxtype = type; if (pRunSet->bDIContinue) { vsq2 |= 0x80; } IEC101_Asdu_Add(pt, pdata, num, (vsq2), QOI, ti2, attr | FRAME_I_COS); // cot=9总查询 num = 0; pd = pdata; if (pRunSet->bDIContinue) { pd[num++] = (u8)(cp + vsq); pd[num++] = (u8)((cp + vsq) >> 8); cp += vsq; } vsq = 0; yxtype = type; } SIQ = tbl_get_yx(i, type); ti = type ? 3 : 1; } else { SIQ = tbl_get_yx(i, tRunPara.bDPI); } #if !defined FUNC_JX_YX_BEFORE if (g_run_stu.bjx) { SIQ |= 0x80; // 带品质描述,有效 <1>:=无效 } #endif if (g_run_stu.bToolRmtTest || g_run_stu.bHmiRmtTest) // 测试模式,遥信置为无效 { if (pRunSet->bTT_RmtTest) SIQ |= 0x80; } // 点号 if (!pRunSet->bDIContinue) { cp = g_di_table[i].cp; pd[num++] = (u8)cp; pd[num++] = (u8)(cp >> 8); } pd[num++] = SIQ; vsq++; if (((vsq > 120) || (num > MAX_101_FRAME_LENTH)) && (i < g_table_head->di_num - 1)) // ngd不能超过128,因个数最高位为连续标示位 { u8 vsq1 = vsq; if (pRunSet->bDIContinue) { vsq1 |= 0x80; } IEC101_Asdu_Add(pt, pdata, num, (vsq1), QOI, ti, attr | FRAME_I_COS); // cot=9总查询 num = 0; pd = pdata; if (pRunSet->bDIContinue) { pd[num++] = (u8)(cp + vsq); pd[num++] = (u8)((cp + vsq) >> 8); cp += vsq; } vsq = 0; } } if (num > 2) { u8 vsq1 = vsq; if (pRunSet->bDIContinue) { vsq1 |= 0x80; } IEC101_Asdu_Add(pt, pdata, num, (vsq1), QOI, ti, attr | FRAME_I_COS); // cot=9总查询 } } // 总召唤结束帧 num = 0; pd = pdata; pd[num++] = 0; pd[num++] = 0; pd[num++] = QOI; IEC101_Asdu_Add(pt, pdata, num, 1, 10, 100, attr); // 不允许通讯续传的情况下,第一次总召先清SOE队列,保证上传事件是总召后产生的,后续总召不清SOE队列 if ((pRunSet->bTT_SoeResumeComm == 0) && (!pt->bSendChange)) { g_soe_queue.tail_send[pt->chnl].n = g_soe_queue.head.n; g_soe_queue.tail_ack[pt->chnl].n = g_soe_queue.tail_send[pt->chnl].n; } #ifdef FUN_SOE_DELAY_TIME if (pt->bSendChange == false) { pt->DTSendChange = dTCounter; pt->bfirst_call = true; } #endif // 置总召标志 pt->bSendChange = true; } static void IEC101_DDAll(IEC101_DEF *pt, u8 QCC) // 确认帧 { u8 *pd, *pdata; u8 vsq, cot; // 通用分类数据集数目 u16 cp; int i, num; pdata = g_arrIECBuf; num = 0; // 总召唤确认帧 pd = pdata; pd[num++] = 0; pd[num++] = 0; pd[num++] = QCC; IEC101_Asdu_Add(pt, pdata, num, 1, 7, 101, FRAME_I); // 电度值 if ((QCC == 1) || (QCC == 5)) { // 初始数据 pd = pdata; vsq = 0; num = 0; cp = g_dd_table[0].cp; if (QCC == 5) cot = 37; // 总召 else cot = QCC + 37; // 召某一组 pd[num++] = (u8)cp; pd[num++] = (u8)(cp >> 8); for (i = 0; i < g_table_head->dd_num; i++) { int sw, no; float rate; union { float ff; u8 tt[4]; } ff; sw = g_dd_table[i].owner; no = g_dd_table[i].indexno; if (no > 0) { rate = (float)g_dd_table[i].rate / 65536.0; ff.ff = GetRmtDdVal(sw, no - 1) * rate; } else { ff.ff = 0; } num += CopySwap(&pd[num], ff.tt, sizeof(ff.tt), true); pd[num++] = 0; vsq++; // 发送一帧 if ((num > MAX_101_FRAME_LENTH) || (i == g_table_head->dd_num - 1)) { IEC101_Asdu_Add(pt, pdata, num, (vsq | 0x80), cot, 206, FRAME_I_PRIOR_LOW); cp += vsq; pd = pdata; num = 0; vsq = 0; pd[num++] = (u8)cp; pd[num++] = (u8)(cp >> 8); } } } if (QCC == 0x45) // 主站扩展瞬时冻结电度命令 { // rand_file_info.b_frz=true; } // 总召唤结束帧 num = 0; pd = pdata; pd[num++] = 0; pd[num++] = 0; pd[num++] = QCC; IEC101_Asdu_Add(pt, pdata, num, 1, 10, 101, FRAME_I_PRIOR_LOW); } /************************************************************************** 函数名称:IEC101_RmtCtrl 函数版本:1.00 作者: 创建日期:2007.9.1 函数功能说明:遥控处理 输入参数: 点号开始21 0b 00 82 (10-13) 输出参数: 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ void IEC101_Echo_YK(IEC101_DEF *pt, u16 cp, u8 ti, u8 cot, u8 v) { u8 *pd, *pdata; int num; pdata = g_arrIECBuf; pd = pdata; num = 0; pd[num++] = (u8)cp; pd[num++] = (u8)(cp >> 8); pd[num++] = v; IEC101_Asdu_Add(pt, pdata, num, 1, cot, ti, FRAME_I | FRAME_I_PRIOR_HIGH); // cot=9总查询 pt->bRmtSM2 = false; // 遥控执行完成,清除SM2标志,开放事件上传 } void IEC101_Echo_PAR(IEC101_DEF *pt, u16 cp, u8 ti, u8 cot, BYTE *buf, u8 len, u8 vsq) { u8 *pd, *pdata; int num; pdata = g_arrIECBuf; pd = pdata; num = 0; // pd[num++]=(u8)cp; // pd[num++]=(u8)(cp>>8); memcpy(&pd[num], buf, len); num += len; // pd[num++]=v; IEC101_Asdu_Add(pt, pdata, num, vsq, cot, ti, FRAME_I); // cot=9总查询 } int get_yk_info_cp(u8 cp) { int i; for (i = 0; i < g_table_head->do_num; i++) { // rt_printf("cp=%X link_ch=%x cp=%x \r\n",cp,(g_do_table[i].link_ch&0x0f),g_do_table[i].link_cp); if ((g_do_table[i].link_ch & 0x01f) == cp) { return (u8)i; } } return -1; } u8 get_par_link_101_to_104(BYTE *out, BYTE *in, BYTE parNum, u8 ti) { int size = 0, i = 0, j = 0; int no = 0; if ((ti == FOS_PAR_SET_MUL) || (ti == FOS_PAR_SET_ONE)) size = (parNum * (sizeof(float) + 2 + 1)); // 写定值时需多一位QOS位 else if (ti == FOS_PAR_READ_MUL || ti == FOS_PAR_READ_ONE) size = (parNum * (sizeof(float) + 2)); rt_printf("size=%d,parNum=%d\n\r", size, parNum); for (no = 0; no < parNum; no++) { if ((ti == FOS_PAR_READ_MUL) || (ti == FOS_PAR_READ_ONE)) { out[j++] = in[i++]; // 地址 out[j++] = in[i++]; // 地址 out[j++] = 0; out[j++] = in[i++]; // 值 out[j++] = in[i++]; // 值 out[j++] = in[i++]; // 值 out[j++] = in[i++]; // 值 } else if ((ti == FOS_PAR_SET_MUL) || (ti == FOS_PAR_SET_ONE)) { out[j++] = in[i++]; // 地址 out[j++] = in[i++]; // 地址 out[j++] = 0; out[j++] = in[i++]; // 值 out[j++] = in[i++]; // 值 out[j++] = in[i++]; // 值 out[j++] = in[i++]; // 值 out[j++] = in[i++]; // QOS } } return (size + parNum); } u8 get_par_link_data(BYTE *out, BYTE *in, BYTE parNum, u8 ti) { int size; if (ti == FOS_PAR_SET_MUL) // 写带品质描述,长度+1 size = (parNum * (sizeof(float) + 2 + 1)); // 4 else size = (parNum * (sizeof(float) + 2)); memcpy(out, in, size); // 拷贝遥参点表地址=2 && 参数值=size return (size); } u8 get_par_link_104_to_104(BYTE *out, BYTE *in, BYTE parNum, u8 ti) { int size; if (ti == FOS_PAR_SET_MUL) // 写带品质描述,长度+1 size = (parNum * (sizeof(float) + 3 + 1)); // 4 else size = (parNum * (sizeof(float) + 3)); memcpy(out, in, size); // 拷贝遥参点表地址=2 && 参数值=size return (size); } u8 get_par_link_data_dz(BYTE *out, BYTE *in, BYTE parNum, u8 ti) { int size = 0, i, j = 0; if ((ti == FOS_PAR_SET_MUL) || (ti == FOS_PAR_SET_ONE)) size = (parNum * (sizeof(float) + 3 + 1)); // 写定值时需多一位QOS位 else if (ti == FOS_PAR_READ_MUL || ti == FOS_PAR_READ_ONE) size = (parNum * (sizeof(float) + 3)); rt_printf("size=%d,parNum=%d\n\r", size, parNum); for (i = 0; i < size; i++) { // 104遥调时,因管理机与级联间仍为101通讯,故管理机转发给级联的报文应去掉1位地址位(104为3位地址位,101为2位地址位) if ((ti == FOS_PAR_READ_MUL) || (ti == FOS_PAR_READ_ONE)) { if (2 == i % 7) // 读定值时3位地址位+4位数据位共7位 continue; // 跳过地址位第3位 else { out[j++] = in[i]; } } else if ((ti == FOS_PAR_SET_MUL) || (ti == FOS_PAR_SET_ONE)) { if (2 == i % 8) // 写定值时3位地址位+4位数据位+1位QOS位 continue; // 跳过地址位第3位 else { out[j++] = in[i]; } } } return (size - parNum); } // 获取通道信息 bool get_par_ch_info(WORD parAddr, struct do_table *parTab) { WORD Addr = 0; int ykCp = 0; // 判断遥参ID是否合法 if (parAddr < FOS_PAR_STATR_ADDR) return false; Addr = parAddr - FOS_PAR_STATR_ADDR; #ifdef YC_QUANTITY parTab->link_cp = (Addr / pRunSet->dYC_num) + 1; // 转发通信地址 #else parTab->link_cp = (Addr / FOS_PAR_SW_NUM) + 1; // 转发通信地址 #endif // 通过遥控的级联地址去判断遥参是否合法 ykCp = get_yk_info_cp(parTab->link_cp); if (ykCp < 0) { rt_printf("定值参数未找到Yk对应的级联地址=%X link_ch=%x \r\n", parAddr, parTab->link_cp); return false; } memcpy(parTab, &g_do_table[ykCp], sizeof(g_do_table[0])); // 将遥控配置的转发信息赋到遥参 #if 0 parTab->cp = Addr%FOS_PAR_SW_NUM + FOS_PAR_STATR_ADDR; //转发点号 #endif parTab->link_cp = parTab->cp = parAddr; // 转发通信地址 #ifdef YC_QUANTITY rt_printf("\n parAddr=%x link_cp=%x cp=%x link_ch=0X%X ykCp=%d linkAddr=%x\r\n", parAddr, parTab->link_cp, parTab->link_ch, parTab->cp, ykCp, Addr % pRunSet->dYC_num); #else rt_printf("\n parAddr=%x link_cp=%x cp=%x link_ch=0X%X ykCp=%d linkAddr=%x\r\n", parAddr, parTab->link_cp, parTab->link_ch, parTab->cp, ykCp, Addr % FOS_PAR_SW_NUM); #endif return true; } #ifdef FUNC_MORE_PRESET YT_BUF PresetData101[256]; YT_BUF SetData101[256]; int WrongCount101[256]; int g_CountPreset101 = 0, g_CountSet101 = 0; bool gb_101_yt = false; #endif static void IEC101_ParCtrl(IEC101_DEF *pt, u8 *pdat) // ps从传送原因开始 { u8 cot, ykcot, ykvalue; WORD parNum = 1; WORD ykno = 0; WORD yksave = 0; WORD parAddr = 0; u8 AppByte = 0; u8 CotByte = 0; u8 *ps = &pdat[2]; u8 ti = pdat[0]; // 类型标示 struct do_table parTab; BYTE buf[256]; BYTE len = 0; BYTE addrStart = 0; WORD sw = 1; static DWORD ctrlParTime = 0; // 系统定时器 static bool bSetParaDisable = false; if (tRunPara.b101App2Byte) // 应用单元双字节地址 { AppByte = 1; } if (tRunPara.b101Cot2Byte) // 双字节传送原因 { CotByte = 1; } ykcot = (ps[0] & 0x01f); parNum = (pdat[1] & 0xff); // 遥参数量,支持遥调大于30个定值 ykno = ps[2 + AppByte + CotByte] + (WORD)ps[3 + AppByte + CotByte] * 256; addrStart = 2 + AppByte + CotByte; yksave = ykno; cot = IEC_COT_UN_OA | IEC_COT_PN; parAddr = (ps[addrStart + 1] << 8) + ps[addrStart]; // 遥参通信地址 len = get_par_link_data(&buf[0], &ps[addrStart], parNum, ti); // 2+(parNum*4);//2:点号 4:参数值 ykvalue = ps[AppByte + CotByte + 2 - 1 + 7]; // rt_printf("ti=%d parAddr=%x parNum= %d recDataLen=%d addrStart=%d ykvalue=%x ykcot=%02x\r\n", ti, parAddr, parNum, len, addrStart, ykvalue, ykcot); if (pRunSet->wEquTypeManager) // 通信管理机模式,转发 { #ifdef YC_QUANTITY if (!get_par_ch_info(parAddr, &parTab) || parNum > pRunSet->dYC_num) #else if (!get_par_ch_info(parAddr, &parTab) || parNum > FOS_PAR_SW_NUM) #endif { cot = IEC_COT_ACTCON | IEC_COT_PN; // 否定应答 // refresh_co_file(true,ti,yktype,ykno,cot,yk_cmd); return; } // parTab.vsq = parNum; if (lnk_master_par(&parTab, ti, ps[0], len, &buf[0], parNum) == 0) { #ifdef FUNC_MORE_PREREAD /*级联遥参方式:0开关,1队列*/ pt->lnk_par_send_flag |= (1 << g_link_par_cnt); rt_printf("-------lnk_par_send_flag=%x,g_link_par_cnt=%d\r\n", pt->lnk_par_send_flag, g_link_par_cnt); g_link_par_cnt = (g_link_par_cnt + 1) % SWITCH_NUM_EXT_PUB; #else pt->lnk_par_send_flag |= (1 << parTab.owner); #endif pt->us0_lnk_par = ustimer_get_origin(); } } else if (pRunSet->wEquTypeDFTU) // 测控装置遥参交互 { WORD parCount; WORD addLenParLen; #ifdef FUNC_MORE_PRESET int n; #endif cot = IEC_COT_ACTCON; // ykvalue = (ti==FOS_PAR_SET_ONE) ? (ykvalue|0x80) :ykvalue; #ifndef FUNC_MORE_PRESET bfirst_sel = false; #endif #ifdef FUNC_MORE_PRESET if ((dTCounter - ctrlParTime) > tRunPara.dYKTime) { // 超时清零预置/执行/出错数据存储区 memset(SetData101, 0, sizeof(SetData101)); g_CountSet101 = 0; memset(PresetData101, 0, sizeof(PresetData101)); g_CountPreset101 = 0; memset(WrongCount101, 0, sizeof(WrongCount101)); rt_printf("超时清除101报文缓存区\r\n"); } #endif if (ti == FOS_PAR_SET_ONE || ti == FOS_PAR_SET_MUL) { if (ykvalue & 0x80) { #ifdef FUNC_MORE_PRESET memcpy(&PresetData101[g_CountPreset101++].yc_data, &ps[addrStart], parNum * 7); rt_printf_time("101预置报文[第%d帧]:", g_CountPreset101); for (n = 0; n < parNum * 7; n++) { rt_printf("%02x ", PresetData101[g_CountPreset101 - 1].yc_data[n]); } #else // 预置时先对buf_forCompare进行初始化,避免此数组有101/104共用的原因导致存储出错 memset(buf_forCompare, 0, sizeof(buf_forCompare)); i_forCompare = 0; j_forCompare = 0; bfirst_sel = true; // 为预置报文时才置1 #endif } #ifdef FUNC_MORE_PRESET else { memcpy(&SetData101[g_CountSet101++].yc_data, &ps[addrStart], parNum * 7); rt_printf_time("101固化报文[第%d帧]:", g_CountSet101); for (n = 0; n < parNum * 7; n++) { rt_printf("%02x ", SetData101[g_CountSet101 - 1].yc_data[n]); } } #endif } for (parCount = 0; parCount < parNum; parCount++) // 多参数处理 { #ifdef FUNC_MORE_PRESET gb_101_yt = true; #endif if (ti == FOS_PAR_SET_MUL) // 读取参数带品质描述 addLenParLen = (parCount) * (2 + 4 + 1); // ID=2,PAR=4,QDS=1 else addLenParLen = (parCount) * (2 + 4); // ID=2,PAR=4 #ifdef YC_QUANTITY parAddr = (ps[addrStart + 1 + addLenParLen] << 8) + ps[addrStart + addLenParLen] - ((tRunPara.byAddr - 1) * pRunSet->dYC_num); #else parAddr = (ps[addrStart + 1 + addLenParLen] << 8) + ps[addrStart + addLenParLen] - ((tRunPara.byAddr - 1) * FOS_PAR_SW_NUM); #endif // parAddr = (ps[addrStart+1]<<8)+ps[addrStart];//遥参通信地址 #ifdef YC_QUANTITY if (((ps[addrStart + 1 + addLenParLen] << 8) + ps[addrStart + addLenParLen]) >= FOS_PAR_STATR_ADDR && ((ps[addrStart + 1 + addLenParLen] << 8) + ps[addrStart + addLenParLen]) < (FOS_PAR_STATR_ADDR + pRunSet->dYC_num)) #else if (((ps[addrStart + 1 + addLenParLen] << 8) + ps[addrStart + addLenParLen]) >= FOS_PAR_STATR_ADDR && ((ps[addrStart + 1 + addLenParLen] << 8) + ps[addrStart + addLenParLen]) < (FOS_PAR_STATR_ADDR + FOS_PAR_SW_NUM)) #endif { parAddr = (ps[addrStart + 1 + addLenParLen] << 8) + ps[addrStart + addLenParLen]; } rt_printf("parAddr=%x addr2=%X %x addrStart=%x\r\n", parAddr, (ps[addrStart + 1] << 8) + ps[addrStart], addLenParLen, addrStart); if (ti == FOS_PAR_READ_ONE || ti == FOS_PAR_READ_MUL) { rt_printf("读取遥参激活 parAddr=%x\r\n", parAddr); if (!readRunParId(sw, parAddr, &ps[addrStart + 2 + addLenParLen])) // 暂默认为0开关 cot = IEC_COT_ACTCON | IEC_COT_PN; // 否定应答// memcpy(&ps[addrStart+2],"1234",sizeof(float));//2:参数点表地址 } else { #ifdef FUNC_FIT_QOS if (ykcot == IEC_COT_DEACT) { cot = IEC_COT_DEACTCON; // 预置取消确认 bSetParaDisable = true; // 取消预置时置1 rt_printf("QOS(101)=%02x,取消遥参预置...\r\n", ykvalue); break; } #endif if (ykvalue & 0x80) { ctrlParTime = dTCounter; // 系统时间戳 if (!selectRunParId(sw, parAddr, &ps[addrStart + 2 + addLenParLen])) { cot = IEC_COT_ACTCON | IEC_COT_PN; #ifdef FUNC_MORE_PRESET WrongCount101[g_CountPreset101 - 1] = g_CountPreset101; #else bSetParaDisable = true; // 预置报文出错时置1,禁止固化定值 #endif break; } else { bSetParaDisable = false; // 重新预置时清0 } } else { #ifdef FUNC_MORE_PRESET int s_stop; s_stop = WrongCount101[g_CountSet101 - 1]; #endif #if defined FUNC_MORE_PRESET if (ykcot == IEC_COT_DEACT) { cot = IEC_COT_DEACTCON; // 预置取消确认 bSetParaDisable = true; // 取消预置时置1 rt_printf("取消遥参预置...\r\n"); break; } else if (dTCounter - ctrlParTime > tRunPara.dYKTime || (0 != s_stop) || bSetParaDisable || !wirteRunParId(sw, parAddr, &ps[addrStart + 2 + addLenParLen], ti)) // 暂默认为0开关 #elif defined FUNC_FIT_QOS if (dTCounter - ctrlParTime > tRunPara.dYKTime || bSetParaDisable || !wirteRunParId(sw, parAddr, &ps[addrStart + 2 + addLenParLen], ti)) // 暂默认为0开关 #else if (ykcot == IEC_COT_DEACT) { cot = IEC_COT_DEACTCON; // 预置取消确认 bSetParaDisable = true; // 取消预置时置1 rt_printf("取消遥参预置...\r\n"); break; } else if (dTCounter - ctrlParTime > tRunPara.dYKTime || bSetParaDisable || !wirteRunParId(sw, parAddr, &ps[addrStart + 2 + addLenParLen], ti)) // 暂默认为0开关 #endif { if (dTCounter - ctrlParTime > tRunPara.dYKTime) { bSetParaDisable = false; // 超时后才清0,避免取消预置后在超时时间内多次发执行帧而误执行 rt_printf("设置遥参超时...\r\n"); } #ifdef FUNC_MORE_PRESET else if (0 != s_stop) { rt_printf("[第%d帧]101预置报文出错,请检查...\r\n", s_stop); } #endif else if (bSetParaDisable) { #ifdef FUNC_MORE_PRESET rt_printf("取消预置,禁止固化定值\r\n"); #else rt_printf("预置报文出错或取消预置,禁止固化定值\r\n"); #endif } cot = IEC_COT_ACTCON | IEC_COT_PN; // 否定应答 // 屏蔽break语句,除出错定值外均可修改,与104遥调统一做法 // break; } } } } #ifdef FUNC_MORE_PRESET gb_101_yt = false; #endif // parTab.vsq = parNum; // if (cot & IEC_COT_PN) rt_printf("设置&&读取 遥参失败 parAddr=%x\r\n", parAddr); else rt_printf("设置&&读取 遥参OK parAddr=%x\r\n", parAddr); #ifndef FUNC_MORE_PRESET if ((!(ykvalue & 0x80) && ti == FOS_PAR_SET_MUL) #ifdef FUNC_FIT_QOS || (ykcot == IEC_COT_DEACT) #endif ) ctrlParTime = 0; #endif IEC101_Echo_PAR(pt, yksave, ti, cot, &ps[addrStart], len, parNum); #ifndef FUNC_MORE_PRESET if ((!(ykvalue & 0x80)) #ifdef FUNC_FIT_QOS || (ykcot == IEC_COT_DEACT) #endif ) { memset(buf_forCompare, 0, sizeof(buf_forCompare)); i_forCompare = 0; j_forCompare = 0; } #endif return; } cot = (ykcot == IEC_COT_ACT) ? IEC_COT_ACTCON : IEC_COT_DEACTCON; cot |= IEC_COT_PN; // rt_printf("设置遥参 error parAddr=%x\r\n",parAddr); // IEC101_Echo_PAR(pt,yksave,ti,cot,&ps[addrStart],len); } static void IEC101_RmtCtrl(IEC101_DEF *pt, u8 *pdat) // ps从传送原因开始 { u8 cot, ykcot, ykvalue, yktype, ykThz, v; WORD ykno; WORD yksave; u8 AppByte = 0; u8 CotByte = 0; int i, ret; bool bValidYk = false; u8 *ps = &pdat[2]; u8 ti = pdat[0]; // 类型标示 u8 yk_cmd; yktype = 0; if (tRunPara.b101App2Byte) // 应用单元双字节地址 { AppByte = 1; } if (tRunPara.b101Cot2Byte) // 双字节传送原因 { CotByte = 1; } ykcot = (ps[0] & 0x01f); ykvalue = ps[4 + AppByte + CotByte]; yk_cmd = ykvalue & 0x03; ykno = ps[2 + AppByte + CotByte] + (WORD)ps[3 + AppByte + CotByte] * 256; yksave = ykno; cot = IEC_COT_UN_OA | IEC_COT_PN; v = ykvalue; if (ti == 45) { ykvalue += 1; // 单点遥控转为双点遥控 } for (i = 0; i < g_table_head->do_num; i++) // 查找遥控转发表,看是否有有效的遥控点 { if (ykno != g_do_table[i].cp) // 遥控点号对应 { continue; } // 如果配置了级联通道,转发 if (g_do_table[i].link_ch) { if (lnk_master_yk(&g_do_table[i], ti, ps[0], v) == 0) { pt->lnk_yk_send_flag |= (1 << g_do_table[i].owner); pt->us0_lnk_yk = ustimer_get_origin(); } else { cot = (ykcot == IEC_COT_ACT) ? IEC_COT_ACTCON : IEC_COT_DEACTCON; cot |= IEC_COT_PN; IEC101_Echo_YK(pt, yksave, ti, cot, v); } if (ykvalue & 0x80) { yktype = YK_TYPE_SEL; } else { yktype = YK_TYPE_EXE; } refresh_co_file(true, ti, yktype, ykno, cot, yk_cmd); return; } // 本装置遥控 { cot = IEC_COT_ACTCON; bValidYk = true; if (ykvalue & 0x80) { yktype = YK_TYPE_SEL; } else { yktype = YK_TYPE_EXE; } if (ykcot == 8) // 遥控撤销 { yktype = YK_TYPE_CANCEL; cot = IEC_COT_DEACTCON; } if ((ykvalue & 0x03) == 2) { ykThz = YK_VAL_HZ; } else { ykThz = YK_VAL_TZ; } ret = RemoteCtrl(yktype, i, ykThz, MASTER_101 + pt->chnl); if (ret != 0) { cot = IEC_COT_ACTCON | IEC_COT_PN; // 否定应答 } // 接收到主站的遥控信息:选择,执行,撤销 refresh_co_file(true, ti, yktype, ykno, cot, yk_cmd); break; } } // 没有找到对应遥控点 if (i == g_table_head->do_num) { char buf[128]; char log_info[128]; sprintf(buf, "遥控:转发点表未找到对应点(ykno=0x%04x,yktype=%d,ykvalue=%d)", ykno, yktype, ykvalue); log_str_time(LOG_OPERATE, buf, 0, 0); rt_printf_time("%s\r\n", buf); sprintf(log_info, "IEC101(%d):Remote control:Corresponding di is not in the iectable(ykno=0x%04x,yktype=%d,ykvalue=%d)", pt->chnl + 1, ykno, yktype, ykvalue); load_hs_log_rcd(TYPE_COMM_ERR, true, NULL, log_info, 1); log_flag.com_err[pt->chnl] = true; } pt->bRmtSM2 = false; IEC101_Echo_YK(pt, yksave, ti, cot, v); // 必须有对应点号 if (i != g_table_head->do_num) { // 终端发出遥控信息:选择确认,执行确认 refresh_co_file(false, ti, yktype, ykno, cot, yk_cmd); } if ((yktype == YK_TYPE_EXE) && (cot == IEC_COT_ACTCON)) { IEC101_Echo_YK(pt, yksave, ti, 10, v); refresh_co_file(false, ti, yktype, ykno, IEC_COT_ACTTERM, yk_cmd); } } /************************************************************************** 函数名称:IEC101_MFrameCall 函数版本:1.00 作者: 创建日期:2008.9.1 函数功能说明:应答短帧报文 输入参数:平衡方式下,发送帧 输出参数: 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ static void IEC101_MFrameCall(IEC101_DEF *pt, unsigned char ZoneByte) { u8 *pd = pt->tx_buf_m; IEC101_SETPRM(ZoneByte); if (!pt->bDIR) IEC101_SETDIR(ZoneByte); if (!tRunPara.b101Addr2Byte) { pd[0] = 5; pd[1] = 0x10; pd[2] = ZoneByte; pd[3] = (u8)tRunPara.byAddr; pd[4] = pd[2] + pd[3]; pd[5] = 0x16; } else { pd[0] = 6; pd[1] = 0x10; pd[2] = ZoneByte; pd[3] = (u8)tRunPara.byAddr; pd[4] = (u8)(tRunPara.byAddr >> 8); pd[5] = pd[2] + pd[3] + pd[4]; pd[6] = 0x16; } } static void IEC101_CallGprsInf(IEC101_DEF *pt) { u8 *pd = pt->tx_buf_s; pd[0] = 9; pd[1] = 0x55; pd[2] = 0xAA; pd[3] = 0x55; pd[4] = 0xAA; pd[5] = 0x1A; pd[6] = 0x00; pd[7] = 0x00; pd[8] = 0x9A; pd[9] = 0x7C; } /************************************************************************** 函数名称:IEC101_link_par 函数版本:1.00 作者: 创建日期:2019.4.19 函数功能说明:101规约遥参级联处理 输入参数: 输出参数: 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ void IEC101_link_par(IEC101_DEF *pt) { int i; LINKPAR_DEF *par; LINK_PARDATA *yd; if (pt->lnk_par_send_flag == 0) { return; } for (i = 0; i < SWITCH_NUM_EXT_PUB; i++) { if (pt->lnk_par_send_flag & (1 << i)) { par = &g_link_par[i]; // 遥控应答帧 if (par->flag[IECLINK_YK_ACK]) { yd = &par->data[IECLINK_YK_ACK]; IEC101_Echo_PAR(pt, par->cp_m, yd->ti, yd->cot, &yd->buf[0], yd->len, yd->vsq); par->flag[IECLINK_YK_ACK] = 0; rt_printf("参数应答帧\r\n"); } // 遥控结束帧 if (par->flag[IECLINK_YK_END]) { yd = &par->data[IECLINK_YK_END]; IEC101_Echo_PAR(pt, par->cp_m, yd->ti, yd->cot, &yd->buf[0], yd->len, yd->vsq); par->flag[IECLINK_YK_END] = 0; pt->lnk_par_send_flag &= ~(1 << i); rt_printf("参数结束帧\r\n"); } // 遥控超时,复位 if (ustimer_get_duration(pt->us0_lnk_par) > tRunPara.dSM2Time * USTIMER_SEC) { yd = &par->data[IECLINK_YK_SND]; rt_printf("参数遥控超时复位:cp_m=%04x,cp_s=%04x,ti=%d,v=%d.\r\n", par->cp_m, par->cp_s, yd->ti, yd->len); g_link_par[i].flag[IECLINK_YK_SND] = false; pt->lnk_par_send_flag &= ~(1 << i); } } } } /************************************************************************** 函数名称:IEC101_Applay 函数版本:1.00 作者: 创建日期:2008.9.1 函数功能说明:101规约链路数据处理 输入参数: 输出参数: 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ void IEC101_lnk_yk(IEC101_DEF *pt) { int i; LINKYK_DEF *yk; LINK_YKDATA *yd; if (pt->lnk_yk_send_flag == 0) { return; } for (i = 0; i < SWITCH_NUM_EXT_PUB; i++) { if (pt->lnk_yk_send_flag & (1 << i)) { u8 yktype, yk_cmd; bool yk_hs = false; yk = &g_lnk_yk[i]; // 遥控应答帧 if (yk->flag[IECLINK_YK_ACK]) { yd = &yk->data[IECLINK_YK_ACK]; IEC101_Echo_YK(pt, yk->cp_m, yd->ti, yd->cot, yd->v); yk->flag[IECLINK_YK_ACK] = 0; } // 遥控结束帧 if (yk->flag[IECLINK_YK_END]) { yd = &yk->data[IECLINK_YK_END]; IEC101_Echo_YK(pt, yk->cp_m, yd->ti, yd->cot, yd->v); yk->flag[IECLINK_YK_END] = 0; pt->lnk_yk_send_flag &= ~(1 << i); } // 遥控超时,复位 if (ustimer_get_duration(pt->us0_lnk_yk) > tRunPara.dSM2Time * USTIMER_SEC) { yd = &yk->data[IECLINK_YK_SND]; rt_printf("级联遥控超时复位:cp_m=%04x,cp_s=%04x,ti=%d,v=%d.\r\n", yk->cp_m, yk->cp_s, yd->ti, yd->v); pt->lnk_yk_send_flag &= ~(1 << i); } if (yk_hs) { if (yd->v & 0x80) { yktype = YK_TYPE_SEL; } else { yktype = YK_TYPE_EXE; } if (yd->cot == IEC_COT_DEACT) // 遥控撤销 { yktype = YK_TYPE_CANCEL; } yk_cmd = yd->v & 0x03; refresh_co_file(false, yd->ti, yktype, yk->cp_m, yd->cot, yk_cmd); } } } } #ifdef YPARA_LINK void IEC101_lnk_msg_ypara(IEC101_DEF *pt101) { u8 *pd; int num = 0; u8 pdi = 0, cot = 0, ti = 0, vsq = 0; int index = 0, i = 0; msg_data_struct *msg_data = &pt101->msg_ypara_recv; pd = g_arrIECBuf; #if 0 /*遥参是否超时*/ if(!pt101->msg_ypara_send.flag) return; /*没有下发遥参不返回*/ if(ustimer_get_duration(pt101->us0_msg_ypara) > tRunPara.dSM2Time*USTIMER_SEC) { pt101->msg_ypara_send.flag = 0; /*超时*/ return; } /*遥参是否超时*/ #endif if (!msg_data->flag) return; /*有返回消息*/ msg_data->flag = 0; if (msg_data->type == modify_set) { msg_ypara_data_struct *ypara_msg = &pt101->msg_ypara_recv.ypara_msg; ti = 203; /*该协议各类遥参返回格式一致,统一接口即可*/ switch (ypara_msg->cmd) { case YP_SEL_RETURN: pdi = 0x80; cot = IEC_COT_ACTCON; break; case YP_SEL_FAULT: pdi = 0x80; cot = IEC_COT_ACTCON | IEC_COT_PN; break; case YP_EXE_RETURN: pdi = 0x00; cot = IEC_COT_ACTCON; break; case YP_EXE_FAULT: pdi = 0x00; cot = IEC_COT_ACTCON | IEC_COT_PN; break; case YP_CANCEL_RETURN: pdi = 0x40; cot = IEC_COT_DEACTCON; break; case YP_CANCEL_FAULT: pdi = 0x40; cot = IEC_COT_DEACTCON | IEC_COT_PN; break; default: break; } pd[num++] = (u8)m_runsection; // 当前定值区 pd[num++] = (u8)(m_runsection >> 8); // 当前定值区 pd[num++] = pdi; /*特征符*/ // print_msg_base(ypara_msg); for (i = 0; i < ypara_msg->num; i++) { index = get_index_link(msg_data->sport, ypara_msg->base_data[i].cp_s); if (index < 0) continue; pd[num++] = (u8)tParaID[index].parId; pd[num++] = (u8)(tParaID[index].parId >> 8); pd[num++] = ypara_msg->base_data[i].val_iec.tag; pd[num++] = ypara_msg->base_data[i].val_iec.size; if (ypara_msg->base_data[i].val_iec.size > 0) { memcpy(&pd[num], ypara_msg->base_data[i].val_iec.value, ypara_msg->base_data[i].val_iec.size); num += ypara_msg->base_data[i].val_iec.size; } vsq++; } IEC101_Asdu_Add(pt101, pd, num, vsq, cot, ti, FRAME_I); } } void IEC101_Rmt_msg_ypara_write(IEC101_DEF *pt, BYTE *pdat, BYTE vsq, u8 link_cp) { WORD section = 0; BYTE *ps = 0; // 从点号开始 int i, j = 0; u16 di = 0; msg_data_struct *msg_data; msg_ypara_data_struct *msg; iec_val_struct v; BYTE pdi; // 参数特征标识符 msg_data = &pt->msg_ypara_send; msg = &pt->msg_ypara_send.ypara_msg; memset(msg, 0, sizeof(msg_ypara_data_struct)); pt->msg_ypara_send.dport = link_cp; pt->msg_ypara_send.sport = pt->chnl; pt->msg_ypara_send.flag = 1; section = pdat[0] + (pdat[1] << 8); // 定值区号 pdi = pdat[2]; msg->section_p = section; // 定值区 if ((pdi & 0xc0) == 0) // 定值固化 { msg->cmd = YP_EXE; } else if ((pdi & 0xc0) == 0x40) /*撤销*/ { msg->cmd = YP_CANCEL; } else // 参数预置 { msg->cmd = YP_SEL; ps = &pdat[3]; for (i = 0; i < vsq; i++) { di = ps[0] + (ps[1] << 8); ps += 2; v.tag = *ps++; v.size = *ps++; memcpy(v.value, ps, v.size); ps += v.size; for (j = 0; j < ParaIDNum; j++) { if (tParaID[j].link_ch == 0) continue; if (di != tParaID[j].parId) continue; msg->base_data[msg->num].cp_m = tParaID[j].parId; msg->base_data[msg->num].cp_s = tParaID[j].link_cp; msg->base_data[msg->num].val_iec = v; msg->num++; break; } } } msg_data->type = modify_set; msg_data->sport = pt->chnl; msg_data->dport = link_cp; mananger_msg_ypara_s(msg_data, msg_data->sport, msg_data->dport); pt->us0_msg_ypara = ustimer_get_origin(); } #endif int IEC101_TransFile(IEC101_DEF *pt, unsigned char *buf, int len); int IEC101_Maintain(IEC101_DEF *pt, unsigned char *buf, int len); #ifdef RCD_STRAN_S static void IEC101_Rcd_Ack(IEC101_DEF *pt101) // 发送新录波文件名 { u8 *pd; int num; u8 rcd_num = 0; char tmp[128] = {0}; char note[100]; // 组织报文 pd = g_arrIECBuf; num = 0; if (get_queue_count(&pt101->rcd_qt.queue) > 0) /*有新录波*/ { check_data_queque(&pt101->rcd_qt.queue, tmp); sprintf(note, "%s----------准备\r\n", tmp); log_str_time(LOG_OPERATE, note, 0, 1); pd[num++] = 0; // 信息体地址 pd[num++] = 0; pd[num++] = 1; /*文件数量*/ pd[num++] = strlen(tmp); /*文件名长度*/ memcpy(&pd[num], tmp, strlen(tmp)); num += strlen(tmp); } else { pd[num++] = 0; // 信息体地址 pd[num++] = 0; // pd[num++] = rcd_num; } IEC101_Asdu_Add(pt101, g_arrIECBuf, num, 1, 7, RCD_TYPE, FRAME_I | FRAME_I_PRIOR_LOW); // } #endif void IEC101_Applay(IEC101_DEF *pt) { u8 *pd; u8 AddByte = 0; u8 CotByte = 0; // 传送原因双字节 u8 AppByte = 0; // 应用单元地址双字节 u8 ti, vsq, cot, L, *info_addr, info_len; bool bLFrame = false; // 短帧标志 #ifdef FUNC_ENCRY_IN_ONE_SERIAL if (tRunPara.tUartPara[0].wProtocol == PROTOCOL_WED_ENC) { if ((pt->chnl != 1) && (pt->chnl != 3) && (pt->chnl != 4)) // 投入海南加密时,固定串口2、4、5用于加密数据处理,不允许接收串口数据 { // 接收报文 IEC101_Recv_App(pt); if (pt->chnl == 0) { return; } } } else #endif { // 接收报文 IEC101_Recv_App(pt); } #ifdef IEC_NOLINK_NO_CALLYX if (g_link_comm.link_flag == false) return; #endif // 主动SOE处理,放在报文处理之前,使时间同步报文之前的SOE报文在时间报文应答前发送 IEC101_SendEvent(pt, tRunPara.bAutoCos); // 处理级联遥控 IEC101_lnk_yk(pt); // 处理级联遥参 IEC101_link_par(pt); #ifdef YPARA_LINK IEC101_lnk_msg_ypara(pt); #endif // 处理接收的加密报文 // 先处理加密报文,再处理普通报文,保证同一报文一个循环处理完 if (pt->bSM2Data) { if (pRunSet->bTT_SM2) // 加密功能投入 { #ifdef ENCRYPT_SM2 IEC101_SM2(pt); #endif } pt->bSM2Data = false; } // 处理接收的普通报文 else if (pt->bData) { // 如果从动站buf有数据,说明至少有2帧报文还没有发送完, // 在gprs网络,同时收到多帧报文后连续应答处理时会出现这种情况。 // 为了保证过程的完整性,待数据发送完后再处理接收的报文。 if (pt->btx_buf_s) { return; } // 报文长度调整变量赋值 if (tRunPara.b101Addr2Byte) AddByte = 1; if (tRunPara.b101Cot2Byte) CotByte = 1; if (tRunPara.b101App2Byte) AppByte = 1; // 打印接收的报文 if (g_print_101 && (g_print_port & (1 << pt->chnl))) { if (pt->recvbuf[0] == 0x10) { rt_printf("RX_S101_%d:", pt->chnl); print_msg("", pt->recvbuf, (5 + AddByte)); } else { rt_printf("RX_L101_%d:", pt->chnl); print_msg("", pt->recvbuf, pt->recvbuf[1] + 6); } } // 由于101在本函数开始处接收数据,所以可以在此处复位接收标志 pt->bData = false; // 检查帧标志并跳过固定报文头 if (pt->recvbuf[0] == 0x10) { pd = &pt->recvbuf[1]; // 短帧 L = 2; } else if (pt->recvbuf[0] == 0x68) { pd = &pt->recvbuf[4]; // 长帧 L = pt->recvbuf[1]; bLFrame = true; } else { return; } // 检查是否是GPRS维护信息 if (pd[0] == 0x05) { memcpy(tGprsInf.data, &pd[1], 64); tGprsInf.bInf = true; return; } // 检查地址 if (tRunPara.b101Addr2Byte) // 双字节地址 { WORD addr; addr = pd[1] + (pd[2] << 8); if (addr != tRunPara.byAddr && addr != 0xffff) return; // 不是发给本装置的数据 } else { if (pd[1] != (u8)tRunPara.byAddr && pd[1] != 0xff) return; // 不是发给本装置的数据 } // 通道空闲时间复位 pt->dValidTime = 0; // 私有101文件传输协议 if ((((pd[0] & 0x0f) == 3) || ((pd[0] & 0x0f) == 4)) && (pd[2 + AddByte] == 137)) { IEC101_TransFile(pt, &pd[2 + AddByte], pt->recvbuf[1] - (2 + AddByte)); pt->btx_buf_s = true; return; } // 私有维护工具软件接口 if ((((pd[0] & 0x0f) == 3) || ((pd[0] & 0x0f) == 4)) && (pd[2 + AddByte] == 138)) { IEC101_Maintain(pt, &pd[2 + AddByte], pt->recvbuf[1] - (2 + AddByte)); pt->btx_buf_s = true; return; } // 主站主动发送数据PRM=1 master if (pd[0] & 0x40) { if (pd[0] & 0x80) { pt->bDIR = true; } else { pt->bDIR = false; } // 非重复报文 if (!pRunSet->bTT_101FCB || (((pd[0] & 0x10) && pt->FCBSave != (pd[0] & 0x20)) // FCV位有效,FCB位变位 || (!(pd[0] & 0x10)) // FCV位无效 || pt->bFirstFCV)) // 该标志有效,不判断FCB位 { // FCB位 if (pd[0] & 0x10) { // 如果不是第一个FCB位且和以前FCB不同,确认发送BUF if ((pt->bFirstFCV == 0) && (pt->FCBSave != (pd[0] & 0x20))) { IEC101_AckOneBuf(pt); } pt->FCBSave = pd[0] & 0x20; // 保存帧记数位 pt->bFirstFCV = 0; // 如果FCV位有效,标志清0 } switch ((pd[0] & 0x0f)) { // 只有非平衡101才会请求一二级数据 case 10: // 请求/响应(一级数据) case 11: // 请求/响应(二级数据) // 如果不是数据状态,不应收到2、3、4、10、11,不进行任何应答,让主站超时复位 if (pt->st101 != IEC101_ST_DATA) { return; } if (!IEC101_SendOneBuf(pt)) { if (pt->tfile.bSegtransing) // 文件传输,放入一级数据缓冲区 { IEC101_SegmentContinue(pt); } else if (pt->tf.bTransing) { iec_readfile_send(pt, false); } if (!IEC101_SendOneBuf(pt)) { pt->bycsend = IEC101_GetYc(pt); if (!pt->bycsend) // add by sunxi { IEC101_FrameEcho(pt, IEC101_ECHO_NO_DATA); } } } break; case 9: // 请求链路状态 IEC101_FrameEcho(pt, IEC101_ECHO_LINK_OK); break; case 3: // 请求/确认 case 4: // 请求/无应答 // 如果不是数据状态,不应收到2、3、4、10、11,不进行任何应答,让主站超时复位 // 如果是复位进程状态,不应答主站发送应用层报文 if (pt->st101 != IEC101_ST_DATA || pt->bResetProcess) { return; } ti = pd[2 + AddByte]; vsq = pd[3 + AddByte]; cot = pd[4 + AddByte]; info_addr = pt->recvbuf + IEC101_InfoAddr(); info_len = L + 4 - IEC101_InfoAddr(); switch (ti) // 类型标识判断 { case 250: // 级联报文数据上送 IEC101_LinkInf(pt); break; case 106: if (cot == 6) // 延时获得 { // 此处不修改报文,真正发送的时候再计算修改 IEC101_Asdu_Add(pt, info_addr, info_len, vsq, IEC_COT_ACTCON, ti, FRAME_I | FRAME_I_106); } else if (cot == 3) // 延时传递 { // 保存主站下发的传输延时 pt->ms_delay = info_addr[2] + (info_addr[3] << 8); } else { IEC101_Asdu_Add(pt, info_addr, info_len, vsq, IEC_COT_UN_COT | IEC_COT_PN, ti, FRAME_I); } IEC101_FrameEcho(pt, IEC101_ECHO_SURE); // 链路应答确认 break; case 103: // 0xFF: //set time ,BroadCast Command // 检查COT if (cot == IEC_COT_REQ || cot == IEC_COT_ACT) { IEC101_TimeSetEcho(pt, &pd[8 + AddByte + AppByte + CotByte], cot); // Addbyte 链路地址和应用单元地址多了2个字节,传送原因多1个字节 } else { IEC101_Asdu_Add(pt, info_addr, info_len, vsq, IEC_COT_UN_COT | IEC_COT_PN, ti, FRAME_I); } pt->bTimeSyn = true; IEC101_FrameEcho(pt, IEC101_ECHO_SURE); // 链路应答确认 break; case 100: // 总查询 // 检查COT if (cot != IEC_COT_ACT) { IEC101_Asdu_Add(pt, info_addr, info_len, vsq, IEC_COT_UN_COT | IEC_COT_PN, ti, FRAME_I); } else { IEC101_CallAll(pt, pd[8 + AddByte + AppByte + CotByte]); // Addbyte 链路地址和应用单元地址多了2个字节,传送原因多1个字节 lnk_call_all(); } IEC101_FrameEcho(pt, IEC101_ECHO_SURE); // 链路应答确认 break; case 101: // 电度总召 // 检查COT if (cot != IEC_COT_ACT) { IEC101_Asdu_Add(pt, info_addr, info_len, vsq, IEC_COT_UN_COT | IEC_COT_PN, ti, FRAME_I); } else { IEC101_DDAll(pt, pd[8 + AddByte + AppByte + CotByte]); // Addbyte 链路地址和应用单元地址多了2个字节,传送原因多1个字节 } IEC101_FrameEcho(pt, IEC101_ECHO_SURE); // 链路应答确认 break; case 104: // 测试帧 IEC101_TestFrame(pt, &pd[8 + AddByte + AppByte + CotByte]); IEC101_FrameEcho(pt, IEC101_ECHO_SURE); break; case 105: // 检查COT if (cot != IEC_COT_ACT) { IEC101_Asdu_Add(pt, info_addr, info_len, vsq, IEC_COT_UN_COT | IEC_COT_PN, ti, FRAME_I); } else { u8 qrp; qrp = pd[8 + AddByte + AppByte + CotByte]; #ifdef FUNC_RESET_EQU gb_ResetEqu = true; #endif switch (qrp) // 复位进程命令限定词 QRP { // 进程的总复位 case 1: rt_printf_time("IEC101(%d) Reset: 进程的总复位!\r\n", pt->chnl); IEC101_ResetTxBuf(pt); // 记录复位计时起点和设置复位进程标志 pt->us0_ResetProcess = ustimer_get_origin(); pt->bResetProcess = true; break; // 复位事件缓冲区等待处理的带时标信息 case 2: rt_printf_time("IEC101(%d) Reset: 复位事件缓冲区!\r\n", pt->chnl); g_soe_queue.tail_send[pt->chnl].n = g_soe_queue.head.n; g_soe_queue.tail_ack[pt->chnl].n = g_soe_queue.tail_send[pt->chnl].n; break; } IEC101_ResetProcess(pt, qrp); } IEC101_FrameEcho(pt, IEC101_ECHO_SURE); break; case 45: // 单点遥控 case 46: // 双点遥控 // 检查单双点是否和参数设置相符 if ((ti == 45 && tRunPara.YKtype == 1) || (ti == 46 && tRunPara.YKtype == 0)) { // 对不支持的类型标识,否定应答 IEC101_FrameEcho(pt, IEC101_ECHO_DENY); rt_printf("101遥控类型不符(ti=%d,YKtype=%d)\r\n", ti, tRunPara.YKtype); break; } // 检查COT if (cot != IEC_COT_ACT && cot != IEC_COT_DEACT) { IEC101_Asdu_Add(pt, info_addr, info_len, vsq, IEC_COT_UN_COT | IEC_COT_PN, ti, FRAME_I | FRAME_I_PRIOR_HIGH); } else { IEC101_RmtCtrl(pt, &pd[2 + AddByte]); } IEC101_FrameEcho(pt, IEC101_ECHO_SURE); // 链路应答确认 break; case 122: // 召目录,选择文件,召唤文件,召唤节 IEC101_File_Echo(pt, &pd[2 + AddByte]); // 响应文件召唤 IEC101_FrameEcho(pt, IEC101_ECHO_SURE); // 链路应答确认 break; case 124: // 确认文件,确认节的应答 IEC101_File_Sure(pt, &pd[2 + AddByte]); // 对文件操作确认 IEC101_FrameEcho(pt, IEC101_ECHO_SURE); // 链路应答确认 break; case 176: // 本装置复归 ResetHzLed(0); SignalReset(0, false); IEC101_FrameEcho(pt, IEC101_ECHO_SURE); // 链路应答确认 break; #ifdef RCD_STRAN_S case RCD_TYPE: // 本装置复归 IEC101_Rcd_Ack(pt); IEC101_FrameEcho(pt, IEC101_ECHO_SURE); // 链路应答确认 break; #endif case 200: // 切换定值区 if (cot == IEC_COT_ACT) { IEC101_SectionWrite(pt, &pd[8 + AddByte + AppByte + CotByte]); // Addbyte 链路地址和应用单元地址多了2个字节,传送原因多1个字节 } else { IEC101_Asdu_Add(pt, info_addr, info_len, vsq, IEC_COT_UN_COT | IEC_COT_PN, ti, FRAME_I); } IEC101_FrameEcho(pt, IEC101_ECHO_SURE); // 链路应答确认 break; case 201: // 读取当前定值区号 if (cot == IEC_COT_ACT) { IEC101_SectionRead(pt); // Addbyte 链路地址和应用单元地址多了2个字节,传送原因多1个字节 } else { IEC101_Asdu_Add(pt, info_addr, info_len, vsq, IEC_COT_UN_COT | IEC_COT_PN, ti, FRAME_I); } IEC101_FrameEcho(pt, IEC101_ECHO_SURE); // 链路应答确认 break; break; case FOS_PAR_READ_ONE: // 南网佛山局 读取单个参数 case FOS_PAR_READ_MUL: // 南网 读取多个参数 case FOS_PAR_SET_ONE: // 南网佛山局 设置单个参数 case FOS_PAR_SET_MUL: // 南网 设置多数参数 // 检查COT if (cot != IEC_COT_ACT && cot != IEC_COT_DEACT) { IEC101_Asdu_Add(pt, info_addr, info_len, vsq, IEC_COT_UN_COT | IEC_COT_PN, ti, FRAME_I); } else { IEC101_ParCtrl(pt, &pd[2 + AddByte]); } IEC101_FrameEcho(pt, IEC101_ECHO_SURE); // 链路应答确认 break; case 202: // 读参数 if (cot == IEC_COT_ACT) { int ret; #ifdef YPARA_LINK ret = iec_setread_mananger((void *)pt, &pd[6 + AddByte + AppByte + CotByte], vsq, false); #else ret = iec_setread((void *)pt, &pd[6 + AddByte + AppByte + CotByte], vsq, false); // vsq为需读参数的个数,0,代表读全部参数,参数为从定值区开始的数据 #endif if (ret != 0) // 读定值出错,否定应答 { IEC101_Asdu_Add(pt, info_addr, info_len, vsq, IEC_COT_UN_OA, ti, FRAME_I); } } else { IEC101_Asdu_Add(pt, info_addr, info_len, vsq, IEC_COT_UN_COT | IEC_COT_PN, ti, FRAME_I); } IEC101_FrameEcho(pt, IEC101_ECHO_SURE); // 链路应答确认 break; case 203: // 修改参数 if (cot == IEC_COT_ACT || cot == IEC_COT_DEACT) { int ret; #ifdef YPARA_LINK int link_ch; link_ch = get_ypara_linkch_write((void *)pt, &pd[6 + AddByte + AppByte + CotByte], vsq, false); if (link_ch == 0) { rt_printf("101------写本地定值\r\n"); ret = iec_setwrite((void *)pt, &pd[6 + AddByte + AppByte + CotByte], cot, vsq, false); // vsq为需读参数的个数,0,代表固化参数,参数为从定值区开始的数据 if (ret != 0) // 写定值出错,否定应答 { BYTE rcot = IEC_COT_ACTCON | IEC_COT_PN; if (cot == IEC_COT_DEACT) rcot = IEC_COT_DEACTCON | IEC_COT_PN; IEC101_Asdu_Add(pt, info_addr, info_len, vsq, rcot, ti, FRAME_I); } else { BYTE rcot = IEC_COT_ACTCON; if (cot == IEC_COT_DEACT) rcot = IEC_COT_DEACTCON; IEC101_Asdu_Add(pt, info_addr, info_len, vsq, rcot, ti, FRAME_I); } } else { rt_printf("101------写级联定值\r\n"); IEC101_Rmt_msg_ypara_write((void *)pt, &pd[6 + AddByte + AppByte + CotByte], vsq, link_ch); if ((link_ch & 0x1f) == 0x1f) /*广播写直接返回确认*/ { BYTE rcot = IEC_COT_ACTCON; if (cot == IEC_COT_DEACT) rcot = IEC_COT_DEACTCON; IEC101_Asdu_Add(pt, info_addr, info_len, vsq, rcot, ti, FRAME_I); } } #else ret = iec_setwrite((void *)pt, &pd[6 + AddByte + AppByte + CotByte], cot, vsq, false); // vsq为需读参数的个数,0,代表固化参数,参数为从定值区开始的数据 if (ret != 0) // 写定值出错,否定应答 { BYTE rcot = IEC_COT_ACTCON | IEC_COT_PN; if (cot == IEC_COT_DEACT) rcot = IEC_COT_DEACTCON | IEC_COT_PN; IEC101_Asdu_Add(pt, info_addr, info_len, vsq, rcot, ti, FRAME_I); } else { BYTE rcot = IEC_COT_ACTCON; if (cot == IEC_COT_DEACT) rcot = IEC_COT_DEACTCON; IEC101_Asdu_Add(pt, info_addr, info_len, vsq, rcot, ti, FRAME_I); } #endif } else { IEC101_Asdu_Add(pt, info_addr, info_len, vsq, IEC_COT_UN_COT | IEC_COT_PN, ti, FRAME_I); } IEC101_FrameEcho(pt, IEC101_ECHO_SURE); // 链路应答确认 break; case 210: // 文件传输 { pt->tf.vsq = vsq; if (cot == IEC_COT_ACT || cot == IEC_COT_REQ) { int ret; ret = iec_file_app((void *)pt, &pt->recvbuf[12 + AddByte + AppByte + CotByte], cot, pt->recvbuf[1] - (AddByte + CotByte + AppByte) - 9, false); // 从操作标识开始 if (ret != 0) // 文件处理错误 { BYTE rcot = IEC_COT_UN_OA; if (ret == -2) rcot = IEC_COT_UN_COT; IEC101_Asdu_Add(pt, info_addr, info_len, vsq, rcot, ti, FRAME_I); } } else { IEC101_Asdu_Add(pt, info_addr, info_len, vsq, IEC_COT_UN_COT | IEC_COT_PN, ti, FRAME_I); } IEC101_FrameEcho(pt, IEC101_ECHO_SURE); // 链路应答确认 } break; case 211: // 文件升级 { if (cot == IEC_COT_ACT || cot == IEC_COT_DEACT) { int ret; ret = iec_pro_update((void *)pt, cot, pd[8 + AddByte + CotByte + AppByte], false); // 从操作标识开始 if (ret != 0) // 文件处理错误 { BYTE rcot = IEC_COT_UN_OA; IEC101_Asdu_Add(pt, info_addr, info_len, vsq, rcot, ti, FRAME_I); } } else { IEC101_Asdu_Add(pt, info_addr, info_len, vsq, IEC_COT_UN_COT | IEC_COT_PN, ti, FRAME_I); } IEC101_FrameEcho(pt, IEC101_ECHO_SURE); // 链路应答确认 } break; default: IEC101_FrameEcho(pt, IEC101_ECHO_DENY); break; } break; case 0: // 复位通信单元 // 复位通道 IEC101_Reset(pt); if (pd[0] & 0x80) // add by sunxi 防止IEC101_Reset调用后,bDIR变为默认值,后续发送报文,标示出错 { pt->bDIR = true; } else { pt->bDIR = false; } g_comm_link_status |= (1 << pt->chnl); IEC101_State(pt, tRunPara.tUartPara[pt->chnl].b101PH ? IEC101_ST_LINK : IEC101_ST_DATA); pt->LinkCount++; // 应答报文 IEC101_InitOK(pt); IEC101_FrameEcho(pt, IEC101_ECHO_SURE); // "重新连接后,总召前,发送完所有老的SOE,不发送COS" IEC101_SendEvent(pt, 0); break; case 2: // 链路测试功能 // 如果不是数据状态,不应收到2、3、4、10、11,不进行任何应答,让主站超时复位 if (pt->st101 != IEC101_ST_DATA) { return; } // 南瑞主站,功能码是2,发送长帧测试帧 if (bLFrame) { ti = pd[2 + AddByte]; if (ti == 104) { IEC101_TestFrame(pt, &pd[8 + AddByte + AppByte + CotByte]); } } IEC101_FrameEcho(pt, IEC101_ECHO_SURE); break; default: IEC101_FrameEcho(pt, IEC101_ECHO_DENY); break; } // 类型判断结束 // 广播地址不用应答 if ((pd[1] != 0xff) || (tRunPara.b101Addr2Byte && (pd[2] != 0xff)) // 平衡101广播查询链路状态回本机地址,江西吉安博威主站要求 || (tRunPara.tUartPara[pt->chnl].b101PH && (pd[0] & 0x0f) == 9)) { pt->btx_buf_s = true; } } // FCV位有效,FCB没有变位,重发保存的确认帧。 else { // 重发数据 if (pt->st101 == IEC101_ST_DATA) { if (pt->btx_buf_s) { rt_printf_time("IEC101_Applay:,btx_buf_s==1.\r\n"); } pt->btx_buf_s = true; } } } // 平衡101 ,接收到主站回复的数据 else { if (!tRunPara.tUartPara[pt->chnl].b101PH) return; switch (pt->st101) { case IEC101_ST_LINK: { if ((pd[0] & 0x0f) == 11) // 应答链路状态 { IEC101_State(pt, IEC101_ST_RSTLINK); pt->bMSend = false; } break; } case IEC101_ST_RSTLINK: { if ((pd[0] & 0x0f) == 0) // 应答链路状态 { IEC101_State(pt, IEC101_ST_DATA); pt->bMSend = false; } break; } case IEC101_ST_DATA: { if ((pd[0] & 0x0f) == 0) // 确认应答,调整一级数据缓冲区 { IEC101_AckOneBuf(pt); pt->bMSend = false; } break; } } } } // 获取GPRS信息 else if (pt->bCallGprsInf) { if (g_tRsComm[pt->chnl].bextsend || pt->btx_buf_s || pt->btx_buf_m) return; // 正在发送数据,不处理 pt->bCallGprsInf = false; IEC101_CallGprsInf(pt); pt->btx_buf_s = true; } // 无有效数据接收,平衡式101处理自动发送数据 else { if (!tRunPara.tUartPara[pt->chnl].b101PH) return; // 如果启动了主站发送,必须先处理应答 if (pt->bMSend) { bool bRtn = true; // 20秒后,未收到应答报文 if (ustimer_get_duration(pt->us0_msend) > pRunSet->dT101Resend * USTIMER_SEC) { pt->us0_msend = ustimer_get_origin(); if (++pt->msend_retries > 2) // 重发三次后,重新初始化 { // rt_printf_time("IEC101(%d):启动帧重发错误!\r\n",pt->chnl); char log_info[128]; sprintf(log_info, "IEC101(%d):Restart the frame to make three errors", pt->chnl + 1); load_hs_log_rcd(TYPE_COMM_ERR, true, NULL, log_info, 1); log_flag.com_err[pt->chnl] = true; IEC101_Info_Printf_One(pt); IEC101_Reset(pt); } else { // 需重新组织发送 pt->btx_buf_m = true; } return; } #ifdef COMM_STATION_NR if (pt->tx_buf[0].tb_head != pt->tx_buf[0].tb_tail_send) // 有遥控报文要发送 { if (ustimer_get_duration(pt->us0_msend) > 2 * USTIMER_SEC) // 且上帧报文发送超过2秒,不等待应道报文,直接发送 { IEC101_AckOneBuf(pt); pt->bMSend = false; bRtn = false; } } if (pt->bTestFrameL || pt->bSoeResume || pt->bTimeSyn) // 总召前有SOE续传不需要主站确认 { pt->bTestFrameL = false; pt->bSoeResume = false; pt->bTimeSyn = false; IEC101_AckOneBuf(pt); pt->bMSend = false; bRtn = false; } #endif if (bRtn) { return; // 发送,未收到数据,返回 } } // 平衡101应答完成 // 先复位应答相关变量 pt->msend_retries = 0; pt->us0_msend = ustimer_get_origin(); // 开始主动发送流程 switch (pt->st101) { case IEC101_ST_LINK: { IEC101_MFrameCall(pt, IEC101_CALL_LINK); pt->bMFCB = false; pt->bMSend = true; break; } case IEC101_ST_RSTLINK: { IEC101_MFrameCall(pt, IEC101_CALL_RSTLINK); pt->bMFCB = false; pt->bMSend = true; break; } case IEC101_ST_DATA: { pt->bMSend = IEC101_SendOneBuf(pt); if (!pt->bMSend) // 无数据 判断 { if (pt->tfile.bSegtransing) // 文件传输,放入一级数据缓冲区 { IEC101_SegmentContinue(pt); } else if (pt->tf.bTransing) // 国网新标准文件传输 { iec_readfile_send(pt, false); } else { pt->bycsend = IEC101_GetYc(pt); if (pt->bycsend) pt->bMSend = TRUE; } } break; } } // 有数据要发送 if (pt->bMSend) { if (pt->btx_buf_m) { // rt_printf_time("IEC101_Applay:bMSend==1,btx_buf_m==1.\r\n"); } pt->bMFCB = !pt->bMFCB; pt->btx_buf_m = true; } } } /************************************************************************** 函数名称:IEC101_SendEvent 函数版本:1.00 作者: 创建日期:2008.9.1 函数功能说明:上送事件记录及变位信息,在RS485定时处理任务(IEC101_AutoTimer)中调用 输入参数: 输出参数: 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ EVENT_STRUCT event_p[64]; u32 IEC101_SendEvent(IEC101_DEF *pt, int is_send_cos) { u8 type; int vsq = 0, num_vsp; u8 arrCos[200], arrSoe[200]; u8 arrCos1[200], arrSoe1[200]; u16 head, cnt; u16 soe_no_s, soe_no_d, arg_index[64]; bool bFault; FAULT_PARA_BUF *tf = &pt->tfault; // 主动上送条件: // 1、是IEC101_ST_DATA状态 // 2、没有在处理加密遥控报文 // 3、不是复位进程状态 if ((pt->st101 != IEC101_ST_DATA) || pt->bRmtSM2 || pt->bResetProcess) { return 0; } // 不允许通讯续传的情况下,总召后才允许上送事件 if (pt->bSendChange == 0) // && pRunSet->bTT_SoeResumeComm == 0) { return 0; } #ifdef FUN_SOE_DELAY_TIME if ((pt->bfirst_call) && (dTCounter - pt->DTSendChange <= 15 * T_1s)) { return 0; } pt->bfirst_call = false; #endif // 一级缓冲区未空,不添加事件记录 if (pt->tx_buf[0].tb_head != pt->tx_buf[0].tb_tail_send) { // rt_printf("%s(0):tb_head=%d,tb_tail_send=%d\r\n",__func__,pt->tx_buf[0].tb_head,pt->tx_buf[0].tb_tail_send); return 0; } if (pt->tx_buf[1].tb_head != pt->tx_buf[1].tb_tail_send) { return 0; } #ifndef IEC_JXYB_DEAL if (g_run_stu.bjx) // 检修压板投入,不发送变化的事件记录 { g_soe_queue.tail_send[pt->chnl].n = g_soe_queue.tail_eep.n; // soe cos tf->head = tf->tail; // 故障参数 return 0; } #endif // 发送级联来的故障参数 if (tRunPara.bEvPara) { if (tf->head != tf->tail) { u8 index = tf->tail; IEC101_Asdu_Add_sts(pt, &tf->data[index][1], tf->data[index][0], 0, 3, 42, FRAME_I_PRIOR_LOW, 0); tf->tail++; return 0; } } // 等SOE保存后再发送,避免通讯确认没有保存的事件 head = g_soe_queue.tail_eep.n; // 一次调用,将事件池中所有事件发送完, // 已保证满足"重新连接后,总召前,发送完所有老的SOE,不发送COS"的需求。 cnt = 0; while (1) { u8 yxtype = 0; u8 pcosnum = 0, pcosnum1 = 0; // 检查发送队列空间,空间应大于1/4,防止发送缓冲区满 if (IEC101_BufFreeNum(&pt->tx_buf[0]) < (IEC_TBUF_NUM >> 2)) { rt_printf("IEC101_SendEvent:BufFreeNum=%d.\r\n", IEC101_BufFreeNum(&pt->tx_buf[0])); break; } // 获取一帧报文的事件 bFault = false; num_vsp = asdu_get_event(pt->chnl, head, arrCos, arrSoe, arrCos1, arrSoe1, &soe_no_s, &soe_no_d, 0, &bFault, &event_p[0], &arg_index[0], &yxtype, &pcosnum, &pcosnum1); if (num_vsp == 0) break; // rt_printf("%s:vsq_s=%d\r\n",__func__,vsq); if (pcosnum != 0) { // 发送COS if (is_send_cos && pt->bSendChange) { type = 1; IEC101_Asdu_Add(pt, arrCos, pcosnum * 3, pcosnum, 3, type, FRAME_I_COS); } } vsq = (u8)(num_vsp & 0xff); if (vsq != 0) { cnt += vsq; // 发送SOE type = 30; IEC101_Asdu_Add_sts(pt, arrSoe, vsq * 10, vsq, 3, type, FRAME_I_SOE, soe_no_s); } if (pcosnum1 != 0) { // 发送COS if (is_send_cos) { type = 3; IEC101_Asdu_Add(pt, arrCos1, pcosnum1 * 3, pcosnum1, 3, type, FRAME_I_COS); } } vsq = (u8)((num_vsp >> 8) & 0xff); // rt_printf("%s:vsq_d=%d\r\n",__func__,vsq); if (vsq != 0) { cnt += vsq; // 发送SOE type = 31; IEC101_Asdu_Add_sts(pt, arrSoe1, vsq * 10, vsq, 3, type, FRAME_I_SOE, soe_no_d); // rt_printf_time("IEC101_SendEvent:soe_no=%d.\r\n",soe_no); } vsq = (u8)((num_vsp >> 16) & 0xff); // rt_printf("%s:vsq_e=%d\r\n",__func__,vsq); if (vsq > 0 && tRunPara.bEvPara) // 上送故障信息 { int i; u16 no; u8 SIQ, week; struct rtc_time_t rt; for (i = 0; i < vsq; i++) { u8 *pd = g_arrIECBuf; u8 yc_num; no = soe_lp2cp(event_p[i].ev_type, event_p[i].ev_code, event_p[i].ev_arg[1]); SIQ = event_p[i].ev_value; if ((yxtype == 1)) { SIQ += 1; // 转为双点信息 } #if !defined FUNC_JX_YC_SIQ00 if (g_run_stu.bjx) { SIQ |= 0x80; // 带品质描述,有效 <1>:=无效 } #endif timespec_to_rtc(event_p[i].ts, &rt, 1); week = WEEK_DAY(event_p[i].ts.tv_sec); // if(pRunSet->bTT_faultparaDKY) //{ yc_num = asdu_get_event_yc(arrSoe, 0, arg_index[i]); // 101规约是6个字节 按照规约规定,101 及104 遥信信息体地址都是两个字节 //} // else //{ // vsq=asdu_get_event_yc(arrSoe,1,arg_index); // 101规约是6个字节 按照规约规定,101 及104 遥信信息体地址都是两个字节 //} *pd++ = 1; // 遥信个数 *pd++ = (yxtype == 1) ? 31 : 30; // 类型标识 *pd++ = (u8)no; // 信息体地址L *pd++ = (u8)(no >> 8); // 信息体地址H if (pRunSet->bTT_faultparaDKY) { *pd++ = 0; // 信息体地址H2 按照规约规定,101 及104 遥信信息体地址都是两个字节 } *pd++ = SIQ; // 遥信 *pd++ = (u8)rt.ms; *pd++ = (u8)(rt.ms >> 8); *pd++ = (u8)rt.min; *pd++ = (u8)rt.hour; *pd++ = ((week << 5) | rt.day); *pd++ = (u8)rt.month; *pd++ = (u8)rt.year; *pd++ = yc_num; // 遥 测个数 *pd++ = 13; // 类型标识 浮点值 // if(pRunSet->bTT_faultparaDKY) //{ memcpy(pd, arrSoe, yc_num * 6); // 标准规定,遥测点号是2个字节 IEC101_Asdu_Add_sts(pt, g_arrIECBuf, yc_num * 6 + (pd - g_arrIECBuf), 0, 3, 42, FRAME_I_PRIOR_LOW, 0); //} // else //{ // memcpy(pd,arrSoe,vsq*7); // 标准规定,遥测点号是3个字节 // IEC101_Asdu_Add_sts(pt,g_arrIECBuf,vsq*7+14,0,3,42,FRAME_I_PRIOR_LOW ,0); //} } } // pt->bSoeRelink=false; } return cnt; } /************************************************************************** 函数名称:IEC101_Recv_Rst 函数版本:1.00 作者: 创建日期:2008.9.1 函数功能说明:复归数据的接受 输入参数: 输出参数: 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ static void IEC101_Recv_Rst(IEC101_DEF *pt) { if (pt->nTypeCounter) { rt_printf_time("IEC101_Recv_Rst:state=%d,counter=%d\r\n", pt->nTypeCounter, pt->nRecvCounter); } pt->nTypeCounter = 0; pt->nRecvLenth = 0; pt->nRecvCounter = 0; } /************************************************************************** 函数名称:IEC101_AutoTimer 函数版本:1.00 作者: 创建日期:2008.9.1 函数功能说明:101规约链路内容定时处理部分,100毫秒调用1次 输入参数: 输出参数: 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ void IEC101_AutoTimer(IEC101_DEF *pt) { // 如果有数据就发送 IEC101_Send_Polling(pt); // 配电终端在发送复位进程确认命令后需等待3秒,然后重启进程 if (pt->bResetProcess && ustimer_get_duration(pt->us0_ResetProcess) > 3 * USTIMER_SEC) { IEC101_Reset(pt); #ifdef IEC_RESET_PROCESS rt_printf("装置重启\r\n"); watchdog_reset_cpu(2); // 装置复位 #endif } // 如果连接没有建立,不处理下面流程 if (pt->st101 == IEC101_ST_NONE) { return; } // 处理加密遥控报文标志超时返回 if (pRunSet->bTT_SM2) { if (ustimer_get_duration(pt->us0_SM2Over) > 60 * USTIMER_SEC) { pt->bRmtSM2 = false; } } // 1秒处理任务 if (ustimer_get_duration(pt->us0_sec) > USTIMER_SEC) { pt->us0_sec += USTIMER_SEC; pt->secs++; // 通道检查 默认10分钟没有收到数据,认为通道错误 if (pt->dValidTime++ > pRunSet->dT101Check) { char log_info[128]; rt_printf_time("IEC101(%d):通道超时\r\n", pt->chnl); sprintf(log_info, "IEC101(%d):Channel timeout", pt->chnl + 1); load_hs_log_rcd(TYPE_COMM_ERR, true, NULL, log_info, 1); log_flag.com_err[pt->chnl] = true; IEC101_Info_Printf_One(pt); IEC101_Reset(pt); } } if (pt->tf.bTransing || pt->tf.bdatTraned) // 文件启动了传输,在3次重发间隔内,未有文件内容传输更新,释放已申请的文件 { if (ustimer_get_duration(pt->tf.us0_file_trans) > (pRunSet->dT101Resend * USTIMER_SEC * 3)) { #ifdef RCD_STRAN_S log_str_time(LOG_OPERATE, "读文件传输超时!!!!!!!", 0, 1); #endif iec_freefile(&pt->tf); pt->tf.bTransing = false; pt->tf.bdatTraned = false; } } if (pt->tf.bdatwriting) // 文件启动了传输,在3次重发间隔内,未有文件内容传输更新,释放已申请的写文件内存 { if (ustimer_get_duration(pt->tf.us0_file_write) > (pRunSet->dT101Resend * USTIMER_SEC * 3)) { if (!pt->tf.writebuf) { rt_free(&pt->tf.writebuf); pt->tf.writebuf = NULL; } pt->tf.bdatwriting = false; } } if (pt->tf.bUpdateReset) // 文件启动了传输,在3次重发间隔内,未有文件内容传输更新,释放已申请的写文件内存 { if (ustimer_get_duration(pt->tf.us0_updatereset) > (USTIMER_SEC * 2)) { rt_printf("101远程升级,装置复位\r\n"); pt->tf.bUpdateReset = false; watchdog_reset_cpu(2); // 装置复位 } } if (pt->tr.bpreset) // 参数预置 { if (ustimer_get_duration(pt->tr.us0_preset) > (tRunPara.dSM2Time * USTIMER_SEC)) { int i; rt_printf("101预置参数超时清除\r\n"); pt->tr.bpreset = false; for (i = 0; i < RMT_SET_NUMBER; i++) { pt->tr.di[i] = 0; } } } } static bool IEC101_RmtCheck(IEC101_DEF *pt) // 判断是否是遥控报文 { u8 AddByte = 0; if (!pRunSet->bTT_SM2) return false; // 加密功能未投入 if (tRunPara.b101Addr2Byte) AddByte = 1; if ((pt->recvbuf[0] == 0x68) && (pt->recvbuf[4] & 0x40) && ((pt->recvbuf[6 + AddByte] == 45) || (pt->recvbuf[6 + AddByte] == 46))) // 遥控报文 { return true; } return false; } /************************************************************************** 函数名称:IEC101_Recv 函数版本:1.00 作者: 创建日期:2008.9.1 函数功能说明:101规约数据接受判断,只判断是否满足报文格式 输入参数: 输出参数:有完整帧接受,返回1,否则返回 0 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ static int IEC101_Recv(IEC101_DEF *pt, u8 byRevData) { #ifdef FUNC_ENCRY_IN_ONE_SERIAL IEC101_DEF *pt1; static int iCount = 0, enc_tunnel = 0; #endif // 帧内字符间隔超时检测 if (g_tRsComm[pt->chnl].b_recv_reset) { // 不能复位fifo rt_fifo_reset(&pt->recv_fifo); // IEC101_Recv_Rst(pt); g_tRsComm[pt->chnl].b_recv_reset = 0; } pt->recvbuf[pt->nRecvCounter++] = byRevData; pt->dTRecvPiece = dTCounter; // 接收字符时的时刻,在大循环中判断,是否需要复归 switch (pt->nTypeCounter) { case 0: // 统计接收帧数 s_stat_rx_frame(pt->chnl); if (byRevData == 0x68) { pt->nTypeCounter = 1; } else if (byRevData == 0x10) { if (!tRunPara.b101Addr2Byte) { pt->nRecvLenth = 2; } else { pt->nRecvLenth = 3; } pt->nTypeCounter = 6; pt->nRecvCnt = 0; pt->sum = 0; } else if (byRevData == 0x16) // SM2 报文 { pt->nTypeCounter = 20; pt->nRecvCounter = 256; pt->recvbuf[pt->nRecvCounter++] = byRevData; pt->recvbuf[0] = 0x68; // 恢复被0x16 覆盖的第一字节数据 if (!pRunSet->bTT_SM2) // 加密功能未投入 { IEC101_Recv_Rst(pt); // 统计接收错误帧数 s_stat_rx_errframe(pt->chnl); } } #ifdef FUNC_ENCRY_IN_ONE_SERIAL else if (byRevData == 0xAA) // 海南101规约报文 { pt->nTypeCounter = 26; pt->sum = 0; } #endif else { rt_printf_time("IEC101_Recv(%d):0x%02x.\r\n", pt->chnl, byRevData); IEC101_Recv_Rst(pt); // 统计接收错误帧数 s_stat_rx_errframe(pt->chnl); } break; case 1: pt->nTypeCounter = 2; pt->nRecvLenth = byRevData; // 长帧长度 if (pt->nRecvLenth > IEC101_RECVBUF_MAX) { IEC101_Recv_Rst(pt); // 统计接收错误帧数 s_stat_rx_errframe(pt->chnl); } break; case 2: if (pt->nRecvLenth == byRevData) { pt->nTypeCounter = 3; } else { IEC101_Recv_Rst(pt); // 统计接收错误帧数 s_stat_rx_errframe(pt->chnl); } break; case 3: if (byRevData == 0x68) { pt->nTypeCounter = 6; pt->nRecvCnt = 0; pt->sum = 0; } else { IEC101_Recv_Rst(pt); // 统计接收错误帧数 s_stat_rx_errframe(pt->chnl); } break; // case 4: //控制域 // pt->sum=byRevData; // pt->nTypeCounter=5; // break; // case 5: //目标地址 // pt->sum+=byRevData; // pt->nRecvCnt=0; // pt->nTypeCounter=6; // if(pt->nRecvLenth==2)//短帧 // pt->nTypeCounter=7; // break; case 6: // 有效数据 //有效数据 if (pt->recvbuf[4] == 0x05 && pt->recvbuf[0] == 0x68) // 映翰通 GPRS信息返回报文,同101不同,需特殊处理 { pt->nRecvLenth = 0x30 + 1; } if (++pt->nRecvCnt >= pt->nRecvLenth) { pt->nTypeCounter = 7; } pt->sum += byRevData; break; case 7: // 校验和 pt->recvsum = byRevData; pt->nTypeCounter = 8; break; case 8: // 结束符 pt->nTypeCounter = 0; IEC101_Recv_Rst(pt); if (byRevData == 0x16) // 校验结束字符,报文接收完毕, { if ((pt->sum != pt->recvsum) && (pt->recvbuf[4] != 0x05)) // 映翰通 GPRS信息返回报文,同101不同,需特殊处理 { s_stat_rx_errframe(pt->chnl); // 统计接收帧错误计数 } else if (IEC101_RmtCheck(pt)) // 是遥控报文 { pt->bData = false; // 应用层暂不处理,等解密完成后处理 if (pRunSet->bTT_SM2) { pt->bRmtSM2 = true; pt->us0_SM2Over = ustimer_get_origin(); } } else { pt->bData = true; if (uart_test_begin) { uart_test_flag[pt->chnl][1] = 1; } // 唤醒主循环 mainloop_wakeup(); return 1; } } break; case 20: // SM2报文 L2长度 pt->SM2L2 = byRevData; pt->nTypeCounter++; break; case 21: // SM2报文 L2长度 pt->nTypeCounter++; if (byRevData >= pt->SM2L2) // L2长度包含L3长度,L3chnl); IEC101_Recv_Rst(pt); } break; case 22: if (byRevData == 0x16) { pt->nTypeCounter++; pt->nRecvCnt = 0; } else { // 统计接收错误帧数 s_stat_rx_errframe(pt->chnl); IEC101_Recv_Rst(pt); } break; case 23: if (++pt->nRecvCnt >= pt->SM2L2) { pt->nTypeCounter++; } break; case 24: // 校验和 pt->nTypeCounter++; break; case 25: // 结束符 pt->nTypeCounter = 0; IEC101_Recv_Rst(pt); if (byRevData == 0x16) // 校验结束字符,报文接收完毕, { pt->bSM2Data = true; mainloop_wakeup(); return 1; } else { s_stat_rx_errframe(pt->chnl); } break; #ifdef FUNC_ENCRY_IN_ONE_SERIAL case 26: // 帧长低字节 pt->nTypeCounter++; pt->nRecvLenth = byRevData; pt->sum += byRevData; break; case 27: // 帧长高字节 pt->nTypeCounter++; pt->nRecvLenth = (byRevData << 8) | pt->nRecvLenth; pt->sum += byRevData; break; case 28: // 加密隧道编号 pt->nTypeCounter++; pt->enc_tunnel = byRevData; if (pt->enc_tunnel > 1) { enc_tunnel = pt->enc_tunnel + 1; } else { enc_tunnel = pt->enc_tunnel; } pt->sum += byRevData; pt->nRecvCnt = 0; break; case 29: // 数据 if (++pt->nRecvCnt >= pt->nRecvLenth) { pt->nTypeCounter++; } pt->sum += byRevData; if (tRunPara.tUartPara[0].wProtocol == PROTOCOL_WED_ENC) { pt1 = (IEC101_DEF *)g_tRsComm[enc_tunnel].ptBuf; pt1->recvbuf[iCount] = byRevData; pt1->enc_tunnel = pt->enc_tunnel; pt1->nRecvLenth = pt->nRecvLenth; iCount++; } break; case 30: // 校验和 pt->recvsum = byRevData; pt->nTypeCounter++; break; case 31: // 帧尾 pt->nTypeCounter = 0; IEC101_Recv_Rst(pt); iCount = 0; if (byRevData == 0x55) // 校验结束字符,报文接收完毕, { if (pt->sum != pt->recvsum) { s_stat_rx_errframe(pt->chnl); // 统计接收帧错误计数 } else { pt1 = (IEC101_DEF *)g_tRsComm[enc_tunnel].ptBuf; pt1->bData = true; enc_tunnel = 0; // 唤醒主循环 mainloop_wakeup(); return 1; } } break; #endif default: // 统计接收错误帧数 s_stat_rx_errframe(pt->chnl); IEC101_Recv_Rst(pt); break; } // 类型校验结束 return 0; } void IEC101_Recv_App(IEC101_DEF *pt) { u8 c; // 接收数据 while (pt->bSM2Data == false && pt->bData == false) { if (rt_fifo_get(&pt->recv_fifo, &c, 1) == 0) { if (g_tRsComm[pt->chnl].b_recv_reset) { IEC101_Recv_Rst(pt); g_tRsComm[pt->chnl].b_recv_reset = 0; } break; } if (pRunSet->bTT_ESAM && (!pt->bMaintainComm)) { int ret; ret = sec_recv(&pt->rx_buf, c, pt->extsendbuf, true); if (ret == SEC_FRAME_OK_APP) // 带规约的报文 { pt->bData = 1; } else if (ret == SEC_FRAME_OK_EXT) // 纯安全扩展报文 发送 { IEC101_ext_send(pt, pt->extsendbuf + 2, ((pt->extsendbuf[0] << 8) + pt->extsendbuf[1])); } } else { #ifdef FUNC_ENCRY_IN_ONE_SERIAL int ret; ret = IEC101_Recv(pt, c); if (ret == 1) break; #else IEC101_Recv(pt, c); #endif } } } void IEC101_Recv_Int(IEC101_DEF *pt, u8 byRevData) { // 记录每个不定长帧报文的接收时刻 if (!pRunSet->bTT_ESAM || pt->bMaintainComm) { if (byRevData == 0x68) { struct rt_fifo *f; f = &pt->recv_fifo; // 判断不定帧长报头 0x68 L L 0X68 if ((f->buffer[(f->in - 1) & (f->size - 1)]) == (f->buffer[(f->in - 2) & (f->size - 1)]) && (f->buffer[(f->in - 3) & (f->size - 1)]) == 0x68) { pt->us0_frame = ustimer_get_origin(); } } } rt_fifo_put(&pt->recv_fifo, &byRevData, 1); } int IEC101_PH_GprsInfo(void) { int i; for (i = 0; i < CFG_UART_NUM_MAX; i++) { if (UART_CHANNEL[i] < 0) { continue; } // 外部通信报文处理 if (tRunPara.tUartPara[i].wProtocol == PROTOCOL_101_PH) // 101规约 { IEC101_DEF *pt = (IEC101_DEF *)g_tRsComm[i].ptBuf; pt->bCallGprsInf = true; } } return 0; } void IEC101_Info_Printf_One(IEC101_DEF *p) { iec_tbuf_t *pt; rt_printf("\r\n 通道属性:type=%d,chnl=%d\r\n", p->type, p->chnl); if (p->st101 == 0) { return; } rt_printf(" 规约状态:state=%d,bSendChange=%d\r\n", p->st101, p->bSendChange); rt_printf(" 连接计时:LinkCount(%d):%d秒\r\n", p->LinkCount, p->secs); pt = &p->tx_buf[0]; rt_printf("发送BUF[0]:head=%d,tail_send=%d,tail_ack=%d\r\n", pt->tb_head, pt->tb_tail_send, pt->tb_tail_ack); pt = &p->tx_buf[1]; rt_printf("发送BUF[1]:head=%d,tail_send=%d,tail_ack=%d\r\n", pt->tb_head, pt->tb_tail_send, pt->tb_tail_ack); rt_printf("SOE通讯BUF:head=%d,tail_send=%d,tail_ack=%d\r\n", g_soe_queue.head.n, g_soe_queue.tail_send[p->chnl].n, g_soe_queue.tail_ack[p->chnl].n); rt_printf("SOE事件BUF:head=%d,tail_send=%d\r\n", g_soe_queue.head.n, g_soe_queue.tail_eep.n); pt = &p->tx_buf[2]; rt_printf("发送BUF[1]:head=%d,tail_send=%d,tail_ack=%d\r\n", pt->tb_head, pt->tb_tail_send, pt->tb_tail_ack); rt_printf("SOE通讯BUF:head=%d,tail_send=%d,tail_ack=%d\r\n", g_soe_queue.head.n, g_soe_queue.tail_send[p->chnl].n, g_soe_queue.tail_ack[p->chnl].n); rt_printf("SOE事件BUF:head=%d,tail_send=%d\r\n", g_soe_queue.head.n, g_soe_queue.tail_eep.n); return; } int IEC101_Info_Printf(void) { int i; IEC101_DEF *p; // 串口101 rt_printf("\r\n串口101:"); for (i = 0; i < CFG_UART_NUM_MAX; i++) { // 如果串口不存在,跳过 if (UART_CHANNEL[i] < 0) { continue; } if (tRunPara.tUartPara[i].wProtocol != PROTOCOL_101 && tRunPara.tUartPara[i].wProtocol != PROTOCOL_101_PH && tRunPara.tUartPara[i].wProtocol != PROTOCOL_MAINTAIN) { rt_printf("\r\n通道属性:非104通道(chnl=%d)\r\n", i); continue; } p = (IEC101_DEF *)g_tRsComm[i].ptBuf; IEC101_Info_Printf_One(p); } return 0; } /************************************************下面是远方修改定值代码******************************************************/ void IEC101_setion_chg_msg(IEC101_DEF *pt101, u8 *ps) { #ifdef YPARA_LINK WORD section; msg_data_struct *msg_data = &temp_par; msg_oper_setion_struct *setion_msg = &msg_data->setion_msg; section = ps[0] + (ps[1] << 8); // 定值区号 msg_data->sport = pt101->chnl; msg_data->dport = 0x1F; // 进行广播 msg_data->type = settion_swht; setion_msg->section_p = section; mananger_msg_ypara_s(msg_data, msg_data->sport, msg_data->dport); #endif } void IEC101_SectionWrite(IEC101_DEF *pt101, u8 *ps) // 确认帧 { u8 *pd; int num; u8 cot; WORD section; if (pRunSet->wEquTypeManager) IEC101_setion_chg_msg(pt101, ps); // 组包 section = ps[0] + (ps[1] << 8); // 定值区号 cot = 0x47; if (section < SEC_NUMBER) { m_dstsection = section; if (mmd_changesection(section) == M_JUMP0) cot = 0x07; } // 组织报文 pd = g_arrIECBuf; num = 0; pd[num++] = 0; // 信息体地址 pd[num++] = 0; // 信息体地址 pd[num++] = ps[0]; // 当前定值区 pd[num++] = ps[1]; // 当前定值区 IEC101_Asdu_Add(pt101, g_arrIECBuf, num, 1, cot, 200, FRAME_I); // } void IEC101_SectionRead(IEC101_DEF *pt101) // 读定值区 { u8 *pd; int num; u8 cot; // 组织报文 pd = g_arrIECBuf; num = 0; pd[num++] = 0; // 信息体地址 pd[num++] = 0; // 信息体地址 pd[num++] = (u8)m_runsection; // 当前定值区 pd[num++] = (u8)(m_runsection >> 8); // 当前定值区 pd[num++] = 0; // 最小定值区 pd[num++] = 0; // 最小定值区 pd[num++] = SEC_NUMBER - 1; // 最大定值区 1 标准上是7个字节,暂时认为是2个字节 pd[num++] = 0; // 最大定值区 2 cot = IEC_COT_ACTCON; IEC101_Asdu_Add(pt101, g_arrIECBuf, num, 1, cot, 201, FRAME_I); // } /**************************************************远方修改定值结束*****************************************************/ /***********************************************下面是101规约文件传输部分代码2016-8-10**************************************************/ /************************************************************************** 函数名称:IEC101_Direct_Echo 函数版本:1.00 作者: 创建日期:2007.9.1 函数功能说明:101规约目录响应 输出参数:pt101缓冲区地址,COT 传送原因 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ void IEC101_Direct_Echo(IEC101_DEF *pt101, DWORD InfoAddr) { BYTE *pd = g_arrIECBuf; // 从第7个字节开始 BYTE vsq; int filenum; struct dir_file_struct *pdir, *pdirfree; int i; vsq = 0; pdir = hf_get_dir_file(IEC_Getdir(InfoAddr), &filenum); // 根据信息体地址,判断读读哪个目录,获取目录信息 if (pdir == NULL) { IEC101_Asdu_Add(pt101, g_arrIECBuf, 0, vsq, 5, 126, FRAME_I); // 无文件应答 return; } pdirfree = pdir; *pd++ = (BYTE)InfoAddr; *pd++ = (BYTE)(InfoAddr >> 8); for (i = 0; i < filenum; i++) { struct rtc_time_t rtc; /*文件名称*/ *pd++ = (BYTE)(pdir->file_name); *pd++ = (BYTE)(pdir->file_name >> 8); /*文件长度*/ *pd++ = (BYTE)(pdir->file_size); *pd++ = (BYTE)(pdir->file_size >> 8); *pd++ = (BYTE)(pdir->file_size >> 16); /*文件的状态SOF*/ *pd++ = 0; /*日历时钟*/ timespec_to_rtc(pdir->file_time, &rtc, 1); *pd++ = (BYTE)(rtc.ms); *pd++ = (BYTE)(rtc.ms >> 8); *pd++ = (BYTE)(rtc.min); *pd++ = (BYTE)(rtc.hour); *pd++ = (BYTE)(rtc.day); *pd++ = (BYTE)(rtc.month); *pd++ = (BYTE)(rtc.year); vsq++; if ((vsq >= 15) && i < (pdir->file_size - 1)) // 目录超出一帧上送范围,分帧,VSQ置后续标准位 { vsq |= 0x80; IEC101_Asdu_Add(pt101, g_arrIECBuf, (vsq & 0x7f) * 13 + 2, vsq, 5, 126, FRAME_I); // 无文件应答 vsq = 0; pd = g_arrIECBuf; } pdir++; } rt_free(pdirfree); if (vsq > 0) { if (vsq > 1) { vsq |= 0x80; } IEC101_Asdu_Add(pt101, g_arrIECBuf, (vsq & 0x7f) * 13, vsq, 5, 126, FRAME_I); // 无文件应答 } else { IEC101_Asdu_Add(pt101, g_arrIECBuf, 0, vsq, 5, 126, FRAME_I); // 无文件应答 } } /************************************************************************** 函数名称:IEC101_EchoFileReady 函数版本:1.00 作者: 创建日期:2007.9.1 函数功能说明:应答文件准备好 输出参数:pt101缓冲区地址,filename 文件名 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ static void IEC101_EchoFileReady(IEC101_DEF *pt101, DWORD InfoAddr, WORD filename) { BYTE *pd = g_arrIECBuf; // 从第7个字节开始 int filelenth, sectionnum; filelenth = hf_get_file_inf(IEC_Getdir(InfoAddr), filename, §ionnum); /*文件名称*/ *pd++ = (BYTE)InfoAddr; *pd++ = (BYTE)(InfoAddr >> 8); *pd++ = (BYTE)(filename); // 10 *pd++ = (BYTE)(filename >> 8); // 11 *pd++ = (BYTE)filelenth; // 文件长度 *pd++ = (BYTE)(filelenth >> 8); // 文件长度 *pd++ = (BYTE)(filelenth >> 16); // 文件长度 if (filelenth > 0) { *pd++ = 0; /*FRQ=0肯定认可*/ } else { *pd++ = 0x80; /*FRQ=0否定定认可*/ } pt101->tfile.filelenth = filelenth; pt101->tfile.setctionnum = sectionnum; pt101->tfile.filename = filename; pt101->tfile.InfoAddr = InfoAddr; pt101->tfile.filechecksum = 0; IEC101_Asdu_Add(pt101, g_arrIECBuf, 8, 1, 13, 120, FRAME_I); // 无文件应答 } /************************************************************************** 函数名称:IEC101_EchoSectionReady 函数版本:1.00 作者: 创建日期:2007.9.1 函数功能说明:应答节准备好 输出参数:pt101缓冲区地址,filename 文件名 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ static void IEC101_EchoSectionReady(IEC101_DEF *pt101, DWORD InfoAddr, WORD filename, BYTE section) { BYTE *pd = g_arrIECBuf; // 从第7个字节开始 int setctionlenth; char *pdat = NULL; IEC101_FreeFile(pt101); *pd++ = (BYTE)InfoAddr; *pd++ = (BYTE)(InfoAddr >> 8); /*文件名称*/ *pd++ = (BYTE)(filename); // 10 *pd++ = (BYTE)(filename >> 8); // 11 if (pt101->tfile.InfoAddr == InfoAddr && pt101->tfile.filename == filename && section <= pt101->tfile.setctionnum) // 文件名相同文件有效 { pdat = hf_get_file_part(IEC_Getdir(InfoAddr), filename, section, &setctionlenth); } *pd++ = section; // 节名 /*节长度长度*/ *pd++ = (BYTE)(setctionlenth); // 12 *pd++ = (BYTE)(setctionlenth >> 8); // 13 *pd++ = (BYTE)(setctionlenth >> 16); // 14 if (pdat == NULL || setctionlenth == 0) { *pd++ = 0x80; /*FRQ=0否定定认可*/ } else { *pd++ = 0x0; /*FRQ=0 肯定认可*/ // 15 pt101->tfile.pdat = pdat; // 保存节内容地址,后续需要释放 pt101->tfile.sectionlenth = setctionlenth; pt101->tfile.transsection = section; pt101->tfile.bFiletransing = true; pt101->tfile.bSegtransing = false; pt101->tfile.us0_file_trans = ustimer_get_origin(); pt101->tfile.transBytes = 0; pt101->tfile.sectionchecksum = 0; } IEC101_Asdu_Add(pt101, g_arrIECBuf, 9, 1, 13, 121, FRAME_I); // 无文件应答 } /************************************************************************** 函数名称:IEC101_SegmentSend 函数版本:1.00 作者: 创建日期:2007.9.1 函数功能说明:应答节传输中段的数据 输出参数:pt101缓冲区地址,filename 文件名,section 节名 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ static void IEC101_SegmentSend(IEC101_DEF *pt101, DWORD InfoAddr, WORD filename, BYTE section) { BYTE *pd = g_arrIECBuf; // 从第7个字节开始 BYTE sectionchecksum = 0; // 节校验和 BYTE segmentbytes; BYTE cot; int i; cot = 13; segmentbytes = 0; if (pt101->tfile.bFiletransing && pt101->tfile.InfoAddr == InfoAddr && pt101->tfile.filename == filename && section == pt101->tfile.transsection) // 文件名相同 节相同 { if (pt101->tfile.transBytes + FILE_SEGMENT_BYTES >= pt101->tfile.sectionlenth) { segmentbytes = pt101->tfile.sectionlenth % FILE_SEGMENT_BYTES; // 最后的段的字节数 } else { segmentbytes = FILE_SEGMENT_BYTES; } pt101->tfile.sectionchecksum = sectionchecksum; pt101->tfile.us0_file_trans = ustimer_get_origin(); pt101->tfile.bSegtransing = true; } else { cot |= 0x40; segmentbytes = 0; IEC101_FreeFile(pt101); } /*文件名称*/ *pd++ = (BYTE)InfoAddr; *pd++ = (BYTE)(InfoAddr >> 8); *pd++ = (BYTE)(filename); // 10 *pd++ = (BYTE)(filename >> 8); // 11 *pd++ = section; // 节名 12 *pd++ = segmentbytes; // 段的长度 13 for (i = 0; i < segmentbytes; i++) { *pd = pt101->tfile.pdat[pt101->tfile.transBytes++]; sectionchecksum += *pd++; } IEC101_Asdu_Add(pt101, g_arrIECBuf, 6 + segmentbytes, 1, cot, 125, FRAME_I); // 无文件应答 } /************************************************************************** 函数名称:IEC101_EchoLastSection 函数版本:1.00 作者: 创建日期:2007.9.1 函数功能说明:应答最后的节或段 输出参数:pt101缓冲区地址,filename 文件名,section 节名,LSQ限定词 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ static void IEC101_EchoLastSection(IEC101_DEF *pt101, WORD filename, BYTE section, BYTE LSQ) { BYTE *pd = g_arrIECBuf; // 从第7个字节开始 /*文件名称*/ *pd++ = (BYTE)pt101->tfile.InfoAddr; *pd++ = (BYTE)(pt101->tfile.InfoAddr >> 8); *pd++ = (BYTE)(filename); // 10 *pd++ = (BYTE)(filename >> 8); // 11 *pd++ = section; // 节名 12 switch (LSQ) { case 1: *pd++ = 1; // LSQ=1:不带停止激活文件传输13 *pd++ = pt101->tfile.filechecksum; // 文件校验和 14 break; case 3: *pd++ = 3; // LSQ=3:不带停止激活的节传输 *pd++ = pt101->tfile.sectionchecksum; // 节校验和 break; } IEC101_Asdu_Add(pt101, g_arrIECBuf, 7, 1, 13, 123, FRAME_I); // 无文件应答 } /************************************************************************** 函数名称:IEC101_SegmentContinue 函数版本:1.00 作者: 创建日期:2007.9.1 函数功能说明:主循环中继续发送节中剩余的段 输出参数:pt101缓冲区地址,filename 文件名,section 节名 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ void IEC101_SegmentContinue(IEC101_DEF *pt101) { BYTE *pd = g_arrIECBuf; // 从第7个字节开始 BYTE sectionchecksum = 0; // 节校验和 BYTE segmentbytes; int i; if (!pt101->tfile.bSegtransing) return; if (pt101->tfile.transBytes < pt101->tfile.sectionlenth) // 段还未传输完毕 { if (pt101->tfile.transBytes + FILE_SEGMENT_BYTES >= pt101->tfile.sectionlenth) { segmentbytes = pt101->tfile.sectionlenth % FILE_SEGMENT_BYTES; // 最后的段的字节数 } else { segmentbytes = FILE_SEGMENT_BYTES; } *pd++ = (BYTE)pt101->tfile.InfoAddr; *pd++ = (BYTE)(pt101->tfile.InfoAddr >> 8); /*文件名称*/ *pd++ = (BYTE)(pt101->tfile.filename); // 10 *pd++ = (BYTE)(pt101->tfile.filename >> 8); // 11 *pd++ = pt101->tfile.transsection; // 节名 12 *pd++ = segmentbytes; // 段的长度 13 for (i = 0; i < segmentbytes; i++) { *pd = pt101->tfile.pdat[pt101->tfile.transBytes++]; sectionchecksum += *pd++; } pt101->tfile.sectionchecksum += sectionchecksum; pt101->tfile.us0_file_trans = ustimer_get_origin(); IEC101_Asdu_Add(pt101, g_arrIECBuf, 6 + segmentbytes, 1, 13, 125, FRAME_I); // 无文件应答 } else { IEC101_EchoLastSection(pt101, pt101->tfile.filename, pt101->tfile.transsection, 3); IEC101_FreeFile(pt101); } } void IEC101_File_Respone(IEC101_DEF *pt101, BYTE *dat, BYTE cot, BYTE SCAFQ) // { BYTE *pd = g_arrIECBuf; // 从第7个字节开始 u8 vsq, type; u8 CotByte = 0; // 传送原因双字节 u8 AppByte = 0; // 应用单元地址双字节 if (tRunPara.b101Cot2Byte) CotByte = 1; if (tRunPara.b101App2Byte) AppByte = 1; type = dat[0]; vsq = dat[1]; *pd++ = dat[4 + CotByte + AppByte]; // 应用单元地址 *pd++ = dat[5 + CotByte + AppByte]; // *pd++ = dat[6 + CotByte + AppByte]; // 文件名 *pd++ = dat[7 + CotByte + AppByte]; // *pd++ = dat[8 + CotByte + AppByte]; // 节名 12 *pd++ = SCAFQ; // 限定词 IEC101_Asdu_Add(pt101, g_arrIECBuf, 6, vsq, cot, type, FRAME_I); // 无文件应答 } /************************************************************************** 函数名称:IEC101_File_Echo 函数版本:1.00 作者: 创建日期:2007.9.1 函数功能说明:对文件召唤的应答处理 输出参数:pt101缓冲区地址,dat报文缓冲区地址从报文的帧类型开始 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ void IEC101_File_App(IEC101_DEF *pt101, BYTE *dat) // dat 从报文的帧类型开始 { BYTE COT, SCQ; // 传送原因 WORD filename, section; // 文件名 节名 DWORD InfoAddr; // 信息体地址 u8 CotByte = 0; // 传送原因双字节 u8 AppByte = 0; // 应用单元地址双字节 if (tRunPara.b101Cot2Byte) CotByte = 1; if (tRunPara.b101App2Byte) AppByte = 1; COT = dat[2]; InfoAddr = dat[4 + CotByte + AppByte] + (dat[5 + CotByte + AppByte] << 8); filename = dat[6 + CotByte + AppByte] + (dat[7 + CotByte + AppByte] << 8); // 文件名 section = dat[8 + CotByte + AppByte]; // 节名 SCQ = dat[9 + CotByte + AppByte]; // 选择和召唤限定词 // rt_printf("\r\n COT=%d addr=%4x filename=%d,section=%d,SCQ=%d",COT,InfoAddr,filename,section,SCQ); if (IEC_Getdir(InfoAddr) == NULL) { IEC101_File_Respone(pt101, dat, (47 | 0x40), SCQ); // 否定应答,非所希望的通信服务 return; } if (COT == 5) /*请求目录 */ { IEC101_Direct_Echo(pt101, InfoAddr); /*目录响应 cot =5代表召唤目录*/ } else if (COT == 13) { switch (SCQ) { case 1: /*选择文件*/ IEC101_EchoFileReady(pt101, InfoAddr, filename); break; case 2: /*请求文件*/ IEC101_EchoSectionReady(pt101, InfoAddr, filename, section); // 节准备好报文 break; case 6: /*请求节*/ IEC101_SegmentSend(pt101, InfoAddr, filename, section); // 发送本节中所有的段 break; default: IEC101_File_Respone(pt101, dat, COT, SCQ | 0x30); // 否定应答,非所希望的通信服务 break; } } else { IEC101_File_Respone(pt101, dat, (45 | 0x40), SCQ); // 否定应答,未知的传送原因 } } /************************************************************************** 函数名称:IEC101_File_Echo 函数版本:1.00 作者: 创建日期:2007.9.1 函数功能说明:对文件召唤的应答处理 输出参数:pt101缓冲区地址,dat报文缓冲区地址从报文的帧类型开始 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ void IEC101_File_Echo(IEC101_DEF *pt101, BYTE *dat) // dat 从报文的帧类型开始 { BYTE COT, SCQ; // 传送原因 WORD filename, section; // 文件名 节名 DWORD InfoAddr; // 信息体地址 u8 CotByte = 0; // 传送原因双字节 u8 AppByte = 0; // 应用单元地址双字节 if (tRunPara.b101Cot2Byte) CotByte = 1; if (tRunPara.b101App2Byte) AppByte = 1; COT = dat[2]; InfoAddr = dat[4 + CotByte + AppByte] + (dat[5 + CotByte + AppByte] << 8); filename = dat[6 + CotByte + AppByte] + (dat[7 + CotByte + AppByte] << 8); // 文件名 section = dat[8 + CotByte + AppByte]; // 节名 SCQ = dat[9 + CotByte + AppByte]; // 选择和召唤限定词 // rt_printf("\r\n COT=%d addr=%4x filename=%d,section=%d,SCQ=%d",COT,InfoAddr,filename,section,SCQ); if (IEC_Getdir(InfoAddr) == NULL) { IEC101_File_Respone(pt101, dat, (47 | 0x40), SCQ); // 否定应答,非所希望的通信服务 return; } if (COT == 5) /*请求目录 */ { IEC101_Direct_Echo(pt101, InfoAddr); /*目录响应 cot =5代表召唤目录*/ } else if (COT == 13) { switch (SCQ) { case 1: /*选择文件*/ IEC101_EchoFileReady(pt101, InfoAddr, filename); break; case 2: /*请求文件*/ IEC101_EchoSectionReady(pt101, InfoAddr, filename, section); // 节准备好报文 break; case 6: /*请求节*/ IEC101_SegmentSend(pt101, InfoAddr, filename, section); // 发送本节中所有的段 break; default: IEC101_File_Respone(pt101, dat, COT, SCQ | 0x30); // 否定应答,非所希望的通信服务 break; } } else { IEC101_File_Respone(pt101, dat, (45 | 0x40), SCQ); // 否定应答,未知的传送原因 } } /************************************************************************** 函数名称:IEC101_File_Sure 函数版本:1.00 作者: 创建日期:2007.9.1 函数功能说明:对文件处理的确认应答 输出参数:pt缓冲区地址,dat报文缓冲区地址从报文的帧类型开始 返回值: 更新信息: 更新日志1: 日期: 修改者: 修改内容: 修改原因: ***************************************************************************/ void IEC101_File_Sure(IEC101_DEF *pt101, BYTE *dat) // dat 从报文的帧类型开始 { BYTE COT, AFQ; // 传送原因 WORD filename, section; // 文件名 节名 DWORD InfoAddr; // 信息体地址 u8 CotByte = 0; // 传送原因双字节 u8 AppByte = 0; // 应用单元地址双字节 if (tRunPara.b101Cot2Byte) CotByte = 1; if (tRunPara.b101App2Byte) AppByte = 1; COT = dat[2]; InfoAddr = dat[4 + CotByte + AppByte] + (dat[5 + CotByte + AppByte] << 8); filename = dat[6 + CotByte + AppByte] + (dat[7 + CotByte + AppByte] << 8); // 文件名 section = dat[8 + CotByte + AppByte]; // 节名 AFQ = dat[9 + CotByte + AppByte]; // 选择和召唤限定词 if (IEC_Getdir(InfoAddr) == NULL) { IEC101_File_Respone(pt101, dat, (47 | 0x40), AFQ); // 否定应答,未知的信息体地址 return; } if (COT != 13) { IEC101_File_Respone(pt101, dat, (45 | 0x40), AFQ); // 否定应答,未知的传送原因 } switch (AFQ) /*AFQ文件确认或节确认*/ { case 1: // 文件传输的正确认可 IEC101_FreeFile(pt101); IEC101_File_Respone(pt101, dat, COT, AFQ); // 需商讨,文件传输完毕后,对文件的确认,需要上送什么信息合适,以前是上送更新后的目录 sunxi break; case 2: // 文件传输的否定认可 IEC101_FreeFile(pt101); IEC101_File_Respone(pt101, dat, COT, AFQ); // 需商讨,文件传输完毕后,对文件的确认,需要上送什么信息合适,以前是上送更新后的目录 sunxi break; case 3: // 节传输的正确认可 IEC101_FreeFile(pt101); if (pt101->tfile.transsection < pt101->tfile.setctionnum - 1) // 现有调试工具UTDEBU,节名从0开始, tfile.setctionnum 需减1 sunxi ,后续程序恢复 { pt101->tfile.filechecksum += pt101->tfile.sectionchecksum; // 文件校验和 section += 1; IEC101_EchoSectionReady(pt101, InfoAddr, filename, section); // 下一节准备好 } else if (section >= pt101->tfile.setctionnum - 1) // //现有调试工具UTDEBU,节名从0开始, tfile.setctionnum 需减1 sunxi,后续程序恢复 { IEC101_EchoLastSection(pt101, filename, section, 1); // LSQ = 3; 最后的段 } break; case 4: // 节传输的否定认可 IEC101_FreeFile(pt101); IEC101_EchoSectionReady(pt101, InfoAddr, filename, section); // 节准备好报文 break; default: break; } } void IEC101_FreeFile(IEC101_DEF *pt101) { if (pt101->tfile.bFiletransing) { pt101->tfile.bFiletransing = false; pt101->tfile.bSegtransing = false; if (!pt101->tfile.pdat) { rt_free(pt101->tfile.pdat); pt101->tfile.pdat = NULL; } rt_printf("\r\nIEC101文件节传输完毕,释放内存,文件:%d 节:%d\r\n", pt101->tfile.filename, pt101->tfile.transsection); } }