Sb7:近期处理的一个面向对象的用电协议数据的解析问题解决方法的日记-2

发布时间 2023-12-12 14:16:23作者: 晨耕暮饮

续写“Sb3:近期处理的一个面向对象的用电协议数据的解析问题解决方法的日记”https://www.cnblogs.com/yjcore/p/15156386.html

这可能是我见过拖延症最严重的一个程序员了

上一次写这个话题日记还是2021-08-18,那时候我应该才加入到这个电力公司,也是刚接触“DL/T698数据协议”整整两个月,但是对于里面的面向对象思想我已经基本看懂了,所以才有了上面那篇嘚瑟的日记,其实那时候还没转正,能够快速读懂电力通讯协议对于转正还是非常加分的,虽然社招人员还有很多其他要求。

申明:本文编写的内容若含有保密信息或者敏感信息,请联系我,立刻删除。

参考:《DL/T、Q/GDW电力行业标准、国家电网公司企业标准DL/T 698.45-2017、Q/GDW 11778-2017面向对象的用电信息数据交换协议》

首先我需要对上一篇日记提出一个修正:文中提到帧头是固定格式,这个是不妥的,因为截至目前我涉及的业务中服务器地址SA就不是固定的,它也会根据实际情况决定是否含有“扩展逻辑地址/分路地址”。那么我们在解析SA对象的时候,就需要判断“地址特征”,当bit5=1的时候我们需要解析以为“扩展逻辑地址/分路地址”,同理我们在实际组包的时候也需要注意;

下面我就具体说一下我是怎么实现数据解析的(关于对应的数据组包还需要另外说)。

其实我们在解析报文的时候也是按照顺序解析的,只是在apdu模块我们是不知道具体的数据协议类型的,这个时候就用到了面向对象的多态思想。下面我就不多废话了,直接上代码吧。

if (!framHexstring.StartsWith("68") || !framHexstring.EndsWith("16"))
{
    throw new Exception("协议格式不正确,帧头帧尾不正确");
}
string _headstring = "";
int frameLength = framHexstring.Length / 2;
framHexstring = framHexstring.Substring(2);//去68
byte[] _L = BasicMethod.StrToToHexByte(framHexstring.Substring(0, 4));//取L
int _frameLength = (BasicMethod.Bytes2ToInt(_L) << 2) >> 2;
if (frameLength != (_frameLength + 2))//检测协议长度
{
    throw new Exception("协议格式不正确,长度有误");
}
FCS = framHexstring.Substring(framHexstring.Length - 6, 4).ToUpper();
string _infcs = BasicMethod.GetCRC698(BasicMethod.StrToToHexByte(framHexstring.Substring(0, framHexstring.Length - 6)));
if (FCS != _infcs)
{
  throw new Exception("帧尾协议校验出错");
}
#endregion
#region 2-68帧头
RootElement.Add(new XElement("Object", "", new XAttribute[] { new XAttribute("FrameRegion", "起始符68H(1BIN)"), new XAttribute("FrameRegionShow", "起始符"), new XAttribute("Data", "68"), new XAttribute("Explain", "起始符"), new XAttribute("Key", "68"), new XAttribute("Val1", ""), new XAttribute("index", ""), new XAttribute("Type", ""), new XAttribute("StartIndex", "0"), new XAttribute("Length", "2"), new XAttribute("ShowFlag", "0"), new XAttribute("Remark", "") }));
#endregion
#region 3-解析Head<返回帧头List<XElement>>
_headstring += framHexstring.Substring(0, 6);
framHexstring = framHexstring.Substring(6);//去L+C
byte[] _SaAttribute = BasicMethod.StrToToHexByte(framHexstring.Substring(0, 2));//取SA[0]
int _AddLength = (((((int)_SaAttribute[0]) << 4) % 128) >> 4) + 1;//地址长度
int _Bit5 = (((int)_SaAttribute[0]) & 32)>>5;
_headstring += framHexstring.Substring(0, (1 +  _AddLength) * 2 + 2+4);
framHexstring = framHexstring.Substring((1 + _AddLength) * 2 + 2+4);//去SA+CA+帧头校验
Head = new HeadStruct(_headstring);
foreach (XElement element in Head.RootElement)
{
     RootElement.Add(element);
}
#endregion
View Code

上述代码就是将帧头区域预解析以下,确定帧头的具体长度,然后将帧头(包含帧头校验位)送到对象Head = new HeadStruct(_headstring);中进行解析,就可以把帧头部分全部解析了。

