代码之家  ›  专栏  ›  技术社区  ›  abestic9

二进制PLC通信的校验和

  •  2
  • abestic9  · 技术社区  · 11 年前

    我一直在绞尽脑汁计算校验和,以便使用二进制命令与Unitronics PLC通信。他们提供了源代码,但它是在一个仅限Windows的C#实现中实现的,这对我来说除了基本语法之外几乎没有帮助。

    Specification PDF (校验和计算接近尾声)

    C# driver source (Utils.cs中的校验和计算)

    预期结果

    下面是字节索引、消息描述和有效的示例。

    #  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 24 25 26 27 28 29 | 30 31 32
    # sx--------------- id FE 01 00 00 00 cn 00 specific--------- lengt CHKSM | numbr ot FF addr- | CHKSM ex
    # 2F 5F 4F 50 4C 43 00 FE 01 00 00 00 4D 00 00 00 00 00 01 00 06 00 F1 FC | 01 00 01 FF 01 00 | FE FE 5C
    

    该规范要求计算22字节消息头的累加值,并分别计算6+字节的细节,得到模65536的和的值,然后返回该值的2的补码。

    尝试#1

    我的理解是Python中的波浪号(~)运算符是直接从C/C++派生而来的。在编写了创建消息的Python一天后,我想到了这个(精简版):

    #!/usr/bin/env python
    
    def Checksum( s ):
        x = ( int( s, 16 ) ) % 0x10000
        x = ( ~x ) + 1 
        return hex( x ).split( 'x' )[1].zfill( 4 )
    
    Details = ''
    Footer  = ''
    Header  = ''
    Message = ''
    
    Details += '0x010001FF0100'
    
    Header += '0x2F5F4F504C4300FE010000004D000000000001000600'
    Header += Checksum( Header )
    
    Footer += Checksum( Details )
    Footer += '5C'
    
    Message +=  Header.split( 'x' )[1].zfill( 4 )
    Message += Details.split( 'x' )[1].zfill( 4 )
    Message +=  Footer
    
    print Message
    

    消息: 2F5F4F504C4300FE010000004D000000000001000600600L010001FF010001005C

    我在那里看到了一个L,这是一个与昨天不同的结果,昨天并没有更接近。如果您希望根据消息的其余部分快速得出公式结果: 校验和(标头)应返回F1FC,校验和(详细信息)应返回FEFE .

    它返回的值与规范的示例相去甚远。我认为问题可能是一两件事:Checksum方法没有正确计算十六进制字符串的和,或者Python ~ 运算符不等价于C++ ~ 操作人员

    尝试#2

    一位朋友给了我他对计算应该是什么的C++解释,我就是无法理解这段代码,我的C++知识很少。

    short PlcBinarySpec::CalcHeaderChecksum( std::vector<byte>  _header ) {
        short bytesum = 0;
        for ( std::vector<byte>::iterator it = _header.begin(); it != _header.end(); ++it ) {
            bytesum = bytesum + ( *it );
        }
        return ( ~( bytesum % 0x10000 ) ) + 1;
    }
    
    2 回复  |  直到 11 年前
        1
  •  2
  •   abarnert    11 年前

    我不完全确定正确的代码应该是什么,但如果目的是 Checksum(Header) 返回f705,返回08fb,问题是:

    x = ( ~( x % 0x10000 ) ) + 1
    

    简短的版本是您想要的:

    x = (( ~( x % 0x10000 ) ) + 1) % 0x10000
    

    问题不在于 ~ 意思不同。像 the documentation 说, ~x 返回“ x inverted”,这实际上与它在C中的含义相同(至少在2s补充平台上,包括所有Windows平台)。

    在这里,您可能会遇到C和Python类型之间的差异问题(C积分类型是固定大小的,并且溢出;Python积分类型实际上是无限大小的,并根据需要增长)。但我认为这不是你的问题。

    问题只是如何将结果转换为字符串的问题。

    调用的结果 校验和(表头) ,直到格式化,在这两个版本中都是-2299或0x08fb。

    在C中,您几乎可以将有符号整数视为大小相同的无符号整数(尽管在某些情况下,您可能不得不忽略警告)。具体作用取决于您的平台,但在2s补码平台上,有符号短0x08fb相当于无符号0xf705。例如,如果你这样做了 sprintf(buf, "%04hx", -0x08fb) ,它工作得很好,它给你(在大多数平台上,包括Windows的所有平台上)提供了未签名的等价物, f705 .

    但在Python中,没有无符号整数。int-0x08fb与0xf705无关。如果你这样做了 "%04hx" % -0x08fb ,你会得到 -8fb ,并且没有办法强制“将其强制转换为未签名”或类似的东西。

    你的代码确实是这样 hex(-0x08fb) ,这给了你 -0x8fb ,然后你 split x ,给你 8fb ,你 zfill 08fb ,这使得这个问题更难被注意到(因为这看起来像是一对完全有效的十六进制字节,而不是一个减号和三个十六进制数字),但这也是同样的问题。

    无论如何,您必须明确决定“无符号等价”的含义,并编写代码来实现这一点。由于您试图匹配C在2s补码平台上的功能,因此可以将显式转换写成 % 0x10000 。如果你这样做了 "%04hx" % (-0x08fb % 0x10000) ,你会得到 功能705 ,就像您在C中所做的那样。对于您现有的代码也是如此。

        2
  •  2
  •   paddy    11 年前

    这很简单。我通过在计算器上手动添加所有头字节来检查你朋友的算法,结果是正确的( 0xfcf1 ).

    现在,我实际上并不了解python,但在我看来,它就像是在加半字节的值。您的标题字符串是这样的:

    Header = '2F5F4F504C4300FE010000004D000000000001000600'
    

    然后,你将该字符串中的每个字节从十六进制转换并相加。这意味着你要处理从0到15的值。您需要将每两个字节视为一对,并将其转换(值从0到255)。或者您需要使用实际的二进制数据,而不是二进制数据的文本表示。

    在算法结束时,您实际上不需要执行 ~ 操作员,如果你不信任它。相反,你可以 (0xffff - (x % 0x10000)) + 1 请记住,在加1之前,实际值可能为 0xffff ,所以您需要将整个结果取模 0x10000 之后您朋友的C++版本使用 short 数据类型,所以根本不需要模,因为 短的 会自然溢出