我一直在努力计算使用二进制命令与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的值,然后返回该值的二进制补码。
尝试#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,这与昨天的结果不同,直到现在为止。如果您需要基于消息其余部分的快速公式结果: Checksum(Header)应该返回F1FC,而Checksum(Details)应该返回FEFE 。
它返回的值与规范的示例几乎没有相同。我认为问题可能是一两件事:Checksum方法无法正确计算十六进制字符串的总和,或者Python
~
运算符不等同于C++ ~
运算符。尝试#2
一位 friend 给了我有关计算的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;
}
最佳答案
我不确定要正确的代码是什么……但是如果打算让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
,从而给您8fb
,然后再将zfill
转换为08fb
,这使问题更难以发现(因为它看起来像一对完美有效的十六进制字节) ,而不是减号和三个十六进制数字),但这是相同的问题。无论如何,您必须明确确定“无符号等效项”的含义,并编写代码来做到这一点。由于您尝试匹配C在2s补码平台上的功能,因此可以将该显式转换编写为
% 0x10000
。如果执行"%04hx" % (-0x08fb % 0x10000)
,则将获得f705
,就像在C中所做的一样。对于现有代码也是如此。