public HeadStruct(string headHexstring)
{
    try
    {
        RootElement = new List<XElement>();
        HCS = headHexstring.Substring(headHexstring.Length - 4).ToUpper();
        string _inhcs = BasicMethod.GetCRC698(BasicMethod.StrToToHexByte(headHexstring.Substring(0, headHexstring.Length - 4)));
        if (HCS != _inhcs)
        {
            throw new Exception("帧头协议校验出错");
        }
        byte[] _L = BasicMethod.StrToToHexByte(headHexstring.Substring(0, 4));//取L
        Length = ((BasicMethod.Bytes2ToInt(_L) << 2) >> 2) + 2;

        RootElement.Add(new XElement("Object", "", new XAttribute[] { new XAttribute("FrameRegion", "长度L(2BIN)"), new XAttribute("FrameRegionShow", "长度L"), new XAttribute("Data", headHexstring.Substring(0, 4)), new XAttribute("Explain", "长度=" + (Length-2).ToString()+",总长度(长度+2)="+Length.ToString()), new XAttribute("Key", "L"), new XAttribute("Val1", ""), new XAttribute("index", ""), new XAttribute("Type", "byte[2]"), new XAttribute("StartIndex", "2"), new XAttribute("Length", "4"), new XAttribute("ShowFlag", "0"), new XAttribute("Remark", "") }));

        Unit = ((BasicMethod.Bytes2ToInt(_L) & int.Parse(Math.Pow(2, 14).ToString())) == 1) ? LengthUnit.THOUSANDBYTE : LengthUnit.BYTE;
        headHexstring = headHexstring.Substring(4);
        int _C = (int)BasicMethod.StrToToHexByte(headHexstring.Substring(0, 2)).First();
        HexC = headHexstring.Substring(0, 2);
        DIR = (_C & 128) >> 7;
        DIR_Exp = DIR == 0 ? "客户机发出的" : "服务器发出的";
        PRM = (_C & 64) >> 6;
        PRM_Exp = PRM == 0 ? "服务器发起的" : "客户机发起的";
        FZFlag = (_C & 32) >> 5;
        FZFlagExp = FZFlag == 0 ? "完整APDU" : "APDU片段";
        RmSC = (_C & 16) >> 4;
        RmSC_Exp = RmSC == 1 ? "扰码" : "默认";
        RmSC_Exp += "(0:APDU不加33H,默认;1:APDU加33H,扰码)";
        GNM = _C & 7;
        switch (GNM)
        {
            case 1:
                GNM_Exp = "链路连接管理(登录,心跳,退出登录)";
                break;
            case 3:
                GNM_Exp = "应用连接管理及数据交换服务";
                break;
            default:
                GNM_Exp = "";
                break;
        }
        XElement cXelement = new XElement("Object", "", new XAttribute[] { new XAttribute("FrameRegion", "控制域C(1BIN)"), new XAttribute("FrameRegionShow", "控制域C"), new XAttribute("Data", headHexstring.Substring(0, 2)), new XAttribute("Explain", ""), new XAttribute("Key", "C"), new XAttribute("Val1", ""), new XAttribute("index", ""), new XAttribute("Type", "byte"), new XAttribute("StartIndex", "6"), new XAttribute("Length", "2"), new XAttribute("ShowFlag", "0"), new XAttribute("Remark", "") });
        cXelement.Add(new XElement("Object", "", new XAttribute[] { new XAttribute("FrameRegion", "D7传输方向位DIR"), new XAttribute("FrameRegionShow", "传输方向"), new XAttribute("Data", DIR.ToString()), new XAttribute("Explain", DIR_Exp), new XAttribute("Key", "D7"), new XAttribute("Val1", ""), new XAttribute("index", ""), new XAttribute("Type", "bit"), new XAttribute("StartIndex", ""), new XAttribute("Length", ""), new XAttribute("ShowFlag", "0"), new XAttribute("Remark", "") }));
        cXelement.Add(new XElement("Object", "", new XAttribute[] { new XAttribute("FrameRegion", "D6启动标志位PRM"), new XAttribute("FrameRegionShow", "启动标志位PRM"), new XAttribute("Data", PRM.ToString()), new XAttribute("Explain", PRM_Exp), new XAttribute("Key", "D6"), new XAttribute("Val1", ""), new XAttribute("index", ""), new XAttribute("Type", "bit"), new XAttribute("StartIndex", ""), new XAttribute("Length", ""), new XAttribute("ShowFlag", "0"), new XAttribute("Remark", "") }));
        cXelement.Add(new XElement("Object", "", new XAttribute[] { new XAttribute("FrameRegion", "D5分帧标志位"), new XAttribute("FrameRegionShow", "分帧标志位"), new XAttribute("Data", FZFlag.ToString()), new XAttribute("Explain", FZFlagExp), new XAttribute("Key", "D5"), new XAttribute("Val1", ""), new XAttribute("index", ""), new XAttribute("Type", "bit"), new XAttribute("StartIndex", ""), new XAttribute("Length", ""), new XAttribute("ShowFlag", "0"), new XAttribute("Remark", "") }));
        cXelement.Add(new XElement("Object", "", new XAttribute[] { new XAttribute("FrameRegion", "D4保留"), new XAttribute("FrameRegionShow", "保留"), new XAttribute("Data", "0"), new XAttribute("Explain", "保留"), new XAttribute("Key", "D4"), new XAttribute("Val1", ""), new XAttribute("index", ""), new XAttribute("Type", "bit"), new XAttribute("StartIndex", ""), new XAttribute("Length", ""), new XAttribute("ShowFlag", "0"), new XAttribute("Remark", "") }));
        cXelement.Add(new XElement("Object", "", new XAttribute[] { new XAttribute("FrameRegion", "D3扰码标志位SC"), new XAttribute("FrameRegionShow", "扰码标志位"), new XAttribute("Data", RmSC.ToString()), new XAttribute("Explain", RmSC_Exp), new XAttribute("Key", "D3"), new XAttribute("Val1", ""), new XAttribute("index", ""), new XAttribute("Type", "bit"), new XAttribute("StartIndex", ""), new XAttribute("Length", ""), new XAttribute("ShowFlag", "0"), new XAttribute("Remark", "") }));
        cXelement.Add(new XElement("Object", "", new XAttribute[] { new XAttribute("FrameRegion", "D2~D0功能码"), new XAttribute("FrameRegionShow", "功能码"), new XAttribute("Data", GNM.ToString()), new XAttribute("Explain", GNM_Exp), new XAttribute("Key", "D2D0"), new XAttribute("Val1", ""), new XAttribute("index", ""), new XAttribute("Type", "bits"), new XAttribute("StartIndex", ""), new XAttribute("Length", ""), new XAttribute("ShowFlag", "0"), new XAttribute("Remark", "") }));
        RootElement.Add(cXelement);

        headHexstring = headHexstring.Substring(2);
        Sa = new SA(headHexstring.Substring(0, headHexstring.Length - 6));
        headHexstring = headHexstring.Substring(headHexstring.Length - 6);
        Ca = (int)BasicMethod.StrToToHexByte(headHexstring.Substring(0, 2)).First();
        foreach(XElement element in Sa.RootElement)
        {
            RootElement.Add(element);
        }
        RootElement.Add(new XElement("Object", "", new XAttribute[] { new XAttribute("FrameRegion", "客户机地址CA(1BIN)"), new XAttribute("FrameRegionShow", "CA"), new XAttribute("Data", Ca.ToString("X2")), new XAttribute("Explain", "客户机地址="+ Ca.ToString()), new XAttribute("Key", "CA"), new XAttribute("Val1", ""), new XAttribute("index", ""), new XAttribute("Type", "byte"), new XAttribute("StartIndex", "22"), new XAttribute("Length", "2"), new XAttribute("ShowFlag", "0"), new XAttribute("Remark", "") }));
        RootElement.Add(new XElement("Object", "", new XAttribute[] { new XAttribute("FrameRegion", "帧头校验HCS(2BIN)"), new XAttribute("FrameRegionShow", "帧头校验"), new XAttribute("Data", HCS), new XAttribute("Explain", "帧头校验正确"), new XAttribute("Key", "HCS"), new XAttribute("Val1", ""), new XAttribute("index", ""), new XAttribute("Type", "byte[2]"), new XAttribute("StartIndex", "24"), new XAttribute("Length", "4"), new XAttribute("ShowFlag", "0"), new XAttribute("Remark", "") }));
    }
    catch (Exception ex)
    {
        throw ex;
    }
}
View Code

 对于截取的SA字符串,我们传入sa对象中进行解析(实际上我们传递给SA的这个字符串我们没有去解析具体sa的长度,而是利用帧头减去后面的CA和校验位得到剩余的默认为sa)

