校验和用于二进制PLC通信

时间:2013-04-09 23:22:49

标签: c++ python hex checksum plc

我一直在考虑使用二进制命令计算校验和与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+字节的详细信息,得到sum的值为65536,然后返回该值的两个补码。

尝试#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 个答案:

答案 0 :(得分:2)

我不完全确定正确的代码应该是什么......但是如果Checksum(Header)的意图是返回f705,并且它返回08fb,那就是问题所在:

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

简短版本就是你想要的:

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

这里的问题不是~意味着不同的东西。正如the documentation所说,~x返回“x倒”的位,这实际上与C中的含义相同(至少在2s补码平台上,包括所有Windows平台) )。

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

问题只在于如何将结果转换为字符串。

在两个版本中,调用Checksum(Header)(格式化)的结果为-2299或-0x08fb。

在C中,您几乎可以将有符号整数视为相同大小的无符号整数(尽管在某些情况下可能必须忽略警告)。具体取决于您的平台,但在2s补码平台上,带符号的短-0x08fb是无符号的0xf705的比特。因此,例如,如果你执行sprintf(buf, "%04hx", -0x08fb),它就可以正常工作 - 它会为你(在大多数平台上,包括所有Windows)提供无符号的等价物f705

但在Python中,没有无符号整数。 int -0x08fb与0xf705无关。如果你"%04hx" % -0x08fb -8fb,你将获得hex(-0x08fb),并且无法强行“将其转换为无符号”或类似的内容。

您的代码实际上是-0x8fb,它会为您split提供x 8fbzfill给您08fb,您% 0x10000 1}}到"%04hx" % (-0x08fb % 0x10000),这使问题有点难以注意(因为它看起来像一对完全有效的十六进制字节,而不是减号和三个十六进制数字),但它是同样的问题。 / p>

无论如何,你必须明确地决定“unsigned equivalent”的意思,并编写代码来做到这一点。由于您尝试匹配C在2s补码平台上的作用,因此您可以将该显式转换编写为f705。如果您执行{{1}},则会获得{{1}},就像在C中一样。同样适用于现有代码。

答案 1 :(得分:2)

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

现在,我实际上并不知道python,但它看起来像是在添加半字节值。你已经制作了这样的标题字符串:

Header = '2F5F4F504C4300FE010000004D000000000001000600'

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

在算法结束时,如果您不信任它,则不需要执行~运算符。相反,你可以做(0xffff - (x % 0x10000)) + 1。请记住,在添加1之前,该值实际上可能是0xffff,因此您需要在0x10000之后对整个结果进行模数化。您朋友的C ++版本使用short数据类型,因此根本不需要模数,因为short将自然溢出