public SA(string saHexstring)
{
    RootElement = new List<XElement>();
    AddrAttribute = (int)BasicMethod.StrToToHexByte(saHexstring.Substring(0, 2)).First();
    AdrType = (AddrAttribute & (128 + 64)) >> 6;
    switch(AdrType)
    {
        case 0:
            AdrType_Exp = "单地址";
            break;
        case 1:
            AdrType_Exp = "通配地址";
            break;
        case 2:
            AdrType_Exp = "组地址";
            break;
        case 3:
            AdrType_Exp = "广播地址";
            break;
        default:
            AdrType_Exp = "未知地址类型";
            break;
    }
    LocAdr = (AddrAttribute & (32 + 16)) >> 4;
    AdrLength = (AddrAttribute & 15)+1;

    XElement saTypeXelement = new XElement("Object", "", new XAttribute[] { new XAttribute("FrameRegion", "服务器地址SA标志(1BIN)"), new XAttribute("FrameRegionShow", "服务器地址"),  new XAttribute("Data", saHexstring.Substring(0, 2)), new XAttribute("Explain", ""), new XAttribute("Key", "SATYPE"), new XAttribute("Val1", ""), new XAttribute("index", ""), new XAttribute("Type", "byte"), new XAttribute("StartIndex", "8"), new XAttribute("Length", "2"), new XAttribute("ShowFlag", "0"), new XAttribute("Remark", "") });
    saTypeXelement.Add(new XElement("Object", "", new XAttribute[] { new XAttribute("FrameRegion", "D7~D6地址类型"), new XAttribute("FrameRegionShow", "地址类型"),  new XAttribute("Data", BasicMethod.ByteToBit(BasicMethod.StrToToHexByte(saHexstring.Substring(0, 2)).First(),0,2)), new XAttribute("Explain", AdrType_Exp), new XAttribute("Key", "D7D6"), new XAttribute("Val1", ""), new XAttribute("index", ""), new XAttribute("Type", "bits"), new XAttribute("StartIndex", ""), new XAttribute("Length", ""), new XAttribute("ShowFlag", "0"), new XAttribute("Remark", "") }));
    saTypeXelement.Add(new XElement("Object", "", new XAttribute[] { new XAttribute("FrameRegion", "D5~D4地址类型"), new XAttribute("FrameRegionShow", "逻辑地址"),  new XAttribute("Data", BasicMethod.ByteToBit(BasicMethod.StrToToHexByte(saHexstring.Substring(0, 2)).First(), 2, 2)), new XAttribute("Explain","bit5="+Bit5.ToString()+(Bit5==0?"无扩展逻辑地址": "有扩展逻辑地址")+" 逻辑地址(D4)="+Bit4.ToString()), new XAttribute("Key", "D5D4"), new XAttribute("Val1", ""), new XAttribute("index", ""), new XAttribute("Type", "bits"), new XAttribute("StartIndex", ""), new XAttribute("Length", ""), new XAttribute("ShowFlag", "0"), new XAttribute("Remark", "") }));
    saTypeXelement.Add(new XElement("Object", "", new XAttribute[] { new XAttribute("FrameRegion", "D3~D0地址长度"), new XAttribute("FrameRegionShow", "地址长度"),  new XAttribute("Data", BasicMethod.ByteToBit(BasicMethod.StrToToHexByte(saHexstring.Substring(0, 2)).First(), 4, 4)), new XAttribute("Explain", AdrLength.ToString()), new XAttribute("Key", "D3D0"), new XAttribute("Val1", ""), new XAttribute("index", ""), new XAttribute("Type", "bits"), new XAttribute("StartIndex", ""), new XAttribute("Length", ""), new XAttribute("ShowFlag", "0"), new XAttribute("Remark", "") }));
    RootElement.Add(saTypeXelement);

    saHexstring = saHexstring.Substring(2);
    if (Bit5 == 1)
    {
        AdrExpand = BasicMethod.StrToToHexByte(saHexstring.Substring(0, 2)).First();
        
        XElement saAdrexpXelement = new XElement("Object", "", new XAttribute[] { new XAttribute("FrameRegion", "扩展逻辑地址"), new XAttribute("FrameRegionShow", "扩展逻辑地址"), new XAttribute("Data", saHexstring.Substring(0, 2)), new XAttribute("Explain", AdrExpand), new XAttribute("Key", "AdrExp"), new XAttribute("Val1", ""), new XAttribute("index", ""), new XAttribute("Type", ""), new XAttribute("StartIndex", "10"), new XAttribute("Length", "2"), new XAttribute("ShowFlag", "0"), new XAttribute("Remark", "") });
        RootElement.Add(saAdrexpXelement);
        saHexstring = saHexstring.Substring(2);
    }
    Address = saHexstring;
    XElement saAdreXelement = new XElement("Object", "", new XAttribute[] { new XAttribute("FrameRegion", "服务器地址SA(6BIN)"), new XAttribute("FrameRegionShow", "服务器地址"),  new XAttribute("Data", Address), new XAttribute("Explain", BasicMethod.Reserved(Address)), new XAttribute("Key", "SA"), new XAttribute("Val1", ""), new XAttribute("index", ""), new XAttribute("Type", "byte[6]"), new XAttribute("StartIndex", "10"), new XAttribute("Length", "12"), new XAttribute("ShowFlag", "0"), new XAttribute("Remark","") });
    RootElement.Add(saAdreXelement);
}
View Code

帧头解析结束后,我们需要根据帧头中分帧标志判断是否需要独立解析分帧格式域,根据分帧格式域决定是否解析分帧信息。若解析出来分帧信息,缓存当前分帧直到分帧结束,然后组合分帧,后面就是如何解析消息体。

下一章我们来说如何解析消息体吧。顺道把代码也分享了。